feat(asm): the segment-absolute and crash-store forms GOROOT writes
Test / test (push) Failing after 2m28s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-21 22:19:53 +02:00
parent 2c9042d62c
commit f720381d43
6 changed files with 105 additions and 5 deletions
+6 -4
View File
@@ -23,13 +23,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(`BL R9` beside `BL (R9)`, both BLR) and the zero-immediate store (`BL R9` beside `BL (R9)`, both BLR) and the zero-immediate store
(`MOVD $0, mem` through the zero register, rejecting non-zero immediates (`MOVD $0, mem` through the zero register, rejecting non-zero immediates
as the toolchain does); `PCALIGN` now aligns on amd64, padding with the as the toolchain does); `PCALIGN` now aligns on amd64, padding with the
toolchain's greedy single-instruction NOPs; and `gasm asm` predefines toolchain's greedy single-instruction NOPs; the segment-absolute forms
(`MOVQ 0x30(GS), AX` and the store direction) and the absolute
crash-store (`MOVL $0xf1, 0xf1`) encode; and `gasm asm` predefines
the `GOARCH_<arch>` and `GOOS_<goos>` macros the go command passes to the `GOARCH_<arch>` and `GOOS_<goos>` macros the go command passes to
`go tool asm`, so GOROOT headers' `#ifdef GOARCH_amd64` platform blocks `go tool asm`, so GOROOT headers' `#ifdef GOARCH_amd64` platform blocks
(`go_tls.h`'s `get_tls` and friends) select as intended. The GOROOT (`go_tls.h`'s `get_tls` and friends) select as intended. The GOROOT
corpus measure moves to 285 of 353 files assembling for every target corpus measure moves to 291 of 353 files assembling for every target
architecture (80.7 %), 91.4 % of the real-code corpus (280 of 303), architecture (82.4 %), 97.4 % of the real-code corpus, from 70.8 %
from 70.8 % and 82.2 %. and 82.2 %.
### Added ### Added
+24
View File
@@ -1254,6 +1254,16 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
// Memory with a real base register: (base), off(base), (base)(index*scale). // Memory with a real base register: (base), off(base), (base)(index*scale).
if a.Base != "" { if a.Base != "" {
// Segment-absolute: 0x30(GS) and 0x28(FS), the windows TLS
// spellings. The segment override prefixes a disp32 absolute
// reference with no relocation.
if a.Base == "GS" || a.Base == "FS" {
seg := byte(0x64)
if a.Base == "GS" {
seg = 0x65
}
return SegAbs{Disp: a.Offset, Size: size, Seg: seg}, nil
}
base, ok := ParseReg(a.Base) base, ok := ParseReg(a.Base)
if !ok { if !ok {
return nil, fmt.Errorf("unknown base register %q", a.Base) return nil, fmt.Errorf("unknown base register %q", a.Base)
@@ -1273,6 +1283,15 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
} }
return TLSMem{Disp: a.Offset, Size: size, Seg: seg}, nil return TLSMem{Disp: a.Offset, Size: size, Seg: seg}, nil
} }
if a.Index == "GS" || a.Index == "FS" {
// 0(CX)(GS): the segment annotation rides the base
// access as the override prefix.
m.Seg = 0x64
if a.Index == "GS" {
m.Seg = 0x65
}
return m, nil
}
idx, ok := ParseReg(a.Index) idx, ok := ParseReg(a.Index)
if !ok { if !ok {
return nil, fmt.Errorf("unknown index register %q", a.Index) return nil, fmt.Errorf("unknown index register %q", a.Index)
@@ -1293,6 +1312,11 @@ func operandFromAST(mnemUpper string, op *ast.Operand, size int, fi frameInfo, l
} }
return Mem{Index: idx, Scale: a.Scale, Disp: a.Offset, HasIndex: true, Size: size}, nil return Mem{Index: idx, Scale: a.Scale, Disp: a.Offset, HasIndex: true, Size: size}, nil
} }
// A bare displacement with no base: the absolute address form,
// MOVL $0xf1, 0xf1. No segment and no relocation.
if a.Sym == nil && a.Base == "" && a.Index == "" && a.HasOff {
return SegAbs{Disp: a.Offset, Size: size}, nil
}
// Bare register. // Bare register.
if a.Sym != nil && a.Sym.Pseudo == "" && a.Sym.Name != "" { if a.Sym != nil && a.Sym.Pseudo == "" && a.Sym.Name != "" {
if r, ok := ParseReg(a.Sym.Name); ok { if r, ok := ParseReg(a.Sym.Name); ok {
+18
View File
@@ -689,12 +689,20 @@ func setRMReg(i *instr, regField int, rexR, regForced bool, rm Operand, opSize i
i.disp = le32(r.Disp) i.disp = le32(r.Disp)
i.tls = true i.tls = true
return nil return nil
case SegAbs:
// 0x30(GS): the segment override with the SIB escape's disp32
// absolute form, no relocation.
setSegAbs(i, regField, r)
return nil
default: default:
return fmt.Errorf("invalid r/m operand %T", rm) return fmt.Errorf("invalid r/m operand %T", rm)
} }
} }
func setMem(i *instr, regField int, m Mem) error { func setMem(i *instr, regField int, m Mem) error {
if m.Seg != 0 {
i.prefix = m.Seg
}
modrm, sib, disp, xBit, bBit, err := memComponents(regField, m) modrm, sib, disp, xBit, bBit, err := memComponents(regField, m)
if err != nil { if err != nil {
return err return err
@@ -707,6 +715,16 @@ func setMem(i *instr, regField int, m Mem) error {
return nil return nil
} }
// setSegAbs assembles a segment-absolute operand, 0x30(GS): the segment
// override with the mod=00 SIB escape's disp32 absolute form and no
// relocation.
func setSegAbs(i *instr, regField int, m SegAbs) {
i.prefix = m.Seg
i.modrm = 0x04 | regField<<3
i.sib = 0x25
i.disp = le32(m.Disp)
}
// memComponents computes the ModR/M byte (with the given reg field), the SIB // memComponents computes the ModR/M byte (with the given reg field), the SIB
// byte (-1 if none), the displacement bytes, and the high index/base bits, for // byte (-1 if none), the displacement bytes, and the high index/base bits, for
// a memory operand. It is shared by the REX (scalar) and VEX (vector) paths. // a memory operand. It is shared by the REX (scalar) and VEX (vector) paths.
+25 -1
View File
@@ -205,6 +205,17 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
} }
return e.emit(i) return e.emit(i)
case SegAbs:
if !dstIsReg {
return fmt.Errorf("MOV: two memory operands")
}
// MOV r, 0x30(GS): the segment-absolute load.
i := newInstr(size, []byte{movRR(size)})
if err := setRM(i, dstReg, src, size); err != nil {
return err
}
return e.emit(i)
case Imm: case Imm:
if dstIsReg { if dstIsReg {
v := int64(src) v := int64(src)
@@ -245,11 +256,24 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
i.imm = imm i.imm = imm
return e.emit(i) return e.emit(i)
} }
// MOV r/m, imm: 0xC6 (8-bit) / 0xC7 /0. // MOV r/m, imm: 0xC6 (8-bit) / 0xC7 /0. An immediate in the
// destination slot is the absolute-address crash-store spelling,
// MOVL $0xf1, 0xf1: the parser reads the trailing bare constant
// as an immediate, and the store's disp32 carries the address.
op := byte(0xC7) op := byte(0xC7)
if size == 1 { if size == 1 {
op = 0xC6 op = 0xC6
} }
if d, ok := dst.(Imm); ok {
i := newInstr(size, []byte{op})
setSegAbs(i, 0, SegAbs{Disp: int64(d)})
immBytes, err := immediate(int64(src), size, false)
if err != nil {
return err
}
i.imm = immBytes
return e.emit(i)
}
i := newInstr(size, []byte{op}) i := newInstr(size, []byte{op})
if err := setRMDigit(i, 0, dst, size); err != nil { if err := setRMDigit(i, 0, dst, size); err != nil {
return err return err
+12
View File
@@ -48,6 +48,17 @@ type TLSMem struct {
func (TLSMem) isOperand() {} func (TLSMem) isOperand() {}
// SegAbs is a segment-absolute access, 0x30(GS): the segment override
// prefixes a disp32 absolute reference with no relocation. The base
// register spellings GS and FS produce it.
type SegAbs struct {
Disp int64
Size int
Seg byte // 0x64 FS, 0x65 GS
}
func (SegAbs) isOperand() {}
// Mem is a memory operand of the form disp(base)(index*scale). // Mem is a memory operand of the form disp(base)(index*scale).
type Mem struct { type Mem struct {
Base Reg Base Reg
@@ -57,6 +68,7 @@ type Mem struct {
Size int // operand width in bytes Size int // operand width in bytes
HasBase bool HasBase bool
HasIndex bool HasIndex bool
Seg byte // segment override prefix (0x64 FS, 0x65 GS); 0 = none
} }
func (Mem) isOperand() {} func (Mem) isOperand() {}
+20
View File
@@ -684,6 +684,26 @@ func parseAddress(g []token.Token) ast.Address {
if i > 0 && i < len(g) { if i > 0 && i < len(g) {
addr.Shift = joinRaw(g[i:]) addr.Shift = joinRaw(g[i:])
} }
// A lone (possibly signed) number is an absolute address: MOVL $0xf1,
// 0xf1 stores through the bare displacement with no base at all. In
// operand position a number without $ is an address, never a value.
if addr.Sym == nil && addr.Base == "" && addr.Index == "" && !addr.HasOff {
neg := false
j := 0
if j < len(g) && (g[j].Kind == token.Minus || g[j].Kind == token.Plus) {
neg = g[j].Kind == token.Minus
j++
}
if j == len(g)-1 && g[j].Kind == token.Number {
v := parseInt(g[j].Text)
if neg {
v = -v
}
addr.Offset = v
addr.HasOff = true
return addr
}
}
return addr return addr
} }