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) }