72 lines
1.9 KiB
Go
72 lines
1.9 KiB
Go
package build
|
|
|
|
import (
|
|
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
|
"git.akyoto.dev/cli/q/src/build/asm"
|
|
"git.akyoto.dev/cli/q/src/build/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)
|
|
}
|
|
}
|
|
|
|
// Write write the final executable to disk.
|
|
func (r *Result) Write(path string) error {
|
|
code, data := r.Finalize()
|
|
return Write(path, code, data)
|
|
}
|