diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index c97aac3..5f8c42d 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -12,14 +12,17 @@ import ( // assembleARM64 assembles an AArch64 (arm64) TEXT function body into machine // code. Every instruction is 4 bytes; the MOV pseudo-instruction and the -// immediate-arithmetic forms expand to 2–4 instructions when the immediate +// immediate-arithmetic forms expand to 2-4 instructions when the immediate // does not fit, so the layout is computed in two passes (sizes, then encoding // with resolved branch targets). // // The emitted bytes match the Go toolchain's arm64 assembler, which is the // ground-truth oracle: prologue/epilogue, FP/SP frame mapping, branch // encodings and the MOV immediate expansions all follow cmd/internal/obj/ -// arm64's asmout cases. +// arm64's asmout cases. One deliberate difference: the stack-growth guard +// (the morestack check in the prologue and the call back into the runtime in +// the epilogue) is not emitted, so the bytes match only for NOSPLIT functions +// or zero-frame leaves, where the toolchain emits no guard either. func assembleARM64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) { fi := arm64ComputeFrame(t) prologue := arm64Prologue(fi) @@ -67,7 +70,12 @@ func assembleARM64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [ return nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", in.Mnemonic.Text, err) } for j := preCount; j < len(relocs); j++ { - relocs[j].Off += pc - len(prologue) + // Make the relocation offsets function-relative: each instruction + // records its reloc offset relative to its own start, and pc is + // that instruction's offset from the function start (prologue + // included). After shifts by the same amount. + relocs[j].Off += pc + relocs[j].After += pc } preCount = len(relocs) lines = append(lines, LineEntry{Offset: pc, Line: in.Pos().Line}) @@ -462,7 +470,7 @@ func encodeARM64AddSubImm(mnem string, ops []*ast.Operand) ([]byte, error) { // ---- MOV pseudo-instruction ---- -// encodeARM64Mov encodes the MOV family — the load/store/immediate workhorse +// encodeARM64Mov encodes the MOV family, the load/store/immediate workhorse // of Go's arm64 assembly. MOV is an alias of MOVD (the width mnemonics // select the access width). The forms, mirroring the toolchain: // @@ -839,7 +847,9 @@ func encodeARM64SBAddr(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte { ) } -// encodeARM64SBLoad emits ADRP R20, 0; LDR Rd, [R20, 0] with relocations. +// encodeARM64SBLoad emits ADRP R27, 0; LDR Rd, [R27, 0] with relocations, +// matching the toolchain: the scratch register is REGTMP (R27) and the pair +// carries R_ARM64_PCREL_LDST64. func encodeARM64SBLoad(sym *ast.Symbol, rd int, mnem string, relocs *[]Reloc) ([]byte, error) { lt, ok := a64LoadTable[mnem] if !ok { @@ -847,17 +857,17 @@ func encodeARM64SBLoad(sym *ast.Symbol, rd int, mnem string, relocs *[]Reloc) ([ } if relocs != nil { *relocs = append(*relocs, - Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelArm64Addr, Addend: sym.Offset}, - Reloc{Off: 4, After: 4, Name: sym.Name, Kind: RelArm64Addr, Addend: sym.Offset}, + Reloc{Off: 0, After: 8, Name: sym.Name, Kind: RelArm64LDST64, Addend: sym.Offset}, ) } return a64WordsLE( - a64ADR(1, 0, 0, 20), // ADRP R20, 0 - a64LSU(uint32(lt.size), uint32(lt.V), uint32(lt.opc), 0, 20, uint32(rd)), // LDR Rd, [R20, #0] + a64ADR(1, 0, 0, 27), // ADRP R27, 0 + a64LSU(uint32(lt.size), uint32(lt.V), uint32(lt.opc), 0, 27, uint32(rd)), // LDR Rd, [R27, #0] ), nil } -// encodeARM64SBStore emits ADRP R20, 0; STR Rs, [R20, 0] with relocations. +// encodeARM64SBStore emits ADRP R27, 0; STR Rs, [R27, 0] with relocations, +// matching the toolchain's R27 scratch and R_ARM64_PCREL_LDST64 pair. func encodeARM64SBStore(sym *ast.Symbol, rs int, mnem string, relocs *[]Reloc) ([]byte, error) { lt, ok := a64LoadTable[mnem] if !ok { @@ -866,13 +876,12 @@ func encodeARM64SBStore(sym *ast.Symbol, rs int, mnem string, relocs *[]Reloc) ( storeOpc := a64StoreOpc(lt) if relocs != nil { *relocs = append(*relocs, - Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelArm64Addr, Addend: sym.Offset}, - Reloc{Off: 4, After: 4, Name: sym.Name, Kind: RelArm64Addr, Addend: sym.Offset}, + Reloc{Off: 0, After: 8, Name: sym.Name, Kind: RelArm64LDST64, Addend: sym.Offset}, ) } return a64WordsLE( - a64ADR(1, 0, 0, 20), // ADRP R20, 0 - a64LSU(uint32(lt.size), uint32(lt.V), uint32(storeOpc), 0, 20, uint32(rs)), // STR Rs, [R20, #0] + a64ADR(1, 0, 0, 27), // ADRP R27, 0 + a64LSU(uint32(lt.size), uint32(lt.V), uint32(storeOpc), 0, 27, uint32(rs)), // STR Rs, [R27, #0] ), nil } @@ -1092,7 +1101,7 @@ func encodeARM64CSEL(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er return a64wordLE(baseOp | uint32(rn)<<16 | invCond<<12 | uint32(rn)<<5 | uint32(rd)), nil } - // CSEL cond, Rn, Rm, Rd (4 operands) — condition first. + // CSEL cond, Rn, Rm, Rd (4 operands), condition first. // Go assembler syntax: CSEL cond, Rn, Rm, Rd // ARM64 encoding: Rm in bits[20:16], Rn in bits[9:5], Rd in bits[4:0]. if len(ops) != 4 { diff --git a/asm/arm64_reloc_test.go b/asm/arm64_reloc_test.go new file mode 100644 index 0000000..f994207 --- /dev/null +++ b/asm/arm64_reloc_test.go @@ -0,0 +1,123 @@ +// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) +// SPDX-License-Identifier: BSD-3-Clause + +package asm + +import ( + "encoding/binary" + "testing" + + "sourcedock.dev/petrbalvin/gasm-devkit/parser" +) + +// parseArm64File is a helper assembling one arm64 source file. +func parseArm64File(t *testing.T, src string) *Image { + t.Helper() + f, errs := parser.Parse("k_arm64.s", src) + if len(errs) > 0 { + t.Fatalf("parse: %v", errs) + } + img, err := AssembleFileARM64(f) + if err != nil { + t.Fatalf("assemble: %v", err) + } + return img +} + +// TestArm64RelocOffsetsIncludePrologue pins the function-relative relocation +// offsets of a framed function: the offsets used to exclude the prologue, so +// every relocation landed on a prologue instruction in the GOOBJ/ELF output. +func TestArm64RelocOffsetsIncludePrologue(t *testing.T) { + img := parseArm64File(t, "TEXT \u00b7f(SB), $16-0\n"+ + "\tBL ext\u00b7foo(SB)\n"+ + "\tMOVD $gdata(SB), R5\n"+ + "\tMOVD $extsym(SB), R6\n"+ + "\tRET\n"+ + "GLOBL gdata(SB), $8\n") + fn := img.Funcs[0] + + // Layout: 12-byte prologue, BL (12), ADRP+ADD (16, 20), ADRP+ADD (24, 28), + // 12-byte epilogue with RET. + want := []struct { + off int + after int + name string + kind RelocKind + external bool + }{ + {12, 16, "foo", RelArm64Branch, true}, + {16, 16, "gdata", RelArm64Addr, false}, + {20, 20, "gdata", RelArm64Addr, false}, + {24, 24, "extsym", RelArm64Addr, true}, + {28, 28, "extsym", RelArm64Addr, true}, + } + if len(fn.Relocs) != len(want) { + t.Fatalf("relocs = %d, want %d", len(fn.Relocs), len(want)) + } + for i, w := range want { + r := fn.Relocs[i] + if r.Off != w.off || r.After != w.after || r.Name != w.name || r.Kind != w.kind || r.External != w.external { + t.Errorf("reloc %d = {off %d after %d name %q kind %d ext %v}, want {off %d after %d name %q kind %d ext %v}", + i, r.Off, r.After, r.Name, r.Kind, r.External, w.off, w.after, w.name, w.kind, w.external) + } + } + + // The BL with a zero offset sits exactly at the first reloc site. + code := img.Code[fn.Offset : fn.Offset+fn.Size] + if w := binary.LittleEndian.Uint32(code[12:16]); w != 0x94000000 { + t.Errorf("BL word = %08x, want 94000000", w) + } +} + +// TestArm64SBLoadStoreMatchesToolchain pins the ADRP scratch register +// (REGTMP, R27) and the LDST64 relocation kind for sym loads and stores, +// against the bytes go tool asm emits for MOVD sym(SB), R5. +func TestArm64SBLoadStoreMatchesToolchain(t *testing.T) { + img := parseArm64File(t, "TEXT \u00b7ld(SB), NOSPLIT, $0\n"+ + "\tMOVD sym(SB), R5\n"+ + "\tMOVD R5, sym(SB)\n"+ + "\tRET\n"+ + "GLOBL sym(SB), $8\n") + fn := img.Funcs[0] + code := img.Code[fn.Offset : fn.Offset+fn.Size] + + // go tool asm: ADRP 0(PC), R27 (9000001b); MOVD (R27), R5 (f9400365); + // ADRP 0(PC), R27; MOVD R5, (R27) (f9000365). + for off, want := range map[int]uint32{0: 0x9000001b, 4: 0xf9400365, 8: 0x9000001b, 12: 0xf9000365} { + if got := binary.LittleEndian.Uint32(code[off : off+4]); got != want { + t.Errorf("word at %d = %08x, want %08x", off, got, want) + } + } + + if len(fn.Relocs) != 2 { + t.Fatalf("relocs = %d, want 2", len(fn.Relocs)) + } + for i, w := range []struct{ off, after int }{{0, 8}, {8, 16}} { + r := fn.Relocs[i] + if r.Kind != RelArm64LDST64 { + t.Errorf("reloc %d kind = %d, want RelArm64LDST64 (%d)", i, r.Kind, RelArm64LDST64) + } + if r.Off != w.off || r.After != w.after { + t.Errorf("reloc %d = {off %d after %d}, want {off %d after %d}", i, r.Off, r.After, w.off, w.after) + } + } +} + +// TestArm64GOObjRelocTypes checks that GOOBJ emission succeeds with the new +// relocation kinds in play; the detailed layout is covered by the goobj tests. +func TestArm64GOObjRelocTypes(t *testing.T) { + img := parseArm64File(t, "TEXT \u00b7ld(SB), NOSPLIT, $0\n"+ + "\tMOVD sym(SB), R5\n"+ + "\tMOVD R5, sym(SB)\n"+ + "\tRET\n"+ + "GLOBL sym(SB), $8\n") + obj, err := img.GOObjectAARCH64("testpkg", "k_arm64.s") + if err != nil { + t.Fatalf("GOObjectAARCH64: %v", err) + } + if len(obj) == 0 { + t.Fatal("empty object") + } + // The detailed layout is covered by the goobj tests; here we only pin + // that emission succeeds with the new relocation kinds in play. +} diff --git a/asm/elfarm64.go b/asm/elfarm64.go index aeddb19..733d94e 100644 --- a/asm/elfarm64.go +++ b/asm/elfarm64.go @@ -15,8 +15,9 @@ const ( // AArch64 relocation types (the ELF psABI). rArm64PrelPgHi21 = 275 // R_AARCH64_ADR_PREL_PG_HI21 (ADRP page) - rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD/STR/LDR page offset) + rArm64AddAbsLo12NC = 277 // R_AARCH64_ADD_ABS_LO12_NC (ADD page offset) rArm64Call26 = 283 // R_AARCH64_CALL26 (BL instruction) + rArm64Ldst64Lo12NC = 286 // R_AARCH64_LDST64_ABS_LO12_NC (64-bit LDR/STR page offset) ) // ELFAARCH64Object returns the image as an ELF64 relocatable object file for @@ -81,7 +82,13 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { // Build relocations. Each SB reference is an ADRP pair: // ADRP Rd, 0 → R_AARCH64_ADR_PREL_PG_HI21 - // ADD/LDR/STR → R_AARCH64_ADD_ABS_LO12_NC + // ADD → R_AARCH64_ADD_ABS_LO12_NC + // LDR/STR X → R_AARCH64_LDST64_ABS_LO12_NC + // BL → R_AARCH64_CALL26 + // Addends stay raw: ADR_PREL_PG_HI21 and the ABS_LO12_NC forms resolve + // against S+A, and CALL26 branches take the branch instruction's own + // place as the PC-relative base, so subtracting the field width (the + // amd64 R_PCREL convention) would misplace every branch by 4 bytes. type elfRela struct { off uint64 typ uint32 @@ -99,6 +106,8 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { switch { case r.Kind == RelArm64Branch: typ = rArm64Call26 + case r.Kind == RelArm64LDST64 && r.Off%4 == 4: + typ = rArm64Ldst64Lo12NC case r.Kind == RelArm64Addr && r.Off%4 == 4: typ = rArm64AddAbsLo12NC default: @@ -108,7 +117,7 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { off: uint64(fn.Offset + r.Off), typ: typ, sym: idx, - addend: r.Addend - int64(r.After-r.Off), + addend: r.Addend, }) } } diff --git a/asm/elfloong64.go b/asm/elfloong64.go index 501ed08..a1dd0df 100644 --- a/asm/elfloong64.go +++ b/asm/elfloong64.go @@ -102,7 +102,7 @@ func (img *Image) ELFLOONG64Object() ([]byte, error) { off: uint64(fn.Offset + r.Off), typ: typ, sym: idx, - addend: r.Addend - int64(r.After-r.Off), + addend: r.Addend, }) } } diff --git a/asm/goobjarm64.go b/asm/goobjarm64.go index 2d6e361..2a6e044 100644 --- a/asm/goobjarm64.go +++ b/asm/goobjarm64.go @@ -13,28 +13,33 @@ import ( ) // GOObjectAARCH64 emits a GOOBJ object file for AArch64. The layout is -// the shared one in goobj.go — the toolchain preamble, the go120ld header +// the shared one in goobj.go, the toolchain preamble, the go120ld header // with its block offsets, the string table, the symbol definitions and the -// reloc/aux/data index arrays — with the arm64 preamble, the MinLC of 4 -// for the pc-value deltas, and R_ADDRARM64 relocation types for the -// ADRP+ADD/LDR/STR address pairs. +// reloc/aux/data index arrays, with the arm64 preamble, the MinLC of 4 +// for the pc-value deltas, and the arm64 relocation types for the ADRP +// pairs and BL calls. func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) { pre, err := toolchainObjectPreambleAARCH64() if err != nil { return nil, err } return img.emitGOObject(pkgPath, srcPath, pre, 4, func(r Reloc) (uint16, uint8) { - if r.Kind == RelArm64Branch { + switch r.Kind { + case RelArm64Branch: return relocArm64Branch, 4 + case RelArm64LDST64: + return relocArm64LDST64, 4 + default: + return relocArm64Addr, 4 } - return relocArm64Addr, 4 }) } // arm64 relocation types (cmd/internal/objabi). const ( - relocArm64Addr = 3 // R_ADDRARM64 — ADRP+ADD/LDR/STR pair - relocArm64Branch = 9 // R_CALLARM64 — BL instruction + relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair + relocArm64Branch = 9 // R_CALLARM64, BL instruction + relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair ) // toolchainObjectPreambleAARCH64 returns the "go object ...\n!\n" header diff --git a/asm/link.go b/asm/link.go index d4871a8..9e3d047 100644 --- a/asm/link.go +++ b/asm/link.go @@ -6,6 +6,7 @@ package asm import ( "fmt" "sort" + "strconv" "sourcedock.dev/petrbalvin/gasm-devkit/ast" ) @@ -15,7 +16,7 @@ import ( // file-local static symbols are encoded RIP-relative and resolved within the // image, so the raw bytes are self-consistent and executable at any base // address; references to external symbols are recorded as relocations -// (Funcs[i].Relocs, Externals) and left unresolved — the object-file +// (Funcs[i].Relocs, Externals) and left unresolved, the object-file // emitters turn them into linker relocations. type Image struct { Code []byte // concatenated function bodies @@ -96,8 +97,9 @@ const ( RelPCRelAbs // 32-bit absolute (R_RISCV_32) RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i) RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st) - RelArm64Addr // R_ADDRARM64 (ADRP + ADD/LDR/STR pair) + RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair) RelArm64Branch // R_CALLARM64 (BL instruction) + RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair) ) type Reloc struct { @@ -132,7 +134,7 @@ func (img *Image) Bytes() []byte { // reference to a file-local static symbol becomes a RIP-relative load whose // displacement is resolved against that layout; a reference to a symbol no // GLOBL defines is recorded as an external relocation (Externals) with its -// displacement left zero — the object-file emitters resolve it at link +// displacement left zero, the object-file emitters resolve it at link // time, while the raw image (Bytes) cannot represent it. func AssembleFile(f *ast.File) (*Image, error) { dataSyms, err := collectData(f) @@ -451,13 +453,18 @@ func collectData(f *ast.File) ([]dataSym, error) { ds.rodata = true case "DUPOK": ds.dupok = true - case "1": - ds.dupok = true - case "8": - ds.rodata = true - case "9": - ds.dupok = true - ds.rodata = true + default: + // Legacy numeric flag constants (runtime/textflag.h): + // DUPOK is 2, RODATA is 8; combinations arrive as one + // number (e.g. 10 = RODATA|DUPOK). + if n, err := strconv.Atoi(f); err == nil { + if n&2 != 0 { + ds.dupok = true + } + if n&8 != 0 { + ds.rodata = true + } + } } } syms = append(syms, ds) diff --git a/asm/link_test.go b/asm/link_test.go index 03d4553..af49841 100644 --- a/asm/link_test.go +++ b/asm/link_test.go @@ -128,3 +128,41 @@ DATA x<>+0(SB)/4, $1 t.Errorf("single-function SB: error %v, want a file-level-assembly error", err) } } + +// TestCollectDataNumericFlags pins the numeric GLOBL flag constants from +// runtime/textflag.h: DUPOK is 2, RODATA is 8, and combinations arrive as +// one number (9 = NOPROF|RODATA, 10 = RODATA|DUPOK). +func TestCollectDataNumericFlags(t *testing.T) { + tests := []struct { + flags string + rodata bool + dupok bool + }{ + {"2", false, true}, + {"8", true, false}, + {"9", true, false}, // NOPROF|RODATA, not DUPOK + {"10", true, true}, // RODATA|DUPOK + {"RODATA", true, false}, + {"DUPOK", false, true}, + {"RODATA|DUPOK", true, true}, + } + for _, tt := range tests { + src := "TEXT \u00b7f(SB), NOSPLIT, $0\n\tRET\nGLOBL sym(SB), " + tt.flags + ", $8\n" + f, errs := parser.Parse("f_amd64.s", src) + if len(errs) > 0 { + t.Fatalf("parse %q: %v", tt.flags, errs) + } + img, err := AssembleFile(f) + if err != nil { + t.Fatalf("assemble %q: %v", tt.flags, err) + } + if len(img.DataSyms) != 1 { + t.Fatalf("%q: data syms = %d, want 1", tt.flags, len(img.DataSyms)) + } + d := img.DataSyms[0] + if d.Rodata != tt.rodata || d.Dupok != tt.dupok { + t.Errorf("flags %q: rodata=%v dupok=%v, want rodata=%v dupok=%v", + tt.flags, d.Rodata, d.Dupok, tt.rodata, tt.dupok) + } + } +} diff --git a/asm/loong64_assemble.go b/asm/loong64_assemble.go index c864c03..6afe475 100644 --- a/asm/loong64_assemble.go +++ b/asm/loong64_assemble.go @@ -13,14 +13,17 @@ import ( // assembleLOONG64 assembles a LoongArch (loong64) TEXT function body into // machine code. Every instruction is 4 bytes; the MOV pseudo-instruction and -// the immediate-arithmetic forms expand to 2–5 instructions when the +// the immediate-arithmetic forms expand to 2-5 instructions when the // immediate does not fit, so the layout is computed in two passes (sizes, // then encoding with resolved branch targets). // // The emitted bytes match the Go toolchain's loong64 assembler, which is the // ground-truth oracle: prologue/epilogue, FP/SP frame mapping, branch // encodings and the MOV immediate expansions all follow cmd/internal/obj/ -// loong64's asmout cases. +// loong64's asmout cases. One deliberate difference: the stack-growth guard +// (the morestack check in the prologue and the call back into the runtime in +// the epilogue) is not emitted, so the bytes match only for NOSPLIT functions +// or zero-frame leaves, where the toolchain emits no guard either. func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) { fi := loong64ComputeFrame(t) prologue := loong64Prologue(fi) @@ -69,7 +72,12 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, return nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", in.Mnemonic.Text, err) } for j := preCount; j < len(relocs); j++ { - relocs[j].Off += pc - len(prologue) + // Make the relocation offsets function-relative: each instruction + // records its reloc offset relative to its own start, and pc is + // that instruction's offset from the function start (prologue + // included). After shifts by the same amount. + relocs[j].Off += pc + relocs[j].After += pc } preCount = len(relocs) lines = append(lines, LineEntry{Offset: pc, Line: in.Pos().Line}) @@ -417,7 +425,7 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo case l64Firrr: // ALSL: INSTR $sa, rj, rk, rd (the toolchain's optab places rj in - // the second register position); the source amount is 1–4, encoded + // the second register position); the source amount is 1-4, encoded // as sa-1. if len(ops) != 4 { return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops)) @@ -578,8 +586,8 @@ func encodeLOONG64Branch16(mnem string, op uint32, ops []*ast.Operand, pc int, o // encodeLOONG64Branch21 encodes a single-register branch: BLTZ/BGEZ and // BFPT/BFPF use the 21-bit offset form (register in the rj field), while -// BGTZ/BLEZ — which the toolchain encodes with the register in the rd field -// and a 16-bit offset — are handled separately. +// BGTZ/BLEZ, which the toolchain encodes with the register in the rd field +// and a 16-bit offset, are handled separately. func encodeLOONG64Branch21(mnem string, op uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string) ([]byte, error) { if len(ops) != 2 { return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops)) @@ -692,7 +700,7 @@ func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]by } // isLoong64ShiftD reports whether a shift-immediate opcode constant is one of -// the 6-bit (.d) variants — the toolchain distinguishes them by the bit +// the 6-bit (.d) variants, the toolchain distinguishes them by the bit // position of the opcode field (bits [25:16]). func isLoong64ShiftD(op uint32) bool { return op&0x03ff0000 != 0 && op>>25 == 0 @@ -740,7 +748,7 @@ func l64MemOperands(ops []*ast.Operand, fi loong64FrameInfo) (rd, rj int, off in // ---- the MOV pseudo-instruction ---- -// encodeLOONG64Mov encodes the MOV family — the load/store/immediate +// encodeLOONG64Mov encodes the MOV family, the load/store/immediate // workhorse of Go's loong64 assembly. MOV is an alias of MOVV (the width // mnemonics MOVB/MOVH/MOVW/MOVV/MOVBU/MOVHU/MOVWU/MOVF/MOVD select the // access width). The forms, mirroring the toolchain: diff --git a/asm/loong64_reloc_test.go b/asm/loong64_reloc_test.go new file mode 100644 index 0000000..c58256f --- /dev/null +++ b/asm/loong64_reloc_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) +// SPDX-License-Identifier: BSD-3-Clause + +package asm + +import ( + "testing" + + "sourcedock.dev/petrbalvin/gasm-devkit/parser" +) + +// TestLOONG64RelocOffsetsIncludePrologue pins the function-relative +// relocation offsets of a framed loong64 function: the offsets used to +// exclude the prologue, so every relocation landed on a prologue +// instruction in the GOOBJ/ELF output. +func TestLOONG64RelocOffsetsIncludePrologue(t *testing.T) { + f, errs := parser.Parse("k_loong64.s", "TEXT \u00b7f(SB), $16-0\n"+ + "\tMOVV $gdata(SB), R4\n"+ + "\tRET\n"+ + "GLOBL gdata(SB), $8\n") + if len(errs) > 0 { + t.Fatalf("parse: %v", errs) + } + img, err := AssembleFileLOONG64(f) + if err != nil { + t.Fatalf("assemble: %v", err) + } + fn := img.Funcs[0] + + // Layout: 12-byte prologue (autosize 32), pcalau12i+addi.d (12, 16), + // epilogue with RET. + if len(fn.Relocs) != 2 { + t.Fatalf("relocs = %d, want 2", len(fn.Relocs)) + } + hi, lo := fn.Relocs[0], fn.Relocs[1] + if hi.Kind != RelLoong64AddrHi || hi.Off != 12 || hi.After != 12 { + t.Errorf("hi reloc = {off %d after %d kind %d}, want {off 12 after 12 kind RelLoong64AddrHi}", hi.Off, hi.After, hi.Kind) + } + if lo.Kind != RelLoong64AddrLo || lo.Off != 16 || lo.After != 16 { + t.Errorf("lo reloc = {off %d after %d kind %d}, want {off 16 after 16 kind RelLoong64AddrLo}", lo.Off, lo.After, lo.Kind) + } +}