diff --git a/compiler/src/prog8/compiler/Main.kt b/compiler/src/prog8/compiler/Main.kt index fee816ea4..94678440c 100644 --- a/compiler/src/prog8/compiler/Main.kt +++ b/compiler/src/prog8/compiler/Main.kt @@ -187,10 +187,11 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) { while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = programAst.simplifyExpressions() - val optsDone2 = programAst.optimizeStatements(errors) + val optsDone2 = programAst.splitBinaryExpressions() + val optsDone3 = programAst.optimizeStatements(errors) programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away errors.handle() - if (optsDone1 + optsDone2 == 0) + if (optsDone1 + optsDone2 + optsDone3 == 0) break } diff --git a/compiler/src/prog8/optimizer/BinExprSplitter.kt b/compiler/src/prog8/optimizer/BinExprSplitter.kt new file mode 100644 index 000000000..7bede0e79 --- /dev/null +++ b/compiler/src/prog8/optimizer/BinExprSplitter.kt @@ -0,0 +1,89 @@ +package prog8.optimizer + +import prog8.ast.INameScope +import prog8.ast.Node +import prog8.ast.Program +import prog8.ast.expressions.* +import prog8.ast.processing.AstWalker +import prog8.ast.processing.IAstModification +import prog8.ast.statements.AssignTarget +import prog8.ast.statements.Assignment + + +internal class BinExprSplitter(private val program: Program) : AstWalker() { + private val noModifications = emptyList() + +// override fun after(decl: VarDecl, parent: Node): Iterable { +// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: +// if(decl.type==VarDeclType.VAR ) { +// val binExpr = decl.value as? BinaryExpression +// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) { +// // split into a vardecl with just the left expression, and an aug. assignment with the right expression. +// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position) +// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) +// val assign = Assignment(target, augExpr, binExpr.position) +// println("SPLIT VARDECL $decl") +// return listOf( +// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl), +// IAstModification.InsertAfter(decl, assign, parent) +// ) +// } +// } +// return noModifications +// } + + override fun after(assignment: Assignment, parent: Node): Iterable { + + val binExpr = assignment.value as? BinaryExpression + if (binExpr != null) { +/* + +reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, +by attempting to splitting it up into individual simple steps: + + +X = BinExpr X = LeftExpr + followed by + / \ IF 'X' not used X = BinExpr + / \ IN LEFTEXPR ==> + / \ / \ + LeftExpr. RightExpr. / \ + / \ / \ X RightExpr. + .. .. .. .. + + */ + if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) { + if (!assignment.isAugmentable) { + val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position) + val targetExpr = assignment.target.toExpression() + val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) + return listOf( + IAstModification.InsertBefore(assignment, firstAssign, parent), + IAstModification.ReplaceNode(assignment.value, augExpr, assignment)) + } + } + + // TODO further unraveling of binary expression trees into flat statements. + // however this should probably be done in a more generic way to also service + // the expressiontrees that are not used in an assignment statement... + } + + return noModifications + } + + private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean { + return when { + target.identifier!=null -> target.isInRegularRAM(namespace) + target.memoryAddress!=null -> target.isInRegularRAM(namespace) + target.arrayindexed!=null -> { + val index = target.arrayindexed!!.arrayspec.index + if(index is NumericLiteralValue) + target.isInRegularRAM(namespace) + else + false + } + else -> false + } + } + +} diff --git a/compiler/src/prog8/optimizer/ExpressionSimplifier.kt b/compiler/src/prog8/optimizer/ExpressionSimplifier.kt index 98eb48c24..9bab6d40c 100644 --- a/compiler/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/compiler/src/prog8/optimizer/ExpressionSimplifier.kt @@ -1,15 +1,11 @@ package prog8.optimizer -import prog8.ast.INameScope import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.* import prog8.ast.expressions.* import prog8.ast.processing.AstWalker import prog8.ast.processing.IAstModification -import prog8.ast.statements.AssignTarget -import prog8.ast.statements.Assignment -import prog8.ast.statements.VarDecl import kotlin.math.abs import kotlin.math.log2 import kotlin.math.pow @@ -279,80 +275,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker() return noModifications } - -// override fun after(decl: VarDecl, parent: Node): Iterable { -// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: -// if(decl.type==VarDeclType.VAR ) { -// val binExpr = decl.value as? BinaryExpression -// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) { -// // split into a vardecl with just the left expression, and an aug. assignment with the right expression. -// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position) -// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) -// val assign = Assignment(target, augExpr, binExpr.position) -// println("SPLIT VARDECL $decl") -// return listOf( -// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl), -// IAstModification.InsertAfter(decl, assign, parent) -// ) -// } -// } -// return noModifications -// } - - override fun after(assignment: Assignment, parent: Node): Iterable { - - val binExpr = assignment.value as? BinaryExpression - if (binExpr != null) { -/* - -reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, -by attempting to splitting it up into individual simple steps: - - -X = BinExpr X = LeftExpr - followed by - / \ IF 'X' not used X = BinExpr - / \ IN LEFTEXPR ==> - / \ / \ - LeftExpr. RightExpr. / \ - / \ / \ X RightExpr. - .. .. .. .. - - */ - if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) { - if (!assignment.isAugmentable) { - val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position) - val targetExpr = assignment.target.toExpression() - val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) - return listOf( - IAstModification.InsertBefore(assignment, firstAssign, parent), - IAstModification.ReplaceNode(assignment.value, augExpr, assignment)) - } - } - - // TODO further unraveling of binary expression trees into flat statements. - // however this should probably be done in a more generic way to also service - // the expressiontrees that are not used in an assignment statement... - } - - return noModifications - } - - private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean { - return when { - target.identifier!=null -> target.isInRegularRAM(namespace) - target.memoryAddress!=null -> target.isInRegularRAM(namespace) - target.arrayindexed!=null -> { - val index = target.arrayindexed!!.arrayspec.index - if(index is NumericLiteralValue) - target.isInRegularRAM(namespace) - else - false - } - else -> false - } - } - override fun after(functionCall: FunctionCall, parent: Node): Iterable { if(functionCall.target.nameInSource == listOf("lsb")) { val arg = functionCall.args[0] diff --git a/compiler/src/prog8/optimizer/Extensions.kt b/compiler/src/prog8/optimizer/Extensions.kt index 87800312e..307e21370 100644 --- a/compiler/src/prog8/optimizer/Extensions.kt +++ b/compiler/src/prog8/optimizer/Extensions.kt @@ -53,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int { opti.visit(this) return opti.applyModifications() } + +internal fun Program.splitBinaryExpressions() : Int { + val opti = BinExprSplitter(this) + opti.visit(this) + return opti.applyModifications() +} diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 782264d23..0fbdae9b4 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -4,6 +4,7 @@ TODO - get rid of all other TODO's in the code ;-) - make it possible for array literals to not only contain compile time constants? +- allow assignment of all elements of an array at once via new array literal assignment (flatten, like with struct) - implement @stack for asmsub parameters - make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_' - option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)