Added build result struct

This commit is contained in:
Eduard Urbach 2024-06-26 20:16:18 +02:00
parent 3268f7a7ee
commit 988a538661
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
7 changed files with 61 additions and 54 deletions

View File

@ -18,7 +18,7 @@ func New(files ...string) *Build {
}
// Run parses the input files and generates an executable file.
func (build *Build) Run() (map[string]*Function, error) {
func (build *Build) Run() (Result, error) {
functions, errors := scan(build.Files)
return compile(functions, errors)
}

View File

@ -1,36 +0,0 @@
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"
)
// Finalize generates the final machine code.
func Finalize(functions map[string]*Function) ([]byte, []byte) {
a := entry()
main := functions["main"]
delete(functions, "main")
a.Merge(&main.Assembler)
for _, f := range functions {
a.Merge(&f.Assembler)
}
code, data := a.Finalize()
return code, data
}
// entry returns 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`.
func entry() *asm.Assembler {
entry := asm.New()
entry.Call("main")
entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[0], linux.Exit)
entry.RegisterNumber(asm.MOVE, x64.SyscallRegisters[1], 0)
entry.Syscall()
return entry
}

42
src/build/Result.go Normal file
View File

@ -0,0 +1,42 @@
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 {
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()
// Place the `main` function immediately after the entry point.
main := r.Functions["main"]
delete(r.Functions, "main")
final.Merge(&main.Assembler)
// Merge all the remaining functions.
for _, f := range r.Functions {
final.Merge(&f.Assembler)
}
code, data := final.Finalize()
return code, data
}

View File

@ -11,13 +11,6 @@ type Assembler struct {
Instructions []Instruction
}
// New creates a new assembler.
func New() *Assembler {
return &Assembler{
Instructions: make([]Instruction, 0, 8),
}
}
// Finalize generates the final machine code.
func (a *Assembler) Finalize() ([]byte, []byte) {
code := make([]byte, 0, len(a.Instructions)*8)

View File

@ -5,8 +5,10 @@ import (
)
// compile waits for the scan to finish and compiles all functions.
func compile(functions <-chan *Function, errors <-chan error) (map[string]*Function, error) {
allFunctions := map[string]*Function{}
func compile(functions <-chan *Function, errors <-chan error) (Result, error) {
result := Result{
Functions: map[string]*Function{},
}
for functions != nil || errors != nil {
select {
@ -16,7 +18,7 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct
continue
}
return nil, err
return result, err
case function, ok := <-functions:
if !ok {
@ -24,19 +26,21 @@ func compile(functions <-chan *Function, errors <-chan error) (map[string]*Funct
continue
}
allFunctions[function.Name] = function
result.Functions[function.Name] = function
}
}
compileFunctions(allFunctions)
compileFunctions(result.Functions)
for _, function := range allFunctions {
for _, function := range result.Functions {
if function.Error != nil {
return nil, function.Error
}
return result, function.Error
}
return allFunctions, nil
result.instructionCount += len(function.Assembler.Instructions)
}
return result, nil
}
// compileFunctions starts a goroutine for each function compilation and waits for completion.

View File

@ -7,6 +7,7 @@ import (
"sync"
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token"
@ -231,6 +232,9 @@ func scanFile(path string, functions chan<- *Function) error {
Syscall: x64.SyscallRegisters,
Return: x64.ReturnValueRegisters,
},
Assembler: asm.Assembler{
Instructions: make([]asm.Instruction, 0, 32),
},
}
nameStart = -1

View File

@ -43,7 +43,7 @@ func Build(args []string) int {
}
if config.Verbose {
for _, function := range result {
for _, function := range result.Functions {
function.PrintAsm()
}
}
@ -53,7 +53,7 @@ func Build(args []string) int {
}
path := b.Executable()
code, data := build.Finalize(result)
code, data := result.Finalize()
err = build.Write(path, code, data)
if err != nil {