shortcutting part one

This commit is contained in:
Irmen de Jong
2023-12-30 02:18:56 +01:00
parent 8f18b5b8a7
commit 1c55a6c6dc
7 changed files with 152 additions and 70 deletions

View File

@@ -1,6 +1,6 @@
package prog8.code.core package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=") val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not") val LogicalOperators = setOf("and", "or", "xor", "not")
val BitwiseOperators = setOf("&", "|", "^", "~") val BitwiseOperators = setOf("&", "|", "^", "~")

View File

@@ -1041,39 +1041,60 @@ internal class AssignmentAsmGen(private val program: PtProgram,
private fun optimizedLogicalAndOrExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean { private fun optimizedLogicalAndOrExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) { if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) { if (expr.right is PtNumber || expr.right is PtIdentifier) {
if (expr.right is PtNumber || expr.right is PtIdentifier) { assignLogicalAndOrWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right)
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right) return true
return true }
} else if (expr.left is PtNumber || expr.left is PtIdentifier) {
else if (expr.left is PtNumber || expr.left is PtIdentifier) { assignLogicalAndOrWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left)
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left) return true
return true
}
} }
assignExpressionToRegister(expr.left, RegisterOrPair.A, false) if(asmgen.options.shortCircuit && (!expr.left.isSimple() && !expr.right.isSimple())) {
asmgen.saveRegisterStack(CpuRegister.A, false) // shortcircuit evaluation into A
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE) val shortcutLabel = asmgen.makeLabel("shortcut")
asmgen.restoreRegisterStack(CpuRegister.A, false) when (expr.operator) {
when (expr.operator) { "and" -> {
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}") // short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}") println("SHORTCUT AND ${expr.position}") // TODO weg
else -> throw AssemblyError("invalid logical operator") assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" beq $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
asmgen.out(shortcutLabel)
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
println("SHORTCUT OR ${expr.position}") // TODO weg
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" bne $shortcutLabel")
assignExpressionToRegister(expr.right, RegisterOrPair.A, false)
asmgen.out(shortcutLabel)
}
else -> throw AssemblyError("invalid logical operator")
}
} else {
// normal evaluation into A
assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (expr.operator) {
"and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid logical operator")
}
} }
assignRegisterByte(target, CpuRegister.A, false, true) assignRegisterByte(target, CpuRegister.A, false, true)
return true return true
} }
else if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) { else if (expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
if (expr.right.isSimple()) { if (expr.right is PtNumber || expr.right is PtIdentifier) {
if (expr.right is PtNumber || expr.right is PtIdentifier) { assignLogicalAndOrWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right)
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right) return true
return true }
} else if (expr.left is PtNumber || expr.left is PtIdentifier) {
else if (expr.left is PtNumber || expr.left is PtIdentifier) { assignLogicalAndOrWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left)
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left) return true
return true
}
} }
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1") asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
when (expr.operator) { when (expr.operator) {
@@ -1775,11 +1796,15 @@ internal class AssignmentAsmGen(private val program: PtProgram,
} }
private fun assignLogicalAndOrWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) { private fun assignLogicalAndOrWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
// normal evaluation, not worth to shortcircuit the simple right operand
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(right, RegisterOrPair.A, false)
when (operator) { when (operator) {
"and" -> TODO("logical and (with optional shortcircuit) ${left.position}") "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"or" -> TODO("logical or (with optional shortcircuit) ${left.position}") "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid logical operator") else -> throw AssemblyError("invalid logical operator")
} }
assignRegisterByte(target, CpuRegister.A, false, true)
} }
private fun assignBitwiseWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) { private fun assignBitwiseWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {

View File

@@ -37,8 +37,10 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"*=" -> inplaceModification(assign.target, "*", assign.source) "*=" -> inplaceModification(assign.target, "*", assign.source)
"/=" -> inplaceModification(assign.target, "/", assign.source) "/=" -> inplaceModification(assign.target, "/", assign.source)
"|=" -> inplaceModification(assign.target, "|", assign.source) "|=" -> inplaceModification(assign.target, "|", assign.source)
"or=" -> inplaceModification(assign.target, "or", assign.source)
"&=" -> inplaceModification(assign.target, "&", assign.source) "&=" -> inplaceModification(assign.target, "&", assign.source)
"^=" -> inplaceModification(assign.target, "^", assign.source) "and=" -> inplaceModification(assign.target, "and", assign.source)
"^=", "xor=" -> inplaceModification(assign.target, "^", assign.source)
"<<=" -> inplaceModification(assign.target, "<<", assign.source) "<<=" -> inplaceModification(assign.target, "<<", assign.source)
">>=" -> inplaceModification(assign.target, ">>", assign.source) ">>=" -> inplaceModification(assign.target, ">>", assign.source)
"%=" -> inplaceModification(assign.target, "%", assign.source) "%=" -> inplaceModification(assign.target, "%", assign.source)
@@ -791,12 +793,42 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} }
private fun inplacemodificationByteVariableWithValue(name: String, dt: DataType, operator: String, value: PtExpression) { private fun inplacemodificationByteVariableWithValue(name: String, dt: DataType, operator: String, value: PtExpression) {
if(asmgen.options.shortCircuit) {
val shortcutLabel = asmgen.makeLabel("shortcut")
when (operator) {
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
println("SHORTCUT AND ${value.position}") // TODO weg
asmgen.out(" lda $name | beq $shortcutLabel")
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out("""
and $name
sta $name
$shortcutLabel:""")
return
}
"or" -> {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
println("SHORTCUT OR ${value.position}") // TODO weg
asmgen.out(" lda $name | bne $shortcutLabel")
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
asmgen.out("""
ora $name
sta $name
$shortcutLabel:""")
return
}
}
}
// normal evaluation
asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes) asmgen.assignExpressionToRegister(value, RegisterOrPair.A, dt in SignedDatatypes)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes) inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
asmgen.out(" sta $name") asmgen.out(" sta $name")
} }
private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) { private fun inplacemodificationByteVariableWithVariable(name: String, dt: DataType, operator: String, otherName: String) {
// note: no logical and/or shortcut here, not worth it due to simple right operand
asmgen.out(" lda $name") asmgen.out(" lda $name")
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes) inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
asmgen.out(" sta $name") asmgen.out(" sta $name")
@@ -852,6 +884,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} }
"&" -> asmgen.out(" and $variable") "&" -> asmgen.out(" and $variable")
"|" -> asmgen.out(" ora $variable") "|" -> asmgen.out(" ora $variable")
"and", "or" -> throw AssemblyError("logical and/or should have been handled earlier because of shortcircuit handling")
"^" -> asmgen.out(" eor $variable") "^" -> asmgen.out(" eor $variable")
"==" -> { "==" -> {
asmgen.out(""" asmgen.out("""
@@ -1130,6 +1163,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private fun inplacemodificationByteVariableWithLiteralval(name: String, dt: DataType, operator: String, value: Int) { private fun inplacemodificationByteVariableWithLiteralval(name: String, dt: DataType, operator: String, value: Int) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine. // note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
// note: no logical and/or shortcut here, not worth it due to simple right operand
when (operator) { when (operator) {
"+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name") "+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name") "-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name")
@@ -1192,9 +1226,9 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
} }
} }
} }
"&" -> immediateAndInplace(name, value) "&", "and" -> immediateAndInplace(name, value)
"|" -> immediateOrInplace(name, value) "|", "or" -> immediateOrInplace(name, value)
"^" -> asmgen.out(" lda $name | eor #$value | sta $name") "^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> { "==" -> {
asmgen.out(""" asmgen.out("""
lda $name lda $name

View File

@@ -717,6 +717,7 @@ Logical expressions are expressions that calculate a boolean result: true or fal
(which in reality are just a 1 or 0 integer value). When using variables of the type ``bool``, (which in reality are just a 1 or 0 integer value). When using variables of the type ``bool``,
logical expressions will compile more efficiently than when you're using regular integer type operands logical expressions will compile more efficiently than when you're using regular integer type operands
(because these have to be converted to 0 or 1 every time) (because these have to be converted to 0 or 1 every time)
Prog8 applies short-circuit aka McCarthy evaluation for ``and`` and ``or`` on boolean expressions.
You can use parentheses to group parts of an expression to change the precedence. You can use parentheses to group parts of an expression to change the precedence.
Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but subexpressions

View File

@@ -599,18 +599,13 @@ logical: ``not`` ``and`` ``or`` ``xor``
about truths (boolean values). The result of such an expression is a 'boolean' value 'true' or 'false' about truths (boolean values). The result of such an expression is a 'boolean' value 'true' or 'false'
(which in reality is just a byte value of 1 or 0). (which in reality is just a byte value of 1 or 0).
Notice that the expression ``not x`` is equivalent to ``x==0``, and the compiler will treat it as such. Notice that the expression ``not x`` is equivalent to ``x==0``, and the compiler will treat it as such.
Prog8 applies short-circuit aka McCarthy evaluation for ``and`` and ``or``.
.. note:: .. note::
You can use regular integers directly in logical expressions but these have to be converted to You can use regular integers directly in logical expressions but these have to be converted to
the boolean value 0 or 1 every time, resulting in larger and slower code. Consider using the boolean value 0 or 1 every time, resulting in larger and slower code. Consider using
the ``bool`` variable type instead, where this conversion doesn't need to occur. the ``bool`` variable type instead, where this conversion doesn't need to occur.
.. note::
Unlike most other programming languages, there is no short-circuit or McCarthy evaluation
for the logical ``and`` and ``or`` operators. This means that prog8 currently always evaluates
all operands from these logical expressions, even when one of them already determines the outcome!
If you don't want this to happen, you have to split and nest the if-statements yourself.
range creation: ``to``, ``downto`` range creation: ``to``, ``downto``
Creates a range of values from the LHS value to the RHS value, inclusive. Creates a range of values from the LHS value to the RHS value, inclusive.
These are mainly used in for loops to set the loop range. These are mainly used in for loops to set the loop range.

View File

@@ -4,8 +4,9 @@ TODO
- [on branch: shortcircuit] complete McCarthy evaluation. This may also reduce code size perhaps for things like if a>4 or a<2 .... - [on branch: shortcircuit] complete McCarthy evaluation. This may also reduce code size perhaps for things like if a>4 or a<2 ....
- note: shortcircuit only on logical boolean expressions (and,or) not on bitwise (&,|) - note: shortcircuit only on logical boolean expressions (and,or) not on bitwise (&,|)
- vm ircodegen (DONE!) - vm ircodegen (PARTIALLY DONE!)
- in 6502 codegen (see vm's ExpressionGen operatorAnd / operatorOr) - in 6502 codegen (see vm's ExpressionGen operatorAnd / operatorOr)
- test on word variables, if the TODO doesn't trigger, just replace it with a specific notification
... ...

View File

@@ -2,13 +2,22 @@
%zeropage dontuse %zeropage dontuse
main { main {
sub start () { /*
ubyte a1 = 10 sub start() {
ubyte a2 = 20 if bool_true() and bool_false() and bool_true()
ubyte x1 = 30 txt.print("all true")
ubyte x2 = 40 else
ubyte zero = 0 txt.print("not all true")
}
*/
ubyte @shared a1 = 10
ubyte @shared a2 = 20
ubyte @shared x1 = 30
ubyte @shared x2 = 40
ubyte @shared zero = 0
sub start () {
txt.print("1a:\n") txt.print("1a:\n")
if calc_a1()<calc_x1() and calc_a2()<=calc_x2() if calc_a1()<calc_x1() and calc_a2()<=calc_x2()
txt.print("* 1a and ok\n") txt.print("* 1a and ok\n")
@@ -56,30 +65,47 @@ main {
txt.print("\n5d:\n") txt.print("\n5d:\n")
result = bool_false() xor bool_true() result = bool_false() xor bool_true()
txt.print("augmented and shortcut:\n")
bool @shared b1 = false
cx16.r0++
b1 = b1 and bool_true()
txt.print("augmented and no shortcut:\n")
b1 = true
cx16.r0++
b1 = b1 and bool_true()
sub bool_true() -> bool { txt.print("augmented or shortcut:\n")
txt.print("bool_true\n") b1 = true
return true cx16.r0++
} b1 = b1 or bool_true()
sub bool_false() -> bool { txt.print("augmented or no shortcut:\n")
txt.print("bool_false\n") b1 = false
return false cx16.r0++
} b1 = b1 or bool_true()
sub calc_a1() -> ubyte { }
txt.print("calc_a1\n")
return a1+zero sub bool_true() -> bool {
} txt.print("bool_true\n")
sub calc_a2() -> ubyte { return true
txt.print("calc_a2\n") }
return a2+zero sub bool_false() -> bool {
} txt.print("bool_false\n")
sub calc_x1() -> ubyte { return false
txt.print("calc_x1\n") }
return x1+zero sub calc_a1() -> ubyte {
} txt.print("calc_a1\n")
sub calc_x2() -> ubyte { return a1+zero
txt.print("calc_x2\n") }
return x2+zero sub calc_a2() -> ubyte {
} txt.print("calc_a2\n")
return a2+zero
}
sub calc_x1() -> ubyte {
txt.print("calc_x1\n")
return x1+zero
}
sub calc_x2() -> ubyte {
txt.print("calc_x2\n")
return x2+zero
} }
} }