Implemented if statements

This commit is contained in:
Eduard Urbach 2024-07-07 12:30:57 +02:00
parent 6fc234c700
commit 91e300e49a
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
16 changed files with 280 additions and 14 deletions

View File

@ -1,5 +1,11 @@
main() {
exit(f(1) + f(2) + f(3))
x := f(1) + f(2) + f(3)
if x != 9 {
exit(42)
}
exit(0)
}
exit(code) {

View File

@ -0,0 +1,13 @@
package x64
import "git.akyoto.dev/cli/q/src/build/cpu"
// Compares the register with the number and sets the status flags in the EFLAGS register.
func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte {
return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81)
}
// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register.
func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte {
return regReg(code, byte(registerB), byte(registerA), 0x39)
}

View File

@ -0,0 +1,88 @@
package x64_test
import (
"testing"
"git.akyoto.dev/cli/q/src/build/arch/x64"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/go/assert"
)
func TestCompareRegisterNumber(t *testing.T) {
usagePatterns := []struct {
Register cpu.Register
Number int
Code []byte
}{
{x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}},
{x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}},
{x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}},
{x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}},
{x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}},
{x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}},
{x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}},
{x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}},
{x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}},
{x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}},
{x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}},
{x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}},
{x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}},
{x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}},
{x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}},
{x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}},
{x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}},
{x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}},
}
for _, pattern := range usagePatterns {
t.Logf("cmp %s, %x", pattern.Register, pattern.Number)
code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number)
assert.DeepEqual(t, code, pattern.Code)
}
}
func TestCompareRegisterRegister(t *testing.T) {
usagePatterns := []struct {
Left cpu.Register
Right cpu.Register
Code []byte
}{
{x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}},
{x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}},
{x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}},
{x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}},
{x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}},
{x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}},
{x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}},
{x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}},
{x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}},
{x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}},
{x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}},
{x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}},
{x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}},
{x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}},
{x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}},
{x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}},
}
for _, pattern := range usagePatterns {
t.Logf("cmp %s, %s", pattern.Left, pattern.Right)
code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right)
assert.DeepEqual(t, code, pattern.Code)
}
}

View File

@ -3,9 +3,43 @@ package x64
// Jump continues program flow at the new address.
// The address is relative to the next instruction.
func Jump8(code []byte, address int8) []byte {
return jump8(code, 0xEB, address)
}
// JumpIfLess jumps if the result was less.
func Jump8IfLess(code []byte, address int8) []byte {
return jump8(code, 0x7C, address)
}
// JumpIfLessOrEqual jumps if the result was less or equal.
func Jump8IfLessOrEqual(code []byte, address int8) []byte {
return jump8(code, 0x7E, address)
}
// JumpIfGreater jumps if the result was greater.
func Jump8IfGreater(code []byte, address int8) []byte {
return jump8(code, 0x7F, address)
}
// JumpIfGreaterOrEqual jumps if the result was greater or equal.
func Jump8IfGreaterOrEqual(code []byte, address int8) []byte {
return jump8(code, 0x7D, address)
}
// JumpIfEqual jumps if the result was equal.
func Jump8IfEqual(code []byte, address int8) []byte {
return jump8(code, 0x74, address)
}
// JumpIfNotEqual jumps if the result was not equal.
func Jump8IfNotEqual(code []byte, address int8) []byte {
return jump8(code, 0x75, address)
}
func jump8(code []byte, opCode byte, address int8) []byte {
return append(
code,
0xEB,
opCode,
byte(address),
)
}

View File

@ -22,8 +22,8 @@ const (
)
var (
CallRegisters = SyscallRegisters
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9}
ReturnValueRegisters = []cpu.Register{RAX, RCX, R11}
GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15}
CallRegisters = SyscallRegisters
ReturnValueRegisters = SyscallRegisters
)

View File

@ -66,8 +66,32 @@ func (a Assembler) Finalize() ([]byte, []byte) {
case COMMENT:
continue
case JUMP:
code = x64.Jump8(code, 0x00)
case COMPARE:
switch operands := x.Data.(type) {
case *RegisterNumber:
code = x64.CompareRegisterNumber(code, operands.Register, operands.Number)
case *RegisterRegister:
code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source)
}
case JUMP, JE, JNE, JG, JL, JGE, JLE:
switch x.Mnemonic {
case JUMP:
code = x64.Jump8(code, 0x00)
case JE:
code = x64.Jump8IfEqual(code, 0x00)
case JNE:
code = x64.Jump8IfNotEqual(code, 0x00)
case JG:
code = x64.Jump8IfGreater(code, 0x00)
case JL:
code = x64.Jump8IfLess(code, 0x00)
case JGE:
code = x64.Jump8IfGreaterOrEqual(code, 0x00)
case JLE:
code = x64.Jump8IfLessOrEqual(code, 0x00)
}
size := 1
label := x.Data.(*Label)
nextInstructionAddress := Address(len(code))

View File

@ -34,10 +34,10 @@ func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) {
})
}
// Label adds a label at the current position.
func (a *Assembler) Label(name string) {
// Label adds an instruction using a label.
func (a *Assembler) Label(mnemonic Mnemonic, name string) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: LABEL,
Mnemonic: mnemonic,
Data: &Label{
Name: name,
},

View File

@ -7,7 +7,14 @@ const (
ADD
CALL
COMMENT
COMPARE
DIV
JE
JNE
JG
JGE
JL
JLE
JUMP
MUL
LABEL
@ -28,10 +35,24 @@ func (m Mnemonic) String() string {
return "call"
case COMMENT:
return "comment"
case COMPARE:
return "compare"
case DIV:
return "div"
case JUMP:
return "jump"
case JE:
return "jump =="
case JNE:
return "jump !="
case JL:
return "jump <"
case JG:
return "jump >"
case JLE:
return "jump <="
case JGE:
return "jump >="
case LABEL:
return "label"
case MOVE:

View File

@ -37,6 +37,15 @@ func (node *Define) String() string {
return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value)
}
type If struct {
Condition *expression.Expression
Body AST
}
func (node *If) String() string {
return fmt.Sprintf("(if %s %s)", node.Condition, node.Body)
}
type Loop struct {
Body AST
}

View File

@ -47,6 +47,22 @@ func toASTNode(tokens token.List) (Node, error) {
tree, err := Parse(tokens[blockStart:blockEnd])
return &Loop{Body: tree}, err
case keyword.If:
blockStart := tokens.IndexKind(token.BlockStart) + 1
blockEnd := tokens.LastIndexKind(token.BlockEnd)
if blockStart == -1 {
return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End())
}
if blockEnd == -1 {
return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End())
}
condition := expression.Parse(tokens[1:token.BlockStart])
tree, err := Parse(tokens[blockStart:blockEnd])
return &If{Condition: condition, Body: tree}, err
default:
return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position)
}

View File

@ -0,0 +1,48 @@
package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileIf compiles a branch instruction.
func (f *Function) CompileIf(branch *ast.If) error {
condition := branch.Condition
tmpRight := f.cpu.Input[1]
err := f.ExpressionToRegister(condition.Children[1], tmpRight)
if err != nil {
return err
}
tmpLeft := f.cpu.Input[0]
err = f.ExpressionToRegister(condition.Children[0], tmpLeft)
if err != nil {
return err
}
f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight)
elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch)
switch condition.Token.Text() {
case "==":
f.assembler.Label(asm.JNE, elseLabel)
case "!=":
f.assembler.Label(asm.JE, elseLabel)
case ">":
f.assembler.Label(asm.JLE, elseLabel)
case "<":
f.assembler.Label(asm.JGE, elseLabel)
case ">=":
f.assembler.Label(asm.JL, elseLabel)
case "<=":
f.assembler.Label(asm.JG, elseLabel)
}
defer f.assembler.Label(asm.LABEL, elseLabel)
f.count.branch++
return f.CompileAST(branch.Body)
}

View File

@ -3,13 +3,14 @@ package core
import (
"fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
)
// CompileLoop compiles a loop instruction.
func (f *Function) CompileLoop(loop *ast.Loop) error {
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
f.assembler.Label(label)
f.assembler.Label(asm.LABEL, label)
defer f.assembler.Jump(label)
f.count.loop++
return f.CompileAST(loop.Body)

View File

@ -45,7 +45,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
// Compile turns a function into machine code.
func (f *Function) Compile() {
defer close(f.finished)
f.assembler.Label(f.Name)
f.assembler.Label(asm.LABEL, f.Name)
f.err = f.CompileTokens(f.Body)
f.assembler.Return()
}
@ -90,6 +90,9 @@ func (f *Function) CompileASTNode(node ast.Node) error {
case *ast.Return:
return f.CompileReturn(node)
case *ast.If:
return f.CompileIf(node)
case *ast.Loop:
return f.CompileLoop(node)

View File

@ -21,7 +21,8 @@ type state struct {
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
loop int
loop int
branch int
}
// PrintInstructions shows the assembly instructions.

View File

@ -1,12 +1,14 @@
package keyword
const (
If = "if"
Loop = "loop"
Return = "return"
)
// Map is a map of all keywords used in the language.
var Map = map[string][]byte{
If: []byte(If),
Loop: []byte(Loop),
Return: []byte(Return),
}

View File

@ -10,7 +10,7 @@ var examples = []struct {
ExpectedOutput string
ExpectedExitCode int
}{
{"hello", "", 9},
{"hello", "", 0},
{"write", "ELF", 0},
}