This commit is contained in:
parent
bac5986425
commit
3ae47f93eb
45 changed files with 1417 additions and 0 deletions
38
src/arm/Add.go
Normal file
38
src/arm/Add.go
Normal 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
45
src/arm/Add_test.go
Normal 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
16
src/arm/And.go
Normal 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
49
src/arm/And_test.go
Normal 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
8
src/arm/Call.go
Normal 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
25
src/arm/Call_test.go
Normal 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
17
src/arm/Compare.go
Normal 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
45
src/arm/Compare_test.go
Normal 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
8
src/arm/Div.go
Normal 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
26
src/arm/Div_test.go
Normal 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
41
src/arm/Jump.go
Normal 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
68
src/arm/Jump_test.go
Normal 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
19
src/arm/Load.go
Normal 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
10
src/arm/LoadAddress.go
Normal 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)
|
||||
}
|
26
src/arm/LoadAddress_test.go
Normal file
26
src/arm/LoadAddress_test.go
Normal 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
10
src/arm/LoadPair.go
Normal 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
28
src/arm/LoadPair_test.go
Normal 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
39
src/arm/Load_test.go
Normal 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
100
src/arm/Move.go
Normal 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
121
src/arm/Move_test.go
Normal 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
13
src/arm/Mul.go
Normal 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
45
src/arm/Mul_test.go
Normal 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
8
src/arm/Negate.go
Normal 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
26
src/arm/Negate_test.go
Normal 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
6
src/arm/Nop.go
Normal 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
14
src/arm/Or.go
Normal 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
49
src/arm/Or_test.go
Normal 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
42
src/arm/Registers.go
Normal 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
6
src/arm/Return.go
Normal 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
15
src/arm/Shift.go
Normal 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
52
src/arm/Shift_test.go
Normal 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
21
src/arm/Store.go
Normal 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
12
src/arm/StorePair.go
Normal 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
28
src/arm/StorePair_test.go
Normal 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
34
src/arm/Store_test.go
Normal 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
38
src/arm/Sub.go
Normal 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
46
src/arm/Sub_test.go
Normal 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
6
src/arm/Syscall.go
Normal 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
14
src/arm/Xor.go
Normal 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
49
src/arm/Xor_test.go
Normal 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
37
src/arm/arm_test.go
Normal 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
33
src/arm/bitmask.go
Normal 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
22
src/arm/condition.go
Normal 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
51
src/arm/encode.go
Normal 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
11
src/arm/mask.go
Normal 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
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue