begin qbe support and custom build system

This commit is contained in:
Robin Bärtschi 2025-01-27 18:38:07 +01:00
parent bc554cdedf
commit b53d40d101
7 changed files with 279 additions and 51 deletions

8
asm/backend.go Normal file
View File

@ -0,0 +1,8 @@
package asm
type Backend string
const (
Fasm Backend = "fasm"
Qbe Backend = "qbe"
)

1
asm/qbe/qbe.go Normal file
View File

@ -0,0 +1 @@
package qbe

30
asm/qbe/qbe_stub.asm Normal file
View File

@ -0,0 +1,30 @@
; Reference: https://filippo.io/linux-syscall-table/
; https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
; https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions
format ELF64
public syscall1
public syscall2
public syscall3
; rdi => Syscall number, rsi => argument
syscall1:
mov rax, rdi
mov rdi, rsi
syscall
ret
syscall2:
mov rax, rdi
mov rdi, rsi
mov rsi, rdx
syscall
ret
syscall3:
mov rax, rdi
mov rdi, rsi
mov rsi, rdx
mov rdx, rcx
syscall
ret

143
build/build.go Normal file
View File

@ -0,0 +1,143 @@
// Build allows the user to build a tt file. The goal is to make it easy to support multiple backends with different requirements
package build
import (
"errors"
"fmt"
"os"
"os/exec"
"robaertschi.xyz/robaertschi/tt/asm"
"robaertschi.xyz/robaertschi/tt/utils"
)
type SourceProgram struct {
// The tt source file
InputFile string
// A list of additional assembly files to compile
// This file could be extended by different backends
// .asm is for fasm, .S for gas
InputAssemblies []string
// Additional object files, will also include the generated object files
ObjectFiles []string
// The linkded executable
OutputFile string
}
type task interface {
Run(id int, doneChan chan taskResult)
}
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 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
}
}
}
}
}
}
func (sp *SourceProgram) Build(backend asm.Backend) {
}

View File

@ -1,7 +1,6 @@
package cmd
import (
"bytes"
"fmt"
"io"
"os"
@ -15,6 +14,7 @@ import (
"robaertschi.xyz/robaertschi/tt/token"
"robaertschi.xyz/robaertschi/tt/ttir"
"robaertschi.xyz/robaertschi/tt/typechecker"
"robaertschi.xyz/robaertschi/tt/utils"
)
type ToPrintFlags int
@ -32,55 +32,6 @@ type Arguments struct {
ToPrint ToPrintFlags
}
// Prefix writer writes a prefix before each new line from another io.Writer
type PrefixWriter struct {
output io.Writer
outputPrefix []byte
outputPrefixWritten bool
}
func NewPrefixWriter(output io.Writer, prefix []byte) *PrefixWriter {
return &PrefixWriter{
output: output,
outputPrefix: prefix,
}
}
func NewPrefixWriterString(output io.Writer, prefix string) *PrefixWriter {
return &PrefixWriter{
output: output,
outputPrefix: []byte(prefix),
}
}
func (w *PrefixWriter) Write(p []byte) (n int, err error) {
toWrites := bytes.SplitAfter(p, []byte{'\n'})
for _, toWrite := range toWrites {
if len(toWrite) <= 0 {
continue
}
if !w.outputPrefixWritten {
w.outputPrefixWritten = true
w.output.Write(w.outputPrefix)
}
if bytes.Contains(toWrite, []byte{'\n'}) {
w.outputPrefixWritten = false
}
var written int
written, err = w.output.Write(toWrite)
n += written
if err != nil {
return
}
}
return
}
func Compile(args Arguments) {
output := args.Output
input := args.Input
@ -168,7 +119,7 @@ func Compile(args Arguments) {
if !onlyEmitAsm {
args := []string{asmOutputName, output}
cmd := exec.Command(fasmPath, args...)
cmd.Stdout = NewPrefixWriterString(os.Stdout, "fasm output: ")
cmd.Stdout = utils.NewPrefixWriterString(os.Stdout, "fasm output: ")
cmd.Stderr = cmd.Stdout
err = cmd.Run()

View File

@ -9,3 +9,44 @@ fn main() = {
i
};
```
## Reference
### Basic Datatypes
#### Numbers
There is currently only one number type, `i64`, `i64` is a signed integer of the size of 64 bits.
#### Booleans
The boolean type `bool` can be either true or false, nothing else, it's size is implementation dependend and is only guaranteed to be 1 bit big.
### Expressions
There are many types of expression, tt is expression oriented.
#### Integer Expression
A Integer Expression contains an untyped, non-floating point, integer.
```tt
1234567890
100000
```
The Integer Expression must at minimum support the largest number type.
#### Boolean Expression
Is either the keyword `true` or `false`.
```tt
true
false
```
#### Binary Expression
A Binary Expression is a expression with two expression and an operator between them. A Operator has a precedence, that deteirmines, which way they have to be parsed.
##### Operators
- `+` Adds two numbers with the same type together
- `-` Subtracts the left expression with the right expression, they have the same type
- `*`

View File

@ -1 +1,55 @@
package utils
import (
"bytes"
"io"
)
// Prefix writer writes a prefix before each new line from another io.Writer
type PrefixWriter struct {
output io.Writer
outputPrefix []byte
outputPrefixWritten bool
}
func NewPrefixWriter(output io.Writer, prefix []byte) *PrefixWriter {
return &PrefixWriter{
output: output,
outputPrefix: prefix,
}
}
func NewPrefixWriterString(output io.Writer, prefix string) *PrefixWriter {
return &PrefixWriter{
output: output,
outputPrefix: []byte(prefix),
}
}
func (w *PrefixWriter) Write(p []byte) (n int, err error) {
toWrites := bytes.SplitAfter(p, []byte{'\n'})
for _, toWrite := range toWrites {
if len(toWrite) <= 0 {
continue
}
if !w.outputPrefixWritten {
w.outputPrefixWritten = true
w.output.Write(w.outputPrefix)
}
if bytes.Contains(toWrite, []byte{'\n'}) {
w.outputPrefixWritten = false
}
var written int
written, err = w.output.Write(toWrite)
n += written
if err != nil {
return
}
}
return
}