Implemented instruction lists

This commit is contained in:
Eduard Urbach 2023-10-21 17:46:20 +02:00
parent 4967719902
commit a54c62f6e0
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
12 changed files with 165 additions and 23 deletions

View File

@ -4,8 +4,8 @@ A simple programming language.
## Features ## Features
* 🔥 Fast compilation * Fast compilation
* 📦 Small binaries * Small binaries
## Installation ## Installation
@ -17,10 +17,18 @@ go build
## Usage ## Usage
Build a Linux ELF executable from `examples/hello`:
```shell ```shell
./q build examples/hello ./q build examples/hello
``` ```
Run the generated executable:
```shell
./examples/hello/hello
```
## License ## License
Please see the [license documentation](https://akyoto.dev/license). Please see the [license documentation](https://akyoto.dev/license).

23
src/asm/Base.go Normal file
View File

@ -0,0 +1,23 @@
package asm
import (
"io"
"git.akyoto.dev/cli/q/src/asm/x64"
)
// Base represents the data that is common among all instructions.
type Base struct {
Mnemonic Mnemonic
}
func (x *Base) Write(w io.ByteWriter) {
switch x.Mnemonic {
case SYSCALL:
x64.Syscall(w)
}
}
func (x *Base) String() string {
return x.Mnemonic.String()
}

8
src/asm/Instruction.go Normal file
View File

@ -0,0 +1,8 @@
package asm
import "io"
type Instruction interface {
Write(io.ByteWriter)
String() string
}

View File

@ -0,0 +1,47 @@
package asm
import (
"fmt"
"git.akyoto.dev/cli/q/src/register"
)
type InstructionList struct {
Instructions []Instruction
}
// Finalize generates the final assembly code.
func (list *InstructionList) Finalize() *Result {
final := Result{}
for _, instr := range list.Instructions {
instr.Write(&final.Code)
fmt.Println(instr.String())
}
return &final
}
func (list *InstructionList) MoveRegisterNumber(reg register.Register, number uint64) {
list.addRegisterNumber(MOV, reg, number)
}
func (list *InstructionList) Syscall() {
list.add(SYSCALL)
}
// add adds an instruction without any operands.
func (list *InstructionList) add(mnemonic Mnemonic) {
list.Instructions = append(list.Instructions, &Base{Mnemonic: mnemonic})
}
// addRegisterNumber adds an instruction using a register and a number.
func (list *InstructionList) addRegisterNumber(mnemonic Mnemonic, reg register.Register, number uint64) {
list.Instructions = append(list.Instructions, &RegisterNumber{
Base: Base{
Mnemonic: mnemonic,
},
Register: reg,
Number: number,
})
}

21
src/asm/Mnemonic.go Normal file
View File

@ -0,0 +1,21 @@
package asm
type Mnemonic uint8
const (
NONE Mnemonic = iota
MOV
SYSCALL
)
func (m Mnemonic) String() string {
switch m {
case MOV:
return "mov"
case SYSCALL:
return "syscall"
}
return "NONE"
}

26
src/asm/RegisterNumber.go Normal file
View File

@ -0,0 +1,26 @@
package asm
import (
"fmt"
"io"
"git.akyoto.dev/cli/q/src/asm/x64"
"git.akyoto.dev/cli/q/src/register"
)
type RegisterNumber struct {
Base
Register register.Register
Number uint64
}
func (x *RegisterNumber) Write(w io.ByteWriter) {
switch x.Mnemonic {
case MOV:
x64.MoveRegNum32(w, uint8(x.Register), uint32(x.Number))
}
}
func (x *RegisterNumber) String() string {
return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Register, x.Number)
}

View File

@ -2,7 +2,7 @@ package asm
import "bytes" import "bytes"
type Assembler struct { type Result struct {
Code bytes.Buffer Code bytes.Buffer
Data bytes.Buffer Data bytes.Buffer
} }

View File

@ -2,7 +2,7 @@ package x64
import "io" import "io"
// AppendUint32 appends a 32 bit number in Little Endian to the given writer. // AppendUint32 appends a 32 bit integer in Little Endian to the given writer.
func AppendUint32(w io.ByteWriter, number uint32) { func AppendUint32(w io.ByteWriter, number uint32) {
w.WriteByte(byte(number)) w.WriteByte(byte(number))
w.WriteByte(byte(number >> 8)) w.WriteByte(byte(number >> 8))

View File

@ -4,7 +4,8 @@ import (
"io" "io"
) )
func MoveRegNum32(w io.ByteWriter, register byte, number uint32) { // MoveRegNum32 moves a 32 bit integer into the given register.
func MoveRegNum32(w io.ByteWriter, register uint8, number uint32) {
w.WriteByte(0xb8 + register) w.WriteByte(0xb8 + register)
AppendUint32(w, number) AppendUint32(w, number)
} }

View File

@ -4,6 +4,7 @@ import (
"io" "io"
) )
// Syscall is the primary way to communicate with the OS kernel.
func Syscall(w io.ByteWriter) { func Syscall(w io.ByteWriter) {
w.WriteByte(0x0f) w.WriteByte(0x0f)
w.WriteByte(0x05) w.WriteByte(0x05)

View File

@ -7,7 +7,6 @@ import (
"strings" "strings"
"git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/asm"
"git.akyoto.dev/cli/q/src/asm/x64"
"git.akyoto.dev/cli/q/src/directory" "git.akyoto.dev/cli/q/src/directory"
"git.akyoto.dev/cli/q/src/elf" "git.akyoto.dev/cli/q/src/elf"
"git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/errors"
@ -40,26 +39,26 @@ func (build *Build) Run() error {
return err return err
} }
list := asm.InstructionList{}
list.MoveRegisterNumber(register.Syscall0, syscall.Write)
list.MoveRegisterNumber(register.Syscall1, 1)
list.MoveRegisterNumber(register.Syscall2, 0x4000a2)
list.MoveRegisterNumber(register.Syscall3, 6)
list.Syscall()
list.MoveRegisterNumber(register.Syscall0, syscall.Exit)
list.MoveRegisterNumber(register.Syscall1, 0)
list.Syscall()
result := list.Finalize()
result.Data.WriteString("Hello\n")
if !build.WriteExecutable { if !build.WriteExecutable {
return nil return nil
} }
final := asm.Assembler{} return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes())
code := &final.Code
data := &final.Data
x64.MoveRegNum32(code, register.Syscall0, syscall.Write)
x64.MoveRegNum32(code, register.Syscall1, 1)
x64.MoveRegNum32(code, register.Syscall2, 0x4000a2)
x64.MoveRegNum32(code, register.Syscall3, 6)
x64.Syscall(code)
x64.MoveRegNum32(code, register.Syscall0, syscall.Exit)
x64.MoveRegNum32(code, register.Syscall1, 0)
x64.Syscall(code)
data.WriteString("Hello\n")
return writeToDisk(build.Executable(), code.Bytes(), data.Bytes())
} }
// Compile compiles all the functions. // Compile compiles all the functions.

View File

@ -1,7 +1,11 @@
package register package register
import "fmt"
type Register uint8
const ( const (
R0 = iota R0 Register = iota
R1 R1
R2 R2
R3 R3
@ -18,3 +22,7 @@ const (
R14 R14
R15 R15
) )
func (r Register) String() string {
return fmt.Sprintf("r%d", r)
}