keep distinction between logical and bitwise boolean operators

This commit is contained in:
Irmen de Jong 2023-11-15 23:10:20 +01:00
parent f790182f0b
commit 8f18b5b8a7
8 changed files with 163 additions and 121 deletions

View File

@ -49,54 +49,24 @@ internal class AnyExprAsmGen(
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
when(expr.operator) {
"+" -> {
TODO("word + at ${expr.position}")
}
"-" -> {
TODO("word - at ${expr.position}")
}
"*" -> {
TODO("word * at ${expr.position}")
}
"/" -> {
TODO("word / at ${expr.position}")
}
"<<" -> {
TODO("word << at ${expr.position}")
}
">>" -> {
TODO("word >> at ${expr.position}")
}
"%" -> {
TODO("word % at ${expr.position}")
}
"&", "and" -> {
TODO("word and at ${expr.position}")
}
"|", "or" -> {
TODO("word or at ${expr.position}")
}
"^", "xor" -> {
TODO("word xor at ${expr.position}")
}
"==" -> {
TODO("word == at ${expr.position}")
}
"!=" -> {
TODO("word != at ${expr.position}")
}
"<" -> {
TODO("word < at ${expr.position}")
}
"<=" -> {
TODO("word <= at ${expr.position}")
}
">" -> {
TODO("word > at ${expr.position}")
}
">=" -> {
TODO("word >= at ${expr.position}")
}
"+" -> TODO("word + at ${expr.position}")
"-" -> TODO("word - at ${expr.position}")
"*" -> TODO("word * at ${expr.position}")
"/" -> TODO("word / at ${expr.position}")
"<<" -> TODO("word << at ${expr.position}")
">>" -> TODO("word >> at ${expr.position}")
"%" -> TODO("word % at ${expr.position}")
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
"&" -> TODO("word and at ${expr.position}")
"|" -> TODO("word or at ${expr.position}")
"^", "xor" -> TODO("word xor at ${expr.position}")
"==" -> TODO("word == at ${expr.position}")
"!=" -> TODO("word != at ${expr.position}")
"<" -> TODO("word < at ${expr.position}")
"<=" -> TODO("word <= at ${expr.position}")
">" -> TODO("word > at ${expr.position}")
">=" -> TODO("word >= at ${expr.position}")
else -> return false
}
}
@ -134,7 +104,9 @@ internal class AnyExprAsmGen(
"%" -> {
TODO("byte % at ${expr.position}")
}
"&", "and" -> {
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)
@ -142,7 +114,7 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"|", "or" -> {
"|" -> {
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.A, false)
asmgen.out(" pha")
asmgen.assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", DataType.UBYTE)

View File

@ -427,7 +427,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
private fun attemptAssignOptimizedBinexpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
val translatedOk = when (expr.operator) {
in ComparisonOperators -> optimizedComparison(expr, assign)
in setOf("&", "|", "^", "and", "or", "xor") -> optimizedLogicalOrBitwiseExpr(expr, assign.target)
in setOf("&", "|", "^", "xor") -> optimizedBitwiseExpr(expr, assign.target)
in setOf("and", "or") -> optimizedLogicalAndOrExpr(expr, assign.target)
"+", "-" -> optimizedPlusMinExpr(expr, assign.target)
"<<", ">>" -> optimizedBitshiftExpr(expr, assign.target)
"*" -> optimizedMultiplyExpr(expr, assign.target)
@ -988,15 +989,15 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false
}
private fun optimizedLogicalOrBitwiseExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
private fun optimizedBitwiseExpr(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) {
assignLogicalWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right)
assignBitwiseWithSimpleRightOperandByte(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left)
assignBitwiseWithSimpleRightOperandByte(target, expr.right, expr.operator, expr.left)
return true
}
}
@ -1006,10 +1007,10 @@ internal class AssignmentAsmGen(private val program: PtProgram,
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")
"&" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
else -> throw AssemblyError("invalid bitwise operator")
}
assignRegisterByte(target, CpuRegister.A, false, true)
return true
@ -1017,20 +1018,68 @@ internal class AssignmentAsmGen(private val program: PtProgram,
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) {
assignLogicalWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right)
assignBitwiseWithSimpleRightOperandWord(target, expr.left, expr.operator, expr.right)
return true
}
else if (expr.left is PtNumber || expr.left is PtIdentifier) {
assignLogicalWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left)
assignBitwiseWithSimpleRightOperandWord(target, expr.right, expr.operator, expr.left)
return true
}
}
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
when (expr.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_W1 | tax | tya | and P8ZP_SCRATCH_W1+1 | tay | txa")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_W1 | tax | tya | ora P8ZP_SCRATCH_W1+1 | tay | txa")
"&" -> asmgen.out(" and P8ZP_SCRATCH_W1 | tax | tya | and P8ZP_SCRATCH_W1+1 | tay | txa")
"|" -> asmgen.out(" ora P8ZP_SCRATCH_W1 | tax | tya | ora P8ZP_SCRATCH_W1+1 | tay | txa")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_W1 | tax | tya | eor P8ZP_SCRATCH_W1+1 | tay | txa")
else -> throw AssemblyError("invalid operator")
else -> throw AssemblyError("invalid bitwise operator")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
}
return false
}
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
}
}
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")
}
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
}
}
asmgen.assignWordOperandsToAYAndVar(expr.left, expr.right, "P8ZP_SCRATCH_W1")
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")
}
assignRegisterpairWord(target, RegisterOrPair.AY)
return true
@ -1709,7 +1758,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return true
}
private fun assignLogicalWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
private fun assignBitwiseWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
assignExpressionToRegister(left, RegisterOrPair.A, false)
val operand = when(right) {
is PtNumber -> "#${right.number.toHex()}"
@ -1717,33 +1766,41 @@ internal class AssignmentAsmGen(private val program: PtProgram,
else -> throw AssemblyError("wrong right operand type")
}
when (operator) {
"&", "and" -> asmgen.out(" and $operand")
"|", "or" -> asmgen.out(" ora $operand")
"&" -> asmgen.out(" and $operand")
"|" -> asmgen.out(" ora $operand")
"^", "xor" -> asmgen.out(" eor $operand")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(target, CpuRegister.A, false, true)
}
private fun assignLogicalWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
private fun assignLogicalAndOrWithSimpleRightOperandByte(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
when (operator) {
"and" -> TODO("logical and (with optional shortcircuit) ${left.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${left.position}")
else -> throw AssemblyError("invalid logical operator")
}
}
private fun assignBitwiseWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
assignExpressionToRegister(left, RegisterOrPair.AY, false)
when(right) {
is PtNumber -> {
val number = right.number.toHex()
when (operator) {
"&", "and" -> asmgen.out(" and #<$number | tax | tya | and #>$number | tay | txa")
"|", "or" -> asmgen.out(" ora #<$number | tax | tya | ora #>$number | tay | txa")
"&" -> asmgen.out(" and #<$number | tax | tya | and #>$number | tay | txa")
"|" -> asmgen.out(" ora #<$number | tax | tya | ora #>$number | tay | txa")
"^", "xor" -> asmgen.out(" eor #<$number | tax | tya | eor #>$number | tay | txa")
else -> throw AssemblyError("invalid operator")
else -> throw AssemblyError("invalid bitwise operator")
}
}
is PtIdentifier -> {
val name = asmgen.asmSymbolName(right)
when (operator) {
"&", "and" -> asmgen.out(" and $name | tax | tya | and $name+1 | tay | txa")
"|", "or" -> asmgen.out(" ora $name | tax | tya | ora $name+1 | tay | txa")
"&" -> asmgen.out(" and $name | tax | tya | and $name+1 | tay | txa")
"|" -> asmgen.out(" ora $name | tax | tya | ora $name+1 | tay | txa")
"^", "xor" -> asmgen.out(" eor $name | tax | tya | eor $name+1 | tay | txa")
else -> throw AssemblyError("invalid operator")
else -> throw AssemblyError("invalid bitwise operator")
}
}
else -> throw AssemblyError("wrong right operand type")
@ -1751,6 +1808,26 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterpairWord(target, RegisterOrPair.AY)
}
private fun assignLogicalAndOrWithSimpleRightOperandWord(target: AsmAssignTarget, left: PtExpression, operator: String, right: PtExpression) {
when(right) {
is PtNumber -> {
when (operator) {
"and" -> TODO("logical and (with optional shortcircuit) ${left.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${left.position}")
else -> throw AssemblyError("invalid logical operator")
}
}
is PtIdentifier -> {
when (operator) {
"and" -> TODO("logical and (with optional shortcircuit) ${left.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${left.position}")
else -> throw AssemblyError("invalid logical operator")
}
}
else -> throw AssemblyError("wrong right operand type")
}
}
private fun attemptAssignToByteCompareZero(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when (expr.operator) {
"==" -> {

View File

@ -367,9 +367,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
"*" -> operatorMultiply(binExpr, vmDt)
"/" -> operatorDivide(binExpr, vmDt, signed)
"%" -> operatorModulo(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt)
"&" -> operatorAnd(binExpr, vmDt)
"^" -> operatorXor(binExpr, vmDt)
"|" -> operatorOr(binExpr, vmDt, true)
"&" -> operatorAnd(binExpr, vmDt, true)
"^", "xor" -> operatorXor(binExpr, vmDt)
"or" -> operatorOr(binExpr, vmDt, false)
"and" -> operatorAnd(binExpr, vmDt, false)
"<<" -> operatorShiftLeft(binExpr, vmDt)
">>" -> operatorShiftRight(binExpr, vmDt, signed)
"==" -> operatorEquals(binExpr, vmDt, false)
@ -695,9 +697,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType, bitwise: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
if(!bitwise && codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
// short-circuit LEFT and RIGHT --> if LEFT then RIGHT else LEFT (== if !LEFT then LEFT else RIGHT)
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
@ -724,9 +726,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: IRDataType): ExpressionCodeResult {
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: IRDataType, bitwise: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
if(!bitwise && codeGen.options.shortCircuit && (!binExpr.left.isSimple() && !binExpr.right.isSimple())) {
// short-circuit LEFT or RIGHT --> if LEFT then LEFT else RIGHT
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)

View File

@ -74,16 +74,8 @@ internal class BoolRemover(val program: Program) : AstWalker() {
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// convert boolean and/or/xor/not operators to bitwise equivalents.
// so that codegen only has to work with bitwise boolean operations from now on.
// note: this has to be done here and not in BeforeAsmTypecastCleaner! (code size will increase if done there...)
if(expr.operator in setOf("and", "or", "xor")) {
expr.operator = when(expr.operator) {
"and" -> "&"
"or" -> "|"
"xor" -> "^"
else -> "invalid"
}
// see if any of the arguments to a logical boolean expression need type casting to bool
val mods = mutableListOf<IAstModification>()
val newLeft = wrapWithBooleanCastIfNeeded(expr.left, program)
val newRight = wrapWithBooleanCastIfNeeded(expr.right, program)

View File

@ -305,8 +305,8 @@ class TestOptimization: FunSpec({
ubyte a1
ubyte a2
a1 = not not a1 ; a1 = a1==0
a1 = not a1 or not a2 ; a1 = a1==0 | a2==0
a1 = not a1 and not a2 ; a1 = a1==0 & a2==0
a1 = not a1 or not a2 ; a1 = a1==0 or a2==0
a1 = not a1 and not a2 ; a1 = a1==0 and a2==0
}
}
"""
@ -319,8 +319,8 @@ class TestOptimization: FunSpec({
val value3 = (stmts[6] as Assignment).value as BinaryExpression
value1.operator shouldBe "=="
value1.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
value2.operator shouldBe "|"
value3.operator shouldBe "&"
value2.operator shouldBe "or"
value3.operator shouldBe "and"
}
test("asmgen correctly deals with float typecasting in augmented assignment") {

View File

@ -60,7 +60,7 @@ class TestTypecasts: FunSpec({
val stmts2 = result2.compilerAst.entrypoint.statements
stmts2.size shouldBe 5
val expr2 = (stmts2[3] as Assignment).value as BinaryExpression
expr2.operator shouldBe "&"
expr2.operator shouldBe "and"
expr2.right shouldBe NumericLiteral(DataType.UBYTE, 1.0, Position.DUMMY)
}
@ -93,18 +93,18 @@ main {
ubyte ub3
ub3 = 1
ubyte @shared bvalue
bvalue = ub1 ^ ub2 ^ ub3 ^ true
bvalue = (((ub1^ub2)^ub3)^(ftrue(99)!=0))
bvalue = ((ub1&ub2)&(ftrue(99)!=0))
bvalue = ub1 xor ub2 xor ub3 xor true
bvalue = (((ub1 xor ub2)xor ub3) xor (ftrue(99)!=0))
bvalue = ((ub1 and ub2) and (ftrue(99)!=0))
return
*/
stmts.size shouldBe 11
val assignValue1 = (stmts[7] as Assignment).value as BinaryExpression
val assignValue2 = (stmts[8] as Assignment).value as BinaryExpression
val assignValue3 = (stmts[9] as Assignment).value as BinaryExpression
assignValue1.operator shouldBe "^"
assignValue2.operator shouldBe "^"
assignValue3.operator shouldBe "&"
assignValue1.operator shouldBe "xor"
assignValue2.operator shouldBe "xor"
assignValue3.operator shouldBe "and"
val right2 = assignValue2.right as BinaryExpression
val right3 = assignValue3.right as BinaryExpression
right2.operator shouldBe "!="
@ -115,24 +115,6 @@ main {
right3.right shouldBe NumericLiteral(DataType.UBYTE, 0.0, Position.DUMMY)
}
test("simple logical with bool no typecast") {
val text="""
main {
bool bb
sub start() {
bb = bb and 123
}
}"""
val result = compileText(C64Target(), true, text, writeAssembly = true)!!
val stmts = result.compilerAst.entrypoint.statements
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<IdentifierReference>()
assignValue.operator shouldBe "&"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}
test("simple logical with byte instead of bool ok with typecasting") {
val text="""
main {
@ -147,7 +129,7 @@ main {
stmts.size shouldBe 2
val assignValue = (stmts[0] as Assignment).value as BinaryExpression
assignValue.left shouldBe instanceOf<BinaryExpression>() // as a result of the cast to boolean
assignValue.operator shouldBe "&"
assignValue.operator shouldBe "and"
(assignValue.right as NumericLiteral).number shouldBe 1.0
}

View File

@ -3,11 +3,10 @@ TODO
====
- [on branch: shortcircuit] complete McCarthy evaluation. This may also reduce code size perhaps for things like if a>4 or a<2 ....
- vm ircodegen (DONE)
- note: shortcircuit only on logical boolean expressions (and,or) not on bitwise (&,|)
- vm ircodegen (DONE!)
- in 6502 codegen (see vm's ExpressionGen operatorAnd / operatorOr)
- IR: reduce amount of CMP/CMPI after instructions that set the status bits correctly (LOADs? INC? Bitwise operations, etc), but only after setting the status bits is verified!
...

View File

@ -47,6 +47,24 @@ main {
txt.nl()
@($4000) &= 22
txt.print("\n5a:\n")
result = bool_true() or bool_false()
txt.print("\n5b:\n")
result = bool_true() and bool_false()
txt.print("\n5c:\n")
result = bool_false() and bool_true()
txt.print("\n5d:\n")
result = bool_false() xor 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