From 3ae47f93ebca4a04f0751a1a35977b3cc8c03959 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 23 Jun 2025 11:49:39 +0200 Subject: [PATCH] Added arm package --- src/arm/Add.go | 38 +++++++++++ src/arm/Add_test.go | 45 ++++++++++++++ src/arm/And.go | 16 +++++ src/arm/And_test.go | 49 +++++++++++++++ src/arm/Call.go | 8 +++ src/arm/Call_test.go | 25 ++++++++ src/arm/Compare.go | 17 +++++ src/arm/Compare_test.go | 45 ++++++++++++++ src/arm/Div.go | 8 +++ src/arm/Div_test.go | 26 ++++++++ src/arm/Jump.go | 41 ++++++++++++ src/arm/Jump_test.go | 68 ++++++++++++++++++++ src/arm/Load.go | 19 ++++++ src/arm/LoadAddress.go | 10 +++ src/arm/LoadAddress_test.go | 26 ++++++++ src/arm/LoadPair.go | 10 +++ src/arm/LoadPair_test.go | 28 +++++++++ src/arm/Load_test.go | 39 ++++++++++++ src/arm/Move.go | 100 +++++++++++++++++++++++++++++ src/arm/Move_test.go | 121 ++++++++++++++++++++++++++++++++++++ src/arm/Mul.go | 13 ++++ src/arm/Mul_test.go | 45 ++++++++++++++ src/arm/Negate.go | 8 +++ src/arm/Negate_test.go | 26 ++++++++ src/arm/Nop.go | 6 ++ src/arm/Or.go | 14 +++++ src/arm/Or_test.go | 49 +++++++++++++++ src/arm/Registers.go | 42 +++++++++++++ src/arm/Return.go | 6 ++ src/arm/Shift.go | 15 +++++ src/arm/Shift_test.go | 52 ++++++++++++++++ src/arm/Store.go | 21 +++++++ src/arm/StorePair.go | 12 ++++ src/arm/StorePair_test.go | 28 +++++++++ src/arm/Store_test.go | 34 ++++++++++ src/arm/Sub.go | 38 +++++++++++ src/arm/Sub_test.go | 46 ++++++++++++++ src/arm/Syscall.go | 6 ++ src/arm/Xor.go | 14 +++++ src/arm/Xor_test.go | 49 +++++++++++++++ src/arm/arm_test.go | 37 +++++++++++ src/arm/bitmask.go | 33 ++++++++++ src/arm/condition.go | 22 +++++++ src/arm/encode.go | 51 +++++++++++++++ src/arm/mask.go | 11 ++++ 45 files changed, 1417 insertions(+) create mode 100644 src/arm/Add.go create mode 100644 src/arm/Add_test.go create mode 100644 src/arm/And.go create mode 100644 src/arm/And_test.go create mode 100644 src/arm/Call.go create mode 100644 src/arm/Call_test.go create mode 100644 src/arm/Compare.go create mode 100644 src/arm/Compare_test.go create mode 100644 src/arm/Div.go create mode 100644 src/arm/Div_test.go create mode 100644 src/arm/Jump.go create mode 100644 src/arm/Jump_test.go create mode 100644 src/arm/Load.go create mode 100644 src/arm/LoadAddress.go create mode 100644 src/arm/LoadAddress_test.go create mode 100644 src/arm/LoadPair.go create mode 100644 src/arm/LoadPair_test.go create mode 100644 src/arm/Load_test.go create mode 100644 src/arm/Move.go create mode 100644 src/arm/Move_test.go create mode 100644 src/arm/Mul.go create mode 100644 src/arm/Mul_test.go create mode 100644 src/arm/Negate.go create mode 100644 src/arm/Negate_test.go create mode 100644 src/arm/Nop.go create mode 100644 src/arm/Or.go create mode 100644 src/arm/Or_test.go create mode 100644 src/arm/Registers.go create mode 100644 src/arm/Return.go create mode 100644 src/arm/Shift.go create mode 100644 src/arm/Shift_test.go create mode 100644 src/arm/Store.go create mode 100644 src/arm/StorePair.go create mode 100644 src/arm/StorePair_test.go create mode 100644 src/arm/Store_test.go create mode 100644 src/arm/Sub.go create mode 100644 src/arm/Sub_test.go create mode 100644 src/arm/Syscall.go create mode 100644 src/arm/Xor.go create mode 100644 src/arm/Xor_test.go create mode 100644 src/arm/arm_test.go create mode 100644 src/arm/bitmask.go create mode 100644 src/arm/condition.go create mode 100644 src/arm/encode.go create mode 100644 src/arm/mask.go diff --git a/src/arm/Add.go b/src/arm/Add.go new file mode 100644 index 0000000..fb03d5a --- /dev/null +++ b/src/arm/Add.go @@ -0,0 +1,38 @@ +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) (code uint32, encodable bool) { + return addRegisterNumber(destination, source, number, 0) +} + +// AddRegisterRegister adds a register to a register. +func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return addRegisterRegister(destination, source, operand, 0) +} + +// 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) (code uint32, encodable bool) { + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + return 0, false + } + + shift = 1 + number >>= 12 + + if number > mask12 { + return 0, false + } + } + + 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. +func addRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b10001011000<<21 | reg3(destination, source, operand) +} \ No newline at end of file diff --git a/src/arm/Add_test.go b/src/arm/Add_test.go new file mode 100644 index 0000000..32c98f4 --- /dev/null +++ b/src/arm/Add_test.go @@ -0,0 +1,45 @@ +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 TestAddRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0x91000400}, + {arm.X0, arm.X0, 0x1000, 0x91400400}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.AddRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.Equal(t, code, pattern.Code) + } +} + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x8B020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.AddRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/And.go b/src/arm/And.go new file mode 100644 index 0000000..bb140f3 --- /dev/null +++ b/src/arm/And.go @@ -0,0 +1,16 @@ +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, bool) { + 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. +func AndRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10001010<<24 | reg3Imm(destination, source, operand, 0) +} \ No newline at end of file diff --git a/src/arm/And_test.go b/src/arm/And_test.go new file mode 100644 index 0000000..836107a --- /dev/null +++ b/src/arm/And_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 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, encodable := arm.AndRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.Equal(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.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Call.go b/src/arm/Call.go new file mode 100644 index 0000000..d311431 --- /dev/null +++ b/src/arm/Call.go @@ -0,0 +1,8 @@ +package arm + +// Call branches to a PC-relative offset, setting the register X30 to PC+4. +// The offset starts from the address of this instruction and is encoded as "imm26" times 4. +// This instruction is also known as BL (branch with link). +func Call(offset int) uint32 { + return uint32(0b100101<<26) | uint32(offset&mask26) +} \ No newline at end of file diff --git a/src/arm/Call_test.go b/src/arm/Call_test.go new file mode 100644 index 0000000..206f90e --- /dev/null +++ b/src/arm/Call_test.go @@ -0,0 +1,25 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestCall(t *testing.T) { + usagePatterns := []struct { + Offset int + Code uint32 + }{ + {0, 0x94000000}, + {1, 0x94000001}, + {-1, 0x97FFFFFF}, + } + + for _, pattern := range usagePatterns { + t.Logf("bl %d", pattern.Offset) + code := arm.Call(pattern.Offset) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Compare.go b/src/arm/Compare.go new file mode 100644 index 0000000..989483f --- /dev/null +++ b/src/arm/Compare.go @@ -0,0 +1,17 @@ +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) (code uint32, encodable bool) { + if number < 0 { + return addRegisterNumber(ZR, register, -number, 1) + } + + return subRegisterNumber(ZR, register, number, 1) +} + +// CompareRegisterRegister is an alias for a subtraction that updates the conditional flags and discards the result. +func CompareRegisterRegister(reg1 cpu.Register, reg2 cpu.Register) uint32 { + return subRegisterRegister(ZR, reg1, reg2, 1) +} \ No newline at end of file diff --git a/src/arm/Compare_test.go b/src/arm/Compare_test.go new file mode 100644 index 0000000..7184e59 --- /dev/null +++ b/src/arm/Compare_test.go @@ -0,0 +1,45 @@ +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 TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 0, 0xF100001F}, + {arm.X0, 1, 0xF100041F}, + {arm.X0, -1, 0xB100041F}, + {arm.X0, 0x1000, 0xF140041F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %d", pattern.Source, pattern.Number) + code, encodable := arm.CompareRegisterNumber(pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.Equal(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xEB01001F}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := arm.CompareRegisterRegister(pattern.Left, pattern.Right) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Div.go b/src/arm/Div.go new file mode 100644 index 0000000..f953a18 --- /dev/null +++ b/src/arm/Div.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// DivSigned divides source by operand and stores the value in the destination. +func DivSigned(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand) +} \ No newline at end of file diff --git a/src/arm/Div_test.go b/src/arm/Div_test.go new file mode 100644 index 0000000..10cc176 --- /dev/null +++ b/src/arm/Div_test.go @@ -0,0 +1,26 @@ +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 TestDivSigned(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9AC20C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Jump.go b/src/arm/Jump.go new file mode 100644 index 0000000..29178cc --- /dev/null +++ b/src/arm/Jump.go @@ -0,0 +1,41 @@ +package arm + +// Jump continues program flow at the new offset. +func Jump(offset int) uint32 { + return 0b000101<<26 | uint32(offset&mask26) +} + +// JumpIfEqual jumps if the result was equal. +func JumpIfEqual(offset int) uint32 { + return branchCond(EQ, offset) +} + +// JumpIfNotEqual jumps if the result was not equal. +func JumpIfNotEqual(offset int) uint32 { + return branchCond(NE, offset) +} + +// JumpIfGreater jumps if the result was greater. +func JumpIfGreater(offset int) uint32 { + return branchCond(GT, offset) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func JumpIfGreaterOrEqual(offset int) uint32 { + return branchCond(GE, offset) +} + +// JumpIfLess jumps if the result was less. +func JumpIfLess(offset int) uint32 { + return branchCond(LS, offset) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func JumpIfLessOrEqual(offset int) uint32 { + return branchCond(LE, offset) +} + +// branchCond performs a conditional branch to a PC-relative offset. +func branchCond(cond condition, imm19 int) uint32 { + return 0b01010100<<24 | uint32(imm19&mask19)<<5 | uint32(cond) +} \ No newline at end of file diff --git a/src/arm/Jump_test.go b/src/arm/Jump_test.go new file mode 100644 index 0000000..6991033 --- /dev/null +++ b/src/arm/Jump_test.go @@ -0,0 +1,68 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestJump(t *testing.T) { + usagePatterns := []struct { + Type byte + Offset int + Code uint32 + }{ + {0, 0, 0x14000000}, + {0, 1, 0x14000001}, + {0, -1, 0x17FFFFFF}, + + {1, 0, 0x54000000}, + {1, 1, 0x54000020}, + {1, -1, 0x54FFFFE0}, + + {2, 0, 0x54000001}, + {2, 1, 0x54000021}, + {2, -1, 0x54FFFFE1}, + + {3, 0, 0x5400000C}, + {3, 1, 0x5400002C}, + {3, -1, 0x54FFFFEC}, + + {4, 0, 0x5400000A}, + {4, 1, 0x5400002A}, + {4, -1, 0x54FFFFEA}, + + {5, 0, 0x54000009}, + {5, 1, 0x54000029}, + {5, -1, 0x54FFFFE9}, + + {6, 0, 0x5400000D}, + {6, 1, 0x5400002D}, + {6, -1, 0x54FFFFED}, + } + + for _, pattern := range usagePatterns { + t.Logf("b %d", pattern.Offset) + var code uint32 + + switch pattern.Type { + case 0: + code = arm.Jump(pattern.Offset) + case 1: + code = arm.JumpIfEqual(pattern.Offset) + case 2: + code = arm.JumpIfNotEqual(pattern.Offset) + case 3: + code = arm.JumpIfGreater(pattern.Offset) + case 4: + code = arm.JumpIfGreaterOrEqual(pattern.Offset) + case 5: + code = arm.JumpIfLess(pattern.Offset) + case 6: + code = arm.JumpIfLessOrEqual(pattern.Offset) + } + + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Load.go b/src/arm/Load.go new file mode 100644 index 0000000..8454f42 --- /dev/null +++ b/src/arm/Load.go @@ -0,0 +1,19 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadRegister loads from memory into a register. +func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { + common := 1<<22 | memory(destination, base, offset) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + default: + return 0b11111<<27 | common + } +} \ No newline at end of file diff --git a/src/arm/LoadAddress.go b/src/arm/LoadAddress.go new file mode 100644 index 0000000..1d60287 --- /dev/null +++ b/src/arm/LoadAddress.go @@ -0,0 +1,10 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadAddress calculates the address with the PC-relative offset and writes the result to the destination register. +func LoadAddress(destination cpu.Register, offset int) uint32 { + hi := uint32(offset) >> 2 + lo := uint32(offset) & 0b11 + return lo<<29 | 0b10000<<24 | hi<<5 | uint32(destination) +} \ No newline at end of file diff --git a/src/arm/LoadAddress_test.go b/src/arm/LoadAddress_test.go new file mode 100644 index 0000000..2a0be17 --- /dev/null +++ b/src/arm/LoadAddress_test.go @@ -0,0 +1,26 @@ +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 TestLoadAddress(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Number int + Code uint32 + }{ + {arm.X0, 56, 0x100001C0}, + {arm.X1, 80, 0x10000281}, + } + + for _, pattern := range usagePatterns { + t.Logf("adr %s, %d", pattern.Destination, pattern.Number) + code := arm.LoadAddress(pattern.Destination, pattern.Number) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go new file mode 100644 index 0000000..14c3d49 --- /dev/null +++ b/src/arm/LoadPair.go @@ -0,0 +1,10 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// LoadPair calculates an address from a base register value and an immediate offset, +// loads two 64-bit doublewords from memory, and writes them to two registers. +// This is the post-index version of the instruction so the offset is applied to the base register after the memory access. +func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8) +} \ No newline at end of file diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go new file mode 100644 index 0000000..dfd2c29 --- /dev/null +++ b/src/arm/LoadPair_test.go @@ -0,0 +1,28 @@ +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 TestLoadPair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, 32, 0xA8C27BFD}, + {arm.FP, arm.LR, arm.SP, 16, 0xA8C17BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldp %s, %s, [%s], #%d", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.LoadPair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Load_test.go b/src/arm/Load_test.go new file mode 100644 index 0000000..a7a4e69 --- /dev/null +++ b/src/arm/Load_test.go @@ -0,0 +1,39 @@ +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 TestLoadRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Base cpu.Register + Offset int + Length byte + Code uint32 + }{ + {arm.X0, arm.X1, -8, 1, 0x385F8020}, + {arm.X1, arm.X0, -8, 1, 0x385F8001}, + {arm.X0, arm.X1, -8, 2, 0x785F8020}, + {arm.X1, arm.X0, -8, 2, 0x785F8001}, + {arm.X0, arm.X1, -8, 4, 0xB85F8020}, + {arm.X1, arm.X0, -8, 4, 0xB85F8001}, + {arm.X0, arm.X1, -8, 8, 0xF85F8020}, + {arm.X1, arm.X0, -8, 8, 0xF85F8001}, + {arm.X2, arm.X1, -8, 8, 0xF85F8022}, + {arm.X2, arm.X1, 0, 8, 0xF8400022}, + {arm.X2, arm.X1, 8, 8, 0xF8408022}, + {arm.X2, arm.X1, -256, 8, 0xF8500022}, + {arm.X2, arm.X1, 255, 8, 0xF84FF022}, + } + + for _, pattern := range usagePatterns { + t.Logf("ldur %s, [%s, %d] %db", pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + code := arm.LoadRegister(pattern.Destination, pattern.Base, pattern.Offset, pattern.Length) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Move.go b/src/arm/Move.go new file mode 100644 index 0000000..b442118 --- /dev/null +++ b/src/arm/Move.go @@ -0,0 +1,100 @@ +package arm + +import ( + "encoding/binary" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/sizeof" +) + +// MoveRegisterNumber moves a number into the given register. +func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte { + instruction, encodable := MoveRegisterNumberSI(destination, number) + + if encodable { + return binary.LittleEndian.AppendUint32(code, instruction) + } + + return MoveRegisterNumberMI(code, destination, number) +} + +// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions. +func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte { + movz := MoveZero(destination, 0, uint16(number)) + code = binary.LittleEndian.AppendUint32(code, movz) + num := uint64(number) + halfword := 1 + + for { + num >>= 16 + + if num == 0 { + return code + } + + movk := MoveKeep(destination, halfword, uint16(num)) + code = binary.LittleEndian.AppendUint32(code, movk) + halfword++ + } +} + +// MoveRegisterNumberSI moves a number into the given register using a single instruction. +func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) { + if sizeof.Signed(number) <= 2 { + if number < 0 { + return MoveInvertedNumber(destination, uint16(^number), 0), true + } + + return MoveZero(destination, 0, uint16(number)), true + } + + if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 { + return MoveInvertedNumber(destination, uint16((^number)>>48), 3), true + } + + code, encodable := MoveBitmaskNumber(destination, number) + + if encodable { + return code, true + } + + if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 { + return MoveInvertedNumber(destination, uint16((^number)>>32), 2), true + } + + if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 { + return MoveInvertedNumber(destination, uint16((^number)>>16), 1), true + } + + return 0, false +} + +// MoveRegisterRegister copies a register to another register. +func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 { + if source == SP || destination == SP { + code, _ := AddRegisterNumber(destination, source, 0) + return code + } + + return OrRegisterRegister(destination, ZR, source) +} + +// MoveBitmaskNumber moves a bitmask immediate value to a register. +func MoveBitmaskNumber(destination cpu.Register, number int) (uint32, bool) { + return OrRegisterNumber(destination, ZR, number) +} + +// MoveInvertedNumber moves an inverted 16-bit immediate value to a register. +func MoveInvertedNumber(destination cpu.Register, number uint16, shift uint32) uint32 { + return 0b100100101<<23 | shift<<21 | regImm(destination, number) +} + +// MoveKeep moves a 16-bit integer into the given register and keeps all other bits. +func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 { + return 0b111100101<<23 | regImmHw(destination, halfword, number) +} + +// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero. +func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 { + return 0b110100101<<23 | regImmHw(destination, halfword, number) +} \ No newline at end of file diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go new file mode 100644 index 0000000..cd94d7b --- /dev/null +++ b/src/arm/Move_test.go @@ -0,0 +1,121 @@ +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 TestMoveRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, 0xAA0103E0}, + {arm.X1, arm.X0, 0xAA0003E1}, + {arm.FP, arm.SP, 0x910003FD}, + {arm.SP, arm.FP, 0x910003BF}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %s", pattern.Destination, pattern.Source) + code := arm.MoveRegisterRegister(pattern.Destination, pattern.Source) + assert.Equal(t, code, pattern.Code) + } +} + +func TestMoveRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint64 + Code []byte + }{ + {arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}}, + {arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}}, + {arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number) + code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number)) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestMoveRegisterNumberSI(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint64 + Code uint32 + }{ + // MOVZ + {arm.X0, 0x0, 0xD2800000}, + {arm.X0, 0x1, 0xD2800020}, + {arm.X0, 0x1000, 0xD2820000}, + + // MOV (bitmask immediate) + {arm.X0, 0x1FFFF, 0xB24043E0}, + {arm.X0, 0x7FFFFFFF, 0xB2407BE0}, + {arm.X0, 0xFFFFFFFF, 0xB2407FE0}, + {arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0}, + + // MOV (inverted wide immediate) + {arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000}, + {arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000}, + {arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler + {arm.X0, 0x2FFFF, 0x92BFFFA0}, // not encodable in the GNU assembler + + // Not encodable + {arm.X0, 0xCAFEBABE, 0}, + {arm.X0, 0xDEADC0DE, 0}, + } + + for _, pattern := range usagePatterns { + t.Logf("mov %s, %d", pattern.Register, pattern.Number) + code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number)) + + if pattern.Code != 0 { + assert.True(t, encodable) + assert.Equal(t, code, pattern.Code) + } else { + assert.False(t, encodable) + } + } +} + +func TestMoveKeep(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code uint32 + }{ + {arm.X0, 0, 0xF2800000}, + {arm.X0, 1, 0xF2800020}, + } + + for _, pattern := range usagePatterns { + t.Logf("movk %s, %d", pattern.Register, pattern.Number) + code := arm.MoveKeep(pattern.Register, 0, pattern.Number) + assert.Equal(t, code, pattern.Code) + } +} + +func TestMoveZero(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number uint16 + Code uint32 + }{ + {arm.X0, 0, 0xD2800000}, + {arm.X0, 1, 0xD2800020}, + } + + for _, pattern := range usagePatterns { + t.Logf("movz %s, %d", pattern.Register, pattern.Number) + code := arm.MoveZero(pattern.Register, 0, pattern.Number) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Mul.go b/src/arm/Mul.go new file mode 100644 index 0000000..4b383ae --- /dev/null +++ b/src/arm/Mul.go @@ -0,0 +1,13 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// MulRegisterRegister multiplies `multiplicand` with `multiplier` and saves the result in `destination` +func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register) uint32 { + return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR) +} + +// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts the product from `minuend` and saves the result in `destination`. +func MultiplySubtract(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register, minuend cpu.Register) uint32 { + return 0b10011011000<<21 | 1<<15 | reg4(destination, multiplicand, multiplier, minuend) +} \ No newline at end of file diff --git a/src/arm/Mul_test.go b/src/arm/Mul_test.go new file mode 100644 index 0000000..36de8f0 --- /dev/null +++ b/src/arm/Mul_test.go @@ -0,0 +1,45 @@ +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 TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0x9B027C20}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.MulRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.Equal(t, code, pattern.Code) + } +} + +func TestMultiplySubtract(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Extra cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, arm.X3, 0x9B028C20}, + {arm.X3, arm.X0, arm.X2, arm.X1, 0x9B028403}, + } + + for _, pattern := range usagePatterns { + t.Logf("msub %s, %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + code := arm.MultiplySubtract(pattern.Destination, pattern.Source, pattern.Operand, pattern.Extra) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Negate.go b/src/arm/Negate.go new file mode 100644 index 0000000..3d071fd --- /dev/null +++ b/src/arm/Negate.go @@ -0,0 +1,8 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +// NegateRegister negates the value in the source register and writes it to the destination register. +func NegateRegister(destination cpu.Register, source cpu.Register) uint32 { + return 0b11001011<<24 | reg3Imm(destination, ZR, source, 0) +} \ No newline at end of file diff --git a/src/arm/Negate_test.go b/src/arm/Negate_test.go new file mode 100644 index 0000000..76dcdcf --- /dev/null +++ b/src/arm/Negate_test.go @@ -0,0 +1,26 @@ +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 TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Code uint32 + }{ + {arm.X0, arm.X0, 0xCB0003E0}, + {arm.X1, arm.X1, 0xCB0103E1}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s, %s", pattern.Destination, pattern.Source) + code := arm.NegateRegister(pattern.Destination, pattern.Source) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Nop.go b/src/arm/Nop.go new file mode 100644 index 0000000..e252847 --- /dev/null +++ b/src/arm/Nop.go @@ -0,0 +1,6 @@ +package arm + +// Nop does nothing. This can be used for alignment purposes. +func Nop() uint32 { + return 0xD503201F +} \ No newline at end of file diff --git a/src/arm/Or.go b/src/arm/Or.go new file mode 100644 index 0000000..cf9bd9b --- /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) { + 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. +func OrRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b10101010<<24 | reg3(destination, source, operand) +} \ No newline at end of file diff --git a/src/arm/Or_test.go b/src/arm/Or_test.go new file mode 100644 index 0000000..f9eb276 --- /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.Equal(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.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Registers.go b/src/arm/Registers.go new file mode 100644 index 0000000..368850b --- /dev/null +++ b/src/arm/Registers.go @@ -0,0 +1,42 @@ +package arm + +import "git.urbach.dev/cli/q/src/cpu" + +const ( + X0 cpu.Register = iota // Function arguments and return values [0-7] + X1 + X2 + X3 + X4 + X5 + X6 + X7 + X8 // Indirect result location register (used to pass a pointer to a structure return value) + X9 // Temporary registers (caller-saved, used for general computation) [9-15] + X10 + X11 + X12 + X13 + X14 + X15 + X16 // Intra-procedure call scratch registers [16-17] + X17 + X18 // Platform register (reserved by the platform ABI for thread-local storage) + X19 // Callee-saved registers (must be preserved across function calls) [19-28] + X20 + X21 + X22 + X23 + X24 + X25 + X26 + X27 + X28 + FP // Frame pointer + LR // Link register + SP // Stack pointer +) + +const ( + ZR = SP // Zero register uses the same numerical value as SP +) \ No newline at end of file diff --git a/src/arm/Return.go b/src/arm/Return.go new file mode 100644 index 0000000..55460f1 --- /dev/null +++ b/src/arm/Return.go @@ -0,0 +1,6 @@ +package arm + +// Return transfers program control to the caller. +func Return() uint32 { + return 0xD65F03C0 +} \ No newline at end of file diff --git a/src/arm/Shift.go b/src/arm/Shift.go new file mode 100644 index 0000000..840723b --- /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) +} \ No newline at end of file diff --git a/src/arm/Shift_test.go b/src/arm/Shift_test.go new file mode 100644 index 0000000..f0fe0f5 --- /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.Equal(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.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Store.go b/src/arm/Store.go new file mode 100644 index 0000000..d6b1212 --- /dev/null +++ b/src/arm/Store.go @@ -0,0 +1,21 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StoreRegister writes the contents of the register to a memory address. +func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 { + common := memory(source, base, offset) + + switch length { + case 1: + return 0b00111<<27 | common + case 2: + return 0b01111<<27 | common + case 4: + return 0b10111<<27 | common + default: + return 0b11111<<27 | common + } +} \ No newline at end of file diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go new file mode 100644 index 0000000..2f70825 --- /dev/null +++ b/src/arm/StorePair.go @@ -0,0 +1,12 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// StorePair calculates an address from a base register value and an immediate offset multiplied by 8, +// and stores the values of two registers to the calculated address. +// This is the pre-index version of the instruction so the offset is applied to the base register before the memory access. +func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 { + return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8) +} \ No newline at end of file diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go new file mode 100644 index 0000000..5d7ca28 --- /dev/null +++ b/src/arm/StorePair_test.go @@ -0,0 +1,28 @@ +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 TestStorePair(t *testing.T) { + usagePatterns := []struct { + Reg1 cpu.Register + Reg2 cpu.Register + Base cpu.Register + Offset int + Code uint32 + }{ + {arm.FP, arm.LR, arm.SP, -32, 0xA9BE7BFD}, + {arm.FP, arm.LR, arm.SP, -16, 0xA9BF7BFD}, + } + + for _, pattern := range usagePatterns { + t.Logf("stp %s, %s, [%s, #%d]!", pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + code := arm.StorePair(pattern.Reg1, pattern.Reg2, pattern.Base, pattern.Offset) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Store_test.go b/src/arm/Store_test.go new file mode 100644 index 0000000..2b80285 --- /dev/null +++ b/src/arm/Store_test.go @@ -0,0 +1,34 @@ +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 TestStoreRegister(t *testing.T) { + usagePatterns := []struct { + Source cpu.Register + Base cpu.Register + Offset int + Length byte + Code uint32 + }{ + {arm.X0, arm.X1, -8, 1, 0x381F8020}, + {arm.X1, arm.X0, -8, 1, 0x381F8001}, + {arm.X0, arm.X1, -8, 2, 0x781F8020}, + {arm.X1, arm.X0, -8, 2, 0x781F8001}, + {arm.X0, arm.X1, -8, 4, 0xB81F8020}, + {arm.X1, arm.X0, -8, 4, 0xB81F8001}, + {arm.X0, arm.X1, -8, 8, 0xF81F8020}, + {arm.X1, arm.X0, -8, 8, 0xF81F8001}, + } + + for _, pattern := range usagePatterns { + t.Logf("stur %s, [%s, #%d] %db", pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + code := arm.StoreRegister(pattern.Source, pattern.Base, pattern.Offset, pattern.Length) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Sub.go b/src/arm/Sub.go new file mode 100644 index 0000000..ce48f80 --- /dev/null +++ b/src/arm/Sub.go @@ -0,0 +1,38 @@ +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) (code uint32, encodable bool) { + return subRegisterNumber(destination, source, number, 0) +} + +// SubRegisterRegister subtracts a register from a register. +func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return subRegisterRegister(destination, source, operand, 0) +} + +// 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) (code uint32, encodable bool) { + shift := uint32(0) + + if number > mask12 { + if number&mask12 != 0 { + return 0, false + } + + shift = 1 + number >>= 12 + + if number > mask12 { + return 0, false + } + } + + 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. +func subRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register, flags uint32) uint32 { + return flags<<29 | 0b11001011000<<21 | reg3(destination, source, operand) +} \ No newline at end of file diff --git a/src/arm/Sub_test.go b/src/arm/Sub_test.go new file mode 100644 index 0000000..15118c0 --- /dev/null +++ b/src/arm/Sub_test.go @@ -0,0 +1,46 @@ +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 TestSubRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Number int + Code uint32 + }{ + {arm.X0, arm.X0, 1, 0xD1000400}, + {arm.X0, arm.X0, 0x1000, 0xD1400400}, + {arm.SP, arm.SP, 16, 0xD10043FF}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %d", pattern.Destination, pattern.Source, pattern.Number) + code, encodable := arm.SubRegisterNumber(pattern.Destination, pattern.Source, pattern.Number) + assert.True(t, encodable) + assert.Equal(t, code, pattern.Code) + } +} + +func TestSubRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Source cpu.Register + Operand cpu.Register + Code uint32 + }{ + {arm.X0, arm.X1, arm.X2, 0xCB020020}, + } + + for _, pattern := range usagePatterns { + t.Logf("sub %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand) + code := arm.SubRegisterRegister(pattern.Destination, pattern.Source, pattern.Operand) + assert.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/Syscall.go b/src/arm/Syscall.go new file mode 100644 index 0000000..1617a9e --- /dev/null +++ b/src/arm/Syscall.go @@ -0,0 +1,6 @@ +package arm + +// Syscall is the primary way to communicate with the OS kernel. +func Syscall() uint32 { + return 0xD4000001 +} \ No newline at end of file diff --git a/src/arm/Xor.go b/src/arm/Xor.go new file mode 100644 index 0000000..4533e89 --- /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) { + 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. +func XorRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 { + return 0b11001010<<24 | reg3(destination, source, operand) +} \ No newline at end of file diff --git a/src/arm/Xor_test.go b/src/arm/Xor_test.go new file mode 100644 index 0000000..aa353dd --- /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.Equal(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.Equal(t, code, pattern.Code) + } +} \ No newline at end of file diff --git a/src/arm/arm_test.go b/src/arm/arm_test.go new file mode 100644 index 0000000..100f2a6 --- /dev/null +++ b/src/arm/arm_test.go @@ -0,0 +1,37 @@ +package arm_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/arm" + "git.urbach.dev/go/assert" +) + +func TestConstants(t *testing.T) { + 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) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.AddRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xFFFF) + assert.False(t, encodable) + _, encodable = arm.SubRegisterNumber(arm.X0, arm.X0, 0xF0000000) + assert.False(t, encodable) +} \ No newline at end of file diff --git a/src/arm/bitmask.go b/src/arm/bitmask.go new file mode 100644 index 0000000..86b2547 --- /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) (N int, immr int, imms int, encodable bool) { + if val == 0 || ^val == 0 { + return 0, 0, 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, 0, 0, false + } + + return N, immr, (imms & 0x3F), true +} + +// clearTrailingOnes clears trailing one bits. +func clearTrailingOnes(x uint) uint { + return x & (x + 1) +} \ No newline at end of file diff --git a/src/arm/condition.go b/src/arm/condition.go new file mode 100644 index 0000000..364c8fd --- /dev/null +++ b/src/arm/condition.go @@ -0,0 +1,22 @@ +package arm + +type condition uint8 + +const ( + EQ condition = iota + NE + CS + CC + MI + PL + VS + VC + HI + LS + GE + LT + GT + LE + AL + NV +) \ No newline at end of file diff --git a/src/arm/encode.go b/src/arm/encode.go new file mode 100644 index 0000000..72150eb --- /dev/null +++ b/src/arm/encode.go @@ -0,0 +1,51 @@ +package arm + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// memory encodes an instruction with a register, a base register and an offset. +func memory(destination cpu.Register, base cpu.Register, imm9 int) uint32 { + return uint32(imm9&mask9)<<12 | uint32(base)<<5 | uint32(destination) +} + +// pair encodes an instruction using a register pair with memory. +func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uint32 { + return uint32(imm7&mask7)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} + +// regImm encodes an instruction with a register and an immediate. +func regImm(d cpu.Register, imm16 uint16) uint32 { + return uint32(imm16)<<5 | uint32(d) +} + +// regImmHw encodes an instruction with a register, an immediate and +// the 2-bit halfword specifying which 16-bit region of the register is addressed. +func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 { + return uint32(hw)<<21 | uint32(imm16)<<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) +} + +// reg2BitmaskImm encodes an instruction with 2 registers and a bitmask immediate. +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. +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) +} \ No newline at end of file diff --git a/src/arm/mask.go b/src/arm/mask.go new file mode 100644 index 0000000..c7850f8 --- /dev/null +++ b/src/arm/mask.go @@ -0,0 +1,11 @@ +package arm + +const ( + mask6 = 0b111111 + mask7 = 0b1111111 + mask9 = 0b1_11111111 + mask12 = 0b1111_11111111 + mask16 = 0b11111111_11111111 + mask19 = 0b111_11111111_11111111 + mask26 = 0b11_11111111_11111111_11111111 +) \ No newline at end of file