feat(asm): emit the riscv64 local-exec TLS sequence

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:51:02 +02:00
1 parent f8dbd4f017
commit f2892e4f59
6 files changed
+114 -29

No files matched your search

+45 -9
View File
@@ -17,7 +17,7 @@ import (
// assembleRISCV assembles a RISC-V TEXT function body into machine code.
// It handles the full RV64IMAFDC instruction set including RVC compression.
func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, []RiscvLiteral, error) {
func assembleRISCV(t *ast.Text, tlsSyms map[string]bool) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, []RiscvLiteral, error) {
fi := riscvComputeFrame(t)
prologue := riscvPrologue(fi)
guardLen, err := riscvGuardLen(fi)
@@ -66,7 +66,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
}
recs = append(recs, instrRec{instr: s, pad: pendingPad})
pendingPad = 0
pos += riscvInstrSize(s, fi)
pos += riscvInstrSize(s, fi, tlsSyms)
}
}
@@ -78,12 +78,12 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
pc := len(prologue)
for i := range recs {
branchLike := isBranchLike(recs[i].instr.Mnemonic.Text) || riscvIsCondBranch(recs[i].instr.Mnemonic.Text)
code, err := encodeRISCVInstr(recs[i].instr, pc, offsets, fi, nil, nil, lits) // no relocs in Pass 2
code, err := encodeRISCVInstr(recs[i].instr, pc, offsets, fi, nil, nil, lits, tlsSyms) // no relocs in Pass 2
if err != nil && !(branchLike && riscvIsRangeError(err)) {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", recs[i].instr.Mnemonic.Text, err)
}
if err != nil {
code = make([]byte, riscvInstrSize(recs[i].instr, fi))
code = make([]byte, riscvInstrSize(recs[i].instr, fi, tlsSyms))
}
recs[i].code = code
pc += len(code)
@@ -252,7 +252,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
code = r.code
default:
var err error
code, err = encodeRISCVInstr(r.instr, pc, offsets, fi, &relocs, pcRelPcs, lits)
code, err = encodeRISCVInstr(r.instr, pc, offsets, fi, &relocs, pcRelPcs, lits, tlsSyms)
if err != nil {
return nil, nil, nil, nil, nil, nil, err
}
@@ -376,7 +376,7 @@ func riscvFenceFlag(op *ast.Operand) (uint32, bool) {
// Most instructions are 4 bytes; MOV with a large immediate and I-type
// arithmetic with a large immediate expand to several (possibly compressed)
// instructions.
func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo, tlsSyms map[string]bool) int {
mnem := instr.Mnemonic.Text
ops := instr.Operands
mnem = riscvNormalisePseudo(mnem)
@@ -401,6 +401,14 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
return 8
}
}
// A TLSBSS symbol's memory reference takes the 16-byte local-exec
// sequence (LUI + ADDIW + ADD of TP + the access).
tlsRef := func(op *ast.Operand) bool {
return op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" && tlsSyms[op.Addr.Sym.Name]
}
if tlsRef(ops[0]) || tlsRef(ops[1]) {
return 16
}
// MOV $sym(SB), rd → 8 bytes (AUIPC + ADDI).
if isImmOperand(ops[0]) && ops[0].Imm.Sym != nil && ops[0].Imm.Sym.Pseudo == "SB" {
return 8
@@ -1088,7 +1096,7 @@ func riscvCheckJumpOffset(target string, off int32) error {
}
// encodeRISCVInstr encodes a single RISC-V instruction.
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int, lits *riscvLiterals) ([]byte, error) {
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int, lits *riscvLiterals, tlsSyms map[string]bool) ([]byte, error) {
mnem := instr.Mnemonic.Text
ops := instr.Operands
mnem = riscvNormalisePseudo(mnem)
@@ -1269,7 +1277,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
// (MOVB/MOVH/MOVW and unsigned forms) select the access width, and
// MOVD/MOVF address the FP registers.
case "MOV", "MOVB", "MOVBU", "MOVH", "MOVHU", "MOVW", "MOVWU", "MOVF", "MOVD":
return encodeRISCVMov(instr, fi, relocs, lits)
return encodeRISCVMov(instr, fi, relocs, lits, tlsSyms)
// JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1).
case "JALR":
@@ -1915,7 +1923,7 @@ func isImmOperand(op *ast.Operand) bool {
// - MOV Rs, (Rd) register-relative store
// - MOV Rs, Rd register-to-register move (ADDI $0)
// - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW)
func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *riscvLiterals) ([]byte, error) {
func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *riscvLiterals, tlsSyms map[string]bool) ([]byte, error) {
ops := instr.Operands
if len(ops) != 2 {
return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops))
@@ -2006,10 +2014,16 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
// Memory → register (load).
if isMemOperand(src) && !isMemOperand(dst) {
rd := regFromOperand(dst)
// MOV sym(SB), rd, load from static data. A TLSBSS symbol takes
// the local-exec sequence: LUI + ADDIW carry the offset against TP,
// the ADD folds the thread pointer in, the access reads through TMP.
if src.Addr.Sym != nil && src.Addr.Sym.Pseudo == "SB" {
if rd < 0 {
return nil, fmt.Errorf("MOV sym(SB): invalid destination register")
}
if tlsSyms[src.Addr.Sym.Name] {
return riscvTLSBytes(riscvMovEnc(strings.ToUpper(instr.Mnemonic.Text), false), false, rd, src.Addr.Sym, relocs), nil
}
return encodeRISCVSBLoad(src.Addr.Sym, rd, relocs), nil
}
rs1, off := memFromOperandWithFrame(src, fi)
@@ -2022,10 +2036,15 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
// Register → memory (store).
if !isMemOperand(src) && isMemOperand(dst) {
rs2 := regFromOperand(src)
// MOV rd, sym(SB), store to static data. A TLSBSS symbol takes
// the local-exec sequence with the store through TMP.
if dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB" {
if rs2 < 0 {
return nil, fmt.Errorf("MOV rd, sym(SB): invalid source register")
}
if tlsSyms[dst.Addr.Sym.Name] {
return riscvTLSBytes(riscvMovEnc(strings.ToUpper(instr.Mnemonic.Text), true), true, rs2, dst.Addr.Sym, relocs), nil
}
return encodeRISCVSBStore(dst.Addr.Sym, rs2, relocs), nil
}
rs1, off := memFromOperandWithFrame(dst, fi)
@@ -2497,6 +2516,23 @@ func encodeRISCVSBFPLoad(name string, rd int, double bool, relocs *[]Reloc) []by
return append(wordLE(auipc), wordLE(fl)...)
}
// riscvTLSBytes emits the toolchain's local-exec TLS sequence for an SB
// reference to a TLSBSS symbol: LUI TMP + ADDIW TMP (the 8-byte
// R_RISCV_TLS_LE field the linker patches as the offset from the thread
// pointer), ADD TMP, TP, TMP, then the access at zero offset through TMP.
func riscvTLSBytes(enc riscvEnc, store bool, reg int, sym *ast.Symbol, relocs *[]Reloc) []byte {
if relocs != nil {
*relocs = append(*relocs, Reloc{Off: 0, After: 8, Name: sym.Name, Kind: RelRISCVTLSLE, Addend: sym.Offset})
}
out := wordLE(riscvUType(riscvEnc{0x37, 0x0, 0x00}, 31, 0)) // LUI X31, hi
out = append(out, wordLE(riscvIType(riscvEnc{0x1b, 0x0, 0x00}, 31, 31, 0))...) // ADDIW X31, X31, lo
out = append(out, wordLE(riscvRType(riscvInstrTable["ADD"], 31, 31, 4))...) // ADD X31, X31, X4(TP)
if store {
return append(out, wordLE(riscvSType(enc, 31, reg, 0))...)
}
return append(out, wordLE(riscvIType(enc, reg, 31, 0))...)
}
// RiscvLiteral is one pooled 64-bit constant: a MOV whose immediate sits
// beyond both the 32-bit span and the shift sequences loads its bits from a
// read-only data symbol named like the toolchain's $i64 pool.