Improved SSA and added unused value checks
All checks were successful
/ test (push) Successful in 17s
All checks were successful
/ test (push) Successful in 17s
This commit is contained in:
parent
cc2e98ca49
commit
2b703e9af2
25 changed files with 519 additions and 213 deletions
16
src/core/CheckDeadCode.go
Normal file
16
src/core/CheckDeadCode.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -26,4 +26,6 @@ func (f *Function) Compile() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.Err = f.CheckDeadCode()
|
||||||
}
|
}
|
|
@ -15,9 +15,10 @@ func (f *Function) CompileReturn(tokens token.List) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f.Append(ssa.Value{
|
f.Append(&ssa.Return{
|
||||||
Type: ssa.Return,
|
Arguments: ssa.Arguments{
|
||||||
Args: []*ssa.Value{value},
|
Args: []ssa.Value{value},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Evaluate converts an expression to an SSA value.
|
// 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() {
|
if expr.IsLeaf() {
|
||||||
switch expr.Token.Kind {
|
switch expr.Token.Kind {
|
||||||
case token.Identifier:
|
case token.Identifier:
|
||||||
|
@ -30,12 +30,16 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.AppendInt(number), nil
|
v := f.AppendInt(number)
|
||||||
|
v.Source = expr.Token
|
||||||
|
return v, nil
|
||||||
|
|
||||||
case token.String:
|
case token.String:
|
||||||
data := expr.Token.Bytes(f.File.Bytes)
|
data := expr.Token.Bytes(f.File.Bytes)
|
||||||
data = Unescape(data)
|
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)
|
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 {
|
switch expr.Token.Kind {
|
||||||
case token.Call:
|
case token.Call:
|
||||||
children := expr.Children
|
children := expr.Children
|
||||||
typ := ssa.Call
|
isSyscall := false
|
||||||
|
|
||||||
if children[0].Token.Kind == token.Identifier {
|
if children[0].Token.Kind == token.Identifier {
|
||||||
funcName := children[0].String(f.File.Bytes)
|
funcName := children[0].String(f.File.Bytes)
|
||||||
|
@ -56,11 +60,11 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) {
|
||||||
|
|
||||||
if funcName == "syscall" {
|
if funcName == "syscall" {
|
||||||
children = children[1:]
|
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 {
|
for i, child := range children {
|
||||||
value, err := f.Evaluate(child)
|
value, err := f.Evaluate(child)
|
||||||
|
@ -72,12 +76,27 @@ func (f *Function) Evaluate(expr *expression.Expression) (*ssa.Value, error) {
|
||||||
args[i] = value
|
args[i] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
call := f.Append(ssa.Value{Type: typ, Args: args})
|
if isSyscall {
|
||||||
return call, nil
|
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:
|
case token.Dot:
|
||||||
name := fmt.Sprintf("%s.%s", expr.Children[0].String(f.File.Bytes), expr.Children[1].String(f.File.Bytes))
|
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
|
return nil, nil
|
||||||
|
|
|
@ -10,14 +10,14 @@ import (
|
||||||
|
|
||||||
// Function is the smallest unit of code.
|
// Function is the smallest unit of code.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
ssa.Function
|
ssa.IR
|
||||||
Name string
|
Name string
|
||||||
UniqueName string
|
UniqueName string
|
||||||
File *fs.File
|
File *fs.File
|
||||||
Input []*Parameter
|
Input []*Parameter
|
||||||
Output []*Parameter
|
Output []*Parameter
|
||||||
Body token.List
|
Body token.List
|
||||||
Identifiers map[string]*ssa.Value
|
Identifiers map[string]ssa.Value
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,11 @@ func NewFunction(name string, file *fs.File) *Function {
|
||||||
Name: name,
|
Name: name,
|
||||||
File: file,
|
File: file,
|
||||||
UniqueName: fmt.Sprintf("%s.%s", file.Package, name),
|
UniqueName: fmt.Sprintf("%s.%s", file.Package, name),
|
||||||
Identifiers: make(map[string]*ssa.Value),
|
Identifiers: make(map[string]ssa.Value),
|
||||||
Function: ssa.Function{
|
IR: ssa.IR{
|
||||||
Blocks: []*ssa.Block{{}},
|
Blocks: []*ssa.Block{
|
||||||
|
{Instructions: make([]ssa.Value, 0, 8)},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,12 @@ func (err *UnknownIdentifier) Error() string {
|
||||||
|
|
||||||
return fmt.Sprintf("Unknown identifier '%s'", err.Name)
|
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)
|
||||||
|
}
|
|
@ -13,9 +13,9 @@ type operator struct {
|
||||||
Operands int8
|
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.
|
// 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.Dot: {".", 13, 2},
|
||||||
token.Call: {"λ", 12, 1},
|
token.Call: {"λ", 12, 1},
|
||||||
token.Array: {"@", 12, 2},
|
token.Array: {"@", 12, 2},
|
||||||
|
@ -59,9 +59,9 @@ var operators = [64]operator{
|
||||||
}
|
}
|
||||||
|
|
||||||
func numOperands(symbol token.Kind) int {
|
func numOperands(symbol token.Kind) int {
|
||||||
return int(operators[symbol].Operands)
|
return int(Operators[symbol].Operands)
|
||||||
}
|
}
|
||||||
|
|
||||||
func precedence(symbol token.Kind) int8 {
|
func precedence(symbol token.Kind) int8 {
|
||||||
return operators[symbol].Precedence
|
return Operators[symbol].Precedence
|
||||||
}
|
}
|
|
@ -17,9 +17,9 @@ func (expr *Expression) write(builder *strings.Builder, source []byte) {
|
||||||
|
|
||||||
switch expr.Token.Kind {
|
switch expr.Token.Kind {
|
||||||
case token.Call:
|
case token.Call:
|
||||||
builder.WriteString(operators[token.Call].Symbol)
|
builder.WriteString(Operators[token.Call].Symbol)
|
||||||
case token.Array:
|
case token.Array:
|
||||||
builder.WriteString(operators[token.Array].Symbol)
|
builder.WriteString(Operators[token.Array].Symbol)
|
||||||
default:
|
default:
|
||||||
builder.WriteString(expr.Token.String(source))
|
builder.WriteString(expr.Token.String(source))
|
||||||
}
|
}
|
||||||
|
|
23
src/ssa/Arguments.go
Normal file
23
src/ssa/Arguments.go
Normal file
|
@ -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
|
||||||
|
}
|
50
src/ssa/BinaryOperation.go
Normal file
50
src/ssa/BinaryOperation.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -6,7 +6,11 @@ type Block struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append adds a new instruction to the block.
|
// Append adds a new instruction to the block.
|
||||||
func (b *Block) Append(instr Value) *Value {
|
func (block *Block) Append(instr Value) Value {
|
||||||
b.Instructions = append(b.Instructions, instr)
|
for _, dep := range instr.Dependencies() {
|
||||||
return &b.Instructions[len(b.Instructions)-1]
|
dep.AddUse(instr)
|
||||||
|
}
|
||||||
|
|
||||||
|
block.Instructions = append(block.Instructions, instr)
|
||||||
|
return instr
|
||||||
}
|
}
|
31
src/ssa/Bytes.go
Normal file
31
src/ssa/Bytes.go
Normal file
|
@ -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)
|
||||||
|
}
|
27
src/ssa/Call.go
Normal file
27
src/ssa/Call.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -1,61 +1,29 @@
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import (
|
|
||||||
"git.urbach.dev/cli/q/src/cpu"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Function is a list of basic blocks.
|
|
||||||
type Function struct {
|
type Function struct {
|
||||||
Blocks []*Block
|
UniqueName string
|
||||||
|
Liveness
|
||||||
|
HasToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBlock adds a new block to the function.
|
func (v *Function) Dependencies() []Value {
|
||||||
func (f *Function) AddBlock() *Block {
|
return nil
|
||||||
block := &Block{}
|
|
||||||
f.Blocks = append(f.Blocks, block)
|
|
||||||
return block
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append adds a new value to the last block.
|
func (a *Function) Equals(v Value) bool {
|
||||||
func (f *Function) Append(instr Value) *Value {
|
b, sameType := v.(*Function)
|
||||||
if len(f.Blocks) == 0 {
|
|
||||||
f.Blocks = append(f.Blocks, &Block{})
|
if !sameType {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if instr.IsConst() {
|
return a.UniqueName == b.UniqueName
|
||||||
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)
|
func (v *Function) IsConst() bool {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendInt adds a new integer value to the last block.
|
func (v *Function) String() string {
|
||||||
func (f *Function) AppendInt(x int) *Value {
|
return v.UniqueName
|
||||||
return f.Append(Value{Type: Int, Int: x})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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})
|
|
||||||
}
|
}
|
11
src/ssa/HasToken.go
Normal file
11
src/ssa/HasToken.go
Normal file
|
@ -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
|
||||||
|
}
|
83
src/ssa/IR.go
Normal file
83
src/ssa/IR.go
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
src/ssa/Int.go
Normal file
33
src/ssa/Int.go
Normal file
|
@ -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)
|
||||||
|
}
|
13
src/ssa/Liveness.go
Normal file
13
src/ssa/Liveness.go
Normal file
|
@ -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
|
||||||
|
}
|
31
src/ssa/Register.go
Normal file
31
src/ssa/Register.go
Normal file
|
@ -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()
|
||||||
|
}
|
39
src/ssa/Return.go
Normal file
39
src/ssa/Return.go
Normal file
|
@ -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)
|
||||||
|
}
|
27
src/ssa/Syscall.go
Normal file
27
src/ssa/Syscall.go
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
|
||||||
)
|
|
|
@ -1,83 +1,13 @@
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import (
|
import "git.urbach.dev/cli/q/src/token"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/cpu"
|
type Value interface {
|
||||||
)
|
AddUse(Value)
|
||||||
|
Alive() int
|
||||||
// Value is a single instruction in a basic block.
|
Dependencies() []Value
|
||||||
// It is implemented as a "fat struct" for performance reasons.
|
Equals(Value) bool
|
||||||
// It contains all the fields necessary to represent all instruction types.
|
IsConst() bool
|
||||||
type Value struct {
|
String() string
|
||||||
Args []*Value
|
Token() token.Token
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,12 +5,16 @@ import (
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.urbach.dev/cli/q/src/ssa"
|
||||||
"git.urbach.dev/go/assert"
|
"git.urbach.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This benchmark compares the performance of fat structs and interfaces.
|
// This benchmark compares the performance of fat structs and interfaces.
|
||||||
// It allocates `n` objects where `n` must be divisible by 2.
|
// It allocates `actual` objects where `actual` must be divisible by 2.
|
||||||
const n = 100
|
const (
|
||||||
|
actual = 64
|
||||||
|
estimate = 8
|
||||||
|
)
|
||||||
|
|
||||||
type FatStruct struct {
|
type FatStruct struct {
|
||||||
Type byte
|
Type byte
|
||||||
|
@ -45,9 +49,9 @@ func TestMain(m *testing.M) {
|
||||||
|
|
||||||
func BenchmarkFatStructRaw(b *testing.B) {
|
func BenchmarkFatStructRaw(b *testing.B) {
|
||||||
for b.Loop() {
|
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{
|
entries = append(entries, FatStruct{
|
||||||
Type: byte(i % 2),
|
Type: byte(i % 2),
|
||||||
A: i,
|
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) {
|
func BenchmarkFatStructPtr(b *testing.B) {
|
||||||
for b.Loop() {
|
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{
|
entries = append(entries, &FatStruct{
|
||||||
Type: byte(i % 2),
|
Type: byte(i % 2),
|
||||||
A: i,
|
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) {
|
func BenchmarkInterfaceRaw(b *testing.B) {
|
||||||
for b.Loop() {
|
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 {
|
if i%2 == 0 {
|
||||||
entries = append(entries, BinaryInstruction{
|
entries = append(entries, BinaryInstruction{
|
||||||
A: i,
|
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) {
|
func BenchmarkInterfacePtr(b *testing.B) {
|
||||||
for b.Loop() {
|
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 {
|
if i%2 == 0 {
|
||||||
entries = append(entries, &BinaryInstruction{
|
entries = append(entries, &BinaryInstruction{
|
||||||
A: i,
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,23 +4,19 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.urbach.dev/cli/q/src/ssa"
|
"git.urbach.dev/cli/q/src/ssa"
|
||||||
|
"git.urbach.dev/cli/q/src/token"
|
||||||
"git.urbach.dev/go/assert"
|
"git.urbach.dev/go/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFunction(t *testing.T) {
|
func TestFunction(t *testing.T) {
|
||||||
fn := ssa.Function{}
|
fn := ssa.IR{}
|
||||||
a := fn.AppendInt(1)
|
a := fn.AppendInt(1)
|
||||||
b := fn.AppendInt(2)
|
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()
|
fn.AddBlock()
|
||||||
d := fn.AppendInt(3)
|
d := fn.AppendInt(3)
|
||||||
e := fn.AppendInt(4)
|
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, c.String(), "1 + 2")
|
||||||
assert.Equal(t, f.String(), "3 + 4")
|
assert.Equal(t, f.String(), "3 + 4")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidInstruction(t *testing.T) {
|
|
||||||
instr := ssa.Value{}
|
|
||||||
assert.Equal(t, instr.String(), "")
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue