feat(asm): the TLS access pair in the toolchain's one-instruction form
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+95
-1
@@ -38,10 +38,25 @@ func Assemble(t *ast.Text) ([]byte, map[string]int, error) {
|
||||
// rejects SB operands outright (single-function assembly cannot resolve
|
||||
// them). When allowExternal is set, a reference to a symbol no GLOBL in the
|
||||
// file defines is recorded as an external relocation instead of failing
|
||||
// the object-file emitters resolve it at link time.
|
||||
// the object-file emitters resolve it at link time. goos selects the TLS
|
||||
// access form: the empty default behaves as linux.
|
||||
type linkInfo struct {
|
||||
symbols map[string]bool
|
||||
allowExternal bool
|
||||
goos string
|
||||
}
|
||||
|
||||
// tlsOneInsn reports the one-instruction TLS form, obj6.go's
|
||||
// CanUse1InsnTLS for the GOOS gasm supports: the bare TLS load nops out and
|
||||
// the (TLS*1) index folds to a segment-absolute access. Windows and plan9
|
||||
// keep the two-instruction form; shared linux does too, which gasm's raw
|
||||
// path does not model and therefore does not select.
|
||||
func (l *linkInfo) tlsOneInsn() bool {
|
||||
switch l.goos {
|
||||
case "", "linux", "freebsd":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// sbPatch is a function-relative static-symbol relocation: the disp32 field
|
||||
@@ -834,6 +849,15 @@ func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch
|
||||
}
|
||||
return e.out, ps, nil, nil
|
||||
}
|
||||
// MOVQ/MOVL TLS, r: the bare TLS load. The toolchain's progedit nops
|
||||
// it out on the one-instruction TLS systems (linux and freebsd, not
|
||||
// shared) and encodes the segment-prefixed load elsewhere; get_tls(r),
|
||||
// the macro GOROOT's go_tls.h defines, expands to exactly this
|
||||
// statement, and the toolchain's pairing pass removes it whenever the
|
||||
// following instruction's (TLS*1) index folds.
|
||||
if (mnemUpper == "MOVQ" || mnemUpper == "MOVL") && len(s.Operands) == 2 && isBareTLS(s.Operands[0]) {
|
||||
return encodeTLSBaseLoad(s, fi, link)
|
||||
}
|
||||
_, size := splitSize(mnemUpper)
|
||||
if size == 0 {
|
||||
size = 8
|
||||
@@ -853,10 +877,67 @@ func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch
|
||||
ps := make([]sbPatch, len(e.patches))
|
||||
for i, p := range e.patches {
|
||||
ps[i] = sbPatch{off: p.off, name: p.name, addend: p.addend}
|
||||
if p.tls {
|
||||
ps[i].kind = RelTLSLE
|
||||
}
|
||||
}
|
||||
return e.out, ps, e.floatPoolList(), nil
|
||||
}
|
||||
|
||||
// isBareTLS reports whether the operand is the bare TLS pseudo-register
|
||||
// load source, the expansion of go_tls.h's get_tls(r) macro.
|
||||
func isBareTLS(op *ast.Operand) bool {
|
||||
return op.Kind == ast.OpAddr && op.Addr.Sym != nil &&
|
||||
op.Addr.Sym.Pseudo == "" && op.Addr.Sym.Name == "TLS" &&
|
||||
op.Addr.Base == "" && op.Addr.Index == ""
|
||||
}
|
||||
|
||||
// encodeTLSBaseLoad assembles MOVQ/MOVL TLS, r. On the one-instruction TLS
|
||||
// systems (linux and freebsd outside -shared, obj6.go's CanUse1InsnTLS) the
|
||||
// statement nops out: the following (TLS*1) access folds to a direct
|
||||
// segment-absolute load. The two-instruction systems keep the segment load,
|
||||
// nine bytes with the R_TLSLE patch site at the disp32.
|
||||
func encodeTLSBaseLoad(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch, []floatPoolEntry, error) {
|
||||
_, size := splitSize(strings.ToUpper(s.Mnemonic.Text))
|
||||
if size == 0 {
|
||||
size = 8
|
||||
}
|
||||
dst, err := operandFromAST("MOVQ", s.Operands[1], 8, fi, link)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
reg, ok := dst.(Reg)
|
||||
if !ok || reg.isVec() {
|
||||
return nil, nil, nil, fmt.Errorf("TLS: destination must be a general register")
|
||||
}
|
||||
if link == nil || link.tlsOneInsn() {
|
||||
return nil, nil, nil, nil // noped out
|
||||
}
|
||||
seg := byte(0x64) // FS
|
||||
if link.goos == "windows" {
|
||||
seg = 0x65 // GS
|
||||
}
|
||||
e := &enc{}
|
||||
i := &instr{
|
||||
prefix: seg,
|
||||
rexW: size == 8,
|
||||
rexR: reg.idx >= 8,
|
||||
opcode: []byte{0x8B},
|
||||
modrm: 0x04 | (reg.idx&7)<<3,
|
||||
sib: 0x25,
|
||||
disp: le32(0),
|
||||
tls: true,
|
||||
}
|
||||
if err := e.emit(i); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
ps := make([]sbPatch, len(e.patches))
|
||||
for i, p := range e.patches {
|
||||
ps[i] = sbPatch{off: p.off, name: p.name, addend: p.addend, kind: RelTLSLE}
|
||||
}
|
||||
return e.out, ps, nil, nil
|
||||
}
|
||||
|
||||
// encodeBookkeeping accepts-and-ignores FUNCDATA and PCDATA at the statement
|
||||
// level, before operand conversion: the toolchain's shapes are FUNCDATA
|
||||
// $n, sym(SB) and PCDATA $n, $m, and neither contributes a byte to the
|
||||
@@ -1108,6 +1189,19 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
|
||||
}
|
||||
m := Mem{Base: base, Disp: a.Offset, HasBase: true, Size: size}
|
||||
if a.Index != "" {
|
||||
if a.Index == "TLS" {
|
||||
// off(base)(TLS*1): the thread-local annotation. The
|
||||
// one-instruction TLS form folds it to off(TLS), the
|
||||
// segment-prefixed absolute whose disp32 carries an
|
||||
// R_TLS_LE patch site; the base register disappears
|
||||
// from the encoding, exactly as the toolchain's
|
||||
// progedit rewrites the address.
|
||||
seg := byte(0x64) // FS on linux, freebsd, plan9
|
||||
if link != nil && link.goos == "windows" {
|
||||
seg = 0x65 // GS
|
||||
}
|
||||
return TLSMem{Disp: a.Offset, Size: size, Seg: seg}, nil
|
||||
}
|
||||
idx, ok := ParseReg(a.Index)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown index register %q", a.Index)
|
||||
|
||||
Reference in New Issue
Block a user