Codegen: use BIT instruction for memory location bit 7 and 6 tests (use N and V flags)

This commit is contained in:
Irmen de Jong 2024-07-24 01:20:28 +02:00
parent 0ec719e429
commit c67f877857
9 changed files with 243 additions and 78 deletions

View File

@ -82,6 +82,8 @@ val BuiltinFunctions: Map<String, FSignature> = 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),

View File

@ -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()

View File

@ -626,6 +626,16 @@ private fun optimizeJsrRtsAndOtherCombinations(linesByFour: Sequence<List<Indexe
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvc " in first || "\tbvc " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvs")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
else if((" bvs " in first || "\tbvs " in first) && sameLabel(first, second, third)) {
val branch = second.replace("jmp", "bvc")
mods.add(Modification(lines[0].index, true, null))
mods.add(Modification(lines[1].index, false, branch))
}
}
}
return mods

View File

@ -65,6 +65,8 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"cmp" -> 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)

View File

@ -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

View File

@ -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)

View File

@ -1318,6 +1318,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
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()) {

View File

@ -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:

View File

@ -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")
}
}
}