From e123a26a1dff842afb993bf1b217c4487cc84713 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 14 Mar 2025 17:45:39 +0100 Subject: [PATCH] Implemented bitwise AND on arm64 --- src/arm/Add.go | 2 +- src/arm/And.go | 21 ++++++++++++++++++++ src/arm/And_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/arm/Sub.go | 2 +- src/arm/bitmask.go | 33 +++++++++++++++++++++++++++++++ src/arm/encode.go | 18 ++++++++++++++--- 6 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 src/arm/And.go create mode 100644 src/arm/And_test.go create mode 100644 src/arm/bitmask.go diff --git a/src/arm/Add.go b/src/arm/Add.go index 4cacfc0..3ae49e3 100644 --- a/src/arm/Add.go +++ b/src/arm/Add.go @@ -14,7 +14,7 @@ func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand // addRegisterNumber adds the register and optionally updates the condition flags based on the result. func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b100100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b100100010<<23 | reg2Imm(destination, source, number) } // addRegisterRegister adds the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/And.go b/src/arm/And.go new file mode 100644 index 0000000..add44af --- /dev/null +++ b/src/arm/And.go @@ -0,0 +1,21 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// AndRegisterNumber performs a bitwise AND using a register and a number. +func AndRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { + 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) +} + +// AndRegisterRegister performs a bitwise AND using two registers. +func AndRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10001010<<24 | reg3Imm(destination, source, operand, 0) +} diff --git a/src/arm/And_test.go b/src/arm/And_test.go new file mode 100644 index 0000000..6e684c4 --- /dev/null +++ b/src/arm/And_test.go @@ -0,0 +1,48 @@ +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 TestAndRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X1, 1, 0x92400020}, + {arm.X0, arm.X1, 2, 0x927F0020}, + {arm.X0, arm.X1, 3, 0x92400420}, + {arm.X0, arm.X1, 7, 0x92400820}, + {arm.X0, arm.X1, 16, 0x927C0020}, + {arm.X0, arm.X1, 255, 0x92401C20}, + } + + 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) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestAndRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8A020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("and %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AndRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Sub.go b/src/arm/Sub.go index bf84f50..ef2377f 100644 --- a/src/arm/Sub.go +++ b/src/arm/Sub.go @@ -14,7 +14,7 @@ func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand // subRegisterNumber subtracts the register and optionally updates the condition flags based on the result. func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) uint32 { - return flags<<29 | 0b110100010<<23 | reg2imm(destination, source, number) + return flags<<29 | 0b110100010<<23 | reg2Imm(destination, source, number) } // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result. diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go new file mode 100644 index 0000000..76668a2 --- /dev/null +++ b/src/arm/bitmask.go @@ -0,0 +1,33 @@ +package arm + +import "math/bits" + +// encodeLogicalImmediate encodes a bitmask immediate. +// The algorithm used here was made by Dougall Johnson. +func encodeLogicalImmediate(val uint) (int, bool) { + if val == 0 || ^val == 0 { + return 0, false + } + + rotation := bits.TrailingZeros(clearTrailingOnes(val)) + normalized := bits.RotateLeft(val, -(rotation & 63)) + + zeroes := bits.LeadingZeros(normalized) + ones := bits.TrailingZeros(^normalized) + size := zeroes + ones + + immr := -rotation & (size - 1) + imms := -(size << 1) | (ones - 1) + N := (size >> 6) + + if bits.RotateLeft(val, -(size&63)) != val { + return 0, false + } + + return N<<12 | immr<<6 | (imms & 0x3f), true +} + +// clearTrailingOnes clears trailing one bits. +func clearTrailingOnes(x uint) uint { + return x & (x + 1) +} diff --git a/src/arm/encode.go b/src/arm/encode.go index bab6330..5f3e56b 100644 --- a/src/arm/encode.go +++ b/src/arm/encode.go @@ -1,6 +1,8 @@ package arm -import "git.urbach.dev/cli/q/src/cpu" +import ( + "git.urbach.dev/cli/q/src/cpu" +) const ( mask6 = 0b111111 @@ -25,8 +27,13 @@ func regImm(d cpu.Register, imm16 int) uint32 { return uint32(imm16&mask16)<<5 | uint32(d) } -// reg2imm encodes an instruction with 2 registers and an immediate. -func reg2imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { +// reg2 encodes an instruction with 2 registers. +func reg2(d cpu.Register, n cpu.Register) uint32 { + return uint32(n)<<5 | uint32(d) +} + +// reg2Imm encodes an instruction with 2 registers and an immediate. +func reg2Imm(d cpu.Register, n cpu.Register, imm12 int) uint32 { return uint32(imm12&mask12)<<10 | uint32(n)<<5 | uint32(d) } @@ -35,6 +42,11 @@ func reg3(d cpu.Register, n cpu.Register, m cpu.Register) uint32 { return uint32(m)<<16 | uint32(n)<<5 | uint32(d) } +// reg3Imm encodes an instruction with 3 registers. +func reg3Imm(d cpu.Register, n cpu.Register, m cpu.Register, imm6 int) uint32 { + return uint32(m)<<16 | uint32(imm6&mask6)<<10 | uint32(n)<<5 | uint32(d) +} + // reg4 encodes an instruction with 4 registers. func reg4(d cpu.Register, n cpu.Register, m cpu.Register, a cpu.Register) uint32 { return uint32(m)<<16 | uint32(a)<<10 | uint32(n)<<5 | uint32(d)