From c67f877857b1288cd9aa3bae045958b730de7356 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 24 Jul 2024 01:20:28 +0200 Subject: [PATCH] Codegen: use BIT instruction for memory location bit 7 and 6 tests (use N and V flags) --- .../src/prog8/code/core/BuiltinFunctions.kt | 2 + codeCore/src/prog8/code/optimize/Optimizer.kt | 46 +++++ .../src/prog8/codegen/cpu6502/AsmOptimizer.kt | 10 + .../codegen/cpu6502/BuiltinFunctionsAsmGen.kt | 2 + .../src/prog8/codegen/cpu6502/IfElseAsmGen.kt | 73 +++++++- .../codegen/intermediate/BuiltinFuncGen.kt | 2 + .../prog8/codegen/intermediate/IRCodeGen.kt | 4 + docs/source/todo.rst | 10 - examples/test.p8 | 172 +++++++++++------- 9 files changed, 243 insertions(+), 78 deletions(-) diff --git a/codeCore/src/prog8/code/core/BuiltinFunctions.kt b/codeCore/src/prog8/code/core/BuiltinFunctions.kt index afd27e11c..5267efdd0 100644 --- a/codeCore/src/prog8/code/core/BuiltinFunctions.kt +++ b/codeCore/src/prog8/code/core/BuiltinFunctions.kt @@ -82,6 +82,8 @@ val BuiltinFunctions: Map = mapOf( "prog8_lib_arraycopy" to FSignature(false, listOf(FParam("source", ArrayDatatypes), FParam("target", ArrayDatatypes)), null), "prog8_lib_square_byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE, DataType.UBYTE))), DataType.UBYTE), "prog8_lib_square_word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD, DataType.UWORD))), DataType.UWORD), + "prog8_ifelse_bittest_set" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL), + "prog8_ifelse_bittest_notset" to FSignature(true, listOf(FParam("variable", ByteDatatypes), FParam("bitnumber", arrayOf(DataType.UBYTE))), DataType.BOOL), "abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null), "abs__byte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.BYTE))), DataType.BYTE), "abs__word" to FSignature(true, listOf(FParam("value", arrayOf(DataType.WORD))), DataType.WORD), diff --git a/codeCore/src/prog8/code/optimize/Optimizer.kt b/codeCore/src/prog8/code/optimize/Optimizer.kt index dbe158ade..e6daebdf9 100644 --- a/codeCore/src/prog8/code/optimize/Optimizer.kt +++ b/codeCore/src/prog8/code/optimize/Optimizer.kt @@ -11,6 +11,7 @@ fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: return while (errors.noErrors() && (optimizeCommonSubExpressions(program, errors) + + optimizeBitTest(program, options) + optimizeAssignTargets(program, st, errors)) > 0 ) { // keep rolling @@ -164,6 +165,51 @@ private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: I return changes } + +private fun optimizeBitTest(program: PtProgram, options: CompilationOptions): Int { + if(options.compTarget.machine.cpu == CpuType.VIRTUAL) + return 0 // the special bittest optimization is not yet valid for the IR + + 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 + } + } + } + } + } + recurse + } + return changes +} + + internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean { if(returnedRegister in Cx16VirtualRegisters) { val regname = returnedRegister.name.lowercase() diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmOptimizer.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmOptimizer.kt index dfb34647f..5d16bcda8 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmOptimizer.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmOptimizer.kt @@ -626,6 +626,16 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence funcCmp(fcall) "callfar" -> funcCallFar(fcall, resultRegister) "call" -> funcCall(fcall) + "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_lib_stringcompare" -> funcStringCompare(fcall, resultRegister) "prog8_lib_square_byte" -> funcSquare(fcall, DataType.UBYTE, resultRegister) "prog8_lib_square_word" -> funcSquare(fcall, DataType.UWORD, resultRegister) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt index 8fbb3ad85..6f5fd1ec0 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/IfElseAsmGen.kt @@ -83,9 +83,80 @@ internal class IfElseAsmGen(private val program: PtProgram, } private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) { + val bittest = ifElse.condition as? PtBuiltinFunctionCall + val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump + + 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) { + if(jumpAfterIf!=null) { + val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf) + if(indirect) + throw AssemblyError("cannot BIT to indirect label ${ifElse.position}") + if(ifElse.hasElse()) + throw AssemblyError("didn't expect else part here ${ifElse.position}") + else + asmgen.out(" bmi $asmLabel") + } + else + translateIfElseBodies("bpl", ifElse) + } else { + if(jumpAfterIf!=null) { + val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf) + if(indirect) + throw AssemblyError("cannot BIT to indirect label ${ifElse.position}") + if(ifElse.hasElse()) + throw AssemblyError("didn't expect else part here ${ifElse.position}") + else + asmgen.out(" bpl $asmLabel") + } + else + translateIfElseBodies("bmi", ifElse) + } + return + } + 6 -> { + // test via bit + V flag + asmgen.out(" bit ${variable.name}") + if(testForBitSet) { + if(jumpAfterIf!=null) { + val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf) + if(indirect) + throw AssemblyError("cannot BIT to indirect label ${ifElse.position}") + if(ifElse.hasElse()) + throw AssemblyError("didn't expect else part here ${ifElse.position}") + else + asmgen.out(" bvs $asmLabel") + } + else + translateIfElseBodies("bvc", ifElse) + } else { + if(jumpAfterIf!=null) { + val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf) + if(indirect) + throw AssemblyError("cannot BIT to indirect label ${ifElse.position}") + if(ifElse.hasElse()) + throw AssemblyError("didn't expect else part here ${ifElse.position}") + else + asmgen.out(" bvc $asmLabel") + } + else + translateIfElseBodies("bvs", ifElse) + } + 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(ifElse.condition) - val jumpAfterIf = ifElse.ifScope.children.singleOrNull() as? PtJump if(jumpAfterIf!=null) translateJumpElseBodies("bne", "beq", jumpAfterIf, ifElse.elseScope) else diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index 556e6111f..88d2098b8 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -38,6 +38,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_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/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 0198b12e1..9f98e9c9e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -1318,6 +1318,10 @@ class IRCodeGen( val result = mutableListOf() fun translateSimple(condition: PtExpression, jumpFalseOpcode: Opcode) { + + if(condition is PtBuiltinFunctionCall && condition.name.startsWith("prog8_ifelse_bittest_")) + throw AssemblyError("IR codegen doesn't have special instructions for dedicated BIT tests and should just still use normal AND") + val tr = expressionEval.translateExpression(condition) result += tr.chunks if(ifElse.hasElse()) { diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 03eb10cc7..51ac6e45e 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,16 +3,6 @@ TODO See open issues on github. -Asm peephole optimizer: while cx16.VERA_AUDIO_CTRL & %01000000 == 0 { } compiles into the following. Replace the bne+bra into a beq. Similar for !=0 I guess? - p8l_label_5_whileloop - lda cx16.VERA_AUDIO_CTRL - and #$40 - bne p8l_label_6_afterwhile - bra p8l_label_5_whileloop - p8l_label_6_afterwhile - -Codegen: use BIT instruction for memory location bit 7 and 6 tests (use N and V flags) - Re-generate the skeletons doc files. Improve register load order in subroutine call args assignments: diff --git a/examples/test.p8 b/examples/test.p8 index 93acbeec4..223868820 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,4 +1,3 @@ -%import buffers %import textio %option no_sysinit %zeropage basicsafe @@ -6,75 +5,114 @@ main { sub start() { - signed() - unsigned() - } + ubyte @shared variable - sub signed() { - txt.print("signed\n") - byte @shared bvalue = -88 - word @shared wvalue = -8888 + variable = 0 + while variable & %10000000 == 0 { + cx16.r0L++ + variable = 128 + } + txt.chrout('1') + while variable & %10000000 != 0 { + cx16.r0L++ + variable = 0 + } + txt.chrout('2') + while variable & %01000000 == 0 { + cx16.r0L++ + variable = 64 + } + txt.chrout('3') + while variable & %01000000 != 0 { + cx16.r0L++ + variable=0 + } + txt.chrout('4') + variable = 255 + while variable & %10000000 == 0 { + } + while variable & %01000000 == 0 { + } + txt.chrout('5') + variable = 0 + while variable & %10000000 != 0 { + } + while variable & %01000000 != 0 { + } + txt.chrout('6') + txt.chrout('\n') - txt.print_b(bvalue/2) - txt.spc() - txt.print_b(bvalue/4) - txt.spc() - txt.print_b(bvalue/8) - txt.nl() + variable = 0 + cx16.r0L++ + if variable & %10000000 == 0 { + txt.print("bit 7 not set\n") + } + if variable & %10000000 != 0 { + txt.print("bit 7 set\n") + } + if variable & %10000000 == 0 { + txt.print("bit 7 not set\n") + } else { + txt.print("bit 7 set\n") + } + if variable & %10000000 != 0 { + txt.print("bit 7 set\n") + } else { + txt.print("bit 7 not set\n") + } - bvalue /= 2 - txt.print_b(bvalue) - txt.spc() - bvalue /= 8 - txt.print_b(bvalue) - txt.nl() + variable = 128 + cx16.r0L++ + if variable & %10000000 == 0 { + txt.print("bit 7 not set\n") + } + if variable & %10000000 != 0 { + txt.print("bit 7 set\n") + } + if variable & %10000000 == 0 { + txt.print("bit 7 not set\n") + } else { + txt.print("bit 7 set\n") + } + if variable & %10000000 != 0 { + txt.print("bit 7 set\n") + } else { + txt.print("bit 7 not set\n") + } - txt.print_w(wvalue/2) - txt.spc() - txt.print_w(wvalue/4) - txt.spc() - txt.print_w(wvalue/8) - txt.nl() - - wvalue /= 2 - txt.print_w(wvalue) - txt.spc() - wvalue /= 8 - txt.print_w(wvalue) - txt.nl() - } - - sub unsigned() { - txt.print("\nunsigned\n") - ubyte @shared bvalue = 88 - uword @shared wvalue = 8888 - - txt.print_ub(bvalue/2) - txt.spc() - txt.print_ub(bvalue/4) - txt.spc() - txt.print_ub(bvalue/8) - txt.nl() - - bvalue /= 2 - txt.print_ub(bvalue) - txt.spc() - bvalue /= 8 - txt.print_ub(bvalue) - txt.nl() - - txt.print_uw(wvalue/2) - txt.spc() - txt.print_uw(wvalue/4) - txt.spc() - txt.print_uw(wvalue/8) - txt.nl() - - wvalue /= 2 - txt.print_uw(wvalue) - txt.spc() - wvalue /= 8 - txt.print_uw(wvalue) - txt.nl() + if variable & %01000000 == 0 { + txt.print("bit 6 not set\n") + } + if variable & %01000000 != 0 { + txt.print("bit 6 set\n") + } + if variable & %01000000 == 0 { + txt.print("bit 6 not set\n") + } else { + txt.print("bit 6 set\n") + } + if variable & %01000000 != 0 { + txt.print("bit 6 set\n") + } else { + txt.print("bit 6 not set\n") + } + variable = %01000000 + cx16.r0L++ + if variable & %01000000 == 0 { + txt.print("bit 6 not set\n") + } + if variable & %01000000 != 0 { + txt.print("bit 6 set\n") + } + if variable & %01000000 == 0 { + txt.print("bit 6 not set\n") + } else { + txt.print("bit 6 set\n") + } + if variable & %01000000 != 0 { + txt.print("bit 6 set\n") + } else { + txt.print("bit 6 not set\n") + } } }