diff --git a/codeCore/src/prog8/code/optimize/Optimizer.kt b/codeCore/src/prog8/code/optimize/Optimizer.kt index eafb0c74e..2fcc81673 100644 --- a/codeCore/src/prog8/code/optimize/Optimizer.kt +++ b/codeCore/src/prog8/code/optimize/Optimizer.kt @@ -78,38 +78,65 @@ private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): In if(options.compTarget.machine.cpu == CpuType.VIRTUAL) return 0 // the special bittest optimization is not yet valid for the IR + fun makeBittestCall(condition: PtBinaryExpression, and: PtBinaryExpression, variable: PtIdentifier, bitmask: Int): PtBuiltinFunctionCall { + require(bitmask==128 || bitmask==64) + val setOrNot = if(condition.operator=="!=") "set" else "notset" + val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, condition.position) + bittestCall.add(variable) + if(bitmask==128) + bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position)) + else + bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position)) + return bittestCall + } + + fun isAndByteCondition(condition: PtBinaryExpression?): Triple? { + if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) { + if (condition.right.asConstInteger() == 0) { + val and = condition.left as? PtBinaryExpression + if (and != null && and.operator == "&" && and.type == DataType.UBYTE) { + val variable = and.left as? PtIdentifier + val bitmask = and.right.asConstInteger() + if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) { + return Triple(and, variable, bitmask) + } + } + } + } + return null + } + var changes = 0 var recurse = true walkAst(program) { node: PtNode, depth: Int -> if(node is PtIfElse) { val condition = node.condition as? PtBinaryExpression - if(condition!=null && (condition.operator=="==" || condition.operator=="!=")) { - if(condition.right.asConstInteger()==0) { - val and = condition.left as? PtBinaryExpression - if(and != null && and.operator=="&" && and.type == DataType.UBYTE) { - val variable = and.left as? PtIdentifier - val bitmask = and.right.asConstInteger() - if(variable!=null && variable.type in ByteDatatypes && (bitmask==128 || bitmask==64)) { - val setOrNot = if(condition.operator=="!=") "set" else "notset" - val index = node.parent.children.indexOf(node) - val bittestCall = PtBuiltinFunctionCall("prog8_ifelse_bittest_$setOrNot", false, true, DataType.BOOL, node.condition.position) - bittestCall.add(variable) - if(bitmask==128) - bittestCall.add(PtNumber(DataType.UBYTE, 7.0, and.right.position)) - else - bittestCall.add(PtNumber(DataType.UBYTE, 6.0, and.right.position)) - val ifElse = PtIfElse(node.position) - ifElse.add(bittestCall) - ifElse.add(node.ifScope) - if(node.hasElse()) - ifElse.add(node.elseScope) - node.parent.children[index] = ifElse - ifElse.parent = node.parent - changes++ - recurse = false - } - } - } + val check = isAndByteCondition(condition) + if(check!=null) { + val (and, variable, bitmask) = check + val bittestCall = makeBittestCall(condition!!, and, variable, bitmask) + val ifElse = PtIfElse(node.position) + ifElse.add(bittestCall) + ifElse.add(node.ifScope) + if (node.hasElse()) + ifElse.add(node.elseScope) + val index = node.parent.children.indexOf(node) + node.parent.children[index] = ifElse + ifElse.parent = node.parent + changes++ + recurse = false + } + } + if (node is PtIfExpression) { + val condition = node.condition as? PtBinaryExpression + val check = isAndByteCondition(condition) + if(check!=null) { + val (and, variable, bitmask) = check + val bittestCall = makeBittestCall(condition!!, and, variable, bitmask) + node.children[0] = bittestCall + bittestCall.parent = node + changes++ + recurse = false } } recurse diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 83d0b4019..0b94708fd 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -1381,6 +1381,10 @@ $repeatLabel""") pla jsr floats.copy_float""") } + + internal fun assignIfExpression(target: AsmAssignTarget, value: PtIfExpression) { + ifElseAsmgen.assignIfExpression(target, value) + } } /** diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt index 6f5fd1ec0..aa65714cb 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt @@ -54,6 +54,85 @@ internal class IfElseAsmGen(private val program: PtProgram, throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}") } + internal fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) { + // this is NOT for the if-else STATEMENT, but this is code for the IF-EXPRESSION. + require(target.datatype==expr.type) + val falseLabel = asmgen.makeLabel("ifexpr_false") + val endLabel = asmgen.makeLabel("ifexpr_end") + evalConditonAndBranchWhenFalse(expr.condition, falseLabel) + when(expr.type) { + in ByteDatatypesWithBoolean -> { + asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false) + asmgen.jmp(endLabel) + asmgen.out(falseLabel) + asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false) + asmgen.out(endLabel) + assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false) + } + in WordDatatypes -> { + asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false) + asmgen.jmp(endLabel) + asmgen.out(falseLabel) + asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false) + asmgen.out(endLabel) + assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY) + } + DataType.FLOAT -> { + asmgen.assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true) + asmgen.jmp(endLabel) + asmgen.out(falseLabel) + asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true) + asmgen.out(endLabel) + asmgen.assignRegister(RegisterOrPair.FAC1, target) + } + else -> throw AssemblyError("weird dt") + } + } + + private fun evalConditonAndBranchWhenFalse(condition: PtExpression, falseLabel: String) { + if (condition is PtBinaryExpression) { + return when(condition.right.type) { + in ByteDatatypesWithBoolean -> translateIfByteConditionBranch(condition, falseLabel) + in WordDatatypes -> translateIfWordConditionBranch(condition, falseLabel) + DataType.FLOAT -> translateFloatConditionBranch(condition, falseLabel) + else -> throw AssemblyError("weird dt") + } + } + else if(condition is PtPrefix && condition.operator=="not") { + assignConditionValueToRegisterAndTest(condition.value) + asmgen.out(" bne $falseLabel") + } else { + // 'simple' condition, check if it is a byte bittest + val bittest = condition as? PtBuiltinFunctionCall + if(bittest!=null && bittest.name.startsWith("prog8_ifelse_bittest_")) { + val variable = bittest.args[0] as PtIdentifier + val bitnumber = (bittest.args[1] as PtNumber).number.toInt() + val testForBitSet = bittest.name.endsWith("_set") + when (bitnumber) { + 7 -> { + // test via bit + N flag + asmgen.out(" bit ${variable.name}") + if(testForBitSet) asmgen.out(" bpl $falseLabel") + else asmgen.out(" bmi $falseLabel") + return + } + 6 -> { + // test via bit + V flag + asmgen.out(" bit ${variable.name}") + if(testForBitSet) asmgen.out(" bvc $falseLabel") + else asmgen.out(" bvs $falseLabel") + return + } + else -> throw AssemblyError("prog8_ifelse_bittest can only work on bits 7 and 6") + } + } + + // the condition is "simple" enough to just assign its 0/1 value to a register and branch on that + assignConditionValueToRegisterAndTest(condition) + asmgen.out(" beq $falseLabel") + } + } + private fun checkNotRomsubReturnsStatusReg(condition: PtExpression) { val fcall = condition as? PtFunctionCall if(fcall!=null && fcall.type==DataType.BOOL) { @@ -73,6 +152,7 @@ internal class IfElseAsmGen(private val program: PtProgram, is PtIrRegister, is PtArrayIndexer, is PtPrefix, + is PtIfExpression, is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ } is PtTypeCast -> { if(condition.value.type !in ByteDatatypes && condition.value.type !in WordDatatypes) @@ -276,6 +356,103 @@ internal class IfElseAsmGen(private val program: PtProgram, } } + private fun translateIfByteConditionBranch(condition: PtBinaryExpression, falseLabel: String) { + val signed = condition.left.type in SignedDatatypes + val constValue = condition.right.asConstInteger() + if(constValue==0) { + return translateIfCompareWithZeroByteBranch(condition, signed, falseLabel) + } + + when(condition.operator) { + "==" -> { + // if X==value + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed) + cmpAwithByteValue(condition.right, false) + asmgen.out(" bne $falseLabel") + } + "!=" -> { + // if X!=value + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed) + cmpAwithByteValue(condition.right, false) + asmgen.out(" beq $falseLabel") + } + in LogicalOperators -> { + val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, condition.definingISub(), condition.position, register=RegisterOrPair.A) + if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) { + asmgen.out(" beq $falseLabel") + } else { + errors.warn("SLOW FALLBACK FOR 'IFEXPR' CODEGEN - ask for support", condition.position) // should not occur ;-) + assignConditionValueToRegisterAndTest(condition) + asmgen.out(" beq $falseLabel") + } + } + else -> { + // TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does + // TODO: special cases for <, <=, >, >= above. + assignConditionValueToRegisterAndTest(condition) + asmgen.out(" beq $falseLabel") + } + } + } + + private fun translateIfWordConditionBranch(condition: PtBinaryExpression, falseLabel: String) { + val signed = condition.left.type in SignedDatatypes + val constValue = condition.right.asConstInteger() + if(constValue==0) { + + // TODO reuse more code from regular if statements. Need a shared routine like isWordExprZero() ? + when(condition.operator) { + "==" -> { + // if w==0 + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.AY, signed) + asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | bne $falseLabel") + return + } + "!=" -> { + // if w!=0 + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.AY, signed) + asmgen.out(" sty P8ZP_SCRATCH_REG | ora P8ZP_SCRATCH_REG | beq $falseLabel") + return + } + } + } + + // TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does + assignConditionValueToRegisterAndTest(condition) + asmgen.out(" beq $falseLabel") + } + + private fun translateIfCompareWithZeroByteBranch(condition: PtBinaryExpression, signed: Boolean, falseLabel: String) { + // optimized code for byte comparisons with 0 + assignConditionValueToRegisterAndTest(condition.left) + when (condition.operator) { + "==" -> asmgen.out(" bne $falseLabel") + "!=" -> asmgen.out(" beq $falseLabel") + ">" -> { + if(signed) asmgen.out(" bmi $falseLabel | beq $falseLabel") + else asmgen.out(" beq $falseLabel") + } + ">=" -> { + if(signed) asmgen.out(" bmi $falseLabel") + else { /* always true for unsigned */ } + } + "<" -> { + if(signed) asmgen.out(" bpl $falseLabel") + else asmgen.jmp(falseLabel) + } + "<=" -> { + if(signed) { + // inverted '>' + asmgen.out(""" + beq + + bpl $falseLabel ++""") + } else asmgen.out(" bne $falseLabel") + } + else -> throw AssemblyError("expected comparison operator") + } + } + private fun translateIfCompareWithZeroByte(stmt: PtIfElse, signed: Boolean, jumpAfterIf: PtJump?) { // optimized code for byte comparisons with 0 val condition = stmt.condition as PtBinaryExpression @@ -1741,6 +1918,51 @@ _jump jmp ($asmLabel) } } + private fun translateFloatConditionBranch(condition: PtBinaryExpression, elseLabel: String) { + val constValue = (condition.right as? PtNumber)?.number + if(constValue==0.0) { + if (condition.operator == "==") { + // if FL==0.0 + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true) + asmgen.out(" jsr floats.SIGN | cmp #0 | bne $elseLabel") + return + } else if(condition.operator=="!=") { + // if FL!=0.0 + asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.FAC1, true) + asmgen.out(" jsr floats.SIGN | cmp #0 | beq $elseLabel") + return + } + } + + when(condition.operator) { + "==" -> { + translateFloatsEqualsConditionIntoA(condition.left, condition.right) + asmgen.out(" beq $elseLabel") + } + "!=" -> { + translateFloatsEqualsConditionIntoA(condition.left, condition.right) + asmgen.out(" bne $elseLabel") + } + "<" -> { + translateFloatsLessConditionIntoA(condition.left, condition.right, false) + asmgen.out(" beq $elseLabel") + } + "<=" -> { + translateFloatsLessConditionIntoA(condition.left, condition.right, true) + asmgen.out(" beq $elseLabel") + } + ">" -> { + translateFloatsLessConditionIntoA(condition.left, condition.right, true) + asmgen.out(" bne $elseLabel") + } + ">=" -> { + translateFloatsLessConditionIntoA(condition.left, condition.right, false) + asmgen.out(" bne $elseLabel") + } + else -> throw AssemblyError("expected comparison operator") + } + } + private fun translateFloatsEqualsConditionIntoA(left: PtExpression, right: PtExpression) { fun equalf(leftName: String, rightName: String) { asmgen.out(""" diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index eef92c5fb..755cdbf4e 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -639,47 +639,11 @@ internal class AssignmentAsmGen( throw AssemblyError("Expression is too complex to translate into assembly. Split it up into several separate statements, introduce a temporary variable, or otherwise rewrite it. Location: $pos") } } - is PtIfExpression -> assignIfExpression(assign.target, value) + is PtIfExpression -> asmgen.assignIfExpression(assign.target, value) else -> throw AssemblyError("weird assignment value type $value") } } - private fun assignIfExpression(target: AsmAssignTarget, expr: PtIfExpression) { - // TODO don't store condition as expression result but just use the flags, like a normal PtIfElse translation does - require(target.datatype==expr.type) - val falseLabel = asmgen.makeLabel("ifexpr_false") - val endLabel = asmgen.makeLabel("ifexpr_end") - assignExpressionToRegister(expr.condition, RegisterOrPair.A, false) - asmgen.out(" beq $falseLabel") - when(expr.type) { - in ByteDatatypesWithBoolean -> { - assignExpressionToRegister(expr.truevalue, RegisterOrPair.A, false) - asmgen.jmp(endLabel) - asmgen.out(falseLabel) - assignExpressionToRegister(expr.falsevalue, RegisterOrPair.A, false) - asmgen.out(endLabel) - assignRegisterByte(target, CpuRegister.A, false, false) - } - in WordDatatypes -> { - assignExpressionToRegister(expr.truevalue, RegisterOrPair.AY, false) - asmgen.jmp(endLabel) - asmgen.out(falseLabel) - assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY, false) - asmgen.out(endLabel) - assignRegisterpairWord(target, RegisterOrPair.AY) - } - DataType.FLOAT -> { - assignExpressionToRegister(expr.truevalue, RegisterOrPair.FAC1, true) - asmgen.jmp(endLabel) - asmgen.out(falseLabel) - assignExpressionToRegister(expr.falsevalue, RegisterOrPair.FAC1, true) - asmgen.out(endLabel) - asmgen.assignRegister(RegisterOrPair.FAC1, target) - } - else -> throw AssemblyError("weird dt") - } - } - private fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment, scope: IPtSubroutine?) { require(assign.source.expression is PtPrefix) if(assign.source.datatype==DataType.FLOAT) { diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 979eb853a..e2b01fbab 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -39,8 +39,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe "ror" -> funcRolRor(call) "rol2" -> funcRolRor(call) "ror2" -> funcRolRor(call) - "prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse statement") - "prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse statement") + "prog8_ifelse_bittest_set" -> throw AssemblyError("prog8_ifelse_bittest_set() should have been translated as part of an ifElse/ifExpression statement") + "prog8_ifelse_bittest_notset" -> throw AssemblyError("prog8_ifelse_bittest_notset() should have been translated as part of an ifElse/ifExpression statement") "prog8_lib_stringcompare" -> funcStringCompare(call) "prog8_lib_square_byte" -> funcSquare(call, IRDataType.BYTE) "prog8_lib_square_word" -> funcSquare(call, IRDataType.WORD) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 13f0aaa75..3fdc2fd0e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -101,6 +101,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val endLabel = codeGen.createLabelName() addToResult(result, condTr, condTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null) addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null) if (irDt != IRDataType.FLOAT) { diff --git a/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt b/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt index 9501254bb..dc491fd5c 100644 --- a/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt +++ b/compiler/src/prog8/compiler/astprocessing/IntermediateAstPostprocess.kt @@ -122,6 +122,7 @@ private fun integrateDefers(subdefers: Map>, program: PtPro is PtNumber, is PtRange, is PtString -> true + // note that unlike most other times, PtIdentifier IS "complex" this time (it's a variable that might change) else -> false } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 4f258d9d3..00243a178 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,9 +1,6 @@ TODO ==== -- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) (assignIfExpression / translate(ifExpr: PtIfExpression)) - - Improve register load order in subroutine call args assignments: in certain situations, the "wrong" order of evaluation of function call arguments is done which results in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!) @@ -62,6 +59,7 @@ Libraries: Optimizations: +- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression" - VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served? for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest - various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, diff --git a/examples/test.p8 b/examples/test.p8 index af05876d9..2e3ff110f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import floats %import textio %option no_sysinit %zeropage basicsafe @@ -6,19 +5,15 @@ main { sub start() { - ubyte[4] values - uword[4] wvalues - float[4] fvalues - cx16.r0L = 0 - cx16.r1L = 3 - values[cx16.r0L+2] = if cx16.r1L>2 99 else 111 - wvalues[cx16.r0L+2] = if cx16.r1L>2 9999 else 1111 - fvalues[cx16.r0L+2] = if cx16.r1L>2 9.99 else 1.111 + word @shared ww = 1234 - txt.print_ub(values[2]) + txt.print_ub(if ww==0 111 else 222) + txt.spc() + txt.print_ub(if ww!=0 111 else 222) + txt.spc() + txt.print_ub(if ww==1000 111 else 222) + txt.spc() + txt.print_ub(if ww!=1000 111 else 222) txt.nl() - txt.print_uw(wvalues[2]) - txt.nl() - floats.print(fvalues[2]) } }