Separated compiler into its own package
This commit is contained in:
75
src/build/compiler/Compile.go
Normal file
75
src/build/compiler/Compile.go
Normal file
@ -0,0 +1,75 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
)
|
||||
|
||||
// Compile waits for the scan to finish and compiles all functions.
|
||||
func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
||||
result := Result{}
|
||||
all := map[string]*core.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 = all
|
||||
all[function.Name] = function
|
||||
}
|
||||
}
|
||||
|
||||
// Start parallel compilation
|
||||
CompileFunctions(all)
|
||||
|
||||
// Report errors if any occurred
|
||||
for _, function := range all {
|
||||
if function.Err != nil {
|
||||
return result, function.Err
|
||||
}
|
||||
|
||||
result.InstructionCount += len(function.Assembler.Instructions)
|
||||
result.DataCount += len(function.Assembler.Data)
|
||||
}
|
||||
|
||||
// Check for existence of `main`
|
||||
main, exists := all["main"]
|
||||
|
||||
if !exists {
|
||||
return result, errors.MissingMainFunction
|
||||
}
|
||||
|
||||
result.Main = main
|
||||
result.Functions = all
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CompileFunctions starts a goroutine for each function compilation and waits for completion.
|
||||
func CompileFunctions(functions map[string]*core.Function) {
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, function := range functions {
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
function.Compile()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
106
src/build/compiler/Result.go
Normal file
106
src/build/compiler/Result.go
Normal file
@ -0,0 +1,106 @@
|
||||
package compiler
|
||||
|
||||
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/core"
|
||||
"git.akyoto.dev/cli/q/src/build/elf"
|
||||
"git.akyoto.dev/cli/q/src/build/os/linux"
|
||||
)
|
||||
|
||||
// Result contains all the compiled functions in a build.
|
||||
type Result struct {
|
||||
Main *core.Function
|
||||
Functions map[string]*core.Function
|
||||
InstructionCount int
|
||||
DataCount int
|
||||
}
|
||||
|
||||
// finalize generates the final machine code.
|
||||
func (r *Result) finalize() ([]byte, []byte) {
|
||||
// This will be the entry point of the executable.
|
||||
// The only job of the entry function is to call `main` and exit cleanly.
|
||||
// The reason we call `main` instead of using `main` itself is to place
|
||||
// a return address on the stack, which allows return statements in `main`.
|
||||
final := asm.Assembler{
|
||||
Instructions: make([]asm.Instruction, 0, r.InstructionCount+4),
|
||||
Data: make(map[string][]byte, r.DataCount),
|
||||
}
|
||||
|
||||
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[*core.Function]bool{}, func(f *core.Function) {
|
||||
final.Merge(f.Assembler)
|
||||
})
|
||||
|
||||
code, data := final.Finalize()
|
||||
return code, data
|
||||
}
|
||||
|
||||
// eachFunction recursively finds all the calls to external functions.
|
||||
// It avoids calling the same function twice with the help of a hashmap.
|
||||
func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Function]bool, call func(*core.Function)) {
|
||||
call(caller)
|
||||
traversed[caller] = true
|
||||
|
||||
for _, x := range caller.Assembler.Instructions {
|
||||
if x.Mnemonic != asm.CALL {
|
||||
continue
|
||||
}
|
||||
|
||||
name := x.Data.(*asm.Label).Name
|
||||
callee, exists := r.Functions[name]
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
if traversed[callee] {
|
||||
continue
|
||||
}
|
||||
|
||||
r.eachFunction(callee, traversed, call)
|
||||
}
|
||||
}
|
||||
|
||||
// PrintInstructions prints out the generated instructions.
|
||||
func (r *Result) PrintInstructions() {
|
||||
r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) {
|
||||
f.PrintInstructions()
|
||||
})
|
||||
}
|
||||
|
||||
// Write writes 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)
|
||||
}
|
Reference in New Issue
Block a user