diff --git a/README.md b/README.md index 3df933b..4154f99 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A simple programming language. ## Features -* 🔥 Fast compilation -* 📦 Small binaries +* Fast compilation +* Small binaries ## Installation @@ -17,10 +17,18 @@ go build ## Usage +Build a Linux ELF executable from `examples/hello`: + ```shell ./q build examples/hello ``` +Run the generated executable: + +```shell +./examples/hello/hello +``` + ## License Please see the [license documentation](https://akyoto.dev/license). diff --git a/src/asm/Base.go b/src/asm/Base.go new file mode 100644 index 0000000..a67c670 --- /dev/null +++ b/src/asm/Base.go @@ -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() +} diff --git a/src/asm/Instruction.go b/src/asm/Instruction.go new file mode 100644 index 0000000..db13758 --- /dev/null +++ b/src/asm/Instruction.go @@ -0,0 +1,8 @@ +package asm + +import "io" + +type Instruction interface { + Write(io.ByteWriter) + String() string +} diff --git a/src/asm/InstructionList.go b/src/asm/InstructionList.go new file mode 100644 index 0000000..cc7ab7a --- /dev/null +++ b/src/asm/InstructionList.go @@ -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, + }) +} diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go new file mode 100644 index 0000000..dea828b --- /dev/null +++ b/src/asm/Mnemonic.go @@ -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" +} diff --git a/src/asm/RegisterNumber.go b/src/asm/RegisterNumber.go new file mode 100644 index 0000000..5637079 --- /dev/null +++ b/src/asm/RegisterNumber.go @@ -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) +} diff --git a/src/asm/Assembler.go b/src/asm/Result.go similarity index 74% rename from src/asm/Assembler.go rename to src/asm/Result.go index 8369cc5..248dc23 100644 --- a/src/asm/Assembler.go +++ b/src/asm/Result.go @@ -2,7 +2,7 @@ package asm import "bytes" -type Assembler struct { +type Result struct { Code bytes.Buffer Data bytes.Buffer } diff --git a/src/asm/x64/AppendUint32.go b/src/asm/x64/AppendUint32.go index 52cb813..78b9040 100644 --- a/src/asm/x64/AppendUint32.go +++ b/src/asm/x64/AppendUint32.go @@ -2,7 +2,7 @@ package x64 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) { w.WriteByte(byte(number)) w.WriteByte(byte(number >> 8)) diff --git a/src/asm/x64/MoveRegNum32.go b/src/asm/x64/MoveRegNum32.go index 64a22bb..126dcf5 100644 --- a/src/asm/x64/MoveRegNum32.go +++ b/src/asm/x64/MoveRegNum32.go @@ -4,7 +4,8 @@ import ( "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) AppendUint32(w, number) } diff --git a/src/asm/x64/Syscall.go b/src/asm/x64/Syscall.go index 3c22056..145f5a3 100644 --- a/src/asm/x64/Syscall.go +++ b/src/asm/x64/Syscall.go @@ -4,6 +4,7 @@ import ( "io" ) +// Syscall is the primary way to communicate with the OS kernel. func Syscall(w io.ByteWriter) { w.WriteByte(0x0f) w.WriteByte(0x05) diff --git a/src/build/Build.go b/src/build/Build.go index 58bc7ff..5ecf62d 100644 --- a/src/build/Build.go +++ b/src/build/Build.go @@ -7,7 +7,6 @@ import ( "strings" "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/elf" "git.akyoto.dev/cli/q/src/errors" @@ -40,26 +39,26 @@ func (build *Build) Run() error { 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 { return nil } - final := asm.Assembler{} - 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()) + return writeToDisk(build.Executable(), result.Code.Bytes(), result.Data.Bytes()) } // Compile compiles all the functions. diff --git a/src/register/General.go b/src/register/General.go index 4358735..d22c83a 100644 --- a/src/register/General.go +++ b/src/register/General.go @@ -1,7 +1,11 @@ package register +import "fmt" + +type Register uint8 + const ( - R0 = iota + R0 Register = iota R1 R2 R3 @@ -18,3 +22,7 @@ const ( R14 R15 ) + +func (r Register) String() string { + return fmt.Sprintf("r%d", r) +}