diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index f05a62c37..4e5b55ad6 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -4,8 +4,9 @@ import java.nio.file.Paths import il65.ast.* import il65.parser.* import il65.compiler.* -import il65.optimizing.optimizeExpressions +import il65.optimizing.constantFold import il65.optimizing.optimizeStatements +import il65.optimizing.simplifyExpressions import java.io.File import kotlin.system.exitProcess @@ -23,9 +24,10 @@ fun main(args: Array) { try { // import main module and process additional imports + println("Parsing...") val moduleAst = importModule(filepath) moduleAst.linkParents() - val globalNameSpaceBeforeOptimization = moduleAst.definingScope() + var namespace = moduleAst.definingScope() // determine special compiler options @@ -46,16 +48,25 @@ fun main(args: Array) { // perform syntax checks and optimizations moduleAst.checkIdentifiers() - moduleAst.optimizeExpressions(globalNameSpaceBeforeOptimization) - moduleAst.checkValid(globalNameSpaceBeforeOptimization, compilerOptions) // check if tree is valid - val allScopedSymbolDefinitions = moduleAst.checkIdentifiers() - moduleAst.optimizeStatements(globalNameSpaceBeforeOptimization, allScopedSymbolDefinitions) - StatementReorderer().process(moduleAst) // reorder statements to please the compiler later - val globalNamespaceAfterOptimize = moduleAst.definingScope() // create it again, it could have changed in the meantime - moduleAst.checkValid(globalNamespaceAfterOptimize, compilerOptions) // check if final tree is valid - moduleAst.checkRecursion(globalNamespaceAfterOptimize) // check if there are recursive subroutine calls - // globalNamespaceAfterOptimize.debugPrint() + println("Optimizing...") + moduleAst.constantFold(namespace) + moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid + val allScopedSymbolDefinitions = moduleAst.checkIdentifiers() + while(true) { + // keep optimizing expressions and statements until no more steps remain + val optsDone1 = moduleAst.simplifyExpressions(namespace) + val optsDone2 = moduleAst.optimizeStatements(namespace, allScopedSymbolDefinitions) + if(optsDone1 + optsDone2 == 0) + break + } + + StatementReorderer().process(moduleAst) // reorder statements to please the compiler later + namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime + moduleAst.checkValid(namespace, compilerOptions) // check if final tree is valid + moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls + + // namespace.debugPrint() // compile the syntax tree into stackvmProg form, and optimize that val compiler = Compiler(compilerOptions) diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index ce9a39b28..e2b1169f0 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -225,19 +225,6 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return super.process(decl) } - /** - * check if condition - */ - override fun process(ifStatement: IfStatement): IStatement { - val constvalue = ifStatement.condition.constValue(namespace) - if(constvalue!=null) { - val msg = if (constvalue.asBooleanValue) "condition is always true" else "condition is always false" - println("${ifStatement.position} Warning: $msg") - } - - return super.process(ifStatement) - } - /** * check the arguments of the directive */ @@ -452,7 +439,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: if(av.register!=Register.A && av.register!=Register.X && av.register!=Register.Y) return err("register '$av' in byte array is not a single register") } else { - TODO("array value $av") + TODO("check array value $av") } } diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index 641d7bee7..96efda972 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -88,7 +88,7 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va class Compiler(private val options: CompilationOptions) { fun compile(module: Module) : StackVmProgram { - println("\nCompiling parsed source code to stackvmProg code...") + println("\nCreating stackVM code...") val namespace = module.definingScope() @@ -423,7 +423,7 @@ class StackVmProgram(val name: String) { } fun optimize() { - println("\nOptimizing stackvmProg code...") + println("\nOptimizing stackVM code...") // todo optimize stackvm code } diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ConstExprEvaluator.kt similarity index 58% rename from il65/src/il65/optimizing/ExpressionOptimizer.kt rename to il65/src/il65/optimizing/ConstExprEvaluator.kt index dff6f8b68..26ee6587a 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ConstExprEvaluator.kt @@ -1,269 +1,8 @@ package il65.optimizing -import il65.parser.ParsingFailedError import il65.ast.* -import il65.compiler.Petscii import kotlin.math.pow - -fun Module.optimizeExpressions(globalNamespace: INameScope) { - val optimizer = ExpressionOptimizer(globalNamespace) - try { - this.process(optimizer) - } catch (ax: AstException) { - optimizer.addError(ax) - } - - if(optimizer.optimizationsDone==0) - println("[${this.name}] 0 expression optimizations performed") - - while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) { - println("[${this.name}] ${optimizer.optimizationsDone} expression optimizations performed") - optimizer.optimizationsDone = 0 - this.process(optimizer) - } - - if(optimizer.errors.isNotEmpty()) { - optimizer.errors.forEach { System.err.println(it) } - throw ParsingFailedError("There are ${optimizer.errors.size} errors.") - } else { - this.linkParents() // re-link in final configuration - } -} - - - -/* - todo eliminate useless terms: - *0 -> constant 0 - X*1, X/1, X//1 -> just X - X*-1 -> unary prefix -X - X**0 -> 1 - X**1 -> X - X**-1 -> 1.0/X - X << 0 -> X - X | 0 -> X - x & 0 -> 0 - X ^ 0 -> X - - todo expression optimization: remove redundant builtin function calls - todo expression optimization: reduce expression nesting / flattening of parenthesis - todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0) - todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2) - todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call) - - */ -class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcessor { - var optimizationsDone: Int = 0 - var errors : MutableList = mutableListOf() - - private val reportedErrorMessages = mutableSetOf() - - fun addError(x: AstException) { - // check that we don't add the same error more than once - if(!reportedErrorMessages.contains(x.toString())) { - reportedErrorMessages.add(x.toString()) - errors.add(x) - } - } - - override fun process(decl: VarDecl): IStatement { - // the initializer value can't refer to the variable itself (recursive definition) - if(decl.value?.referencesIdentifier(decl.name) == true|| - decl.arrayspec?.x?.referencesIdentifier(decl.name) == true || - decl.arrayspec?.y?.referencesIdentifier(decl.name) == true) { - errors.add(ExpressionError("recursive var declaration", decl.position)) - return decl - } - - val result = super.process(decl) - - if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) { - when { - decl.datatype == DataType.FLOAT -> { - // vardecl: for float vars, promote constant integer initialization values to floats - val literal = decl.value as? LiteralValue - if (literal != null && (literal.type == DataType.BYTE || literal.type==DataType.WORD)) { - val newValue = LiteralValue(DataType.FLOAT, floatvalue = literal.asNumericValue!!.toDouble(), position = literal.position) - decl.value = newValue - } - } - decl.datatype == DataType.BYTE || decl.datatype == DataType.WORD -> { - // vardecl: for byte/word vars, convert char/string of length 1 initialization values to integer - val literal = decl.value as? LiteralValue - if (literal != null && literal.isString && literal.strvalue?.length == 1) { - val petscii = Petscii.encodePetscii(literal.strvalue)[0] - val newValue = LiteralValue(DataType.BYTE, bytevalue = petscii, position = literal.position) - decl.value = newValue - } - } - } - } - return result - } - - /** - * replace identifiers that refer to const value, with the value itself - */ - override fun process(identifier: IdentifierReference): IExpression { - return try { - identifier.constValue(globalNamespace) ?: identifier - } catch (ax: AstException) { - addError(ax) - identifier - } - } - - override fun process(functionCall: FunctionCall): IExpression { - return try { - super.process(functionCall) - functionCall.constValue(globalNamespace) ?: functionCall - } catch (ax: AstException) { - addError(ax) - functionCall - } - } - - /** - * Try to process a unary prefix expression. - * Compile-time constant sub expressions will be evaluated on the spot. - * For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 - */ - override fun process(expr: PrefixExpression): IExpression { - return try { - super.process(expr) - - val subexpr = expr.expression - if (subexpr is LiteralValue) { - // process prefixed literal values (such as -3, not true) - return when { - expr.operator == "+" -> subexpr - expr.operator == "-" -> when { - subexpr.asIntegerValue!= null -> { - optimizationsDone++ - LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position) - } - subexpr.floatvalue != null -> { - optimizationsDone++ - LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position) - } - else -> throw ExpressionError("can only take negative of int or float", subexpr.position) - } - expr.operator == "~" -> when { - subexpr.asIntegerValue != null -> { - optimizationsDone++ - LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position) - } - else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) - } - expr.operator == "not" -> when { - subexpr.asIntegerValue != null -> { - optimizationsDone++ - LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position) - } - subexpr.floatvalue != null -> { - optimizationsDone++ - LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position) - } - else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position) - } - else -> throw ExpressionError(expr.operator, subexpr.position) - } - } - return expr - } catch (ax: AstException) { - addError(ax) - expr - } - } - - /** - * Try to process a binary expression. - * Compile-time constant sub expressions will be evaluated on the spot. - * For instance, "9 * (4 + 2)" will be optimized into the integer literal 54. - */ - override fun process(expr: BinaryExpression): IExpression { - return try { - super.process(expr) - - val evaluator = ConstExprEvaluator() - val leftconst = expr.left.constValue(globalNamespace) - val rightconst = expr.right.constValue(globalNamespace) - return when { - leftconst != null && rightconst != null -> { - optimizationsDone++ - evaluator.evaluate(leftconst, expr.operator, rightconst) - } - else -> expr - } - } catch (ax: AstException) { - addError(ax) - expr - } - } - - override fun process(range: RangeExpr): IExpression { - return try { - super.process(range) - val from = range.from.constValue(globalNamespace) - val to = range.to.constValue(globalNamespace) - if (from != null && to != null) { - when { - from.type==DataType.WORD || to.type==DataType.WORD -> { - // range on word value boundaries - val rangeValue = from.asIntegerValue!!.rangeTo(to.asIntegerValue!!) - if (rangeValue.last - rangeValue.first > 65535) { - throw ExpressionError("amount of values in range exceeds 65535", range.position) - } - return LiteralValue(DataType.ARRAY_W, arrayvalue = rangeValue.map { - val v = LiteralValue(DataType.WORD, wordvalue = it, position = range.position) - v.parent = range.parent - v - }.toMutableList(), position = from.position) - } - from.type==DataType.BYTE && to.type==DataType.BYTE -> { - // range on byte value boundaries - val rangeValue = from.bytevalue!!.rangeTo(to.bytevalue!!) - if (rangeValue.last - rangeValue.first > 65535) { - throw ExpressionError("amount of values in range exceeds 65535", range.position) - } - return LiteralValue(DataType.ARRAY, arrayvalue = rangeValue.map { - val v = LiteralValue(DataType.BYTE, bytevalue = it.toShort(), position = range.position) - v.parent = range.parent - v - }.toMutableList(), position = from.position) - } - from.strvalue != null && to.strvalue != null -> { - // char range - val rangevalue = from.strvalue[0].rangeTo(to.strvalue[0]) - if (rangevalue.last - rangevalue.first > 65535) { - throw ExpressionError("amount of characters in range exceeds 65535", range.position) - } - val newval = LiteralValue(DataType.STR, strvalue = rangevalue.toList().joinToString(""), position = range.position) - newval.parent = range.parent - return newval - } - else -> AstException("range on weird datatype") - } - } - return range - } catch (ax: AstException) { - addError(ax) - range - } - } - - override fun process(literalValue: LiteralValue): LiteralValue { - if(literalValue.arrayvalue!=null) { - val newArray = literalValue.arrayvalue.map { it.process(this) } - literalValue.arrayvalue.clear() - literalValue.arrayvalue.addAll(newArray) - } - return super.process(literalValue) - } -} - - class ConstExprEvaluator { fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression { @@ -410,11 +149,11 @@ class ConstExprEvaluator { } private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue { - if(left.type==DataType.BYTE) { + if(left.type== DataType.BYTE) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position) } - } else if(left.type==DataType.WORD) { + } else if(left.type== DataType.WORD) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position) } @@ -423,11 +162,11 @@ class ConstExprEvaluator { } private fun bitwiseor(left: LiteralValue, right: LiteralValue): LiteralValue { - if(left.type==DataType.BYTE) { + if(left.type== DataType.BYTE) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position) } - } else if(left.type==DataType.WORD) { + } else if(left.type== DataType.WORD) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position) } @@ -436,11 +175,11 @@ class ConstExprEvaluator { } private fun bitwiseand(left: LiteralValue, right: LiteralValue): LiteralValue { - if(left.type==DataType.BYTE) { + if(left.type== DataType.BYTE) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.BYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position) } - } else if(left.type==DataType.WORD) { + } else if(left.type== DataType.WORD) { if(right.asIntegerValue!=null) { return LiteralValue(DataType.WORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position) } @@ -555,4 +294,4 @@ class ConstExprEvaluator { else -> throw ExpressionError(error, left.position) } } -} +} \ No newline at end of file diff --git a/il65/src/il65/optimizing/ConstantFolding.kt b/il65/src/il65/optimizing/ConstantFolding.kt new file mode 100644 index 000000000..7e145e0bf --- /dev/null +++ b/il65/src/il65/optimizing/ConstantFolding.kt @@ -0,0 +1,217 @@ +package il65.optimizing + +import il65.ast.* +import il65.compiler.Petscii + + +class ConstantFolding(private val globalNamespace: INameScope) : IAstProcessor { + var optimizationsDone: Int = 0 + var errors : MutableList = mutableListOf() + + private val reportedErrorMessages = mutableSetOf() + + fun addError(x: AstException) { + // check that we don't add the same error more than once + if(!reportedErrorMessages.contains(x.toString())) { + reportedErrorMessages.add(x.toString()) + errors.add(x) + } + } + + override fun process(decl: VarDecl): IStatement { + // the initializer value can't refer to the variable itself (recursive definition) + if(decl.value?.referencesIdentifier(decl.name) == true|| + decl.arrayspec?.x?.referencesIdentifier(decl.name) == true || + decl.arrayspec?.y?.referencesIdentifier(decl.name) == true) { + errors.add(ExpressionError("recursive var declaration", decl.position)) + return decl + } + + val result = super.process(decl) + + if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) { + when { + decl.datatype == DataType.FLOAT -> { + // vardecl: for float vars, promote constant integer initialization values to floats + val literal = decl.value as? LiteralValue + if (literal != null && (literal.type == DataType.BYTE || literal.type==DataType.WORD)) { + val newValue = LiteralValue(DataType.FLOAT, floatvalue = literal.asNumericValue!!.toDouble(), position = literal.position) + decl.value = newValue + } + } + decl.datatype == DataType.BYTE || decl.datatype == DataType.WORD -> { + // vardecl: for byte/word vars, convert char/string of length 1 initialization values to integer + val literal = decl.value as? LiteralValue + if (literal != null && literal.isString && literal.strvalue?.length == 1) { + val petscii = Petscii.encodePetscii(literal.strvalue)[0] + val newValue = LiteralValue(DataType.BYTE, bytevalue = petscii, position = literal.position) + decl.value = newValue + } + } + } + } + return result + } + + /** + * replace identifiers that refer to const value, with the value itself + */ + override fun process(identifier: IdentifierReference): IExpression { + return try { + identifier.constValue(globalNamespace) ?: identifier + } catch (ax: AstException) { + addError(ax) + identifier + } + } + + override fun process(functionCall: FunctionCall): IExpression { + return try { + super.process(functionCall) + functionCall.constValue(globalNamespace) ?: functionCall + } catch (ax: AstException) { + addError(ax) + functionCall + } + } + + /** + * Try to process a unary prefix expression. + * Compile-time constant sub expressions will be evaluated on the spot. + * For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 + */ + override fun process(expr: PrefixExpression): IExpression { + return try { + super.process(expr) + + val subexpr = expr.expression + if (subexpr is LiteralValue) { + // process prefixed literal values (such as -3, not true) + return when { + expr.operator == "+" -> subexpr + expr.operator == "-" -> when { + subexpr.asIntegerValue!= null -> { + optimizationsDone++ + LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position) + } + subexpr.floatvalue != null -> { + optimizationsDone++ + LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position) + } + else -> throw ExpressionError("can only take negative of int or float", subexpr.position) + } + expr.operator == "~" -> when { + subexpr.asIntegerValue != null -> { + optimizationsDone++ + LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position) + } + else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) + } + expr.operator == "not" -> when { + subexpr.asIntegerValue != null -> { + optimizationsDone++ + LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position) + } + subexpr.floatvalue != null -> { + optimizationsDone++ + LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position) + } + else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position) + } + else -> throw ExpressionError(expr.operator, subexpr.position) + } + } + return expr + } catch (ax: AstException) { + addError(ax) + expr + } + } + + /** + * Try to process a binary expression. + * Compile-time constant sub expressions will be evaluated on the spot. + * For instance, "9 * (4 + 2)" will be optimized into the integer literal 54. + */ + override fun process(expr: BinaryExpression): IExpression { + return try { + super.process(expr) + + val evaluator = ConstExprEvaluator() + val leftconst = expr.left.constValue(globalNamespace) + val rightconst = expr.right.constValue(globalNamespace) + return when { + leftconst != null && rightconst != null -> { + optimizationsDone++ + evaluator.evaluate(leftconst, expr.operator, rightconst) + } + else -> expr + } + } catch (ax: AstException) { + addError(ax) + expr + } + } + + override fun process(range: RangeExpr): IExpression { + return try { + super.process(range) + val from = range.from.constValue(globalNamespace) + val to = range.to.constValue(globalNamespace) + if (from != null && to != null) { + when { + from.type==DataType.WORD || to.type==DataType.WORD -> { + // range on word value boundaries + val rangeValue = from.asIntegerValue!!.rangeTo(to.asIntegerValue!!) + if (rangeValue.last - rangeValue.first > 65535) { + throw ExpressionError("amount of values in range exceeds 65535", range.position) + } + return LiteralValue(DataType.ARRAY_W, arrayvalue = rangeValue.map { + val v = LiteralValue(DataType.WORD, wordvalue = it, position = range.position) + v.parent = range.parent + v + }.toMutableList(), position = from.position) + } + from.type==DataType.BYTE && to.type==DataType.BYTE -> { + // range on byte value boundaries + val rangeValue = from.bytevalue!!.rangeTo(to.bytevalue!!) + if (rangeValue.last - rangeValue.first > 65535) { + throw ExpressionError("amount of values in range exceeds 65535", range.position) + } + return LiteralValue(DataType.ARRAY, arrayvalue = rangeValue.map { + val v = LiteralValue(DataType.BYTE, bytevalue = it.toShort(), position = range.position) + v.parent = range.parent + v + }.toMutableList(), position = from.position) + } + from.strvalue != null && to.strvalue != null -> { + // char range + val rangevalue = from.strvalue[0].rangeTo(to.strvalue[0]) + if (rangevalue.last - rangevalue.first > 65535) { + throw ExpressionError("amount of characters in range exceeds 65535", range.position) + } + val newval = LiteralValue(DataType.STR, strvalue = rangevalue.toList().joinToString(""), position = range.position) + newval.parent = range.parent + return newval + } + else -> AstException("range on weird datatype") + } + } + return range + } catch (ax: AstException) { + addError(ax) + range + } + } + + override fun process(literalValue: LiteralValue): LiteralValue { + if(literalValue.arrayvalue!=null) { + val newArray = literalValue.arrayvalue.map { it.process(this) } + literalValue.arrayvalue.clear() + literalValue.arrayvalue.addAll(newArray) + } + return super.process(literalValue) + } +} + + diff --git a/il65/src/il65/optimizing/Extensions.kt b/il65/src/il65/optimizing/Extensions.kt new file mode 100644 index 000000000..f63cded39 --- /dev/null +++ b/il65/src/il65/optimizing/Extensions.kt @@ -0,0 +1,48 @@ +package il65.optimizing + +import il65.ast.AstException +import il65.ast.INameScope +import il65.ast.IStatement +import il65.ast.Module +import il65.parser.ParsingFailedError + +fun Module.constantFold(globalNamespace: INameScope) { + val optimizer = ConstantFolding(globalNamespace) + try { + this.process(optimizer) + } catch (ax: AstException) { + optimizer.addError(ax) + } + + while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) { + println("[${this.name}] ${optimizer.optimizationsDone} constant folds performed") + optimizer.optimizationsDone = 0 + this.process(optimizer) + } + + if(optimizer.errors.isNotEmpty()) { + optimizer.errors.forEach { System.err.println(it) } + throw ParsingFailedError("There are ${optimizer.errors.size} errors.") + } else { + this.linkParents() // re-link in final configuration + } +} + + +fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefinitions: MutableMap): Int { + val optimizer = StatementOptimizer(globalNamespace) + this.process(optimizer) + optimizer.removeUnusedNodes(globalNamespace.usedNames(), allScopedSymbolDefinitions) + if(optimizer.optimizationsDone > 0) + println("[${this.name}] ${optimizer.optimizationsDone} statement optimizations performed") + this.linkParents() // re-link in final configuration + return optimizer.optimizationsDone +} + +fun Module.simplifyExpressions(namespace: INameScope) : Int { + val optimizer = SimplifyExpressions(namespace) + this.process(optimizer) + if(optimizer.optimizationsDone > 0) + println("[${this.name}] ${optimizer.optimizationsDone} expression optimizations performed") + return optimizer.optimizationsDone +} diff --git a/il65/src/il65/optimizing/SimplifyExpressions.kt b/il65/src/il65/optimizing/SimplifyExpressions.kt new file mode 100644 index 000000000..888c54a4c --- /dev/null +++ b/il65/src/il65/optimizing/SimplifyExpressions.kt @@ -0,0 +1,33 @@ +package il65.optimizing + +import il65.ast.IAstProcessor +import il65.ast.INameScope + +/* + todo eliminate useless terms: + *0 -> constant 0 + X*1, X/1, X//1 -> just X + X*-1 -> unary prefix -X + X**0 -> 1 + X**1 -> X + X**-1 -> 1.0/X + X << 0 -> X + X | 0 -> X + x & 0 -> 0 + X ^ 0 -> X + + todo expression optimization: remove redundant builtin function calls + todo expression optimization: reduce expression nesting / flattening of parenthesis + todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0) + todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2) + todo optimize addition with self into shift 1 (A+=A -> A<<=1) + todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call) + todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?) + + */ + +class SimplifyExpressions(namespace: INameScope) : IAstProcessor { + var optimizationsDone: Int = 0 + + // @todo build this optimizer +} \ No newline at end of file diff --git a/il65/src/il65/optimizing/StatementsOptimizer.kt b/il65/src/il65/optimizing/StatementOptimizer.kt similarity index 85% rename from il65/src/il65/optimizing/StatementsOptimizer.kt rename to il65/src/il65/optimizing/StatementOptimizer.kt index e23ea4815..a71866f86 100644 --- a/il65/src/il65/optimizing/StatementsOptimizer.kt +++ b/il65/src/il65/optimizing/StatementOptimizer.kt @@ -5,22 +5,9 @@ import il65.functions.BuiltinFunctionNames import il65.functions.BuiltinFunctionsWithoutSideEffects -fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefinitions: MutableMap) { - val optimizer = StatementOptimizer(globalNamespace) - this.process(optimizer) - optimizer.removeUnusedNodes(globalNamespace.usedNames(), allScopedSymbolDefinitions) - if(optimizer.optimizationsDone==0) - println("[${this.name}] 0 statement optimizations performed") - - while(optimizer.optimizationsDone>0) { - println("[${this.name}] ${optimizer.optimizationsDone} statement optimizations performed") - optimizer.reset() - this.process(optimizer) - } - this.linkParents() // re-link in final configuration -} - /* + todo remove if statements with empty statement blocks + todo replace if statements with only else block todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...) todo statement optimization: X+=1, X-=1 --> X++/X-- , todo remove statements that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc @@ -37,10 +24,6 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso private var statementsToRemove = mutableListOf() - fun reset() { - optimizationsDone = 0 - } - override fun process(functionCall: FunctionCall): IExpression { val target = globalNamespace.lookup(functionCall.target.nameInSource, functionCall) if(target!=null) @@ -100,6 +83,8 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso } fun removeUnusedNodes(usedNames: Set, allScopedSymbolDefinitions: MutableMap) { + val symbolsToRemove = mutableListOf() + for ((name, value) in allScopedSymbolDefinitions) { if(!usedNames.contains(name)) { val parentScope = value.parent as INameScope @@ -109,10 +94,15 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso if(value is Block) println("${value.position} Info: block '$localname' is never used") parentScope.removeStatement(value) + symbolsToRemove.add(name) optimizationsDone++ } } + for(name in symbolsToRemove) { + allScopedSymbolDefinitions.remove(name) + } + for(stmt in statementsToRemove) { stmt.definingScope().removeStatement(stmt) } diff --git a/il65/src/il65/parser/ModuleParsing.kt b/il65/src/il65/parser/ModuleParsing.kt index 01f893ae5..c0817920c 100644 --- a/il65/src/il65/parser/ModuleParsing.kt +++ b/il65/src/il65/parser/ModuleParsing.kt @@ -15,7 +15,7 @@ private val importedModules : HashMap = hashMapOf() fun importModule(filePath: Path) : Module { - println("importing '${filePath.fileName}' (from ${filePath.parent})...") + println("importing '${filePath.fileName}' (from '${filePath.parent}')") if(!Files.isReadable(filePath)) throw ParsingFailedError("No such file: $filePath") diff --git a/il65/src/il65/stackvm/StackVm.kt b/il65/src/il65/stackvm/StackVm.kt index 8a6a6af44..16d740107 100644 --- a/il65/src/il65/stackvm/StackVm.kt +++ b/il65/src/il65/stackvm/StackVm.kt @@ -130,9 +130,10 @@ enum class Syscall(val callNr: Short) { INPUT_VAR(15), // user input a string into a variable GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order GFX_CLEARSCR(17), // clear the screen with color pushed on stack - GFX_TEXT(18), // write text on screen at (x,y,text,color) pushed on stack in that order + GFX_TEXT(18), // write text on screen at (x,y,color,text) pushed on stack in that order RANDOM(19), // push a random byte on the stack RANDOM_W(20), // push a random word on the stack + RANDOM_F(21), // push a random float on the stack (between 0.0 and 1.0) FUNC_P_CARRY(100), @@ -598,7 +599,7 @@ class Program (prog: MutableList, Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues) } else -> { - println("INSTR $opcode at $lineNr args=$args") // TODO weg + // println("INSTR $opcode at $lineNr args=$args") Instruction(opcode, getArgValue(args)) } } @@ -1006,13 +1007,14 @@ class StackVm(val traceOutputFile: String?) { canvas.clearScreen(color.integerValue()) } Syscall.GFX_TEXT -> { - val color = evalstack.pop() val text = evalstack.pop() + val color = evalstack.pop() val (y, x) = evalstack.pop2() canvas.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue()) } Syscall.RANDOM -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255)) Syscall.RANDOM_W -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535)) + Syscall.RANDOM_F -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble())) else -> throw VmExecutionException("unimplemented syscall $syscall") } }