diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fe7d9..c7c7ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 bare `MOVQ TLS, r` load nops out and `off(r)(TLS*1)` folds to the segment-prefixed absolute whose disp32 carries the R_TLSLE - relocation, per-GOOS); and `gasm asm` predefines the `GOARCH_` + 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_` and `GOOS_` macros the go command passes to `go tool asm`, so GOROOT headers' `#ifdef GOARCH_amd64` platform blocks (`go_tls.h`'s `get_tls` and friends) select as intended. The GOROOT corpus measure - moves to 267 of 353 files assembling for every target architecture - (75.6 %), 89.4 % of the real-code corpus, from 70.8 % and 82.2 %. + moves to 276 of 353 files assembling for every target architecture + (78.2 %), 91.4 % of the real-code corpus (280 of 303), from 70.8 % + and 82.2 %. ### Added diff --git a/asm/arm64_assemble.go b/asm/arm64_assemble.go index 48b58cc..a5e57ff 100644 --- a/asm/arm64_assemble.go +++ b/asm/arm64_assemble.go @@ -637,6 +637,20 @@ func encodeARM64Branch(mnem string, ops []*ast.Operand, pc int, offsets map[stri 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 // relocation (R_CALLARM64 either way). 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 } + // 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)) if rd < 0 { return nil, fmt.Errorf("%s $imm: invalid destination register", mnem) diff --git a/asm/instrs.go b/asm/instrs.go index 4772a5c..afd6676 100644 --- a/asm/instrs.go +++ b/asm/instrs.go @@ -640,7 +640,16 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error { func (e *enc) encodeImul(ops []Operand, size int) error { switch len(ops) { 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) if !ok { return fmt.Errorf("IMUL: destination must be a register") @@ -660,29 +669,36 @@ func (e *enc) encodeImul(ops []Operand, size int) error { if !ok { return fmt.Errorf("IMUL: immediate operand expected first") } - // Plan 9 order: IMUL $imm, src, dst. - if fits8(int64(imm)) { - i := newInstr(size, []byte{0x6B}) - 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) + // Plan 9 order: IMUL $imm, src, dst; the source stays a general + // r/m operand (setRM takes registers and memory alike). + return e.encodeImulImm(imm, ops[1], dstReg, size) } 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 ------------------------------------------------------------- func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {