feat(asm): the immediate multiply and arm64 indirect branches GOROOT writes

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-21 21:50:11 +02:00
parent 7246b0e002
commit 82ef289d3a
3 changed files with 66 additions and 23 deletions
+7 -3
View File
@@ -19,12 +19,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
access pair assembles as the toolchain's one-instruction form (the access pair assembles as the toolchain's one-instruction form (the
bare `MOVQ TLS, r` load nops out and `off(r)(TLS*1)` folds to the bare `MOVQ TLS, r` load nops out and `off(r)(TLS*1)` folds to the
segment-prefixed absolute whose disp32 carries the R_TLSLE segment-prefixed absolute whose disp32 carries the R_TLSLE
relocation, per-GOOS); and `gasm asm` predefines the `GOARCH_<arch>` relocation, per-GOOS); arm64 accepts the bare-register indirect branch
(`BL R9` beside `BL (R9)`, both BLR) and the zero-immediate store
(`MOVD $0, mem` through the zero register, rejecting non-zero immediates
as the toolchain does); and `gasm asm` predefines the `GOARCH_<arch>`
and `GOOS_<goos>` macros the go command passes to `go tool asm`, so and `GOOS_<goos>` macros the go command passes to `go tool asm`, so
GOROOT headers' `#ifdef GOARCH_amd64` platform blocks (`go_tls.h`'s GOROOT headers' `#ifdef GOARCH_amd64` platform blocks (`go_tls.h`'s
`get_tls` and friends) select as intended. The GOROOT corpus measure `get_tls` and friends) select as intended. The GOROOT corpus measure
moves to 267 of 353 files assembling for every target architecture moves to 276 of 353 files assembling for every target architecture
(75.6 %), 89.4 % of the real-code corpus, from 70.8 % and 82.2 %. (78.2 %), 91.4 % of the real-code corpus (280 of 303), from 70.8 %
and 82.2 %.
### Added ### Added
+23
View File
@@ -637,6 +637,20 @@ func encodeARM64Branch(mnem string, ops []*ast.Operand, pc int, offsets map[stri
return a64wordLE(a64UncondBranch(opc, uint32(rn), 0)), nil return a64wordLE(a64UncondBranch(opc, uint32(rn), 0)), nil
} }
// The bare spelling BL R9 is the same indirect branch: the parser reads
// a bare identifier as a symbol, and one named for a register is an
// indirect branch through it, which the toolchain accepts alongside the
// parenthesised form (BL (R3) and BL R3 both encode BLR R3).
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" && op.Addr.Base == "" && op.Addr.Index == "" {
if rn := arm64RegNum(op.Addr.Sym.Name); rn >= 0 {
opc := uint32(0) // BR
if link {
opc = 1 // BLR
}
return a64wordLE(a64UncondBranch(opc, uint32(rn), 0)), nil
}
}
// Symbol reference: BL sym(SB), or B sym(SB) for a tail call, against a // Symbol reference: BL sym(SB), or B sym(SB) for a tail call, against a
// relocation (R_CALLARM64 either way). // relocation (R_CALLARM64 either way).
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" { if op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "SB" {
@@ -1454,6 +1468,15 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
} }
return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil
} }
// Immediate → memory: only storing zero is encodable (the ZR
// register); the toolchain rejects any other immediate-to-memory
// combination ("illegal combination").
if isMemOperand(dst) {
if arm64Imm64(src) != 0 {
return nil, fmt.Errorf("%s: illegal combination: an immediate store must be zero", mnem)
}
return encodeARM64MemOp(mnem, dst, 31, false, fi, "")
}
rd := arm64RegNum(operandRegName(dst)) rd := arm64RegNum(operandRegName(dst))
if rd < 0 { if rd < 0 {
return nil, fmt.Errorf("%s $imm: invalid destination register", mnem) return nil, fmt.Errorf("%s $imm: invalid destination register", mnem)
+36 -20
View File
@@ -640,7 +640,16 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
func (e *enc) encodeImul(ops []Operand, size int) error { func (e *enc) encodeImul(ops []Operand, size int) error {
switch len(ops) { switch len(ops) {
case 2: case 2:
// IMUL r, r/m: 0x0F 0xAF. // Two shapes. The leading-immediate spelling IMUL $imm, r multiplies
// r in place (dst = rm = r): the shape GOROOT's clock code writes.
// Otherwise IMUL r, r/m: 0x0F 0xAF.
if imm, ok := ops[0].(Imm); ok {
dstReg, isReg := ops[1].(Reg)
if !isReg {
return fmt.Errorf("IMUL: destination must be a register")
}
return e.encodeImulImm(imm, dstReg, dstReg, size)
}
dstReg, ok := ops[1].(Reg) dstReg, ok := ops[1].(Reg)
if !ok { if !ok {
return fmt.Errorf("IMUL: destination must be a register") return fmt.Errorf("IMUL: destination must be a register")
@@ -660,29 +669,36 @@ func (e *enc) encodeImul(ops []Operand, size int) error {
if !ok { if !ok {
return fmt.Errorf("IMUL: immediate operand expected first") return fmt.Errorf("IMUL: immediate operand expected first")
} }
// Plan 9 order: IMUL $imm, src, dst. // Plan 9 order: IMUL $imm, src, dst; the source stays a general
if fits8(int64(imm)) { // r/m operand (setRM takes registers and memory alike).
i := newInstr(size, []byte{0x6B}) return e.encodeImulImm(imm, ops[1], dstReg, size)
if err := setRM(i, dstReg, ops[1], size); err != nil {
return err
}
i.imm = []byte{byte(int8(imm))}
return e.emit(i)
}
i := newInstr(size, []byte{0x69})
if err := setRM(i, dstReg, ops[1], size); err != nil {
return err
}
immBytes, err := immediate(int64(imm), size, false)
if err != nil {
return err
}
i.imm = immBytes
return e.emit(i)
} }
return fmt.Errorf("IMUL expects 2 or 3 operands, got %d", len(ops)) return fmt.Errorf("IMUL expects 2 or 3 operands, got %d", len(ops))
} }
// encodeImulImm emits the immediate multiply: 0x6B with a sign-extended imm8
// when the value fits, 0x69 with a 32-bit immediate otherwise.
func (e *enc) encodeImulImm(imm Imm, rm Operand, dst Reg, size int) error {
if fits8(int64(imm)) {
i := newInstr(size, []byte{0x6B})
if err := setRM(i, dst, rm, size); err != nil {
return err
}
i.imm = []byte{byte(int8(imm))}
return e.emit(i)
}
i := newInstr(size, []byte{0x69})
if err := setRM(i, dst, rm, size); err != nil {
return err
}
immBytes, err := immediate(int64(imm), size, false)
if err != nil {
return err
}
i.imm = immBytes
return e.emit(i)
}
// --- PUSH / POP ------------------------------------------------------------- // --- PUSH / POP -------------------------------------------------------------
func (e *enc) encodePushPop(ops []Operand, size int, push bool) error { func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {