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