This commit is contained in:
parent
2b703e9af2
commit
70c2da4a4d
40 changed files with 821 additions and 117 deletions
12
lib/core/core_linux_arm.q
Normal file
12
lib/core/core_linux_arm.q
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
init() {
|
||||||
|
main.main()
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit() {
|
||||||
|
syscall(93, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
syscall(93, 1)
|
||||||
|
}
|
12
lib/core/core_linux_x86.q
Normal file
12
lib/core/core_linux_x86.q
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
init() {
|
||||||
|
main.main()
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit() {
|
||||||
|
syscall(60, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
syscall(60, 1)
|
||||||
|
}
|
12
lib/core/core_mac.q
Normal file
12
lib/core/core_mac.q
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
init() {
|
||||||
|
main.main()
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit() {
|
||||||
|
syscall(0x2000001, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
syscall(0x2000001, 1)
|
||||||
|
}
|
28
lib/core/core_windows.q
Normal file
28
lib/core/core_windows.q
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
init() {
|
||||||
|
// kernel32.SetConsoleCP(cp.utf8)
|
||||||
|
// kernel32.SetConsoleOutputCP(cp.utf8)
|
||||||
|
main.main()
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
exit() {
|
||||||
|
// kernel32.ExitProcess(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
crash() {
|
||||||
|
// kernel32.ExitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// const {
|
||||||
|
// cp {
|
||||||
|
// utf8 65001
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// extern {
|
||||||
|
// kernel32 {
|
||||||
|
// SetConsoleCP(cp uint)
|
||||||
|
// SetConsoleOutputCP(cp uint)
|
||||||
|
// ExitProcess(code uint)
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -1,3 +1,3 @@
|
||||||
write(buffer []byte) -> (written int) {
|
write(buffer []byte) -> (written int) {
|
||||||
return syscall(64, 0, buffer, len(buffer))
|
return syscall(64, 1, buffer, len(buffer))
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
write(buffer []byte) -> (written int) {
|
write(buffer []byte) -> (written int) {
|
||||||
return syscall(1, 0, buffer, len(buffer))
|
return syscall(1, 1, buffer, len(buffer))
|
||||||
}
|
}
|
3
lib/io/write_mac.q
Normal file
3
lib/io/write_mac.q
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
write(buffer []byte) -> (written int) {
|
||||||
|
return syscall(0x2000004, 1, buffer, len(buffer))
|
||||||
|
}
|
3
lib/io/write_windows.q
Normal file
3
lib/io/write_windows.q
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
write(_ []byte) -> (written int) {
|
||||||
|
return 0
|
||||||
|
}
|
|
@ -40,3 +40,8 @@ const (
|
||||||
const (
|
const (
|
||||||
ZR = SP // Zero register uses the same numerical value as SP
|
ZR = SP // Zero register uses the same numerical value as SP
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var CPU = cpu.CPU{
|
||||||
|
Call: []cpu.Register{X0, X1, X2, X3, X4, X5, X6},
|
||||||
|
Syscall: []cpu.Register{X8, X0, X1, X2, X3, X4, X5},
|
||||||
|
}
|
4
src/asm/Address.go
Normal file
4
src/asm/Address.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
// Address represents a memory address.
|
||||||
|
type Address = int
|
78
src/asm/Assembler.go
Normal file
78
src/asm/Assembler.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
"git.urbach.dev/cli/q/src/data"
|
||||||
|
"git.urbach.dev/cli/q/src/elf"
|
||||||
|
"git.urbach.dev/cli/q/src/exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assembler contains a list of instructions.
|
||||||
|
type Assembler struct {
|
||||||
|
Data data.Data
|
||||||
|
Instructions []Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append adds another instruction.
|
||||||
|
func (a *Assembler) Append(instr Instruction) {
|
||||||
|
a.Instructions = append(a.Instructions, instr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile compiles the instructions to machine code.
|
||||||
|
func (a *Assembler) Compile(b *build.Build) (code []byte, data []byte) {
|
||||||
|
data, dataLabels := a.Data.Finalize()
|
||||||
|
|
||||||
|
c := compiler{
|
||||||
|
code: make([]byte, 0, len(a.Instructions)*8),
|
||||||
|
data: data,
|
||||||
|
dataLabels: dataLabels,
|
||||||
|
labels: make(map[string]Address, 32),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch b.Arch {
|
||||||
|
case build.ARM:
|
||||||
|
armc := compilerARM{compiler: &c}
|
||||||
|
|
||||||
|
for _, instr := range a.Instructions {
|
||||||
|
armc.Compile(instr)
|
||||||
|
}
|
||||||
|
|
||||||
|
case build.X86:
|
||||||
|
x86c := compilerX86{compiler: &c}
|
||||||
|
|
||||||
|
for _, instr := range a.Instructions {
|
||||||
|
x86c.Compile(instr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x := exe.New(elf.HeaderEnd, b.FileAlign, b.MemoryAlign)
|
||||||
|
x.InitSections(c.code, c.data)
|
||||||
|
dataSectionOffset := x.Sections[1].MemoryOffset - x.Sections[0].MemoryOffset
|
||||||
|
|
||||||
|
for dataLabel, address := range dataLabels {
|
||||||
|
c.labels[dataLabel] = dataSectionOffset + address
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, call := range c.deferred {
|
||||||
|
call()
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.code, c.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Insert(label, bytes)
|
||||||
|
}
|
34
src/asm/Instruction.go
Normal file
34
src/asm/Instruction.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import "git.urbach.dev/cli/q/src/cpu"
|
||||||
|
|
||||||
|
type Instruction interface{}
|
||||||
|
|
||||||
|
type Call struct {
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FunctionStart struct{}
|
||||||
|
type FunctionEnd struct{}
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveRegisterLabel struct {
|
||||||
|
Destination cpu.Register
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveRegisterNumber struct {
|
||||||
|
Destination cpu.Register
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveRegisterRegister struct {
|
||||||
|
Destination cpu.Register
|
||||||
|
Source cpu.Register
|
||||||
|
}
|
||||||
|
|
||||||
|
type Return struct{}
|
||||||
|
type Syscall struct{}
|
13
src/asm/compiler.go
Normal file
13
src/asm/compiler.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
type compiler struct {
|
||||||
|
code []byte
|
||||||
|
data []byte
|
||||||
|
dataLabels map[string]Address
|
||||||
|
labels map[string]Address
|
||||||
|
deferred []func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) Defer(call func()) {
|
||||||
|
c.deferred = append(c.deferred, call)
|
||||||
|
}
|
63
src/asm/compilerARM.go
Normal file
63
src/asm/compilerARM.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/arm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type compilerARM struct {
|
||||||
|
*compiler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) append(code uint32) {
|
||||||
|
c.code = binary.LittleEndian.AppendUint32(c.code, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compilerARM) Compile(instr Instruction) {
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *Call:
|
||||||
|
start := len(c.code)
|
||||||
|
c.append(arm.Call(0))
|
||||||
|
|
||||||
|
c.Defer(func() {
|
||||||
|
address, exists := c.labels[instr.Label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown label: " + instr.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (address - start) / 4
|
||||||
|
binary.LittleEndian.PutUint32(c.code[start:start+4], arm.Call(offset))
|
||||||
|
})
|
||||||
|
case *FunctionStart:
|
||||||
|
c.append(arm.StorePair(arm.FP, arm.LR, arm.SP, -16))
|
||||||
|
c.append(arm.MoveRegisterRegister(arm.FP, arm.SP))
|
||||||
|
case *FunctionEnd:
|
||||||
|
c.append(arm.LoadPair(arm.FP, arm.LR, arm.SP, 16))
|
||||||
|
case *Label:
|
||||||
|
c.labels[instr.Name] = len(c.code)
|
||||||
|
case *MoveRegisterLabel:
|
||||||
|
start := len(c.code)
|
||||||
|
c.append(arm.LoadAddress(instr.Destination, 0))
|
||||||
|
|
||||||
|
c.Defer(func() {
|
||||||
|
address, exists := c.labels[instr.Label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown label: " + instr.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := address - start
|
||||||
|
binary.LittleEndian.PutUint32(c.code[start:start+4], arm.LoadAddress(instr.Destination, offset))
|
||||||
|
})
|
||||||
|
case *MoveRegisterNumber:
|
||||||
|
c.code = arm.MoveRegisterNumber(c.code, instr.Destination, instr.Number)
|
||||||
|
case *MoveRegisterRegister:
|
||||||
|
c.append(arm.MoveRegisterRegister(instr.Destination, instr.Source))
|
||||||
|
case *Return:
|
||||||
|
c.append(arm.Return())
|
||||||
|
case *Syscall:
|
||||||
|
c.append(arm.Syscall())
|
||||||
|
}
|
||||||
|
}
|
54
src/asm/compilerX86.go
Normal file
54
src/asm/compilerX86.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/x86"
|
||||||
|
)
|
||||||
|
|
||||||
|
type compilerX86 struct {
|
||||||
|
*compiler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compilerX86) Compile(instr Instruction) {
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *Call:
|
||||||
|
c.code = x86.Call(c.code, 0)
|
||||||
|
end := len(c.code)
|
||||||
|
|
||||||
|
c.Defer(func() {
|
||||||
|
address, exists := c.labels[instr.Label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown label: " + instr.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := address - end
|
||||||
|
binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset))
|
||||||
|
})
|
||||||
|
case *Label:
|
||||||
|
c.labels[instr.Name] = len(c.code)
|
||||||
|
case *MoveRegisterLabel:
|
||||||
|
c.code = x86.LoadAddress(c.code, instr.Destination, 0)
|
||||||
|
end := len(c.code)
|
||||||
|
|
||||||
|
c.Defer(func() {
|
||||||
|
address, exists := c.labels[instr.Label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
panic("unknown label: " + instr.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := address - end
|
||||||
|
binary.LittleEndian.PutUint32(c.code[end-4:end], uint32(offset))
|
||||||
|
})
|
||||||
|
case *MoveRegisterNumber:
|
||||||
|
c.code = x86.MoveRegisterNumber(c.code, instr.Destination, instr.Number)
|
||||||
|
case *MoveRegisterRegister:
|
||||||
|
c.code = x86.MoveRegisterRegister(c.code, instr.Destination, instr.Source)
|
||||||
|
case *Return:
|
||||||
|
c.code = x86.Return(c.code)
|
||||||
|
case *Syscall:
|
||||||
|
c.code = x86.Syscall(c.code)
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ func _build(args []string) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
err = linker.WriteExecutable(b, result)
|
err = linker.WriteFile(b.Executable(), b, result)
|
||||||
return exit(err)
|
return exit(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ func Compile(b *build.Build) (*core.Environment, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(all.Files) == 0 {
|
// Check for existence of `main`
|
||||||
return nil, NoInputFiles
|
_, exists := all.Functions["main.main"]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, MissingMainFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
compileFunctions(maps.Values(all.Functions))
|
compileFunctions(maps.Values(all.Functions))
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestNoInputFiles(t *testing.T) {
|
||||||
b := build.New(".")
|
b := build.New(".")
|
||||||
_, err := compiler.Compile(b)
|
_, err := compiler.Compile(b)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.True(t, errors.Is(err, compiler.NoInputFiles))
|
assert.True(t, errors.Is(err, compiler.MissingMainFunction))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelloExample(t *testing.T) {
|
func TestHelloExample(t *testing.T) {
|
||||||
|
|
|
@ -3,5 +3,5 @@ package compiler
|
||||||
import "git.urbach.dev/cli/q/src/errors"
|
import "git.urbach.dev/cli/q/src/errors"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
NoInputFiles = errors.String("No input files")
|
MissingMainFunction = errors.String("Missing main function")
|
||||||
)
|
)
|
|
@ -1,21 +1,28 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/asm"
|
||||||
"git.urbach.dev/cli/q/src/cpu"
|
"git.urbach.dev/cli/q/src/cpu"
|
||||||
|
"git.urbach.dev/cli/q/src/ssa"
|
||||||
"git.urbach.dev/cli/q/src/token"
|
"git.urbach.dev/cli/q/src/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compile turns a function into machine code.
|
// Compile turns a function into machine code.
|
||||||
func (f *Function) Compile() {
|
func (f *Function) Compile() {
|
||||||
registerCount := 0
|
extra := 0
|
||||||
|
|
||||||
for _, input := range f.Input {
|
for i, input := range f.Input {
|
||||||
f.Identifiers[input.Name] = f.AppendRegister(cpu.Register(registerCount))
|
if input.Name == "_" {
|
||||||
registerCount++
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Identifiers[input.Name] = f.AppendRegister(i + extra)
|
||||||
|
|
||||||
if input.TypeTokens[0].Kind == token.ArrayStart {
|
if input.TypeTokens[0].Kind == token.ArrayStart {
|
||||||
f.Identifiers[input.Name+".length"] = f.AppendRegister(cpu.Register(registerCount))
|
extra++
|
||||||
registerCount++
|
f.Identifiers[input.Name+".length"] = f.AppendRegister(i + extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,4 +35,83 @@ func (f *Function) Compile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Err = f.CheckDeadCode()
|
f.Err = f.CheckDeadCode()
|
||||||
|
|
||||||
|
if f.Err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Assembler.Append(&asm.Label{Name: f.UniqueName})
|
||||||
|
|
||||||
|
if !f.IsLeaf() && f.UniqueName != "core.init" {
|
||||||
|
f.Assembler.Append(&asm.FunctionStart{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for instr := range f.Values {
|
||||||
|
switch instr := instr.(type) {
|
||||||
|
case *ssa.Call:
|
||||||
|
f.mv(instr.Args[1:], f.CPU.Call)
|
||||||
|
|
||||||
|
switch arg := instr.Args[0].(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
f.Assembler.Instructions = append(f.Assembler.Instructions, &asm.Call{Label: arg.UniqueName})
|
||||||
|
}
|
||||||
|
|
||||||
|
case *ssa.Syscall:
|
||||||
|
f.mv(instr.Args, f.CPU.Syscall)
|
||||||
|
f.Assembler.Append(&asm.Syscall{})
|
||||||
|
|
||||||
|
case *ssa.Return:
|
||||||
|
f.Assembler.Append(&asm.Return{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.IsLeaf() && f.UniqueName != "core.init" {
|
||||||
|
f.Assembler.Append(&asm.FunctionEnd{})
|
||||||
|
}
|
||||||
|
|
||||||
|
switch f.Assembler.Instructions[len(f.Assembler.Instructions)-1].(type) {
|
||||||
|
case *asm.Return:
|
||||||
|
default:
|
||||||
|
f.Assembler.Append(&asm.Return{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) mv(args []ssa.Value, registers []cpu.Register) {
|
||||||
|
extra := 0
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
switch arg.(type) {
|
||||||
|
case *ssa.Bytes:
|
||||||
|
extra++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range slices.Backward(args) {
|
||||||
|
switch arg := arg.(type) {
|
||||||
|
case *ssa.Int:
|
||||||
|
f.Assembler.Append(&asm.MoveRegisterNumber{
|
||||||
|
Destination: registers[i+extra],
|
||||||
|
Number: arg.Int,
|
||||||
|
})
|
||||||
|
case *ssa.Parameter:
|
||||||
|
f.Assembler.Append(&asm.MoveRegisterRegister{
|
||||||
|
Destination: registers[i+extra],
|
||||||
|
Source: f.CPU.Call[arg.Index],
|
||||||
|
})
|
||||||
|
case *ssa.Bytes:
|
||||||
|
f.Assembler.SetData("data0", arg.Bytes)
|
||||||
|
|
||||||
|
f.Assembler.Append(&asm.MoveRegisterNumber{
|
||||||
|
Destination: registers[i+extra],
|
||||||
|
Number: len(arg.Bytes),
|
||||||
|
})
|
||||||
|
|
||||||
|
extra--
|
||||||
|
|
||||||
|
f.Assembler.Append(&asm.MoveRegisterLabel{
|
||||||
|
Destination: registers[i+extra],
|
||||||
|
Label: "data0",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,10 @@ func (f *Function) CompileInstruction(instr token.List) error {
|
||||||
|
|
||||||
expr := expression.Parse(instr)
|
expr := expression.Parse(instr)
|
||||||
|
|
||||||
|
if expr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if expr.Token.Kind == token.Define {
|
if expr.Token.Kind == token.Define {
|
||||||
name := expr.Children[0].String(f.File.Bytes)
|
name := expr.Children[0].String(f.File.Bytes)
|
||||||
value, err := f.Evaluate(expr.Children[1])
|
value, err := f.Evaluate(expr.Children[1])
|
||||||
|
|
|
@ -2,6 +2,7 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/errors"
|
"git.urbach.dev/cli/q/src/errors"
|
||||||
"git.urbach.dev/cli/q/src/expression"
|
"git.urbach.dev/cli/q/src/expression"
|
||||||
|
@ -17,10 +18,19 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
||||||
name := expr.Token.String(f.File.Bytes)
|
name := expr.Token.String(f.File.Bytes)
|
||||||
value, exists := f.Identifiers[name]
|
value, exists := f.Identifiers[name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
function, exists := f.All.Functions[f.File.Package+"."+name]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, errors.New(&UnknownIdentifier{Name: name}, f.File, expr.Token.Position)
|
return nil, errors.New(&UnknownIdentifier{Name: name}, f.File, expr.Token.Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.Dependencies.Add(function)
|
||||||
|
v := f.AppendFunction(function.UniqueName)
|
||||||
|
v.Source = expr.Token
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
return value, nil
|
return value, nil
|
||||||
|
|
||||||
case token.Number:
|
case token.Number:
|
||||||
|
@ -66,7 +76,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
||||||
|
|
||||||
args := make([]ssa.Value, len(children))
|
args := make([]ssa.Value, len(children))
|
||||||
|
|
||||||
for i, child := range children {
|
for i, child := range slices.Backward(children) {
|
||||||
value, err := f.Evaluate(child)
|
value, err := f.Evaluate(child)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,8 +103,17 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case token.Dot:
|
case token.Dot:
|
||||||
name := fmt.Sprintf("%s.%s", expr.Children[0].String(f.File.Bytes), expr.Children[1].String(f.File.Bytes))
|
left := expr.Children[0]
|
||||||
v := f.AppendFunction(name)
|
right := expr.Children[1]
|
||||||
|
label := fmt.Sprintf("%s.%s", left.String(f.File.Bytes), right.String(f.File.Bytes))
|
||||||
|
function, exists := f.All.Functions[label]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New(&UnknownIdentifier{Name: label}, f.File, left.Token.Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Dependencies.Add(function)
|
||||||
|
v := f.AppendFunction(function.UniqueName)
|
||||||
v.Source = expr.Children[1].Token
|
v.Source = expr.Children[1].Token
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ package core
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/asm"
|
||||||
|
"git.urbach.dev/cli/q/src/cpu"
|
||||||
"git.urbach.dev/cli/q/src/fs"
|
"git.urbach.dev/cli/q/src/fs"
|
||||||
|
"git.urbach.dev/cli/q/src/set"
|
||||||
"git.urbach.dev/cli/q/src/ssa"
|
"git.urbach.dev/cli/q/src/ssa"
|
||||||
"git.urbach.dev/cli/q/src/token"
|
"git.urbach.dev/cli/q/src/token"
|
||||||
)
|
)
|
||||||
|
@ -18,6 +21,10 @@ type Function struct {
|
||||||
Output []*Parameter
|
Output []*Parameter
|
||||||
Body token.List
|
Body token.List
|
||||||
Identifiers map[string]ssa.Value
|
Identifiers map[string]ssa.Value
|
||||||
|
All *Environment
|
||||||
|
Dependencies set.Ordered[*Function]
|
||||||
|
Assembler asm.Assembler
|
||||||
|
CPU *cpu.CPU
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +40,24 @@ func NewFunction(name string, file *fs.File) *Function {
|
||||||
{Instructions: make([]ssa.Value, 0, 8)},
|
{Instructions: make([]ssa.Value, 0, 8)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Assembler: asm.Assembler{
|
||||||
|
Instructions: make([]asm.Instruction, 0, 8),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EachDependency recursively finds all the calls to other functions.
|
||||||
|
// It avoids calling the same function twice with the help of a hashmap.
|
||||||
|
func (f *Function) EachDependency(traversed map[*Function]bool, call func(*Function)) {
|
||||||
|
call(f)
|
||||||
|
traversed[f] = true
|
||||||
|
|
||||||
|
for dep := range f.Dependencies.All() {
|
||||||
|
if traversed[dep] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dep.EachDependency(traversed, call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +66,11 @@ func (f *Function) IsExtern() bool {
|
||||||
return f.Body == nil
|
return f.Body == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLeaf returns true if the function doesn't call other functions.
|
||||||
|
func (f *Function) IsLeaf() bool {
|
||||||
|
return f.Dependencies.Count() == 0
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the unique name.
|
// String returns the unique name.
|
||||||
func (f *Function) String() string {
|
func (f *Function) String() string {
|
||||||
return f.UniqueName
|
return f.UniqueName
|
||||||
|
|
7
src/cpu/CPU.go
Normal file
7
src/cpu/CPU.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package cpu
|
||||||
|
|
||||||
|
// CPU represents the processor.
|
||||||
|
type CPU struct {
|
||||||
|
Call []Register
|
||||||
|
Syscall []Register
|
||||||
|
}
|
4
src/data/Data.go
Normal file
4
src/data/Data.go
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Data saves slices of bytes referenced by labels.
|
||||||
|
type Data map[string][]byte
|
28
src/data/Data_test.go
Normal file
28
src/data/Data_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package data_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/data"
|
||||||
|
"git.urbach.dev/go/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInterning(t *testing.T) {
|
||||||
|
d := data.Data{}
|
||||||
|
d.Insert("label1", []byte("Hello"))
|
||||||
|
d.Insert("label2", []byte("ello"))
|
||||||
|
raw, positions := d.Finalize()
|
||||||
|
assert.DeepEqual(t, raw, []byte("Hello"))
|
||||||
|
assert.Equal(t, positions["label1"], 0)
|
||||||
|
assert.Equal(t, positions["label2"], 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterningReverse(t *testing.T) {
|
||||||
|
d := data.Data{}
|
||||||
|
d.Insert("label1", []byte("ello"))
|
||||||
|
d.Insert("label2", []byte("Hello"))
|
||||||
|
raw, positions := d.Finalize()
|
||||||
|
assert.DeepEqual(t, raw, []byte("Hello"))
|
||||||
|
assert.Equal(t, positions["label1"], 1)
|
||||||
|
assert.Equal(t, positions["label2"], 0)
|
||||||
|
}
|
41
src/data/Finalize.go
Normal file
41
src/data/Finalize.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Finalize returns the final raw data slice and a map of labels with their respective indices.
|
||||||
|
// It will try to reuse existing data whenever possible.
|
||||||
|
func (data Data) Finalize() ([]byte, map[string]int) {
|
||||||
|
var (
|
||||||
|
keys = make([]string, 0, len(data))
|
||||||
|
positions = make(map[string]int, len(data))
|
||||||
|
capacity = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
for key, value := range data {
|
||||||
|
keys = append(keys, key)
|
||||||
|
capacity += len(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(keys, func(i, j int) bool {
|
||||||
|
return len(data[keys[i]]) > len(data[keys[j]])
|
||||||
|
})
|
||||||
|
|
||||||
|
final := make([]byte, 0, capacity)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
raw := data[key]
|
||||||
|
position := bytes.Index(final, raw)
|
||||||
|
|
||||||
|
if position != -1 {
|
||||||
|
positions[key] = position
|
||||||
|
} else {
|
||||||
|
positions[key] = len(final)
|
||||||
|
final = append(final, raw...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return final, positions
|
||||||
|
}
|
6
src/data/Insert.go
Normal file
6
src/data/Insert.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
// Insert registers a slice of bytes for the given label.
|
||||||
|
func (data Data) Insert(label string, raw []byte) {
|
||||||
|
data[label] = raw
|
||||||
|
}
|
21
src/data/bench_test.go
Normal file
21
src/data/bench_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package data_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/data"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkFinalize(b *testing.B) {
|
||||||
|
d := data.Data{}
|
||||||
|
d.Insert("1", []byte("Beautiful is better than ugly."))
|
||||||
|
d.Insert("2", []byte("Explicit is better than implicit."))
|
||||||
|
d.Insert("3", []byte("Simple is better than complex."))
|
||||||
|
d.Insert("4", []byte("Complex is better than complicated."))
|
||||||
|
d.Insert("5", []byte("Flat is better than nested."))
|
||||||
|
d.Insert("6", []byte("Sparse is better than dense."))
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
d.Finalize()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,50 +0,0 @@
|
||||||
package linker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/arm"
|
|
||||||
"git.urbach.dev/cli/q/src/build"
|
|
||||||
"git.urbach.dev/cli/q/src/core"
|
|
||||||
"git.urbach.dev/cli/q/src/elf"
|
|
||||||
"git.urbach.dev/cli/q/src/x86"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WriteExecutable writes an executable file to disk.
|
|
||||||
func WriteExecutable(b *build.Build, result *core.Environment) error {
|
|
||||||
executable := b.Executable()
|
|
||||||
file, err := os.Create(executable)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
code := []byte{}
|
|
||||||
data := []byte{}
|
|
||||||
|
|
||||||
switch b.Arch {
|
|
||||||
case build.ARM:
|
|
||||||
code = arm.MoveRegisterNumber(code, arm.X8, 93)
|
|
||||||
code = arm.MoveRegisterNumber(code, arm.X0, 0)
|
|
||||||
code = binary.LittleEndian.AppendUint32(code, arm.Syscall())
|
|
||||||
|
|
||||||
case build.X86:
|
|
||||||
code = x86.MoveRegisterNumber(code, x86.R0, 60)
|
|
||||||
code = x86.MoveRegisterNumber(code, x86.R7, 0)
|
|
||||||
code = x86.Syscall(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch b.OS {
|
|
||||||
case build.Linux:
|
|
||||||
elf.Write(file, b, code, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = file.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.Chmod(executable, 0755)
|
|
||||||
}
|
|
49
src/linker/WriteFile.go
Normal file
49
src/linker/WriteFile.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package linker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/asm"
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
"git.urbach.dev/cli/q/src/core"
|
||||||
|
"git.urbach.dev/cli/q/src/data"
|
||||||
|
"git.urbach.dev/cli/q/src/elf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteFile writes an executable file to disk.
|
||||||
|
func WriteFile(executable string, b *build.Build, env *core.Environment) error {
|
||||||
|
file, err := os.Create(executable)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
init := env.Functions["core.init"]
|
||||||
|
traversed := make(map[*core.Function]bool, len(env.Functions))
|
||||||
|
|
||||||
|
final := asm.Assembler{
|
||||||
|
Instructions: make([]asm.Instruction, 0, 8),
|
||||||
|
Data: make(data.Data, 32),
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will place the main function immediately after the entry point
|
||||||
|
// and also add everything the main function calls recursively.
|
||||||
|
init.EachDependency(traversed, func(f *core.Function) {
|
||||||
|
final.Merge(&f.Assembler)
|
||||||
|
})
|
||||||
|
|
||||||
|
code, data := final.Compile(b)
|
||||||
|
|
||||||
|
switch b.OS {
|
||||||
|
case build.Linux:
|
||||||
|
elf.Write(file, b, code, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Chmod(executable, 0755)
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"git.urbach.dev/go/assert"
|
"git.urbach.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWriteExecutable(t *testing.T) {
|
func TestWriteFile(t *testing.T) {
|
||||||
tmpDir := filepath.Join(os.TempDir(), "q", "tests")
|
tmpDir := filepath.Join(os.TempDir(), "q", "tests")
|
||||||
err := os.MkdirAll(tmpDir, 0755)
|
err := os.MkdirAll(tmpDir, 0755)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
@ -29,10 +29,10 @@ func TestWriteExecutable(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
b.SetArch(build.ARM)
|
b.SetArch(build.ARM)
|
||||||
err = linker.WriteExecutable(b, env)
|
err = linker.WriteFile(b.Executable(), b, env)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
b.SetArch(build.X86)
|
b.SetArch(build.X86)
|
||||||
err = linker.WriteExecutable(b, env)
|
err = linker.WriteFile(b.Executable(), b, env)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package scanner
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/build"
|
"git.urbach.dev/cli/q/src/build"
|
||||||
"git.urbach.dev/cli/q/src/core"
|
"git.urbach.dev/cli/q/src/core"
|
||||||
"git.urbach.dev/cli/q/src/fs"
|
"git.urbach.dev/cli/q/src/fs"
|
||||||
|
"git.urbach.dev/cli/q/src/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan scans all the files included in the build.
|
// Scan scans all the files included in the build.
|
||||||
|
@ -16,6 +19,7 @@ func Scan(b *build.Build) (*core.Environment, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
s.queueDirectory(filepath.Join(global.Library, "core"), "core")
|
||||||
s.queue(b.Files...)
|
s.queue(b.Files...)
|
||||||
s.group.Wait()
|
s.group.Wait()
|
||||||
close(s.functions)
|
close(s.functions)
|
||||||
|
@ -36,6 +40,7 @@ func Scan(b *build.Build) (*core.Environment, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.All = all
|
||||||
all.Functions[f.UniqueName] = f
|
all.Functions[f.UniqueName] = f
|
||||||
|
|
||||||
case file, ok := <-s.files:
|
case file, ok := <-s.files:
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package scanner
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"git.urbach.dev/cli/q/src/arm"
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
"git.urbach.dev/cli/q/src/errors"
|
"git.urbach.dev/cli/q/src/errors"
|
||||||
"git.urbach.dev/cli/q/src/fs"
|
"git.urbach.dev/cli/q/src/fs"
|
||||||
"git.urbach.dev/cli/q/src/token"
|
"git.urbach.dev/cli/q/src/token"
|
||||||
|
"git.urbach.dev/cli/q/src/x86"
|
||||||
)
|
)
|
||||||
|
|
||||||
// scanFunction scans a function.
|
// scanFunction scans a function.
|
||||||
|
@ -67,6 +70,13 @@ func (s *scanner) scanFunction(file *fs.File, tokens token.List, i int) (int, er
|
||||||
return i, errors.New(ExpectedFunctionDefinition, file, tokens[i].Position)
|
return i, errors.New(ExpectedFunctionDefinition, file, tokens[i].Position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch s.build.Arch {
|
||||||
|
case build.ARM:
|
||||||
|
function.CPU = &arm.CPU
|
||||||
|
case build.X86:
|
||||||
|
function.CPU = &x86.CPU
|
||||||
|
}
|
||||||
|
|
||||||
function.Body = tokens[bodyStart:i]
|
function.Body = tokens[bodyStart:i]
|
||||||
s.functions <- function
|
s.functions <- function
|
||||||
return i, nil
|
return i, nil
|
||||||
|
|
38
src/set/Ordered.go
Normal file
38
src/set/Ordered.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package set
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ordered is an ordered set.
|
||||||
|
type Ordered[T comparable] struct {
|
||||||
|
values []T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a value to the set if it doesn't exist yet.
|
||||||
|
// It returns `false` if it already exists, `true` if it was added.
|
||||||
|
func (set *Ordered[T]) Add(value T) bool {
|
||||||
|
if slices.Contains(set.values, value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
set.values = append(set.values, value)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// All returns an iterator over all the values in the set.
|
||||||
|
func (set *Ordered[T]) All() iter.Seq[T] {
|
||||||
|
return func(yield func(T) bool) {
|
||||||
|
for _, value := range set.values {
|
||||||
|
if !yield(value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of elements in the set.
|
||||||
|
func (set *Ordered[T]) Count() int {
|
||||||
|
return len(set.values)
|
||||||
|
}
|
|
@ -1,9 +1,5 @@
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import (
|
|
||||||
"git.urbach.dev/cli/q/src/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IR is a list of basic blocks.
|
// IR is a list of basic blocks.
|
||||||
type IR struct {
|
type IR struct {
|
||||||
Blocks []*Block
|
Blocks []*Block
|
||||||
|
@ -44,8 +40,8 @@ func (f *IR) AppendInt(x int) *Int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendRegister adds a new register value to the last block.
|
// AppendRegister adds a new register value to the last block.
|
||||||
func (f *IR) AppendRegister(reg cpu.Register) *Register {
|
func (f *IR) AppendRegister(index int) *Parameter {
|
||||||
v := &Register{Register: reg}
|
v := &Parameter{Index: uint8(index)}
|
||||||
f.Append(v)
|
f.Append(v)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
31
src/ssa/Parameter.go
Normal file
31
src/ssa/Parameter.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package ssa
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Parameter struct {
|
||||||
|
Index uint8
|
||||||
|
Liveness
|
||||||
|
HasToken
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Parameter) Dependencies() []Value {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Parameter) Equals(v Value) bool {
|
||||||
|
b, sameType := v.(*Parameter)
|
||||||
|
|
||||||
|
if !sameType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Index == b.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Parameter) IsConst() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Parameter) String() string {
|
||||||
|
return fmt.Sprintf("arg[%d]", v.Index)
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
package ssa
|
|
||||||
|
|
||||||
import "git.urbach.dev/cli/q/src/cpu"
|
|
||||||
|
|
||||||
type Register struct {
|
|
||||||
Register cpu.Register
|
|
||||||
Liveness
|
|
||||||
HasToken
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Register) Dependencies() []Value {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Register) Equals(v Value) bool {
|
|
||||||
b, sameType := v.(*Register)
|
|
||||||
|
|
||||||
if !sameType {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.Register == b.Register
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Register) IsConst() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Register) String() string {
|
|
||||||
return v.Register.String()
|
|
||||||
}
|
|
|
@ -20,3 +20,8 @@ const (
|
||||||
R14
|
R14
|
||||||
R15
|
R15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var CPU = cpu.CPU{
|
||||||
|
Call: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
||||||
|
Syscall: []cpu.Register{R0, R7, R6, R2, R10, R8, R9},
|
||||||
|
}
|
81
tests/examples_test.go
Normal file
81
tests/examples_test.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package tests_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/build"
|
||||||
|
"git.urbach.dev/cli/q/src/compiler"
|
||||||
|
"git.urbach.dev/cli/q/src/linker"
|
||||||
|
"git.urbach.dev/go/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var examples = []struct {
|
||||||
|
Name string
|
||||||
|
Input string
|
||||||
|
Output string
|
||||||
|
ExitCode int
|
||||||
|
}{
|
||||||
|
{"hello", "", "Hello\n", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExamples(t *testing.T) {
|
||||||
|
for _, test := range examples {
|
||||||
|
directory := filepath.Join("..", "examples", test.Name)
|
||||||
|
run(t, directory, test.Input, test.Output, test.ExitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExamples(b *testing.B) {
|
||||||
|
for _, test := range examples {
|
||||||
|
b.Run(test.Name, func(b *testing.B) {
|
||||||
|
example := build.New(filepath.Join("..", "examples", test.Name))
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
_, err := compiler.Compile(example)
|
||||||
|
assert.Nil(b, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run builds and runs the file to check if the output matches the expected output.
|
||||||
|
func run(t *testing.T, path string, input string, expectedOutput string, expectedExitCode int) {
|
||||||
|
b := build.New(path)
|
||||||
|
env, err := compiler.Compile(b)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
tmpDir := filepath.Join(os.TempDir(), "q", "tests")
|
||||||
|
err = os.MkdirAll(tmpDir, 0755)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
executable := b.Executable()
|
||||||
|
executable = filepath.Join(tmpDir, filepath.Base(executable))
|
||||||
|
err = linker.WriteFile(executable, b, env)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
stat, err := os.Stat(executable)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.True(t, stat.Size() > 0)
|
||||||
|
|
||||||
|
cmd := exec.Command(executable)
|
||||||
|
cmd.Stdin = strings.NewReader(input)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
exitCode := 0
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
exitError, ok := err.(*exec.ExitError)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatal(exitError)
|
||||||
|
}
|
||||||
|
|
||||||
|
exitCode = exitError.ExitCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, exitCode, expectedExitCode)
|
||||||
|
assert.DeepEqual(t, string(output), expectedOutput)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue