From fea4e4cbe7875039b361c51d6d98e51f97c750eb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 13:49:30 +0100 Subject: [PATCH] Fixed move instruction encoding on arm64 --- src/arm/Move.go | 105 ++++++++++++++++++++++-------------- src/arm/Move_test.go | 24 +++++++-- src/arm/encode.go | 11 ++-- src/asmc/compileARM.go | 12 ++--- src/asmc/movARM.go | 16 ------ src/sizeof/Unsigned.go | 10 ++-- src/sizeof/Unsigned_test.go | 4 +- 7 files changed, 108 insertions(+), 74 deletions(-) delete mode 100644 src/asmc/movARM.go diff --git a/src/arm/Move.go b/src/arm/Move.go index c687c2a..a0fc070 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -1,10 +1,73 @@ package arm import ( + "encoding/binary" + "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/sizeof" ) +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + instruction, encodable := MoveRegisterNumberSI(destination, number) + + if encodable { + return binary.LittleEndian.AppendUint32(code, instruction) + } + + return MoveRegisterNumberMI(code, destination, number) +} + +// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions. +func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { + movz := MoveZero(destination, 0, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movz) + halfword := 1 + + for { + number >>= 16 + + if number == 0 { + return code + } + + movk := MoveKeep(destination, halfword, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movk) + halfword++ + } +} + +// MoveRegisterNumberSI moves a number into the given register using a single instruction. +func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { + if sizeof.Signed(number) <= 2 { + if number < 0 { + return MoveInvertedWideImmediate(destination, uint16(^number), 0), true + } + + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true + } + + code, encodable := MoveBitmaskImmediate(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true + } + + return 0, false +} + // MoveRegisterRegister copies a register to another register. func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { if source == SP || destination == SP { @@ -14,58 +77,22 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return OrRegisterRegister(destination, ZR, source) } -// MoveRegisterNumber moves a number into the given register. -func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) { - if number < 0 { - return MoveInvertedWideImmediate(destination, ^number, 0), true - } - - if sizeof.Signed(number) <= 2 { - return MoveZero(destination, 0, uint16(number)), true - } - - if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true - } - - code, encodable := MoveBitmaskImmediate(destination, number) - - if encodable { - return code, true - } - - if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true - } - - if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { - return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true - } - - return 0, false -} - // MoveBitmaskImmediate moves a bitmask immediate value to a register. func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) { return OrRegisterNumber(destination, ZR, number) } // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register. -func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 { +func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 { return 0b100100101<<23 | shift<<21 | regImm(destination, number) } // MoveKeep moves a 16-bit integer into the given register and keeps all other bits. func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b11, halfword, number, destination) + return 0b111100101<<23 | regImmHw(destination, halfword, number) } // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { - return mov(0b10, halfword, number, destination) -} - -// mov encodes a generic move instruction. -func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { - return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination) + return 0b110100101<<23 | regImmHw(destination, halfword, number) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 6e02a4e..af4c58e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -30,7 +30,24 @@ func TestMoveRegisterRegister(t *testing.T) { func TestMoveRegisterNumber(t *testing.T) { usagePatterns := []struct { Register cpu.Register - Number int + Number uint64 + Code []byte + }{ + {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, + {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterNumberSI(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint64 Code uint32 }{ // MOVZ @@ -41,9 +58,10 @@ func TestMoveRegisterNumber(t *testing.T) { {arm.X0, 0x1FFFF, 0xB24043E0}, {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + {arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0}, // MOV (inverted wide immediate) - {arm.X0, -1, 0x92800000}, + {arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000}, {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler @@ -55,7 +73,7 @@ func TestMoveRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("mov %s, %d", pattern.Register, pattern.Number) - code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number) + code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number)) if pattern.Code != 0 { assert.True(t, encodable) diff --git a/src/arm/encode.go b/src/arm/encode.go index 69724a7..3e9aa4b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -23,13 +23,14 @@ func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uin } // regImm encodes an instruction with a register and an immediate. -func regImm(d cpu.Register, imm16 int) uint32 { - return uint32(imm16&mask16)<<5 | uint32(d) +func regImm(d cpu.Register, imm16 uint16) uint32 { + return uint32(imm16)<<5 | uint32(d) } -// reg2 encodes an instruction with 2 registers. -func reg2(d cpu.Register, n cpu.Register) uint32 { - return uint32(n)<<5 | uint32(d) +// regImmHw encodes an instruction with a register, an immediate and +// the 2-bit halfword specifying which 16-bit region of the register is addressed. +func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 { + return uint32(hw)<<21 | uint32(imm16)<<5 | uint32(d) } // reg2Imm encodes an instruction with 2 registers and an immediate. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index a5b8e05..972d1e3 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -77,7 +77,7 @@ func (c *compiler) compileARM(x asm.Instruction) { if operands.Address.OffsetRegister < 0 { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number) c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length)) } else { panic("not implemented") @@ -110,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -128,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -146,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(code) } else { tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number) c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) } case asm.TypeRegisterRegister: @@ -201,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operand := c.assembler.Param.RegisterNumber[x.Index] tmp := arm.X28 - c.moveRegisterNumberARM(tmp, operand.Number) + c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number) c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp)) } @@ -227,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] - c.moveRegisterNumberARM(operands.Register, operands.Number) + c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number) case asm.TypeRegisterLabel: operands := c.assembler.Param.RegisterLabel[x.Index] diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go deleted file mode 100644 index e34fb0e..0000000 --- a/src/asmc/movARM.go +++ /dev/null @@ -1,16 +0,0 @@ -package asmc - -import ( - "git.urbach.dev/cli/q/src/arm" - "git.urbach.dev/cli/q/src/cpu" -) - -func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) { - code, encodable := arm.MoveRegisterNumber(register, number) - - if encodable { - c.append(code) - } else { - panic("not implemented") // movz movk - } -} diff --git a/src/sizeof/Unsigned.go b/src/sizeof/Unsigned.go index 949b77a..b2df90a 100644 --- a/src/sizeof/Unsigned.go +++ b/src/sizeof/Unsigned.go @@ -3,15 +3,17 @@ package sizeof import "math" // Unsigned tells you how many bytes are needed to encode this unsigned number. -func Unsigned(number uint64) int { +func Unsigned[T uint | uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64](number T) int { + x := uint64(number) + switch { - case number <= math.MaxUint8: + case x <= math.MaxUint8: return 1 - case number <= math.MaxUint16: + case x <= math.MaxUint16: return 2 - case number <= math.MaxUint32: + case x <= math.MaxUint32: return 4 default: diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go index 94cdd10..b8c20f4 100644 --- a/src/sizeof/Unsigned_test.go +++ b/src/sizeof/Unsigned_test.go @@ -11,7 +11,9 @@ import ( func TestUnsigned(t *testing.T) { assert.Equal(t, sizeof.Unsigned(0), 1) assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1) + assert.Equal(t, sizeof.Unsigned(math.MaxUint8+1), 2) assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2) + assert.Equal(t, sizeof.Unsigned(math.MaxUint16+1), 4) assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4) - assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8) + assert.Equal(t, sizeof.Unsigned(math.MaxUint32+1), 8) }