diff --git a/src/build/config/config.go b/src/build/config/config.go index 99791d2..7ff0e87 100644 --- a/src/build/config/config.go +++ b/src/build/config/config.go @@ -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 ) diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go index 16cb0bd..eac1e31 100644 --- a/src/build/core/CompileASTNode.go +++ b/src/build/core/CompileASTNode.go @@ -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: diff --git a/src/build/core/CompileAssert.go b/src/build/core/CompileAssert.go index 2715e44..05cc38c 100644 --- a/src/build/core/CompileAssert.go +++ b/src/build/core/CompileAssert.go @@ -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) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 4c1d69c..a49264f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -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) } diff --git a/src/build/core/Fold.go b/src/build/core/Fold.go new file mode 100644 index 0000000..11b6d59 --- /dev/null +++ b/src/build/core/Fold.go @@ -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 +} diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 7b39b12..057476b 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -12,6 +12,8 @@ type Expression struct { Children []*Expression Token token.Token Precedence int8 + Value int + IsFolded bool } // New creates a new expression. diff --git a/src/build/token/Token.go b/src/build/token/Token.go index caac815..3885218 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -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 diff --git a/tests/programs_test.go b/tests/programs_test.go index 2bead24..429695f 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -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) {