feat(asm): the immediate multiply and arm64 indirect branches GOROOT writes
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+7
-3
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user