Added basic support for arm64

This commit is contained in:
2025-03-06 13:40:17 +01:00
parent 14abb8202b
commit 2f09b96f34
25 changed files with 270 additions and 33 deletions

10
src/arm/Call.go Normal file
View File

@ -0,0 +1,10 @@
package arm
import "encoding/binary"
// 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(code []byte, offset uint32) []byte {
return binary.LittleEndian.AppendUint32(code, uint32(0b100101<<26)|offset)
}

29
src/arm/Move.go Normal file
View File

@ -0,0 +1,29 @@
package arm
import (
"encoding/binary"
"git.urbach.dev/cli/q/src/cpu"
)
// MoveRegisterNumber moves an integer into the given register.
func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
return MoveZero(code, destination, 0, uint16(number))
}
// MoveKeep moves a 16-bit integer into the given register and keeps all other bits.
func MoveKeep(code []byte, destination cpu.Register, halfword int, number uint16) []byte {
x := mov(0b11, halfword, number, destination)
return binary.LittleEndian.AppendUint32(code, x)
}
// MoveZero moves a 16-bit integer into the given register and clears all other bits to zero.
func MoveZero(code []byte, destination cpu.Register, halfword int, number uint16) []byte {
x := mov(0b10, halfword, number, destination)
return binary.LittleEndian.AppendUint32(code, x)
}
// mov encodes a generic move instruction.
func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 {
return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination)
}

43
src/arm/Move_test.go Normal file
View File

@ -0,0 +1,43 @@
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 TestMoveKeep(t *testing.T) {
usagePatterns := []struct {
Register cpu.Register
Number uint16
Code []byte
}{
{arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xF2}},
{arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xF2}},
}
for _, pattern := range usagePatterns {
t.Logf("movk %s, %x", pattern.Register, pattern.Number)
code := arm.MoveKeep(nil, pattern.Register, 0, pattern.Number)
assert.DeepEqual(t, code, pattern.Code)
}
}
func TestMoveZero(t *testing.T) {
usagePatterns := []struct {
Register cpu.Register
Number uint16
Code []byte
}{
{arm.X0, 0, []byte{0x00, 0x00, 0x80, 0xD2}},
{arm.X0, 1, []byte{0x20, 0x00, 0x80, 0xD2}},
}
for _, pattern := range usagePatterns {
t.Logf("movz %s, %x", pattern.Register, pattern.Number)
code := arm.MoveZero(nil, pattern.Register, 0, pattern.Number)
assert.DeepEqual(t, code, pattern.Code)
}
}

6
src/arm/Nop.go Normal file
View File

@ -0,0 +1,6 @@
package arm
// Nop does nothing. This can be used for alignment purposes.
func Nop(code []byte) []byte {
return append(code, 0x1F, 0x20, 0x03, 0xD5)
}

View File

@ -38,7 +38,20 @@ const (
)
var (
GeneralRegisters = []cpu.Register{X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28}
InputRegisters = SyscallInputRegisters
OutputRegisters = SyscallInputRegisters
SyscallInputRegisters = []cpu.Register{X8, X0, X1, X2, X3, X4, X5}
SyscallOutputRegisters = []cpu.Register{X0, X1}
WindowsInputRegisters = []cpu.Register{X0, X1, X2, X3, X4, X5, X6, X7}
WindowsOutputRegisters = []cpu.Register{X0, X1}
CPU = cpu.CPU{
General: GeneralRegisters,
Input: InputRegisters,
Output: OutputRegisters,
SyscallInput: SyscallInputRegisters,
SyscallOutput: SyscallOutputRegisters,
NumRegisters: 32,
}
)

6
src/arm/Return.go Normal file
View File

@ -0,0 +1,6 @@
package arm
// Return transfers program control to the caller.
func Return(code []byte) []byte {
return append(code, 0xC0, 0x03, 0x5F, 0xD6)
}

6
src/arm/Syscall.go Normal file
View File

@ -0,0 +1,6 @@
package arm
// Syscall is the primary way to communicate with the OS kernel.
func Syscall(code []byte) []byte {
return append(code, 0x01, 0x00, 0x00, 0xD4)
}

16
src/arm/arm_test.go Normal file
View File

@ -0,0 +1,16 @@
package arm_test
import (
"testing"
"git.urbach.dev/cli/q/src/arm"
"git.urbach.dev/go/assert"
)
func TestARM(t *testing.T) {
assert.DeepEqual(t, arm.Call(nil, 0), []byte{0x00, 0x00, 0x00, 0x94})
assert.DeepEqual(t, arm.MoveRegisterNumber(nil, arm.X0, 42), arm.MoveZero(nil, arm.X0, 0, 42))
assert.DeepEqual(t, arm.Nop(nil), []byte{0x1F, 0x20, 0x03, 0xD5})
assert.DeepEqual(t, arm.Return(nil), []byte{0xC0, 0x03, 0x5F, 0xD6})
assert.DeepEqual(t, arm.Syscall(nil), []byte{0x01, 0x00, 0x00, 0xD4})
}