Implemented more arm64 instructions

This commit is contained in:
2025-03-13 23:12:15 +01:00
parent 5b3769a0db
commit ac14ab4f7a
18 changed files with 218 additions and 29 deletions

View File

@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu"
// AddRegisterNumber adds a number to a register.
func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 {
return encodeRegisterNumberFlags(0b10, destination, source, number, false)
return addRegisterNumber(0b10, 0, destination, source, number)
}
// AddRegisterRegister adds a register to a register.
func AddRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
return addRegisterRegister(0b10, 0, destination, source, operand)
}

View File

@ -24,3 +24,20 @@ func TestAddRegisterNumber(t *testing.T) {
assert.DeepEqual(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.DeepEqual(t, code, pattern.Code)
}
}

View File

@ -4,5 +4,10 @@ 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) uint32 {
return encodeRegisterNumberFlags(0b11, 0b11111, register, number, true)
return addRegisterNumber(0b11, 1, ZR, register, number)
}
// 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 addRegisterRegister(0b11, 1, ZR, reg1, reg2)
}

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

@ -0,0 +1,8 @@
package arm
import "git.urbach.dev/cli/q/src/cpu"
// DivSigned divides source by operand and stores the value in the destination.
func DivSigned(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
return 0b10011010110<<21 | 0b000011<<10 | reg3(destination, source, operand)
}

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

@ -0,0 +1,26 @@
package arm_test
import (
"testing"
"git.urbach.dev/cli/q/src/arm"
"git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/go/assert"
)
func TestDivSigned(t *testing.T) {
usagePatterns := []struct {
Destination cpu.Register
Source cpu.Register
Operand cpu.Register
Code uint32
}{
{arm.X0, arm.X1, arm.X2, 0x9AC20C20},
}
for _, pattern := range usagePatterns {
t.Logf("sdiv %s, %s, %s", pattern.Destination, pattern.Source, pattern.Operand)
code := arm.DivSigned(pattern.Destination, pattern.Source, pattern.Operand)
assert.DeepEqual(t, code, pattern.Code)
}
}

View File

@ -4,8 +4,7 @@ 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 {
offset &= 0b1_1111_1111
common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination)
common := 1<<22 | memory(destination, base, offset)
switch length {
case 1:

View File

@ -6,7 +6,5 @@ import "git.urbach.dev/cli/q/src/cpu"
// loads two 64-bit doublewords from memory, and writes them to two registers.
// This is the post-index version of the instruction so the offset is applied to the base register after the memory access.
func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
offset /= 8
offset &= 0b111_1111
return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1)
return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8)
}

View File

@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32
return AddRegisterNumber(destination, source, 0)
}
return 0b10101010<<24 | uint32(source)<<16 | 0b11111<<5 | uint32(destination)
return 0b10101010<<24 | reg3(destination, ZR, source)
}
// MoveRegisterNumber moves an integer into the given register.
@ -30,5 +30,5 @@ func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 {
// mov encodes a generic move instruction.
func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 {
return (1 << 31) | (opCode << 29) | (0b100101 << 23) | uint32(halfword<<21) | uint32(number<<5) | uint32(destination)
return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination)
}

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

@ -0,0 +1,13 @@
package arm
import "git.urbach.dev/cli/q/src/cpu"
// MulRegisterRegister multiplies `multiplicand` with `multiplier` and saves the result in `destination`
func MulRegisterRegister(destination cpu.Register, multiplicand cpu.Register, multiplier cpu.Register) uint32 {
return 0b10011011000<<21 | reg4(destination, multiplicand, multiplier, ZR)
}
// MultiplySubtract multiplies `multiplicand` with `multiplier`, subtracts `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)
}

44
src/arm/Mul_test.go Normal file
View File

@ -0,0 +1,44 @@
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.DeepEqual(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},
}
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.DeepEqual(t, code, pattern.Code)
}
}

View File

@ -32,9 +32,10 @@ const (
X26
X27
X28
FP // Frame pointer
LR // Link register
SP // Stack pointer
FP // Frame pointer
LR // Link register
SP // Stack pointer
ZR = SP // Zero register uses the same numerical value as SP
)
var (

View File

@ -6,8 +6,7 @@ import (
// StoreRegister writes the contents of the register to a memory address.
func StoreRegister(source cpu.Register, base cpu.Register, offset int, length byte) uint32 {
offset &= 0b1_1111_1111
common := uint32(offset)<<12 | uint32(base)<<5 | uint32(source)
common := memory(source, base, offset)
switch length {
case 1:

View File

@ -8,7 +8,5 @@ import (
// and stores the values of two registers to the calculated address.
// This is the pre-index version of the instruction so the offset is applied to the base register before the memory access.
func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
offset /= 8
offset &= 0b111_1111
return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1)
return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8)
}

View File

@ -4,5 +4,10 @@ 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) uint32 {
return encodeRegisterNumberFlags(0b11, destination, source, number, false)
return addRegisterNumber(0b11, 0, destination, source, number)
}
// SubRegisterRegister subtracts a register from a register.
func SubRegisterRegister(destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
return addRegisterRegister(0b11, 0, destination, source, operand)
}

View File

@ -25,3 +25,20 @@ func TestSubRegisterNumber(t *testing.T) {
assert.DeepEqual(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.DeepEqual(t, code, pattern.Code)
}
}

View File

@ -2,15 +2,35 @@ package arm
import "git.urbach.dev/cli/q/src/cpu"
// encodeRegisterNumberFlags performs addition or subtraction on the given register
// addRegisterNumber performs addition or subtraction on the given register
// and optionally updates the condition flags based on the result.
func encodeRegisterNumberFlags(op uint32, destination cpu.Register, source cpu.Register, number int, flags bool) uint32 {
func addRegisterNumber(op uint32, flags uint32, destination cpu.Register, source cpu.Register, number int) uint32 {
number &= 0b1111_1111_1111
common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination)
if flags {
return 1<<29 | common
}
return common
return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination)
}
// addRegisterRegister performs addition or subtraction on the given registers
// and optionally updates the condition flags based on the result.
func addRegisterRegister(op uint32, flags uint32, destination cpu.Register, source cpu.Register, operand cpu.Register) uint32 {
return op<<30 | flags<<29 | 0b01011000<<21 | reg3(destination, source, operand)
}
// memory encodes an instruction with a register, a base register and an offset.
func memory(destination cpu.Register, base cpu.Register, offset int) uint32 {
return uint32(offset&0b1_1111_1111)<<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, offset int) uint32 {
return uint32(offset&0b111_1111)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1)
}
// 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)
}
// 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)
}

View File

@ -105,7 +105,8 @@ func (c *compiler) compileARM(x asm.Instruction) {
operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number))
case asm.TypeRegisterRegister:
panic("not implemented")
operand := c.assembler.Param.RegisterRegister[x.Index]
c.append(arm.AddRegisterRegister(operand.Destination, operand.Destination, operand.Source))
}
case asm.SUB:
@ -114,7 +115,8 @@ func (c *compiler) compileARM(x asm.Instruction) {
operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number))
case asm.TypeRegisterRegister:
panic("not implemented")
operand := c.assembler.Param.RegisterRegister[x.Index]
c.append(arm.SubRegisterRegister(operand.Destination, operand.Destination, operand.Source))
}
case asm.COMPARE:
@ -123,6 +125,36 @@ func (c *compiler) compileARM(x asm.Instruction) {
operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.CompareRegisterNumber(operand.Register, operand.Number))
case asm.TypeRegisterRegister:
operand := c.assembler.Param.RegisterRegister[x.Index]
c.append(arm.CompareRegisterRegister(operand.Destination, operand.Source))
}
case asm.DIV:
switch x.Type {
case asm.TypeRegisterRegister:
operand := c.assembler.Param.RegisterRegister[x.Index]
c.append(arm.DivSigned(operand.Destination, operand.Destination, operand.Source))
case asm.TypeRegisterNumber:
panic("not implemented")
}
case asm.MUL:
switch x.Type {
case asm.TypeRegisterRegister:
operand := c.assembler.Param.RegisterRegister[x.Index]
c.append(arm.MulRegisterRegister(operand.Destination, operand.Destination, operand.Source))
case asm.TypeRegisterNumber:
panic("not implemented")
}
case asm.MODULO:
switch x.Type {
case asm.TypeRegisterRegister:
operand := c.assembler.Param.RegisterRegister[x.Index]
quotient := arm.X28
c.append(arm.DivSigned(quotient, operand.Destination, operand.Source))
c.append(arm.MultiplySubtract(operand.Destination, quotient, operand.Source, operand.Destination))
case asm.TypeRegisterNumber:
panic("not implemented")
}

View File

@ -51,6 +51,7 @@ func (c *compiler) compileX86(x asm.Instruction) {
switch x.Type {
case asm.TypeRegisterRegister:
operands := c.assembler.Param.RegisterRegister[x.Index]
if operands.Destination != x86.RAX {
c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination)
}
@ -67,6 +68,7 @@ func (c *compiler) compileX86(x asm.Instruction) {
switch x.Type {
case asm.TypeRegisterRegister:
operands := c.assembler.Param.RegisterRegister[x.Index]
if operands.Destination != x86.RAX {
c.code = x86.MoveRegisterRegister(c.code, x86.RAX, operands.Destination)
}