Improved separation of concerns

This commit is contained in:
Eduard Urbach 2024-07-23 16:41:21 +02:00
parent d6db1b1096
commit 69245caf62
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
36 changed files with 243 additions and 236 deletions

View File

@ -16,6 +16,5 @@ const (
var ( var (
Assembler = false Assembler = false
Comments = false
Dry = false Dry = false
) )

View File

@ -1,15 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/scope"
)
// AddVariable adds a new variable to the current scope.
func (f *Function) AddVariable(variable *scope.Variable) {
if config.Comments {
f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive)
}
f.CurrentScope().AddVariable(variable)
}

View File

@ -1,11 +0,0 @@
package core
import (
"fmt"
)
// Comment adds a comment to the assembler.
func (f *Function) Comment(format string, args ...any) {
f.Assembler.Comment(fmt.Sprintf(format, args...))
f.postInstruction()
}

View File

@ -31,10 +31,10 @@ func (f *Function) Compare(comparison *expression.Expression) error {
return err return err
} }
return f.Execute(comparison.Token, f.cpu.Output[0], right) return f.Execute(comparison.Token, f.CPU.Output[0], right)
} }
tmp := f.CurrentScope().MustFindFree(f.cpu.General) tmp := f.CurrentScope().MustFindFree(f.CPU.General)
err := f.ExpressionToRegister(left, tmp) err := f.ExpressionToRegister(left, tmp)
if err != nil { if err != nil {

View File

@ -2,7 +2,6 @@ package core
// Compile turns a function into machine code. // Compile turns a function into machine code.
func (f *Function) Compile() { func (f *Function) Compile() {
defer close(f.finished)
f.AddLabel(f.Name) f.AddLabel(f.Name)
f.Err = f.CompileTokens(f.Body) f.Err = f.CompileTokens(f.Body)
f.Return() f.Return()

View File

@ -31,10 +31,10 @@ func (f *Function) CompileCall(root *expression.Expression) error {
} }
parameters := root.Children[1:] parameters := root.Children[1:]
registers := f.cpu.Input[:len(parameters)] registers := f.CPU.Input[:len(parameters)]
if isSyscall { if isSyscall {
registers = f.cpu.Syscall[:len(parameters)] registers = f.CPU.Syscall[:len(parameters)]
} }
err := f.ExpressionsToRegisters(parameters, registers) err := f.ExpressionsToRegisters(parameters, registers)
@ -43,10 +43,10 @@ func (f *Function) CompileCall(root *expression.Expression) error {
return err return err
} }
f.SaveRegister(f.cpu.Output[0]) f.SaveRegister(f.CPU.Output[0])
// Push // Push
for _, register := range f.cpu.General { for _, register := range f.CPU.General {
if f.CurrentScope().IsUsed(register) { if f.CurrentScope().IsUsed(register) {
f.Register(asm.PUSH, register) f.Register(asm.PUSH, register)
} }
@ -60,7 +60,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
} }
for _, register := range registers { for _, register := range registers {
if register == f.cpu.Output[0] && root.Parent != nil { if register == f.CPU.Output[0] && root.Parent != nil {
continue continue
} }
@ -68,8 +68,8 @@ func (f *Function) CompileCall(root *expression.Expression) error {
} }
// Pop // Pop
for i := len(f.cpu.General) - 1; i >= 0; i-- { for i := len(f.CPU.General) - 1; i >= 0; i-- {
register := f.cpu.General[i] register := f.CPU.General[i]
if f.CurrentScope().IsUsed(register) { if f.CurrentScope().IsUsed(register) {
f.Register(asm.POP, register) f.Register(asm.POP, register)

View File

@ -11,7 +11,7 @@ import (
func (f *Function) CompileDefinition(node *ast.Define) error { func (f *Function) CompileDefinition(node *ast.Define) error {
name := node.Name.Text(f.File.Bytes) name := node.Name.Text(f.File.Bytes)
if f.identifierExists(name) { if f.IdentifierExists(name) {
return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position) return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, node.Name.Position)
} }
@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position) return errors.New(&errors.UnusedVariable{Name: name}, f.File, node.Name.Position)
} }
register := f.CurrentScope().MustFindFree(f.cpu.General) register := f.CurrentScope().MustFindFree(f.CPU.General)
f.CurrentScope().Reserve(register) f.CurrentScope().Reserve(register)
err := f.ExpressionToRegister(node.Value, register) err := f.ExpressionToRegister(node.Value, register)

View File

@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error {
return nil return nil
} }
return f.ExpressionToRegister(node.Value, f.cpu.Output[0]) return f.ExpressionToRegister(node.Value, f.CPU.Output[0])
} }

View File

@ -20,10 +20,10 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
return err return err
} }
return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) return f.ExecuteRegisterRegister(operation, register, f.CPU.Output[0])
} }
tmp := f.CurrentScope().MustFindFree(f.cpu.General) tmp := f.CurrentScope().MustFindFree(f.CPU.General)
defer f.CurrentScope().Free(tmp) defer f.CurrentScope().Free(tmp)
err := f.ExpressionToRegister(value, tmp) err := f.ExpressionToRegister(value, tmp)

View File

@ -3,7 +3,6 @@ package core
import ( import (
"git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors" "git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/expression"
@ -18,8 +17,8 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if ast.IsFunctionCall(node) { if ast.IsFunctionCall(node) {
err := f.CompileCall(node) err := f.CompileCall(node)
if register != f.cpu.Output[0] { if register != f.CPU.Output[0] {
f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0])
} }
return err return err
@ -34,11 +33,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
final := register final := register
if f.UsesRegister(right, register) { if f.UsesRegister(right, register) {
register = f.CurrentScope().MustFindFree(f.cpu.General) register = f.CurrentScope().MustFindFree(f.CPU.General)
if config.Comments {
f.Comment("temporary register %s", register)
}
} }
f.CurrentScope().Reserve(register) f.CurrentScope().Reserve(register)

View File

@ -1,26 +1,20 @@
package core package core
import ( import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/scope"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/build/z"
) )
// Function represents the smallest unit of code. // Function represents the smallest unit of code.
type Function struct { type Function struct {
scope.Stack z.Compiler
Name string Name string
File *fs.File File *fs.File
Body []token.Token Body []token.Token
Assembler asm.Assembler Functions map[string]*Function
Functions map[string]*Function Err error
Err error count counter
registerHistory []uint64
finished chan struct{}
cpu cpu.CPU
count counter
} }
// counter stores how often a certain statement appeared so we can generate a unique label from it. // counter stores how often a certain statement appeared so we can generate a unique label from it.

View File

@ -1,7 +1,7 @@
package core package core
// identifierExists returns true if the identifier has been defined. // IdentifierExists returns true if the identifier has been defined.
func (f *Function) identifierExists(name string) bool { func (f *Function) IdentifierExists(name string) bool {
variable := f.VariableByName(name) variable := f.VariableByName(name)
if variable != nil { if variable != nil {

View File

@ -1,103 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Function) AddLabel(label string) {
f.Assembler.Label(asm.LABEL, label)
f.postInstruction()
}
func (f *Function) Call(label string) {
f.Assembler.Call(label)
f.CurrentScope().Use(f.cpu.Output[0])
f.postInstruction()
}
func (f *Function) Jump(mnemonic asm.Mnemonic, label string) {
f.Assembler.Label(mnemonic, label)
f.postInstruction()
}
func (f *Function) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) {
f.Assembler.MemoryNumber(mnemonic, a, b)
f.postInstruction()
}
func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
f.Assembler.Register(mnemonic, a)
if mnemonic == asm.POP {
f.CurrentScope().Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) {
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterNumber(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) {
if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) {
f.SaveRegister(register)
}
f.Assembler.RegisterLabel(mnemonic, register, label)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(register)
}
f.postInstruction()
}
func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) {
if mnemonic == asm.MOVE && a == b {
return
}
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterRegister(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(a)
}
f.postInstruction()
}
func (f *Function) Return() {
f.Assembler.Return()
f.postInstruction()
}
func (f *Function) Syscall() {
f.Assembler.Syscall()
f.CurrentScope().Use(f.cpu.Output[0])
f.postInstruction()
}
func isDestructive(mnemonic asm.Mnemonic) bool {
switch mnemonic {
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:
return true
default:
return false
}
}

View File

@ -7,6 +7,7 @@ import (
"git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/fs"
"git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/scope"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/build/z"
) )
// NewFunction creates a new function. // NewFunction creates a new function.
@ -15,19 +16,20 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function {
Name: name, Name: name,
File: file, File: file,
Body: body, Body: body,
Assembler: asm.Assembler{ Compiler: z.Compiler{
Instructions: make([]asm.Instruction, 0, 8), Assembler: asm.Assembler{
Instructions: make([]asm.Instruction, 0, 8),
},
Stack: scope.Stack{
Scopes: []*scope.Scope{{}},
},
CPU: cpu.CPU{
All: x64.AllRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters,
Output: x64.ReturnValueRegisters,
},
}, },
Stack: scope.Stack{
Scopes: []*scope.Scope{{}},
},
cpu: cpu.CPU{
All: x64.AllRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters,
Output: x64.ReturnValueRegisters,
},
finished: make(chan struct{}),
} }
} }

View File

@ -33,9 +33,9 @@ func (f *Function) PrintInstructions() {
} }
registers := bytes.Buffer{} registers := bytes.Buffer{}
used := f.registerHistory[i] used := f.RegisterHistory[i]
for _, reg := range f.cpu.All { for _, reg := range f.CPU.All {
if used&(1<<reg) != 0 { if used&(1<<reg) != 0 {
registers.WriteString("⬤ ") registers.WriteString("⬤ ")
} else { } else {

View File

@ -13,12 +13,12 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist
} }
if ast.IsFunctionCall(expr) { if ast.IsFunctionCall(expr) {
if register == f.cpu.Output[0] { if register == f.CPU.Output[0] {
return true return true
} }
for i, parameter := range expr.Children[1:] { for i, parameter := range expr.Children[1:] {
if register == f.cpu.Input[i] { if register == f.CPU.Input[i] {
return true return true
} }

View File

@ -1,8 +0,0 @@
package core
import "git.akyoto.dev/cli/q/src/build/scope"
// VariableByName returns the variable with the given name or `nil` if it doesn't exist.
func (f *Function) VariableByName(name string) *scope.Variable {
return f.CurrentScope().VariableByName(name)
}

View File

@ -1,17 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/scope"
)
// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register.
func (f *Function) VariableInRegister(register cpu.Register) *scope.Variable {
for _, v := range f.CurrentScope().Variables {
if v.Register == register {
return v
}
}
return nil
}

View File

@ -1,11 +0,0 @@
package core
import "git.akyoto.dev/cli/q/src/build/config"
func (f *Function) postInstruction() {
if !config.Assembler {
return
}
f.registerHistory = append(f.registerHistory, f.CurrentScope().Used)
}

View File

@ -2,6 +2,7 @@ package scope
import ( import (
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/build/token"
) )
@ -9,6 +10,11 @@ type Stack struct {
Scopes []*Scope Scopes []*Scope
} }
// AddVariable adds a new variable to the current scope.
func (stack *Stack) AddVariable(variable *Variable) {
stack.CurrentScope().AddVariable(variable)
}
// CurrentScope returns the current scope. // CurrentScope returns the current scope.
func (stack *Stack) CurrentScope() *Scope { func (stack *Stack) CurrentScope() *Scope {
return stack.Scopes[len(stack.Scopes)-1] return stack.Scopes[len(stack.Scopes)-1]
@ -69,3 +75,19 @@ func (stack *Stack) UseVariable(variable *Variable) {
} }
} }
} }
// VariableByName returns the variable with the given name or `nil` if it doesn't exist.
func (stack *Stack) VariableByName(name string) *Variable {
return stack.CurrentScope().VariableByName(name)
}
// VariableByRegister returns the variable that occupies the given register or `nil` if none occupy the register.
func (stack *Stack) VariableByRegister(register cpu.Register) *Variable {
for _, v := range stack.CurrentScope().Variables {
if v.Register == register {
return v
}
}
return nil
}

8
src/build/z/AddLabel.go Normal file
View File

@ -0,0 +1,8 @@
package z
import "git.akyoto.dev/cli/q/src/build/asm"
func (f *Compiler) AddLabel(label string) {
f.Assembler.Label(asm.LABEL, label)
f.postInstruction()
}

7
src/build/z/Call.go Normal file
View File

@ -0,0 +1,7 @@
package z
func (f *Compiler) Call(label string) {
f.Assembler.Call(label)
f.CurrentScope().Use(f.CPU.Output[0])
f.postInstruction()
}

8
src/build/z/Comment.go Normal file
View File

@ -0,0 +1,8 @@
package z
import "fmt"
func (f *Compiler) Comment(format string, args ...any) {
f.Assembler.Comment(fmt.Sprintf(format, args...))
f.postInstruction()
}

15
src/build/z/Compiler.go Normal file
View File

@ -0,0 +1,15 @@
package z
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/scope"
)
// Compiler is a register usage aware assembler.
type Compiler struct {
scope.Stack
Assembler asm.Assembler
CPU cpu.CPU
RegisterHistory []uint64
}

8
src/build/z/Jump.go Normal file
View File

@ -0,0 +1,8 @@
package z
import "git.akyoto.dev/cli/q/src/build/asm"
func (f *Compiler) Jump(mnemonic asm.Mnemonic, label string) {
f.Assembler.Label(mnemonic, label)
f.postInstruction()
}

View File

@ -0,0 +1,8 @@
package z
import "git.akyoto.dev/cli/q/src/build/asm"
func (f *Compiler) MemoryNumber(mnemonic asm.Mnemonic, a asm.Memory, b int) {
f.Assembler.MemoryNumber(mnemonic, a, b)
f.postInstruction()
}

16
src/build/z/Register.go Normal file
View File

@ -0,0 +1,16 @@
package z
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Compiler) Register(mnemonic asm.Mnemonic, a cpu.Register) {
f.Assembler.Register(mnemonic, a)
if mnemonic == asm.POP {
f.CurrentScope().Use(a)
}
f.postInstruction()
}

View File

@ -0,0 +1,20 @@
package z
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Compiler) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) {
if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) {
f.SaveRegister(register)
}
f.Assembler.RegisterLabel(mnemonic, register, label)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(register)
}
f.postInstruction()
}

View File

@ -0,0 +1,20 @@
package z
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Compiler) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) {
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterNumber(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(a)
}
f.postInstruction()
}

View File

@ -0,0 +1,24 @@
package z
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Compiler) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) {
if mnemonic == asm.MOVE && a == b {
return
}
if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.Assembler.RegisterRegister(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.CurrentScope().Use(a)
}
f.postInstruction()
}

6
src/build/z/Return.go Normal file
View File

@ -0,0 +1,6 @@
package z
func (f *Compiler) Return() {
f.Assembler.Return()
f.postInstruction()
}

View File

@ -1,36 +1,30 @@
package core package z
import ( import (
"git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/cpu"
) )
// SaveRegister attempts to move a variable occupying this register to another register. // SaveRegister attempts to move a variable occupying this register to another register.
func (f *Function) SaveRegister(register cpu.Register) { func (f *Compiler) SaveRegister(register cpu.Register) {
if !f.CurrentScope().IsUsed(register) { if !f.CurrentScope().IsUsed(register) {
return return
} }
for _, general := range f.cpu.General { for _, general := range f.CPU.General {
if register == general { if register == general {
return return
} }
} }
variable := f.VariableInRegister(register) variable := f.VariableByRegister(register)
if variable == nil || variable.Alive == 0 { if variable == nil || variable.Alive == 0 {
return return
} }
newRegister := f.CurrentScope().MustFindFree(f.cpu.General) newRegister := f.CurrentScope().MustFindFree(f.CPU.General)
f.CurrentScope().Reserve(newRegister) f.CurrentScope().Reserve(newRegister)
if config.Comments {
f.Comment("save %s to %s", register, newRegister)
}
f.RegisterRegister(asm.MOVE, newRegister, register) f.RegisterRegister(asm.MOVE, newRegister, register)
variable.Register = newRegister variable.Register = newRegister
} }

7
src/build/z/Syscall.go Normal file
View File

@ -0,0 +1,7 @@
package z
func (f *Compiler) Syscall() {
f.Assembler.Syscall()
f.CurrentScope().Use(f.CPU.Output[0])
f.postInstruction()
}

View File

@ -0,0 +1,12 @@
package z
import "git.akyoto.dev/cli/q/src/build/asm"
func isDestructive(mnemonic asm.Mnemonic) bool {
switch mnemonic {
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:
return true
default:
return false
}
}

View File

@ -0,0 +1,11 @@
package z
import "git.akyoto.dev/cli/q/src/build/config"
func (f *Compiler) postInstruction() {
if !config.Assembler {
return
}
f.RegisterHistory = append(f.RegisterHistory, f.CurrentScope().Used)
}

View File

@ -37,13 +37,10 @@ func buildWithArgs(args []string) (*build.Build, error) {
switch args[i] { switch args[i] {
case "-a", "--assembler": case "-a", "--assembler":
config.Assembler = true config.Assembler = true
case "-c", "--comments":
config.Comments = true
case "-d", "--dry": case "-d", "--dry":
config.Dry = true config.Dry = true
case "-v", "--verbose": case "-v", "--verbose":
config.Assembler = true config.Assembler = true
config.Comments = true
default: default:
if strings.HasPrefix(args[i], "-") { if strings.HasPrefix(args[i], "-") {
return b, &errors.UnknownCLIParameter{Parameter: args[i]} return b, &errors.UnknownCLIParameter{Parameter: args[i]}