Implemented constant folding

This commit is contained in:
Eduard Urbach 2024-07-29 14:44:16 +02:00
parent 8d4eb9935d
commit 5b1d456720
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
8 changed files with 118 additions and 1 deletions

View File

@ -15,6 +15,15 @@ const (
) )
var ( var (
// Shows the assembly instructions at the end of the compilation.
Assembler = false 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
) )

View File

@ -8,21 +8,27 @@ import (
func (f *Function) CompileASTNode(node ast.Node) error { func (f *Function) CompileASTNode(node ast.Node) error {
switch node := node.(type) { switch node := node.(type) {
case *ast.Assert: case *ast.Assert:
f.Fold(node.Condition)
return f.CompileAssert(node) return f.CompileAssert(node)
case *ast.Assign: case *ast.Assign:
f.Fold(node.Expression)
return f.CompileAssign(node) return f.CompileAssign(node)
case *ast.Call: case *ast.Call:
f.Fold(node.Expression)
return f.CompileCall(node.Expression) return f.CompileCall(node.Expression)
case *ast.Define: case *ast.Define:
f.Fold(node.Expression)
return f.CompileDefinition(node) return f.CompileDefinition(node)
case *ast.Return: case *ast.Return:
f.Fold(node.Value)
return f.CompileReturn(node) return f.CompileReturn(node)
case *ast.If: case *ast.If:
f.Fold(node.Condition)
return f.CompileIf(node) return f.CompileIf(node)
case *ast.Loop: case *ast.Loop:

View File

@ -5,10 +5,15 @@ 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"
) )
// CompileAssert compiles an assertion. // CompileAssert compiles an assertion.
func (f *Function) CompileAssert(assert *ast.Assert) error { func (f *Function) CompileAssert(assert *ast.Assert) error {
if config.SkipAsserts {
return nil
}
f.count.assert++ f.count.assert++
success := fmt.Sprintf("%s_assert_%d_true", f.Name, 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) fail := fmt.Sprintf("%s_assert_%d_false", f.Name, f.count.assert)

View File

@ -10,6 +10,11 @@ import (
// ExpressionToRegister puts the result of an expression into the specified register. // ExpressionToRegister puts the result of an expression into the specified register.
func (f *Function) ExpressionToRegister(node *expression.Expression, register cpu.Register) error { 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() { if node.IsLeaf() {
return f.TokenToRegister(node.Token, register) return f.TokenToRegister(node.Token, register)
} }

74
src/build/core/Fold.go Normal file
View 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
}

View File

@ -12,6 +12,8 @@ type Expression struct {
Children []*Expression Children []*Expression
Token token.Token Token token.Token
Precedence int8 Precedence int8
Value int
IsFolded bool
} }
// New creates a new expression. // New creates a new expression.

View File

@ -38,6 +38,11 @@ func (t Token) IsKeyword() bool {
return t.Kind > _keywords && t.Kind < _keywordsEnd 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. // IsOperator returns true if the token is an operator.
func (t Token) IsOperator() bool { func (t Token) IsOperator() bool {
return t.Kind > _operators && t.Kind < _operatorsEnd return t.Kind > _operators && t.Kind < _operatorsEnd

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"git.akyoto.dev/cli/q/src/build" "git.akyoto.dev/cli/q/src/build"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/go/assert" "git.akyoto.dev/go/assert"
) )
@ -52,11 +53,21 @@ var programs = []struct {
} }
func TestPrograms(t *testing.T) { func TestPrograms(t *testing.T) {
config.ConstantFold = false
for _, test := range programs { for _, test := range programs {
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
run(t, filepath.Join("programs", test.Name+".q"), test.Input, test.Output, test.ExitCode) 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) { func BenchmarkPrograms(b *testing.B) {