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 (
Assembler = false
Comments = 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 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)
if err != nil {

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import (
func (f *Function) CompileDefinition(node *ast.Define) error {
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)
}
@ -21,7 +21,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
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)
err := f.ExpressionToRegister(node.Value, register)

View File

@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error {
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 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)
err := f.ExpressionToRegister(value, tmp)

View File

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

View File

@ -1,26 +1,20 @@
package core
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/scope"
"git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/build/z"
)
// Function represents the smallest unit of code.
type Function struct {
scope.Stack
Name string
File *fs.File
Body []token.Token
Assembler asm.Assembler
Functions map[string]*Function
Err error
registerHistory []uint64
finished chan struct{}
cpu cpu.CPU
count counter
z.Compiler
Name string
File *fs.File
Body []token.Token
Functions map[string]*Function
Err error
count counter
}
// 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
// identifierExists returns true if the identifier has been defined.
func (f *Function) identifierExists(name string) bool {
// IdentifierExists returns true if the identifier has been defined.
func (f *Function) IdentifierExists(name string) bool {
variable := f.VariableByName(name)
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/scope"
"git.akyoto.dev/cli/q/src/build/token"
"git.akyoto.dev/cli/q/src/build/z"
)
// NewFunction creates a new function.
@ -15,19 +16,20 @@ func NewFunction(name string, file *fs.File, body []token.Token) *Function {
Name: name,
File: file,
Body: body,
Assembler: asm.Assembler{
Instructions: make([]asm.Instruction, 0, 8),
Compiler: z.Compiler{
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{}
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 {
registers.WriteString("⬤ ")
} else {

View File

@ -13,12 +13,12 @@ func (f *Function) UsesRegister(expr *expression.Expression, register cpu.Regist
}
if ast.IsFunctionCall(expr) {
if register == f.cpu.Output[0] {
if register == f.CPU.Output[0] {
return true
}
for i, parameter := range expr.Children[1:] {
if register == f.cpu.Input[i] {
if register == f.CPU.Input[i] {
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 (
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/token"
)
@ -9,6 +10,11 @@ type Stack struct {
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.
func (stack *Stack) CurrentScope() *Scope {
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 (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu"
)
// 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) {
return
}
for _, general := range f.cpu.General {
for _, general := range f.CPU.General {
if register == general {
return
}
}
variable := f.VariableInRegister(register)
variable := f.VariableByRegister(register)
if variable == nil || variable.Alive == 0 {
return
}
newRegister := f.CurrentScope().MustFindFree(f.cpu.General)
newRegister := f.CurrentScope().MustFindFree(f.CPU.General)
f.CurrentScope().Reserve(newRegister)
if config.Comments {
f.Comment("save %s to %s", register, newRegister)
}
f.RegisterRegister(asm.MOVE, newRegister, register)
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] {
case "-a", "--assembler":
config.Assembler = true
case "-c", "--comments":
config.Comments = true
case "-d", "--dry":
config.Dry = true
case "-v", "--verbose":
config.Assembler = true
config.Comments = true
default:
if strings.HasPrefix(args[i], "-") {
return b, &errors.UnknownCLIParameter{Parameter: args[i]}