From b8f05c8994ee663b1e3cabd236abf7e7010aabe7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 15 Mar 2025 19:45:14 +0100 Subject: [PATCH] Implemented bit shifting on arm64 --- src/arm/And.go | 4 ++-- src/arm/Or.go | 4 ++-- src/arm/Shift.go | 15 ++++++++++++ src/arm/Shift_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 4 ++-- src/arm/arm_test.go | 17 +++++++++++++- src/arm/bitmask.go | 14 ++++++------ src/arm/encode.go | 4 ++-- src/asmc/compileARM.go | 18 +++++++++++++++ src/asmc/compileX86.go | 4 ++++ 10 files changed, 120 insertions(+), 16 deletions(-) create mode 100644 src/arm/Shift.go create mode 100644 src/arm/Shift_test.go diff --git a/src/arm/And.go b/src/arm/And.go index 9c23ce0..8850855 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -6,8 +6,8 @@ import ( // AndRegisterNumber performs a bitwise AND using a register and a number. func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/Or.go b/src/arm/Or.go index 610328b..1d137a8 100644 --- a/src/arm/Or.go +++ b/src/arm/Or.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // OrRegisterNumber performs a bitwise OR using a register and a number. func OrRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b101100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b101100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // OrRegisterRegister performs a bitwise OR using two registers. diff --git a/src/arm/Shift.go b/src/arm/Shift.go new file mode 100644 index 0000000..99ad4d2 --- /dev/null +++ b/src/arm/Shift.go @@ -0,0 +1,15 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// ShiftLeftNumber shifts the register value a specified amount of bits to the left. +func ShiftLeftNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b110100110<<23 | reg2BitmaskImm(destination, source, 1, 64-bits, (^bits)&mask6) +} + +// ShiftRightSignedNumber shifts the signed register value a specified amount of bits to the right. +func ShiftRightSignedNumber(destination cpu.Register, source cpu.Register, bits int) uint32 { + return 0b100100110<<23 | reg2BitmaskImm(destination, source, 1, bits&mask6, 0b111111) +} diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go new file mode 100644 index 0000000..8aa96d2 --- /dev/null +++ b/src/arm/Shift_test.go @@ -0,0 +1,52 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/go/assert" +) + +func TestShiftLeftNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0xD340FC00}, + {arm.X0, arm.X0, 1, 0xD37FF800}, + {arm.X0, arm.X0, 8, 0xD378DC00}, + {arm.X0, arm.X0, 16, 0xD370BC00}, + {arm.X0, arm.X0, 63, 0xD3410000}, + } + + for _, pattern := range usagePatterns { + t.Logf("%b", pattern.Code) + t.Logf("lsl %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftLeftNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestShiftRightSignedNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Bits int + Code uint32 + }{ + {arm.X0, arm.X0, 0, 0x9340FC00}, + {arm.X0, arm.X0, 1, 0x9341FC00}, + {arm.X0, arm.X0, 8, 0x9348FC00}, + {arm.X0, arm.X0, 16, 0x9350FC00}, + {arm.X0, arm.X0, 63, 0x937FFC00}, + } + + for _, pattern := range usagePatterns { + t.Logf("asr %s, %s, %x", pattern.Destination, pattern.Source, pattern.Bits) + code := arm.ShiftRightSignedNumber(pattern.Destination, pattern.Source, pattern.Bits) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go index 3194543..3580136 100644 --- a/src/arm/Xor.go +++ b/src/arm/Xor.go @@ -4,8 +4,8 @@ import "git.urbach.dev/cli/q/src/cpu" // XorRegisterNumber performs a bitwise XOR using a register and a number. func XorRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { - imm13, encodable := encodeLogicalImmediate(uint(number)) - return 0b110100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable + n, immr, imms, encodable := encodeLogicalImmediate(uint(number)) + return 0b110100100<<23 | reg2BitmaskImm(destination, source, n, immr, imms), encodable } // XorRegisterRegister performs a bitwise XOR using two registers. diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go index 43ef207..b81e610 100644 --- a/src/arm/arm_test.go +++ b/src/arm/arm_test.go @@ -7,9 +7,24 @@ import ( "git.urbach.dev/go/assert" ) -func TestARM(t *testing.T) { +func TestGeneral(t *testing.T) { assert.DeepEqual(t, arm.Call(0), 0x94000000) assert.DeepEqual(t, arm.Nop(), 0xD503201F) assert.DeepEqual(t, arm.Return(), 0xD65F03C0) assert.DeepEqual(t, arm.Syscall(), 0xD4000001) } + +func TestNotEncodable(t *testing.T) { + _, encodable := arm.AndRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, 0) + assert.False(t, encodable) + _, encodable = arm.AndRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.OrRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) + _, encodable = arm.XorRegisterNumber(arm.X0, arm.X0, -1) + assert.False(t, encodable) +} diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go index 76668a2..19a65f8 100644 --- a/src/arm/bitmask.go +++ b/src/arm/bitmask.go @@ -4,9 +4,9 @@ import "math/bits" // encodeLogicalImmediate encodes a bitmask immediate. // The algorithm used here was made by Dougall Johnson. -func encodeLogicalImmediate(val uint) (int, bool) { +func encodeLogicalImmediate(val uint) (N int, immr int, imms int, encodable bool) { if val == 0 || ^val == 0 { - return 0, false + return 0, 0, 0, false } rotation := bits.TrailingZeros(clearTrailingOnes(val)) @@ -16,15 +16,15 @@ func encodeLogicalImmediate(val uint) (int, bool) { ones := bits.TrailingZeros(^normalized) size := zeroes + ones - immr := -rotation & (size - 1) - imms := -(size << 1) | (ones - 1) - N := (size >> 6) + immr = -rotation & (size - 1) + imms = -(size << 1) | (ones - 1) + N = (size >> 6) if bits.RotateLeft(val, -(size&63)) != val { - return 0, false + return 0, 0, 0, false } - return N<<12 | immr<<6 | (imms & 0x3f), true + return N, immr, (imms & 0x3f), true } // clearTrailingOnes clears trailing one bits. diff --git a/src/arm/encode.go b/src/arm/encode.go index 3e9aa4b..0b9d45c 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -39,8 +39,8 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { } // reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. -func reg2BitmaskImm(d cpu.Register, n cpu.Register, imm13 int) uint32 { - return uint32(imm13)<<10 | uint32(n)<<5 | uint32(d) +func reg2BitmaskImm(d cpu.Register, n cpu.Register, N int, immr int, imms int) uint32 { + return uint32(N)<<22 | uint32(immr)<<16 | uint32(imms)<<10 | uint32(n)<<5 | uint32(d) } // reg3 encodes an instruction with 3 registers. diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 972d1e3..6f0c122 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -256,6 +256,24 @@ func (c *compiler) compileARM(x asm.Instruction) { } } + case asm.SHIFTL: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftLeftNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + + case asm.SHIFTRS: + switch x.Type { + case asm.TypeRegisterNumber: + operands := c.assembler.Param.RegisterNumber[x.Index] + c.append(arm.ShiftRightSignedNumber(operands.Register, operands.Register, operands.Number&0b111111)) + case asm.TypeRegisterRegister: + panic("not implemented") + } + case asm.RETURN: c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) diff --git a/src/asmc/compileX86.go b/src/asmc/compileX86.go index 122873d..e54435a 100644 --- a/src/asmc/compileX86.go +++ b/src/asmc/compileX86.go @@ -155,6 +155,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftLeftNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.SHIFTRS: @@ -162,6 +164,8 @@ func (c *compiler) compileX86(x asm.Instruction) { case asm.TypeRegisterNumber: operands := c.assembler.Param.RegisterNumber[x.Index] c.code = x86.ShiftRightSignedNumber(c.code, operands.Register, byte(operands.Number)&0b111111) + case asm.TypeRegisterRegister: + panic("not implemented") } case asm.STORE: