diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index 17b6d28..30d7176 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -30,8 +30,9 @@ import ( // (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, []Arm64Literal, error) { +func assembleARM64(t *ast.Text, tls map[string]bool) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, []Arm64Literal, error) { fi := arm64ComputeFrame(t) + fi.tls = tls prologue := arm64Prologue(fi) guardLen := arm64GuardLen(fi) chain := arm64JumpChain(t) @@ -1989,6 +1990,24 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo, if rd < 0 { return nil, fmt.Errorf("%s sym(SB): invalid destination register", mnem) } + // A TLSBSS symbol's load rides the local-exec model: one MOVZ word + // with the R_ARM64_TLS_LE relocation (asm7.go case 69), the width + // rows existing for MOVD alone. + if fi.tls[src.Addr.Sym.Name] { + if mnem != "MOVD" && mnem != "MOV" { + return nil, fmt.Errorf("%s: illegal combination: the TLS load exists for MOVD only", mnem) + } + if relocs != nil { + *relocs = append(*relocs, Reloc{ + Off: 0, + After: 4, + Name: src.Addr.Sym.Name, + Addend: src.Addr.Sym.Offset, + Kind: RelArm64TLSLE, + }) + } + return a64wordLE(a64MoveWide(1, 2, 0, 0, uint32(rd))), nil + } return encodeARM64SBLoad(src.Addr.Sym, rd, mnem, relocs) } if dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB" && isMemOperand(dst) { @@ -2116,6 +2135,9 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int { if mnem == "FMOVQ" { return 12 // ADRP + ADD + LDR: the unaligned-access fallback } + if fi.tls != nil && fi.tls[src.Addr.Sym.Name] { + return 4 // the TLS-LE load is one MOVZ word + } return 8 // ADRP + LDR case dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB": if mnem == "FMOVQ" { @@ -5771,6 +5793,22 @@ func AssembleFileARM64(f *ast.File) (*Image, error) { return nil, err } + // The TLS symbols: a GLOBL marked TLSBSS makes the toolchain's aclass + // key its load off objabi.STLSBSS (asm7.go C_TLSIE), so the loads of + // these names encode as the local-exec MOVZ with the TLS_LE relocation. + tlsSyms := map[string]bool{} + for _, d := range f.Decls { + g, ok := d.(*ast.Globl) + if !ok || g.Name == nil { + continue + } + for _, fl := range g.Flags { + if fl == "TLSBSS" { + tlsSyms[g.Name.Name] = true + } + } + } + img := &Image{Symbols: map[string]int{}, SourcePath: f.Path} var pendingLits []Arm64Literal litSeen := map[string]bool{} @@ -5779,7 +5817,7 @@ func AssembleFileARM64(f *ast.File) (*Image, error) { if !ok { continue } - code, labels, relocs, lines, spadj, lits, err := assembleARM64(t) + code, labels, relocs, lines, spadj, lits, err := assembleARM64(t, tlsSyms) if err != nil { return nil, fmt.Errorf("%s: %w", t.Name.Name, err) } diff --git a/asm/arm64_frame.go b/asm/arm64_frame.go index 14fc25f..bf7cca8 100644 --- a/asm/arm64_frame.go +++ b/asm/arm64_frame.go @@ -69,6 +69,12 @@ type arm64FrameInfo struct { // autosize below StackSmall as NOSPLIT. needSplit bool splitClass int // 0: <=StackSmall, 1: <=StackBig, 2: >StackBig + + // tls holds the names the file's own GLOBL declarations mark TLSBSS: + // the toolchain's aclass keys the TLS-LE load off the symbol's type + // (objabi.STLSBSS), which only the file's declarations reveal. A nil + // map answers no, the plain static-symbol load. + tls map[string]bool } // arm64ComputeFrame derives the frame layout for a TEXT function. diff --git a/asm/arm64_reloc_test.go b/asm/arm64_reloc_test.go index 3d5c47d..32f6287 100644 --- a/asm/arm64_reloc_test.go +++ b/asm/arm64_reloc_test.go @@ -124,3 +124,59 @@ func TestArm64GOObjRelocTypes(t *testing.T) { // The detailed layout is covered by the goobj tests; here we only pin // that emission succeeds with the new relocation kinds in play. } + +// TestArm64TLSLoad pins the local-exec TLS load: a symbol the file's own +// GLOBL marks TLSBSS loads as one MOVZ word carrying the R_ARM64_TLS_LE +// relocation (asm7.go case 69), the shape `go tool asm` emits for the +// runtime's tls_g accesses. A non-TLS GLOBL keeps the ADRP+LDR pair. +func TestArm64TLSLoad(t *testing.T) { + img := parseArm64File(t, "#include \"textflag.h\"\n\n"+ + "TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+ + "\tMOVD tlsvar(SB), R0\n"+ + "\tMOVD plain(SB), R1\n"+ + "\tRET\n"+ + "GLOBL tlsvar(SB), TLSBSS, $8\n"+ + "GLOBL plain(SB), NOPTR, $8\n") + fn := img.Funcs[0] + if fn.Size != 4+8+4 { + t.Fatalf("function size = %d, want 16", fn.Size) + } + if w := binary.LittleEndian.Uint32(img.Code[fn.Offset:]); w != 0xd2800000 { + t.Errorf("TLS load word = %08x, want MOVZ 0 (d2800000)", w) + } + var tlsSeen, plainSeen bool + for _, r := range fn.Relocs { + if r.Name != "tlsvar" { + continue + } + tlsSeen = true + if r.Kind != RelArm64TLSLE { + t.Errorf("tlsvar reloc kind = %v, want RelArm64TLSLE", r.Kind) + } + if r.Off != 0 { + t.Errorf("tlsvar reloc off = %d, want 0", r.Off) + } + } + for _, r := range fn.Relocs { + if r.Name == "plain" && r.Kind == RelArm64LDST64 { + plainSeen = true + } + } + if !tlsSeen { + t.Error("no tlsvar relocation recorded") + } + if !plainSeen { + t.Error("the plain GLOBL load lost its ADRP+LDR relocation") + } + // The other widths have no TLS row: the toolchain refuses them. + f, errs := parser.Parse("k_arm64.s", "TEXT \u00b7f(SB), NOSPLIT, $0-0\n"+ + "\tMOVW tlsvar(SB), R0\n"+ + "\tRET\n"+ + "GLOBL tlsvar(SB), TLSBSS, $8\n") + if len(errs) > 0 { + t.Fatalf("parse: %v", errs) + } + if _, err := AssembleFileARM64(f); err == nil { + t.Error("MOVW of a TLS symbol assembled, want an illegal combination") + } +} diff --git a/asm/elfarm64.go b/asm/elfarm64.go index 6be91af..9b149ca 100644 --- a/asm/elfarm64.go +++ b/asm/elfarm64.go @@ -18,6 +18,9 @@ const ( 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) + // R_AARCH64_TLSLE_MOVW_TPREL_G0 (debug/elf 547): the local-exec TLS + // load's MOVZ field, the module offset at bits [15:0]. + rArm64TLSLEMovwTprelG0 = 547 // R_AARCH64_ABS32 (debug/elf 258): the absolute 32-bit address of a // symbol, the R_ADDR shape a 4-byte DATA field carries. ABS64 (257) // lives with the DWARF fixup constants as rAARCH64Abs64. @@ -131,6 +134,11 @@ func (img *Image) ELFAARCH64Object() ([]byte, error) { elfRela{off: uint64(fn.Offset + r.Off), typ: rArm64PrelPgHi21, sym: idx, addend: r.Addend}, elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rArm64Ldst64Lo12NC, sym: idx, addend: r.Addend}, ) + case RelArm64TLSLE: + // The local-exec MOVZ: one relocation over the imm16 field. + relas = append(relas, elfRela{ + off: uint64(fn.Offset + r.Off), typ: rArm64TLSLEMovwTprelG0, sym: idx, addend: r.Addend, + }) default: return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind) } diff --git a/asm/goobjarm64.go b/asm/goobjarm64.go index 74e1139..50e1346 100644 --- a/asm/goobjarm64.go +++ b/asm/goobjarm64.go @@ -50,6 +50,8 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) { return relocArm64Branch, 4 case RelArm64LDST64: return relocArm64LDST64, 8 + case RelArm64TLSLE: + return relocArm64TLSLE, 4 default: return relocArm64Addr, 8 } @@ -60,6 +62,7 @@ func (img *Image) GOObjectAARCH64(pkgPath, srcPath string) ([]byte, error) { const ( relocArm64Addr = 3 // R_ADDRARM64, ADRP+ADD pair relocArm64Branch = 9 // R_CALLARM64, BL instruction + relocArm64TLSLE = 32 // R_ARM64_TLS_LE, MOVZ local-exec TLS load relocArm64LDST64 = 40 // R_ARM64_PCREL_LDST64, ADRP+LDR/STR pair ) diff --git a/asm/link.go b/asm/link.go index 97f3172..303240d 100644 --- a/asm/link.go +++ b/asm/link.go @@ -106,6 +106,7 @@ const ( RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair) RelLoong64Branch // R_CALLLOONG64 (BL instruction) RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field + RelArm64TLSLE // R_ARM64_TLS_LE (MOVZ local-exec TLS load) ) type Reloc struct {