Fixed move instruction encoding on arm64

This commit is contained in:
2025-03-15 13:49:30 +01:00
parent 99ef02bbd6
commit fea4e4cbe7
7 changed files with 108 additions and 74 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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.