From ae4c4ca160957e8f38fb81ec774abdd29c999f8f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 24 Apr 2026 22:26:16 +0200 Subject: [PATCH] IR: optimized boolean expression codegen (use status flags directly more often instead of transforming to 1 / 0 values) --- .../prog8/codegen/intermediate/IRCodeGen.kt | 837 +++++------------- compiler/test/TestCompilerOnExamples.kt | 20 +- compiler/test/vm/TestCompilerVirtual.kt | 2 +- 3 files changed, 212 insertions(+), 647 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 872127d1c..a60ca2144 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1052,92 +1052,19 @@ class IRCodeGen( } private fun translateIfFollowedByJustGoto(ifElse: PtIfElse, goto: PtJump): MutableList { - val condition = ifElse.condition as? PtBinaryExpression - if(condition==null || !condition.left.type.isFloat) { - return if(isIndirectJump(goto)) - ifWithOnlyIndirectJump_IntegerCond(ifElse, goto) - else - ifWithOnlyNormalJump_IntegerCond(ifElse, goto) - } - - // floating-point condition only from here! - // we assume only a binary expression can contain a floating point. val result = mutableListOf() - val leftTr = expressionEval.translateExpression(condition.left) - addToResult(result, leftTr, -1, leftTr.resultFpReg) - val rightTr = expressionEval.translateExpression(condition.right) - addToResult(result, rightTr, -1, rightTr.resultFpReg) - var afterIfLabel = "" - result += IRCodeChunk(null, null).also { - val compResultReg = registers.next(IRDataType.BYTE) - it += IRInstruction( - Opcode.FCOMP, - IRDataType.FLOAT, - reg1 = compResultReg, - fpReg1 = RegisterNum(leftTr.resultFpReg), - fpReg2 = RegisterNum(rightTr.resultFpReg) - ) - - if(isIndirectJump(goto)) { - // indirect jump to target so the if has to jump past it instead - afterIfLabel = createLabelName() - when(condition.operator) { - "==" -> { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel) - } - "!=" -> { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel) - } - else -> { - val gotoOpcode = when (condition.operator) { - "<" -> Opcode.BGES - ">" -> Opcode.BLES - "<=" -> Opcode.BGTS - ">=" -> Opcode.BLTS - else -> throw AssemblyError("weird operator") - } - it += IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel) - } - } - // evaluate jump address expression into a register and jump indirectly to it - val tr = expressionEval.translateExpression(goto.target) - for(i in tr.chunks.flatMap { c -> c.instructions }) { - it += i - } - it += IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg) - } else { - // normal jump, directly to target with branch opcode - when(condition.operator) { - "==" -> { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += branchInstr(goto, Opcode.BSTEQ) - } - "!=" -> { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += branchInstr(goto, Opcode.BSTNE) - } - else -> { - val gotoOpcode = when (condition.operator) { - "<" -> Opcode.BLTS - ">" -> Opcode.BGTS - "<=" -> Opcode.BLES - ">=" -> Opcode.BGES - else -> throw AssemblyError("weird operator") - } - it += if (goto.target.asConstInteger() != null) - IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, address = goto.target.asConstInteger()?.toUInt()?.toAddress()) - else if(goto.target is PtIdentifier && !isIndirectJump(goto)) - IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = (goto.target as PtIdentifier).name) - else - throw AssemblyError("non-indirect jump shouldn't have an expression as target") - } - } - } - } - if(afterIfLabel.isNotEmpty()) + if (isIndirectJump(goto)) { + val afterIfLabel = createLabelName() + translateCondition(ifElse.condition, null, afterIfLabel, result) + val tr = expressionEval.translateExpression(goto.target) + result += tr.chunks + addInstr(result, IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg), null) result += IRCodeChunk(afterIfLabel, null) + } else { + val address = goto.target.asConstInteger()?.toUInt()?.toAddress() + val label = if (address == null) (goto.target as PtIdentifier).name else null + translateCondition(ifElse.condition, onTrueLabel = label, onTrueAddress = address, onFalseLabel = null, result = result) + } return result } @@ -1154,598 +1081,236 @@ class IRCodeGen( } } - private fun ifWithOnlyIndirectJump_IntegerCond(ifElse: PtIfElse, goto: PtJump): MutableList { - // indirect jump to target so the if has to jump past it instead - val result = mutableListOf() - val afterIfLabel = createLabelName() + private fun translateCondition( + condition: PtExpression, + onTrueLabel: String?, + onFalseLabel: String?, + result: MutableList, + onTrueAddress: MemoryAddress? = null, + onFalseAddress: MemoryAddress? = null + ) { + if (onTrueLabel == null && onFalseLabel == null && onTrueAddress == null && onFalseAddress == null) { + val tr = expressionEval.translateExpression(condition) + result += tr.chunks + return + } - fun ifNonZeroIntThenJump_BinExpr(condition: PtBinaryExpression) { - if(condition.operator in LogicalOperators) { - val trCond = expressionEval.translateExpression(condition) - result += trCond.chunks - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null) + if (condition is PtBinaryExpression) { + if (condition.operator == "or") { + val bodyLabel = onTrueLabel ?: createLabelName() + translateCondition(condition.left, bodyLabel, null, result) + translateCondition(condition.right, onTrueLabel, onFalseLabel, result, onTrueAddress, onFalseAddress) + if (onTrueLabel == null && onTrueAddress == null) result += IRCodeChunk(bodyLabel, null) return } - - val leftTr = expressionEval.translateExpression(condition.left) - val irDt = leftTr.dt - val signed = condition.left.type.isSigned - addToResult(result, leftTr, leftTr.resultReg, -1) - val number = (condition.right as? PtNumber)?.number?.toInt() - if(number!=null) { - val firstReg = leftTr.resultReg - when(condition.operator) { - "==" -> { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number) - it += IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel) - } - } - "!=" -> { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number) - it += IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel) - } - } - else -> { - val opcode = when (condition.operator) { - "<" -> if(signed) Opcode.BGES else Opcode.BGE - ">" -> if(signed) Opcode.BLES else Opcode.BLE - "<=" -> if(signed) Opcode.BGTS else Opcode.BGT - ">=" -> if(signed) Opcode.BLTS else Opcode.BLT - else -> throw AssemblyError("invalid comparison operator") - } - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, labelSymbol = afterIfLabel), null) - } - } - } else { - val rightTr = expressionEval.translateExpression(condition.right) - addToResult(result, rightTr, rightTr.resultReg, -1) - val firstReg: Int - val secondReg: Int - val opcode: Opcode - var useCmp = false - when (condition.operator) { - "==" -> { - useCmp = true - opcode = Opcode.BSTNE - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - "!=" -> { - useCmp = true - opcode = Opcode.BSTEQ - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - "<" -> { - opcode = if (signed) Opcode.BGESR else Opcode.BGER - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - ">" -> { - // swapped operands - opcode = if (signed) Opcode.BGESR else Opcode.BGER - firstReg = rightTr.resultReg - secondReg = leftTr.resultReg - } - "<=" -> { - opcode = if (signed) Opcode.BGTSR else Opcode.BGTR - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - ">=" -> { - // swapped operands - opcode = if (signed) Opcode.BGTSR else Opcode.BGTR - firstReg = rightTr.resultReg - secondReg = leftTr.resultReg - } - else -> throw AssemblyError("invalid comparison operator") - } - - if(useCmp) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMP, irDt, reg1 = firstReg, reg2 = secondReg) - it += IRInstruction(opcode, labelSymbol = afterIfLabel) - } + if (condition.operator == "and") { + val skipLabel = onFalseLabel ?: createLabelName() + translateCondition(condition.left, null, skipLabel, result) + translateCondition(condition.right, onTrueLabel, onFalseLabel, result, onTrueAddress, onFalseAddress) + if (onFalseLabel == null && onFalseAddress == null) result += IRCodeChunk(skipLabel, null) + return + } + if (condition.operator in ComparisonOperators) { + if (condition.left.type.isFloat) { + translateFloatComparison(condition, onTrueLabel, onTrueAddress, onFalseLabel, onFalseAddress, result) } else { - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, labelSymbol = afterIfLabel), null) + translateIntegerComparison(condition, onTrueLabel, onTrueAddress, onFalseLabel, onFalseAddress, result) } + return } } - - when(val cond = ifElse.condition) { - is PtTypeCast -> { - require(cond.type.isBool && cond.value.type.isNumeric) - val tr = expressionEval.translateExpression(cond) - result += tr.chunks - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null) - } - is PtIdentifier, is PtArrayIndexer, is PtFunctionCall, is PtContainmentCheck -> { - val tr = expressionEval.translateExpression(cond) - result += tr.chunks - addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = afterIfLabel), null) - } - is PtPrefix -> { - require(cond.operator=="not") - val tr = expressionEval.translateExpression(cond.value) - result += tr.chunks - addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = afterIfLabel), null) - } - is PtBinaryExpression -> { - ifNonZeroIntThenJump_BinExpr(cond) - } - else -> throw AssemblyError("weird if condition ${ifElse.condition}") + if (condition is PtPrefix && condition.operator == "not") { + translateCondition(condition.value, onFalseLabel, onTrueLabel, result, onFalseAddress, onTrueAddress) + return } - // indirect jump to some computed address - val tr = expressionEval.translateExpression(goto.target) + // Fallback: materialize expression and branch on result + val tr = expressionEval.translateExpression(condition) result += tr.chunks - addInstr(result, IRInstruction(Opcode.JUMPI, reg1 = tr.resultReg), null) - result += IRCodeChunk(afterIfLabel, null) - return result + if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { + addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = onTrueLabel, address = onTrueAddress), null) + addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + } else if (onTrueLabel != null || onTrueAddress != null) { + addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = onTrueLabel, address = onTrueAddress), null) + } else if (onFalseLabel != null || onFalseAddress != null) { + addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = onFalseLabel, address = onFalseAddress), null) + } } - private fun ifWithOnlyNormalJump_IntegerCond(ifElse: PtIfElse, goto: PtJump): MutableList { - // normal goto after if, using branch instructions - - val result = mutableListOf() - - fun ifNonZeroIntThenJump_BinExpr(condition: PtBinaryExpression) { - if(condition.operator in LogicalOperators) { - val trCond = expressionEval.translateExpression(condition) - result += trCond.chunks - addInstr(result, branchInstr(goto, Opcode.BSTNE), null) - return - } - - val leftTr = expressionEval.translateExpression(condition.left) - val irDt = leftTr.dt - val signed = condition.left.type.isSigned - addToResult(result, leftTr, leftTr.resultReg, -1) - val number = (condition.right as? PtNumber)?.number?.toInt() - if(number!=null) { - val firstReg = leftTr.resultReg - when(condition.operator) { - "==" -> { - addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number), null) - addInstr(result, branchInstr(goto, Opcode.BSTEQ), null) - } - "!=" -> { - addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = firstReg, immediate = number), null) - addInstr(result, branchInstr(goto, Opcode.BSTNE), null) - } - else -> { - val opcode = when (condition.operator) { - "<" -> if(signed) Opcode.BLTS else Opcode.BLT - ">" -> if(signed) Opcode.BGTS else Opcode.BGT - "<=" -> if(signed) Opcode.BLES else Opcode.BLE - ">=" -> if(signed) Opcode.BGES else Opcode.BGE - else -> throw AssemblyError("invalid comparison operator") - } - if (goto.target.asConstInteger() != null) - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, address = goto.target.asConstInteger()?.toUInt()?.toAddress()), null) - else if(goto.target is PtIdentifier && !isIndirectJump(goto)) - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, immediate = number, labelSymbol = (goto.target as PtIdentifier).name), null) - else - throw AssemblyError("non-indirect jump shouldn't have an expression as target") - } - } - } else { - val rightTr = expressionEval.translateExpression(condition.right) - addToResult(result, rightTr, rightTr.resultReg, -1) - val firstReg: Int - val secondReg: Int - val opcode: Opcode - var useCmp = false - when (condition.operator) { - "==" -> { - useCmp = true - opcode = Opcode.BSTEQ - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - "!=" -> { - useCmp = true - opcode = Opcode.BSTNE - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - "<" -> { - // swapped '>' - opcode = if (signed) Opcode.BGTSR else Opcode.BGTR - firstReg = rightTr.resultReg - secondReg = leftTr.resultReg - } - ">" -> { - opcode = if (signed) Opcode.BGTSR else Opcode.BGTR - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - "<=" -> { - // swapped '>=' - opcode = if (signed) Opcode.BGESR else Opcode.BGER - firstReg = rightTr.resultReg - secondReg = leftTr.resultReg - } - ">=" -> { - opcode = if (signed) Opcode.BGESR else Opcode.BGER - firstReg = leftTr.resultReg - secondReg = rightTr.resultReg - } - else -> throw AssemblyError("invalid comparison operator") - } - - if(useCmp) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMP, irDt, reg1 = firstReg, reg2 = secondReg) - it += branchInstr(goto, opcode) - } - } else { - if (goto.target.asConstInteger() != null) - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, address = goto.target.asConstInteger()?.toUInt()?.toAddress()), null) - else if(goto.target is PtIdentifier && !isIndirectJump(goto)) - addInstr(result, IRInstruction(opcode, irDt, reg1 = firstReg, reg2 = secondReg, labelSymbol = (goto.target as PtIdentifier).name), null) - else - throw AssemblyError("non-indirect jump shouldn't have an expression as target") - } + private fun translateIntegerComparison(condition: PtBinaryExpression, onTrueLabel: String?, onTrueAddress: MemoryAddress?, onFalseLabel: String?, onFalseAddress: MemoryAddress?, result: MutableList) { + val useBIT = expressionEval.checkIfConditionCanUseBIT(condition) + if (useBIT != null) { + val (testBitSet, variable, bitmask) = useBIT + addInstr(result, IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name), null) + if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { + addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null) + } else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) { + addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, !testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null) + } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { + addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null) + addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) } + return } - when(val cond = ifElse.condition) { - is PtTypeCast -> { - require(cond.type.isBool && cond.value.type.isNumeric) - val tr = expressionEval.translateExpression(cond) - result += tr.chunks - addInstr(result, branchInstr(goto, Opcode.BSTNE), null) + val signed = condition.left.type.isSigned + val number = (condition.right as? PtNumber)?.number?.toInt() + val leftTr = expressionEval.translateExpression(condition.left) + val branchDt = leftTr.dt + addToResult(result, leftTr, leftTr.resultReg, -1) + + if (number != null) { + val isComparingWithZero = number == 0 + if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { + val (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed, isComparingWithZero) + emitIntegerComparisonBranch(result, opcode, useCmpi, branchDt, leftTr.resultReg, number, onTrueLabel, onTrueAddress) + } else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) { + val (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, true, signed, isComparingWithZero) + emitIntegerComparisonBranch(result, opcode, useCmpi, branchDt, leftTr.resultReg, number, onFalseLabel, onFalseAddress) + } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { + val (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed, isComparingWithZero) + emitIntegerComparisonBranch(result, opcode, useCmpi, branchDt, leftTr.resultReg, number, onTrueLabel, onTrueAddress) + addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) } - is PtIdentifier, is PtArrayIndexer, is PtFunctionCall, is PtContainmentCheck -> { - val tr = expressionEval.translateExpression(cond) - result += tr.chunks - addInstr(result, branchInstr(goto, Opcode.BSTNE), null) + } else { + val rightTr = expressionEval.translateExpression(condition.right) + addToResult(result, rightTr, rightTr.resultReg, -1) + if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { + val res = getIntegerComparisonRegBranch(condition.operator, false, signed, leftTr.resultReg, rightTr.resultReg) + emitIntegerComparisonRegBranch(result, res.opcode, res.useCmp, branchDt, res.leftReg, res.rightReg, onTrueLabel, onTrueAddress) + } else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) { + val res = getIntegerComparisonRegBranch(condition.operator, true, signed, leftTr.resultReg, rightTr.resultReg) + emitIntegerComparisonRegBranch(result, res.opcode, res.useCmp, branchDt, res.leftReg, res.rightReg, onFalseLabel, onFalseAddress) + } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { + val res = getIntegerComparisonRegBranch(condition.operator, false, signed, leftTr.resultReg, rightTr.resultReg) + emitIntegerComparisonRegBranch(result, res.opcode, res.useCmp, branchDt, res.leftReg, res.rightReg, onTrueLabel, onTrueAddress) + addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) } - is PtPrefix -> { - require(cond.operator=="not") - val tr = expressionEval.translateExpression(cond.value) - result += tr.chunks - addInstr(result, branchInstr(goto, Opcode.BSTEQ), null) - } - is PtBinaryExpression -> ifNonZeroIntThenJump_BinExpr(cond) - else -> throw AssemblyError("weird if condition ${ifElse.condition}") } - return result } - private fun translateIfElse(ifElse: PtIfElse): IRCodeChunks { - if((ifElse.condition as? PtPrefix)?.operator=="not" && ifElse.hasElse()) - throw AssemblyError("not prefix in ifelse should have been replaced by swapped if-else blocks") - - val condition = ifElse.condition as? PtBinaryExpression - if(condition==null || !condition.left.type.isFloat) { - return ifElse_IntegerCond(ifElse) - } - - // we assume only a binary expression can contain a floating point. - val result = mutableListOf() + private fun translateFloatComparison(condition: PtBinaryExpression, onTrueLabel: String?, onTrueAddress: MemoryAddress?, onFalseLabel: String?, onFalseAddress: MemoryAddress?, result: MutableList) { val leftTr = expressionEval.translateExpression(condition.left) addToResult(result, leftTr, -1, leftTr.resultFpReg) val rightTr = expressionEval.translateExpression(condition.right) addToResult(result, rightTr, -1, rightTr.resultFpReg) val compResultReg = registers.next(IRDataType.BYTE) addInstr(result, IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1 = compResultReg, fpReg1 = RegisterNum(leftTr.resultFpReg), fpReg2 = RegisterNum(rightTr.resultFpReg)), null) - val elseBranch: Opcode - var useCmpi = false // for the branch opcodes that have been converted to CMPI + BSTxx form already - when (condition.operator) { - "==" -> { - elseBranch = Opcode.BSTNE - useCmpi = true - } - "!=" -> { - elseBranch = Opcode.BSTEQ - useCmpi = true - } - "<" -> elseBranch = Opcode.BGES - ">" -> elseBranch = Opcode.BLES - "<=" -> elseBranch = Opcode.BGTS - ">=" -> elseBranch = Opcode.BLTS + + if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) { + val (opcode, useCmpi) = getFloatComparisonBranch(condition.operator, false) + emitIntegerComparisonBranch(result, opcode, useCmpi, IRDataType.BYTE, compResultReg, 0, onTrueLabel, onTrueAddress) + } else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) { + val (opcode, useCmpi) = getFloatComparisonBranch(condition.operator, true) + emitIntegerComparisonBranch(result, opcode, useCmpi, IRDataType.BYTE, compResultReg, 0, onFalseLabel, onFalseAddress) + } else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) { + val (opcode, useCmpi) = getFloatComparisonBranch(condition.operator, false) + emitIntegerComparisonBranch(result, opcode, useCmpi, IRDataType.BYTE, compResultReg, 0, onTrueLabel, onTrueAddress) + addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null) + } + } + + private fun getBitBranchOpcode(bitmask: Int, wantSet: Boolean): Opcode { + return if (wantSet) { + if (bitmask == 64) Opcode.BSTVS else Opcode.BSTNEG + } else { + if (bitmask == 64) Opcode.BSTVC else Opcode.BSTPOS + } + } + + private fun invertOperator(operator: String) = when (operator) { + "==" -> "!=" + "!=" -> "==" + "<" -> ">=" + ">" -> "<=" + "<=" -> ">" + ">=" -> "<" + else -> throw AssemblyError("invalid operator to invert") + } + + private fun getIntegerComparisonBranch(operator: String, invert: Boolean, signed: Boolean, isComparingWithZero: Boolean): Pair { + val op = if (invert) invertOperator(operator) else operator + return when (op) { + "==" -> Opcode.BSTEQ to true + "!=" -> Opcode.BSTNE to true + "<" -> (if (signed) Opcode.BLTS else Opcode.BLT) to false + ">" -> (if (signed) Opcode.BGTS else Opcode.BGT) to false + "<=" -> (if (signed) Opcode.BLES else Opcode.BLE) to false + ">=" -> (if (signed) Opcode.BGES else Opcode.BGE) to false + else -> throw AssemblyError("invalid comparison operator") + } + } + + private data class RegBranchResult(val opcode: Opcode, val useCmp: Boolean, val leftReg: Int, val rightReg: Int) + + private fun getIntegerComparisonRegBranch(operator: String, invert: Boolean, signed: Boolean, leftReg: Int, rightReg: Int): RegBranchResult { + val op = if (invert) invertOperator(operator) else operator + return when (op) { + "==" -> RegBranchResult(Opcode.BSTEQ, true, leftReg, rightReg) + "!=" -> RegBranchResult(Opcode.BSTNE, true, leftReg, rightReg) + "<" -> if (signed) RegBranchResult(Opcode.BGTSR, false, rightReg, leftReg) else RegBranchResult(Opcode.BGTR, false, rightReg, leftReg) + ">" -> if (signed) RegBranchResult(Opcode.BGTSR, false, leftReg, rightReg) else RegBranchResult(Opcode.BGTR, false, leftReg, rightReg) + "<=" -> if (signed) RegBranchResult(Opcode.BGESR, false, rightReg, leftReg) else RegBranchResult(Opcode.BGER, false, rightReg, leftReg) + ">=" -> if (signed) RegBranchResult(Opcode.BGESR, false, leftReg, rightReg) else RegBranchResult(Opcode.BGER, false, leftReg, rightReg) + else -> throw AssemblyError("invalid comparison operator") + } + } + + private fun getFloatComparisonBranch(operator: String, invert: Boolean): Pair { + val op = if (invert) invertOperator(operator) else operator + return when (op) { + "==" -> Opcode.BSTEQ to true + "!=" -> Opcode.BSTNE to true + "<" -> Opcode.BLTS to false + ">" -> Opcode.BGTS to false + "<=" -> Opcode.BLES to false + ">=" -> Opcode.BGES to false else -> throw AssemblyError("weird operator") } + } + private fun emitIntegerComparisonBranch(result: MutableList, opcode: Opcode, useCmpi: Boolean, dt: IRDataType, reg: Int, immediate: Int, label: String?, address: MemoryAddress?) { + if (useCmpi) { + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.CMPI, dt, reg1 = reg, immediate = immediate) + it += IRInstruction(opcode, labelSymbol = label, address = address) + } + } else if (opcode in setOf(Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTPOS, Opcode.BSTNEG, Opcode.BSTCS, Opcode.BSTCC, Opcode.BSTVS, Opcode.BSTVC)) { + addInstr(result, IRInstruction(opcode, labelSymbol = label, address = address), null) + } else { + addInstr(result, IRInstruction(opcode, dt, reg1 = reg, immediate = immediate, labelSymbol = label, address = address), null) + } + } + + private fun emitIntegerComparisonRegBranch(result: MutableList, opcode: Opcode, useCmp: Boolean, dt: IRDataType, leftReg: Int, rightReg: Int, label: String?, address: MemoryAddress?) { + if (useCmp) { + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.CMP, dt, reg1 = leftReg, reg2 = rightReg) + it += IRInstruction(opcode, labelSymbol = label, address = address) + } + } else { + addInstr(result, IRInstruction(opcode, dt, reg1 = leftReg, reg2 = rightReg, labelSymbol = label, address = address), null) + } + } + + + private fun translateIfElse(ifElse: PtIfElse): IRCodeChunks { + val result = mutableListOf() + val afterIfLabel = createLabelName() if (ifElse.hasElse()) { - // if and else parts val elseLabel = createLabelName() - val afterIfLabel = createLabelName() - if(useCmpi) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += IRInstruction(elseBranch, labelSymbol = elseLabel) - } - } else - addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = elseLabel), null) + translateCondition(ifElse.condition, null, elseLabel, result) result += translateNode(ifElse.ifScope) addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) - result += IRCodeChunk(afterIfLabel, null) } else { - // only if part - val afterIfLabel = createLabelName() - if(useCmpi) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = compResultReg, immediate = 0) - it += IRInstruction(elseBranch, labelSymbol = afterIfLabel) - } - } else - addInstr(result, IRInstruction(elseBranch, IRDataType.BYTE, reg1 = compResultReg, immediate = 0, labelSymbol = afterIfLabel), null) + translateCondition(ifElse.condition, null, afterIfLabel, result) result += translateNode(ifElse.ifScope) - result += IRCodeChunk(afterIfLabel, null) } + result += IRCodeChunk(afterIfLabel, null) return result } - private fun ifElse_IntegerCond(ifElse: PtIfElse): List { - val result = mutableListOf() - - fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode, addCmpiZero: Boolean) { - val tr = expressionEval.translateExpression(condition) - if(addCmpiZero) - tr.chunks.last().instructions.add(IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0)) - result += tr.chunks - if(ifElse.hasElse()) { - val elseLabel = createLabelName() - val afterIfLabel = createLabelName() - addInstr(result, IRInstruction(jumpFalseOpcode, labelSymbol = elseLabel), null) - result += translateNode(ifElse.ifScope) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) - result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) - result += IRCodeChunk(afterIfLabel, null) - } else { - val afterIfLabel = createLabelName() - addInstr(result, IRInstruction(jumpFalseOpcode, labelSymbol = afterIfLabel), null) - result += translateNode(ifElse.ifScope) - result += IRCodeChunk(afterIfLabel, null) - } - } - - fun translateBinExpr(condition: PtBinaryExpression) { - if(condition.operator in LogicalOperators) - return translateSimple(condition, Opcode.BSTEQ, false) - - val useBIT = expressionEval.checkIfConditionCanUseBIT(condition) - if(useBIT!=null) { - // use a BIT instruction to test for bit 7 or 6 set/clear - val (testBitSet, variable, bitmask) = useBIT - addInstr(result, IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name), null) - val bitBranchOpcode = when(testBitSet) { - true -> when(bitmask) { - 64 -> Opcode.BSTVC - 128 -> Opcode.BSTPOS - else -> throw AssemblyError("need bit 6 or 7") - } - false -> when(bitmask) { - 64 -> Opcode.BSTVS - 128 -> Opcode.BSTNEG - else -> throw AssemblyError("need bit 6 or 7") - } - } - - if(ifElse.hasElse()) { - val elseLabel = createLabelName() - val afterIfLabel = createLabelName() - addInstr(result, IRInstruction(bitBranchOpcode, labelSymbol = elseLabel), null) - result += translateNode(ifElse.ifScope) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) - result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) - result += IRCodeChunk(afterIfLabel, null) - } else { - val afterIfLabel = createLabelName() - addInstr(result, IRInstruction(bitBranchOpcode, labelSymbol = afterIfLabel), null) - result += translateNode(ifElse.ifScope) - result += IRCodeChunk(afterIfLabel, null) - } - return - } - - val signed = condition.left.type.isSigned - val elseBranchFirstReg: Int - val elseBranchSecondReg: Int - val number = (condition.right as? PtNumber)?.number?.toInt() - - val leftTr = expressionEval.translateExpression(condition.left) - val branchDt = leftTr.dt - addToResult(result, leftTr, leftTr.resultReg, -1) - if (number!=null) { - val elseBranch: Opcode - var useCmpi: Boolean // for the branch opcodes that have been converted to CMPI + BSTxx form already - - // Optimization: when comparing signed integer with 0, we can use direct status flag branches - // instead of CMPI #0 + branch, because the previous LOAD instruction already sets the N and Z flags. - val isComparingWithZero = number == 0 - when (condition.operator) { - "==" -> { - elseBranch = Opcode.BSTNE - useCmpi = true - } - "!=" -> { - elseBranch = Opcode.BSTEQ - useCmpi = true - } - "<" -> { - if (isComparingWithZero && signed) { - // x < 0 → skip when x >= 0 (positive or zero, N flag clear) - elseBranch = Opcode.BSTPOS - useCmpi = false - } else { - elseBranch = if(signed) Opcode.BGES else Opcode.BGE - useCmpi = false // BGES/BGE do their own comparison - } - } - ">" -> { - elseBranch = if(signed) Opcode.BLES else Opcode.BLE - useCmpi = false // BLES/BLE do their own comparison - } - "<=" -> { - elseBranch = if(signed) Opcode.BGTS else Opcode.BGT - useCmpi = false // BGTS/BGT do their own comparison - } - ">=" -> { - if (isComparingWithZero && signed) { - // x >= 0 → skip when x < 0 (negative, N flag set) - elseBranch = Opcode.BSTNEG - useCmpi = false - } else { - elseBranch = if(signed) Opcode.BLTS else Opcode.BLT - useCmpi = false // BLTS/BLT do their own comparison - } - } - else -> throw AssemblyError("invalid comparison operator") - } - - if (ifElse.hasElse()) { - // if and else parts - val elseLabel = createLabelName() - val afterIfLabel = createLabelName() - if(useCmpi) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number) - it += IRInstruction(elseBranch, labelSymbol = elseLabel) - } - } else if(elseBranch in setOf(Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTPOS, Opcode.BSTNEG, Opcode.BSTCS, Opcode.BSTCC, Opcode.BSTVS, Opcode.BSTVC)) { - // Direct status flag branch (BSTxx) - only needs labelSymbol, flags set by previous instruction - addInstr(result, IRInstruction(elseBranch, labelSymbol = elseLabel), null) - } else { - // Comparison branch (BGx/BLx etc.) - does its own comparison, needs full params - addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = elseLabel), null) - } - result += translateNode(ifElse.ifScope) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) - result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) - result += IRCodeChunk(afterIfLabel, null) - } else { - // only if part - val afterIfLabel = createLabelName() - if(useCmpi) { - result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.CMPI, branchDt, reg1 = leftTr.resultReg, immediate = number) - it += IRInstruction(elseBranch, labelSymbol = afterIfLabel) - } - } else if(elseBranch in setOf(Opcode.BSTEQ, Opcode.BSTNE, Opcode.BSTPOS, Opcode.BSTNEG, Opcode.BSTCS, Opcode.BSTCC, Opcode.BSTVS, Opcode.BSTVC)) { - // Direct status flag branch (BSTxx) - only needs labelSymbol, flags set by previous instruction - addInstr(result, IRInstruction(elseBranch, labelSymbol = afterIfLabel), null) - } else { - // Comparison branch (BGx/BLx etc.) - does its own comparison, needs full params - addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = leftTr.resultReg, immediate = number, labelSymbol = afterIfLabel), null) - } - result += translateNode(ifElse.ifScope) - result += IRCodeChunk(afterIfLabel, null) - } - } else { - val rightTr = expressionEval.translateExpression(condition.right) - val elseBranch: Opcode - var useCmp = false - addToResult(result, rightTr, rightTr.resultReg, -1) - when (condition.operator) { - "==" -> { - useCmp = true - elseBranch = Opcode.BSTNE - elseBranchFirstReg = leftTr.resultReg - elseBranchSecondReg = rightTr.resultReg - } - "!=" -> { - useCmp = true - elseBranch = Opcode.BSTEQ - elseBranchFirstReg = leftTr.resultReg - elseBranchSecondReg = rightTr.resultReg - } - "<" -> { - // else part when left >= right - elseBranch = if (signed) Opcode.BGESR else Opcode.BGER - elseBranchFirstReg = leftTr.resultReg - elseBranchSecondReg = rightTr.resultReg - } - ">" -> { - // else part when left <= right --> right >= left - elseBranch = if (signed) Opcode.BGESR else Opcode.BGER - elseBranchFirstReg = rightTr.resultReg - elseBranchSecondReg = leftTr.resultReg - } - "<=" -> { - // else part when left > right - elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR - elseBranchFirstReg = leftTr.resultReg - elseBranchSecondReg = rightTr.resultReg - } - ">=" -> { - // else part when left < right --> right > left - elseBranch = if (signed) Opcode.BGTSR else Opcode.BGTR - elseBranchFirstReg = rightTr.resultReg - elseBranchSecondReg = leftTr.resultReg - } - else -> throw AssemblyError("invalid comparison operator") - } - - if (ifElse.hasElse()) { - // if and else parts - val elseLabel = createLabelName() - val afterIfLabel = createLabelName() - if(useCmp) { - result += IRCodeChunk(null,null).also { - it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg) - it += IRInstruction(elseBranch, labelSymbol = elseLabel) - } - } else { - addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = elseLabel), null) - } - result += translateNode(ifElse.ifScope) - addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null) - result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel) - result += IRCodeChunk(afterIfLabel, null) - } else { - // only if part - val afterIfLabel = createLabelName() - if(useCmp) { - result += IRCodeChunk(null,null).also { - it += IRInstruction(Opcode.CMP, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg) - it += IRInstruction(elseBranch, labelSymbol = afterIfLabel) - } - } else { - addInstr(result, IRInstruction(elseBranch, branchDt, reg1 = elseBranchFirstReg, reg2 = elseBranchSecondReg, labelSymbol = afterIfLabel), null) - } - result += translateNode(ifElse.ifScope) - result += IRCodeChunk(afterIfLabel, null) - } - } - } - - when(val cond=ifElse.condition) { - // TODO investigate; maybe do all conditions require a CMPI at the end? Some here still have false, but do they work correctly in all cases? - is PtBool -> { - // normally this will be optimized away, but not with -noopt - translateSimple(cond, Opcode.BSTEQ, false) - } - is PtTypeCast -> { - require(cond.type.isBool && (cond.value.type.isNumeric || cond.value.type.isPointer)) - translateSimple(cond, Opcode.BSTEQ, false) - } - is PtIdentifier, is PtArrayIndexer, is PtContainmentCheck -> { - translateSimple(cond, Opcode.BSTEQ, false) - } - is PtFunctionCall -> { - translateSimple(cond, Opcode.BSTEQ, true) - } - is PtPrefix -> { - require(cond.operator=="not") - translateSimple(cond.value, Opcode.BSTNE, true) - } - is PtBinaryExpression -> { - translateBinExpr(cond) - } - is PtPointerDeref -> { - translateSimple(cond, Opcode.BSTEQ, false) - } - else -> throw AssemblyError("weird if condition ${ifElse.condition}") - } - return result - } private fun translate(repeat: PtRepeatLoop): IRCodeChunks { when (repeat.count.asConstInteger()) { diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index f4abdd7bf..f28de9abf 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -336,17 +336,17 @@ class TestCompilerOnExamplesVirtual: FunSpec({ val onlyVirtual = cartesianProduct( listOf( - ExampleSizes("bouncegfx", virtualInstrCountOptimized=208, virtualInstrCountUnoptimized=878, virtualRegCountOptimized=100, virtualRegCountUnoptimized=396), - ExampleSizes("bsieve", virtualInstrCountOptimized=158, virtualInstrCountUnoptimized=2070, virtualRegCountOptimized=66, virtualRegCountUnoptimized=892), - ExampleSizes("fountain", virtualInstrCountOptimized=152, virtualInstrCountUnoptimized=820, virtualRegCountOptimized=77, virtualRegCountUnoptimized=371), + ExampleSizes("bouncegfx", virtualInstrCountOptimized=186, virtualInstrCountUnoptimized=834, virtualRegCountOptimized=92, virtualRegCountUnoptimized=380), + ExampleSizes("bsieve", virtualInstrCountOptimized=158, virtualInstrCountUnoptimized=1953, virtualRegCountOptimized=66, virtualRegCountUnoptimized=860), + ExampleSizes("fountain", virtualInstrCountOptimized=152, virtualInstrCountUnoptimized=802, virtualRegCountOptimized=77, virtualRegCountUnoptimized=363), ExampleSizes("pixelshader", virtualInstrCountOptimized=43, virtualInstrCountUnoptimized=291, virtualRegCountOptimized=15, virtualRegCountUnoptimized=109), - ExampleSizes("sincos", virtualInstrCountOptimized=276, virtualInstrCountUnoptimized=926, virtualRegCountOptimized=133, virtualRegCountUnoptimized=411), - ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=221, virtualInstrCountUnoptimized=2213, virtualRegCountOptimized=103, virtualRegCountUnoptimized=965), - ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=510, virtualInstrCountUnoptimized=2384, virtualRegCountOptimized=251, virtualRegCountUnoptimized=1052), - ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=578, virtualInstrCountUnoptimized=2395, virtualRegCountOptimized=293, virtualRegCountUnoptimized=1077), - ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=267, virtualInstrCountUnoptimized=2572, virtualRegCountOptimized=118, virtualRegCountUnoptimized=1126), - ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=146, virtualInstrCountUnoptimized=815, virtualRegCountOptimized=71, virtualRegCountUnoptimized=365), - ExampleSizes("pointers/sorting", virtualInstrCountOptimized=369, virtualInstrCountUnoptimized=2382, virtualRegCountOptimized=173, virtualRegCountUnoptimized=1042) + ExampleSizes("sincos", virtualInstrCountOptimized=276, virtualInstrCountUnoptimized=908, virtualRegCountOptimized=133, virtualRegCountUnoptimized=407), + ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=221, virtualInstrCountUnoptimized=2097, virtualRegCountOptimized=103, virtualRegCountUnoptimized=933), + ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=510, virtualInstrCountUnoptimized=2268, virtualRegCountOptimized=251, virtualRegCountUnoptimized=1020), + ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=578, virtualInstrCountUnoptimized=2279, virtualRegCountOptimized=293, virtualRegCountUnoptimized=1045), + ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=267, virtualInstrCountUnoptimized=2434, virtualRegCountOptimized=118, virtualRegCountUnoptimized=1086), + ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=146, virtualInstrCountUnoptimized=797, virtualRegCountOptimized=71, virtualRegCountUnoptimized=357), + ExampleSizes("pointers/sorting", virtualInstrCountOptimized=369, virtualInstrCountUnoptimized=2266, virtualRegCountOptimized=173, virtualRegCountUnoptimized=1010) ), listOf(false, true) ) diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index fd1b1607d..eccac1766 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -647,7 +647,7 @@ main { val irProgram = IRFileReader().read(virtfile) val func = irProgram.blocks[0].children[1] as IRSubroutine func.label shouldBe "main.func" - func.chunks.size shouldBe 11 + func.chunks.size shouldBe 4 for(chunk in func.chunks) { if(chunk.next == null) { chunk.instructions.last().opcode shouldBeIn OpcodesThatBranchUnconditionally