From 5f339187e69bbe607cfca5fd87e920fba5dfb126 Mon Sep 17 00:00:00 2001 From: Eduard Urbach <eduard@urbach.dev> Date: Fri, 14 Mar 2025 18:37:39 +0100 Subject: [PATCH] Implemented bitwise operations on arm64 --- src/arm/And.go | 9 ++----- src/arm/And_test.go | 3 ++- src/arm/Move.go | 2 +- src/arm/Or.go | 14 +++++++++++ src/arm/Or_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/Xor.go | 14 +++++++++++ src/arm/Xor_test.go | 49 ++++++++++++++++++++++++++++++++++++++ src/arm/encode.go | 5 ++++ src/asmc/compileARM.go | 54 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/arm/Or.go create mode 100644 src/arm/Or_test.go create mode 100644 src/arm/Xor.go create mode 100644 src/arm/Xor_test.go diff --git a/src/arm/And.go b/src/arm/And.go index add44af..9c23ce0 100644 --- a/src/arm/And.go +++ b/src/arm/And.go @@ -5,14 +5,9 @@ import ( ) // AndRegisterNumber performs a bitwise AND using a register and a number. -func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) (uint32, bool) { imm13, encodable := encodeLogicalImmediate(uint(number)) - - if !encodable { - panic("bitwise and operand can't be encoded as a bitmask immediate") - } - - return 0b100100100<<23 | uint32(imm13)<<10 | reg2(destination, source) + return 0b100100100<<23 | reg2BitmaskImm(destination, source, imm13), encodable } // AndRegisterRegister performs a bitwise AND using two registers. diff --git a/src/arm/And_test.go b/src/arm/And_test.go index 6e684c4..c8e6317 100644 --- a/src/arm/And_test.go +++ b/src/arm/And_test.go @@ -25,7 +25,8 @@ func TestAndRegisterNumber(t *testing.T) { for _, pattern := range usagePatterns { t.Logf("and %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) - code := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) assert.DeepEqual(t, code, pattern.Code) } } diff --git a/src/arm/Move.go b/src/arm/Move.go index 5cbc4c9..e18e81d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 return AddRegisterNumber(destination, source, 0) } - return 0b10101010<<24 | reg3(destination, ZR, source) + return OrRegisterRegister(destination, ZR, source) } // MoveRegisterNumber moves an integer into the given register. diff --git a/src/arm/Or.go b/src/arm/Or.go new file mode 100644 index 0000000..610328b --- /dev/null +++ b/src/arm/Or.go @@ -0,0 +1,14 @@ +package arm + +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 +} + +// OrRegisterRegister performs a bitwise OR using two registers. +func OrRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10101010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go new file mode 100644 index 0000000..0d7aee9 --- /dev/null +++ b/src/arm/Or_test.go @@ -0,0 +1,49 @@ +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 TestOrRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xB2400020}, + {arm.X0, arm.X1, 2, 0xB27F0020}, + {arm.X0, arm.X1, 3, 0xB2400420}, + {arm.X0, arm.X1, 7, 0xB2400820}, + {arm.X0, arm.X1, 16, 0xB27C0020}, + {arm.X0, arm.X1, 255, 0xB2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.OrRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestOrRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xAA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("orr %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.OrRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Xor.go b/src/arm/Xor.go new file mode 100644 index 0000000..3194543 --- /dev/null +++ b/src/arm/Xor.go @@ -0,0 +1,14 @@ +package arm + +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 +} + +// XorRegisterRegister performs a bitwise XOR using two registers. +func XorRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b11001010<<24 | reg3(destination, source, operand) +} diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go new file mode 100644 index 0000000..c8f3dc5 --- /dev/null +++ b/src/arm/Xor_test.go @@ -0,0 +1,49 @@ +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 TestXorRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0xD2400020}, + {arm.X0, arm.X1, 2, 0xD27F0020}, + {arm.X0, arm.X1, 3, 0xD2400420}, + {arm.X0, arm.X1, 7, 0xD2400820}, + {arm.X0, arm.X1, 16, 0xD27C0020}, + {arm.X0, arm.X1, 255, 0xD2401C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.XorRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestXorRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCA020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("eor %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.XorRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/encode.go b/src/arm/encode.go index 5f3e56b..69724a7 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -37,6 +37,11 @@ func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } +// 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) +} + // reg3 encodes an instruction with 3 registers. func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 5658a91..111c708 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -99,6 +99,60 @@ func (c *compiler) compileARM(x asm.Instruction) { c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16)) } + case asm.AND: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.AndRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.AndRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.OR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.OrRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.OrRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + + case asm.XOR: + switch x.Type { + case asm.TypeRegisterNumber: + operand := c.assembler.Param.RegisterNumber[x.Index] + code, encodable := arm.XorRegisterNumber(operand.Register, operand.Register, operand.Number) + + if encodable { + c.append(code) + } else { + tmp := arm.X28 + c.append(arm.MoveRegisterNumber(tmp, operand.Number)) + c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp)) + } + case asm.TypeRegisterRegister: + operand := c.assembler.Param.RegisterRegister[x.Index] + c.append(arm.XorRegisterRegister(operand.Destination, operand.Destination, operand.Source)) + } + case asm.ADD: switch x.Type { case asm.TypeRegisterNumber: