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: