Improved assembler

This commit is contained in:
2023-10-23 12:37:20 +02:00
parent a54c62f6e0
commit ab48a86ccd
22 changed files with 329 additions and 139 deletions

42
src/asm/Assembler.go Normal file
View File

@ -0,0 +1,42 @@
package asm
import (
"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 (list *Assembler) Finalize() *Result {
final := Result{}
for _, instr := range list.Instructions {
instr.Write(&final.Code)
}
return &final
}
func (list *Assembler) MoveRegisterNumber(reg register.ID, number uint64) {
list.Instructions = append(list.Instructions, Instruction{
Mnemonic: MOV,
Destination: reg,
Number: number,
})
}
func (list *Assembler) Syscall() {
list.Instructions = append(list.Instructions, Instruction{
Mnemonic: SYSCALL,
})
}

View File

@ -1,23 +0,0 @@
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()
}

View File

@ -1,8 +1,38 @@
package asm
import "io"
import (
"fmt"
"io"
type Instruction interface {
Write(io.ByteWriter)
String() string
"git.akyoto.dev/cli/q/src/asm/x64"
"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
}
// Write writes the machine code of the instruction.
func (x *Instruction) Write(w io.ByteWriter) {
switch x.Mnemonic {
case MOV:
x64.MoveRegNum32(w, uint8(x.Destination), uint32(x.Number))
case SYSCALL:
x64.Syscall(w)
}
}
func (x *Instruction) String() string {
switch x.Mnemonic {
case MOV:
return fmt.Sprintf("%s %s, %x", x.Mnemonic, x.Destination, x.Number)
case SYSCALL:
return x.Mnemonic.String()
default:
return ""
}
}

View File

@ -1,47 +0,0 @@
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,
})
}

View File

@ -1,26 +0,0 @@
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,6 +2,7 @@ package asm
import "bytes"
// Result is the compilation result and contains the machine code as well as the data.
type Result struct {
Code bytes.Buffer
Data bytes.Buffer

10
src/asm/x64/Call.go Normal file
View File

@ -0,0 +1,10 @@
package x64
import "io"
// Call places the return address on the top of the stack and continues
// program flow at the new address. The address is relative to the next instruction.
func Call(w io.ByteWriter, address uint32) {
w.WriteByte(0xe8)
appendUint32(w, address)
}

View File

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

9
src/asm/x64/Return.go Normal file
View File

@ -0,0 +1,9 @@
package x64
import "io"
// Return transfers program control to a return address located on the top of the stack.
// The address is usually placed on the stack by a Call instruction.
func Return(w io.ByteWriter) {
w.WriteByte(0xc3)
}

View File

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