Implemented function calls

This commit is contained in:
Eduard Urbach 2024-06-18 16:42:56 +02:00
parent 086998a0c3
commit 76db8feee3
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
11 changed files with 114 additions and 62 deletions

View File

@ -1,8 +1,12 @@
main() { main() {
hello()
}
hello() {
write := 1 write := 1
stdout := 1 stdout := 1
exit := 60 address := 4194305
length := 3
syscall(write, stdout, 4194305, 3) syscall(write, stdout, address, length)
syscall(exit, 0)
} }

View File

@ -1,12 +1,18 @@
package build package build
import ( 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/asm"
"git.akyoto.dev/cli/q/src/build/os/linux"
) )
// Finalize generates the final machine code. // Finalize generates the final machine code.
func Finalize(functions map[string]*Function) ([]byte, []byte) { 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 { for _, f := range functions {
a.Merge(&f.Assembler) a.Merge(&f.Assembler)
@ -15,3 +21,16 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) {
code, data := a.Finalize() code, data := a.Finalize()
return code, data 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
}

View File

@ -6,7 +6,6 @@ import (
"git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/asm" "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/expression"
"git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
@ -27,11 +26,7 @@ type Function struct {
// Compile turns a function into machine code. // Compile turns a function into machine code.
func (f *Function) Compile() { func (f *Function) Compile() {
if config.Verbose { f.Assembler.Label(f.Name)
ansi.Bold.Println(f.Name)
ansi.Dim.Println("╭────────────────────────────────────────────────────────────")
}
start := 0 start := 0
groupLevel := 0 groupLevel := 0
@ -76,17 +71,11 @@ func (f *Function) Compile() {
} }
f.Assembler.Return() f.Assembler.Return()
if config.Verbose {
ansi.Dim.Println("╰────────────────────────────────────────────────────────────")
f.PrintAsm()
}
} }
// PrintAsm shows the assembly instructions. // PrintAsm shows the assembly instructions.
func (f *Function) PrintAsm() { func (f *Function) PrintAsm() {
fmt.Println() ansi.Bold.Println(f.Name)
ansi.Bold.Println(f.Name + ".asm")
ansi.Dim.Println("╭────────────────────────────────────────────────────────────") ansi.Dim.Println("╭────────────────────────────────────────────────────────────")
for _, x := range f.Assembler.Instructions { for _, x := range f.Assembler.Instructions {
@ -105,11 +94,6 @@ func (f *Function) PrintAsm() {
// CompileInstruction compiles a single instruction. // CompileInstruction compiles a single instruction.
func (f *Function) CompileInstruction(line token.List) error { func (f *Function) CompileInstruction(line token.List) error {
if config.Verbose {
ansi.Dim.Print("│ ")
fmt.Println(line)
}
if len(line) == 0 { if len(line) == 0 {
return nil return nil
} }
@ -117,6 +101,12 @@ func (f *Function) CompileInstruction(line token.List) error {
if line[0].Kind == token.Keyword { if line[0].Kind == token.Keyword {
switch line[0].Text() { switch line[0].Text() {
case "return": case "return":
if len(line) > 1 {
value := expression.Parse(line[1:])
defer value.Close()
// TODO: Set the return value
}
f.Assembler.Return() f.Assembler.Return()
default: default:
@ -132,11 +122,7 @@ func (f *Function) CompileInstruction(line token.List) error {
defer expr.Close() defer expr.Close()
if config.Verbose { if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier || expr.Token.Kind == token.String {
ansi.Dim.Printf("│ %s\n", expr)
}
if expr.Token.Kind == token.Number || expr.Token.Kind == token.Identifier {
return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) 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 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:] parameters := expr.Children[1:]
for i, parameter := range parameters { 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 return nil
} }

View File

@ -4,7 +4,6 @@ import (
"encoding/binary" "encoding/binary"
"git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/config"
) )
// Assembler contains a list of instructions. // Assembler contains a list of instructions.
@ -23,19 +22,14 @@ func New() *Assembler {
func (a *Assembler) Finalize() ([]byte, []byte) { func (a *Assembler) Finalize() ([]byte, []byte) {
code := make([]byte, 0, len(a.Instructions)*8) code := make([]byte, 0, len(a.Instructions)*8)
data := make([]byte, 0, 16) data := make([]byte, 0, 16)
labels := map[string]Address{}
pointers := []Pointer{} pointers := []Pointer{}
for _, x := range a.Instructions { for _, x := range a.Instructions {
switch x.Mnemonic { switch x.Mnemonic {
case MOVE: case MOVE:
code = x64.MoveRegNum32(code, uint8(x.Data.(*RegisterNumber).Register), uint32(x.Data.(*RegisterNumber).Number)) regNum := x.Data.(*RegisterNumber)
code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number))
if x.Data.(*RegisterNumber).IsPointer {
pointers = append(pointers, Pointer{
Position: Address(len(code) - 4),
Address: Address(x.Data.(*RegisterNumber).Number),
})
}
case RETURN: case RETURN:
code = x64.Return(code) code = x64.Return(code)
@ -43,17 +37,33 @@ func (a *Assembler) Finalize() ([]byte, []byte) {
case SYSCALL: case SYSCALL:
code = x64.Syscall(code) 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: default:
panic("Unknown mnemonic: " + x.Mnemonic.String()) 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 { for _, pointer := range pointers {
slice := code[pointer.Position : pointer.Position+4] slice := code[pointer.Position : pointer.Position+4]
address := dataStart + pointer.Address binary.LittleEndian.PutUint32(slice, pointer.Resolve())
binary.LittleEndian.PutUint32(slice, address)
} }
return code, data return code, data

View File

@ -7,21 +7,28 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) {
a.Instructions = append(a.Instructions, Instruction{ a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOVE, Mnemonic: MOVE,
Data: &RegisterNumber{ Data: &RegisterNumber{
Register: reg, Register: reg,
Number: number, Number: number,
IsPointer: false,
}, },
}) })
} }
// MoveRegisterAddress moves an address into the given register. // Label adds a label at the current position.
func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) { func (a *Assembler) Label(name string) {
a.Instructions = append(a.Instructions, Instruction{ a.Instructions = append(a.Instructions, Instruction{
Mnemonic: MOVE, Mnemonic: LABEL,
Data: &RegisterNumber{ Data: &Label{
Register: reg, Name: name,
Number: uint64(address), },
IsPointer: true, })
}
// 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,
}, },
}) })
} }

11
src/build/asm/Label.go Normal file
View File

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

View File

@ -7,6 +7,8 @@ const (
MOVE MOVE
RETURN RETURN
SYSCALL SYSCALL
LABEL
CALL
) )
// String returns a human readable version. // String returns a human readable version.
@ -20,6 +22,12 @@ func (m Mnemonic) String() string {
case SYSCALL: case SYSCALL:
return "syscall" return "syscall"
case LABEL:
return "label"
case CALL:
return "call"
} }
return "NONE" return "NONE"

View File

@ -5,8 +5,8 @@ type Address = uint32
// Pointer stores a relative memory address that we can later turn into an absolute one. // 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. // 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 { type Pointer struct {
Position uint32 Position Address
Address uint32 Resolve func() Address
} }

View File

@ -8,9 +8,8 @@ import (
// RegisterNumber operates with a register and a number. // RegisterNumber operates with a register and a number.
type RegisterNumber struct { type RegisterNumber struct {
Register cpu.Register Register cpu.Register
Number uint64 Number uint64
IsPointer bool
} }
// String returns a human readable version. // String returns a human readable version.

View File

@ -42,6 +42,12 @@ func Build(args []string) int {
return 1 return 1
} }
if config.Verbose {
for _, function := range result {
function.PrintAsm()
}
}
if !writeExecutable { if !writeExecutable {
return 0 return 0
} }

View File

@ -1,7 +1,3 @@
main() { main() {
() return 1+2*3
1+(2*3)
(1+2)
f(x)
(a+b)(c)
} }