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
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=")
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "and", "or", "xor")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not")
val BitwiseOperators = setOf("&", "|", "^", "~")

View File

@ -1041,39 +1041,60 @@ internal class AssignmentAsmGen(private val program: PtProgram,
private fun optimizedLogicalAndOrExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
if (expr.left.type in ByteDatatypes && expr.right.type in ByteDatatypes) {
if (expr.right.isSimple()) {
if (expr.right is PtNumber || expr.right is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left)
return true
}
if (expr.right is PtNumber || expr.right is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left)
return true
}
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" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
else -> throw AssemblyError("invalid logical operator")
if(asmgen.options.shortCircuit && (!expr.left.isSimple() && !expr.right.isSimple())) {
// shortcircuit evaluation into A
val shortcutLabel = asmgen.makeLabel("shortcut")
when (expr.operator) {
"and" -> {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
println("SHORTCUT AND ${expr.position}") // TODO weg
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)
return true
}
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) {
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left)
return true
}
if (expr.right is PtNumber || expr.right is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalAndOrWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left)
return true
}
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
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) {
// normal evaluation, not worth to shortcircuit the simple right operand
assignExpressionToVariable(left, "P8ZP_SCRATCH_B1", DataType.UBYTE)
assignExpressionToRegister(right, RegisterOrPair.A, false)
when (operator) {
"and" -> TODO("logical and (with optional shortcircuit) ${left.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${left.position}")
"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)
}
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)
"or=" -> inplaceModification(assign.target, "or", 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)
@ -791,12 +793,42 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
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)
inplacemodificationRegisterAwithVariableWithSwappedOperands(operator, name, dt in SignedDatatypes)
asmgen.out(" sta $name")
}
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")
inplacemodificationRegisterAwithVariable(operator, otherName, dt in SignedDatatypes)
asmgen.out(" sta $name")
@ -852,6 +884,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
}
"&" -> asmgen.out(" and $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("""
@ -1130,6 +1163,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
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: no logical and/or shortcut here, not worth it due to simple right operand
when (operator) {
"+" -> asmgen.out(" lda $name | clc | adc #$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)
"|" -> immediateOrInplace(name, value)
"^" -> asmgen.out(" lda $name | eor #$value | sta $name")
"&", "and" -> immediateAndInplace(name, value)
"|", "or" -> immediateOrInplace(name, value)
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> {
asmgen.out("""
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``,
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)
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.
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'
(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.
Prog8 applies short-circuit aka McCarthy evaluation for ``and`` and ``or``.
.. note::
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 ``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``
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.

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