Refactored code structure
This commit is contained in:
55
src/build/core/Compile.go
Normal file
55
src/build/core/Compile.go
Normal file
@ -0,0 +1,55 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
)
|
||||
|
||||
// Compile waits for the scan to finish and compiles all functions.
|
||||
func Compile(functions <-chan *Function, errs <-chan error) (Result, error) {
|
||||
result := Result{}
|
||||
allFunctions := map[string]*Function{}
|
||||
|
||||
for functions != nil || errs != nil {
|
||||
select {
|
||||
case err, ok := <-errs:
|
||||
if !ok {
|
||||
errs = nil
|
||||
continue
|
||||
}
|
||||
|
||||
return result, err
|
||||
|
||||
case function, ok := <-functions:
|
||||
if !ok {
|
||||
functions = nil
|
||||
continue
|
||||
}
|
||||
|
||||
function.functions = allFunctions
|
||||
allFunctions[function.Name] = function
|
||||
}
|
||||
}
|
||||
|
||||
// Start parallel compilation
|
||||
CompileAllFunctions(allFunctions)
|
||||
|
||||
// Report errors if any occurred
|
||||
for _, function := range allFunctions {
|
||||
if function.err != nil {
|
||||
return result, function.err
|
||||
}
|
||||
|
||||
result.InstructionCount += len(function.assembler.Instructions)
|
||||
}
|
||||
|
||||
// Check for existence of `main`
|
||||
main, exists := allFunctions["main"]
|
||||
|
||||
if !exists {
|
||||
return result, errors.MissingMainFunction
|
||||
}
|
||||
|
||||
result.Main = main
|
||||
result.Functions = allFunctions
|
||||
return result, nil
|
||||
}
|
19
src/build/core/CompileAllFunctions.go
Normal file
19
src/build/core/CompileAllFunctions.go
Normal file
@ -0,0 +1,19 @@
|
||||
package core
|
||||
|
||||
import "sync"
|
||||
|
||||
// CompileAllFunctions starts a goroutine for each function compilation and waits for completion.
|
||||
func CompileAllFunctions(functions map[string]*Function) {
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, function := range functions {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
function.Compile()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
19
src/build/core/CompileAssign.go
Normal file
19
src/build/core/CompileAssign.go
Normal file
@ -0,0 +1,19 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
)
|
||||
|
||||
// CompileAssign compiles an assign statement.
|
||||
func (f *Function) CompileAssign(node *ast.Assign) error {
|
||||
name := node.Name.Text()
|
||||
variable, exists := f.variables[name]
|
||||
|
||||
if !exists {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Name.Position)
|
||||
}
|
||||
|
||||
defer f.useVariable(variable)
|
||||
return f.Execute(node.Operator, variable.Register, node.Value)
|
||||
}
|
39
src/build/core/CompileCall.go
Normal file
39
src/build/core/CompileCall.go
Normal file
@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"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(expr *expression.Expression) error {
|
||||
_, err := f.EvaluateCall(expr)
|
||||
return err
|
||||
}
|
||||
|
||||
// CompileSyscall executes a syscall.
|
||||
func (f *Function) CompileSyscall(expr *expression.Expression) error {
|
||||
parameters := expr.Children[1:]
|
||||
registers := f.cpu.Syscall[:len(parameters)]
|
||||
err := f.ExpressionsToRegisters(parameters, registers)
|
||||
f.assembler.Syscall()
|
||||
f.sideEffects++
|
||||
return err
|
||||
}
|
||||
|
||||
// ExpressionsToRegisters moves multiple expressions into the specified registers.
|
||||
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
|
||||
for i := len(registers) - 1; i >= 0; i-- {
|
||||
f.SaveRegister(registers[i])
|
||||
err := f.EvaluateTo(expressions[i], registers[i])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
145
src/build/core/CompileDefinition.go
Normal file
145
src/build/core/CompileDefinition.go
Normal file
@ -0,0 +1,145 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// CompileDefinition compiles a variable definition.
|
||||
func (f *Function) CompileDefinition(node *ast.Define) error {
|
||||
name := node.Name.Text()
|
||||
|
||||
if f.identifierExists(name) {
|
||||
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position)
|
||||
}
|
||||
|
||||
uses := CountIdentifier(f.Body, name) - 1
|
||||
|
||||
if uses == 0 {
|
||||
return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position)
|
||||
}
|
||||
|
||||
value := node.Value
|
||||
|
||||
err := value.EachLeaf(func(leaf *expression.Expression) error {
|
||||
if leaf.Token.Kind == token.Identifier && !f.identifierExists(leaf.Token.Text()) {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: leaf.Token.Text()}, f.File, leaf.Token.Position)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if uses == 1 {
|
||||
f.definitions[name] = &Definition{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.storeVariableInRegister(name, value, uses)
|
||||
}
|
||||
|
||||
func (f *Function) AddVariable(variable *Variable) {
|
||||
if config.Comments {
|
||||
f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive))
|
||||
}
|
||||
|
||||
f.variables[variable.Name] = variable
|
||||
f.cpu.Use(variable.Register)
|
||||
}
|
||||
|
||||
func (f *Function) addTemporary(root *expression.Expression) *Variable {
|
||||
f.count.tmps++
|
||||
name := fmt.Sprintf("t%d", f.count.tmps)
|
||||
register := f.cpu.MustUseFree(f.cpu.General)
|
||||
|
||||
tmp := &Variable{
|
||||
Name: name,
|
||||
Value: root,
|
||||
Alive: 1,
|
||||
Register: register,
|
||||
}
|
||||
|
||||
f.variables[name] = tmp
|
||||
|
||||
if config.Comments {
|
||||
f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register))
|
||||
}
|
||||
|
||||
return tmp
|
||||
}
|
||||
|
||||
func (f *Function) useVariable(variable *Variable) {
|
||||
variable.Alive--
|
||||
|
||||
if variable.Alive < 0 {
|
||||
panic("incorrect number of variable use calls")
|
||||
}
|
||||
|
||||
if variable.Alive == 0 {
|
||||
if config.Comments {
|
||||
f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register))
|
||||
}
|
||||
|
||||
f.cpu.Free(variable.Register)
|
||||
}
|
||||
}
|
||||
|
||||
// identifierExists returns true if the identifier has been defined.
|
||||
func (f *Function) identifierExists(name string) bool {
|
||||
_, exists := f.variables[name]
|
||||
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
|
||||
_, exists = f.definitions[name]
|
||||
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
|
||||
_, exists = f.functions[name]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error {
|
||||
reg, exists := f.cpu.FindFree(f.cpu.General)
|
||||
|
||||
if !exists {
|
||||
panic("no free registers")
|
||||
}
|
||||
|
||||
err := f.EvaluateTo(value, reg)
|
||||
|
||||
f.AddVariable(&Variable{
|
||||
Name: name,
|
||||
Register: reg,
|
||||
Alive: uses,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func CountIdentifier(tokens token.List, name string) int {
|
||||
count := 0
|
||||
|
||||
for _, t := range tokens {
|
||||
if t.Kind == token.Identifier && t.Text() == name {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
16
src/build/core/CompileLoop.go
Normal file
16
src/build/core/CompileLoop.go
Normal file
@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileLoop compiles a loop instruction.
|
||||
func (f *Function) CompileLoop(loop *ast.Loop) error {
|
||||
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
|
||||
f.assembler.Label(label)
|
||||
defer f.assembler.Jump(label)
|
||||
f.count.loop++
|
||||
return f.CompileAST(loop.Body)
|
||||
}
|
16
src/build/core/CompileReturn.go
Normal file
16
src/build/core/CompileReturn.go
Normal file
@ -0,0 +1,16 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
)
|
||||
|
||||
// CompileReturn compiles a return instruction.
|
||||
func (f *Function) CompileReturn(node *ast.Return) error {
|
||||
defer f.assembler.Return()
|
||||
|
||||
if node.Value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return f.EvaluateTo(node.Value, f.cpu.Return[0])
|
||||
}
|
123
src/build/core/Evaluate.go
Normal file
123
src/build/core/Evaluate.go
Normal file
@ -0,0 +1,123 @@
|
||||
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/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Evaluate evaluates the result of an expression and saves it into a temporary register.
|
||||
func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) {
|
||||
if root.IsLeaf() {
|
||||
return f.EvaluateLeaf(root)
|
||||
}
|
||||
|
||||
if ast.IsFunctionCall(root) {
|
||||
return f.EvaluateCall(root)
|
||||
}
|
||||
|
||||
left := root.Children[0]
|
||||
right := root.Children[1]
|
||||
|
||||
tmpLeft, err := f.Evaluate(left)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmpLeftExpr := expression.NewLeaf(token.Token{
|
||||
Kind: token.Identifier,
|
||||
Position: left.Token.Position,
|
||||
Bytes: []byte(tmpLeft.Name),
|
||||
})
|
||||
|
||||
tmpLeftExpr.Parent = root
|
||||
root.Children[0].Parent = nil
|
||||
root.Children[0] = tmpLeftExpr
|
||||
|
||||
tmpRight, err := f.Evaluate(right)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmpRightExpr := expression.NewLeaf(token.Token{
|
||||
Kind: token.Identifier,
|
||||
Position: left.Token.Position,
|
||||
Bytes: []byte(tmpRight.Name),
|
||||
})
|
||||
|
||||
tmpRightExpr.Parent = root
|
||||
root.Children[1].Parent = nil
|
||||
root.Children[1] = tmpRightExpr
|
||||
|
||||
tmp := f.addTemporary(root)
|
||||
|
||||
f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register)
|
||||
f.useVariable(tmpLeft)
|
||||
|
||||
err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register)
|
||||
f.useVariable(tmpRight)
|
||||
|
||||
return tmp, err
|
||||
}
|
||||
|
||||
func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) {
|
||||
funcName := root.Children[0].Token.Text()
|
||||
parameters := root.Children[1:]
|
||||
registers := f.cpu.Call[:len(parameters)]
|
||||
isSyscall := funcName == "syscall"
|
||||
|
||||
if isSyscall {
|
||||
registers = f.cpu.Syscall[:len(parameters)]
|
||||
}
|
||||
|
||||
err := f.ExpressionsToRegisters(parameters, registers)
|
||||
|
||||
for _, register := range f.cpu.General {
|
||||
if !f.cpu.IsFree(register) {
|
||||
f.assembler.Register(asm.PUSH, register)
|
||||
}
|
||||
}
|
||||
|
||||
if isSyscall {
|
||||
f.assembler.Syscall()
|
||||
} else {
|
||||
f.assembler.Call(funcName)
|
||||
}
|
||||
|
||||
for i := len(f.cpu.General) - 1; i >= 0; i-- {
|
||||
register := f.cpu.General[i]
|
||||
|
||||
if !f.cpu.IsFree(register) {
|
||||
f.assembler.Register(asm.POP, register)
|
||||
}
|
||||
}
|
||||
|
||||
tmp := f.addTemporary(root)
|
||||
f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0])
|
||||
return tmp, err
|
||||
}
|
||||
|
||||
func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) {
|
||||
tmp := f.addTemporary(root)
|
||||
err := f.TokenToRegister(root.Token, tmp.Register)
|
||||
return tmp, err
|
||||
}
|
||||
|
||||
func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error {
|
||||
tmp, err := f.Evaluate(root)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if register != tmp.Register {
|
||||
f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register)
|
||||
}
|
||||
|
||||
f.useVariable(tmp)
|
||||
return nil
|
||||
}
|
69
src/build/core/Execute.go
Normal file
69
src/build/core/Execute.go
Normal file
@ -0,0 +1,69 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/ast"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Execute executes an operation on a register with a value operand.
|
||||
func (f *Function) Execute(operation token.Token, register cpu.Register, value *expression.Expression) error {
|
||||
if value.IsLeaf() {
|
||||
return f.ExecuteLeaf(operation, register, value.Token)
|
||||
}
|
||||
|
||||
var temporary cpu.Register
|
||||
|
||||
if ast.IsFunctionCall(value) {
|
||||
temporary = f.cpu.Return[0]
|
||||
} else {
|
||||
found := false
|
||||
temporary, found = f.cpu.FindFree(f.cpu.General)
|
||||
|
||||
if !found {
|
||||
panic("no free registers")
|
||||
}
|
||||
}
|
||||
|
||||
f.cpu.Use(temporary)
|
||||
defer f.cpu.Free(temporary)
|
||||
err := f.EvaluateTo(value, temporary)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.opRegisterRegister(operation, register, temporary)
|
||||
}
|
||||
|
||||
// ExecuteLeaf performs an operation on a register with the given leaf operand.
|
||||
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
|
||||
switch operand.Kind {
|
||||
case token.Identifier:
|
||||
name := operand.Text()
|
||||
variable, exists := f.variables[name]
|
||||
|
||||
if !exists {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
|
||||
}
|
||||
|
||||
defer f.useVariable(variable)
|
||||
return f.opRegisterRegister(operation, register, variable.Register)
|
||||
|
||||
case token.Number:
|
||||
value := operand.Text()
|
||||
number, err := strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.opRegisterNumber(operation, register, number)
|
||||
}
|
||||
|
||||
return errors.New(errors.NotImplemented, f.File, operation.Position)
|
||||
}
|
115
src/build/core/Function.go
Normal file
115
src/build/core/Function.go
Normal file
@ -0,0 +1,115 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/fs"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Function represents the smallest unit of code.
|
||||
type Function struct {
|
||||
Name string
|
||||
File *fs.File
|
||||
Body token.List
|
||||
state
|
||||
}
|
||||
|
||||
// NewFunction creates a new function.
|
||||
func NewFunction(name string, file *fs.File, body token.List) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
File: file,
|
||||
Body: body,
|
||||
state: state{
|
||||
assembler: asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, 32),
|
||||
},
|
||||
cpu: cpu.CPU{
|
||||
Call: x64.CallRegisters,
|
||||
General: x64.GeneralRegisters,
|
||||
Syscall: x64.SyscallRegisters,
|
||||
Return: x64.ReturnValueRegisters,
|
||||
},
|
||||
definitions: map[string]*Definition{},
|
||||
variables: map[string]*Variable{},
|
||||
finished: make(chan struct{}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
defer close(f.finished)
|
||||
f.assembler.Label(f.Name)
|
||||
f.err = f.CompileTokens(f.Body)
|
||||
f.assembler.Return()
|
||||
}
|
||||
|
||||
// CompileTokens compiles a token list.
|
||||
func (f *Function) CompileTokens(tokens token.List) error {
|
||||
tree, err := ast.Parse(tokens)
|
||||
|
||||
if err != nil {
|
||||
err.(*errors.Error).File = f.File
|
||||
return err
|
||||
}
|
||||
|
||||
return f.CompileAST(tree)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CompileASTNode compiles a node in the AST.
|
||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Assign:
|
||||
return f.CompileAssign(node)
|
||||
|
||||
case *ast.Call:
|
||||
return f.CompileCall(node.Expression)
|
||||
|
||||
case *ast.Define:
|
||||
return f.CompileDefinition(node)
|
||||
|
||||
case *ast.Return:
|
||||
return f.CompileReturn(node)
|
||||
|
||||
case *ast.Loop:
|
||||
return f.CompileLoop(node)
|
||||
|
||||
default:
|
||||
panic("Unknown AST type")
|
||||
}
|
||||
}
|
||||
|
||||
// Logf formats a message for verbose output.
|
||||
func (f *Function) Logf(format string, data ...any) {
|
||||
fmt.Printf("[%s @ %d] %s\n", f, len(f.assembler.Instructions), fmt.Sprintf(format, data...))
|
||||
}
|
||||
|
||||
// String returns the function name.
|
||||
func (f *Function) String() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// Wait will block until the compilation finishes.
|
||||
func (f *Function) Wait() {
|
||||
<-f.finished
|
||||
}
|
103
src/build/core/Result.go
Normal file
103
src/build/core/Result.go
Normal file
@ -0,0 +1,103 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"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/elf"
|
||||
"git.akyoto.dev/cli/q/src/build/os/linux"
|
||||
)
|
||||
|
||||
// Result contains all the compiled functions in a build.
|
||||
type Result struct {
|
||||
Main *Function
|
||||
Functions map[string]*Function
|
||||
InstructionCount 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+4),
|
||||
}
|
||||
|
||||
final.Call("main")
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit)
|
||||
final.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0)
|
||||
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[*Function]bool{}, func(f *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 *Function, traversed map[*Function]bool, call func(*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[*Function]bool{}, func(f *Function) {
|
||||
f.PrintInstructions()
|
||||
})
|
||||
}
|
||||
|
||||
// Write writes an executable file to disk.
|
||||
func (r *Result) Write(path string) error {
|
||||
code, data := r.finalize()
|
||||
return write(path, code, data)
|
||||
}
|
||||
|
||||
// write writes an executable file to disk.
|
||||
func write(path string, code []byte, data []byte) error {
|
||||
file, err := os.Create(path)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := bufio.NewWriter(file)
|
||||
executable := elf.New(code, data)
|
||||
executable.Write(buffer)
|
||||
buffer.Flush()
|
||||
err = file.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(path, 0755)
|
||||
}
|
39
src/build/core/SaveRegister.go
Normal file
39
src/build/core/SaveRegister.go
Normal file
@ -0,0 +1,39 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
)
|
||||
|
||||
// SaveRegister attempts to move a variable occupying this register to another register.
|
||||
func (f *Function) SaveRegister(register cpu.Register) {
|
||||
if f.cpu.IsFree(register) {
|
||||
return
|
||||
}
|
||||
|
||||
var variable *Variable
|
||||
|
||||
for _, v := range f.variables {
|
||||
if v.Register == register {
|
||||
variable = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if variable == nil || variable.Alive == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
newRegister := f.cpu.MustUseFree(f.cpu.General)
|
||||
|
||||
if config.Comments {
|
||||
f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister))
|
||||
}
|
||||
|
||||
f.assembler.RegisterRegister(asm.MOVE, newRegister, register)
|
||||
f.cpu.Free(register)
|
||||
variable.Register = newRegister
|
||||
}
|
54
src/build/core/TokenToRegister.go
Normal file
54
src/build/core/TokenToRegister.go
Normal file
@ -0,0 +1,54 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// TokenToRegister moves a token into a register.
|
||||
// It only works with identifiers, numbers and strings.
|
||||
func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
|
||||
switch t.Kind {
|
||||
case token.Identifier:
|
||||
name := t.Text()
|
||||
constant, exists := f.definitions[name]
|
||||
|
||||
if exists {
|
||||
return f.EvaluateTo(constant.Value, register)
|
||||
}
|
||||
|
||||
variable, exists := f.variables[name]
|
||||
|
||||
if !exists {
|
||||
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
|
||||
}
|
||||
|
||||
if register != variable.Register {
|
||||
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
|
||||
}
|
||||
|
||||
f.useVariable(variable)
|
||||
return nil
|
||||
|
||||
case token.Number:
|
||||
value := t.Text()
|
||||
n, err := strconv.Atoi(value)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.assembler.RegisterNumber(asm.MOVE, register, n)
|
||||
return nil
|
||||
|
||||
case token.String:
|
||||
return errors.New(errors.NotImplemented, f.File, t.Position)
|
||||
|
||||
default:
|
||||
return errors.New(errors.InvalidExpression, f.File, t.Position)
|
||||
}
|
||||
}
|
20
src/build/core/Variable.go
Normal file
20
src/build/core/Variable.go
Normal file
@ -0,0 +1,20 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
)
|
||||
|
||||
// Variable represents a named register.
|
||||
type Variable struct {
|
||||
Value *expression.Expression
|
||||
Name string
|
||||
Register cpu.Register
|
||||
Alive int
|
||||
}
|
||||
|
||||
// Definitions are single use expressions that don't reside in a register yet.
|
||||
type Definition struct {
|
||||
Value *expression.Expression
|
||||
Name string
|
||||
}
|
60
src/build/core/opRegister.go
Normal file
60
src/build/core/opRegister.go
Normal file
@ -0,0 +1,60 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// opRegisterNumber performs an operation on a register and a number.
|
||||
func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error {
|
||||
switch operation.Text() {
|
||||
case "+", "+=":
|
||||
f.assembler.RegisterNumber(asm.ADD, register, number)
|
||||
|
||||
case "-", "-=":
|
||||
f.assembler.RegisterNumber(asm.SUB, register, number)
|
||||
|
||||
case "*", "*=":
|
||||
f.assembler.RegisterNumber(asm.MUL, register, number)
|
||||
|
||||
case "/", "/=":
|
||||
f.assembler.RegisterNumber(asm.DIV, register, number)
|
||||
|
||||
case "=":
|
||||
f.assembler.RegisterNumber(asm.MOVE, register, number)
|
||||
|
||||
default:
|
||||
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// opRegisterRegister performs an operation on two registers.
|
||||
func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error {
|
||||
switch operation.Text() {
|
||||
case "+", "+=":
|
||||
f.assembler.RegisterRegister(asm.ADD, destination, source)
|
||||
|
||||
case "-", "-=":
|
||||
f.assembler.RegisterRegister(asm.SUB, destination, source)
|
||||
|
||||
case "*", "*=":
|
||||
f.assembler.RegisterRegister(asm.MUL, destination, source)
|
||||
|
||||
case "/", "/=":
|
||||
f.assembler.RegisterRegister(asm.DIV, destination, source)
|
||||
|
||||
case "=":
|
||||
if destination != source {
|
||||
f.assembler.RegisterRegister(asm.MOVE, destination, source)
|
||||
}
|
||||
|
||||
default:
|
||||
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
58
src/build/core/state.go
Normal file
58
src/build/core/state.go
Normal file
@ -0,0 +1,58 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
"git.akyoto.dev/cli/q/src/build/cpu"
|
||||
"git.akyoto.dev/go/color/ansi"
|
||||
)
|
||||
|
||||
// state is the data structure we embed in each function to preserve compilation state.
|
||||
type state struct {
|
||||
err error
|
||||
definitions map[string]*Definition
|
||||
variables map[string]*Variable
|
||||
functions map[string]*Function
|
||||
finished chan struct{}
|
||||
assembler asm.Assembler
|
||||
cpu cpu.CPU
|
||||
count counter
|
||||
sideEffects int
|
||||
}
|
||||
|
||||
// counter stores how often a certain statement appeared so we can generate a unique label from it.
|
||||
type counter struct {
|
||||
loop int
|
||||
tmps int
|
||||
}
|
||||
|
||||
// PrintInstructions shows the assembly instructions.
|
||||
func (s *state) PrintInstructions() {
|
||||
ansi.Dim.Println("╭────────────────────────────────────────────────╮")
|
||||
|
||||
for _, x := range s.assembler.Instructions {
|
||||
ansi.Dim.Print("│ ")
|
||||
|
||||
switch x.Mnemonic {
|
||||
case asm.LABEL:
|
||||
ansi.Yellow.Printf("%-46s", x.Data.String()+":")
|
||||
|
||||
case asm.COMMENT:
|
||||
ansi.Dim.Printf("%-46s", x.Data.String())
|
||||
|
||||
default:
|
||||
ansi.Green.Printf("%-8s", x.Mnemonic.String())
|
||||
|
||||
if x.Data != nil {
|
||||
fmt.Printf("%-38s", x.Data.String())
|
||||
} else {
|
||||
fmt.Printf("%-38s", "")
|
||||
}
|
||||
}
|
||||
|
||||
ansi.Dim.Print(" │\n")
|
||||
}
|
||||
|
||||
ansi.Dim.Println("╰────────────────────────────────────────────────╯")
|
||||
}
|
Reference in New Issue
Block a user