diff --git a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt index 00728fe9d..98b72b4a4 100644 --- a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt +++ b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt @@ -4,7 +4,10 @@ import prog8.ast.IStatementContainer import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.DataType +import prog8.ast.base.FatalAstException import prog8.ast.expressions.BinaryExpression +import prog8.ast.expressions.IdentifierReference +import prog8.ast.expressions.TypecastExpression import prog8.ast.expressions.augmentAssignmentOperators import prog8.ast.statements.AssignTarget import prog8.ast.statements.Assignment @@ -17,34 +20,14 @@ import prog8.compilerinterface.isInRegularRAMof class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() { -// 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...: [ IS THIS STILL TRUE AFTER ALL CHANGES? ] -// 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 { + if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) + return noModifications + val binExpr = assignment.value as? BinaryExpression if (binExpr != null) { - if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) - return noModifications - - /* Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, @@ -78,10 +61,39 @@ X = BinExpr X = LeftExpr } // TODO further unraveling of binary expression trees into flat statements. - // however this should probably be done in a more generic way to also service + // however this should probably be done in a more generic way to also work on // the expressiontrees that are not used in an assignment statement... } + val typecast = assignment.value as? TypecastExpression + if(typecast!=null) { + val origExpr = typecast.expression as? BinaryExpression + if(origExpr!=null) { + // it's a typecast of a binary expression. + // we can see if we can unwrap the binary expression by working on a new temporary variable + // (that has the type of the expression), and then finally doing the typecast. + // Once it's outside the typecast, the regular splitting can commence. + val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED) + val tempVar = when(tempDt) { + DataType.UBYTE -> listOf("cx16", "r15L") + DataType.BYTE -> listOf("cx16", "r15sL") + DataType.UWORD -> listOf("cx16", "r15") + DataType.WORD -> listOf("cx16", "r15s") + DataType.FLOAT -> listOf("floats", "tempvar_swap_float") + else -> throw FatalAstException("invalid dt $tempDt") + } + val assignTempVar = Assignment( + AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position), + typecast.expression, typecast.position + ) + println("UNWRAP TYPECAST: ${assignment.position}") // TODO DEBUG + return listOf( + IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer), + IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVar, typecast.position), typecast) + ) + } + } + return noModifications } diff --git a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt index 2f03c439d..73ca802e4 100644 --- a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt +++ b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt @@ -124,8 +124,10 @@ class UnusedCodeRemover(private val program: Program, it.isInRegularRAMof(compTarget.machine) } if(assignTargets.size==usages.size) { + // TODO FIX THAT A MEMREAD OF THE VARIABLE ISN'T RECOGNISED AS A USE (imageviewer iff_module.p8 pixptr) errors.warn("removing unused variable '${decl.name}'", decl.position) - return assignTargets.map { it.parent to it.definingScope}.toSet().map { + val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet() + return assignmentsToRemove.map { IAstModification.Remove(it.first, it.second) } + listOf( IAstModification.Remove(decl, parent as IStatementContainer) diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index 32ff1f660..42f146dab 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -210,7 +210,7 @@ class TestOptimization: FunSpec({ asm.valid shouldBe true } - test("intermediate assignment steps 2 have correct types for codegen phase (BeforeAsmGenerationAstChanger)") { + test("intermediate assignment steps generated for typecasted expression") { val src = """ main { sub start() { @@ -219,28 +219,28 @@ class TestOptimization: FunSpec({ } } """ - val result = compileText(C64Target, true, src, writeAssembly = false).assertSuccess() - - // bb = (cos8(r)/2 + 100) as ubyte - val bbAssign = result.program.entrypoint.statements.last() as Assignment - val texpr = bbAssign.value as TypecastExpression - texpr.type shouldBe DataType.UBYTE - texpr.expression shouldBe instanceOf() - texpr.expression.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.BYTE - - val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target) - val changer = BeforeAsmGenerationAstChanger(result.program, - options, - ErrorReporterForTests() - ) - - changer.visit(result.program) - while(changer.applyModifications()>0) { - changer.visit(result.program) - } - - // printAst(result.program) - // TODO finish this test + val result = compileText(C64Target, true, src, writeAssembly = true).assertSuccess() + /* turned into: + ubyte r + r = 0 + ubyte bb + cx16.r15sL = cos8(r) + cx16.r15sL >>= 1 + cx16.r15sL += 100 + bb = cx16.r15sL + return + */ + val st = result.program.entrypoint.statements + st.size shouldBe 8 + st.last() shouldBe instanceOf() + var assign = st[3] as Assignment + assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL") + assign = st[4] as Assignment + assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL") + assign = st[5] as Assignment + assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL") + assign = st[6] as Assignment + assign.target.identifier!!.nameInSource shouldBe listOf("bb") } test("asmgen correctly deals with float typecasting in augmented assignment") { @@ -293,4 +293,32 @@ class TestOptimization: FunSpec({ (decl2 as VarDecl).name shouldBe "usedvar" assign2 shouldBe instanceOf() } + + test("unused variable removal from subscope") { + val src=""" + main { + sub start() { + if cx16.r0 { + uword xx = 42 ; to be removed + xx=99 ; to be removed + cx16.r0 = 0 + } + func2() + + sub func2() { + uword yy = 33 ; to be removed + yy=99 ; to be removed + cx16.r0 = 0 + } + } + }""" + val result = compileText(C64Target, optimize=true, src, writeAssembly=false).assertSuccess() + result.program.entrypoint.statements.size shouldBe 3 + val ifstmt = result.program.entrypoint.statements[0] as IfStatement + ifstmt.truepart.statements.size shouldBe 1 + (ifstmt.truepart.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0") + val func2 = result.program.entrypoint.statements[2] as Subroutine + func2.statements.size shouldBe 1 + (func2.statements[0] as Assignment).target.identifier!!.nameInSource shouldBe listOf("cx16", "r0") + } }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 3caafecea..efe2ec682 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,7 @@ TODO For next compiler release (7.3) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- fix compiler crashing on assembler and imageviewer (add unit tests) +- fix compiler crashing on imageviewer (add unit tests) - add expression simplification to while and until loops as well.