Simplified file structure
This commit is contained in:
@ -4,8 +4,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/compiler"
|
||||
"git.akyoto.dev/cli/q/src/build/scanner"
|
||||
"git.akyoto.dev/cli/q/src/compiler"
|
||||
"git.akyoto.dev/cli/q/src/scanner"
|
||||
)
|
||||
|
||||
// Build describes a compiler build.
|
||||
|
@ -1,39 +0,0 @@
|
||||
package arm64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
const (
|
||||
X0 cpu.Register = iota
|
||||
X1
|
||||
X2
|
||||
X3
|
||||
X4
|
||||
X5
|
||||
X6
|
||||
X7
|
||||
X8
|
||||
X9
|
||||
X10
|
||||
X11
|
||||
X12
|
||||
X13
|
||||
X14
|
||||
X15
|
||||
X16
|
||||
X17
|
||||
X18
|
||||
X19
|
||||
X20
|
||||
X21
|
||||
X22
|
||||
X23
|
||||
X24
|
||||
X25
|
||||
X26
|
||||
X27
|
||||
X28
|
||||
X29
|
||||
X30
|
||||
)
|
||||
|
||||
var SyscallArgs = []cpu.Register{X8, X0, X1, X2, X3, X4, X5}
|
@ -1,40 +0,0 @@
|
||||
package riscv
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
const (
|
||||
X0 cpu.Register = iota
|
||||
X1
|
||||
X2
|
||||
X3
|
||||
X4
|
||||
X5
|
||||
X6
|
||||
X7
|
||||
X8
|
||||
X9
|
||||
X10
|
||||
X11
|
||||
X12
|
||||
X13
|
||||
X14
|
||||
X15
|
||||
X16
|
||||
X17
|
||||
X18
|
||||
X19
|
||||
X20
|
||||
X21
|
||||
X22
|
||||
X23
|
||||
X24
|
||||
X25
|
||||
X26
|
||||
X27
|
||||
X28
|
||||
X29
|
||||
X30
|
||||
X31
|
||||
)
|
||||
|
||||
var SyscallArgs = []cpu.Register{X10, X11, X12, X13, X14, X15, X16}
|
@ -1,15 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// AddRegisterNumber adds a number to the given register.
|
||||
func AddRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0, destination, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// AddRegisterRegister adds a register value into another register.
|
||||
func AddRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, operand, destination, 8, 0x01)
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestAddRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, 1, []byte{0x48, 0x83, 0xC0, 0x01}},
|
||||
{x64.RCX, 1, []byte{0x48, 0x83, 0xC1, 0x01}},
|
||||
{x64.RDX, 1, []byte{0x48, 0x83, 0xC2, 0x01}},
|
||||
{x64.RBX, 1, []byte{0x48, 0x83, 0xC3, 0x01}},
|
||||
{x64.RSP, 1, []byte{0x48, 0x83, 0xC4, 0x01}},
|
||||
{x64.RBP, 1, []byte{0x48, 0x83, 0xC5, 0x01}},
|
||||
{x64.RSI, 1, []byte{0x48, 0x83, 0xC6, 0x01}},
|
||||
{x64.RDI, 1, []byte{0x48, 0x83, 0xC7, 0x01}},
|
||||
{x64.R8, 1, []byte{0x49, 0x83, 0xC0, 0x01}},
|
||||
{x64.R9, 1, []byte{0x49, 0x83, 0xC1, 0x01}},
|
||||
{x64.R10, 1, []byte{0x49, 0x83, 0xC2, 0x01}},
|
||||
{x64.R11, 1, []byte{0x49, 0x83, 0xC3, 0x01}},
|
||||
{x64.R12, 1, []byte{0x49, 0x83, 0xC4, 0x01}},
|
||||
{x64.R13, 1, []byte{0x49, 0x83, 0xC5, 0x01}},
|
||||
{x64.R14, 1, []byte{0x49, 0x83, 0xC6, 0x01}},
|
||||
{x64.R15, 1, []byte{0x49, 0x83, 0xC7, 0x01}},
|
||||
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC1, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC2, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC3, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC4, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC5, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC6, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("add %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.AddRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}},
|
||||
{x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}},
|
||||
{x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}},
|
||||
{x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}},
|
||||
{x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}},
|
||||
{x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}},
|
||||
{x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}},
|
||||
{x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}},
|
||||
{x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}},
|
||||
{x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}},
|
||||
{x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}},
|
||||
{x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}},
|
||||
{x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}},
|
||||
{x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}},
|
||||
{x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}},
|
||||
{x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("add %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.AddRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// AndRegisterNumber performs a bitwise AND using a register and a number.
|
||||
func AndRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0b100, destination, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// AndRegisterRegister performs a bitwise AND using two registers.
|
||||
func AndRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, operand, destination, 8, 0x21)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package x64
|
||||
|
||||
// Call places the return address on the top of the stack and continues
|
||||
// program flow at the new address. The address is relative to the next instruction.
|
||||
func Call(code []byte, address uint32) []byte {
|
||||
return append(
|
||||
code,
|
||||
0xE8,
|
||||
byte(address),
|
||||
byte(address>>8),
|
||||
byte(address>>16),
|
||||
byte(address>>24),
|
||||
)
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// Compares the register with the number and sets the status flags in the EFLAGS register.
|
||||
func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0b111, register, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register.
|
||||
func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, registerB, registerA, 8, 0x39)
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestCompareRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}},
|
||||
{x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}},
|
||||
{x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}},
|
||||
{x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}},
|
||||
{x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}},
|
||||
{x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}},
|
||||
{x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}},
|
||||
{x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}},
|
||||
{x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}},
|
||||
{x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}},
|
||||
{x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}},
|
||||
{x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}},
|
||||
{x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}},
|
||||
{x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}},
|
||||
{x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}},
|
||||
{x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}},
|
||||
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}},
|
||||
{x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}},
|
||||
{x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}},
|
||||
{x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}},
|
||||
{x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}},
|
||||
{x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}},
|
||||
{x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}},
|
||||
{x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}},
|
||||
{x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}},
|
||||
{x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}},
|
||||
{x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}},
|
||||
{x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}},
|
||||
{x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}},
|
||||
{x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}},
|
||||
{x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}},
|
||||
{x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("cmp %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// DivRegister divides RDX:RAX by the value in the register.
|
||||
func DivRegister(code []byte, divisor cpu.Register) []byte {
|
||||
rex := byte(0x48)
|
||||
|
||||
if divisor >= 8 {
|
||||
rex++
|
||||
divisor -= 8
|
||||
}
|
||||
|
||||
return append(
|
||||
code,
|
||||
rex,
|
||||
0xF7,
|
||||
0xF8+byte(divisor),
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestDivRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, []byte{0x48, 0xF7, 0xF8}},
|
||||
{x64.RCX, []byte{0x48, 0xF7, 0xF9}},
|
||||
{x64.RDX, []byte{0x48, 0xF7, 0xFA}},
|
||||
{x64.RBX, []byte{0x48, 0xF7, 0xFB}},
|
||||
{x64.RSP, []byte{0x48, 0xF7, 0xFC}},
|
||||
{x64.RBP, []byte{0x48, 0xF7, 0xFD}},
|
||||
{x64.RSI, []byte{0x48, 0xF7, 0xFE}},
|
||||
{x64.RDI, []byte{0x48, 0xF7, 0xFF}},
|
||||
{x64.R8, []byte{0x49, 0xF7, 0xF8}},
|
||||
{x64.R9, []byte{0x49, 0xF7, 0xF9}},
|
||||
{x64.R10, []byte{0x49, 0xF7, 0xFA}},
|
||||
{x64.R11, []byte{0x49, 0xF7, 0xFB}},
|
||||
{x64.R12, []byte{0x49, 0xF7, 0xFC}},
|
||||
{x64.R13, []byte{0x49, 0xF7, 0xFD}},
|
||||
{x64.R14, []byte{0x49, 0xF7, 0xFE}},
|
||||
{x64.R15, []byte{0x49, 0xF7, 0xFF}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("idiv %s", pattern.Register)
|
||||
code := x64.DivRegister(nil, pattern.Register)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package x64
|
||||
|
||||
// ExtendRAXToRDX doubles the size of RAX by sign-extending it to RDX.
|
||||
// This is also known as CQO.
|
||||
func ExtendRAXToRDX(code []byte) []byte {
|
||||
return append(code, 0x48, 0x99)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package x64
|
||||
|
||||
// Jump continues program flow at the new address.
|
||||
// The address is relative to the next instruction.
|
||||
func Jump8(code []byte, address int8) []byte {
|
||||
return append(code, 0xEB, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfLess jumps if the result was less.
|
||||
func Jump8IfLess(code []byte, address int8) []byte {
|
||||
return append(code, 0x7C, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfLessOrEqual jumps if the result was less or equal.
|
||||
func Jump8IfLessOrEqual(code []byte, address int8) []byte {
|
||||
return append(code, 0x7E, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfGreater jumps if the result was greater.
|
||||
func Jump8IfGreater(code []byte, address int8) []byte {
|
||||
return append(code, 0x7F, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfGreaterOrEqual jumps if the result was greater or equal.
|
||||
func Jump8IfGreaterOrEqual(code []byte, address int8) []byte {
|
||||
return append(code, 0x7D, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfEqual jumps if the result was equal.
|
||||
func Jump8IfEqual(code []byte, address int8) []byte {
|
||||
return append(code, 0x74, byte(address))
|
||||
}
|
||||
|
||||
// JumpIfNotEqual jumps if the result was not equal.
|
||||
func Jump8IfNotEqual(code []byte, address int8) []byte {
|
||||
return append(code, 0x75, byte(address))
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestJump(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Offset int8
|
||||
Code []byte
|
||||
}{
|
||||
{0, []byte{0xEB, 0x00}},
|
||||
{1, []byte{0xEB, 0x01}},
|
||||
{2, []byte{0xEB, 0x02}},
|
||||
{3, []byte{0xEB, 0x03}},
|
||||
{127, []byte{0xEB, 0x7F}},
|
||||
{-1, []byte{0xEB, 0xFF}},
|
||||
{-2, []byte{0xEB, 0xFE}},
|
||||
{-3, []byte{0xEB, 0xFD}},
|
||||
{-128, []byte{0xEB, 0x80}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("jmp %x", pattern.Offset)
|
||||
code := x64.Jump8(nil, pattern.Offset)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionalJump(t *testing.T) {
|
||||
assert.DeepEqual(t, x64.Jump8IfEqual(nil, 1), []byte{0x74, 0x01})
|
||||
assert.DeepEqual(t, x64.Jump8IfNotEqual(nil, 1), []byte{0x75, 0x01})
|
||||
assert.DeepEqual(t, x64.Jump8IfLess(nil, 1), []byte{0x7C, 0x01})
|
||||
assert.DeepEqual(t, x64.Jump8IfGreaterOrEqual(nil, 1), []byte{0x7D, 0x01})
|
||||
assert.DeepEqual(t, x64.Jump8IfLessOrEqual(nil, 1), []byte{0x7E, 0x01})
|
||||
assert.DeepEqual(t, x64.Jump8IfGreater(nil, 1), []byte{0x7F, 0x01})
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// LoadRegister loads from memory into a register.
|
||||
func LoadRegister(code []byte, destination cpu.Register, offset byte, length byte, source cpu.Register) []byte {
|
||||
return memoryAccess(code, 0x8A, 0x8B, source, offset, length, destination)
|
||||
}
|
@ -1,157 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestLoadRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
Offset byte
|
||||
Length byte
|
||||
Code []byte
|
||||
}{
|
||||
// No offset
|
||||
{x64.RAX, x64.R15, 0, 8, []byte{0x49, 0x8B, 0x07}},
|
||||
{x64.RAX, x64.R15, 0, 4, []byte{0x41, 0x8B, 0x07}},
|
||||
{x64.RAX, x64.R15, 0, 2, []byte{0x66, 0x41, 0x8B, 0x07}},
|
||||
{x64.RAX, x64.R15, 0, 1, []byte{0x41, 0x8A, 0x07}},
|
||||
{x64.RCX, x64.R14, 0, 8, []byte{0x49, 0x8B, 0x0E}},
|
||||
{x64.RCX, x64.R14, 0, 4, []byte{0x41, 0x8B, 0x0E}},
|
||||
{x64.RCX, x64.R14, 0, 2, []byte{0x66, 0x41, 0x8B, 0x0E}},
|
||||
{x64.RCX, x64.R14, 0, 1, []byte{0x41, 0x8A, 0x0E}},
|
||||
{x64.RDX, x64.R13, 0, 8, []byte{0x49, 0x8B, 0x55, 0x00}},
|
||||
{x64.RDX, x64.R13, 0, 4, []byte{0x41, 0x8B, 0x55, 0x00}},
|
||||
{x64.RDX, x64.R13, 0, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x00}},
|
||||
{x64.RDX, x64.R13, 0, 1, []byte{0x41, 0x8A, 0x55, 0x00}},
|
||||
{x64.RBX, x64.R12, 0, 8, []byte{0x49, 0x8B, 0x1C, 0x24}},
|
||||
{x64.RBX, x64.R12, 0, 4, []byte{0x41, 0x8B, 0x1C, 0x24}},
|
||||
{x64.RBX, x64.R12, 0, 2, []byte{0x66, 0x41, 0x8B, 0x1C, 0x24}},
|
||||
{x64.RBX, x64.R12, 0, 1, []byte{0x41, 0x8A, 0x1C, 0x24}},
|
||||
{x64.RSP, x64.R11, 0, 8, []byte{0x49, 0x8B, 0x23}},
|
||||
{x64.RSP, x64.R11, 0, 4, []byte{0x41, 0x8B, 0x23}},
|
||||
{x64.RSP, x64.R11, 0, 2, []byte{0x66, 0x41, 0x8B, 0x23}},
|
||||
{x64.RSP, x64.R11, 0, 1, []byte{0x41, 0x8A, 0x23}},
|
||||
{x64.RBP, x64.R10, 0, 8, []byte{0x49, 0x8B, 0x2A}},
|
||||
{x64.RBP, x64.R10, 0, 4, []byte{0x41, 0x8B, 0x2A}},
|
||||
{x64.RBP, x64.R10, 0, 2, []byte{0x66, 0x41, 0x8B, 0x2A}},
|
||||
{x64.RBP, x64.R10, 0, 1, []byte{0x41, 0x8A, 0x2A}},
|
||||
{x64.RSI, x64.R9, 0, 8, []byte{0x49, 0x8B, 0x31}},
|
||||
{x64.RSI, x64.R9, 0, 4, []byte{0x41, 0x8B, 0x31}},
|
||||
{x64.RSI, x64.R9, 0, 2, []byte{0x66, 0x41, 0x8B, 0x31}},
|
||||
{x64.RSI, x64.R9, 0, 1, []byte{0x41, 0x8A, 0x31}},
|
||||
{x64.RDI, x64.R8, 0, 8, []byte{0x49, 0x8B, 0x38}},
|
||||
{x64.RDI, x64.R8, 0, 4, []byte{0x41, 0x8B, 0x38}},
|
||||
{x64.RDI, x64.R8, 0, 2, []byte{0x66, 0x41, 0x8B, 0x38}},
|
||||
{x64.RDI, x64.R8, 0, 1, []byte{0x41, 0x8A, 0x38}},
|
||||
{x64.R8, x64.RDI, 0, 8, []byte{0x4C, 0x8B, 0x07}},
|
||||
{x64.R8, x64.RDI, 0, 4, []byte{0x44, 0x8B, 0x07}},
|
||||
{x64.R8, x64.RDI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x07}},
|
||||
{x64.R8, x64.RDI, 0, 1, []byte{0x44, 0x8A, 0x07}},
|
||||
{x64.R9, x64.RSI, 0, 8, []byte{0x4C, 0x8B, 0x0E}},
|
||||
{x64.R9, x64.RSI, 0, 4, []byte{0x44, 0x8B, 0x0E}},
|
||||
{x64.R9, x64.RSI, 0, 2, []byte{0x66, 0x44, 0x8B, 0x0E}},
|
||||
{x64.R9, x64.RSI, 0, 1, []byte{0x44, 0x8A, 0x0E}},
|
||||
{x64.R10, x64.RBP, 0, 8, []byte{0x4C, 0x8B, 0x55, 0x00}},
|
||||
{x64.R10, x64.RBP, 0, 4, []byte{0x44, 0x8B, 0x55, 0x00}},
|
||||
{x64.R10, x64.RBP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x00}},
|
||||
{x64.R10, x64.RBP, 0, 1, []byte{0x44, 0x8A, 0x55, 0x00}},
|
||||
{x64.R11, x64.RSP, 0, 8, []byte{0x4C, 0x8B, 0x1C, 0x24}},
|
||||
{x64.R11, x64.RSP, 0, 4, []byte{0x44, 0x8B, 0x1C, 0x24}},
|
||||
{x64.R11, x64.RSP, 0, 2, []byte{0x66, 0x44, 0x8B, 0x1C, 0x24}},
|
||||
{x64.R11, x64.RSP, 0, 1, []byte{0x44, 0x8A, 0x1C, 0x24}},
|
||||
{x64.R12, x64.RBX, 0, 8, []byte{0x4C, 0x8B, 0x23}},
|
||||
{x64.R12, x64.RBX, 0, 4, []byte{0x44, 0x8B, 0x23}},
|
||||
{x64.R12, x64.RBX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x23}},
|
||||
{x64.R12, x64.RBX, 0, 1, []byte{0x44, 0x8A, 0x23}},
|
||||
{x64.R13, x64.RDX, 0, 8, []byte{0x4C, 0x8B, 0x2A}},
|
||||
{x64.R13, x64.RDX, 0, 4, []byte{0x44, 0x8B, 0x2A}},
|
||||
{x64.R13, x64.RDX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x2A}},
|
||||
{x64.R13, x64.RDX, 0, 1, []byte{0x44, 0x8A, 0x2A}},
|
||||
{x64.R14, x64.RCX, 0, 8, []byte{0x4C, 0x8B, 0x31}},
|
||||
{x64.R14, x64.RCX, 0, 4, []byte{0x44, 0x8B, 0x31}},
|
||||
{x64.R14, x64.RCX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x31}},
|
||||
{x64.R14, x64.RCX, 0, 1, []byte{0x44, 0x8A, 0x31}},
|
||||
{x64.R15, x64.RAX, 0, 8, []byte{0x4C, 0x8B, 0x38}},
|
||||
{x64.R15, x64.RAX, 0, 4, []byte{0x44, 0x8B, 0x38}},
|
||||
{x64.R15, x64.RAX, 0, 2, []byte{0x66, 0x44, 0x8B, 0x38}},
|
||||
{x64.R15, x64.RAX, 0, 1, []byte{0x44, 0x8A, 0x38}},
|
||||
|
||||
// Offset of 1
|
||||
{x64.RAX, x64.R15, 1, 8, []byte{0x49, 0x8B, 0x47, 0x01}},
|
||||
{x64.RAX, x64.R15, 1, 4, []byte{0x41, 0x8B, 0x47, 0x01}},
|
||||
{x64.RAX, x64.R15, 1, 2, []byte{0x66, 0x41, 0x8B, 0x47, 0x01}},
|
||||
{x64.RAX, x64.R15, 1, 1, []byte{0x41, 0x8A, 0x47, 0x01}},
|
||||
{x64.RCX, x64.R14, 1, 8, []byte{0x49, 0x8B, 0x4E, 0x01}},
|
||||
{x64.RCX, x64.R14, 1, 4, []byte{0x41, 0x8B, 0x4E, 0x01}},
|
||||
{x64.RCX, x64.R14, 1, 2, []byte{0x66, 0x41, 0x8B, 0x4E, 0x01}},
|
||||
{x64.RCX, x64.R14, 1, 1, []byte{0x41, 0x8A, 0x4E, 0x01}},
|
||||
{x64.RDX, x64.R13, 1, 8, []byte{0x49, 0x8B, 0x55, 0x01}},
|
||||
{x64.RDX, x64.R13, 1, 4, []byte{0x41, 0x8B, 0x55, 0x01}},
|
||||
{x64.RDX, x64.R13, 1, 2, []byte{0x66, 0x41, 0x8B, 0x55, 0x01}},
|
||||
{x64.RDX, x64.R13, 1, 1, []byte{0x41, 0x8A, 0x55, 0x01}},
|
||||
{x64.RBX, x64.R12, 1, 8, []byte{0x49, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.RBX, x64.R12, 1, 4, []byte{0x41, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.RBX, x64.R12, 1, 2, []byte{0x66, 0x41, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.RBX, x64.R12, 1, 1, []byte{0x41, 0x8A, 0x5C, 0x24, 0x01}},
|
||||
{x64.RSP, x64.R11, 1, 8, []byte{0x49, 0x8B, 0x63, 0x01}},
|
||||
{x64.RSP, x64.R11, 1, 4, []byte{0x41, 0x8B, 0x63, 0x01}},
|
||||
{x64.RSP, x64.R11, 1, 2, []byte{0x66, 0x41, 0x8B, 0x63, 0x01}},
|
||||
{x64.RSP, x64.R11, 1, 1, []byte{0x41, 0x8A, 0x63, 0x01}},
|
||||
{x64.RBP, x64.R10, 1, 8, []byte{0x49, 0x8B, 0x6A, 0x01}},
|
||||
{x64.RBP, x64.R10, 1, 4, []byte{0x41, 0x8B, 0x6A, 0x01}},
|
||||
{x64.RBP, x64.R10, 1, 2, []byte{0x66, 0x41, 0x8B, 0x6A, 0x01}},
|
||||
{x64.RBP, x64.R10, 1, 1, []byte{0x41, 0x8A, 0x6A, 0x01}},
|
||||
{x64.RSI, x64.R9, 1, 8, []byte{0x49, 0x8B, 0x71, 0x01}},
|
||||
{x64.RSI, x64.R9, 1, 4, []byte{0x41, 0x8B, 0x71, 0x01}},
|
||||
{x64.RSI, x64.R9, 1, 2, []byte{0x66, 0x41, 0x8B, 0x71, 0x01}},
|
||||
{x64.RSI, x64.R9, 1, 1, []byte{0x41, 0x8A, 0x71, 0x01}},
|
||||
{x64.RDI, x64.R8, 1, 8, []byte{0x49, 0x8B, 0x78, 0x01}},
|
||||
{x64.RDI, x64.R8, 1, 4, []byte{0x41, 0x8B, 0x78, 0x01}},
|
||||
{x64.RDI, x64.R8, 1, 2, []byte{0x66, 0x41, 0x8B, 0x78, 0x01}},
|
||||
{x64.RDI, x64.R8, 1, 1, []byte{0x41, 0x8A, 0x78, 0x01}},
|
||||
{x64.R8, x64.RDI, 1, 8, []byte{0x4C, 0x8B, 0x47, 0x01}},
|
||||
{x64.R8, x64.RDI, 1, 4, []byte{0x44, 0x8B, 0x47, 0x01}},
|
||||
{x64.R8, x64.RDI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x47, 0x01}},
|
||||
{x64.R8, x64.RDI, 1, 1, []byte{0x44, 0x8A, 0x47, 0x01}},
|
||||
{x64.R9, x64.RSI, 1, 8, []byte{0x4C, 0x8B, 0x4E, 0x01}},
|
||||
{x64.R9, x64.RSI, 1, 4, []byte{0x44, 0x8B, 0x4E, 0x01}},
|
||||
{x64.R9, x64.RSI, 1, 2, []byte{0x66, 0x44, 0x8B, 0x4E, 0x01}},
|
||||
{x64.R9, x64.RSI, 1, 1, []byte{0x44, 0x8A, 0x4E, 0x01}},
|
||||
{x64.R10, x64.RBP, 1, 8, []byte{0x4C, 0x8B, 0x55, 0x01}},
|
||||
{x64.R10, x64.RBP, 1, 4, []byte{0x44, 0x8B, 0x55, 0x01}},
|
||||
{x64.R10, x64.RBP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x55, 0x01}},
|
||||
{x64.R10, x64.RBP, 1, 1, []byte{0x44, 0x8A, 0x55, 0x01}},
|
||||
{x64.R11, x64.RSP, 1, 8, []byte{0x4C, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.R11, x64.RSP, 1, 4, []byte{0x44, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.R11, x64.RSP, 1, 2, []byte{0x66, 0x44, 0x8B, 0x5C, 0x24, 0x01}},
|
||||
{x64.R11, x64.RSP, 1, 1, []byte{0x44, 0x8A, 0x5C, 0x24, 0x01}},
|
||||
{x64.R12, x64.RBX, 1, 8, []byte{0x4C, 0x8B, 0x63, 0x01}},
|
||||
{x64.R12, x64.RBX, 1, 4, []byte{0x44, 0x8B, 0x63, 0x01}},
|
||||
{x64.R12, x64.RBX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x63, 0x01}},
|
||||
{x64.R12, x64.RBX, 1, 1, []byte{0x44, 0x8A, 0x63, 0x01}},
|
||||
{x64.R13, x64.RDX, 1, 8, []byte{0x4C, 0x8B, 0x6A, 0x01}},
|
||||
{x64.R13, x64.RDX, 1, 4, []byte{0x44, 0x8B, 0x6A, 0x01}},
|
||||
{x64.R13, x64.RDX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x6A, 0x01}},
|
||||
{x64.R13, x64.RDX, 1, 1, []byte{0x44, 0x8A, 0x6A, 0x01}},
|
||||
{x64.R14, x64.RCX, 1, 8, []byte{0x4C, 0x8B, 0x71, 0x01}},
|
||||
{x64.R14, x64.RCX, 1, 4, []byte{0x44, 0x8B, 0x71, 0x01}},
|
||||
{x64.R14, x64.RCX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x71, 0x01}},
|
||||
{x64.R14, x64.RCX, 1, 1, []byte{0x44, 0x8A, 0x71, 0x01}},
|
||||
{x64.R15, x64.RAX, 1, 8, []byte{0x4C, 0x8B, 0x78, 0x01}},
|
||||
{x64.R15, x64.RAX, 1, 4, []byte{0x44, 0x8B, 0x78, 0x01}},
|
||||
{x64.R15, x64.RAX, 1, 2, []byte{0x66, 0x44, 0x8B, 0x78, 0x01}},
|
||||
{x64.R15, x64.RAX, 1, 1, []byte{0x44, 0x8A, 0x78, 0x01}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("load %dB %s, [%s+%d]", pattern.Length, pattern.Destination, pattern.Source, pattern.Offset)
|
||||
code := x64.LoadRegister(nil, pattern.Destination, pattern.Offset, pattern.Length, pattern.Source)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package x64
|
||||
|
||||
// AddressMode encodes the addressing mode.
|
||||
type AddressMode = byte
|
||||
|
||||
const (
|
||||
AddressMemory = AddressMode(0b00)
|
||||
AddressMemoryOffset8 = AddressMode(0b01)
|
||||
AddressMemoryOffset32 = AddressMode(0b10)
|
||||
AddressDirect = AddressMode(0b11)
|
||||
)
|
||||
|
||||
// ModRM is used to generate a ModRM suffix.
|
||||
// - mod: 2 bits. The addressing mode.
|
||||
// - reg: 3 bits. Register reference or opcode extension.
|
||||
// - rm: 3 bits. Register operand.
|
||||
func ModRM(mod AddressMode, reg byte, rm byte) byte {
|
||||
return (mod << 6) | (reg << 3) | rm
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestModRM(t *testing.T) {
|
||||
testData := []struct{ mod, reg, rm, expected byte }{
|
||||
{0b_00, 0b_111, 0b_000, 0b_00_111_000},
|
||||
{0b_00, 0b_110, 0b_001, 0b_00_110_001},
|
||||
{0b_00, 0b_101, 0b_010, 0b_00_101_010},
|
||||
{0b_00, 0b_100, 0b_011, 0b_00_100_011},
|
||||
{0b_00, 0b_011, 0b_100, 0b_00_011_100},
|
||||
{0b_00, 0b_010, 0b_101, 0b_00_010_101},
|
||||
{0b_00, 0b_001, 0b_110, 0b_00_001_110},
|
||||
{0b_00, 0b_000, 0b_111, 0b_00_000_111},
|
||||
{0b_11, 0b_111, 0b_000, 0b_11_111_000},
|
||||
{0b_11, 0b_110, 0b_001, 0b_11_110_001},
|
||||
{0b_11, 0b_101, 0b_010, 0b_11_101_010},
|
||||
{0b_11, 0b_100, 0b_011, 0b_11_100_011},
|
||||
{0b_11, 0b_011, 0b_100, 0b_11_011_100},
|
||||
{0b_11, 0b_010, 0b_101, 0b_11_010_101},
|
||||
{0b_11, 0b_001, 0b_110, 0b_11_001_110},
|
||||
{0b_11, 0b_000, 0b_111, 0b_11_000_111},
|
||||
}
|
||||
|
||||
for _, test := range testData {
|
||||
modRM := x64.ModRM(test.mod, test.reg, test.rm)
|
||||
assert.Equal(t, modRM, test.expected)
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/sizeof"
|
||||
)
|
||||
|
||||
// MoveRegisterNumber moves an integer into the given register.
|
||||
func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
w := byte(0)
|
||||
|
||||
if sizeof.Signed(int64(number)) == 8 {
|
||||
w = 1
|
||||
}
|
||||
|
||||
if w == 0 && number < 0 {
|
||||
return MoveRegisterNumber32(code, destination, number)
|
||||
}
|
||||
|
||||
b := byte(0)
|
||||
|
||||
if destination > 0b111 {
|
||||
b = 1
|
||||
destination &= 0b111
|
||||
}
|
||||
|
||||
if w != 0 || b != 0 {
|
||||
code = append(code, REX(w, 0, 0, b))
|
||||
}
|
||||
|
||||
code = append(code, 0xB8+byte(destination))
|
||||
|
||||
if w == 1 {
|
||||
return binary.LittleEndian.AppendUint64(code, uint64(number))
|
||||
} else {
|
||||
return binary.LittleEndian.AppendUint32(code, uint32(number))
|
||||
}
|
||||
}
|
||||
|
||||
// MoveRegisterNumber32 moves an integer into the given register and sign-extends the register.
|
||||
func MoveRegisterNumber32(code []byte, destination cpu.Register, number int) []byte {
|
||||
b := byte(0)
|
||||
|
||||
if destination > 0b111 {
|
||||
b = 1
|
||||
destination &= 0b111
|
||||
}
|
||||
|
||||
code = append(code, REX(1, 0, 0, b))
|
||||
code = append(code, 0xC7)
|
||||
code = append(code, ModRM(AddressDirect, 0, byte(destination)))
|
||||
return binary.LittleEndian.AppendUint32(code, uint32(number))
|
||||
}
|
||||
|
||||
// MoveRegisterRegister moves a register value into another register.
|
||||
func MoveRegisterRegister(code []byte, destination cpu.Register, source cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, source, destination, 8, 0x89)
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMoveRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
// 32 bits
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0xB8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0xB9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0xBA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0xBB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0xBC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0xBD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0xBE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0xBF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x41, 0xB8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x41, 0xB9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x41, 0xBA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x41, 0xBB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x41, 0xBC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x41, 0xBD, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x41, 0xBE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x41, 0xBF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
|
||||
// 64 bits
|
||||
{x64.RAX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFFFFFFFFFF, []byte{0x48, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xB9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBA, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFFFFFFFFFF, []byte{0x49, 0xBF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
|
||||
// Negative numbers
|
||||
{x64.RAX, -1, []byte{0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RCX, -1, []byte{0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RDX, -1, []byte{0x48, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RBX, -1, []byte{0x48, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RSP, -1, []byte{0x48, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RBP, -1, []byte{0x48, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RSI, -1, []byte{0x48, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.RDI, -1, []byte{0x48, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R8, -1, []byte{0x49, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R9, -1, []byte{0x49, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R10, -1, []byte{0x49, 0xC7, 0xC2, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R11, -1, []byte{0x49, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R12, -1, []byte{0x49, 0xC7, 0xC4, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R13, -1, []byte{0x49, 0xC7, 0xC5, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R14, -1, []byte{0x49, 0xC7, 0xC6, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
{x64.R15, -1, []byte{0x49, 0xC7, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mov %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.MoveRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoveRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x4C, 0x89, 0xF8}},
|
||||
{x64.RCX, x64.R14, []byte{0x4C, 0x89, 0xF1}},
|
||||
{x64.RDX, x64.R13, []byte{0x4C, 0x89, 0xEA}},
|
||||
{x64.RBX, x64.R12, []byte{0x4C, 0x89, 0xE3}},
|
||||
{x64.RSP, x64.R11, []byte{0x4C, 0x89, 0xDC}},
|
||||
{x64.RBP, x64.R10, []byte{0x4C, 0x89, 0xD5}},
|
||||
{x64.RSI, x64.R9, []byte{0x4C, 0x89, 0xCE}},
|
||||
{x64.RDI, x64.R8, []byte{0x4C, 0x89, 0xC7}},
|
||||
{x64.R8, x64.RDI, []byte{0x49, 0x89, 0xF8}},
|
||||
{x64.R9, x64.RSI, []byte{0x49, 0x89, 0xF1}},
|
||||
{x64.R10, x64.RBP, []byte{0x49, 0x89, 0xEA}},
|
||||
{x64.R11, x64.RSP, []byte{0x49, 0x89, 0xE3}},
|
||||
{x64.R12, x64.RBX, []byte{0x49, 0x89, 0xDC}},
|
||||
{x64.R13, x64.RDX, []byte{0x49, 0x89, 0xD5}},
|
||||
{x64.R14, x64.RCX, []byte{0x49, 0x89, 0xCE}},
|
||||
{x64.R15, x64.RAX, []byte{0x49, 0x89, 0xC7}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mov %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.MoveRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// MulRegisterNumber multiplies a register with a number.
|
||||
func MulRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, destination, destination, number, 0x6B, 0x69)
|
||||
}
|
||||
|
||||
// MulRegisterRegister multiplies a register with another register.
|
||||
func MulRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, destination, operand, 8, 0x0F, 0xAF)
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMulRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, 1, []byte{0x48, 0x6B, 0xC0, 0x01}},
|
||||
{x64.RCX, 1, []byte{0x48, 0x6B, 0xC9, 0x01}},
|
||||
{x64.RDX, 1, []byte{0x48, 0x6B, 0xD2, 0x01}},
|
||||
{x64.RBX, 1, []byte{0x48, 0x6B, 0xDB, 0x01}},
|
||||
{x64.RSP, 1, []byte{0x48, 0x6B, 0xE4, 0x01}},
|
||||
{x64.RBP, 1, []byte{0x48, 0x6B, 0xED, 0x01}},
|
||||
{x64.RSI, 1, []byte{0x48, 0x6B, 0xF6, 0x01}},
|
||||
{x64.RDI, 1, []byte{0x48, 0x6B, 0xFF, 0x01}},
|
||||
{x64.R8, 1, []byte{0x4D, 0x6B, 0xC0, 0x01}},
|
||||
{x64.R9, 1, []byte{0x4D, 0x6B, 0xC9, 0x01}},
|
||||
{x64.R10, 1, []byte{0x4D, 0x6B, 0xD2, 0x01}},
|
||||
{x64.R11, 1, []byte{0x4D, 0x6B, 0xDB, 0x01}},
|
||||
{x64.R12, 1, []byte{0x4D, 0x6B, 0xE4, 0x01}},
|
||||
{x64.R13, 1, []byte{0x4D, 0x6B, 0xED, 0x01}},
|
||||
{x64.R14, 1, []byte{0x4D, 0x6B, 0xF6, 0x01}},
|
||||
{x64.R15, 1, []byte{0x4D, 0x6B, 0xFF, 0x01}},
|
||||
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC0, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xC9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xD2, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xDB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xE4, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xF6, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x4D, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mul %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.MulRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMulRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}},
|
||||
{x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}},
|
||||
{x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}},
|
||||
{x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}},
|
||||
{x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}},
|
||||
{x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}},
|
||||
{x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}},
|
||||
{x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}},
|
||||
{x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}},
|
||||
{x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}},
|
||||
{x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}},
|
||||
{x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}},
|
||||
{x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}},
|
||||
{x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}},
|
||||
{x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}},
|
||||
{x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("mul %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.MulRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// NegateRegister negates the value in the register.
|
||||
func NegateRegister(code []byte, register cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, 0b011, register, 8, 0xF7)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestNegateRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, []byte{0x48, 0xF7, 0xD8}},
|
||||
{x64.RCX, []byte{0x48, 0xF7, 0xD9}},
|
||||
{x64.RDX, []byte{0x48, 0xF7, 0xDA}},
|
||||
{x64.RBX, []byte{0x48, 0xF7, 0xDB}},
|
||||
{x64.RSP, []byte{0x48, 0xF7, 0xDC}},
|
||||
{x64.RBP, []byte{0x48, 0xF7, 0xDD}},
|
||||
{x64.RSI, []byte{0x48, 0xF7, 0xDE}},
|
||||
{x64.RDI, []byte{0x48, 0xF7, 0xDF}},
|
||||
{x64.R8, []byte{0x49, 0xF7, 0xD8}},
|
||||
{x64.R9, []byte{0x49, 0xF7, 0xD9}},
|
||||
{x64.R10, []byte{0x49, 0xF7, 0xDA}},
|
||||
{x64.R11, []byte{0x49, 0xF7, 0xDB}},
|
||||
{x64.R12, []byte{0x49, 0xF7, 0xDC}},
|
||||
{x64.R13, []byte{0x49, 0xF7, 0xDD}},
|
||||
{x64.R14, []byte{0x49, 0xF7, 0xDE}},
|
||||
{x64.R15, []byte{0x49, 0xF7, 0xDF}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("neg %s", pattern.Register)
|
||||
code := x64.NegateRegister(nil, pattern.Register)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// OrRegisterNumber performs a bitwise OR using a register and a number.
|
||||
func OrRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0b001, destination, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// OrRegisterRegister performs a bitwise OR using two registers.
|
||||
func OrRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, operand, destination, 8, 0x09)
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// PopRegister pops a value from the stack and saves it into the register.
|
||||
func PopRegister(code []byte, register cpu.Register) []byte {
|
||||
if register >= 8 {
|
||||
code = append(code, REX(0, 0, 0, 1))
|
||||
register -= 8
|
||||
}
|
||||
|
||||
return append(
|
||||
code,
|
||||
0x58+byte(register),
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestPopRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, []byte{0x58}},
|
||||
{x64.RCX, []byte{0x59}},
|
||||
{x64.RDX, []byte{0x5A}},
|
||||
{x64.RBX, []byte{0x5B}},
|
||||
{x64.RSP, []byte{0x5C}},
|
||||
{x64.RBP, []byte{0x5D}},
|
||||
{x64.RSI, []byte{0x5E}},
|
||||
{x64.RDI, []byte{0x5F}},
|
||||
{x64.R8, []byte{0x41, 0x58}},
|
||||
{x64.R9, []byte{0x41, 0x59}},
|
||||
{x64.R10, []byte{0x41, 0x5A}},
|
||||
{x64.R11, []byte{0x41, 0x5B}},
|
||||
{x64.R12, []byte{0x41, 0x5C}},
|
||||
{x64.R13, []byte{0x41, 0x5D}},
|
||||
{x64.R14, []byte{0x41, 0x5E}},
|
||||
{x64.R15, []byte{0x41, 0x5F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("pop %s", pattern.Register)
|
||||
code := x64.PopRegister(nil, pattern.Register)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// PushRegister pushes the value inside the register onto the stack.
|
||||
func PushRegister(code []byte, register cpu.Register) []byte {
|
||||
if register >= 8 {
|
||||
code = append(code, REX(0, 0, 0, 1))
|
||||
register -= 8
|
||||
}
|
||||
|
||||
return append(
|
||||
code,
|
||||
0x50+byte(register),
|
||||
)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestPushRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, []byte{0x50}},
|
||||
{x64.RCX, []byte{0x51}},
|
||||
{x64.RDX, []byte{0x52}},
|
||||
{x64.RBX, []byte{0x53}},
|
||||
{x64.RSP, []byte{0x54}},
|
||||
{x64.RBP, []byte{0x55}},
|
||||
{x64.RSI, []byte{0x56}},
|
||||
{x64.RDI, []byte{0x57}},
|
||||
{x64.R8, []byte{0x41, 0x50}},
|
||||
{x64.R9, []byte{0x41, 0x51}},
|
||||
{x64.R10, []byte{0x41, 0x52}},
|
||||
{x64.R11, []byte{0x41, 0x53}},
|
||||
{x64.R12, []byte{0x41, 0x54}},
|
||||
{x64.R13, []byte{0x41, 0x55}},
|
||||
{x64.R14, []byte{0x41, 0x56}},
|
||||
{x64.R15, []byte{0x41, 0x57}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("push %s", pattern.Register)
|
||||
code := x64.PushRegister(nil, pattern.Register)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package x64
|
||||
|
||||
// REX is used to generate a REX prefix.
|
||||
// w, r, x and b can only be set to either 0 or 1.
|
||||
func REX(w, r, x, b byte) byte {
|
||||
return 0b0100_0000 | (w << 3) | (r << 2) | (x << 1) | b
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestREX(t *testing.T) {
|
||||
testData := []struct{ w, r, x, b, expected byte }{
|
||||
{0, 0, 0, 0, 0b_0100_0000},
|
||||
{0, 0, 0, 1, 0b_0100_0001},
|
||||
{0, 0, 1, 0, 0b_0100_0010},
|
||||
{0, 0, 1, 1, 0b_0100_0011},
|
||||
{0, 1, 0, 0, 0b_0100_0100},
|
||||
{0, 1, 0, 1, 0b_0100_0101},
|
||||
{0, 1, 1, 0, 0b_0100_0110},
|
||||
{0, 1, 1, 1, 0b_0100_0111},
|
||||
{1, 0, 0, 0, 0b_0100_1000},
|
||||
{1, 0, 0, 1, 0b_0100_1001},
|
||||
{1, 0, 1, 0, 0b_0100_1010},
|
||||
{1, 0, 1, 1, 0b_0100_1011},
|
||||
{1, 1, 0, 0, 0b_0100_1100},
|
||||
{1, 1, 0, 1, 0b_0100_1101},
|
||||
{1, 1, 1, 0, 0b_0100_1110},
|
||||
{1, 1, 1, 1, 0b_0100_1111},
|
||||
}
|
||||
|
||||
for _, test := range testData {
|
||||
rex := x64.REX(test.w, test.r, test.x, test.b)
|
||||
assert.Equal(t, rex, test.expected)
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
const (
|
||||
RAX cpu.Register = iota
|
||||
RCX
|
||||
RDX
|
||||
RBX
|
||||
RSP
|
||||
RBP
|
||||
RSI
|
||||
RDI
|
||||
R8
|
||||
R9
|
||||
R10
|
||||
R11
|
||||
R12
|
||||
R13
|
||||
R14
|
||||
R15
|
||||
)
|
||||
|
||||
var (
|
||||
AllRegisters = []cpu.Register{RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15}
|
||||
SyscallInputRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9}
|
||||
SyscallOutputRegisters = []cpu.Register{RAX, RCX, R11}
|
||||
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
|
||||
InputRegisters = SyscallInputRegisters
|
||||
OutputRegisters = SyscallInputRegisters
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
package x64
|
||||
|
||||
// Return transfers program control to a return address located on the top of the stack.
|
||||
// The address is usually placed on the stack by a Call instruction.
|
||||
func Return(code []byte) []byte {
|
||||
return append(code, 0xC3)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package x64
|
||||
|
||||
// ScaleFactor encodes the scale factor.
|
||||
type ScaleFactor = byte
|
||||
|
||||
const (
|
||||
Scale1 = ScaleFactor(0b00)
|
||||
Scale2 = ScaleFactor(0b01)
|
||||
Scale4 = ScaleFactor(0b10)
|
||||
Scale8 = ScaleFactor(0b11)
|
||||
)
|
||||
|
||||
// SIB is used to generate an SIB byte.
|
||||
// - scale: 2 bits. Multiplies the value of the index.
|
||||
// - index: 3 bits. Specifies the index register.
|
||||
// - base: 3 bits. Specifies the base register.
|
||||
func SIB(scale ScaleFactor, index byte, base byte) byte {
|
||||
return (scale << 6) | (index << 3) | base
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestSIB(t *testing.T) {
|
||||
testData := []struct{ scale, index, base, expected byte }{
|
||||
{0b_00, 0b_111, 0b_000, 0b_00_111_000},
|
||||
{0b_00, 0b_110, 0b_001, 0b_00_110_001},
|
||||
{0b_00, 0b_101, 0b_010, 0b_00_101_010},
|
||||
{0b_00, 0b_100, 0b_011, 0b_00_100_011},
|
||||
{0b_00, 0b_011, 0b_100, 0b_00_011_100},
|
||||
{0b_00, 0b_010, 0b_101, 0b_00_010_101},
|
||||
{0b_00, 0b_001, 0b_110, 0b_00_001_110},
|
||||
{0b_00, 0b_000, 0b_111, 0b_00_000_111},
|
||||
{0b_11, 0b_111, 0b_000, 0b_11_111_000},
|
||||
{0b_11, 0b_110, 0b_001, 0b_11_110_001},
|
||||
{0b_11, 0b_101, 0b_010, 0b_11_101_010},
|
||||
{0b_11, 0b_100, 0b_011, 0b_11_100_011},
|
||||
{0b_11, 0b_011, 0b_100, 0b_11_011_100},
|
||||
{0b_11, 0b_010, 0b_101, 0b_11_010_101},
|
||||
{0b_11, 0b_001, 0b_110, 0b_11_001_110},
|
||||
{0b_11, 0b_000, 0b_111, 0b_11_000_111},
|
||||
}
|
||||
|
||||
for _, test := range testData {
|
||||
sib := x64.SIB(test.scale, test.index, test.base)
|
||||
assert.Equal(t, sib, test.expected)
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// ShiftLeftNumber shifts the register value by `bitCount` bits to the left.
|
||||
func ShiftLeftNumber(code []byte, register cpu.Register, bitCount byte) []byte {
|
||||
code = encode(code, AddressDirect, 0b100, register, 8, 0xC1)
|
||||
return append(code, bitCount)
|
||||
}
|
||||
|
||||
// ShiftRightSignedNumber shifts the signed register value by `bitCount` bits to the right.
|
||||
func ShiftRightSignedNumber(code []byte, register cpu.Register, bitCount byte) []byte {
|
||||
code = encode(code, AddressDirect, 0b111, register, 8, 0xC1)
|
||||
return append(code, bitCount)
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// StoreNumber stores a number into the memory address included in the given register.
|
||||
func StoreNumber(code []byte, register cpu.Register, offset byte, length byte, number int) []byte {
|
||||
code = memoryAccess(code, 0xC6, 0xC7, register, offset, length, 0b000)
|
||||
|
||||
switch length {
|
||||
case 8, 4:
|
||||
return binary.LittleEndian.AppendUint32(code, uint32(number))
|
||||
|
||||
case 2:
|
||||
return binary.LittleEndian.AppendUint16(code, uint16(number))
|
||||
}
|
||||
|
||||
return append(code, byte(number))
|
||||
}
|
||||
|
||||
// StoreRegister stores the contents of the `source` register into the memory address included in the given register.
|
||||
func StoreRegister(code []byte, register cpu.Register, offset byte, length byte, source cpu.Register) []byte {
|
||||
return memoryAccess(code, 0x88, 0x89, register, offset, length, source)
|
||||
}
|
@ -1,305 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestStoreNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Offset byte
|
||||
Length byte
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
// No offset
|
||||
{x64.RAX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RAX, 0, 4, 0x7F, []byte{0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RAX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x00, 0x7F, 0x00}},
|
||||
{x64.RAX, 0, 1, 0x7F, []byte{0xC6, 0x00, 0x7F}},
|
||||
{x64.RCX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RCX, 0, 4, 0x7F, []byte{0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RCX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x01, 0x7F, 0x00}},
|
||||
{x64.RCX, 0, 1, 0x7F, []byte{0xC6, 0x01, 0x7F}},
|
||||
{x64.RDX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDX, 0, 4, 0x7F, []byte{0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x02, 0x7F, 0x00}},
|
||||
{x64.RDX, 0, 1, 0x7F, []byte{0xC6, 0x02, 0x7F}},
|
||||
{x64.RBX, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBX, 0, 4, 0x7F, []byte{0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBX, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x03, 0x7F, 0x00}},
|
||||
{x64.RBX, 0, 1, 0x7F, []byte{0xC6, 0x03, 0x7F}},
|
||||
{x64.RSP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSP, 0, 4, 0x7F, []byte{0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x04, 0x24, 0x7F, 0x00}},
|
||||
{x64.RSP, 0, 1, 0x7F, []byte{0xC6, 0x04, 0x24, 0x7F}},
|
||||
{x64.RBP, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBP, 0, 4, 0x7F, []byte{0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBP, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x00, 0x7F, 0x00}},
|
||||
{x64.RBP, 0, 1, 0x7F, []byte{0xC6, 0x45, 0x00, 0x7F}},
|
||||
{x64.RSI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSI, 0, 4, 0x7F, []byte{0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x06, 0x7F, 0x00}},
|
||||
{x64.RSI, 0, 1, 0x7F, []byte{0xC6, 0x06, 0x7F}},
|
||||
{x64.RDI, 0, 8, 0x7F, []byte{0x48, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDI, 0, 4, 0x7F, []byte{0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDI, 0, 2, 0x7F, []byte{0x66, 0xC7, 0x07, 0x7F, 0x00}},
|
||||
{x64.RDI, 0, 1, 0x7F, []byte{0xC6, 0x07, 0x7F}},
|
||||
{x64.R8, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R8, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R8, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x00, 0x7F, 0x00}},
|
||||
{x64.R8, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x00, 0x7F}},
|
||||
{x64.R9, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R9, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R9, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x01, 0x7F, 0x00}},
|
||||
{x64.R9, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x01, 0x7F}},
|
||||
{x64.R10, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R10, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x02, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R10, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x02, 0x7F, 0x00}},
|
||||
{x64.R10, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x02, 0x7F}},
|
||||
{x64.R11, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R11, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x03, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R11, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x03, 0x7F, 0x00}},
|
||||
{x64.R11, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x03, 0x7F}},
|
||||
{x64.R12, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R12, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R12, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x04, 0x24, 0x7F, 0x00}},
|
||||
{x64.R12, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x04, 0x24, 0x7F}},
|
||||
{x64.R13, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R13, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R13, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x00, 0x7F, 0x00}},
|
||||
{x64.R13, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x00, 0x7F}},
|
||||
{x64.R14, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R14, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x06, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R14, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x06, 0x7F, 0x00}},
|
||||
{x64.R14, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x06, 0x7F}},
|
||||
{x64.R15, 0, 8, 0x7F, []byte{0x49, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R15, 0, 4, 0x7F, []byte{0x41, 0xC7, 0x07, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R15, 0, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x07, 0x7F, 0x00}},
|
||||
{x64.R15, 0, 1, 0x7F, []byte{0x41, 0xC6, 0x07, 0x7F}},
|
||||
|
||||
// Offset of 1
|
||||
{x64.RAX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RAX, 1, 4, 0x7F, []byte{0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RAX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x40, 0x01, 0x7F, 0x00}},
|
||||
{x64.RAX, 1, 1, 0x7F, []byte{0xC6, 0x40, 0x01, 0x7F}},
|
||||
{x64.RCX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RCX, 1, 4, 0x7F, []byte{0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RCX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x41, 0x01, 0x7F, 0x00}},
|
||||
{x64.RCX, 1, 1, 0x7F, []byte{0xC6, 0x41, 0x01, 0x7F}},
|
||||
{x64.RDX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDX, 1, 4, 0x7F, []byte{0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x42, 0x01, 0x7F, 0x00}},
|
||||
{x64.RDX, 1, 1, 0x7F, []byte{0xC6, 0x42, 0x01, 0x7F}},
|
||||
{x64.RBX, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBX, 1, 4, 0x7F, []byte{0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBX, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x43, 0x01, 0x7F, 0x00}},
|
||||
{x64.RBX, 1, 1, 0x7F, []byte{0xC6, 0x43, 0x01, 0x7F}},
|
||||
{x64.RSP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSP, 1, 4, 0x7F, []byte{0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}},
|
||||
{x64.RSP, 1, 1, 0x7F, []byte{0xC6, 0x44, 0x24, 0x01, 0x7F}},
|
||||
{x64.RBP, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBP, 1, 4, 0x7F, []byte{0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RBP, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x45, 0x01, 0x7F, 0x00}},
|
||||
{x64.RBP, 1, 1, 0x7F, []byte{0xC6, 0x45, 0x01, 0x7F}},
|
||||
{x64.RSI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSI, 1, 4, 0x7F, []byte{0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RSI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x46, 0x01, 0x7F, 0x00}},
|
||||
{x64.RSI, 1, 1, 0x7F, []byte{0xC6, 0x46, 0x01, 0x7F}},
|
||||
{x64.RDI, 1, 8, 0x7F, []byte{0x48, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDI, 1, 4, 0x7F, []byte{0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.RDI, 1, 2, 0x7F, []byte{0x66, 0xC7, 0x47, 0x01, 0x7F, 0x00}},
|
||||
{x64.RDI, 1, 1, 0x7F, []byte{0xC6, 0x47, 0x01, 0x7F}},
|
||||
{x64.R8, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R8, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R8, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x40, 0x01, 0x7F, 0x00}},
|
||||
{x64.R8, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x40, 0x01, 0x7F}},
|
||||
{x64.R9, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R9, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R9, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x41, 0x01, 0x7F, 0x00}},
|
||||
{x64.R9, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x41, 0x01, 0x7F}},
|
||||
{x64.R10, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R10, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R10, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x42, 0x01, 0x7F, 0x00}},
|
||||
{x64.R10, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x42, 0x01, 0x7F}},
|
||||
{x64.R11, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R11, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R11, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x43, 0x01, 0x7F, 0x00}},
|
||||
{x64.R11, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x43, 0x01, 0x7F}},
|
||||
{x64.R12, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R12, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R12, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x44, 0x24, 0x01, 0x7F, 0x00}},
|
||||
{x64.R12, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x44, 0x24, 0x01, 0x7F}},
|
||||
{x64.R13, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R13, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R13, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x45, 0x01, 0x7F, 0x00}},
|
||||
{x64.R13, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x45, 0x01, 0x7F}},
|
||||
{x64.R14, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R14, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R14, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x46, 0x01, 0x7F, 0x00}},
|
||||
{x64.R14, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x46, 0x01, 0x7F}},
|
||||
{x64.R15, 1, 8, 0x7F, []byte{0x49, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R15, 1, 4, 0x7F, []byte{0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00, 0x00, 0x00}},
|
||||
{x64.R15, 1, 2, 0x7F, []byte{0x66, 0x41, 0xC7, 0x47, 0x01, 0x7F, 0x00}},
|
||||
{x64.R15, 1, 1, 0x7F, []byte{0x41, 0xC6, 0x47, 0x01, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("store %dB [%s+%d], %d", pattern.Length, pattern.Register, pattern.Offset, pattern.Number)
|
||||
code := x64.StoreNumber(nil, pattern.Register, pattern.Offset, pattern.Length, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
RegisterTo cpu.Register
|
||||
Offset byte
|
||||
Length byte
|
||||
RegisterFrom cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
// No offset
|
||||
{x64.RAX, 0, 8, x64.R15, []byte{0x4C, 0x89, 0x38}},
|
||||
{x64.RAX, 0, 4, x64.R15, []byte{0x44, 0x89, 0x38}},
|
||||
{x64.RAX, 0, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x38}},
|
||||
{x64.RAX, 0, 1, x64.R15, []byte{0x44, 0x88, 0x38}},
|
||||
{x64.RCX, 0, 8, x64.R14, []byte{0x4C, 0x89, 0x31}},
|
||||
{x64.RCX, 0, 4, x64.R14, []byte{0x44, 0x89, 0x31}},
|
||||
{x64.RCX, 0, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x31}},
|
||||
{x64.RCX, 0, 1, x64.R14, []byte{0x44, 0x88, 0x31}},
|
||||
{x64.RDX, 0, 8, x64.R13, []byte{0x4C, 0x89, 0x2A}},
|
||||
{x64.RDX, 0, 4, x64.R13, []byte{0x44, 0x89, 0x2A}},
|
||||
{x64.RDX, 0, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x2A}},
|
||||
{x64.RDX, 0, 1, x64.R13, []byte{0x44, 0x88, 0x2A}},
|
||||
{x64.RBX, 0, 8, x64.R12, []byte{0x4C, 0x89, 0x23}},
|
||||
{x64.RBX, 0, 4, x64.R12, []byte{0x44, 0x89, 0x23}},
|
||||
{x64.RBX, 0, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x23}},
|
||||
{x64.RBX, 0, 1, x64.R12, []byte{0x44, 0x88, 0x23}},
|
||||
{x64.RSP, 0, 8, x64.R11, []byte{0x4C, 0x89, 0x1C, 0x24}},
|
||||
{x64.RSP, 0, 4, x64.R11, []byte{0x44, 0x89, 0x1C, 0x24}},
|
||||
{x64.RSP, 0, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x1C, 0x24}},
|
||||
{x64.RSP, 0, 1, x64.R11, []byte{0x44, 0x88, 0x1C, 0x24}},
|
||||
{x64.RBP, 0, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x00}},
|
||||
{x64.RBP, 0, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x00}},
|
||||
{x64.RBP, 0, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x00}},
|
||||
{x64.RBP, 0, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x00}},
|
||||
{x64.RSI, 0, 8, x64.R9, []byte{0x4C, 0x89, 0x0E}},
|
||||
{x64.RSI, 0, 4, x64.R9, []byte{0x44, 0x89, 0x0E}},
|
||||
{x64.RSI, 0, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x0E}},
|
||||
{x64.RSI, 0, 1, x64.R9, []byte{0x44, 0x88, 0x0E}},
|
||||
{x64.RDI, 0, 8, x64.R8, []byte{0x4C, 0x89, 0x07}},
|
||||
{x64.RDI, 0, 4, x64.R8, []byte{0x44, 0x89, 0x07}},
|
||||
{x64.RDI, 0, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x07}},
|
||||
{x64.RDI, 0, 1, x64.R8, []byte{0x44, 0x88, 0x07}},
|
||||
{x64.R8, 0, 8, x64.RDI, []byte{0x49, 0x89, 0x38}},
|
||||
{x64.R8, 0, 4, x64.RDI, []byte{0x41, 0x89, 0x38}},
|
||||
{x64.R8, 0, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x38}},
|
||||
{x64.R8, 0, 1, x64.RDI, []byte{0x41, 0x88, 0x38}},
|
||||
{x64.R9, 0, 8, x64.RSI, []byte{0x49, 0x89, 0x31}},
|
||||
{x64.R9, 0, 4, x64.RSI, []byte{0x41, 0x89, 0x31}},
|
||||
{x64.R9, 0, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x31}},
|
||||
{x64.R9, 0, 1, x64.RSI, []byte{0x41, 0x88, 0x31}},
|
||||
{x64.R10, 0, 8, x64.RBP, []byte{0x49, 0x89, 0x2A}},
|
||||
{x64.R10, 0, 4, x64.RBP, []byte{0x41, 0x89, 0x2A}},
|
||||
{x64.R10, 0, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x2A}},
|
||||
{x64.R10, 0, 1, x64.RBP, []byte{0x41, 0x88, 0x2A}},
|
||||
{x64.R11, 0, 8, x64.RSP, []byte{0x49, 0x89, 0x23}},
|
||||
{x64.R11, 0, 4, x64.RSP, []byte{0x41, 0x89, 0x23}},
|
||||
{x64.R11, 0, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x23}},
|
||||
{x64.R11, 0, 1, x64.RSP, []byte{0x41, 0x88, 0x23}},
|
||||
{x64.R12, 0, 8, x64.RBX, []byte{0x49, 0x89, 0x1C, 0x24}},
|
||||
{x64.R12, 0, 4, x64.RBX, []byte{0x41, 0x89, 0x1C, 0x24}},
|
||||
{x64.R12, 0, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x1C, 0x24}},
|
||||
{x64.R12, 0, 1, x64.RBX, []byte{0x41, 0x88, 0x1C, 0x24}},
|
||||
{x64.R13, 0, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x00}},
|
||||
{x64.R13, 0, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x00}},
|
||||
{x64.R13, 0, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x00}},
|
||||
{x64.R13, 0, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x00}},
|
||||
{x64.R14, 0, 8, x64.RCX, []byte{0x49, 0x89, 0x0E}},
|
||||
{x64.R14, 0, 4, x64.RCX, []byte{0x41, 0x89, 0x0E}},
|
||||
{x64.R14, 0, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x0E}},
|
||||
{x64.R14, 0, 1, x64.RCX, []byte{0x41, 0x88, 0x0E}},
|
||||
{x64.R15, 0, 8, x64.RAX, []byte{0x49, 0x89, 0x07}},
|
||||
{x64.R15, 0, 4, x64.RAX, []byte{0x41, 0x89, 0x07}},
|
||||
{x64.R15, 0, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x07}},
|
||||
{x64.R15, 0, 1, x64.RAX, []byte{0x41, 0x88, 0x07}},
|
||||
|
||||
// Offset of 1
|
||||
{x64.RAX, 1, 8, x64.R15, []byte{0x4C, 0x89, 0x78, 0x01}},
|
||||
{x64.RAX, 1, 4, x64.R15, []byte{0x44, 0x89, 0x78, 0x01}},
|
||||
{x64.RAX, 1, 2, x64.R15, []byte{0x66, 0x44, 0x89, 0x78, 0x01}},
|
||||
{x64.RAX, 1, 1, x64.R15, []byte{0x44, 0x88, 0x78, 0x01}},
|
||||
{x64.RCX, 1, 8, x64.R14, []byte{0x4C, 0x89, 0x71, 0x01}},
|
||||
{x64.RCX, 1, 4, x64.R14, []byte{0x44, 0x89, 0x71, 0x01}},
|
||||
{x64.RCX, 1, 2, x64.R14, []byte{0x66, 0x44, 0x89, 0x71, 0x01}},
|
||||
{x64.RCX, 1, 1, x64.R14, []byte{0x44, 0x88, 0x71, 0x01}},
|
||||
{x64.RDX, 1, 8, x64.R13, []byte{0x4C, 0x89, 0x6A, 0x01}},
|
||||
{x64.RDX, 1, 4, x64.R13, []byte{0x44, 0x89, 0x6A, 0x01}},
|
||||
{x64.RDX, 1, 2, x64.R13, []byte{0x66, 0x44, 0x89, 0x6A, 0x01}},
|
||||
{x64.RDX, 1, 1, x64.R13, []byte{0x44, 0x88, 0x6A, 0x01}},
|
||||
{x64.RBX, 1, 8, x64.R12, []byte{0x4C, 0x89, 0x63, 0x01}},
|
||||
{x64.RBX, 1, 4, x64.R12, []byte{0x44, 0x89, 0x63, 0x01}},
|
||||
{x64.RBX, 1, 2, x64.R12, []byte{0x66, 0x44, 0x89, 0x63, 0x01}},
|
||||
{x64.RBX, 1, 1, x64.R12, []byte{0x44, 0x88, 0x63, 0x01}},
|
||||
{x64.RSP, 1, 8, x64.R11, []byte{0x4C, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.RSP, 1, 4, x64.R11, []byte{0x44, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.RSP, 1, 2, x64.R11, []byte{0x66, 0x44, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.RSP, 1, 1, x64.R11, []byte{0x44, 0x88, 0x5C, 0x24, 0x01}},
|
||||
{x64.RBP, 1, 8, x64.R10, []byte{0x4C, 0x89, 0x55, 0x01}},
|
||||
{x64.RBP, 1, 4, x64.R10, []byte{0x44, 0x89, 0x55, 0x01}},
|
||||
{x64.RBP, 1, 2, x64.R10, []byte{0x66, 0x44, 0x89, 0x55, 0x01}},
|
||||
{x64.RBP, 1, 1, x64.R10, []byte{0x44, 0x88, 0x55, 0x01}},
|
||||
{x64.RSI, 1, 8, x64.R9, []byte{0x4C, 0x89, 0x4E, 0x01}},
|
||||
{x64.RSI, 1, 4, x64.R9, []byte{0x44, 0x89, 0x4E, 0x01}},
|
||||
{x64.RSI, 1, 2, x64.R9, []byte{0x66, 0x44, 0x89, 0x4E, 0x01}},
|
||||
{x64.RSI, 1, 1, x64.R9, []byte{0x44, 0x88, 0x4E, 0x01}},
|
||||
{x64.RDI, 1, 8, x64.R8, []byte{0x4C, 0x89, 0x47, 0x01}},
|
||||
{x64.RDI, 1, 4, x64.R8, []byte{0x44, 0x89, 0x47, 0x01}},
|
||||
{x64.RDI, 1, 2, x64.R8, []byte{0x66, 0x44, 0x89, 0x47, 0x01}},
|
||||
{x64.RDI, 1, 1, x64.R8, []byte{0x44, 0x88, 0x47, 0x01}},
|
||||
{x64.R8, 1, 8, x64.RDI, []byte{0x49, 0x89, 0x78, 0x01}},
|
||||
{x64.R8, 1, 4, x64.RDI, []byte{0x41, 0x89, 0x78, 0x01}},
|
||||
{x64.R8, 1, 2, x64.RDI, []byte{0x66, 0x41, 0x89, 0x78, 0x01}},
|
||||
{x64.R8, 1, 1, x64.RDI, []byte{0x41, 0x88, 0x78, 0x01}},
|
||||
{x64.R9, 1, 8, x64.RSI, []byte{0x49, 0x89, 0x71, 0x01}},
|
||||
{x64.R9, 1, 4, x64.RSI, []byte{0x41, 0x89, 0x71, 0x01}},
|
||||
{x64.R9, 1, 2, x64.RSI, []byte{0x66, 0x41, 0x89, 0x71, 0x01}},
|
||||
{x64.R9, 1, 1, x64.RSI, []byte{0x41, 0x88, 0x71, 0x01}},
|
||||
{x64.R10, 1, 8, x64.RBP, []byte{0x49, 0x89, 0x6A, 0x01}},
|
||||
{x64.R10, 1, 4, x64.RBP, []byte{0x41, 0x89, 0x6A, 0x01}},
|
||||
{x64.R10, 1, 2, x64.RBP, []byte{0x66, 0x41, 0x89, 0x6A, 0x01}},
|
||||
{x64.R10, 1, 1, x64.RBP, []byte{0x41, 0x88, 0x6A, 0x01}},
|
||||
{x64.R11, 1, 8, x64.RSP, []byte{0x49, 0x89, 0x63, 0x01}},
|
||||
{x64.R11, 1, 4, x64.RSP, []byte{0x41, 0x89, 0x63, 0x01}},
|
||||
{x64.R11, 1, 2, x64.RSP, []byte{0x66, 0x41, 0x89, 0x63, 0x01}},
|
||||
{x64.R11, 1, 1, x64.RSP, []byte{0x41, 0x88, 0x63, 0x01}},
|
||||
{x64.R12, 1, 8, x64.RBX, []byte{0x49, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.R12, 1, 4, x64.RBX, []byte{0x41, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.R12, 1, 2, x64.RBX, []byte{0x66, 0x41, 0x89, 0x5C, 0x24, 0x01}},
|
||||
{x64.R12, 1, 1, x64.RBX, []byte{0x41, 0x88, 0x5C, 0x24, 01}},
|
||||
{x64.R13, 1, 8, x64.RDX, []byte{0x49, 0x89, 0x55, 0x01}},
|
||||
{x64.R13, 1, 4, x64.RDX, []byte{0x41, 0x89, 0x55, 0x01}},
|
||||
{x64.R13, 1, 2, x64.RDX, []byte{0x66, 0x41, 0x89, 0x55, 0x01}},
|
||||
{x64.R13, 1, 1, x64.RDX, []byte{0x41, 0x88, 0x55, 0x01}},
|
||||
{x64.R14, 1, 8, x64.RCX, []byte{0x49, 0x89, 0x4E, 0x01}},
|
||||
{x64.R14, 1, 4, x64.RCX, []byte{0x41, 0x89, 0x4E, 0x01}},
|
||||
{x64.R14, 1, 2, x64.RCX, []byte{0x66, 0x41, 0x89, 0x4E, 0x01}},
|
||||
{x64.R14, 1, 1, x64.RCX, []byte{0x41, 0x88, 0x4E, 0x01}},
|
||||
{x64.R15, 1, 8, x64.RAX, []byte{0x49, 0x89, 0x47, 0x01}},
|
||||
{x64.R15, 1, 4, x64.RAX, []byte{0x41, 0x89, 0x47, 0x01}},
|
||||
{x64.R15, 1, 2, x64.RAX, []byte{0x66, 0x41, 0x89, 0x47, 0x01}},
|
||||
{x64.R15, 1, 1, x64.RAX, []byte{0x41, 0x88, 0x47, 0x01}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("store %dB [%s+%d], %s", pattern.Length, pattern.RegisterTo, pattern.Offset, pattern.RegisterFrom)
|
||||
code := x64.StoreRegister(nil, pattern.RegisterTo, pattern.Offset, pattern.Length, pattern.RegisterFrom)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// SubRegisterNumber subtracts a number from the given register.
|
||||
func SubRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0b101, destination, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// SubRegisterRegister subtracts a register value from another register.
|
||||
func SubRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, operand, destination, 8, 0x29)
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestSubRegisterNumber(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, 1, []byte{0x48, 0x83, 0xE8, 0x01}},
|
||||
{x64.RCX, 1, []byte{0x48, 0x83, 0xE9, 0x01}},
|
||||
{x64.RDX, 1, []byte{0x48, 0x83, 0xEA, 0x01}},
|
||||
{x64.RBX, 1, []byte{0x48, 0x83, 0xEB, 0x01}},
|
||||
{x64.RSP, 1, []byte{0x48, 0x83, 0xEC, 0x01}},
|
||||
{x64.RBP, 1, []byte{0x48, 0x83, 0xED, 0x01}},
|
||||
{x64.RSI, 1, []byte{0x48, 0x83, 0xEE, 0x01}},
|
||||
{x64.RDI, 1, []byte{0x48, 0x83, 0xEF, 0x01}},
|
||||
{x64.R8, 1, []byte{0x49, 0x83, 0xE8, 0x01}},
|
||||
{x64.R9, 1, []byte{0x49, 0x83, 0xE9, 0x01}},
|
||||
{x64.R10, 1, []byte{0x49, 0x83, 0xEA, 0x01}},
|
||||
{x64.R11, 1, []byte{0x49, 0x83, 0xEB, 0x01}},
|
||||
{x64.R12, 1, []byte{0x49, 0x83, 0xEC, 0x01}},
|
||||
{x64.R13, 1, []byte{0x49, 0x83, 0xED, 0x01}},
|
||||
{x64.R14, 1, []byte{0x49, 0x83, 0xEE, 0x01}},
|
||||
{x64.R15, 1, []byte{0x49, 0x83, 0xEF, 0x01}},
|
||||
|
||||
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE8, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xE9, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEA, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEB, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEC, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xED, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEE, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
{x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xEF, 0xFF, 0xFF, 0xFF, 0x7F}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("sub %s, %x", pattern.Register, pattern.Number)
|
||||
code := x64.SubRegisterNumber(nil, pattern.Register, pattern.Number)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubRegisterRegister(t *testing.T) {
|
||||
usagePatterns := []struct {
|
||||
Left cpu.Register
|
||||
Right cpu.Register
|
||||
Code []byte
|
||||
}{
|
||||
{x64.RAX, x64.R15, []byte{0x4C, 0x29, 0xF8}},
|
||||
{x64.RCX, x64.R14, []byte{0x4C, 0x29, 0xF1}},
|
||||
{x64.RDX, x64.R13, []byte{0x4C, 0x29, 0xEA}},
|
||||
{x64.RBX, x64.R12, []byte{0x4C, 0x29, 0xE3}},
|
||||
{x64.RSP, x64.R11, []byte{0x4C, 0x29, 0xDC}},
|
||||
{x64.RBP, x64.R10, []byte{0x4C, 0x29, 0xD5}},
|
||||
{x64.RSI, x64.R9, []byte{0x4C, 0x29, 0xCE}},
|
||||
{x64.RDI, x64.R8, []byte{0x4C, 0x29, 0xC7}},
|
||||
{x64.R8, x64.RDI, []byte{0x49, 0x29, 0xF8}},
|
||||
{x64.R9, x64.RSI, []byte{0x49, 0x29, 0xF1}},
|
||||
{x64.R10, x64.RBP, []byte{0x49, 0x29, 0xEA}},
|
||||
{x64.R11, x64.RSP, []byte{0x49, 0x29, 0xE3}},
|
||||
{x64.R12, x64.RBX, []byte{0x49, 0x29, 0xDC}},
|
||||
{x64.R13, x64.RDX, []byte{0x49, 0x29, 0xD5}},
|
||||
{x64.R14, x64.RCX, []byte{0x49, 0x29, 0xCE}},
|
||||
{x64.R15, x64.RAX, []byte{0x49, 0x29, 0xC7}},
|
||||
}
|
||||
|
||||
for _, pattern := range usagePatterns {
|
||||
t.Logf("sub %s, %s", pattern.Left, pattern.Right)
|
||||
code := x64.SubRegisterRegister(nil, pattern.Left, pattern.Right)
|
||||
assert.DeepEqual(t, code, pattern.Code)
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package x64
|
||||
|
||||
// Syscall is the primary way to communicate with the OS kernel.
|
||||
func Syscall(code []byte) []byte {
|
||||
return append(code, 0x0F, 0x05)
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// XorRegisterNumber performs a bitwise XOR using a register and a number.
|
||||
func XorRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
|
||||
return encodeNum(code, AddressDirect, 0b110, destination, number, 0x83, 0x81)
|
||||
}
|
||||
|
||||
// XorRegisterRegister performs a bitwise XOR using two registers.
|
||||
func XorRegisterRegister(code []byte, destination cpu.Register, operand cpu.Register) []byte {
|
||||
return encode(code, AddressDirect, operand, destination, 8, 0x31)
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// encode is the core function that encodes an instruction.
|
||||
func encode(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, numBytes byte, opCodes ...byte) []byte {
|
||||
w := byte(0) // Indicates a 64-bit register.
|
||||
r := byte(0) // Extension to the "reg" field in ModRM.
|
||||
x := byte(0) // Extension to the SIB index field.
|
||||
b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this).
|
||||
|
||||
if numBytes == 8 {
|
||||
w = 1
|
||||
}
|
||||
|
||||
if reg > 0b111 {
|
||||
r = 1
|
||||
reg &= 0b111
|
||||
}
|
||||
|
||||
if rm > 0b111 {
|
||||
b = 1
|
||||
rm &= 0b111
|
||||
}
|
||||
|
||||
if w != 0 || r != 0 || x != 0 || b != 0 || (numBytes == 1 && (reg == RSP || reg == RBP || reg == RSI || reg == RDI)) {
|
||||
code = append(code, REX(w, r, x, b))
|
||||
}
|
||||
|
||||
code = append(code, opCodes...)
|
||||
code = append(code, ModRM(mod, byte(reg), byte(rm)))
|
||||
return code
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package x64
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/sizeof"
|
||||
)
|
||||
|
||||
// encodeNum encodes an instruction with up to two registers and a number parameter.
|
||||
func encodeNum(code []byte, mod AddressMode, reg cpu.Register, rm cpu.Register, number int, opCode8 byte, opCode32 byte) []byte {
|
||||
if sizeof.Signed(int64(number)) == 1 {
|
||||
code = encode(code, mod, reg, rm, 8, opCode8)
|
||||
return append(code, byte(number))
|
||||
}
|
||||
|
||||
code = encode(code, mod, reg, rm, 8, opCode32)
|
||||
return binary.LittleEndian.AppendUint32(code, uint32(number))
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// memoryAccess encodes a memory access.
|
||||
func memoryAccess(code []byte, opCode8 byte, opCode32 byte, register cpu.Register, offset byte, numBytes byte, source cpu.Register) []byte {
|
||||
if numBytes == 2 {
|
||||
code = append(code, 0x66)
|
||||
}
|
||||
|
||||
opCode := opCode32
|
||||
|
||||
if numBytes == 1 {
|
||||
opCode = opCode8
|
||||
}
|
||||
|
||||
mod := AddressMemory
|
||||
|
||||
if offset != 0 || register == RBP || register == R13 {
|
||||
mod = AddressMemoryOffset8
|
||||
}
|
||||
|
||||
code = encode(code, mod, source, register, numBytes, opCode)
|
||||
|
||||
if register == RSP || register == R12 {
|
||||
code = append(code, SIB(Scale1, 0b100, 0b100))
|
||||
}
|
||||
|
||||
if mod == AddressMemoryOffset8 {
|
||||
code = append(code, offset)
|
||||
}
|
||||
|
||||
return code
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package x64_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestX64(t *testing.T) {
|
||||
assert.DeepEqual(t, x64.Call(nil, 1), []byte{0xe8, 0x01, 0x00, 0x00, 0x00})
|
||||
assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 0, 1), []byte{0xb8, 0x01, 0x00, 0x00, 0x00})
|
||||
assert.DeepEqual(t, x64.MoveRegisterNumber(nil, 1, 1), []byte{0xb9, 0x01, 0x00, 0x00, 0x00})
|
||||
assert.DeepEqual(t, x64.Return(nil), []byte{0xc3})
|
||||
assert.DeepEqual(t, x64.Syscall(nil), []byte{0x0f, 0x05})
|
||||
assert.DeepEqual(t, x64.ExtendRAXToRDX(nil), []byte{0x48, 0x99})
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"maps"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/data"
|
||||
)
|
||||
|
||||
// Assembler contains a list of instructions.
|
||||
type Assembler struct {
|
||||
Data data.Data
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
// Merge combines the contents of this assembler with another one.
|
||||
func (a *Assembler) Merge(b Assembler) {
|
||||
maps.Copy(a.Data, b.Data)
|
||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||
}
|
||||
|
||||
// SetData sets the data for the given label.
|
||||
func (a *Assembler) SetData(label string, bytes []byte) {
|
||||
if a.Data == nil {
|
||||
a.Data = data.Data{}
|
||||
}
|
||||
|
||||
a.Data[label] = bytes
|
||||
}
|
@ -1,350 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
"git.akyoto.dev/cli/q/src/build/elf"
|
||||
"git.akyoto.dev/cli/q/src/build/sizeof"
|
||||
)
|
||||
|
||||
// Finalize generates the final machine code.
|
||||
func (a Assembler) Finalize() ([]byte, []byte) {
|
||||
var (
|
||||
code = make([]byte, 0, len(a.Instructions)*8)
|
||||
data []byte
|
||||
codeLabels = map[string]Address{}
|
||||
dataLabels map[string]Address
|
||||
codePointers []*Pointer
|
||||
dataPointers []*Pointer
|
||||
)
|
||||
|
||||
for _, x := range a.Instructions {
|
||||
switch x.Mnemonic {
|
||||
case ADD:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.AddRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.AddRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case AND:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.AndRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.AndRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case SUB:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.SubRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.SubRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case MUL:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.MulRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.MulRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case DIV:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterRegister:
|
||||
if operands.Destination != x64.RAX {
|
||||
code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination)
|
||||
}
|
||||
|
||||
code = x64.ExtendRAXToRDX(code)
|
||||
code = x64.DivRegister(code, operands.Source)
|
||||
|
||||
if operands.Destination != x64.RAX {
|
||||
code = x64.MoveRegisterRegister(code, operands.Destination, x64.RAX)
|
||||
}
|
||||
}
|
||||
|
||||
case MODULO:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterRegister:
|
||||
if operands.Destination != x64.RAX {
|
||||
code = x64.MoveRegisterRegister(code, x64.RAX, operands.Destination)
|
||||
}
|
||||
|
||||
code = x64.ExtendRAXToRDX(code)
|
||||
code = x64.DivRegister(code, operands.Source)
|
||||
|
||||
if operands.Destination != x64.RDX {
|
||||
code = x64.MoveRegisterRegister(code, operands.Destination, x64.RDX)
|
||||
}
|
||||
}
|
||||
|
||||
case CALL:
|
||||
code = x64.Call(code, 0x00_00_00_00)
|
||||
size := 4
|
||||
label := x.Data.(*Label)
|
||||
|
||||
pointer := &Pointer{
|
||||
Position: Address(len(code) - size),
|
||||
OpSize: 1,
|
||||
Size: uint8(size),
|
||||
}
|
||||
|
||||
pointer.Resolve = func() Address {
|
||||
destination, exists := codeLabels[label.Name]
|
||||
|
||||
if !exists {
|
||||
panic("unknown jump label")
|
||||
}
|
||||
|
||||
distance := destination - (pointer.Position + Address(pointer.Size))
|
||||
return Address(distance)
|
||||
}
|
||||
|
||||
codePointers = append(codePointers, pointer)
|
||||
|
||||
case COMMENT:
|
||||
continue
|
||||
|
||||
case COMPARE:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.CompareRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case JE, JNE, JG, JGE, JL, JLE, JUMP:
|
||||
switch x.Mnemonic {
|
||||
case JE:
|
||||
code = x64.Jump8IfEqual(code, 0x00)
|
||||
case JNE:
|
||||
code = x64.Jump8IfNotEqual(code, 0x00)
|
||||
case JG:
|
||||
code = x64.Jump8IfGreater(code, 0x00)
|
||||
case JGE:
|
||||
code = x64.Jump8IfGreaterOrEqual(code, 0x00)
|
||||
case JL:
|
||||
code = x64.Jump8IfLess(code, 0x00)
|
||||
case JLE:
|
||||
code = x64.Jump8IfLessOrEqual(code, 0x00)
|
||||
case JUMP:
|
||||
code = x64.Jump8(code, 0x00)
|
||||
}
|
||||
|
||||
size := 1
|
||||
label := x.Data.(*Label)
|
||||
|
||||
pointer := &Pointer{
|
||||
Position: Address(len(code) - size),
|
||||
OpSize: 1,
|
||||
Size: uint8(size),
|
||||
}
|
||||
|
||||
pointer.Resolve = func() Address {
|
||||
destination, exists := codeLabels[label.Name]
|
||||
|
||||
if !exists {
|
||||
panic("unknown jump label")
|
||||
}
|
||||
|
||||
distance := destination - (pointer.Position + Address(pointer.Size))
|
||||
return Address(distance)
|
||||
}
|
||||
|
||||
codePointers = append(codePointers, pointer)
|
||||
|
||||
case LABEL:
|
||||
codeLabels[x.Data.(*Label).Name] = Address(len(code))
|
||||
|
||||
case LOAD:
|
||||
switch operands := x.Data.(type) {
|
||||
case *MemoryRegister:
|
||||
code = x64.LoadRegister(code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base)
|
||||
}
|
||||
|
||||
case MOVE:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.MoveRegisterNumber(code, operands.Register, operands.Number)
|
||||
|
||||
case *RegisterRegister:
|
||||
code = x64.MoveRegisterRegister(code, operands.Destination, operands.Source)
|
||||
|
||||
case *RegisterLabel:
|
||||
start := len(code)
|
||||
code = x64.MoveRegisterNumber(code, operands.Register, 0x00_00_00_00)
|
||||
size := 4
|
||||
opSize := len(code) - size - start
|
||||
regLabel := x.Data.(*RegisterLabel)
|
||||
|
||||
if !strings.HasPrefix(regLabel.Label, "data_") {
|
||||
panic("non-data moves not implemented yet")
|
||||
}
|
||||
|
||||
dataPointers = append(dataPointers, &Pointer{
|
||||
Position: Address(len(code) - size),
|
||||
OpSize: uint8(opSize),
|
||||
Size: uint8(size),
|
||||
Resolve: func() Address {
|
||||
destination, exists := dataLabels[regLabel.Label]
|
||||
|
||||
if !exists {
|
||||
panic("unknown label")
|
||||
}
|
||||
|
||||
return Address(destination)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case NEGATE:
|
||||
switch operands := x.Data.(type) {
|
||||
case *Register:
|
||||
code = x64.NegateRegister(code, operands.Register)
|
||||
}
|
||||
|
||||
case OR:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.OrRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.OrRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
case POP:
|
||||
switch operands := x.Data.(type) {
|
||||
case *Register:
|
||||
code = x64.PopRegister(code, operands.Register)
|
||||
}
|
||||
|
||||
case PUSH:
|
||||
switch operands := x.Data.(type) {
|
||||
case *Register:
|
||||
code = x64.PushRegister(code, operands.Register)
|
||||
}
|
||||
|
||||
case RETURN:
|
||||
code = x64.Return(code)
|
||||
|
||||
case SHIFTL:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.ShiftLeftNumber(code, operands.Register, byte(operands.Number)&0b111111)
|
||||
}
|
||||
|
||||
case SHIFTRS:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.ShiftRightSignedNumber(code, operands.Register, byte(operands.Number)&0b111111)
|
||||
}
|
||||
|
||||
case STORE:
|
||||
switch operands := x.Data.(type) {
|
||||
case *MemoryNumber:
|
||||
code = x64.StoreNumber(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Number)
|
||||
case *MemoryRegister:
|
||||
code = x64.StoreRegister(code, operands.Address.Base, operands.Address.Offset, operands.Address.Length, operands.Register)
|
||||
}
|
||||
|
||||
case SYSCALL:
|
||||
code = x64.Syscall(code)
|
||||
|
||||
case XOR:
|
||||
switch operands := x.Data.(type) {
|
||||
case *RegisterNumber:
|
||||
code = x64.XorRegisterNumber(code, operands.Register, operands.Number)
|
||||
case *RegisterRegister:
|
||||
code = x64.XorRegisterRegister(code, operands.Destination, operands.Source)
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unknown mnemonic: " + x.Mnemonic.String())
|
||||
}
|
||||
}
|
||||
|
||||
restart:
|
||||
for i, pointer := range codePointers {
|
||||
address := pointer.Resolve()
|
||||
|
||||
if sizeof.Signed(int64(address)) > int(pointer.Size) {
|
||||
left := code[:pointer.Position-Address(pointer.OpSize)]
|
||||
right := code[pointer.Position+Address(pointer.Size):]
|
||||
size := pointer.Size + pointer.OpSize
|
||||
opCode := code[pointer.Position-Address(pointer.OpSize)]
|
||||
|
||||
var jump []byte
|
||||
|
||||
switch opCode {
|
||||
case 0x74: // JE
|
||||
jump = []byte{0x0F, 0x84}
|
||||
case 0x75: // JNE
|
||||
jump = []byte{0x0F, 0x85}
|
||||
case 0x7C: // JL
|
||||
jump = []byte{0x0F, 0x8C}
|
||||
case 0x7D: // JGE
|
||||
jump = []byte{0x0F, 0x8D}
|
||||
case 0x7E: // JLE
|
||||
jump = []byte{0x0F, 0x8E}
|
||||
case 0x7F: // JG
|
||||
jump = []byte{0x0F, 0x8F}
|
||||
case 0xEB: // JMP
|
||||
jump = []byte{0xE9}
|
||||
default:
|
||||
panic(fmt.Errorf("failed to increase pointer size for instruction 0x%x", opCode))
|
||||
}
|
||||
|
||||
pointer.Position += Address(len(jump) - int(pointer.OpSize))
|
||||
pointer.OpSize = uint8(len(jump))
|
||||
pointer.Size = 4
|
||||
jump = binary.LittleEndian.AppendUint32(jump, uint32(address))
|
||||
offset := Address(len(jump)) - Address(size)
|
||||
|
||||
for _, following := range codePointers[i+1:] {
|
||||
following.Position += offset
|
||||
}
|
||||
|
||||
for key, address := range codeLabels {
|
||||
if address > pointer.Position {
|
||||
codeLabels[key] += offset
|
||||
}
|
||||
}
|
||||
|
||||
code = slices.Concat(left, jump, right)
|
||||
goto restart
|
||||
}
|
||||
|
||||
slice := code[pointer.Position : pointer.Position+Address(pointer.Size)]
|
||||
|
||||
switch pointer.Size {
|
||||
case 1:
|
||||
slice[0] = uint8(address)
|
||||
case 2:
|
||||
binary.LittleEndian.PutUint16(slice, uint16(address))
|
||||
case 4:
|
||||
binary.LittleEndian.PutUint32(slice, uint32(address))
|
||||
case 8:
|
||||
binary.LittleEndian.PutUint64(slice, uint64(address))
|
||||
}
|
||||
}
|
||||
|
||||
data, dataLabels = a.Data.Finalize()
|
||||
dataStart := config.BaseAddress + config.CodeOffset + Address(len(code))
|
||||
dataStart += int32(elf.Padding(int64(dataStart), config.Align))
|
||||
|
||||
for _, pointer := range dataPointers {
|
||||
address := dataStart + pointer.Resolve()
|
||||
slice := code[pointer.Position : pointer.Position+4]
|
||||
binary.LittleEndian.PutUint32(slice, uint32(address))
|
||||
}
|
||||
|
||||
return code, data
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package asm
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Instruction represents a single instruction which can be converted to machine code.
|
||||
type Instruction struct {
|
||||
Data fmt.Stringer
|
||||
Mnemonic Mnemonic
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package asm
|
||||
|
||||
// Comment adds a comment at the current position.
|
||||
func (a *Assembler) Comment(text string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: COMMENT,
|
||||
Data: &Label{
|
||||
Name: text,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Call calls a function whose position is identified by a label.
|
||||
func (a *Assembler) Call(name string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: CALL,
|
||||
Data: &Label{
|
||||
Name: name,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Return returns back to the caller.
|
||||
func (a *Assembler) Return() {
|
||||
if len(a.Instructions) > 0 {
|
||||
lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic
|
||||
|
||||
if lastMnemonic == RETURN || lastMnemonic == JUMP {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN})
|
||||
}
|
||||
|
||||
// Syscall executes a kernel function.
|
||||
func (a *Assembler) Syscall() {
|
||||
a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL})
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package asm
|
||||
|
||||
// Label represents a jump label.
|
||||
type Label struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *Label) String() string {
|
||||
return data.Name
|
||||
}
|
||||
|
||||
// Label adds an instruction using a label.
|
||||
func (a *Assembler) Label(mnemonic Mnemonic, name string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &Label{
|
||||
Name: name,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package asm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
type Memory struct {
|
||||
Base cpu.Register
|
||||
Offset byte
|
||||
Length byte
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// MemoryNumber operates with a memory address and a number.
|
||||
type MemoryNumber struct {
|
||||
Address Memory
|
||||
Number int
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *MemoryNumber) String() string {
|
||||
return fmt.Sprintf("%dB [%s+%d], %d", data.Address.Length, data.Address.Base, data.Address.Offset, data.Number)
|
||||
}
|
||||
|
||||
// MemoryNumber adds an instruction with a memory address and a number.
|
||||
func (a *Assembler) MemoryNumber(mnemonic Mnemonic, address Memory, number int) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &MemoryNumber{
|
||||
Address: address,
|
||||
Number: number,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// MemoryRegister operates with a memory address and a number.
|
||||
type MemoryRegister struct {
|
||||
Address Memory
|
||||
Register cpu.Register
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *MemoryRegister) String() string {
|
||||
return fmt.Sprintf("%dB [%s+%d], %s", data.Address.Length, data.Address.Base, data.Address.Offset, data.Register)
|
||||
}
|
||||
|
||||
// MemoryRegister adds an instruction with a memory address and a number.
|
||||
func (a *Assembler) MemoryRegister(mnemonic Mnemonic, address Memory, register cpu.Register) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &MemoryRegister{
|
||||
Address: address,
|
||||
Register: register,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package asm
|
||||
|
||||
type Mnemonic uint8
|
||||
|
||||
const (
|
||||
NONE Mnemonic = iota
|
||||
|
||||
// Arithmetic
|
||||
ADD
|
||||
DIV
|
||||
MODULO
|
||||
MUL
|
||||
NEGATE
|
||||
SUB
|
||||
|
||||
// Bitwise operations
|
||||
AND
|
||||
OR
|
||||
SHIFTL
|
||||
SHIFTRS
|
||||
XOR
|
||||
|
||||
// Data movement
|
||||
MOVE
|
||||
LOAD
|
||||
POP
|
||||
PUSH
|
||||
STORE
|
||||
|
||||
// Control flow
|
||||
CALL
|
||||
JE
|
||||
JNE
|
||||
JG
|
||||
JGE
|
||||
JL
|
||||
JLE
|
||||
JUMP
|
||||
LABEL
|
||||
RETURN
|
||||
SYSCALL
|
||||
|
||||
// Others
|
||||
COMMENT
|
||||
COMPARE
|
||||
)
|
||||
|
||||
// String returns a human readable version.
|
||||
func (m Mnemonic) String() string {
|
||||
switch m {
|
||||
case ADD:
|
||||
return "add"
|
||||
case AND:
|
||||
return "and"
|
||||
case CALL:
|
||||
return "call"
|
||||
case COMMENT:
|
||||
return "comment"
|
||||
case COMPARE:
|
||||
return "compare"
|
||||
case DIV:
|
||||
return "div"
|
||||
case JUMP:
|
||||
return "jump"
|
||||
case JE:
|
||||
return "jump if =="
|
||||
case JNE:
|
||||
return "jump if !="
|
||||
case JL:
|
||||
return "jump if <"
|
||||
case JG:
|
||||
return "jump if >"
|
||||
case JLE:
|
||||
return "jump if <="
|
||||
case JGE:
|
||||
return "jump if >="
|
||||
case LABEL:
|
||||
return "label"
|
||||
case LOAD:
|
||||
return "load"
|
||||
case MODULO:
|
||||
return "mod"
|
||||
case MOVE:
|
||||
return "move"
|
||||
case MUL:
|
||||
return "mul"
|
||||
case NEGATE:
|
||||
return "negate"
|
||||
case OR:
|
||||
return "or"
|
||||
case POP:
|
||||
return "pop"
|
||||
case PUSH:
|
||||
return "push"
|
||||
case RETURN:
|
||||
return "return"
|
||||
case SHIFTL:
|
||||
return "shift l"
|
||||
case SHIFTRS:
|
||||
return "shift rs"
|
||||
case SUB:
|
||||
return "sub"
|
||||
case STORE:
|
||||
return "store"
|
||||
case SYSCALL:
|
||||
return "syscall"
|
||||
case XOR:
|
||||
return "xor"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package asm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// unnecessary returns true if the register/register operation can be skipped.
|
||||
func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool {
|
||||
if len(a.Instructions) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
last := a.Instructions[len(a.Instructions)-1]
|
||||
|
||||
if mnemonic == MOVE && last.Mnemonic == MOVE {
|
||||
data, isRegReg := last.Data.(*RegisterRegister)
|
||||
|
||||
if !isRegReg {
|
||||
return false
|
||||
}
|
||||
|
||||
if data.Destination == left && data.Source == right {
|
||||
return true
|
||||
}
|
||||
|
||||
if data.Destination == right && data.Source == left {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package asm
|
||||
|
||||
// Address represents a memory address.
|
||||
type Address = int32
|
||||
|
||||
// Pointer stores a relative memory address that we can later turn into an absolute one.
|
||||
// Position: The machine code offset where the address was inserted.
|
||||
// Resolve: The function that will return the final address.
|
||||
type Pointer struct {
|
||||
Resolve func() Address
|
||||
Position Address
|
||||
OpSize uint8
|
||||
Size uint8
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// Register operates with a single register.
|
||||
type Register struct {
|
||||
Register cpu.Register
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *Register) String() string {
|
||||
return data.Register.String()
|
||||
}
|
||||
|
||||
// Register adds an instruction using a single register.
|
||||
func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &Register{
|
||||
Register: register,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// RegisterLabel operates with a register and a label.
|
||||
type RegisterLabel struct {
|
||||
Label string
|
||||
Register cpu.Register
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *RegisterLabel) String() string {
|
||||
return fmt.Sprintf("%s, %s", data.Register, data.Label)
|
||||
}
|
||||
|
||||
// RegisterLabel adds an instruction with a register and a label.
|
||||
func (a *Assembler) RegisterLabel(mnemonic Mnemonic, reg cpu.Register, label string) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &RegisterLabel{
|
||||
Register: reg,
|
||||
Label: label,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// RegisterNumber operates with a register and a number.
|
||||
type RegisterNumber struct {
|
||||
Register cpu.Register
|
||||
Number int
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *RegisterNumber) String() string {
|
||||
return fmt.Sprintf("%s, %d", data.Register, data.Number)
|
||||
}
|
||||
|
||||
// RegisterNumber adds an instruction with a register and a number.
|
||||
func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number int) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &RegisterNumber{
|
||||
Register: reg,
|
||||
Number: number,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// RegisterRegister operates with two registers.
|
||||
type RegisterRegister struct {
|
||||
Destination cpu.Register
|
||||
Source cpu.Register
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (data *RegisterRegister) String() string {
|
||||
return fmt.Sprintf("%s, %s", data.Destination, data.Source)
|
||||
}
|
||||
|
||||
// RegisterRegister adds an instruction using two registers.
|
||||
func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) {
|
||||
if a.unnecessary(mnemonic, left, right) {
|
||||
return
|
||||
}
|
||||
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: mnemonic,
|
||||
Data: &RegisterRegister{
|
||||
Destination: left,
|
||||
Source: right,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package ast
|
||||
|
||||
type Node any
|
||||
type AST []Node
|
@ -1,10 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Assert represents a condition that must be true, otherwise the program stops.
|
||||
type Assert struct {
|
||||
Condition *expression.Expression
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Assign represents an assignment to an existing variable or memory location.
|
||||
type Assign struct {
|
||||
Expression *expression.Expression
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package ast
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/expression"
|
||||
|
||||
// Call represents a function call.
|
||||
type Call struct {
|
||||
Expression *expression.Expression
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package ast
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/token"
|
||||
|
||||
// Count counts how often the given token appears in the AST.
|
||||
func Count(body AST, buffer []byte, kind token.Kind, name string) uint8 {
|
||||
count := uint8(0)
|
||||
|
||||
for _, node := range body {
|
||||
switch node := node.(type) {
|
||||
case *Assert:
|
||||
count += node.Condition.Count(buffer, kind, name)
|
||||
|
||||
case *Assign:
|
||||
count += node.Expression.Count(buffer, kind, name)
|
||||
|
||||
case *Call:
|
||||
count += node.Expression.Count(buffer, kind, name)
|
||||
|
||||
case *Define:
|
||||
count += node.Expression.Count(buffer, kind, name)
|
||||
|
||||
case *If:
|
||||
count += node.Condition.Count(buffer, kind, name)
|
||||
count += Count(node.Body, buffer, kind, name)
|
||||
count += Count(node.Else, buffer, kind, name)
|
||||
|
||||
case *Loop:
|
||||
count += Count(node.Body, buffer, kind, name)
|
||||
|
||||
case *Return:
|
||||
for _, value := range node.Values {
|
||||
count += value.Count(buffer, kind, name)
|
||||
}
|
||||
|
||||
case *Switch:
|
||||
for _, c := range node.Cases {
|
||||
if c.Condition != nil {
|
||||
count += c.Condition.Count(buffer, kind, name)
|
||||
}
|
||||
|
||||
count += Count(c.Body, buffer, kind, name)
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unknown AST type")
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Define represents a variable definition.
|
||||
type Define struct {
|
||||
Expression *expression.Expression
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package ast
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/token"
|
||||
|
||||
// EachInstruction calls the function on each instruction.
|
||||
func EachInstruction(body token.List, call func(token.List) error) error {
|
||||
start := 0
|
||||
groupLevel := 0
|
||||
blockLevel := 0
|
||||
|
||||
for i, t := range body {
|
||||
if start == i && t.Kind == token.NewLine {
|
||||
start = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
switch t.Kind {
|
||||
case token.NewLine:
|
||||
if groupLevel > 0 || blockLevel > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err := call(body[start:i])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start = i + 1
|
||||
|
||||
case token.GroupStart:
|
||||
groupLevel++
|
||||
|
||||
case token.GroupEnd:
|
||||
groupLevel--
|
||||
|
||||
case token.BlockStart:
|
||||
blockLevel++
|
||||
|
||||
case token.BlockEnd:
|
||||
blockLevel--
|
||||
|
||||
if groupLevel > 0 || blockLevel > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err := call(body[start : i+1])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if start != len(body) {
|
||||
return call(body[start:])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// If represents an if statement.
|
||||
type If struct {
|
||||
Condition *expression.Expression
|
||||
Body AST
|
||||
Else AST
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package ast
|
||||
|
||||
// Loop represents a block of repeatable statements.
|
||||
type Loop struct {
|
||||
Body AST
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Parse generates an AST from a list of tokens.
|
||||
func Parse(tokens []token.Token, source []byte) (AST, error) {
|
||||
nodes := make(AST, 0, len(tokens)/64)
|
||||
|
||||
err := EachInstruction(tokens, func(instruction token.List) error {
|
||||
node, err := parseNode(instruction, source, nodes)
|
||||
|
||||
if err == nil && node != nil {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
// IsAssignment returns true if the expression is an assignment.
|
||||
func IsAssignment(expr *expression.Expression) bool {
|
||||
return expr.Token.IsAssignment()
|
||||
}
|
||||
|
||||
// IsFunctionCall returns true if the expression is a function call.
|
||||
func IsFunctionCall(expr *expression.Expression) bool {
|
||||
return expr.Token.Kind == token.Call
|
||||
}
|
||||
|
||||
// IsVariableDefinition returns true if the expression is a variable definition.
|
||||
func IsVariableDefinition(expr *expression.Expression) bool {
|
||||
return expr.Token.Kind == token.Define
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Return represents a return statement.
|
||||
type Return struct {
|
||||
Values []*expression.Expression
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Switch represents a switch statement.
|
||||
type Switch struct {
|
||||
Cases []Case
|
||||
}
|
||||
|
||||
// Case represents a case inside a switch.
|
||||
type Case struct {
|
||||
Condition *expression.Expression
|
||||
Body AST
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// parseKeyword generates a keyword node from an instruction.
|
||||
func parseKeyword(tokens token.List, source []byte, nodes AST) (Node, error) {
|
||||
switch tokens[0].Kind {
|
||||
case token.Assert:
|
||||
if len(tokens) == 1 {
|
||||
return nil, errors.New(errors.MissingExpression, nil, tokens[0].End())
|
||||
}
|
||||
|
||||
condition := expression.Parse(tokens[1:])
|
||||
return &Assert{Condition: condition}, nil
|
||||
|
||||
case token.If:
|
||||
blockStart, _, body, err := block(tokens, source)
|
||||
condition := expression.Parse(tokens[1:blockStart])
|
||||
return &If{Condition: condition, Body: body}, err
|
||||
|
||||
case token.Else:
|
||||
_, _, body, err := block(tokens, source)
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position)
|
||||
}
|
||||
|
||||
last := nodes[len(nodes)-1]
|
||||
ifNode, exists := last.(*If)
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(errors.ExpectedIfBeforeElse, nil, tokens[0].Position)
|
||||
}
|
||||
|
||||
ifNode.Else = body
|
||||
return nil, err
|
||||
|
||||
case token.Loop:
|
||||
_, _, body, err := block(tokens, source)
|
||||
return &Loop{Body: body}, err
|
||||
|
||||
case token.Return:
|
||||
if len(tokens) == 1 {
|
||||
return &Return{}, nil
|
||||
}
|
||||
|
||||
values := expression.NewList(tokens[1:])
|
||||
return &Return{Values: values}, nil
|
||||
|
||||
case token.Switch:
|
||||
blockStart := tokens.IndexKind(token.BlockStart)
|
||||
blockEnd := tokens.LastIndexKind(token.BlockEnd)
|
||||
|
||||
if blockStart == -1 {
|
||||
return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End())
|
||||
}
|
||||
|
||||
if blockEnd == -1 {
|
||||
return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
|
||||
}
|
||||
|
||||
body := tokens[blockStart+1 : blockEnd]
|
||||
|
||||
if len(body) == 0 {
|
||||
return nil, errors.New(errors.EmptySwitch, nil, tokens[0].Position)
|
||||
}
|
||||
|
||||
cases, err := parseSwitch(body, source)
|
||||
return &Switch{Cases: cases}, err
|
||||
|
||||
default:
|
||||
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text(source)}, nil, tokens[0].Position)
|
||||
}
|
||||
}
|
||||
|
||||
// block retrieves the start and end position of a block.
|
||||
func block(tokens token.List, source []byte) (blockStart int, blockEnd int, body AST, err error) {
|
||||
blockStart = tokens.IndexKind(token.BlockStart)
|
||||
blockEnd = tokens.LastIndexKind(token.BlockEnd)
|
||||
|
||||
if blockStart == -1 {
|
||||
err = errors.New(errors.MissingBlockStart, nil, tokens[0].End())
|
||||
return
|
||||
}
|
||||
|
||||
if blockEnd == -1 {
|
||||
err = errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
|
||||
return
|
||||
}
|
||||
|
||||
body, err = Parse(tokens[blockStart+1:blockEnd], source)
|
||||
return
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// parseNode generates an AST node from an instruction.
|
||||
func parseNode(tokens token.List, source []byte, nodes AST) (Node, error) {
|
||||
if tokens[0].IsKeyword() {
|
||||
return parseKeyword(tokens, source, nodes)
|
||||
}
|
||||
|
||||
expr := expression.Parse(tokens)
|
||||
|
||||
if expr == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case IsVariableDefinition(expr):
|
||||
if len(expr.Children) < 2 {
|
||||
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
||||
}
|
||||
|
||||
return &Define{Expression: expr}, nil
|
||||
|
||||
case IsAssignment(expr):
|
||||
if len(expr.Children) < 2 {
|
||||
return nil, errors.New(errors.MissingOperand, nil, expr.Token.End())
|
||||
}
|
||||
|
||||
return &Assign{Expression: expr}, nil
|
||||
|
||||
case IsFunctionCall(expr):
|
||||
return &Call{Expression: expr}, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text(source)}, nil, expr.Token.Position)
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// parseSwitch generates the cases inside a switch statement.
|
||||
func parseSwitch(tokens token.List, source []byte) ([]Case, error) {
|
||||
var cases []Case
|
||||
|
||||
err := EachInstruction(tokens, func(caseTokens token.List) error {
|
||||
blockStart, _, body, err := block(caseTokens, source)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conditionTokens := caseTokens[:blockStart]
|
||||
var condition *expression.Expression
|
||||
|
||||
if len(conditionTokens) == 1 && conditionTokens[0].Kind == token.Identifier && conditionTokens[0].Text(source) == "_" {
|
||||
condition = nil
|
||||
} else {
|
||||
condition = expression.Parse(conditionTokens)
|
||||
}
|
||||
|
||||
cases = append(cases, Case{
|
||||
Condition: condition,
|
||||
Body: body,
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return cases, err
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/fs"
|
||||
)
|
||||
|
||||
// Compile waits for the scan to finish and compiles all functions.
|
||||
func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
||||
result := Result{}
|
||||
allFiles := make([]*fs.File, 0, 8)
|
||||
allFunctions := map[string]*core.Function{}
|
||||
|
||||
for functions != nil || files != nil || errs != nil {
|
||||
select {
|
||||
case function, ok := <-functions:
|
||||
if !ok {
|
||||
functions = nil
|
||||
continue
|
||||
}
|
||||
|
||||
function.Functions = allFunctions
|
||||
allFunctions[function.UniqueName] = function
|
||||
|
||||
case file, ok := <-files:
|
||||
if !ok {
|
||||
files = nil
|
||||
continue
|
||||
}
|
||||
|
||||
allFiles = append(allFiles, file)
|
||||
|
||||
case err, ok := <-errs:
|
||||
if !ok {
|
||||
errs = nil
|
||||
continue
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
// Start parallel compilation
|
||||
CompileFunctions(allFunctions)
|
||||
|
||||
// Report errors if any occurred
|
||||
for _, function := range allFunctions {
|
||||
if function.Err != nil {
|
||||
return result, function.Err
|
||||
}
|
||||
|
||||
result.InstructionCount += len(function.Assembler.Instructions)
|
||||
result.DataCount += len(function.Assembler.Data)
|
||||
}
|
||||
|
||||
// Check for unused imports in all files
|
||||
for _, file := range allFiles {
|
||||
for _, pkg := range file.Imports {
|
||||
if !pkg.Used {
|
||||
return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existence of `main`
|
||||
main, exists := allFunctions["main.main"]
|
||||
|
||||
if !exists {
|
||||
return result, errors.MissingMainFunction
|
||||
}
|
||||
|
||||
result.Main = main
|
||||
result.Functions = allFunctions
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CompileFunctions starts a goroutine for each function compilation and waits for completion.
|
||||
func CompileFunctions(functions map[string]*core.Function) {
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, function := range functions {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
function.Compile()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/elf"
|
||||
"git.akyoto.dev/cli/q/src/build/os/linux"
|
||||
)
|
||||
|
||||
// Result contains all the compiled functions in a build.
|
||||
type Result struct {
|
||||
Main *core.Function
|
||||
Functions map[string]*core.Function
|
||||
InstructionCount int
|
||||
DataCount int
|
||||
}
|
||||
|
||||
// finalize generates the final machine code.
|
||||
func (r *Result) finalize() ([]byte, []byte) {
|
||||
// This will be the entry point of the executable.
|
||||
// The only job of the entry function is to call `main` and exit cleanly.
|
||||
// The reason we call `main` instead of using `main` itself is to place
|
||||
// a return address on the stack, which allows return statements in `main`.
|
||||
final := asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, r.InstructionCount+8),
|
||||
Data: make(map[string][]byte, r.DataCount),
|
||||
}
|
||||
|
||||
final.Call("main.main")
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0)
|
||||
final.Syscall()
|
||||
|
||||
final.Label(asm.LABEL, "_crash")
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1)
|
||||
final.Syscall()
|
||||
|
||||
// This will place the main function immediately after the entry point
|
||||
// and also add everything the main function calls recursively.
|
||||
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
||||
final.Merge(f.Assembler)
|
||||
})
|
||||
|
||||
code, data := final.Finalize()
|
||||
return code, data
|
||||
}
|
||||
|
||||
// eachFunction recursively finds all the calls to external functions.
|
||||
// It avoids calling the same function twice with the help of a hashmap.
|
||||
func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) {
|
||||
call(caller)
|
||||
traversed[caller] = true
|
||||
|
||||
for _, x := range caller.Assembler.Instructions {
|
||||
if x.Mnemonic != asm.CALL {
|
||||
continue
|
||||
}
|
||||
|
||||
name := x.Data.(*asm.Label).Name
|
||||
callee, exists := r.Functions[name]
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if traversed[callee] {
|
||||
continue
|
||||
}
|
||||
|
||||
r.eachFunction(callee, traversed, call)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintInstructions prints out the generated instructions.
|
||||
func (r *Result) PrintInstructions() {
|
||||
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
||||
f.PrintInstructions()
|
||||
})
|
||||
}
|
||||
|
||||
// Write writes the executable to the given writer.
|
||||
func (r *Result) Write(writer io.Writer) error {
|
||||
code, data := r.finalize()
|
||||
return write(writer, code, data)
|
||||
}
|
||||
|
||||
// Write writes an executable file to disk.
|
||||
func (r *Result) WriteFile(path string) error {
|
||||
file, err := os.Create(path)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = r.Write(file)
|
||||
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(path, 0755)
|
||||
}
|
||||
|
||||
// write writes an executable file to the given writer.
|
||||
func write(writer io.Writer, code []byte, data []byte) error {
|
||||
buffer := bufio.NewWriter(writer)
|
||||
executable := elf.New(code, data)
|
||||
executable.Write(buffer)
|
||||
return buffer.Flush()
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package config
|
||||
|
||||
const (
|
||||
// This is the absolute virtual minimum address we can load a program at, see `sysctl vm.mmap_min_addr`.
|
||||
MinAddress = 0x10000
|
||||
|
||||
// The base address is the virtual address for our ELF file.
|
||||
BaseAddress = 0x40 * MinAddress
|
||||
|
||||
// The code offset is the offset of the executable machine code within the file.
|
||||
CodeOffset = 64 + 56 + 56
|
||||
|
||||
// Align decides the alignment of the sections and it must be a multiple of the page size.
|
||||
Align = 0x1000
|
||||
)
|
||||
|
||||
var (
|
||||
// Shows the assembly instructions at the end of the compilation.
|
||||
Assembler = false
|
||||
|
||||
// Calculates the result of operations on constants at compile time.
|
||||
ConstantFold = true
|
||||
|
||||
// Skips writing the executable to disk.
|
||||
Dry = false
|
||||
)
|
@ -1,59 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
var (
|
||||
Executable string
|
||||
Root string
|
||||
Library string
|
||||
WorkingDirectory string
|
||||
)
|
||||
|
||||
func init() {
|
||||
debug.SetGCPercent(-1)
|
||||
|
||||
var err error
|
||||
Executable, err = os.Executable()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
WorkingDirectory, err = os.Getwd()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Root = filepath.Dir(Executable)
|
||||
Library = filepath.Join(Root, "lib")
|
||||
stat, err := os.Stat(Library)
|
||||
|
||||
if os.IsNotExist(err) || stat == nil || !stat.IsDir() {
|
||||
findLibrary()
|
||||
}
|
||||
}
|
||||
|
||||
func findLibrary() {
|
||||
dir := WorkingDirectory
|
||||
|
||||
for {
|
||||
Library = path.Join(dir, "lib")
|
||||
stat, err := os.Stat(Library)
|
||||
|
||||
if !os.IsNotExist(err) && stat != nil && stat.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
if dir == "/" {
|
||||
panic("standard library not found")
|
||||
}
|
||||
|
||||
dir = filepath.Dir(dir)
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AddBytes adds a sequence of bytes and returns its address as a label.
|
||||
func (f *Function) AddBytes(value []byte) string {
|
||||
f.count.data++
|
||||
label := fmt.Sprintf("data_%s_%d", f.UniqueName, f.count.data)
|
||||
f.Assembler.SetData(label, value)
|
||||
return label
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Compare evaluates a boolean expression.
|
||||
func (f *Function) Compare(comparison *expression.Expression) error {
|
||||
left := comparison.Children[0]
|
||||
right := comparison.Children[1]
|
||||
|
||||
if left.IsLeaf() && left.Token.Kind == token.Identifier {
|
||||
name := left.Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
return f.Execute(comparison.Token, variable.Register, right)
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(left) && right.IsLeaf() {
|
||||
_, err := f.CompileCall(left)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.ExecuteLeaf(comparison.Token, f.CPU.Output[0], right.Token)
|
||||
}
|
||||
|
||||
tmp := f.NewRegister()
|
||||
_, err := f.ExpressionToRegister(left, tmp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer f.FreeRegister(tmp)
|
||||
return f.Execute(comparison.Token, tmp, right)
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package core
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
f.AddLabel(f.UniqueName)
|
||||
f.Err = f.CompileTokens(f.Body)
|
||||
f.Return()
|
||||
|
||||
for _, call := range f.deferred {
|
||||
call()
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileAST compiles an abstract syntax tree.
|
||||
func (f *Function) CompileAST(tree ast.AST) error {
|
||||
for _, node := range tree {
|
||||
err := f.CompileASTNode(node)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileASTNode compiles a node in the AST.
|
||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Assert:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileAssert(node)
|
||||
|
||||
case *ast.Assign:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileAssign(node)
|
||||
|
||||
case *ast.Call:
|
||||
f.Fold(node.Expression)
|
||||
_, err := f.CompileCall(node.Expression)
|
||||
return err
|
||||
|
||||
case *ast.Define:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileDefinition(node)
|
||||
|
||||
case *ast.If:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileIf(node)
|
||||
|
||||
case *ast.Loop:
|
||||
return f.CompileLoop(node)
|
||||
|
||||
case *ast.Return:
|
||||
for _, value := range node.Values {
|
||||
f.Fold(value)
|
||||
}
|
||||
|
||||
return f.CompileReturn(node)
|
||||
|
||||
case *ast.Switch:
|
||||
for _, c := range node.Cases {
|
||||
f.Fold(c.Condition)
|
||||
}
|
||||
|
||||
return f.CompileSwitch(node)
|
||||
|
||||
default:
|
||||
panic("unknown AST type")
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileAssert compiles an assertion.
|
||||
func (f *Function) CompileAssert(assert *ast.Assert) error {
|
||||
f.count.assert++
|
||||
success := fmt.Sprintf("%s_assert_%d_true", f.UniqueName, f.count.assert)
|
||||
fail := fmt.Sprintf("%s_assert_%d_false", f.UniqueName, f.count.assert)
|
||||
err := f.CompileCondition(assert.Condition, success, fail)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
|
||||
f.Defer(func() {
|
||||
f.AddLabel(fail)
|
||||
f.Jump(asm.JUMP, "_crash")
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// CompileAssign compiles an assign statement.
|
||||
func (f *Function) CompileAssign(node *ast.Assign) error {
|
||||
operator := node.Expression.Token
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
if left.IsLeaf() {
|
||||
name := left.Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
return f.Execute(operator, variable.Register, right)
|
||||
}
|
||||
|
||||
if left.Token.Kind == token.Array {
|
||||
return f.CompileAssignArray(node)
|
||||
}
|
||||
|
||||
if left.Token.Kind == token.Separator && right.Token.Kind == token.Div {
|
||||
return f.CompileAssignDivision(node)
|
||||
}
|
||||
|
||||
return errors.New(errors.NotImplemented, f.File, left.Token.Position)
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
)
|
||||
|
||||
// CompileAssignArray compiles an assign statement for array elements.
|
||||
func (f *Function) CompileAssignArray(node *ast.Assign) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
name := left.Children[0].Token.Text(f.File.Bytes)
|
||||
variable := f.VariableByName(name)
|
||||
|
||||
if variable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position)
|
||||
}
|
||||
|
||||
defer f.UseVariable(variable)
|
||||
|
||||
index := left.Children[1]
|
||||
offset, err := f.Number(index.Token)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
memory := asm.Memory{
|
||||
Base: variable.Register,
|
||||
Offset: byte(offset),
|
||||
Length: byte(1),
|
||||
}
|
||||
|
||||
_, err = f.ExpressionToMemory(right, memory)
|
||||
return err
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
)
|
||||
|
||||
// CompileAssignDivision compiles an assign statement that has quotient and remainder on the left side and division on the right.
|
||||
func (f *Function) CompileAssignDivision(node *ast.Assign) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
quotient := left.Children[0]
|
||||
name := quotient.Token.Text(f.File.Bytes)
|
||||
quotientVariable := f.VariableByName(name)
|
||||
|
||||
if quotientVariable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, quotient.Token.Position)
|
||||
}
|
||||
|
||||
remainder := left.Children[1]
|
||||
name = remainder.Token.Text(f.File.Bytes)
|
||||
remainderVariable := f.VariableByName(name)
|
||||
|
||||
if remainderVariable == nil {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, remainder.Token.Position)
|
||||
}
|
||||
|
||||
dividend := right.Children[0]
|
||||
_, dividendRegister, err := f.Evaluate(dividend)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
divisor := right.Children[1]
|
||||
err = f.Execute(right.Token, dividendRegister, divisor)
|
||||
f.RegisterRegister(asm.MOVE, quotientVariable.Register, x64.RAX)
|
||||
f.RegisterRegister(asm.MOVE, remainderVariable.Register, x64.RDX)
|
||||
return err
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// CompileCall executes a function call.
|
||||
// All call registers must hold the correct parameter values before the function invocation.
|
||||
// Registers that are in use must be saved if they are modified by the function.
|
||||
// After the function call, they must be restored in reverse order.
|
||||
func (f *Function) CompileCall(root *expression.Expression) (*Function, error) {
|
||||
var (
|
||||
pkg = f.Package
|
||||
nameNode = root.Children[0]
|
||||
fn *Function
|
||||
name string
|
||||
fullName string
|
||||
exists bool
|
||||
)
|
||||
|
||||
if nameNode.IsLeaf() {
|
||||
name = nameNode.Token.Text(f.File.Bytes)
|
||||
|
||||
if name == "syscall" {
|
||||
return nil, f.CompileSyscall(root)
|
||||
}
|
||||
} else {
|
||||
pkg = nameNode.Children[0].Token.Text(f.File.Bytes)
|
||||
name = nameNode.Children[1].Token.Text(f.File.Bytes)
|
||||
}
|
||||
|
||||
if pkg != f.File.Package {
|
||||
if f.File.Imports == nil {
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
imp, exists := f.File.Imports[pkg]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
imp.Used = true
|
||||
}
|
||||
|
||||
tmp := strings.Builder{}
|
||||
tmp.WriteString(pkg)
|
||||
tmp.WriteString(".")
|
||||
tmp.WriteString(name)
|
||||
fullName = tmp.String()
|
||||
fn, exists = f.Functions[fullName]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position)
|
||||
}
|
||||
|
||||
parameters := root.Children[1:]
|
||||
registers := f.CPU.Input[:len(parameters)]
|
||||
|
||||
for i := len(parameters) - 1; i >= 0; i-- {
|
||||
typ, err := f.ExpressionToRegister(parameters[i], registers[i])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if typ != fn.Parameters[i].Type {
|
||||
return nil, errors.New(&errors.TypeMismatch{
|
||||
Encountered: string(typ),
|
||||
Expected: string(fn.Parameters[i].Type),
|
||||
ParameterName: fn.Parameters[i].Name,
|
||||
}, f.File, parameters[i].Token.Position)
|
||||
}
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.Output[:len(fn.ReturnTypes)] {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.General {
|
||||
if f.RegisterIsUsed(register) {
|
||||
f.Register(asm.PUSH, register)
|
||||
}
|
||||
}
|
||||
|
||||
f.Call(fullName)
|
||||
|
||||
for _, register := range registers {
|
||||
if register == f.CPU.Output[0] && root.Parent != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
for i := len(f.CPU.General) - 1; i >= 0; i-- {
|
||||
register := f.CPU.General[i]
|
||||
|
||||
if f.RegisterIsUsed(register) {
|
||||
f.Register(asm.POP, register)
|
||||
}
|
||||
}
|
||||
|
||||
return fn, nil
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// CompileCondition inserts code to jump to the start label or end label depending on the truth of the condition.
|
||||
func (f *Function) CompileCondition(condition *expression.Expression, successLabel string, failLabel string) error {
|
||||
switch condition.Token.Kind {
|
||||
case token.LogicalOr:
|
||||
f.count.subBranch++
|
||||
leftFailLabel := fmt.Sprintf("%s_false_%d", f.UniqueName, f.count.subBranch)
|
||||
|
||||
// Left
|
||||
left := condition.Children[0]
|
||||
err := f.CompileCondition(left, successLabel, leftFailLabel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.JumpIfTrue(left.Token.Kind, successLabel)
|
||||
|
||||
// Right
|
||||
f.AddLabel(leftFailLabel)
|
||||
right := condition.Children[1]
|
||||
err = f.CompileCondition(right, successLabel, failLabel)
|
||||
|
||||
if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() {
|
||||
f.JumpIfTrue(right.Token.Kind, successLabel)
|
||||
} else {
|
||||
f.JumpIfFalse(right.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
case token.LogicalAnd:
|
||||
f.count.subBranch++
|
||||
leftSuccessLabel := fmt.Sprintf("%s_true_%d", f.UniqueName, f.count.subBranch)
|
||||
|
||||
// Left
|
||||
left := condition.Children[0]
|
||||
err := f.CompileCondition(left, leftSuccessLabel, failLabel)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.JumpIfFalse(left.Token.Kind, failLabel)
|
||||
|
||||
// Right
|
||||
f.AddLabel(leftSuccessLabel)
|
||||
right := condition.Children[1]
|
||||
err = f.CompileCondition(right, successLabel, failLabel)
|
||||
|
||||
if condition.Parent != nil && condition.Parent.Token.Kind == token.LogicalOr && condition != condition.Parent.LastChild() {
|
||||
f.JumpIfTrue(right.Token.Kind, successLabel)
|
||||
} else {
|
||||
f.JumpIfFalse(right.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
default:
|
||||
err := f.Compare(condition)
|
||||
|
||||
if condition.Parent == nil {
|
||||
f.JumpIfFalse(condition.Token.Kind, failLabel)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/types"
|
||||
)
|
||||
|
||||
// CompileDefinition compiles a variable definition.
|
||||
func (f *Function) CompileDefinition(node *ast.Define) error {
|
||||
left := node.Expression.Children[0]
|
||||
right := node.Expression.Children[1]
|
||||
|
||||
if left.IsLeaf() {
|
||||
variable, err := f.Define(left)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
typ, err := f.ExpressionToRegister(right, variable.Register)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
variable.Type = typ
|
||||
|
||||
if variable.Type == types.Invalid {
|
||||
return errors.New(errors.UnknownType, f.File, node.Expression.Token.End())
|
||||
}
|
||||
|
||||
f.AddVariable(variable)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ast.IsFunctionCall(right) {
|
||||
return errors.New(errors.NotImplemented, f.File, node.Expression.Token.Position)
|
||||
}
|
||||
|
||||
count := 0
|
||||
called, err := f.CompileCall(right)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return left.EachLeaf(func(leaf *expression.Expression) error {
|
||||
variable, err := f.Define(leaf)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if called != nil {
|
||||
variable.Type = called.ReturnTypes[count]
|
||||
}
|
||||
|
||||
f.RegisterRegister(asm.MOVE, variable.Register, f.CPU.Output[count])
|
||||
f.AddVariable(variable)
|
||||
count++
|
||||
return nil
|
||||
})
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileIf compiles a branch instruction.
|
||||
func (f *Function) CompileIf(branch *ast.If) error {
|
||||
f.count.branch++
|
||||
|
||||
var (
|
||||
end string
|
||||
success = fmt.Sprintf("%s_if_%d_true", f.UniqueName, f.count.branch)
|
||||
fail = fmt.Sprintf("%s_if_%d_false", f.UniqueName, f.count.branch)
|
||||
err = f.CompileCondition(branch.Condition, success, fail)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if branch.Else != nil {
|
||||
end = fmt.Sprintf("%s_if_%d_end", f.UniqueName, f.count.branch)
|
||||
f.Jump(asm.JUMP, end)
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
f.AddLabel(fail)
|
||||
|
||||
if branch.Else != nil {
|
||||
f.PushScope(branch.Else, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Else)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
f.AddLabel(end)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileLoop compiles a loop instruction.
|
||||
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||
for _, register := range f.CPU.Input {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
f.count.loop++
|
||||
label := fmt.Sprintf("%s_loop_%d", f.UniqueName, f.count.loop)
|
||||
f.AddLabel(label)
|
||||
scope := f.PushScope(loop.Body, f.File.Bytes)
|
||||
scope.InLoop = true
|
||||
err := f.CompileAST(loop.Body)
|
||||
f.Jump(asm.JUMP, label)
|
||||
f.PopScope()
|
||||
return err
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/types"
|
||||
)
|
||||
|
||||
// CompileReturn compiles a return instruction.
|
||||
func (f *Function) CompileReturn(node *ast.Return) error {
|
||||
defer f.Return()
|
||||
|
||||
if len(node.Values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(node.Values) - 1; i >= 0; i-- {
|
||||
typ, err := f.ExpressionToRegister(node.Values[i], f.CPU.Output[i])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if typ != types.Any && typ != f.ReturnTypes[i] {
|
||||
return errors.New(&errors.TypeMismatch{
|
||||
Encountered: string(typ),
|
||||
Expected: string(f.ReturnTypes[i]),
|
||||
ParameterName: "",
|
||||
IsReturn: true,
|
||||
}, f.File, node.Values[i].Token.Position)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileSwitch compiles a multi-branch instruction.
|
||||
func (f *Function) CompileSwitch(s *ast.Switch) error {
|
||||
f.count.multiBranch++
|
||||
end := fmt.Sprintf("%s_switch_%d_end", f.UniqueName, f.count.multiBranch)
|
||||
|
||||
for _, branch := range s.Cases {
|
||||
if branch.Condition == nil {
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err := f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.PopScope()
|
||||
break
|
||||
}
|
||||
|
||||
f.count.branch++
|
||||
|
||||
var (
|
||||
success = fmt.Sprintf("%s_case_%d_true", f.UniqueName, f.count.branch)
|
||||
fail = fmt.Sprintf("%s_case_%d_false", f.UniqueName, f.count.branch)
|
||||
err = f.CompileCondition(branch.Condition, success, fail)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.AddLabel(success)
|
||||
f.PushScope(branch.Body, f.File.Bytes)
|
||||
err = f.CompileAST(branch.Body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Jump(asm.JUMP, end)
|
||||
f.PopScope()
|
||||
f.AddLabel(fail)
|
||||
}
|
||||
|
||||
f.AddLabel(end)
|
||||
return nil
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// CompileSyscall executes a kernel syscall.
|
||||
func (f *Function) CompileSyscall(root *expression.Expression) error {
|
||||
parameters := root.Children[1:]
|
||||
registers := f.CPU.SyscallInput[:len(parameters)]
|
||||
err := f.ExpressionsToRegisters(parameters, registers)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, register := range f.CPU.SyscallOutput {
|
||||
f.SaveRegister(register)
|
||||
}
|
||||
|
||||
f.Syscall()
|
||||
|
||||
for _, register := range registers {
|
||||
if register == f.CPU.SyscallOutput[0] && root.Parent != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
f.FreeRegister(register)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// CompileTokens compiles a token list.
|
||||
func (f *Function) CompileTokens(tokens []token.Token) error {
|
||||
body, err := ast.Parse(tokens, f.File.Bytes)
|
||||
|
||||
if err != nil {
|
||||
err.(*errors.Error).File = f.File
|
||||
return err
|
||||
}
|
||||
|
||||
return f.CompileAST(body)
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package core
|
||||
|
||||
// Defer executes the callback at the end of function compilation.
|
||||
func (f *Function) Defer(call func()) {
|
||||
f.deferred = append(f.deferred, call)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/scope"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Define defines a new variable.
|
||||
func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) {
|
||||
name := leaf.Token.Text(f.File.Bytes)
|
||||
|
||||
if f.IdentifierExists(name) {
|
||||
return nil, errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position)
|
||||
}
|
||||
|
||||
uses := token.Count(f.Body, f.File.Bytes, token.Identifier, name) - 1
|
||||
|
||||
if uses == 0 {
|
||||
return nil, errors.New(&errors.UnusedVariable{Name: name}, f.File, leaf.Token.Position)
|
||||
}
|
||||
|
||||
variable := &scope.Variable{
|
||||
Name: name,
|
||||
Register: f.NewRegister(),
|
||||
Alive: uses,
|
||||
}
|
||||
|
||||
return variable, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user