Simplified file structure

This commit is contained in:
2024-08-07 19:39:10 +02:00
parent 1b13539b22
commit 66569446b1
219 changed files with 453 additions and 457 deletions

View File

@ -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.

View File

@ -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}

View File

@ -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}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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),
)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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),
)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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})
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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),
)
}

View File

@ -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)
}
}

View File

@ -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),
)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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})
}

View File

@ -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,
},
})
}

View File

@ -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
}

View File

@ -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,
},
})
}

View File

@ -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,
},
})
}

View File

@ -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 ""
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
},
})
}

View File

@ -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,
},
})
}

View File

@ -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,
},
})
}

View File

@ -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,
},
})
}

View File

@ -1,4 +0,0 @@
package ast
type Node any
type AST []Node

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,6 +0,0 @@
package ast
// Loop represents a block of repeatable statements.
type Loop struct {
Body AST
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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
)

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
})
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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