Reorganized file structure
This commit is contained in:
parent
c7354b8613
commit
6fe30f31da
@ -1,3 +1,3 @@
|
|||||||
main() {
|
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
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/compiler"
|
|
||||||
"git.akyoto.dev/cli/q/src/elf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build describes a compiler build.
|
// Build describes a compiler build.
|
||||||
@ -25,7 +20,7 @@ func New(directory string) *Build {
|
|||||||
|
|
||||||
// Run parses the input files and generates an executable file.
|
// Run parses the input files and generates an executable file.
|
||||||
func (build *Build) Run() error {
|
func (build *Build) Run() error {
|
||||||
functions, err := compiler.Compile(build.Directory)
|
functions, err := Compile(build.Directory)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -35,33 +30,12 @@ func (build *Build) Run() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
code, data := compiler.Finalize(functions)
|
path := build.Executable()
|
||||||
return writeToDisk(build.Executable(), code, data)
|
code, data := Finalize(functions)
|
||||||
|
return Write(path, code, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executable returns the path to the executable.
|
// Executable returns the path to the executable.
|
||||||
func (build *Build) Executable() string {
|
func (build *Build) Executable() string {
|
||||||
return filepath.Join(build.Directory, filepath.Base(build.Directory))
|
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.
|
// Compile compiles all the functions.
|
||||||
func Compile(directory string) (map[string]*Function, error) {
|
func Compile(directory string) (map[string]*Function, error) {
|
@ -1,9 +1,7 @@
|
|||||||
package compiler
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
"git.akyoto.dev/cli/q/src/build/asm"
|
||||||
"git.akyoto.dev/cli/q/src/asm"
|
|
||||||
"git.akyoto.dev/cli/q/src/os/linux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Finalize generates the final machine code.
|
// Finalize generates the final machine code.
|
||||||
@ -14,10 +12,6 @@ func Finalize(functions map[string]*Function) ([]byte, []byte) {
|
|||||||
a.Merge(&f.Assembler)
|
a.Merge(&f.Assembler)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.MoveRegisterNumber(x64.SyscallNumber, linux.Exit)
|
|
||||||
a.MoveRegisterNumber(x64.SyscallArgs[0], 0)
|
|
||||||
a.Syscall()
|
|
||||||
|
|
||||||
code, data := a.Finalize()
|
code, data := a.Finalize()
|
||||||
return code, data
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/directory"
|
"git.akyoto.dev/cli/q/src/build/directory"
|
||||||
"git.akyoto.dev/cli/q/src/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scan scans the directory.
|
// 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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/arch/x64"
|
"git.akyoto.dev/cli/q/src/build/arch/x64"
|
||||||
"git.akyoto.dev/go/assert"
|
"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 (
|
const (
|
||||||
NONE Mnemonic = iota
|
NONE Mnemonic = iota
|
||||||
MOV
|
MOVE
|
||||||
MOVDATA
|
|
||||||
SYSCALL
|
SYSCALL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a human readable version.
|
||||||
func (m Mnemonic) String() string {
|
func (m Mnemonic) String() string {
|
||||||
switch m {
|
switch m {
|
||||||
case MOV:
|
case MOVE:
|
||||||
return "mov"
|
return "move"
|
||||||
|
|
||||||
case MOVDATA:
|
|
||||||
return "mov"
|
|
||||||
|
|
||||||
case SYSCALL:
|
case SYSCALL:
|
||||||
return "syscall"
|
return "syscall"
|
@ -1,5 +1,8 @@
|
|||||||
package asm
|
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.
|
// 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.
|
// 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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/directory"
|
"git.akyoto.dev/cli/q/src/build/directory"
|
||||||
"git.akyoto.dev/go/assert"
|
"git.akyoto.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/config"
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ELF represents an ELF file.
|
// ELF represents an ELF file.
|
@ -4,7 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/elf"
|
"git.akyoto.dev/cli/q/src/build/elf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestELF(t *testing.T) {
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/token"
|
"git.akyoto.dev/cli/q/src/build/token"
|
||||||
"git.akyoto.dev/go/assert"
|
"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) {
|
func TestSeparator(t *testing.T) {
|
||||||
tokens := token.Tokenize([]byte("a,b,c"))
|
tokens := token.Tokenize([]byte("a,b,c"))
|
||||||
assert.DeepEqual(t, tokens, token.List{
|
assert.DeepEqual(t, tokens, token.List{
|
@ -96,7 +96,25 @@ func Tokenize(buffer []byte) List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens = append(tokens, token)
|
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
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
func isIdentifierStart(c byte) bool {
|
func isIdentifier(c byte) bool {
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'
|
return isLetter(c) || isNumber(c) || c == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
func isIdentifier(c byte) bool {
|
func isIdentifierStart(c byte) bool {
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9')
|
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
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build"
|
"git.akyoto.dev/cli/q/src/build"
|
||||||
"git.akyoto.dev/cli/q/src/config"
|
"git.akyoto.dev/cli/q/src/build/config"
|
||||||
"git.akyoto.dev/cli/q/src/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build builds an executable.
|
// Build builds an executable.
|
||||||
@ -22,7 +22,7 @@ func Build(args []string) int {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(args[i], "-") {
|
if strings.HasPrefix(args[i], "-") {
|
||||||
log.Error.Printf("Unknown parameter: %s\n", args[i])
|
fmt.Printf("Unknown parameter: %s\n", args[i])
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func Build(args []string) int {
|
|||||||
err := b.Run()
|
err := b.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error.Println(err)
|
fmt.Println(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"git.akyoto.dev/cli/q/src/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Help shows the command line argument usage.
|
// Help shows the command line argument usage.
|
||||||
func Help(args []string) int {
|
func Help(args []string) int {
|
||||||
log.Error.Println("Usage: q [command] [options]")
|
fmt.Println("Usage: q [command] [options]")
|
||||||
log.Error.Println("")
|
fmt.Println("")
|
||||||
log.Error.Println(" build [directory]")
|
fmt.Println(" build [directory]")
|
||||||
log.Error.Println(" system")
|
fmt.Println(" system")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,13 @@ package cli_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/cli"
|
"git.akyoto.dev/cli/q/src/cli"
|
||||||
"git.akyoto.dev/cli/q/src/log"
|
|
||||||
"git.akyoto.dev/go/assert"
|
"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) {
|
func TestCLI(t *testing.T) {
|
||||||
type cliTest struct {
|
type cliTest struct {
|
||||||
arguments []string
|
arguments []string
|
||||||
|
@ -1,36 +1,35 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// System shows system information.
|
// System shows system information.
|
||||||
func System(args []string) int {
|
func System(args []string) int {
|
||||||
line := "%-19s%s\n"
|
line := "%-19s%s\n"
|
||||||
|
|
||||||
log.Info.Printf(line, "Platform:", runtime.GOOS)
|
fmt.Printf(line, "Platform:", runtime.GOOS)
|
||||||
log.Info.Printf(line, "Architecture:", runtime.GOARCH)
|
fmt.Printf(line, "Architecture:", runtime.GOARCH)
|
||||||
log.Info.Printf(line, "Go:", runtime.Version())
|
fmt.Printf(line, "Go:", runtime.Version())
|
||||||
|
|
||||||
// Directory
|
// Directory
|
||||||
directory, err := os.Getwd()
|
directory, err := os.Getwd()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Info.Printf(line, "Directory:", directory)
|
fmt.Printf(line, "Directory:", directory)
|
||||||
} else {
|
} else {
|
||||||
log.Info.Printf(line, "Directory:", err.Error())
|
fmt.Printf(line, "Directory:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compiler
|
// Compiler
|
||||||
executable, err := os.Executable()
|
executable, err := os.Executable()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Info.Printf(line, "Compiler:", executable)
|
fmt.Printf(line, "Compiler:", executable)
|
||||||
} else {
|
} else {
|
||||||
log.Info.Printf(line, "Compiler:", err.Error())
|
fmt.Printf(line, "Compiler:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
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