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. // AddRegisterNumber adds a number to a register.
func AddRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { 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) 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. // CompareRegisterNumber is an alias for a subtraction that updates the conditional flags and discards the result.
func CompareRegisterNumber(register cpu.Register, number int) uint32 { 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. // LoadRegister loads from memory into a register.
func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 { func LoadRegister(destination cpu.Register, base cpu.Register, offset int, length byte) uint32 {
offset &= 0b1_1111_1111 common := 1<<22 | memory(destination, base, offset)
common := 1<<22 | uint32(offset)<<12 | uint32(base)<<5 | uint32(destination)
switch length { switch length {
case 1: 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. // 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. // 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 { func LoadPair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
offset /= 8 return 0b1010100011<<22 | pair(reg1, reg2, base, offset/8)
offset &= 0b111_1111
return 0b1010100011<<22 | (uint32(offset) << 15) | (uint32(reg2) << 10) | (uint32(base) << 5) | uint32(reg1)
} }

View File

@ -10,7 +10,7 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32
return AddRegisterNumber(destination, source, 0) 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. // 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. // mov encodes a generic move instruction.
func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 { 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 X26
X27 X27
X28 X28
FP // Frame pointer FP // Frame pointer
LR // Link register LR // Link register
SP // Stack pointer SP // Stack pointer
ZR = SP // Zero register uses the same numerical value as SP
) )
var ( var (

View File

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

View File

@ -8,7 +8,5 @@ import (
// and stores the values of two registers to the calculated address. // 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. // 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 { func StorePair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, offset int) uint32 {
offset /= 8 return 0b1010100110<<22 | pair(reg1, reg2, base, offset/8)
offset &= 0b111_1111
return 0b1010100110<<22 | uint32(offset)<<15 | uint32(reg2)<<10 | uint32(base)<<5 | uint32(reg1)
} }

View File

@ -4,5 +4,10 @@ import "git.urbach.dev/cli/q/src/cpu"
// SubRegisterNumber subtracts a number from the given register. // SubRegisterNumber subtracts a number from the given register.
func SubRegisterNumber(destination cpu.Register, source cpu.Register, number int) uint32 { 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) 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" 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. // 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 number &= 0b1111_1111_1111
common := op<<30 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination) return op<<30 | flags<<29 | 0b100010<<23 | (uint32(number) << 10) | (uint32(source) << 5) | uint32(destination)
}
if flags {
return 1<<29 | common // 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 common 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] operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number)) c.append(arm.AddRegisterNumber(operand.Register, operand.Register, operand.Number))
case asm.TypeRegisterRegister: 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: case asm.SUB:
@ -114,7 +115,8 @@ func (c *compiler) compileARM(x asm.Instruction) {
operand := c.assembler.Param.RegisterNumber[x.Index] operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number)) c.append(arm.SubRegisterNumber(operand.Register, operand.Register, operand.Number))
case asm.TypeRegisterRegister: 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: case asm.COMPARE:
@ -123,6 +125,36 @@ func (c *compiler) compileARM(x asm.Instruction) {
operand := c.assembler.Param.RegisterNumber[x.Index] operand := c.assembler.Param.RegisterNumber[x.Index]
c.append(arm.CompareRegisterNumber(operand.Register, operand.Number)) c.append(arm.CompareRegisterNumber(operand.Register, operand.Number))
case asm.TypeRegisterRegister: 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") panic("not implemented")
} }

View File

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