Added arm package
All checks were successful
/ test (push) Successful in 19s

This commit is contained in:
Eduard Urbach 2025-06-23 11:49:39 +02:00
parent bac5986425
commit 3ae47f93eb
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
45 changed files with 1417 additions and 0 deletions

38
src/arm/Add.go Normal file
View file

@ -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)
}

45
src/arm/Add_test.go Normal file
View file

@ -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)
}
}

16
src/arm/And.go Normal file
View file

@ -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)
}

49
src/arm/And_test.go Normal file
View file

@ -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)
}
}

8
src/arm/Call.go Normal file
View file

@ -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)
}

25
src/arm/Call_test.go Normal file
View file

@ -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)
}
}

17
src/arm/Compare.go Normal file
View file

@ -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)
}

45
src/arm/Compare_test.go Normal file
View file

@ -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)
}
}

8
src/arm/Div.go Normal file
View file

@ -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)
}

26
src/arm/Div_test.go Normal file
View file

@ -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)
}
}

41
src/arm/Jump.go Normal file
View file

@ -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)
}

68
src/arm/Jump_test.go Normal file
View file

@ -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)
}
}

19
src/arm/Load.go Normal file
View file

@ -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
}
}

10
src/arm/LoadAddress.go Normal file
View file

@ -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)
}

View file

@ -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)
}
}

10
src/arm/LoadPair.go Normal file
View file

@ -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)
}

28
src/arm/LoadPair_test.go Normal file
View file

@ -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)
}
}

39
src/arm/Load_test.go Normal file
View file

@ -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)
}
}

100
src/arm/Move.go Normal file
View file

@ -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)
}

121
src/arm/Move_test.go Normal file
View file

@ -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)
}
}

13
src/arm/Mul.go Normal file
View file

@ -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)
}

45
src/arm/Mul_test.go Normal file
View file

@ -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)
}
}

8
src/arm/Negate.go Normal file
View file

@ -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)
}

26
src/arm/Negate_test.go Normal file
View file

@ -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)
}
}

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() uint32 {
return 0xD503201F
}

14
src/arm/Or.go Normal file
View file

@ -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)
}

49
src/arm/Or_test.go Normal file
View file

@ -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)
}
}

42
src/arm/Registers.go Normal file
View file

@ -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
)

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

@ -0,0 +1,6 @@
package arm
// Return transfers program control to the caller.
func Return() uint32 {
return 0xD65F03C0
}

15
src/arm/Shift.go Normal file
View file

@ -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)
}

52
src/arm/Shift_test.go Normal file
View file

@ -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)
}
}

21
src/arm/Store.go Normal file
View file

@ -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
}
}

12
src/arm/StorePair.go Normal file
View file

@ -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)
}

28
src/arm/StorePair_test.go Normal file
View file

@ -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)
}
}

34
src/arm/Store_test.go Normal file
View file

@ -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)
}
}

38
src/arm/Sub.go Normal file
View file

@ -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)
}

46
src/arm/Sub_test.go Normal file
View file

@ -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)
}
}

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() uint32 {
return 0xD4000001
}

14
src/arm/Xor.go Normal file
View file

@ -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)
}

49
src/arm/Xor_test.go Normal file
View file

@ -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)
}
}

37
src/arm/arm_test.go Normal file
View file

@ -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)
}

33
src/arm/bitmask.go Normal file
View file

@ -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)
}

22
src/arm/condition.go Normal file
View file

@ -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
)

51
src/arm/encode.go Normal file
View file

@ -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)
}

11
src/arm/mask.go Normal file
View file

@ -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
)