fixup split words array comparisons

This commit is contained in:
Irmen de Jong 2024-03-01 00:13:03 +01:00
parent 620ffe54ec
commit f29d24e96a
9 changed files with 908 additions and 120 deletions

View File

@ -235,7 +235,7 @@ class AsmGen6502Internal (
private val anyExprGen = AnyExprAsmGen(this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, anyExprGen, allocator)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen)
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, allocator, assignmentAsmGen, errors)
fun compileToAssembly(): IAssemblyProgram? {

View File

@ -4,13 +4,16 @@ import prog8.code.StRomSub
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.AssignmentAsmGen
import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class IfElseAsmGen(private val program: PtProgram,
private val st: SymbolTable,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator,
private val assignmentAsmGen: AssignmentAsmGen) {
private val assignmentAsmGen: AssignmentAsmGen,
private val errors: IErrorReporter) {
fun translate(stmt: PtIfElse) {
require(stmt.condition.type== DataType.BOOL)
@ -19,13 +22,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
val jumpAfterIf = stmt.ifScope.children.singleOrNull() as? PtJump
if(stmt.condition is PtIdentifier ||
stmt.condition is PtBool ||
stmt.condition is PtArrayIndexer ||
stmt.condition is PtTypeCast ||
stmt.condition is PtBuiltinFunctionCall ||
stmt.condition is PtFunctionCall ||
stmt.condition is PtMemoryByte ||
stmt.condition is PtContainmentCheck)
return fallbackTranslate(stmt, false) // the fallback code for these is optimal, so no warning.
return fallbackTranslateForSimpleCondition(stmt)
val compareCond = stmt.condition as? PtBinaryExpression
if(compareCond!=null) {
@ -47,7 +51,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("bne", stmt)
}
fallbackTranslate(stmt, true)
throw AssemblyError("weird non-boolean condition node type ${stmt.condition} at ${stmt.condition.position}")
}
private fun checkNotRomsubReturnsStatusReg(condition: PtExpression) {
@ -78,16 +82,27 @@ internal class IfElseAsmGen(private val program: PtProgram,
}
}
private fun fallbackTranslate(stmt: PtIfElse, warning: Boolean) {
if(warning) println("WARN: SLOW FALLBACK IF: ${stmt.position}. Ask for support.") // TODO should have no more of these
val jumpAfterIf = stmt.ifScope.children.singleOrNull() as? PtJump
private fun fallbackTranslate(stmt: PtIfElse) {
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // TODO should have no more of these
assignConditionValueToRegisterAndTest(stmt.condition)
val jumpAfterIf = stmt.ifScope.children.singleOrNull() as? PtJump
if(jumpAfterIf!=null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
}
private fun fallbackTranslateForSimpleCondition(ifElse: PtIfElse) {
// 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
translateIfElseBodies("beq", ifElse)
}
private fun translateIfElseBodies(elseBranchInstr: String, stmt: PtIfElse) {
// comparison value is already in A
val afterIfLabel = asmgen.makeLabel("afterif")
@ -181,7 +196,18 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("bcc", stmt)
}
}
else -> fallbackTranslate(stmt, false)
in LogicalOperators -> {
val regAtarget = AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, DataType.BOOL, stmt.definingISub(), condition.position, register=RegisterOrPair.A)
// TODO optimize this better for if statements to not require the A register to hold the intermediate boolean result
if (assignmentAsmGen.optimizedLogicalExpr(condition, regAtarget)) {
if (jumpAfterIf != null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("beq", stmt)
} else
fallbackTranslate(stmt)
}
else -> throw AssemblyError("expected comparison or logical operator")
}
}
@ -314,7 +340,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
translateIfElseBodies("bne", stmt)
}
}
else -> throw AssemblyError("weird operator")
else -> throw AssemblyError("expected comparison operator")
}
}
@ -435,13 +461,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
val constValue = condition.right.asConstInteger()
if(constValue==0) {
// optimized comparisons with zero
when (condition.operator) {
"==" -> return wordEqualsZero(condition.left, false, signed, jumpAfterIf, stmt)
"!=" -> return wordEqualsZero(condition.left, true, signed, jumpAfterIf, stmt)
"<" -> return wordLessZero(condition.left, signed, jumpAfterIf, stmt)
"<=" -> return wordLessEqualsZero(condition.left, signed, jumpAfterIf, stmt)
">" -> return wordGreaterZero(condition.left, signed, jumpAfterIf, stmt)
">=" -> return wordGreaterEqualsZero(condition.left, signed, jumpAfterIf, stmt)
return when (condition.operator) {
"==" -> wordEqualsZero(condition.left, false, signed, jumpAfterIf, stmt)
"!=" -> wordEqualsZero(condition.left, true, signed, jumpAfterIf, stmt)
"<" -> wordLessZero(condition.left, signed, jumpAfterIf, stmt)
"<=" -> wordLessEqualsZero(condition.left, signed, jumpAfterIf, stmt)
">" -> wordGreaterZero(condition.left, signed, jumpAfterIf, stmt)
">=" -> wordGreaterEqualsZero(condition.left, signed, jumpAfterIf, stmt)
else -> throw AssemblyError("expected comparison operator")
}
}
@ -453,7 +480,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
"<=" -> throw AssemblyError("X<=Y should have been replaced by Y>=X")
">" -> throw AssemblyError("X>Y should have been replaced by Y<X")
">=" -> wordGreaterEqualsValue(condition.left, condition.right, signed, jumpAfterIf, stmt)
else -> fallbackTranslate(stmt, false)
else -> throw AssemblyError("expected comparison operator")
}
}
@ -1010,20 +1037,26 @@ _jump jmp ($asmLabel)
else
translateIfElseBodies(falseBranch, stmt)
}
fun translateLoadFromVarSplitw(variable: String, constIndex: Int, branch: String, falseBranch: String) {
asmgen.out(" lda ${variable}_lsb+$constIndex | ora ${variable}_msb+$constIndex")
return if(jump!=null)
translateJumpElseBodies(branch, falseBranch, jump, stmt.elseScope)
else
translateIfElseBodies(falseBranch, stmt)
}
if(notEquals) {
when(value) {
is PtArrayIndexer -> {
val constIndex = value.index.asConstInteger()
if(constIndex!=null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.splitWords) {
TODO("split word array != 0")
} else {
val offset = constIndex * program.memsizer.memorySize(value.type)
if (offset < 256) {
val varName = asmgen.asmVariableName(value.variable)
return translateLoadFromVar("$varName+$offset", "bne", "beq")
}
return translateLoadFromVarSplitw(varName, constIndex, "bne", "beq")
}
val offset = constIndex * program.memsizer.memorySize(value.type)
if (offset < 256) {
return translateLoadFromVar("$varName+$offset", "bne", "beq")
}
}
viaScratchReg("bne", "beq")
@ -1036,19 +1069,18 @@ _jump jmp ($asmLabel)
} else {
when (value) {
is PtArrayIndexer -> {
if(value.splitWords) {
TODO("split word array ==0")
} else {
val constIndex = value.index.asConstInteger()
if (constIndex != null) {
val offset = constIndex * program.memsizer.memorySize(value.type)
if (offset < 256) {
val varName = asmgen.asmVariableName(value.variable)
return translateLoadFromVar("$varName+$offset", "beq", "bne")
}
val constIndex = value.index.asConstInteger()
if (constIndex != null) {
val varName = asmgen.asmVariableName(value.variable)
if(value.splitWords) {
return translateLoadFromVarSplitw(varName, constIndex, "beq", "bne")
}
val offset = constIndex * program.memsizer.memorySize(value.type)
if (offset < 256) {
return translateLoadFromVar("$varName+$offset", "beq", "bne")
}
viaScratchReg("beq", "bne")
}
viaScratchReg("beq", "bne")
}
is PtIdentifier -> {
return translateLoadFromVar(asmgen.asmVariableName(value), "beq", "bne")
@ -1170,16 +1202,16 @@ _jump jmp ($asmLabel)
val constIndex = left.index.asConstInteger()
if(constIndex!=null) {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varName = asmgen.asmVariableName(left.variable)
if(left.splitWords) {
TODO("split word array !=")
return translateNotEquals("${varName}_lsb+$constIndex", "${varName}_msb+$constIndex")
}
val offset = constIndex * program.memsizer.memorySize(left.type)
if(offset<256) {
val varName = asmgen.asmVariableName(left.variable)
return translateNotEquals("$varName+$offset", "$varName+$offset+1")
}
}
fallbackTranslate(stmt, true)
fallbackTranslate(stmt)
}
is PtIdentifier -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
@ -1188,14 +1220,14 @@ _jump jmp ($asmLabel)
}
is PtAddressOf -> {
if(left.isFromArrayElement)
fallbackTranslate(stmt, false)
fallbackTranslateForSimpleCondition(stmt)
else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left.identifier)
return translateNotEquals("#<$varname", "#>$varname")
}
}
else -> fallbackTranslate(stmt, false)
else -> fallbackTranslate(stmt)
}
} else {
when(left) {
@ -1203,16 +1235,16 @@ _jump jmp ($asmLabel)
val constIndex = left.index.asConstInteger()
if(constIndex!=null) {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varName = asmgen.asmVariableName(left.variable)
if(left.splitWords) {
TODO("split word array ==")
return translateEquals("${varName}_lsb+$constIndex", "${varName}_msb+$constIndex")
}
val offset = constIndex * program.memsizer.memorySize(left.type)
if(offset<256) {
val varName = asmgen.asmVariableName(left.variable)
return translateEquals("$varName+$offset", "$varName+$offset+1")
}
}
fallbackTranslate(stmt, true)
fallbackTranslate(stmt)
}
is PtIdentifier -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
@ -1221,14 +1253,14 @@ _jump jmp ($asmLabel)
}
is PtAddressOf -> {
if(left.isFromArrayElement)
fallbackTranslate(stmt, false)
fallbackTranslateForSimpleCondition(stmt)
else {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left.identifier)
return translateEquals("#<$varname", "#>$varname")
}
}
else -> fallbackTranslate(stmt, false)
else -> fallbackTranslate(stmt)
}
}
}
@ -1298,7 +1330,7 @@ _jump jmp ($asmLabel)
else
translateIfElseBodies("bne", stmt)
}
else -> fallbackTranslate(stmt, false)
else -> throw AssemblyError("expected comparison operator")
}
}

View File

@ -1207,7 +1207,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
return false
}
private fun optimizedLogicalExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
internal fun optimizedLogicalExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
fun swapOperands(): Boolean =
if(expr.right is PtIdentifier || expr.right is PtMemoryByte)

View File

@ -9,7 +9,9 @@ test: clean generate test_prgs
generate:
python make_eq_tests.py
python make_eq_tests_splitw.py
python make_cmp_tests.py
python make_cmp_tests_splitw.py
p8compile -target cx16 -sourcelines *.p8 >/dev/null
test_prgs:

View File

@ -0,0 +1,196 @@
# generates various Prog8 files with a large amount of number comparison tests,
# using various forms of the if statement (because these forms have their own code gen paths)
import sys
fail_index = 0
class C:
def __init__(self, short, long, operator, compare):
self.short=short
self.long=long
self.operator=operator
self.compare=compare
def header(dt, comparison: C):
print(f"""
%import textio
%import floats
%import test_stack
%zeropage dontuse
%option no_sysinit
main {{
uword success = 0
str datatype = "{dt}"
uword @shared comparison
sub start() {{
txt.print("\\n{comparison.long} split words array tests for: ")
txt.print(datatype)
txt.nl()
test_stack.test()
txt.print("\\n{comparison.operator}array[]: ")
test_cmp_array()
test_stack.test()
}}
sub verify_success(uword expected) {{
if success==expected {{
txt.print("ok")
}} else {{
txt.print(" **failed** ")
txt.print_uw(success)
txt.print(" success, expected ")
txt.print_uw(expected)
}}
}}
sub fail_word(uword idx) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.print(" **")
}}
sub fail_uword(uword idx) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.print(" **")
}}
""")
def tc(value):
if value < 0x8000:
return value
else:
return -(65536 - value)
testnumbers = {
"word": [tc(0xaabb), -1, 0, 1, 0x00aa, 0x7700, 0x7fff],
"uword": [0, 1, 0x7700, 0xffff],
}
def make_test_array(datatype, comparison: C):
numbers = testnumbers[datatype]
print(" sub test_cmp_array() {")
print(f""" {datatype} @shared x
{datatype}[] @split values = [0, 0]
{datatype}[] @split sources = [0, 0]
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" x={x}")
print(f" sources[1]={x}")
for value in numbers:
print(f" values[1]={value}")
result = comparison.compare(x, value)
comp = comparison.operator
test_index += 1
if result:
expected += 8 # there are 4 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
fail_action4 = "cx16.r0L++"
true_action5 = "success++"
true_action6 = "success++"
true_action7 = "success++"
true_action8 = "success++"
fail_action8 = "cx16.r0L++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index})"
fail_action4 = "success++"
fail_index += 1
true_action5 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action6 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action7 = f"fail_{datatype}({fail_index})"
fail_index += 1
true_action8 = f"fail_{datatype}({fail_index})"
fail_action8 = "success++"
expected += 2
print(f""" ; direct jump
if x{comp}values[1]
goto lbl{test_index}a
goto skip{test_index}a
lbl{test_index}a: {true_action1}
skip{test_index}a:
; indirect jump
cx16.r3 = &lbl{test_index}b
if x{comp}values[1]
goto cx16.r3
goto skip{test_index}b
lbl{test_index}b: {true_action2}
skip{test_index}b:
; no else
if x{comp}values[1]
{true_action3}
; with else
if x{comp}values[1]
{true_action4}
else
{fail_action4}
""")
print(f""" ; direct jump
if sources[1]{comp}values[1]
goto lbl{test_index}c
goto skip{test_index}c
lbl{test_index}c: {true_action5}
skip{test_index}c:
; indirect jump
cx16.r3 = &lbl{test_index}d
if sources[1]{comp}values[1]
goto cx16.r3
goto skip{test_index}d
lbl{test_index}d: {true_action6}
skip{test_index}d:
; no else
if sources[1]{comp}values[1]
{true_action7}
; with else
if sources[1]{comp}values[1]
{true_action8}
else
{fail_action8}
""")
print(f" verify_success({expected})\n}}")
def generate(datatype, comparison: C):
global fail_index
fail_index = 0
header(datatype, comparison)
make_test_array(datatype, comparison)
print("\n}\n")
if __name__ == '__main__':
comparisons = [
C("lt", "less-than", "<", lambda x,y: x < y),
C("lte", "less-equal", "<=", lambda x,y: x <= y),
C("gt", "greater-than", ">", lambda x,y: x > y),
C("gte", "greater-equal", ">=", lambda x,y: x >= y),
]
for comparison in comparisons:
for dt in ["uword", "word"]:
sys.stdout = open(f"test_{dt}_splitw_{comparison.short}.p8", "wt")
generate(dt, comparison)

View File

@ -58,53 +58,43 @@ main {{
}}
}}
sub fail_byte(uword idx, byte v1, byte v2) {{
sub fail_byte(uword idx, byte v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_b(v1)
txt.chrout(',')
txt.print_b(v2)
txt.print(" **")
}}
sub fail_ubyte(uword idx, ubyte v1, ubyte v2) {{
sub fail_ubyte(uword idx, ubyte v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_ub(v1)
txt.chrout(',')
txt.print_ub(v2)
txt.print(" **")
}}
sub fail_word(uword idx, word v1, word v2) {{
sub fail_word(uword idx, word v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_w(v1)
txt.chrout(',')
txt.print_w(v2)
txt.print(" **")
}}
sub fail_uword(uword idx, uword v1, uword v2) {{
sub fail_uword(uword idx, uword v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_uw(v1)
txt.chrout(',')
txt.print_uw(v2)
txt.print(" **")
}}
sub fail_float(uword idx, float v1, float v2) {{
sub fail_float(uword idx, float v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
floats.print(v1)
txt.chrout(',')
floats.print(v2)
txt.print(" **")
}}
@ -284,13 +274,13 @@ def make_test_is_number(datatype, equals):
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},x,{value})"
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},x,{value})"
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},x,{value})"
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},x,{value})"
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if x{comp}{value}
goto lbl{test_index}a
@ -342,13 +332,13 @@ def make_test_is_var(datatype, equals):
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},x,value)"
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},x,value)"
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},x,value)"
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},x,value)"
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if x{comp}value
goto lbl{test_index}a
@ -380,12 +370,14 @@ def make_test_is_array(datatype, equals):
print(" sub test_is_array() {" if equals else " sub test_not_array() {")
print(f""" {datatype} @shared x
{datatype}[] values = [0, 0]
{datatype}[] sources = [0, 0]
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" x={x}")
print(f" sources[1]={x}")
for value in numbers:
if value == 0:
continue # 0 already tested separately
@ -394,20 +386,32 @@ def make_test_is_array(datatype, equals):
comp = "==" if equals else "!="
test_index += 1
if result:
expected += 4 # there are 4 test types for every successful combo
expected += 8 # there are 8 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
true_action5 = "success++"
true_action6 = "success++"
true_action7 = "success++"
true_action8 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},x,{value})"
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},x,{value})"
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},x,{value})"
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},x,{value})"
true_action4 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action5 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action6 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action7 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action8 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if x{comp}values[1]
goto lbl{test_index}a
@ -430,6 +434,29 @@ skip{test_index}b:
{true_action4}
else
cx16.r0L++
""")
print(f""" ; direct jump
if sources[1]{comp}values[1]
goto lbl{test_index}c
goto skip{test_index}c
lbl{test_index}c: {true_action5}
skip{test_index}c:
; indirect jump
cx16.r3 = &lbl{test_index}d
if sources[1]{comp}values[1]
goto cx16.r3
goto skip{test_index}d
lbl{test_index}d: {true_action6}
skip{test_index}d:
; no else
if sources[1]{comp}values[1]
{true_action7}
; with else
if sources[1]{comp}values[1]
{true_action8}
else
cx16.r0L++
""")
print(f" verify_success({expected})\n}}")
@ -472,13 +499,13 @@ def make_test_is_expr(datatype, equals):
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},x,{value})"
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},x,{value})"
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},x,{value})"
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},x,{value})"
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if x{comp}{expr}
goto lbl{test_index}a
@ -515,10 +542,10 @@ def generate(datatype):
make_test_is_number(datatype, False)
make_test_is_var(datatype, True)
make_test_is_var(datatype, False)
make_test_is_array(datatype, True)
make_test_is_array(datatype, False)
make_test_is_expr(datatype, True)
make_test_is_expr(datatype, False)
make_test_is_array(datatype, True)
make_test_is_array(datatype, False)
print("\n}\n")

View File

@ -0,0 +1,530 @@
# generates various Prog8 files with a large amount of number equality tests,
# using various forms of the if statement (because these forms have their own code gen paths)
import sys
fail_index = 0
def header(dt):
print(f"""
%import textio
%import floats
%import test_stack
%zeropage dontuse
%option no_sysinit
main {{
ubyte success = 0
str datatype = "{dt}"
uword @shared comparison
sub start() {{
txt.print("\\n(in)equality tests for split words datatype: ")
txt.print(datatype)
txt.nl()
test_stack.test()
txt.print("==0: ")
test_is_zero()
txt.print("\\n!=0: ")
test_not_zero()
txt.print("\\n==number: ")
test_is_number()
txt.print("\\n!=number: ")
test_not_number()
txt.print("\\n==var: ")
test_is_var()
txt.print("\\n!=var: ")
test_not_var()
txt.print("\\n==array[] @split: ")
test_is_array_splitw()
txt.print("\\n!=array[] @split: ")
test_not_array_splitw()
txt.print("\\n==expr: ")
test_is_expr()
txt.print("\\n!=expr: ")
test_not_expr()
test_stack.test()
}}
sub verify_success(ubyte expected) {{
if success==expected {{
txt.print("ok")
}} else {{
txt.print(" **failed** ")
txt.print_ub(success)
txt.print(" success, expected ")
txt.print_ub(expected)
}}
}}
sub fail_word(uword idx, word v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_w(v1)
txt.print(" **")
}}
sub fail_uword(uword idx, uword v1) {{
txt.print(" **fail#")
txt.print_uw(idx)
txt.chrout(':')
txt.print_uw(v1)
txt.print(" **")
}}
""")
zero_values = {
"byte": 0,
"ubyte": 0,
"word": 0,
"uword": 0,
"float": 0.0
}
nonzero_values = {
"byte": -100,
"ubyte": 100,
"word": -9999,
"uword": 9999,
"float": 1234.56
}
def make_test_is_zero(datatype):
print(f"""
sub test_is_zero() {{
{datatype}[] @split sources = [9999, 9999]
success = 0
sources[1]={zero_values[datatype]}
; direct jump
if sources[1]==0
goto lbl1
goto skip1
lbl1: success++
skip1:
; indirect jump
cx16.r3 = &lbl2
if sources[1]==0
goto cx16.r3
goto skip2
lbl2: success++
skip2:
; no else
if sources[1]==0
success++
; with else
if sources[1]==0
success++
else
cx16.r0L++
sources[1] = {nonzero_values[datatype]}
; direct jump
if sources[1]==0
goto skip3
success++
skip3:
; indirect jump
cx16.r3 = &skip4
if sources[1]==0
goto cx16.r3
success++
skip4:
; no else
success++
if sources[1]==0
success--
; with else
if sources[1]==0
cx16.r0L++
else
success++
verify_success(8)
}}
""")
def make_test_not_zero(datatype):
print(f"""
sub test_not_zero() {{
{datatype}[] @split sources = [9999, 9999]
success = 0
sources[1]={nonzero_values[datatype]}
; direct jump
if sources[1]!=0
goto lbl1
goto skip1
lbl1: success++
skip1:
; indirect jump
cx16.r3 = &lbl2
if sources[1]!=0
goto cx16.r3
goto skip2
lbl2: success++
skip2:
; no else
if sources[1]!=0
success++
; with else
if sources[1]!=0
success++
else
cx16.r0L++
sources[1] = {zero_values[datatype]}
; direct jump
if sources[1]!=0
goto skip3
success++
skip3:
; indirect jump
cx16.r3 = &skip4
if sources[1]!=0
goto cx16.r3
success++
skip4:
; no else
success++
if sources[1]!=0
success--
; with else
if sources[1]!=0
cx16.r0L++
else
success++
verify_success(8)
}}
""")
def tc(value):
if value < 0x8000:
return value
else:
return -(65536 - value)
testnumbers = {
"byte": [-100, 0, 100],
"ubyte": [0, 1, 255],
"word": [tc(0xaabb), 0, 0x00aa, 0x7700, 0x7fff],
"uword": [0, 1, 0x7700, 0xffff],
"float": [0.0, 1234.56]
}
def make_test_is_number(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_number() {" if equals else " sub test_not_number() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" sources[1]={x}")
for value in numbers:
if value == 0:
continue # 0 already tested separately
result = (x == value) if equals else (x != value)
comp = "==" if equals else "!="
test_index += 1
if result:
expected += 4 # there are 4 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if sources[1]{comp}{value}
goto lbl{test_index}a
goto skip{test_index}a
lbl{test_index}a: {true_action1}
skip{test_index}a:
; indirect jump
cx16.r3 = &lbl{test_index}b
if sources[1]{comp}{value}
goto cx16.r3
goto skip{test_index}b
lbl{test_index}b: {true_action2}
skip{test_index}b:
; no else
if sources[1]{comp}{value}
{true_action3}
; with else
if sources[1]{comp}{value}
{true_action4}
else
cx16.r0L++
""")
print(f" verify_success({expected})\n}}")
def make_test_is_var(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_var() {" if equals else " sub test_not_var() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
{datatype}[] @split values = [8888,8888]
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" sources[1]={x}")
for value in numbers:
if value == 0:
continue # 0 already tested separately
print(f" values[1]={value}")
result = (x == value) if equals else (x != value)
comp = "==" if equals else "!="
test_index += 1
if result:
expected += 4 # there are 4 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if sources[1]{comp}values[1]
goto lbl{test_index}a
goto skip{test_index}a
lbl{test_index}a: {true_action1}
skip{test_index}a:
; indirect jump
cx16.r3 = &lbl{test_index}b
if sources[1]{comp}values[1]
goto cx16.r3
goto skip{test_index}b
lbl{test_index}b: {true_action2}
skip{test_index}b:
; no else
if sources[1]{comp}values[1]
{true_action3}
; with else
if sources[1]{comp}values[1]
{true_action4}
else
cx16.r0L++
""")
print(f" verify_success({expected})\n}}")
def make_test_is_array(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_array_splitw() {" if equals else " sub test_not_array_splitw() {")
print(f"""
{datatype}[] @split values = [9999, 8888]
{datatype}[] @split sources = [9999, 8888]
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" values[1]={x}")
print(f" sources[1]={x}")
for value in numbers:
if value == 0:
continue # 0 already tested separately
print(f" values[1]={value}")
result = (x == value) if equals else (x != value)
comp = "==" if equals else "!="
test_index += 1
if result:
expected += 8 # there are 8 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
true_action5 = "success++"
true_action6 = "success++"
true_action7 = "success++"
true_action8 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action5 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action6 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action7 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action8 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if sources[1]{comp}values[1]
goto lbl{test_index}a
goto skip{test_index}a
lbl{test_index}a: {true_action1}
skip{test_index}a:
; indirect jump
cx16.r3 = &lbl{test_index}b
if sources[1]{comp}values[1]
goto cx16.r3
goto skip{test_index}b
lbl{test_index}b: {true_action2}
skip{test_index}b:
; no else
if sources[1]{comp}values[1]
{true_action3}
; with else
if sources[1]{comp}values[1]
{true_action4}
else
cx16.r0L++
""")
print(f""" ; direct jump
if sources[1]{comp}values[1]
goto lbl{test_index}c
goto skip{test_index}c
lbl{test_index}c: {true_action5}
skip{test_index}c:
; indirect jump
cx16.r3 = &lbl{test_index}d
if sources[1]{comp}values[1]
goto cx16.r3
goto skip{test_index}d
lbl{test_index}d: {true_action6}
skip{test_index}d:
; no else
if sources[1]{comp}values[1]
{true_action7}
; with else
if sources[1]{comp}values[1]
{true_action8}
else
cx16.r0L++
""")
print(f" verify_success({expected})\n}}")
def make_test_is_expr(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_expr() {" if equals else " sub test_not_expr() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
cx16.r4 = 1
cx16.r5 = 1
success = 0""")
expected = 0
test_index = 0
global fail_index
for x in numbers:
print(f" sources[1]={x}")
for value in numbers:
if value == 0:
continue # 0 already tested separately
if datatype=="byte":
expr = f"cx16.r4sL+{value}-cx16.r5sL"
elif datatype=="ubyte":
expr = f"cx16.r4L+{value}-cx16.r5L"
elif datatype=="word":
expr = f"cx16.r4s+{value}-cx16.r5s"
elif datatype=="uword":
expr = f"cx16.r4+{value}-cx16.r5"
elif datatype=="float":
expr = f"f4+{value}-f5"
result = (x == value) if equals else (x != value)
comp = "==" if equals else "!="
test_index += 1
if result:
expected += 4 # there are 4 test types for every successful combo
true_action1 = "success++"
true_action2 = "success++"
true_action3 = "success++"
true_action4 = "success++"
else:
fail_index += 1
true_action1 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action2 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action3 = f"fail_{datatype}({fail_index},{x})"
fail_index += 1
true_action4 = f"fail_{datatype}({fail_index},{x})"
print(f""" ; direct jump
if sources[1]{comp}{expr}
goto lbl{test_index}a
goto skip{test_index}a
lbl{test_index}a: {true_action1}
skip{test_index}a:
; indirect jump
cx16.r3 = &lbl{test_index}b
if sources[1]{comp}{expr}
goto cx16.r3
goto skip{test_index}b
lbl{test_index}b: {true_action2}
skip{test_index}b:
; no else
if sources[1]{comp}{expr}
{true_action3}
; with else
if sources[1]{comp}{expr}
{true_action4}
else
cx16.r0L++
""")
print(f" verify_success({expected})\n}}")
def generate(datatype):
global fail_index
fail_index = 0
header(datatype)
make_test_is_zero(datatype)
make_test_not_zero(datatype)
make_test_is_number(datatype, True)
make_test_is_number(datatype, False)
make_test_is_var(datatype, True)
make_test_is_var(datatype, False)
make_test_is_expr(datatype, True)
make_test_is_expr(datatype, False)
make_test_is_array(datatype, True)
make_test_is_array(datatype, False)
print("\n}\n")
if __name__ == '__main__':
for dt in ["uword", "word"]:
sys.stdout = open(f"test_{dt}_splitw_equalities.p8", "wt")
generate(dt)

View File

@ -1,21 +1,17 @@
TODO
====
check all comparisons for split word arrays (signed+unsigned, all 4 variants)
snow example is a lot larger!
explore possible optimizations for words when comparing to a constant number (BeforeAsmAstChanger)
add tests for comparison that do an assignment rather than an if
assign to variable, and barray[indexvar], test if they're both correct
(bb = value > -100 --> contains a beq +++ that shouldn't be there??)
optimize assignOptimizedComparisonWords for when comparing to simple things like number and identifier.
optimize optimizedPlusMinExpr for when comparing to simple things like number and identifier.
optimize the IfElseAsmgen's fallbackTranslate even more (for arrays without const index for example?)
optimize byte < and byte >= assignment to use the rol trick instead of branching:
bool @shared bb = cx16.r0L >= 128
bool @shared bb2 = cx16.r0L < 99
snow example is a lot larger!
===== ====== =======
@ -45,7 +41,7 @@ ok ok efficient code for if byte comparisons against 0 (== and !=)
ok ok efficient code for if word comparisons against 0 (== and !=)
ok ok efficient code for if float comparisons against 0 (== and !=)
ok ok efficient code for if byte comparisons against a value
ok FAIL efficient code for if word comparisons against a value
ok ok efficient code for if word comparisons against a value
ok ok efficient code for if float comparisons against a value
ok ok efficient code for assignment byte comparisons against 0 (== and !=)
ok ok efficient code for assignment word comparisons against 0 (== and !=)
@ -61,11 +57,8 @@ ok . check program sizes vs. master branch
===== ====== =======
check that the flood fill routine in gfx2 and paint still works.
re-allow typecast of const true/false back to ubytes 1 and 0?
re-allow typecast of const ubyte 0/1 to false/true boolean?
IR: add TEST instruction to test memory content and set N/Z flags, without affecting any register.
replace all LOADM+CMPI #0 / LOAD #0+LOADM+CMP+BRANCH by this instruction
optimize translateIfByte() handling of shortcircuiting logical operators.

View File

@ -6,32 +6,40 @@
main {
sub start() {
ubyte[] barray = [11,22,33]
uword[] warray = [1111,2222,3333]
if any(barray)
cx16.r0++
ubyte[] envelope_attacks = [1,2,3,4]
if msb(cx16.r0) > cx16.r2L or envelope_attacks[cx16.r1L]==0 {
txt.print("yep")
}
if barray[2] == 33
cx16.r0++
else
cx16.r1++
if warray[2] == 3333
cx16.r0++
else
cx16.r1++
if barray[cx16.r0L] == 33
cx16.r0++
else
cx16.r1++
if warray[cx16.r0L] == 3333
cx16.r0++
else
cx16.r1++
;
; ubyte[] barray = [11,22,33]
; uword[] warray = [1111,2222,3333]
;
; if any(barray)
; cx16.r0++
;
;
; if barray[2] == 33
; cx16.r0++
; else
; cx16.r1++
;
; if warray[2] == 3333
; cx16.r0++
; else
; cx16.r1++
;
; if barray[cx16.r0L] == 33
; cx16.r0++
; else
; cx16.r1++
;
; if warray[cx16.r0L] == 3333
; cx16.r0++
; else
; cx16.r1++
}
}