Implemented function calls
This commit is contained in:
parent
086998a0c3
commit
76db8feee3
@ -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)
|
|
||||||
}
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if funcName == "syscall" {
|
||||||
f.Assembler.Syscall()
|
f.Assembler.Syscall()
|
||||||
|
} else {
|
||||||
|
f.Assembler.Call(funcName)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -9,19 +9,26 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) {
|
|||||||
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
11
src/build/asm/Label.go
Normal 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
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
main() {
|
main() {
|
||||||
()
|
return 1+2*3
|
||||||
1+(2*3)
|
|
||||||
(1+2)
|
|
||||||
f(x)
|
|
||||||
(a+b)(c)
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user