From 2b703e9af26bac8f67d0d525612ae85420894e2b Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 23 Jun 2025 23:11:05 +0200 Subject: [PATCH] Improved SSA and added unused value checks --- src/core/CheckDeadCode.go | 16 +++++++ src/core/Compile.go | 2 + src/core/CompileReturn.go | 7 +-- src/core/Evaluate.go | 37 ++++++++++++---- src/core/Function.go | 12 +++--- src/core/errors.go | 9 ++++ src/expression/operator.go | 8 ++-- src/expression/write.go | 4 +- src/ssa/Arguments.go | 23 ++++++++++ src/ssa/BinaryOperation.go | 50 ++++++++++++++++++++++ src/ssa/Block.go | 10 +++-- src/ssa/Bytes.go | 31 ++++++++++++++ src/ssa/Call.go | 27 ++++++++++++ src/ssa/Function.go | 62 +++++++-------------------- src/ssa/HasToken.go | 11 +++++ src/ssa/IR.go | 83 +++++++++++++++++++++++++++++++++++ src/ssa/Int.go | 33 ++++++++++++++ src/ssa/Liveness.go | 13 ++++++ src/ssa/Register.go | 31 ++++++++++++++ src/ssa/Return.go | 39 +++++++++++++++++ src/ssa/Syscall.go | 27 ++++++++++++ src/ssa/Type.go | 39 ----------------- src/ssa/Value.go | 88 ++++---------------------------------- src/ssa/bench_test.go | 58 +++++++++++++++++++------ src/ssa/ssa_test.go | 12 ++---- 25 files changed, 519 insertions(+), 213 deletions(-) create mode 100644 src/core/CheckDeadCode.go create mode 100644 src/ssa/Arguments.go create mode 100644 src/ssa/BinaryOperation.go create mode 100644 src/ssa/Bytes.go create mode 100644 src/ssa/Call.go create mode 100644 src/ssa/HasToken.go create mode 100644 src/ssa/IR.go create mode 100644 src/ssa/Int.go create mode 100644 src/ssa/Liveness.go create mode 100644 src/ssa/Register.go create mode 100644 src/ssa/Return.go create mode 100644 src/ssa/Syscall.go delete mode 100644 src/ssa/Type.go diff --git a/src/core/CheckDeadCode.go b/src/core/CheckDeadCode.go new file mode 100644 index 0000000..c0d5274 --- /dev/null +++ b/src/core/CheckDeadCode.go @@ -0,0 +1,16 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/errors" +) + +// CheckDeadCode checks for dead values. +func (f *Function) CheckDeadCode() error { + for instr := range f.Values { + if instr.IsConst() && instr.Alive() == 0 { + return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.Token().Position) + } + } + + return nil +} \ No newline at end of file diff --git a/src/core/Compile.go b/src/core/Compile.go index 6a04058..3c0437a 100644 --- a/src/core/Compile.go +++ b/src/core/Compile.go @@ -26,4 +26,6 @@ func (f *Function) Compile() { return } } + + f.Err = f.CheckDeadCode() } \ No newline at end of file diff --git a/src/core/CompileReturn.go b/src/core/CompileReturn.go index 9c04f58..9ededcc 100644 --- a/src/core/CompileReturn.go +++ b/src/core/CompileReturn.go @@ -15,9 +15,10 @@ func (f *Function) CompileReturn(tokens token.List) error { return err } - f.Append(ssa.Value{ - Type: ssa.Return, - Args: []*ssa.Value{value}, + f.Append(&ssa.Return{ + Arguments: ssa.Arguments{ + Args: []ssa.Value{value}, + }, }) return nil diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 9c07152..1b63506 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -10,7 +10,7 @@ import ( ) // Evaluate converts an expression to an SSA value. -func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) { +func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) { if expr.IsLeaf() { switch expr.Token.Kind { case token.Identifier: @@ -30,12 +30,16 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) { return nil, err } - return f.AppendInt(number), nil + v := f.AppendInt(number) + v.Source = expr.Token + return v, nil case token.String: data := expr.Token.Bytes(f.File.Bytes) data = Unescape(data) - return f.AppendBytes(data), nil + v := f.AppendBytes(data) + v.Source = expr.Token + return v, nil } return nil, errors.New(InvalidExpression, f.File, expr.Token.Position) @@ -44,7 +48,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) { switch expr.Token.Kind { case token.Call: children := expr.Children - typ := ssa.Call + isSyscall := false if children[0].Token.Kind == token.Identifier { funcName := children[0].String(f.File.Bytes) @@ -56,11 +60,11 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) { if funcName == "syscall" { children = children[1:] - typ = ssa.Syscall + isSyscall = true } } - args := make([]*ssa.Value, len(children)) + args := make([]ssa.Value, len(children)) for i, child := range children { value, err := f.Evaluate(child) @@ -72,12 +76,27 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) { args[i] = value } - call := f.Append(ssa.Value{Type: typ, Args: args}) - return call, nil + if isSyscall { + v := f.Append(&ssa.Syscall{ + Arguments: ssa.Arguments{Args: args}, + HasToken: ssa.HasToken{Source: expr.Token}, + }) + + return v, nil + } else { + v := f.Append(&ssa.Call{ + Arguments: ssa.Arguments{Args: args}, + HasToken: ssa.HasToken{Source: expr.Token}, + }) + + return v, nil + } case token.Dot: name := fmt.Sprintf("%s.%s", expr.Children[0].String(f.File.Bytes), expr.Children[1].String(f.File.Bytes)) - return f.AppendFunction(name), nil + v := f.AppendFunction(name) + v.Source = expr.Children[1].Token + return v, nil } return nil, nil diff --git a/src/core/Function.go b/src/core/Function.go index 2e7695f..8a0fd60 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -10,14 +10,14 @@ import ( // Function is the smallest unit of code. type Function struct { - ssa.Function + ssa.IR Name string UniqueName string File *fs.File Input []*Parameter Output []*Parameter Body token.List - Identifiers map[string]*ssa.Value + Identifiers map[string]ssa.Value Err error } @@ -27,9 +27,11 @@ func NewFunction(name string, file *fs.File) *Function { Name: name, File: file, UniqueName: fmt.Sprintf("%s.%s", file.Package, name), - Identifiers: make(map[string]*ssa.Value), - Function: ssa.Function{ - Blocks: []*ssa.Block{{}}, + Identifiers: make(map[string]ssa.Value), + IR: ssa.IR{ + Blocks: []*ssa.Block{ + {Instructions: make([]ssa.Value, 0, 8)}, + }, }, } } diff --git a/src/core/errors.go b/src/core/errors.go index b8dbbdb..e8fb774 100644 --- a/src/core/errors.go +++ b/src/core/errors.go @@ -24,4 +24,13 @@ func (err *UnknownIdentifier) Error() string { } return fmt.Sprintf("Unknown identifier '%s'", err.Name) +} + +// UnusedValue error is created when a value is never used. +type UnusedValue struct { + Value string +} + +func (err *UnusedValue) Error() string { + return fmt.Sprintf("Unused value '%s'", err.Value) } \ No newline at end of file diff --git a/src/expression/operator.go b/src/expression/operator.go index d94eb56..66b0be5 100644 --- a/src/expression/operator.go +++ b/src/expression/operator.go @@ -13,9 +13,9 @@ type operator struct { Operands int8 } -// operators defines the operators used in the language. +// Operators defines the Operators used in the language. // The number corresponds to the operator priority and can not be zero. -var operators = [64]operator{ +var Operators = [64]operator{ token.Dot: {".", 13, 2}, token.Call: {"λ", 12, 1}, token.Array: {"@", 12, 2}, @@ -59,9 +59,9 @@ var operators = [64]operator{ } func numOperands(symbol token.Kind) int { - return int(operators[symbol].Operands) + return int(Operators[symbol].Operands) } func precedence(symbol token.Kind) int8 { - return operators[symbol].Precedence + return Operators[symbol].Precedence } \ No newline at end of file diff --git a/src/expression/write.go b/src/expression/write.go index 5db5f14..ba4fcb3 100644 --- a/src/expression/write.go +++ b/src/expression/write.go @@ -17,9 +17,9 @@ func (expr *Expression) write(builder *strings.Builder, source []byte) { switch expr.Token.Kind { case token.Call: - builder.WriteString(operators[token.Call].Symbol) + builder.WriteString(Operators[token.Call].Symbol) case token.Array: - builder.WriteString(operators[token.Array].Symbol) + builder.WriteString(Operators[token.Array].Symbol) default: builder.WriteString(expr.Token.String(source)) } diff --git a/src/ssa/Arguments.go b/src/ssa/Arguments.go new file mode 100644 index 0000000..7b84610 --- /dev/null +++ b/src/ssa/Arguments.go @@ -0,0 +1,23 @@ +package ssa + +type Arguments struct { + Args []Value +} + +func (v *Arguments) Dependencies() []Value { + return v.Args +} + +func (a Arguments) Equals(b Arguments) bool { + if len(a.Args) != len(b.Args) { + return false + } + + for i := range a.Args { + if !a.Args[i].Equals(b.Args[i]) { + return false + } + } + + return true +} \ No newline at end of file diff --git a/src/ssa/BinaryOperation.go b/src/ssa/BinaryOperation.go new file mode 100644 index 0000000..44ef9e8 --- /dev/null +++ b/src/ssa/BinaryOperation.go @@ -0,0 +1,50 @@ +package ssa + +import ( + "fmt" + + "git.urbach.dev/cli/q/src/expression" + "git.urbach.dev/cli/q/src/token" +) + +type BinaryOperation struct { + Left Value + Right Value + Op token.Kind + Liveness + HasToken +} + +func (v *BinaryOperation) Dependencies() []Value { + return []Value{v.Left, v.Right} +} + +func (a *BinaryOperation) Equals(v Value) bool { + b, sameType := v.(*BinaryOperation) + + if !sameType { + return false + } + + if a.Source.Kind != b.Source.Kind { + return false + } + + if !a.Left.Equals(b.Left) { + return false + } + + if !a.Right.Equals(b.Right) { + return false + } + + return true +} + +func (v *BinaryOperation) IsConst() bool { + return true +} + +func (v *BinaryOperation) String() string { + return fmt.Sprintf("%s %s %s", v.Left, expression.Operators[v.Op].Symbol, v.Right) +} \ No newline at end of file diff --git a/src/ssa/Block.go b/src/ssa/Block.go index fe44b97..61b02f1 100644 --- a/src/ssa/Block.go +++ b/src/ssa/Block.go @@ -6,7 +6,11 @@ type Block struct { } // Append adds a new instruction to the block. -func (b *Block) Append(instr Value) *Value { - b.Instructions = append(b.Instructions, instr) - return &b.Instructions[len(b.Instructions)-1] +func (block *Block) Append(instr Value) Value { + for _, dep := range instr.Dependencies() { + dep.AddUse(instr) + } + + block.Instructions = append(block.Instructions, instr) + return instr } \ No newline at end of file diff --git a/src/ssa/Bytes.go b/src/ssa/Bytes.go new file mode 100644 index 0000000..639bb17 --- /dev/null +++ b/src/ssa/Bytes.go @@ -0,0 +1,31 @@ +package ssa + +import "bytes" + +type Bytes struct { + Bytes []byte + Liveness + HasToken +} + +func (v *Bytes) Dependencies() []Value { + return nil +} + +func (a *Bytes) Equals(v Value) bool { + b, sameType := v.(*Bytes) + + if !sameType { + return false + } + + return bytes.Equal(a.Bytes, b.Bytes) +} + +func (v *Bytes) IsConst() bool { + return true +} + +func (v *Bytes) String() string { + return string(v.Bytes) +} \ No newline at end of file diff --git a/src/ssa/Call.go b/src/ssa/Call.go new file mode 100644 index 0000000..cfcd423 --- /dev/null +++ b/src/ssa/Call.go @@ -0,0 +1,27 @@ +package ssa + +import "fmt" + +type Call struct { + Arguments + Liveness + HasToken +} + +func (a *Call) Equals(v Value) bool { + b, sameType := v.(*Call) + + if !sameType { + return false + } + + return a.Arguments.Equals(b.Arguments) +} + +func (v *Call) IsConst() bool { + return false +} + +func (v *Call) String() string { + return fmt.Sprintf("call%v", v.Args) +} \ No newline at end of file diff --git a/src/ssa/Function.go b/src/ssa/Function.go index 93639ab..c49cf29 100644 --- a/src/ssa/Function.go +++ b/src/ssa/Function.go @@ -1,61 +1,29 @@ package ssa -import ( - "git.urbach.dev/cli/q/src/cpu" -) - -// Function is a list of basic blocks. type Function struct { - Blocks []*Block + UniqueName string + Liveness + HasToken } -// AddBlock adds a new block to the function. -func (f *Function) AddBlock() *Block { - block := &Block{} - f.Blocks = append(f.Blocks, block) - return block +func (v *Function) Dependencies() []Value { + return nil } -// Append adds a new value to the last block. -func (f *Function) Append(instr Value) *Value { - if len(f.Blocks) == 0 { - f.Blocks = append(f.Blocks, &Block{}) +func (a *Function) Equals(v Value) bool { + b, sameType := v.(*Function) + + if !sameType { + return false } - if instr.IsConst() { - for _, b := range f.Blocks { - for _, existing := range b.Instructions { - if instr.Equals(existing) { - return &existing - } - } - } - } - - return f.Blocks[len(f.Blocks)-1].Append(instr) + return a.UniqueName == b.UniqueName } -// AppendInt adds a new integer value to the last block. -func (f *Function) AppendInt(x int) *Value { - return f.Append(Value{Type: Int, Int: x}) +func (v *Function) IsConst() bool { + return true } -// AppendRegister adds a new register value to the last block. -func (f *Function) AppendRegister(reg cpu.Register) *Value { - return f.Append(Value{Type: Register, Register: reg}) -} - -// AppendFunction adds a new function value to the last block. -func (f *Function) AppendFunction(name string) *Value { - return f.Append(Value{Type: Func, Text: name}) -} - -// AppendBytes adds a new byte slice value to the last block. -func (f *Function) AppendBytes(s []byte) *Value { - return f.Append(Value{Type: String, Text: string(s)}) -} - -// AppendString adds a new string value to the last block. -func (f *Function) AppendString(s string) *Value { - return f.Append(Value{Type: String, Text: s}) +func (v *Function) String() string { + return v.UniqueName } \ No newline at end of file diff --git a/src/ssa/HasToken.go b/src/ssa/HasToken.go new file mode 100644 index 0000000..e6c45b9 --- /dev/null +++ b/src/ssa/HasToken.go @@ -0,0 +1,11 @@ +package ssa + +import "git.urbach.dev/cli/q/src/token" + +type HasToken struct { + Source token.Token +} + +func (v *HasToken) Token() token.Token { + return v.Source +} \ No newline at end of file diff --git a/src/ssa/IR.go b/src/ssa/IR.go new file mode 100644 index 0000000..f7feae3 --- /dev/null +++ b/src/ssa/IR.go @@ -0,0 +1,83 @@ +package ssa + +import ( + "git.urbach.dev/cli/q/src/cpu" +) + +// IR is a list of basic blocks. +type IR struct { + Blocks []*Block +} + +// AddBlock adds a new block to the function. +func (f *IR) AddBlock() *Block { + block := &Block{ + Instructions: make([]Value, 0, 8), + } + + f.Blocks = append(f.Blocks, block) + return block +} + +// Append adds a new value to the last block. +func (f *IR) Append(instr Value) Value { + if len(f.Blocks) == 0 { + f.AddBlock() + } + + if instr.IsConst() { + for existing := range f.Values { + if existing.IsConst() && instr.Equals(existing) { + return existing + } + } + } + + return f.Blocks[len(f.Blocks)-1].Append(instr) +} + +// AppendInt adds a new integer value to the last block. +func (f *IR) AppendInt(x int) *Int { + v := &Int{Int: x} + f.Append(v) + return v +} + +// AppendRegister adds a new register value to the last block. +func (f *IR) AppendRegister(reg cpu.Register) *Register { + v := &Register{Register: reg} + f.Append(v) + return v +} + +// AppendFunction adds a new function value to the last block. +func (f *IR) AppendFunction(name string) *Function { + v := &Function{UniqueName: name} + f.Append(v) + return v +} + +// AppendBytes adds a new byte slice value to the last block. +func (f *IR) AppendBytes(s []byte) *Bytes { + v := &Bytes{Bytes: s} + f.Append(v) + return v +} + +// AppendString adds a new string value to the last block. +func (f *IR) AppendString(s string) *Bytes { + v := &Bytes{Bytes: []byte(s)} + f.Append(v) + return v +} + +// Values yields on each value. +func (f *IR) Values(yield func(Value) bool) { + for _, block := range f.Blocks { + for _, instr := range block.Instructions { + if !yield(instr) { + return + } + } + } +} \ No newline at end of file diff --git a/src/ssa/Int.go b/src/ssa/Int.go new file mode 100644 index 0000000..9ff9256 --- /dev/null +++ b/src/ssa/Int.go @@ -0,0 +1,33 @@ +package ssa + +import ( + "fmt" +) + +type Int struct { + Int int + Liveness + HasToken +} + +func (v *Int) Dependencies() []Value { + return nil +} + +func (a *Int) Equals(v Value) bool { + b, sameType := v.(*Int) + + if !sameType { + return false + } + + return a.Int == b.Int +} + +func (v *Int) IsConst() bool { + return true +} + +func (v *Int) String() string { + return fmt.Sprintf("%d", v.Int) +} \ No newline at end of file diff --git a/src/ssa/Liveness.go b/src/ssa/Liveness.go new file mode 100644 index 0000000..ca544ea --- /dev/null +++ b/src/ssa/Liveness.go @@ -0,0 +1,13 @@ +package ssa + +type Liveness struct { + alive int +} + +func (v *Liveness) AddUse(user Value) { + v.alive++ +} + +func (v *Liveness) Alive() int { + return v.alive +} \ No newline at end of file diff --git a/src/ssa/Register.go b/src/ssa/Register.go new file mode 100644 index 0000000..2ae96f4 --- /dev/null +++ b/src/ssa/Register.go @@ -0,0 +1,31 @@ +package ssa + +import "git.urbach.dev/cli/q/src/cpu" + +type Register struct { + Register cpu.Register + Liveness + HasToken +} + +func (v *Register) Dependencies() []Value { + return nil +} + +func (a *Register) Equals(v Value) bool { + b, sameType := v.(*Register) + + if !sameType { + return false + } + + return a.Register == b.Register +} + +func (v *Register) IsConst() bool { + return true +} + +func (v *Register) String() string { + return v.Register.String() +} \ No newline at end of file diff --git a/src/ssa/Return.go b/src/ssa/Return.go new file mode 100644 index 0000000..9472f23 --- /dev/null +++ b/src/ssa/Return.go @@ -0,0 +1,39 @@ +package ssa + +import "fmt" + +type Return struct { + Arguments + HasToken +} + +func (a *Return) AddUse(user Value) { panic("return is not a value") } +func (a *Return) Alive() int { return 0 } + +func (a *Return) Equals(v Value) bool { + b, sameType := v.(*Return) + + if !sameType { + return false + } + + if len(a.Args) != len(b.Args) { + return false + } + + for i := range a.Args { + if !a.Args[i].Equals(b.Args[i]) { + return false + } + } + + return true +} + +func (v *Return) IsConst() bool { + return false +} + +func (v *Return) String() string { + return fmt.Sprintf("return %v", v.Args) +} \ No newline at end of file diff --git a/src/ssa/Syscall.go b/src/ssa/Syscall.go new file mode 100644 index 0000000..525b6bc --- /dev/null +++ b/src/ssa/Syscall.go @@ -0,0 +1,27 @@ +package ssa + +import "fmt" + +type Syscall struct { + Arguments + Liveness + HasToken +} + +func (a *Syscall) Equals(v Value) bool { + b, sameType := v.(*Syscall) + + if !sameType { + return false + } + + return a.Arguments.Equals(b.Arguments) +} + +func (v *Syscall) IsConst() bool { + return false +} + +func (v *Syscall) String() string { + return fmt.Sprintf("syscall%v", v.Args) +} \ No newline at end of file diff --git a/src/ssa/Type.go b/src/ssa/Type.go deleted file mode 100644 index 347ae3d..0000000 --- a/src/ssa/Type.go +++ /dev/null @@ -1,39 +0,0 @@ -package ssa - -// Type represents the instruction type. -type Type byte - -const ( - None Type = iota - - // Values - Int - Float - Func - Register - String - - // Binary - Add - Sub - Mul - Div - Mod - - // Bitwise - And - Or - Xor - Shl - Shr - - // Control flow - If - Jump - Call - Return - Syscall - - // Special - Phi -) \ No newline at end of file diff --git a/src/ssa/Value.go b/src/ssa/Value.go index a562949..0cfacbf 100644 --- a/src/ssa/Value.go +++ b/src/ssa/Value.go @@ -1,83 +1,13 @@ package ssa -import ( - "fmt" +import "git.urbach.dev/cli/q/src/token" - "git.urbach.dev/cli/q/src/cpu" -) - -// Value is a single instruction in a basic block. -// It is implemented as a "fat struct" for performance reasons. -// It contains all the fields necessary to represent all instruction types. -type Value struct { - Args []*Value - Int int - Text string - Register cpu.Register - Type Type -} - -// Equals returns true if the values are equal. -func (a Value) Equals(b Value) bool { - if a.Type != b.Type { - return false - } - - if a.Int != b.Int { - return false - } - - if a.Text != b.Text { - return false - } - - if a.Register != b.Register { - return false - } - - if len(a.Args) != len(b.Args) { - return false - } - - for i := range a.Args { - if !a.Args[i].Equals(*b.Args[i]) { - return false - } - } - - return true -} - -// IsConst returns true if the value is constant. -func (i *Value) IsConst() bool { - switch i.Type { - case Func, Int, Register, String: - return true - default: - return false - } -} - -// String returns a human-readable representation of the instruction. -func (i *Value) String() string { - switch i.Type { - case Func: - return i.Text - case Int: - return fmt.Sprintf("%d", i.Int) - case Register: - return i.Register.String() - case String: - return fmt.Sprintf("\"%s\"", i.Text) - case Add: - return fmt.Sprintf("%s + %s", i.Args[0], i.Args[1]) - case Return: - return fmt.Sprintf("return %s", i.Args[0]) - case Call: - return fmt.Sprintf("call%v", i.Args) - case Syscall: - return fmt.Sprintf("syscall%v", i.Args) - default: - return "" - } +type Value interface { + AddUse(Value) + Alive() int + Dependencies() []Value + Equals(Value) bool + IsConst() bool + String() string + Token() token.Token } \ No newline at end of file diff --git a/src/ssa/bench_test.go b/src/ssa/bench_test.go index d959299..6c64485 100644 --- a/src/ssa/bench_test.go +++ b/src/ssa/bench_test.go @@ -5,12 +5,16 @@ import ( "runtime/debug" "testing" + "git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/go/assert" ) // This benchmark compares the performance of fat structs and interfaces. -// It allocates `n` objects where `n` must be divisible by 2. -const n = 100 +// It allocates `actual` objects where `actual` must be divisible by 2. +const ( + actual = 64 + estimate = 8 +) type FatStruct struct { Type byte @@ -45,9 +49,9 @@ func TestMain(m *testing.M) { func BenchmarkFatStructRaw(b *testing.B) { for b.Loop() { - entries := make([]FatStruct, 0, n) + entries := make([]FatStruct, 0, estimate) - for i := range n { + for i := range actual { entries = append(entries, FatStruct{ Type: byte(i % 2), A: i, @@ -65,15 +69,15 @@ func BenchmarkFatStructRaw(b *testing.B) { } } - assert.Equal(b, count, n/2) + assert.Equal(b, count, actual/2) } } func BenchmarkFatStructPtr(b *testing.B) { for b.Loop() { - entries := make([]*FatStruct, 0, n) + entries := make([]*FatStruct, 0, estimate) - for i := range n { + for i := range actual { entries = append(entries, &FatStruct{ Type: byte(i % 2), A: i, @@ -91,15 +95,15 @@ func BenchmarkFatStructPtr(b *testing.B) { } } - assert.Equal(b, count, n/2) + assert.Equal(b, count, actual/2) } } func BenchmarkInterfaceRaw(b *testing.B) { for b.Loop() { - entries := make([]Instruction, 0, n) + entries := make([]Instruction, 0, estimate) - for i := range n { + for i := range actual { if i%2 == 0 { entries = append(entries, BinaryInstruction{ A: i, @@ -123,15 +127,15 @@ func BenchmarkInterfaceRaw(b *testing.B) { } } - assert.Equal(b, count, n/2) + assert.Equal(b, count, actual/2) } } func BenchmarkInterfacePtr(b *testing.B) { for b.Loop() { - entries := make([]Instruction, 0, n) + entries := make([]Instruction, 0, estimate) - for i := range n { + for i := range actual { if i%2 == 0 { entries = append(entries, &BinaryInstruction{ A: i, @@ -155,6 +159,32 @@ func BenchmarkInterfacePtr(b *testing.B) { } } - assert.Equal(b, count, n/2) + assert.Equal(b, count, actual/2) + } +} + +func BenchmarkSSA(b *testing.B) { + for b.Loop() { + f := ssa.IR{} + + for i := range actual { + if i%2 == 0 { + f.Append(&ssa.Return{}) + } else { + f.Append(&ssa.Call{}) + } + } + + count := 0 + + for instr := range f.Values { + switch instr.(type) { + case *ssa.Return: + count++ + case *ssa.Call: + } + } + + assert.Equal(b, count, actual/2) } } \ No newline at end of file diff --git a/src/ssa/ssa_test.go b/src/ssa/ssa_test.go index d8ecb3b..09780e3 100644 --- a/src/ssa/ssa_test.go +++ b/src/ssa/ssa_test.go @@ -4,23 +4,19 @@ import ( "testing" "git.urbach.dev/cli/q/src/ssa" + "git.urbach.dev/cli/q/src/token" "git.urbach.dev/go/assert" ) func TestFunction(t *testing.T) { - fn := ssa.Function{} + fn := ssa.IR{} a := fn.AppendInt(1) b := fn.AppendInt(2) - c := fn.Append(ssa.Value{Type: ssa.Add, Args: []*ssa.Value{a, b}}) + c := fn.Append(&ssa.BinaryOperation{Op: token.Add, Left: a, Right: b}) fn.AddBlock() d := fn.AppendInt(3) e := fn.AppendInt(4) - f := fn.Append(ssa.Value{Type: ssa.Add, Args: []*ssa.Value{d, e}}) + f := fn.Append(&ssa.BinaryOperation{Op: token.Add, Left: d, Right: e}) assert.Equal(t, c.String(), "1 + 2") assert.Equal(t, f.String(), "3 + 4") -} - -func TestInvalidInstruction(t *testing.T) { - instr := ssa.Value{} - assert.Equal(t, instr.String(), "") } \ No newline at end of file