optimize typecasted binary expression to avoid even more estack use. also fix wrong parent crash in removal of unused variable's assignments.

This commit is contained in:
Irmen de Jong 2021-11-13 14:22:37 +01:00
parent 00c6f74481
commit f0f52b9166
4 changed files with 91 additions and 49 deletions

View File

@ -4,7 +4,10 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.augmentAssignmentOperators import prog8.ast.expressions.augmentAssignmentOperators
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment 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() { class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() {
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// 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<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions)
return noModifications
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) { 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, 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. // 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... // 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 return noModifications
} }

View File

@ -124,8 +124,10 @@ class UnusedCodeRemover(private val program: Program,
it.isInRegularRAMof(compTarget.machine) it.isInRegularRAMof(compTarget.machine)
} }
if(assignTargets.size==usages.size) { 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) 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) IAstModification.Remove(it.first, it.second)
} + listOf( } + listOf(
IAstModification.Remove(decl, parent as IStatementContainer) IAstModification.Remove(decl, parent as IStatementContainer)

View File

@ -210,7 +210,7 @@ class TestOptimization: FunSpec({
asm.valid shouldBe true 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 = """ val src = """
main { main {
sub start() { sub start() {
@ -219,28 +219,28 @@ class TestOptimization: FunSpec({
} }
} }
""" """
val result = compileText(C64Target, true, src, writeAssembly = false).assertSuccess() val result = compileText(C64Target, true, src, writeAssembly = true).assertSuccess()
/* turned into:
// bb = (cos8(r)/2 + 100) as ubyte ubyte r
val bbAssign = result.program.entrypoint.statements.last() as Assignment r = 0
val texpr = bbAssign.value as TypecastExpression ubyte bb
texpr.type shouldBe DataType.UBYTE cx16.r15sL = cos8(r)
texpr.expression shouldBe instanceOf<BinaryExpression>() cx16.r15sL >>= 1
texpr.expression.inferType(result.program).getOrElse { fail("dt") } shouldBe DataType.BYTE cx16.r15sL += 100
bb = cx16.r15sL
val options = CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, true, C64Target) return
val changer = BeforeAsmGenerationAstChanger(result.program, */
options, val st = result.program.entrypoint.statements
ErrorReporterForTests() st.size shouldBe 8
) st.last() shouldBe instanceOf<Return>()
var assign = st[3] as Assignment
changer.visit(result.program) assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL")
while(changer.applyModifications()>0) { assign = st[4] as Assignment
changer.visit(result.program) assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL")
} assign = st[5] as Assignment
assign.target.identifier!!.nameInSource shouldBe listOf("cx16","r15sL")
// printAst(result.program) assign = st[6] as Assignment
// TODO finish this test assign.target.identifier!!.nameInSource shouldBe listOf("bb")
} }
test("asmgen correctly deals with float typecasting in augmented assignment") { test("asmgen correctly deals with float typecasting in augmented assignment") {
@ -293,4 +293,32 @@ class TestOptimization: FunSpec({
(decl2 as VarDecl).name shouldBe "usedvar" (decl2 as VarDecl).name shouldBe "usedvar"
assign2 shouldBe instanceOf<Assignment>() assign2 shouldBe instanceOf<Assignment>()
} }
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")
}
}) })

View File

@ -3,7 +3,7 @@ TODO
For next compiler release (7.3) 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. - add expression simplification to while and until loops as well.