Reorganized file structure
This commit is contained in:
parent
c7354b8613
commit
6fe30f31da
@ -1,3 +1,3 @@
|
||||
main() {
|
||||
print("Hello")
|
||||
syscall(60, 0)
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
package register
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/register"
|
||||
|
||||
const (
|
||||
SyscallNumber = register.R8
|
||||
SyscallReturn = register.R0
|
||||
)
|
||||
|
||||
var SyscallArgs = []register.ID{
|
||||
register.R0,
|
||||
register.R1,
|
||||
register.R2,
|
||||
register.R3,
|
||||
register.R4,
|
||||
register.R5,
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/register"
|
||||
|
||||
const (
|
||||
SyscallNumber = register.R0 // rax
|
||||
SyscallReturn = register.R0 // rax
|
||||
)
|
||||
|
||||
var SyscallArgs = []register.ID{
|
||||
register.R7, // rdi
|
||||
register.R6, // rsi
|
||||
register.R2, // rdx
|
||||
register.R10,
|
||||
register.R8,
|
||||
register.R9,
|
||||
}
|
||||
|
||||
// Syscall is the primary way to communicate with the OS kernel.
|
||||
func Syscall(code []byte) []byte {
|
||||
return append(code, 0x0f, 0x05)
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
package asm
|
||||
|
||||
// Address represents a memory address.
|
||||
type Address = uint32
|
@ -1,93 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/config"
|
||||
"git.akyoto.dev/cli/q/src/log"
|
||||
"git.akyoto.dev/cli/q/src/register"
|
||||
)
|
||||
|
||||
// Assembler contains a list of instructions.
|
||||
type Assembler struct {
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
// New creates a new assembler.
|
||||
func New() *Assembler {
|
||||
return &Assembler{
|
||||
Instructions: make([]Instruction, 0, 8),
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize generates the final machine code.
|
||||
func (a *Assembler) Finalize() ([]byte, []byte) {
|
||||
code := make([]byte, 0, len(a.Instructions)*8)
|
||||
data := make(Data, 0, 16)
|
||||
pointers := []Pointer{}
|
||||
|
||||
for _, x := range a.Instructions {
|
||||
switch x.Mnemonic {
|
||||
case MOV:
|
||||
code = x64.MoveRegNum32(code, uint8(x.Destination), uint32(x.Number))
|
||||
|
||||
case MOVDATA:
|
||||
code = x64.MoveRegNum32(code, uint8(x.Destination), 0)
|
||||
|
||||
pointers = append(pointers, Pointer{
|
||||
Position: Address(len(code) - 4),
|
||||
Address: data.Add(x.Data),
|
||||
})
|
||||
|
||||
case SYSCALL:
|
||||
code = x64.Syscall(code)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Verbose {
|
||||
for _, x := range a.Instructions {
|
||||
log.Info.Println(x.String())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return code, data
|
||||
}
|
||||
|
||||
// Merge combines the contents of this assembler with another one.
|
||||
func (a *Assembler) Merge(b *Assembler) {
|
||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||
}
|
||||
|
||||
// MoveRegisterData moves a data section address into the given register.
|
||||
func (a *Assembler) MoveRegisterData(reg register.ID, data []byte) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: MOVDATA,
|
||||
Destination: reg,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// MoveRegisterNumber moves a number into the given register.
|
||||
func (a *Assembler) MoveRegisterNumber(reg register.ID, number uint64) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: MOV,
|
||||
Destination: reg,
|
||||
Number: number,
|
||||
})
|
||||
}
|
||||
|
||||
// Syscall executes a kernel function.
|
||||
func (a *Assembler) Syscall() {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: SYSCALL,
|
||||
})
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package asm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/os/linux"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestHello(t *testing.T) {
|
||||
a := asm.New()
|
||||
|
||||
hello := []byte("Hello\n")
|
||||
a.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
|
||||
a.MoveRegisterNumber(x64.SyscallArgs[0], 1)
|
||||
a.MoveRegisterData(x64.SyscallArgs[1], hello)
|
||||
a.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(hello)))
|
||||
a.Syscall()
|
||||
|
||||
a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit)
|
||||
a.MoveRegisterNumber(x64.SyscallArgs[0], 0)
|
||||
a.Syscall()
|
||||
|
||||
code, data := a.Finalize()
|
||||
|
||||
assert.DeepEqual(t, code, []byte{
|
||||
0xb8, 0x01, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x01, 0x00, 0x00, 0x00,
|
||||
0xbe, 0xa2, 0x00, 0x40, 0x00,
|
||||
0xba, 0x06, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x05,
|
||||
|
||||
0xb8, 0x3c, 0x00, 0x00, 0x00,
|
||||
0xbf, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0f, 0x05,
|
||||
})
|
||||
|
||||
assert.DeepEqual(t, data, hello)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package asm
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Data represents the static read-only data.
|
||||
type Data []byte
|
||||
|
||||
// Add adds the given bytes to the data block if this sequence of bytes doesn't exist yet.
|
||||
// It returns the address relative to the start of the data section.
|
||||
func (data *Data) Add(block []byte) Address {
|
||||
position := bytes.Index(*data, block)
|
||||
|
||||
if position != -1 {
|
||||
return Address(position)
|
||||
}
|
||||
|
||||
address := Address(len(*data))
|
||||
*data = append(*data, block...)
|
||||
return address
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/register"
|
||||
)
|
||||
|
||||
// Instruction represents a single instruction which can be converted to machine code.
|
||||
type Instruction struct {
|
||||
Mnemonic Mnemonic
|
||||
Source register.ID
|
||||
Destination register.ID
|
||||
Number uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// String returns the assembler representation of the instruction.
|
||||
func (x *Instruction) String() string {
|
||||
switch x.Mnemonic {
|
||||
case MOV:
|
||||
return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number)
|
||||
case MOVDATA:
|
||||
return fmt.Sprintf("%s %s, %v", x.Mnemonic, x.Destination, x.Data)
|
||||
case SYSCALL:
|
||||
return x.Mnemonic.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
@ -1,12 +1,7 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/compiler"
|
||||
"git.akyoto.dev/cli/q/src/elf"
|
||||
)
|
||||
|
||||
// Build describes a compiler build.
|
||||
@ -25,7 +20,7 @@ func New(directory string) *Build {
|
||||
|
||||
// Run parses the input files and generates an executable file.
|
||||
func (build *Build) Run() error {
|
||||
functions, err := compiler.Compile(build.Directory)
|
||||
functions, err := Compile(build.Directory)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -35,33 +30,12 @@ func (build *Build) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
code, data := compiler.Finalize(functions)
|
||||
return writeToDisk(build.Executable(), code, data)
|
||||
path := build.Executable()
|
||||
code, data := Finalize(functions)
|
||||
return Write(path, code, data)
|
||||
}
|
||||
|
||||
// Executable returns the path to the executable.
|
||||
func (build *Build) Executable() string {
|
||||
return filepath.Join(build.Directory, filepath.Base(build.Directory))
|
||||
}
|
||||
|
||||
// writeToDisk writes the executable file to disk.
|
||||
func writeToDisk(filePath string, code []byte, data []byte) error {
|
||||
file, err := os.Create(filePath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := bufio.NewWriter(file)
|
||||
executable := elf.New(code, data)
|
||||
executable.Write(buffer)
|
||||
buffer.Flush()
|
||||
|
||||
err = file.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(filePath, 0755)
|
||||
}
|
||||
|
24
src/build/Build_test.go
Normal file
24
src/build/Build_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package build_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
b := build.New("../../examples/hello")
|
||||
assert.Nil(t, b.Run())
|
||||
}
|
||||
|
||||
func TestSkipExecutable(t *testing.T) {
|
||||
b := build.New("../../examples/hello")
|
||||
b.WriteExecutable = false
|
||||
assert.Nil(t, b.Run())
|
||||
}
|
||||
|
||||
func TestNonExisting(t *testing.T) {
|
||||
b := build.New("does-not-exist")
|
||||
assert.NotNil(t, b.Run())
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package compiler
|
||||
package build
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Compile compiles all the functions.
|
||||
func Compile(directory string) (map[string]*Function, error) {
|
@ -1,9 +1,7 @@
|
||||
package compiler
|
||||
package build
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/os/linux"
|
||||
"git.akyoto.dev/cli/q/src/build/asm"
|
||||
)
|
||||
|
||||
// Finalize generates the final machine code.
|
||||
@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) {
|
||||
a.Merge(&f.Assembler)
|
||||
}
|
||||
|
||||
a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit)
|
||||
a.MoveRegisterNumber(x64.SyscallArgs[0], 0)
|
||||
a.Syscall()
|
||||
|
||||
code, data := a.Finalize()
|
||||
return code, data
|
||||
}
|
96
src/build/Function.go
Normal file
96
src/build/Function.go
Normal file
@ -0,0 +1,96 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"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/token"
|
||||
"git.akyoto.dev/go/color/ansi"
|
||||
)
|
||||
|
||||
// Function represents a function.
|
||||
type Function struct {
|
||||
Name string
|
||||
Head token.List
|
||||
Body token.List
|
||||
Assembler asm.Assembler
|
||||
}
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
if config.Verbose {
|
||||
ansi.Underline.Println(f.Name)
|
||||
}
|
||||
|
||||
for _, line := range f.Lines() {
|
||||
if config.Verbose {
|
||||
fmt.Println("[line]", line)
|
||||
}
|
||||
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0].Kind == token.Identifier && line[0].Text() == "syscall" {
|
||||
paramTokens := line[2 : len(line)-1]
|
||||
start := 0
|
||||
i := 0
|
||||
var parameters []token.List
|
||||
|
||||
for i < len(paramTokens) {
|
||||
if paramTokens[i].Kind == token.Separator {
|
||||
parameters = append(parameters, paramTokens[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if i != start {
|
||||
parameters = append(parameters, paramTokens[start:i])
|
||||
}
|
||||
|
||||
for i, list := range parameters {
|
||||
if list[0].Kind == token.Number {
|
||||
numAsText := list[0].Text()
|
||||
n, _ := strconv.Atoi(numAsText)
|
||||
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[i], uint64(n))
|
||||
}
|
||||
}
|
||||
|
||||
f.Assembler.Syscall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lines returns the lines in the function body.
|
||||
func (f *Function) Lines() []token.List {
|
||||
var (
|
||||
lines []token.List
|
||||
start = 0
|
||||
i = 0
|
||||
)
|
||||
|
||||
for i < len(f.Body) {
|
||||
if f.Body[i].Kind == token.NewLine {
|
||||
lines = append(lines, f.Body[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if i != start {
|
||||
lines = append(lines, f.Body[start:i])
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// String returns the function name.
|
||||
func (f *Function) String() string {
|
||||
return f.Name
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package compiler
|
||||
package build
|
||||
|
||||
import (
|
||||
"os"
|
||||
@ -6,8 +6,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/directory"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/build/directory"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Scan scans the directory.
|
29
src/build/Write.go
Normal file
29
src/build/Write.go
Normal file
@ -0,0 +1,29 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/elf"
|
||||
)
|
||||
|
||||
// Write writes the executable file to disk.
|
||||
func Write(filePath string, code []byte, data []byte) error {
|
||||
file, err := os.Create(filePath)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer := bufio.NewWriter(file)
|
||||
executable := elf.New(code, data)
|
||||
executable.Write(buffer)
|
||||
buffer.Flush()
|
||||
err = file.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Chmod(filePath, 0755)
|
||||
}
|
9
src/build/arch/arm64/Syscall.go
Normal file
9
src/build/arch/arm64/Syscall.go
Normal file
@ -0,0 +1,9 @@
|
||||
package register
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
const (
|
||||
SyscallReturn = 0
|
||||
)
|
||||
|
||||
var SyscallArgs = []cpu.Register{8, 0, 1, 2, 3, 4, 5}
|
14
src/build/arch/x64/Syscall.go
Normal file
14
src/build/arch/x64/Syscall.go
Normal file
@ -0,0 +1,14 @@
|
||||
package x64
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
const (
|
||||
SyscallReturn = 0 // rax
|
||||
)
|
||||
|
||||
var SyscallArgs = []cpu.Register{0, 7, 6, 2, 10, 8, 9}
|
||||
|
||||
// Syscall is the primary way to communicate with the OS kernel.
|
||||
func Syscall(code []byte) []byte {
|
||||
return append(code, 0x0f, 0x05)
|
||||
}
|
@ -3,7 +3,7 @@ package x64_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
66
src/build/asm/Assembler.go
Normal file
66
src/build/asm/Assembler.go
Normal file
@ -0,0 +1,66 @@
|
||||
package asm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
)
|
||||
|
||||
// Assembler contains a list of instructions.
|
||||
type Assembler struct {
|
||||
Instructions []Instruction
|
||||
}
|
||||
|
||||
// New creates a new assembler.
|
||||
func New() *Assembler {
|
||||
return &Assembler{
|
||||
Instructions: make([]Instruction, 0, 8),
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize generates the final machine code.
|
||||
func (a *Assembler) Finalize() ([]byte, []byte) {
|
||||
code := make([]byte, 0, len(a.Instructions)*8)
|
||||
data := make([]byte, 0, 16)
|
||||
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),
|
||||
})
|
||||
}
|
||||
|
||||
case SYSCALL:
|
||||
code = x64.Syscall(code)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Verbose {
|
||||
for _, x := range a.Instructions {
|
||||
fmt.Println("[asm]", x.String())
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return code, data
|
||||
}
|
||||
|
||||
// Merge combines the contents of this assembler with another one.
|
||||
func (a *Assembler) Merge(b *Assembler) {
|
||||
a.Instructions = append(a.Instructions, b.Instructions...)
|
||||
}
|
19
src/build/asm/Instruction.go
Normal file
19
src/build/asm/Instruction.go
Normal file
@ -0,0 +1,19 @@
|
||||
package asm
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Instruction represents a single instruction which can be converted to machine code.
|
||||
type Instruction struct {
|
||||
Mnemonic Mnemonic
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// String returns a human readable version.
|
||||
func (x *Instruction) String() string {
|
||||
switch data := x.Data.(type) {
|
||||
case RegisterNumber:
|
||||
return fmt.Sprintf("%s %s, %x", x.Mnemonic, data.Register, data.Number)
|
||||
default:
|
||||
return x.Mnemonic.String()
|
||||
}
|
||||
}
|
32
src/build/asm/Instructions.go
Normal file
32
src/build/asm/Instructions.go
Normal file
@ -0,0 +1,32 @@
|
||||
package asm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// MoveRegisterNumber moves a number into the given register.
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MoveRegisterAddress moves an address into the given register.
|
||||
func (a *Assembler) MoveRegisterAddress(reg cpu.Register, address Address) {
|
||||
a.Instructions = append(a.Instructions, Instruction{
|
||||
Mnemonic: MOVE,
|
||||
Data: RegisterNumber{
|
||||
Register: reg,
|
||||
Number: uint64(address),
|
||||
IsPointer: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Syscall executes a kernel function.
|
||||
func (a *Assembler) Syscall() {
|
||||
a.Instructions = append(a.Instructions, Instruction{Mnemonic: SYSCALL})
|
||||
}
|
@ -4,18 +4,15 @@ type Mnemonic uint8
|
||||
|
||||
const (
|
||||
NONE Mnemonic = iota
|
||||
MOV
|
||||
MOVDATA
|
||||
MOVE
|
||||
SYSCALL
|
||||
)
|
||||
|
||||
// String returns a human readable version.
|
||||
func (m Mnemonic) String() string {
|
||||
switch m {
|
||||
case MOV:
|
||||
return "mov"
|
||||
|
||||
case MOVDATA:
|
||||
return "mov"
|
||||
case MOVE:
|
||||
return "move"
|
||||
|
||||
case SYSCALL:
|
||||
return "syscall"
|
@ -1,5 +1,8 @@
|
||||
package asm
|
||||
|
||||
// Address represents a memory address.
|
||||
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.
|
10
src/build/asm/RegisterNumber.go
Normal file
10
src/build/asm/RegisterNumber.go
Normal file
@ -0,0 +1,10 @@
|
||||
package asm
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/cpu"
|
||||
|
||||
// RegisterNumber operates with a register and a number.
|
||||
type RegisterNumber struct {
|
||||
Register cpu.Register
|
||||
Number uint64
|
||||
IsPointer bool
|
||||
}
|
11
src/build/cpu/Register.go
Normal file
11
src/build/cpu/Register.go
Normal file
@ -0,0 +1,11 @@
|
||||
package cpu
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Register represents the number of the register.
|
||||
type Register uint8
|
||||
|
||||
// String returns the human readable name of the register.
|
||||
func (r Register) String() string {
|
||||
return fmt.Sprintf("r%d", r)
|
||||
}
|
@ -3,7 +3,7 @@ package directory_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/directory"
|
||||
"git.akyoto.dev/cli/q/src/build/directory"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/config"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
)
|
||||
|
||||
// ELF represents an ELF file.
|
@ -4,7 +4,7 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/elf"
|
||||
"git.akyoto.dev/cli/q/src/build/elf"
|
||||
)
|
||||
|
||||
func TestELF(t *testing.T) {
|
27
src/build/output/Compiler.go
Normal file
27
src/build/output/Compiler.go
Normal file
@ -0,0 +1,27 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Compiler implements the arch.Output interface.
|
||||
type Compiler struct{}
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (c Compiler) Compile(f *build.Function) {
|
||||
for i, t := range f.Body {
|
||||
if t.Kind == token.Identifier && t.Text() == "print" {
|
||||
// message := f.Body[i+2].Bytes
|
||||
// f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
|
||||
// f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1)
|
||||
// f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message)
|
||||
// f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message)))
|
||||
// f.Assembler.Syscall()
|
||||
message := f.Body[i+2].Bytes
|
||||
fmt.Println(message)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package token_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
@ -96,6 +96,22 @@ func TestNewline(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNumber(t *testing.T) {
|
||||
tokens := token.Tokenize([]byte(`123 -456`))
|
||||
assert.DeepEqual(t, tokens, token.List{
|
||||
{
|
||||
Kind: token.Number,
|
||||
Bytes: []byte("123"),
|
||||
Position: 0,
|
||||
},
|
||||
{
|
||||
Kind: token.Number,
|
||||
Bytes: []byte("-456"),
|
||||
Position: 4,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestSeparator(t *testing.T) {
|
||||
tokens := token.Tokenize([]byte("a,b,c"))
|
||||
assert.DeepEqual(t, tokens, token.List{
|
@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List {
|
||||
}
|
||||
|
||||
tokens = append(tokens, token)
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
// Numbers
|
||||
if isNumberStart(buffer[i]) {
|
||||
position := i
|
||||
i++
|
||||
|
||||
for i < len(buffer) && isNumber(buffer[i]) {
|
||||
i++
|
||||
}
|
||||
|
||||
tokens = append(tokens, Token{
|
||||
Number,
|
||||
position,
|
||||
buffer[position:i],
|
||||
})
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,10 +124,22 @@ func Tokenize(buffer []byte) List {
|
||||
return tokens
|
||||
}
|
||||
|
||||
func isIdentifierStart(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
|
||||
func isIdentifier(c byte) bool {
|
||||
return isLetter(c) || isNumber(c) || c == '_'
|
||||
}
|
||||
|
||||
func isIdentifier(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9')
|
||||
func isIdentifierStart(c byte) bool {
|
||||
return isLetter(c) || c == '_'
|
||||
}
|
||||
|
||||
func isLetter(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
func isNumber(c byte) bool {
|
||||
return (c >= '0' && c <= '9')
|
||||
}
|
||||
|
||||
func isNumberStart(c byte) bool {
|
||||
return isNumber(c) || c == '-'
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build"
|
||||
"git.akyoto.dev/cli/q/src/config"
|
||||
"git.akyoto.dev/cli/q/src/log"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
)
|
||||
|
||||
// Build builds an executable.
|
||||
@ -22,7 +22,7 @@ func Build(args []string) int {
|
||||
|
||||
default:
|
||||
if strings.HasPrefix(args[i], "-") {
|
||||
log.Error.Printf("Unknown parameter: %s\n", args[i])
|
||||
fmt.Printf("Unknown parameter: %s\n", args[i])
|
||||
return 2
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func Build(args []string) int {
|
||||
err := b.Run()
|
||||
|
||||
if err != nil {
|
||||
log.Error.Println(err)
|
||||
fmt.Println(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/log"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// Help shows the command line argument usage.
|
||||
func Help(args []string) int {
|
||||
log.Error.Println("Usage: q [command] [options]")
|
||||
log.Error.Println("")
|
||||
log.Error.Println(" build [directory]")
|
||||
log.Error.Println(" system")
|
||||
fmt.Println("Usage: q [command] [options]")
|
||||
fmt.Println("")
|
||||
fmt.Println(" build [directory]")
|
||||
fmt.Println(" system")
|
||||
return 2
|
||||
}
|
||||
|
@ -2,21 +2,13 @@ package cli_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/cli"
|
||||
"git.akyoto.dev/cli/q/src/log"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.Info.SetOutput(io.Discard)
|
||||
log.Error.SetOutput(io.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestCLI(t *testing.T) {
|
||||
type cliTest struct {
|
||||
arguments []string
|
||||
|
@ -1,36 +1,35 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/log"
|
||||
)
|
||||
|
||||
// System shows system information.
|
||||
func System(args []string) int {
|
||||
line := "%-19s%s\n"
|
||||
|
||||
log.Info.Printf(line, "Platform:", runtime.GOOS)
|
||||
log.Info.Printf(line, "Architecture:", runtime.GOARCH)
|
||||
log.Info.Printf(line, "Go:", runtime.Version())
|
||||
fmt.Printf(line, "Platform:", runtime.GOOS)
|
||||
fmt.Printf(line, "Architecture:", runtime.GOARCH)
|
||||
fmt.Printf(line, "Go:", runtime.Version())
|
||||
|
||||
// Directory
|
||||
directory, err := os.Getwd()
|
||||
|
||||
if err == nil {
|
||||
log.Info.Printf(line, "Directory:", directory)
|
||||
fmt.Printf(line, "Directory:", directory)
|
||||
} else {
|
||||
log.Info.Printf(line, "Directory:", err.Error())
|
||||
fmt.Printf(line, "Directory:", err.Error())
|
||||
}
|
||||
|
||||
// Compiler
|
||||
executable, err := os.Executable()
|
||||
|
||||
if err == nil {
|
||||
log.Info.Printf(line, "Compiler:", executable)
|
||||
fmt.Printf(line, "Compiler:", executable)
|
||||
} else {
|
||||
log.Info.Printf(line, "Compiler:", err.Error())
|
||||
fmt.Printf(line, "Compiler:", err.Error())
|
||||
}
|
||||
|
||||
return 0
|
||||
|
@ -1,35 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
||||
"git.akyoto.dev/cli/q/src/asm"
|
||||
"git.akyoto.dev/cli/q/src/os/linux"
|
||||
"git.akyoto.dev/cli/q/src/token"
|
||||
)
|
||||
|
||||
// Function represents a function.
|
||||
type Function struct {
|
||||
Name string
|
||||
Head token.List
|
||||
Body token.List
|
||||
Assembler asm.Assembler
|
||||
}
|
||||
|
||||
// Compile turns a function into machine code.
|
||||
func (f *Function) Compile() {
|
||||
for i, t := range f.Body {
|
||||
if t.Kind == token.Identifier && t.Text() == "print" {
|
||||
message := f.Body[i+2].Bytes
|
||||
f.Assembler.MoveRegisterNumber(x64.SyscallNumber, linux.Write)
|
||||
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[0], 1)
|
||||
f.Assembler.MoveRegisterData(x64.SyscallArgs[1], message)
|
||||
f.Assembler.MoveRegisterNumber(x64.SyscallArgs[2], uint64(len(message)))
|
||||
f.Assembler.Syscall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the function name.
|
||||
func (f *Function) String() string {
|
||||
return f.Name
|
||||
}
|
106
src/cpu/CPU.go
106
src/cpu/CPU.go
@ -1,106 +0,0 @@
|
||||
package cpu
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/register"
|
||||
|
||||
// CPU manages the allocation state of registers.
|
||||
type CPU struct {
|
||||
All List
|
||||
General List
|
||||
Call List
|
||||
Syscall List
|
||||
}
|
||||
|
||||
// New creates a new CPU state.
|
||||
func New() *CPU {
|
||||
// Rather than doing lots of mini allocations
|
||||
// we'll allocate memory for all registers at once.
|
||||
registers := [16]Register{
|
||||
{ID: register.R0},
|
||||
{ID: register.R1},
|
||||
{ID: register.R2},
|
||||
{ID: register.R3},
|
||||
{ID: register.R4},
|
||||
{ID: register.R5},
|
||||
{ID: register.R6},
|
||||
{ID: register.R7},
|
||||
{ID: register.R8},
|
||||
{ID: register.R9},
|
||||
{ID: register.R10},
|
||||
{ID: register.R11},
|
||||
{ID: register.R12},
|
||||
{ID: register.R13},
|
||||
{ID: register.R14},
|
||||
{ID: register.R15},
|
||||
}
|
||||
|
||||
rax := ®isters[0]
|
||||
rcx := ®isters[1]
|
||||
rdx := ®isters[2]
|
||||
rbx := ®isters[3]
|
||||
rsp := ®isters[4]
|
||||
rbp := ®isters[5]
|
||||
rsi := ®isters[6]
|
||||
rdi := ®isters[7]
|
||||
r8 := ®isters[8]
|
||||
r9 := ®isters[9]
|
||||
r10 := ®isters[10]
|
||||
r11 := ®isters[11]
|
||||
r12 := ®isters[12]
|
||||
r13 := ®isters[13]
|
||||
r14 := ®isters[14]
|
||||
r15 := ®isters[15]
|
||||
|
||||
// Register configuration
|
||||
return &CPU{
|
||||
All: List{
|
||||
rax,
|
||||
rcx,
|
||||
rdx,
|
||||
rbx,
|
||||
rsp,
|
||||
rbp,
|
||||
rsi,
|
||||
rdi,
|
||||
r8,
|
||||
r9,
|
||||
r10,
|
||||
r11,
|
||||
r12,
|
||||
r13,
|
||||
r14,
|
||||
r15,
|
||||
},
|
||||
General: List{
|
||||
rcx,
|
||||
rbx,
|
||||
rbp,
|
||||
r11,
|
||||
r12,
|
||||
r13,
|
||||
r14,
|
||||
r15,
|
||||
},
|
||||
Call: List{
|
||||
rdi,
|
||||
rsi,
|
||||
rdx,
|
||||
r10,
|
||||
r8,
|
||||
r9,
|
||||
},
|
||||
Syscall: List{
|
||||
rax,
|
||||
rdi,
|
||||
rsi,
|
||||
rdx,
|
||||
r10,
|
||||
r8,
|
||||
r9,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ByID returns the register with the given ID.
|
||||
func (cpu *CPU) ByID(id register.ID) *Register {
|
||||
return cpu.All[id]
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package cpu
|
||||
|
||||
// List is a list of registers.
|
||||
type List []*Register
|
||||
|
||||
// FindFree tries to find a free register
|
||||
// and returns nil when all are currently occupied.
|
||||
func (registers List) FindFree() *Register {
|
||||
for _, register := range registers {
|
||||
if register.IsFree() {
|
||||
return register
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InUse returns a list of registers that are currently in use.
|
||||
func (registers List) InUse() List {
|
||||
var inUse List
|
||||
|
||||
for _, register := range registers {
|
||||
if !register.IsFree() {
|
||||
inUse = append(inUse, register)
|
||||
}
|
||||
}
|
||||
|
||||
return inUse
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package cpu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/errors"
|
||||
"git.akyoto.dev/cli/q/src/register"
|
||||
)
|
||||
|
||||
// Register represents a single CPU register.
|
||||
type Register struct {
|
||||
ID register.ID
|
||||
user fmt.Stringer
|
||||
}
|
||||
|
||||
// Use marks the register as used by the given object.
|
||||
func (register *Register) Use(obj fmt.Stringer) error {
|
||||
if register.user != nil {
|
||||
return &errors.RegisterInUse{Register: register.ID.String(), User: register.user.String()}
|
||||
}
|
||||
|
||||
register.user = obj
|
||||
return nil
|
||||
}
|
||||
|
||||
// Free frees the register so that it can be used for new calculations.
|
||||
func (register *Register) Free() {
|
||||
register.user = nil
|
||||
}
|
||||
|
||||
// IsFree returns true if the register is not in use.
|
||||
func (register *Register) IsFree() bool {
|
||||
return register.user == nil
|
||||
}
|
||||
|
||||
// String returns a human-readable representation of the register.
|
||||
func (register *Register) String() string {
|
||||
return fmt.Sprintf("%s%s%v", register.ID, "=", register.user)
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InvalidDirectory errors are returned when the specified path is not a directory.
|
||||
type InvalidDirectory struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// Error implements the text representation.
|
||||
func (err *InvalidDirectory) Error() string {
|
||||
if err.Path == "" {
|
||||
return "Invalid directory"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Invalid directory '%s'", err.Path)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// RegisterInUse errors are returned when a register is already in use.
|
||||
type RegisterInUse struct {
|
||||
Register string
|
||||
User string
|
||||
}
|
||||
|
||||
// Error implements the text representation.
|
||||
func (err *RegisterInUse) Error() string {
|
||||
return fmt.Sprintf("Register '%s' already used by '%s'", err.Register, err.User)
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// Info is used for general info messages.
|
||||
Info = log.New(os.Stdout, "", 0)
|
||||
|
||||
// Error is used for error messages.
|
||||
Error = log.New(os.Stderr, "", 0)
|
||||
)
|
@ -1,44 +0,0 @@
|
||||
package register
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ID represents the number of the register.
|
||||
type ID uint8
|
||||
|
||||
const (
|
||||
R0 ID = iota
|
||||
R1
|
||||
R2
|
||||
R3
|
||||
R4
|
||||
R5
|
||||
R6
|
||||
R7
|
||||
R8
|
||||
R9
|
||||
R10
|
||||
R11
|
||||
R12
|
||||
R13
|
||||
R14
|
||||
R15
|
||||
R16
|
||||
R17
|
||||
R18
|
||||
R19
|
||||
R20
|
||||
R21
|
||||
R22
|
||||
R23
|
||||
R24
|
||||
R25
|
||||
R26
|
||||
R27
|
||||
R28
|
||||
R29
|
||||
R30
|
||||
)
|
||||
|
||||
func (r ID) String() string {
|
||||
return fmt.Sprintf("r%d", r)
|
||||
}
|
Loading…
Reference in New Issue
Block a user