Implemented constant folding
This commit is contained in:
parent
8d4eb9935d
commit
5b1d456720
@ -15,6 +15,15 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// Shows the assembly instructions at the end of the compilation.
|
||||
Assembler = false
|
||||
Dry = false
|
||||
|
||||
// Calculates the result of operations on constants at compile time.
|
||||
ConstantFold = true
|
||||
|
||||
// Skips writing the executable to disk.
|
||||
Dry = false
|
||||
|
||||
// Skips compiling assert statements.
|
||||
SkipAsserts = false
|
||||
)
|
||||
|
@ -8,21 +8,27 @@ import (
|
||||
func (f *Function) CompileASTNode(node ast.Node) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.Assert:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileAssert(node)
|
||||
|
||||
case *ast.Assign:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileAssign(node)
|
||||
|
||||
case *ast.Call:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileCall(node.Expression)
|
||||
|
||||
case *ast.Define:
|
||||
f.Fold(node.Expression)
|
||||
return f.CompileDefinition(node)
|
||||
|
||||
case *ast.Return:
|
||||
f.Fold(node.Value)
|
||||
return f.CompileReturn(node)
|
||||
|
||||
case *ast.If:
|
||||
f.Fold(node.Condition)
|
||||
return f.CompileIf(node)
|
||||
|
||||
case *ast.Loop:
|
||||
|
@ -5,10 +5,15 @@ 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"
|
||||
)
|
||||
|
||||
// CompileAssert compiles an assertion.
|
||||
func (f *Function) CompileAssert(assert *ast.Assert) error {
|
||||
if config.SkipAsserts {
|
||||
return nil
|
||||
}
|
||||
|
||||
f.count.assert++
|
||||
success := fmt.Sprintf("%s_assert_%d_true", f.Name, f.count.assert)
|
||||
fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert)
|
||||
|
@ -10,6 +10,11 @@ import (
|
||||
|
||||
// ExpressionToRegister puts the result of an expression into the specified register.
|
||||
func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error {
|
||||
if node.IsFolded {
|
||||
f.RegisterNumber(asm.MOVE, register, node.Value)
|
||||
return nil
|
||||
}
|
||||
|
||||
if node.IsLeaf() {
|
||||
return f.TokenToRegister(node.Token, register)
|
||||
}
|
||||
|
74
src/build/core/Fold.go
Normal file
74
src/build/core/Fold.go
Normal file
@ -0,0 +1,74 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
"git.akyoto.dev/cli/q/src/build/expression"
|
||||
"git.akyoto.dev/cli/q/src/build/token"
|
||||
)
|
||||
|
||||
// Fold will try to precalculate the results of operations with constants.
|
||||
func (f *Function) Fold(expr *expression.Expression) error {
|
||||
if !config.ConstantFold {
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if expr.IsLeaf() {
|
||||
if expr.Token.IsNumeric() {
|
||||
value, err := f.Number(expr.Token)
|
||||
expr.Value = value
|
||||
expr.IsFolded = true
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
canFold := true
|
||||
|
||||
for _, child := range expr.Children {
|
||||
f.Fold(child)
|
||||
|
||||
if !child.IsFolded {
|
||||
canFold = false
|
||||
}
|
||||
}
|
||||
|
||||
if !canFold || len(expr.Children) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
a := expr.Children[0].Value
|
||||
b := expr.Children[1].Value
|
||||
|
||||
switch expr.Token.Kind {
|
||||
case token.Or:
|
||||
expr.Value = a | b
|
||||
case token.And:
|
||||
expr.Value = a & b
|
||||
case token.Xor:
|
||||
expr.Value = a ^ b
|
||||
case token.Add:
|
||||
expr.Value = a + b
|
||||
case token.Sub:
|
||||
expr.Value = a - b
|
||||
case token.Mul:
|
||||
expr.Value = a * b
|
||||
case token.Div:
|
||||
expr.Value = a / b
|
||||
case token.Mod:
|
||||
expr.Value = a % b
|
||||
case token.Shl:
|
||||
expr.Value = a << b
|
||||
case token.Shr:
|
||||
expr.Value = a >> b
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
expr.IsFolded = true
|
||||
return nil
|
||||
}
|
@ -12,6 +12,8 @@ type Expression struct {
|
||||
Children []*Expression
|
||||
Token token.Token
|
||||
Precedence int8
|
||||
Value int
|
||||
IsFolded bool
|
||||
}
|
||||
|
||||
// New creates a new expression.
|
||||
|
@ -38,6 +38,11 @@ func (t Token) IsKeyword() bool {
|
||||
return t.Kind > _keywords && t.Kind < _keywordsEnd
|
||||
}
|
||||
|
||||
// IsNumeric returns true if the token is a number or rune.
|
||||
func (t Token) IsNumeric() bool {
|
||||
return t.Kind == Number || t.Kind == Rune
|
||||
}
|
||||
|
||||
// IsOperator returns true if the token is an operator.
|
||||
func (t Token) IsOperator() bool {
|
||||
return t.Kind > _operators && t.Kind < _operatorsEnd
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build"
|
||||
"git.akyoto.dev/cli/q/src/build/config"
|
||||
"git.akyoto.dev/go/assert"
|
||||
)
|
||||
|
||||
@ -52,11 +53,21 @@ var programs = []struct {
|
||||
}
|
||||
|
||||
func TestPrograms(t *testing.T) {
|
||||
config.ConstantFold = false
|
||||
|
||||
for _, test := range programs {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode)
|
||||
})
|
||||
}
|
||||
|
||||
config.ConstantFold = true
|
||||
|
||||
for _, test := range programs {
|
||||
t.Run(test.Name+" (optimized)", func(t *testing.T) {
|
||||
run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrograms(b *testing.B) {
|
||||
|
Loading…
Reference in New Issue
Block a user