This commit is contained in:
parent
2b703e9af2
commit
70c2da4a4d
40 changed files with 821 additions and 117 deletions
|
@ -39,4 +39,9 @@ const (
|
|||
|
||||
const (
|
||||
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
|
||||
}
|
||||
|
||||
err = linker.WriteExecutable(b, result)
|
||||
err = linker.WriteFile(b.Executable(), b, result)
|
||||
return exit(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,11 @@ func Compile(b *build.Build) (*core.Environment, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(all.Files) == 0 {
|
||||
return nil, NoInputFiles
|
||||
// Check for existence of `main`
|
||||
_, exists := all.Functions["main.main"]
|
||||
|
||||
if !exists {
|
||||
return nil, MissingMainFunction
|
||||
}
|
||||
|
||||
compileFunctions(maps.Values(all.Functions))
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestNoInputFiles(t *testing.T) {
|
|||
b := build.New(".")
|
||||
_, err := compiler.Compile(b)
|
||||
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) {
|
||||
|
|
|
@ -3,5 +3,5 @@ package compiler
|
|||
import "git.urbach.dev/cli/q/src/errors"
|
||||
|
||||
var (
|
||||
NoInputFiles = errors.String("No input files")
|
||||
MissingMainFunction = errors.String("Missing main function")
|
||||
)
|
|
@ -1,21 +1,28 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"git.urbach.dev/cli/q/src/asm"
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
"git.urbach.dev/cli/q/src/ssa"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
registerCount := 0
|
||||
extra := 0
|
||||
|
||||
for _, input := range f.Input {
|
||||
f.Identifiers[input.Name] = f.AppendRegister(cpu.Register(registerCount))
|
||||
registerCount++
|
||||
for i, input := range f.Input {
|
||||
if input.Name == "_" {
|
||||
continue
|
||||
}
|
||||
|
||||
f.Identifiers[input.Name] = f.AppendRegister(i + extra)
|
||||
|
||||
if input.TypeTokens[0].Kind == token.ArrayStart {
|
||||
f.Identifiers[input.Name+".length"] = f.AppendRegister(cpu.Register(registerCount))
|
||||
registerCount++
|
||||
extra++
|
||||
f.Identifiers[input.Name+".length"] = f.AppendRegister(i + extra)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,4 +35,83 @@ func (f *Function) Compile() {
|
|||
}
|
||||
|
||||
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)
|
||||
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.Token.Kind == token.Define {
|
||||
name := expr.Children[0].String(f.File.Bytes)
|
||||
value, err := f.Evaluate(expr.Children[1])
|
||||
|
|
|
@ -2,6 +2,7 @@ package core
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"git.urbach.dev/cli/q/src/errors"
|
||||
"git.urbach.dev/cli/q/src/expression"
|
||||
|
@ -18,7 +19,16 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
|||
value, exists := f.Identifiers[name]
|
||||
|
||||
if !exists {
|
||||
return nil, errors.New(&UnknownIdentifier{Name: name}, f.File, expr.Token.Position)
|
||||
function, exists := f.All.Functions[f.File.Package+"."+name]
|
||||
|
||||
if !exists {
|
||||
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
|
||||
|
@ -66,7 +76,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
|||
|
||||
args := make([]ssa.Value, len(children))
|
||||
|
||||
for i, child := range children {
|
||||
for i, child := range slices.Backward(children) {
|
||||
value, err := f.Evaluate(child)
|
||||
|
||||
if err != nil {
|
||||
|
@ -93,8 +103,17 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
|
|||
}
|
||||
|
||||
case token.Dot:
|
||||
name := fmt.Sprintf("%s.%s", expr.Children[0].String(f.File.Bytes), expr.Children[1].String(f.File.Bytes))
|
||||
v := f.AppendFunction(name)
|
||||
left := expr.Children[0]
|
||||
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
|
||||
return v, nil
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ package core
|
|||
import (
|
||||
"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/set"
|
||||
"git.urbach.dev/cli/q/src/ssa"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
)
|
||||
|
@ -11,14 +14,18 @@ import (
|
|||
// Function is the smallest unit of code.
|
||||
type Function struct {
|
||||
ssa.IR
|
||||
Name string
|
||||
UniqueName string
|
||||
File *fs.File
|
||||
Input []*Parameter
|
||||
Output []*Parameter
|
||||
Body token.List
|
||||
Identifiers map[string]ssa.Value
|
||||
Err error
|
||||
Name string
|
||||
UniqueName string
|
||||
File *fs.File
|
||||
Input []*Parameter
|
||||
Output []*Parameter
|
||||
Body token.List
|
||||
Identifiers map[string]ssa.Value
|
||||
All *Environment
|
||||
Dependencies set.Ordered[*Function]
|
||||
Assembler asm.Assembler
|
||||
CPU *cpu.CPU
|
||||
Err error
|
||||
}
|
||||
|
||||
// NewFunction creates a new function.
|
||||
|
@ -33,6 +40,24 @@ func NewFunction(name string, file *fs.File) *Function {
|
|||
{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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (f *Function) String() string {
|
||||
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"
|
||||
)
|
||||
|
||||
func TestWriteExecutable(t *testing.T) {
|
||||
func TestWriteFile(t *testing.T) {
|
||||
tmpDir := filepath.Join(os.TempDir(), "q", "tests")
|
||||
err := os.MkdirAll(tmpDir, 0755)
|
||||
assert.Nil(t, err)
|
||||
|
@ -29,10 +29,10 @@ func TestWriteExecutable(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
b.SetArch(build.ARM)
|
||||
err = linker.WriteExecutable(b, env)
|
||||
err = linker.WriteFile(b.Executable(), b, env)
|
||||
assert.Nil(t, err)
|
||||
|
||||
b.SetArch(build.X86)
|
||||
err = linker.WriteExecutable(b, env)
|
||||
err = linker.WriteFile(b.Executable(), b, env)
|
||||
assert.Nil(t, err)
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
package scanner
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"git.urbach.dev/cli/q/src/build"
|
||||
"git.urbach.dev/cli/q/src/core"
|
||||
"git.urbach.dev/cli/q/src/fs"
|
||||
"git.urbach.dev/cli/q/src/global"
|
||||
)
|
||||
|
||||
// Scan scans all the files included in the build.
|
||||
|
@ -16,6 +19,7 @@ func Scan(b *build.Build) (*core.Environment, error) {
|
|||
}
|
||||
|
||||
go func() {
|
||||
s.queueDirectory(filepath.Join(global.Library, "core"), "core")
|
||||
s.queue(b.Files...)
|
||||
s.group.Wait()
|
||||
close(s.functions)
|
||||
|
@ -36,6 +40,7 @@ func Scan(b *build.Build) (*core.Environment, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
f.All = all
|
||||
all.Functions[f.UniqueName] = f
|
||||
|
||||
case file, ok := <-s.files:
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package scanner
|
||||
|
||||
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/fs"
|
||||
"git.urbach.dev/cli/q/src/token"
|
||||
"git.urbach.dev/cli/q/src/x86"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
switch s.build.Arch {
|
||||
case build.ARM:
|
||||
function.CPU = &arm.CPU
|
||||
case build.X86:
|
||||
function.CPU = &x86.CPU
|
||||
}
|
||||
|
||||
function.Body = tokens[bodyStart:i]
|
||||
s.functions <- function
|
||||
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
|
||||
|
||||
import (
|
||||
"git.urbach.dev/cli/q/src/cpu"
|
||||
)
|
||||
|
||||
// IR is a list of basic blocks.
|
||||
type IR struct {
|
||||
Blocks []*Block
|
||||
|
@ -44,8 +40,8 @@ func (f *IR) AppendInt(x int) *Int {
|
|||
}
|
||||
|
||||
// AppendRegister adds a new register value to the last block.
|
||||
func (f *IR) AppendRegister(reg cpu.Register) *Register {
|
||||
v := &Register{Register: reg}
|
||||
func (f *IR) AppendRegister(index int) *Parameter {
|
||||
v := &Parameter{Index: uint8(index)}
|
||||
f.Append(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()
|
||||
}
|
|
@ -19,4 +19,9 @@ const (
|
|||
R13
|
||||
R14
|
||||
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},
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue