feat(asm): encode the arm64 local-exec TLS load

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 21:34:30 +02:00
1 parent 5f6be4584d
commit c27ba30862
6 files changed
+114 -2

No files matched your search

+40 -2
View File
@@ -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)
}
+6
View File
@@ -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.
+56
View File
@@ -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")
}
}
+8
View File
@@ -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)
}
+3
View File
@@ -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
)
+1
View File
@@ -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 {