From 76356b2d3855df41b920b4049deb8e1ba2cd23c5 Mon Sep 17 00:00:00 2001
From: Eduard Urbach <eduard@urbach.dev>
Date: Mon, 17 Mar 2025 13:36:42 +0100
Subject: [PATCH] Added fallback for numbers that can't be encoded on arm64

---
 src/arm/Add.go                     |  10 +--
 src/arm/Add_test.go                |   3 +-
 src/arm/Compare.go                 |   2 +-
 src/arm/Compare_test.go            |   3 +-
 src/arm/Move.go                    |   3 +-
 src/arm/Sub.go                     |  10 +--
 src/arm/Sub_test.go                |   3 +-
 src/asmc/{compileARM.go => ARM.go} | 126 +++++++++++++++++------------
 src/asmc/Finalize.go               |   4 +-
 src/asmc/{compileX86.go => X86.go} |  46 +++++------
 src/asmc/bench_test.go             |  18 +++++
 11 files changed, 138 insertions(+), 90 deletions(-)
 rename src/asmc/{compileARM.go => ARM.go} (88%)
 rename src/asmc/{compileX86.go => X86.go} (99%)
 create mode 100644 src/asmc/bench_test.go

diff --git a/src/arm/Add.go b/src/arm/Add.go
index ba641a0..6881689 100644
--- a/src/arm/Add.go
+++ b/src/arm/Add.go
@@ -3,7 +3,7 @@ package arm
 import "git.urbach.dev/cli/q/src/cpu"
 
 // AddRegisterNumber adds a number to a register.
-func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 {
+func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) {
 	return addRegisterNumber(destination, source, number, 0)
 }
 
@@ -13,23 +13,23 @@ 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 {
+func addRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) {
 	shift := uint32(0)
 
 	if number > mask12 {
 		if number&mask12 != 0 {
-			panic("number can't be encoded")
+			return 0, false
 		}
 
 		shift = 1
 		number >>= 12
 
 		if number > mask12 {
-			panic("number can't be encoded")
+			return 0, false
 		}
 	}
 
-	return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number)
+	return flags<<29 | 0b100100010<<23 | shift<<22 | reg2Imm(destination, source, number), true
 }
 
 // addRegisterRegister adds the registers and optionally updates the condition flags based on the result.
diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go
index 3c75ecc..d00d9f6 100644
--- a/src/arm/Add_test.go
+++ b/src/arm/Add_test.go
@@ -21,7 +21,8 @@ func TestAddRegisterNumber(t *testing.T) {
 
 	for _, pattern := range usagePatterns {
 		t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
-		code := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
+		code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
+		assert.True(t, encodable)
 		assert.DeepEqual(t, code, pattern.Code)
 	}
 }
diff --git a/src/arm/Compare.go b/src/arm/Compare.go
index eb5a97e..5d4cf78 100644
--- a/src/arm/Compare.go
+++ b/src/arm/Compare.go
@@ -3,7 +3,7 @@ package arm
 import "git.urbach.dev/cli/q/src/cpu"
 
 // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result.
-func CompareRegisterNumber(register cpu.Register, number int) uint32 {
+func CompareRegisterNumber(register cpu.Register, number int) (code uint32, encodable bool) {
 	if number < 0 {
 		return addRegisterNumber(ZR, register, -number, 1)
 	}
diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go
index 4580aea..16cecdd 100644
--- a/src/arm/Compare_test.go
+++ b/src/arm/Compare_test.go
@@ -22,7 +22,8 @@ func TestCompareRegisterNumber(t *testing.T) {
 
 	for _, pattern := range usagePatterns {
 		t.Logf("cmp %s, %d", pattern.Source, pattern.Number)
-		code := arm.CompareRegisterNumber(pattern.Source, pattern.Number)
+		code, encodable := arm.CompareRegisterNumber(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 e1bfcb3..3c52b9c 100644
--- a/src/arm/Move.go
+++ b/src/arm/Move.go
@@ -72,7 +72,8 @@ func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) {
 // MoveRegisterRegister copies a register to another register.
 func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 {
 	if source == SP || destination == SP {
-		return AddRegisterNumber(destination, source, 0)
+		code, _ := AddRegisterNumber(destination, source, 0)
+		return code
 	}
 
 	return OrRegisterRegister(destination, ZR, source)
diff --git a/src/arm/Sub.go b/src/arm/Sub.go
index 37d0a9e..39982a8 100644
--- a/src/arm/Sub.go
+++ b/src/arm/Sub.go
@@ -3,7 +3,7 @@ package arm
 import "git.urbach.dev/cli/q/src/cpu"
 
 // SubRegisterNumber subtracts a number from the given register.
-func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 {
+func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) (code uint32, encodable bool) {
 	return subRegisterNumber(destination, source, number, 0)
 }
 
@@ -13,23 +13,23 @@ 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 {
+func subRegisterNumber(destination cpu.Register, source cpu.Register, number int, flags uint32) (code uint32, encodable bool) {
 	shift := uint32(0)
 
 	if number > mask12 {
 		if number&mask12 != 0 {
-			panic("number can't be encoded")
+			return 0, false
 		}
 
 		shift = 1
 		number >>= 12
 
 		if number > mask12 {
-			panic("number can't be encoded")
+			return 0, false
 		}
 	}
 
-	return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number)
+	return flags<<29 | 0b110100010<<23 | shift<<22 | reg2Imm(destination, source, number), true
 }
 
 // subRegisterRegister subtracts the registers and optionally updates the condition flags based on the result.
diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go
index e508c75..2256704 100644
--- a/src/arm/Sub_test.go
+++ b/src/arm/Sub_test.go
@@ -22,7 +22,8 @@ func TestSubRegisterNumber(t *testing.T) {
 
 	for _, pattern := range usagePatterns {
 		t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number)
-		code := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
+		code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number)
+		assert.True(t, encodable)
 		assert.DeepEqual(t, code, pattern.Code)
 	}
 }
diff --git a/src/asmc/compileARM.go b/src/asmc/ARM.go
similarity index 88%
rename from src/asmc/compileARM.go
rename to src/asmc/ARM.go
index 6f0c122..c6439b3 100644
--- a/src/asmc/compileARM.go
+++ b/src/asmc/ARM.go
@@ -8,8 +8,45 @@ import (
 	"git.urbach.dev/cli/q/src/asm"
 )
 
-func (c *compiler) compileARM(x asm.Instruction) {
+func (c *compiler) ARM(x asm.Instruction) {
 	switch x.Mnemonic {
+	case asm.MOVE:
+		switch x.Type {
+		case asm.TypeRegisterRegister:
+			operands := c.assembler.Param.RegisterRegister[x.Index]
+			c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source))
+
+		case asm.TypeRegisterNumber:
+			operands := c.assembler.Param.RegisterNumber[x.Index]
+			c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number)
+
+		case asm.TypeRegisterLabel:
+			operands := c.assembler.Param.RegisterLabel[x.Index]
+			position := Address(len(c.code))
+			c.append(arm.LoadAddress(operands.Register, 0))
+
+			if operands.Label.Type == asm.DataLabel {
+				c.dataPointers = append(c.dataPointers, &pointer{
+					Position: position,
+					OpSize:   0,
+					Size:     4,
+					Resolve: func() Address {
+						destination, exists := c.dataLabels[operands.Label.Name]
+
+						if !exists {
+							panic("unknown label")
+						}
+
+						destination += c.dataStart - c.codeStart
+						distance := destination - position + 8
+						return arm.LoadAddress(operands.Register, int(distance))
+					},
+				})
+			} else {
+				panic("not implemented")
+			}
+		}
+
 	case asm.CALL:
 		switch x.Type {
 		case asm.TypeLabel:
@@ -84,11 +121,19 @@ func (c *compiler) compileARM(x asm.Instruction) {
 			}
 		}
 
+	case asm.RETURN:
+		c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16))
+		c.append(arm.Return())
+
+	case asm.SYSCALL:
+		c.append(arm.Syscall())
+
 	case asm.PUSH:
 		switch x.Type {
 		case asm.TypeRegister:
 			operand := c.assembler.Param.Register[x.Index]
-			c.append(arm.SubRegisterNumber(arm.SP, arm.SP, 16))
+			code, _ := arm.SubRegisterNumber(arm.SP, arm.SP, 16)
+			c.append(code)
 			c.append(arm.StoreRegister(operand.Register, arm.SP, 0, 8))
 		}
 
@@ -97,7 +142,8 @@ func (c *compiler) compileARM(x asm.Instruction) {
 		case asm.TypeRegister:
 			operand := c.assembler.Param.Register[x.Index]
 			c.append(arm.LoadRegister(operand.Register, arm.SP, 0, 8))
-			c.append(arm.AddRegisterNumber(arm.SP, arm.SP, 16))
+			code, _ := arm.AddRegisterNumber(arm.SP, arm.SP, 16)
+			c.append(code)
 		}
 
 	case asm.AND:
@@ -158,7 +204,15 @@ func (c *compiler) compileARM(x asm.Instruction) {
 		switch x.Type {
 		case asm.TypeRegisterNumber:
 			operand := c.assembler.Param.RegisterNumber[x.Index]
-			c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number))
+			code, encodable := arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)
+
+			if encodable {
+				c.append(code)
+			} else {
+				tmp := arm.X28
+				c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number)
+				c.append(arm.AddRegisterRegister(operand.Register, operand.Register, tmp))
+			}
 		case asm.TypeRegisterRegister:
 			operand := c.assembler.Param.RegisterRegister[x.Index]
 			c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source))
@@ -168,7 +222,15 @@ func (c *compiler) compileARM(x asm.Instruction) {
 		switch x.Type {
 		case asm.TypeRegisterNumber:
 			operand := c.assembler.Param.RegisterNumber[x.Index]
-			c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number))
+			code, encodable := arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)
+
+			if encodable {
+				c.append(code)
+			} else {
+				tmp := arm.X28
+				c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number)
+				c.append(arm.SubRegisterRegister(operand.Register, operand.Register, tmp))
+			}
 		case asm.TypeRegisterRegister:
 			operand := c.assembler.Param.RegisterRegister[x.Index]
 			c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source))
@@ -178,7 +240,15 @@ func (c *compiler) compileARM(x asm.Instruction) {
 		switch x.Type {
 		case asm.TypeRegisterNumber:
 			operand := c.assembler.Param.RegisterNumber[x.Index]
-			c.append(arm.CompareRegisterNumber(operand.Register, operand.Number))
+			code, encodable := arm.CompareRegisterNumber(operand.Register, operand.Number)
+
+			if encodable {
+				c.append(code)
+			} else {
+				tmp := arm.X28
+				c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number)
+				c.append(arm.CompareRegisterRegister(operand.Register, tmp))
+			}
 		case asm.TypeRegisterRegister:
 			operand := c.assembler.Param.RegisterRegister[x.Index]
 			c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source))
@@ -219,43 +289,6 @@ func (c *compiler) compileARM(x asm.Instruction) {
 	case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP:
 		c.jumpARM(x)
 
-	case asm.MOVE:
-		switch x.Type {
-		case asm.TypeRegisterRegister:
-			operands := c.assembler.Param.RegisterRegister[x.Index]
-			c.append(arm.MoveRegisterRegister(operands.Destination, operands.Source))
-
-		case asm.TypeRegisterNumber:
-			operands := c.assembler.Param.RegisterNumber[x.Index]
-			c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number)
-
-		case asm.TypeRegisterLabel:
-			operands := c.assembler.Param.RegisterLabel[x.Index]
-			position := Address(len(c.code))
-			c.append(arm.LoadAddress(operands.Register, 0))
-
-			if operands.Label.Type == asm.DataLabel {
-				c.dataPointers = append(c.dataPointers, &pointer{
-					Position: position,
-					OpSize:   0,
-					Size:     4,
-					Resolve: func() Address {
-						destination, exists := c.dataLabels[operands.Label.Name]
-
-						if !exists {
-							panic("unknown label")
-						}
-
-						destination += c.dataStart - c.codeStart
-						distance := destination - position + 8
-						return arm.LoadAddress(operands.Register, int(distance))
-					},
-				})
-			} else {
-				panic("not implemented")
-			}
-		}
-
 	case asm.SHIFTL:
 		switch x.Type {
 		case asm.TypeRegisterNumber:
@@ -274,13 +307,6 @@ func (c *compiler) compileARM(x asm.Instruction) {
 			panic("not implemented")
 		}
 
-	case asm.RETURN:
-		c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16))
-		c.append(arm.Return())
-
-	case asm.SYSCALL:
-		c.append(arm.Syscall())
-
 	default:
 		panic("unknown mnemonic: " + x.Mnemonic.String())
 	}
diff --git a/src/asmc/Finalize.go b/src/asmc/Finalize.go
index e2b935c..ec9e678 100644
--- a/src/asmc/Finalize.go
+++ b/src/asmc/Finalize.go
@@ -28,12 +28,12 @@ func Finalize(a *asm.Assembler, dlls dll.List) ([]byte, []byte) {
 	switch config.TargetArch {
 	case config.ARM:
 		for _, x := range a.Instructions {
-			c.compileARM(x)
+			c.ARM(x)
 		}
 
 	case config.X86:
 		for _, x := range a.Instructions {
-			c.compileX86(x)
+			c.X86(x)
 		}
 	}
 
diff --git a/src/asmc/compileX86.go b/src/asmc/X86.go
similarity index 99%
rename from src/asmc/compileX86.go
rename to src/asmc/X86.go
index e54435a..448ec41 100644
--- a/src/asmc/compileX86.go
+++ b/src/asmc/X86.go
@@ -5,8 +5,30 @@ import (
 	"git.urbach.dev/cli/q/src/x86"
 )
 
-func (c *compiler) compileX86(x asm.Instruction) {
+func (c *compiler) X86(x asm.Instruction) {
 	switch x.Mnemonic {
+	case asm.MOVE:
+		c.move(x)
+
+	case asm.CALL:
+		c.call(x)
+
+	case asm.LABEL:
+		label := c.assembler.Param.Label[x.Index]
+		c.codeLabels[label.Name] = Address(len(c.code))
+
+	case asm.LOAD:
+		c.load(x)
+
+	case asm.STORE:
+		c.store(x)
+
+	case asm.RETURN:
+		c.code = x86.Return(c.code)
+
+	case asm.SYSCALL:
+		c.code = x86.Syscall(c.code)
+
 	case asm.ADD:
 		switch x.Type {
 		case asm.TypeRegisterNumber:
@@ -81,9 +103,6 @@ func (c *compiler) compileX86(x asm.Instruction) {
 			}
 		}
 
-	case asm.CALL:
-		c.call(x)
-
 	case asm.COMMENT:
 		return
 
@@ -103,16 +122,6 @@ func (c *compiler) compileX86(x asm.Instruction) {
 	case asm.JE, asm.JNE, asm.JG, asm.JGE, asm.JL, asm.JLE, asm.JUMP:
 		c.jumpX86(x)
 
-	case asm.LABEL:
-		label := c.assembler.Param.Label[x.Index]
-		c.codeLabels[label.Name] = Address(len(c.code))
-
-	case asm.LOAD:
-		c.load(x)
-
-	case asm.MOVE:
-		c.move(x)
-
 	case asm.NEGATE:
 		switch x.Type {
 		case asm.TypeRegister:
@@ -147,9 +156,6 @@ func (c *compiler) compileX86(x asm.Instruction) {
 			c.code = x86.PushRegister(c.code, operands.Register)
 		}
 
-	case asm.RETURN:
-		c.code = x86.Return(c.code)
-
 	case asm.SHIFTL:
 		switch x.Type {
 		case asm.TypeRegisterNumber:
@@ -168,12 +174,6 @@ func (c *compiler) compileX86(x asm.Instruction) {
 			panic("not implemented")
 		}
 
-	case asm.STORE:
-		c.store(x)
-
-	case asm.SYSCALL:
-		c.code = x86.Syscall(c.code)
-
 	case asm.XOR:
 		switch x.Type {
 		case asm.TypeRegisterNumber:
diff --git a/src/asmc/bench_test.go b/src/asmc/bench_test.go
new file mode 100644
index 0000000..179c6c1
--- /dev/null
+++ b/src/asmc/bench_test.go
@@ -0,0 +1,18 @@
+package asmc_test
+
+import (
+	"testing"
+
+	"git.urbach.dev/cli/q/src/asm"
+	"git.urbach.dev/cli/q/src/asmc"
+	"git.urbach.dev/cli/q/src/cpu"
+)
+
+func BenchmarkFinalize(b *testing.B) {
+	a := asm.Assembler{}
+	a.RegisterNumber(asm.MOVE, cpu.Register(0), 0)
+
+	for b.Loop() {
+		asmc.Finalize(&a, nil)
+	}
+}