From 450e634d79102f96a3e909b64e1fb18131c0a78b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 12 Mar 2025 12:29:55 +0100 Subject: [PATCH] Implemented arm64 instructions: ldp and stp --- src/arm/Add.go | 9 +++++++++ src/arm/LoadPair.go | 12 ++++++++++++ src/arm/LoadPair_test.go | 28 ++++++++++++++++++++++++++++ src/arm/Move.go | 4 ++++ src/arm/Move_test.go | 2 ++ src/arm/StorePair.go | 14 ++++++++++++++ src/arm/StorePair_test.go | 28 ++++++++++++++++++++++++++++ src/asmc/compileARM.go | 7 +++---- 8 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 src/arm/Add.go create mode 100644 src/arm/LoadPair.go create mode 100644 src/arm/LoadPair_test.go create mode 100644 src/arm/StorePair.go create mode 100644 src/arm/StorePair_test.go diff --git a/src/arm/Add.go b/src/arm/Add.go new file mode 100644 index 0000000..c97afa8 --- /dev/null +++ b/src/arm/Add.go @@ -0,0 +1,9 @@ +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 { + number &= 0xFFF + return 0b100100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) +} diff --git a/src/arm/LoadPair.go b/src/arm/LoadPair.go new file mode 100644 index 0000000..12cc72c --- /dev/null +++ b/src/arm/LoadPair.go @@ -0,0 +1,12 @@ +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 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1) +} diff --git a/src/arm/LoadPair_test.go b/src/arm/LoadPair_test.go new file mode 100644 index 0000000..97edbdb --- /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.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/arm/Move.go b/src/arm/Move.go index e61ff8f..b48bd9d 100644 --- a/src/arm/Move.go +++ b/src/arm/Move.go @@ -6,6 +6,10 @@ import ( // 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) + } + return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination) } diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go index 9811e37..69f524e 100644 --- a/src/arm/Move_test.go +++ b/src/arm/Move_test.go @@ -16,6 +16,8 @@ func TestMoveRegisterRegister(t *testing.T) { }{ {arm.X0, arm.X1, 0xAA0103E0}, {arm.X1, arm.X0, 0xAA0003E1}, + {arm.FP, arm.SP, 0x910003FD}, + {arm.SP, arm.FP, 0x910003BF}, } for _, pattern := range usagePatterns { diff --git a/src/arm/StorePair.go b/src/arm/StorePair.go new file mode 100644 index 0000000..f31cce9 --- /dev/null +++ b/src/arm/StorePair.go @@ -0,0 +1,14 @@ +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 { + offset /= 8 + offset &= 0b1111111 + return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1) +} diff --git a/src/arm/StorePair_test.go b/src/arm/StorePair_test.go new file mode 100644 index 0000000..eefdfbd --- /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.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go index 1c94725..fa4b99f 100644 --- a/src/asmc/compileARM.go +++ b/src/asmc/compileARM.go @@ -42,8 +42,8 @@ func (c *compiler) compileARM(x asm.Instruction) { case asm.LABEL: label := c.assembler.Param.Label[x.Index] c.codeLabels[label.Name] = Address(len(c.code)) - c.append(0xa9be7bfd) - c.append(0x910003fd) + c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16)) + c.append(arm.MoveRegisterRegister(arm.FP, arm.SP)) case asm.LOAD: switch x.Type { @@ -96,8 +96,7 @@ func (c *compiler) compileARM(x asm.Instruction) { } case asm.RETURN: - c.append(0xa8c27bfd) - c.append(0xd65f03c0) + c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16)) c.append(arm.Return()) case asm.SYSCALL: