diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 8754a01..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,8 +1,12 @@ main() { + hello() +} + +hello() { write := 1 stdout := 1 - exit := 60 + address := 4194305 + length := 3 - syscall(write, stdout, 4194305, 3) - syscall(exit, 0) + syscall(write, stdout, address, length) } \ No newline at end of file diff --git a/src/build/Finalize.go b/src/build/Finalize.go index 413ce71..333b849 100644 --- a/src/build/Finalize.go +++ b/src/build/Finalize.go @@ -1,12 +1,18 @@ 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 := asm.New() + a := entry() + + main := functions["main"] + delete(functions, "main") + a.Merge(&main.Assembler) for _, f := range functions { a.Merge(&f.Assembler) @@ -15,3 +21,16 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) { 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.MoveRegisterNumber(x64.SyscallArgs[0], linux.Exit) + entry.MoveRegisterNumber(x64.SyscallArgs[1], 0) + entry.Syscall() + return entry +} diff --git a/src/build/Function.go b/src/build/Function.go index 5da9cc6..f303968 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -6,7 +6,6 @@ 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/config" "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/token" @@ -27,11 +26,7 @@ type Function struct { // Compile turns a function into machine code. func (f *Function) Compile() { - if config.Verbose { - ansi.Bold.Println(f.Name) - ansi.Dim.Println("╭────────────────────────────────────────────────────────────") - } - + f.Assembler.Label(f.Name) start := 0 groupLevel := 0 @@ -76,17 +71,11 @@ func (f *Function) Compile() { } f.Assembler.Return() - - if config.Verbose { - ansi.Dim.Println("╰────────────────────────────────────────────────────────────") - f.PrintAsm() - } } // PrintAsm shows the assembly instructions. func (f *Function) PrintAsm() { - fmt.Println() - ansi.Bold.Println(f.Name + ".asm") + ansi.Bold.Println(f.Name) ansi.Dim.Println("╭────────────────────────────────────────────────────────────") for _, x := range f.Assembler.Instructions { @@ -105,11 +94,6 @@ func (f *Function) PrintAsm() { // CompileInstruction compiles a single instruction. func (f *Function) CompileInstruction(line token.List) error { - if config.Verbose { - ansi.Dim.Print("│ ") - fmt.Println(line) - } - if len(line) == 0 { return nil } @@ -117,6 +101,12 @@ func (f *Function) CompileInstruction(line token.List) error { if line[0].Kind == token.Keyword { switch line[0].Text() { case "return": + if len(line) > 1 { + value := expression.Parse(line[1:]) + defer value.Close() + // TODO: Set the return value + } + f.Assembler.Return() default: @@ -132,11 +122,7 @@ func (f *Function) CompileInstruction(line token.List) error { defer expr.Close() - if config.Verbose { - ansi.Dim.Printf("│ %s\n", expr) - } - - if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier { + if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } @@ -159,7 +145,8 @@ func (f *Function) CompileInstruction(line token.List) error { return nil } - if expr.Token.Text() == "λ" && expr.Children[0].Token.Text() == "syscall" { + if expr.Token.Text() == "λ" { + funcName := expr.Children[0].Token.Text() parameters := expr.Children[1:] for i, parameter := range parameters { @@ -189,7 +176,12 @@ func (f *Function) CompileInstruction(line token.List) error { } } - f.Assembler.Syscall() + if funcName == "syscall" { + f.Assembler.Syscall() + } else { + f.Assembler.Call(funcName) + } + return nil } diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index d0186e7..5af72a6 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -4,7 +4,6 @@ import ( "encoding/binary" "git.akyoto.dev/cli/q/src/build/arch/x64" - "git.akyoto.dev/cli/q/src/build/config" ) // Assembler contains a list of instructions. @@ -23,19 +22,14 @@ func New() *Assembler { func (a *Assembler) Finalize() ([]byte, []byte) { code := make([]byte, 0, len(a.Instructions)*8) data := make([]byte, 0, 16) + labels := map[string]Address{} pointers := []Pointer{} for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) - - if x.Data.(*RegisterNumber).IsPointer { - pointers = append(pointers, Pointer{ - Position: Address(len(code) - 4), - Address: Address(x.Data.(*RegisterNumber).Number), - }) - } + regNum := x.Data.(*RegisterNumber) + code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) case RETURN: code = x64.Return(code) @@ -43,17 +37,33 @@ func (a *Assembler) Finalize() ([]byte, []byte) { case SYSCALL: code = x64.Syscall(code) + case CALL: + code = x64.Call(code, 0x00_00_00_00) + label := x.Data.(*Label) + nextInstructionAddress := len(code) + + pointers = append(pointers, Pointer{ + Position: Address(len(code) - 4), + Resolve: func() Address { + destination := labels[label.Name] + distance := int32(destination) - int32(nextInstructionAddress) + return Address(distance) + }, + }) + + case LABEL: + labels[x.Data.(*Label).Name] = Address(len(code)) + default: panic("Unknown mnemonic: " + x.Mnemonic.String()) } } - dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) + // dataStart := config.BaseAddress + config.CodeOffset + Address(len(code)) for _, pointer := range pointers { slice := code[pointer.Position : pointer.Position+4] - address := dataStart + pointer.Address - binary.LittleEndian.PutUint32(slice, address) + binary.LittleEndian.PutUint32(slice, pointer.Resolve()) } return code, data diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index de5f5d8..aa74d0e 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -7,21 +7,28 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { a.Instructions = append(a.Instructions, Instruction{ Mnemonic: MOVE, Data: &RegisterNumber{ - Register: reg, - Number: number, - IsPointer: false, + Register: reg, + Number: number, }, }) } -// MoveRegisterAddress moves an address into the given register. -func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { +// Label adds a label at the current position. +func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: MOVE, - Data: &RegisterNumber{ - Register: reg, - Number: uint64(address), - IsPointer: true, + Mnemonic: LABEL, + Data: &Label{ + Name: name, + }, + }) +} + +// Call calls a function whose position is identified by a label. +func (a *Assembler) Call(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: CALL, + Data: &Label{ + Name: name, }, }) } diff --git a/src/build/asm/Label.go b/src/build/asm/Label.go new file mode 100644 index 0000000..1636fb8 --- /dev/null +++ b/src/build/asm/Label.go @@ -0,0 +1,11 @@ +package asm + +// Label represents a jump label. +type Label struct { + Name string +} + +// String returns a human readable version. +func (data *Label) String() string { + return data.Name +} diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 7fd1a06..ad071c6 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,6 +7,8 @@ const ( MOVE RETURN SYSCALL + LABEL + CALL ) // String returns a human readable version. @@ -20,6 +22,12 @@ func (m Mnemonic) String() string { case SYSCALL: return "syscall" + + case LABEL: + return "label" + + case CALL: + return "call" } return "NONE" diff --git a/src/build/asm/Pointer.go b/src/build/asm/Pointer.go index 7063ec5..6499ce9 100644 --- a/src/build/asm/Pointer.go +++ b/src/build/asm/Pointer.go @@ -5,8 +5,8 @@ type Address = uint32 // Pointer stores a relative memory address that we can later turn into an absolute one. // Position: The machine code offset where the address was inserted. -// Address: The offset inside the section. +// Resolve: The function that will return the final address. type Pointer struct { - Position uint32 - Address uint32 + Position Address + Resolve func() Address } diff --git a/src/build/asm/RegisterNumber.go b/src/build/asm/RegisterNumber.go index bfd7321..6484e08 100644 --- a/src/build/asm/RegisterNumber.go +++ b/src/build/asm/RegisterNumber.go @@ -8,9 +8,8 @@ import ( // RegisterNumber operates with a register and a number. type RegisterNumber struct { - Register cpu.Register - Number uint64 - IsPointer bool + Register cpu.Register + Number uint64 } // String returns a human readable version. diff --git a/src/cli/Build.go b/src/cli/Build.go index 697ec7e..6120250 100644 --- a/src/cli/Build.go +++ b/src/cli/Build.go @@ -42,6 +42,12 @@ func Build(args []string) int { return 1 } + if config.Verbose { + for _, function := range result { + function.PrintAsm() + } + } + if !writeExecutable { return 0 } diff --git a/tests/benchmarks/expressions.q b/tests/benchmarks/expressions.q index b563384..194f47d 100644 --- a/tests/benchmarks/expressions.q +++ b/tests/benchmarks/expressions.q @@ -1,7 +1,3 @@ main() { - () - 1+(2*3) - (1+2) - f(x) - (a+b)(c) + return 1+2*3 } \ No newline at end of file