From b175ee2beed776e565c1320b079ecc39b83c04dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robin=20B=C3=A4rtschi?= Date: Tue, 28 Jan 2025 10:37:18 +0100 Subject: [PATCH] build system mostly working --- build/build.go | 294 +++++++++---------------------------------------- build/task.go | 257 ++++++++++++++++++++++++++++++++++++++++++ main.go | 22 ++-- 3 files changed, 323 insertions(+), 250 deletions(-) create mode 100644 build/task.go diff --git a/build/build.go b/build/build.go index edcd345..28b0f66 100644 --- a/build/build.go +++ b/build/build.go @@ -2,22 +2,22 @@ package build import ( - "errors" "fmt" - "io" + "log" "os" "os/exec" "path/filepath" "strings" "robaertschi.xyz/robaertschi/tt/asm" - "robaertschi.xyz/robaertschi/tt/asm/amd64" - "robaertschi.xyz/robaertschi/tt/lexer" - "robaertschi.xyz/robaertschi/tt/parser" - "robaertschi.xyz/robaertschi/tt/token" - "robaertschi.xyz/robaertschi/tt/ttir" - "robaertschi.xyz/robaertschi/tt/typechecker" - "robaertschi.xyz/robaertschi/tt/utils" +) + +type ToPrintFlags int + +const ( + PrintAst ToPrintFlags = 1 << iota + PrintTAst + PrintIr ) type SourceProgram struct { @@ -29,221 +29,23 @@ type SourceProgram struct { InputAssemblies []string // Additional object files, will also include the generated object files ObjectFiles []string - // The linkded executable + // The linked executable OutputFile string } -type task interface { - Run(id int, doneChan chan taskResult) +func NewSourceProgram(inputFile string, outputFile string) *SourceProgram { + return &SourceProgram{InputFile: inputFile, OutputFile: outputFile} } -type processTask struct { - name string - args []string -} - -func NewProcessTask(id int, name string, args ...string) task { - return &processTask{ - name: name, - args: args, - } -} - -func (pt *processTask) Run(id int, doneChan chan taskResult) { - cmd := exec.Command(pt.name, pt.args...) - cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, pt.name+" output: ") - cmd.Stderr = cmd.Stdout - - err := cmd.Run() - var exitError error - if cmd.ProcessState.ExitCode() != 0 { - exitError = fmt.Errorf("command %q failed with exit code %d", pt.name, cmd.ProcessState.ExitCode()) - } - doneChan <- taskResult{ - Id: id, - Err: errors.Join(err, exitError), - } -} - -type removeFileTask struct { - file string -} - -func NewRemoveFileTask(file string) task { - return &removeFileTask{file: file} -} - -func (rft *removeFileTask) Run(id int, doneChan chan taskResult) { - doneChan <- taskResult{ - Id: id, - Err: os.Remove(rft.file), - } -} - -type funcTask struct { - f func() error -} - -func NewFuncTask(f func() error) task { - return &funcTask{f: f} -} - -func (rft *funcTask) Run(id int, doneChan chan taskResult) { - doneChan <- taskResult{ - Id: id, - Err: rft.f(), - } -} - -type taskResult struct { - Id int - Err error -} - -type node struct { - next *int - previous []int - task task -} - -func runTasks(nodes map[int]*node, rootNodes []int) { - done := make(map[int]bool) - running := []int{} - doneChan := make(chan taskResult) - - for id, node := range nodes { - // Initalize map - done[id] = false - - // because we are already going trough the whole map, we might as well - // check the relations - if node.next != nil { - if _, ok := nodes[*node.next]; !ok { - panic(fmt.Sprintf("task with id %d has a invalid next node", id)) - } - } - - for _, prev := range node.previous { - if _, ok := nodes[prev]; !ok { - panic(fmt.Sprintf("task with id %d has a invalid prev node", id)) - } - } - } - - for _, rootNode := range rootNodes { - node := nodes[rootNode] - go node.task.Run(rootNode, doneChan) - running = append(running, rootNode) - } - - for { - select { - case result := <-doneChan: - done[result.Id] = true - for i, id := range running { - if id == result.Id { - running = make([]int, len(running)-1) - running = append(running[:i], running[i+1:]...) - } - } - - node := nodes[result.Id] - if node.next != nil { - allDone := true - for _, prev := range node.previous { - if !done[prev] { - allDone = false - break - } - } - if allDone { - node := nodes[*node.next] - go node.task.Run(*node.next, doneChan) - running = append(running, *node.next) - } - } - } - } - -} - -func build(input string, output string) error { - file, err := os.Open(input) - if err != nil { - return fmt.Errorf("could not open file %q because: %v", input, err) - } - defer file.Close() - - inputText, err := io.ReadAll(file) - if err != nil { - return fmt.Errorf("Could not read file %q because: %v", input, err) - } - - l, err := lexer.New(string(inputText), input) - if err != nil { - return fmt.Errorf("error while creating lexer: %v", err) - } - - l.WithErrorCallback(func(l token.Loc, s string, a ...any) { - fmt.Printf("%s:%d:%d: %s\n", l.File, l.Line, l.Col, fmt.Sprintf(s, a...)) - }) - - p := parser.New(l) - p.WithErrorCallback(func(t token.Token, s string, a ...any) { - loc := t.Loc - fmt.Printf("%s:%d:%d: %s\n", loc.File, loc.Line, loc.Col, fmt.Sprintf(s, a...)) - }) - - program := p.ParseProgram() - if p.Errors() > 0 { - return fmt.Errorf("parser encountered 1 or more errors") - } - - tprogram, err := typechecker.New().CheckProgram(program) - if err != nil { - return err - } - - ir := ttir.EmitProgram(tprogram) - asm := amd64.CgProgram(ir) - - asmOutput := asm.Emit() - - asmOutputFile, err := os.Create(output) - if err != nil { - return fmt.Errorf("failed to create/truncate asm file %q because: %v\n", output, err) - } - - _, err = asmOutputFile.WriteString(asmOutput) - asmOutputFile.Close() - if err != nil { - return fmt.Errorf("failed to write to file %q because: %v\n", output, err) - } - - return nil -} - -func (sp *SourceProgram) Build(backend asm.Backend) error { - var gasPath string - fasmPath, err := exec.LookPath("fasm") - if err != nil { - fasmPath, err = exec.LookPath("fasm2") - if err != nil { - return fmt.Errorf("could not find fasm or fasm2, please install any those two using your systems package manager or from https://flatassembler.net") - } - } - if backend == asm.Qbe { - gasPath, err = exec.LookPath("as") - if err != nil { - return fmt.Errorf("could not find as assembler, pleas install it") - } - } +func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint ToPrintFlags) error { + l := log.New(os.Stderr, "[build] ", log.Lshortfile) nodes := make(map[int]*node) rootNodes := []int{} id := 0 addRootNode := func(task task) int { + l.Printf("registering root task %d", id) node := &node{task: task} nodes[id] = node rootNodes = append(rootNodes, id) @@ -252,48 +54,54 @@ func (sp *SourceProgram) Build(backend asm.Backend) error { } addNode := func(task task, deps ...int) int { + l.Printf("registering task %d", id) + if len(deps) <= 0 { + panic("node without dep is useless") + } + node := &node{task: task} nodes[id] = node for _, dep := range deps { nodeDep := nodes[dep] - if nodeDep.next != nil { - panic(fmt.Sprintf("dep %d already has an next", dep)) - } - newId := id - nodeDep.next = &newId + nodeDep.next = append(nodeDep.next, id) node.previous = append(node.previous, dep) } - rootNodes = append(rootNodes, id) id += 1 return id - 1 } - var mainFile int - switch backend { - case asm.Fasm: - mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".asm" - sp.InputAssemblies = append(sp.InputAssemblies, mainAsmOutput) - mainFile = addRootNode(NewFuncTask(func() error { - return build(sp.InputFile, mainAsmOutput) - })) - case asm.Qbe: - panic("qbe support not finished") + err := sp.buildFasm(addRootNode, addNode, emitAsmOnly, toPrint) + if err != nil { + return err } - asmFiles := []int{} - for _, file := range sp.InputAssemblies { - output := strings.TrimSuffix(file, filepath.Ext(file)) + ".o" - if filepath.Ext(file) == ".asm" { - asmFiles = append(asmFiles, addNode( - NewProcessTask(id, fasmPath, file, output), mainFile, - )) - } else { - asmFiles = append(asmFiles, addNode(NewProcessTask(id, gasPath, file, "-o", output))) - } - sp.ObjectFiles = append(sp.ObjectFiles, output) - } - - return runTasks(nodes, rootNodes) + return runTasks(nodes, rootNodes, l) +} + +func (sp *SourceProgram) buildFasm(addRootNode func(task) int, addNode func(task, ...int) int, emitAsmOnly bool, toPrint ToPrintFlags) error { + fasmPath, err := exec.LookPath("fasm") + if err != nil { + fasmPath, err = exec.LookPath("fasm2") + if err != nil { + return fmt.Errorf("could not find fasm or fasm2, please install any those two using your systems package manager or from https://flatassembler.net") + } + } + + mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".asm" + + asmFile := addRootNode(NewFuncTask(func() error { + return build(sp.InputFile, mainAsmOutput, toPrint) + })) + + if !emitAsmOnly { + fasmTask := addNode(NewProcessTask(fasmPath, mainAsmOutput, sp.OutputFile), asmFile) + + // Cleanup + + addNode(NewRemoveFileTask(mainAsmOutput), fasmTask) + } + + return nil } diff --git a/build/task.go b/build/task.go new file mode 100644 index 0000000..8c79600 --- /dev/null +++ b/build/task.go @@ -0,0 +1,257 @@ +package build + +import ( + "errors" + "fmt" + "io" + "log" + "os" + "os/exec" + "slices" + + "robaertschi.xyz/robaertschi/tt/asm/amd64" + "robaertschi.xyz/robaertschi/tt/lexer" + "robaertschi.xyz/robaertschi/tt/parser" + "robaertschi.xyz/robaertschi/tt/token" + "robaertschi.xyz/robaertschi/tt/ttir" + "robaertschi.xyz/robaertschi/tt/typechecker" + "robaertschi.xyz/robaertschi/tt/utils" +) + +type task interface { + Run(id int, doneChan chan taskResult) +} + +type processTask struct { + name string + args []string +} + +func NewProcessTask(name string, args ...string) task { + return &processTask{ + name: name, + args: args, + } +} + +func (pt *processTask) Run(id int, doneChan chan taskResult) { + cmd := exec.Command(pt.name, pt.args...) + cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, pt.name+" output: ") + cmd.Stderr = cmd.Stdout + + fmt.Printf("starting %q %v\n", pt.name, pt.args) + err := cmd.Run() + var exitError error + if cmd.ProcessState.ExitCode() != 0 { + exitError = fmt.Errorf("command %q failed with exit code %d", pt.name, cmd.ProcessState.ExitCode()) + } + doneChan <- taskResult{ + Id: id, + Err: errors.Join(err, exitError), + } +} + +type removeFileTask struct { + file string +} + +func NewRemoveFileTask(file string) task { + return &removeFileTask{file: file} +} + +func (rft *removeFileTask) Run(id int, doneChan chan taskResult) { + err := os.Remove(rft.file) + doneChan <- taskResult{ + Id: id, + Err: err, + } +} + +type funcTask struct { + f func() error +} + +func NewFuncTask(f func() error) task { + return &funcTask{f: f} +} + +func (rft *funcTask) Run(id int, doneChan chan taskResult) { + doneChan <- taskResult{ + Id: id, + Err: rft.f(), + } +} + +func build(input string, output string, toPrint ToPrintFlags) error { + file, err := os.Open(input) + if err != nil { + return fmt.Errorf("could not open file %q because: %v", input, err) + } + defer file.Close() + + inputText, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("Could not read file %q because: %v", input, err) + } + + l, err := lexer.New(string(inputText), input) + if err != nil { + return fmt.Errorf("error while creating lexer: %v", err) + } + + l.WithErrorCallback(func(l token.Loc, s string, a ...any) { + fmt.Printf("%s:%d:%d: %s\n", l.File, l.Line, l.Col, fmt.Sprintf(s, a...)) + }) + + p := parser.New(l) + p.WithErrorCallback(func(t token.Token, s string, a ...any) { + loc := t.Loc + fmt.Printf("%s:%d:%d: %s\n", loc.File, loc.Line, loc.Col, fmt.Sprintf(s, a...)) + }) + + program := p.ParseProgram() + if p.Errors() > 0 { + return fmt.Errorf("parser encountered 1 or more errors") + } + if (toPrint & PrintAst) != 0 { + fmt.Printf("AST:\n%s\n%+#v\n", program.String(), program) + } + + tprogram, err := typechecker.New().CheckProgram(program) + if err != nil { + return err + } + if (toPrint & PrintTAst) != 0 { + fmt.Printf("TAST:\n%s\n%+#v\n", tprogram.String(), tprogram) + } + + ir := ttir.EmitProgram(tprogram) + if (toPrint & PrintIr) != 0 { + fmt.Printf("TTIR:\n%s\n%+#v\n", ir.String(), ir) + } + asm := amd64.CgProgram(ir) + + asmOutput := asm.Emit() + + asmOutputFile, err := os.Create(output) + if err != nil { + return fmt.Errorf("failed to create/truncate asm file %q because: %v\n", output, err) + } + + _, err = asmOutputFile.WriteString(asmOutput) + asmOutputFile.Close() + if err != nil { + return fmt.Errorf("failed to write to file %q because: %v\n", output, err) + } + + return nil +} + +type taskResult struct { + Id int + Err error +} + +type node struct { + next []int + previous []int + task task +} + +type executionState int + +const ( + notStarted executionState = iota + executing + finished + failed +) + +func runTasks(nodes map[int]*node, rootNodes []int, l *log.Logger) error { + + done := make(map[int]executionState) + running := []int{} + doneChan := make(chan taskResult) + errs := []error{} + + startTask := func(id int) { + if done[id] != notStarted { + panic(fmt.Sprintf("tried starting task %d twice", id)) + } + // fmt.Printf("executing task %d\n", id) + node := nodes[id] + go node.task.Run(id, doneChan) + running = append(running, id) + done[id] = executing + } + + for id, node := range nodes { + // Initalize map + done[id] = notStarted + + // because we are already going trough the whole map, we might as well + // check the relations + for _, next := range node.next { + if node.next != nil { + if _, ok := nodes[next]; !ok { + panic(fmt.Sprintf("task with id %d has a invalid next node", id)) + } + } + } + + for _, prev := range node.previous { + if _, ok := nodes[prev]; !ok { + panic(fmt.Sprintf("task with id %d has a invalid prev node", id)) + } + } + } + + // fmt.Printf("starting rootNodes %v\n", rootNodes) + for _, rootNode := range rootNodes { + startTask(rootNode) + } + + allFinished := false + + for !allFinished { + select { + case result := <-doneChan: + // fmt.Printf("task %d is done with err: %v\n", result.Id, result.Err) + for i, id := range running { + if id == result.Id { + running = slices.Delete(running, i, i+1) + break + } + } + + if result.Err != nil { + done[result.Id] = failed + errs = append(errs, result.Err) + break + } else { + done[result.Id] = finished + } + + for _, next := range nodes[result.Id].next { + nextNode := nodes[next] + allDone := true + for _, prev := range nextNode.previous { + if done[prev] != finished { + allDone = false + break + } + } + if allDone { + startTask(next) + } + } + default: + if len(running) <= 0 { + allFinished = true + } + } + } + + return errors.Join(errs...) + +} diff --git a/main.go b/main.go index 674f834..78b649f 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,13 @@ package main import ( "flag" "fmt" + "log" "os" "path/filepath" "strings" - "robaertschi.xyz/robaertschi/tt/cmd" + "robaertschi.xyz/robaertschi/tt/asm" + "robaertschi.xyz/robaertschi/tt/build" ) func main() { @@ -19,7 +21,7 @@ func main() { var output string flag.StringVar(&output, "o", "", "Output a executable named `file`") flag.StringVar(&output, "output", "", "Output a executable named `file`") - onlyEmitAsm := flag.Bool("S", false, "Only emit the asembly file and exit") + emitAsmOnly := flag.Bool("S", false, "Only emit the asembly file and exit") printAst := flag.Bool("ast", false, "Print the AST out to stdout") printTAst := flag.Bool("tast", false, "Print the typed AST out to stdout") @@ -36,16 +38,22 @@ func main() { output = strings.TrimSuffix(input, filepath.Ext(input)) } - var toPrint cmd.ToPrintFlags + var toPrint build.ToPrintFlags if *printAst { - toPrint |= cmd.PrintAst + toPrint |= build.PrintAst } if *printTAst { - toPrint |= cmd.PrintTAst + toPrint |= build.PrintTAst } if *printIr { - toPrint |= cmd.PrintIr + toPrint |= build.PrintIr } - cmd.Compile(cmd.Arguments{Output: output, Input: input, OnlyEmitAsm: *onlyEmitAsm, ToPrint: toPrint}) + logger := log.New(os.Stderr, "", log.Lshortfile) + + err := build.NewSourceProgram(input, output).Build(asm.Fasm, *emitAsmOnly, build.ToPrintFlags(toPrint)) + if err != nil { + logger.Fatalln(err) + os.Exit(1) + } }