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.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<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> {
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
}

View File

@ -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)

View File

@ -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<BinaryExpression>()
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<Return>()
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<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)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 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.