more Rpn optimizations

This commit is contained in:
Irmen de Jong 2023-03-19 00:24:05 +01:00
parent 52b560e72d
commit 6e4ae034b2
14 changed files with 551 additions and 164 deletions

View File

@ -15,7 +15,12 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
* This gives the fastest lookup possible (no need to traverse tree nodes)
*/
val flat: Map<String, StNode> by lazy {
private var cachedFlat: Map<String, StNode>? = null
val flat: Map<String, StNode> get() {
if(cachedFlat!=null)
return cachedFlat!!
val result = mutableMapOf<String, StNode>()
fun collect(node: StNode) {
for(child in node.children) {
@ -24,9 +29,14 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
}
}
collect(this)
result
cachedFlat = result
return result
}
// fun resetCachedFlat() {
// cachedFlat = null
// }
val allVariables: Collection<StStaticVariable> by lazy {
val vars = mutableListOf<StStaticVariable>()
fun collect(node: StNode) {

View File

@ -472,8 +472,8 @@ class AsmGen6502Internal (
internal fun restoreXafterCall(functionCall: PtFunctionCall) =
functioncallAsmGen.restoreXafterCall(functionCall)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
internal fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) =
assignmentAsmGen.translateNormalAssignment(assign, scope)
internal fun assignExpressionToRegister(expr: PtExpression, register: RegisterOrPair, signed: Boolean=false) =
assignmentAsmGen.assignExpressionToRegister(expr, register, signed)
@ -481,8 +481,8 @@ class AsmGen6502Internal (
internal fun assignExpressionToVariable(expr: PtExpression, asmVarName: String, dt: DataType, scope: IPtSubroutine?) =
assignmentAsmGen.assignExpressionToVariable(expr, asmVarName, dt, scope)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, pos: Position, signed: Boolean=false) =
assignmentAsmGen.assignVariableToRegister(asmVarName, register, signed, pos)
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, scope: IPtSubroutine?, pos: Position, signed: Boolean=false) =
assignmentAsmGen.assignVariableToRegister(asmVarName, register, signed, scope, pos)
internal fun assignRegister(reg: RegisterOrPair, target: AsmAssignTarget) {
when(reg) {
@ -512,7 +512,7 @@ class AsmGen6502Internal (
AsmAssignment(
AsmAssignSource(SourceStorageKind.REGISTER, program, this, target.datatype, register=RegisterOrPair.AY),
target, program.memsizer, value.position
)
), value.definingISub()
)
}
DataType.FLOAT -> {
@ -626,11 +626,11 @@ class AsmGen6502Internal (
val name = asmVariableName(stmt.count as PtIdentifier)
when(vardecl.type) {
DataType.UBYTE, DataType.BYTE -> {
assignVariableToRegister(name, RegisterOrPair.Y, stmt.count.position)
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position)
repeatCountInY(stmt, endLabel)
}
DataType.UWORD, DataType.WORD -> {
assignVariableToRegister(name, RegisterOrPair.AY, stmt.count.position)
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position)
repeatWordCountInAY(endLabel, stmt)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
@ -1062,7 +1062,9 @@ $repeatLabel lda $counterVar
// could be that the index was a constant numeric byte but converted to word, check that
val constIdx = right as? PtNumber
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
return Pair(left, PtNumber(DataType.UBYTE, constIdx.number, constIdx.position))
val num = PtNumber(DataType.UBYTE, constIdx.number, constIdx.position)
num.parent = right.parent
return Pair(left, num)
}
// could be that the index was typecasted into uword, check that
val rightTc = right as? PtTypeCast
@ -1160,26 +1162,61 @@ $repeatLabel lda $counterVar
}
internal fun findSubroutineParameter(name: String, asmgen: AsmGen6502Internal): PtSubroutineParameter? {
val node = asmgen.symbolTable.lookup(name)!!.astNode
val stScope = asmgen.symbolTable.lookup(name)
require(stScope!=null) {
"invalid name lookup $name"
}
val node = stScope.astNode
if(node is PtSubroutineParameter)
return node
return node.definingSub()?.parameters?.singleOrNull { it.name===name }
}
private fun translateCompareAndJumpIfTrueRPN(expr: PtRpn, jump: PtJump) {
val (left, oper, right) = expr.finalOperation()
if(expr.children.size>3) {
TODO("RPN comparison too complex ${expr.position}")
}
require(left is PtExpression && right is PtExpression)
if(oper.operator !in ComparisonOperators)
throw AssemblyError("must be comparison expression")
// invert the comparison, so we can reuse the JumpIfFalse code generation routines
val invertedComparisonOperator = invertedComparisonOperator(oper.operator)
?: throw AssemblyError("can't invert comparison $expr")
val rightConstVal = right as? PtNumber
val label = when {
jump.generatedLabel!=null -> jump.generatedLabel!!
jump.identifier!=null -> asmSymbolName(jump.identifier!!)
jump.address!=null -> jump.address!!.toHex()
else -> throw AssemblyError("weird jump")
}
assignExpressionToRegister(expr, RegisterOrPair.A)
out(" bne $label")
if (rightConstVal!=null && rightConstVal.number == 0.0)
testZeroAndJump(left, invertedComparisonOperator, label)
else {
val leftConstVal = left as? PtNumber
testNonzeroComparisonAndJump(left, invertedComparisonOperator, right, label, leftConstVal, rightConstVal)
}
}
private fun translateCompareAndJumpIfFalseRPN(expr: PtRpn, jumpIfFalseLabel: String) {
assignExpressionToRegister(expr, RegisterOrPair.A)
out(" beq $jumpIfFalseLabel")
val (left, oper, right) = expr.finalOperation()
if(expr.children.size>3) {
TODO("RPN comparison too complex ${expr.position}")
}
require(left is PtExpression && right is PtExpression)
val leftConstVal = left as? PtNumber
val rightConstVal = right as? PtNumber
if (rightConstVal!=null && rightConstVal.number == 0.0)
testZeroAndJump(left, oper.operator, jumpIfFalseLabel)
else
testNonzeroComparisonAndJump(left, oper.operator, right, jumpIfFalseLabel, leftConstVal, rightConstVal)
}
private fun translateCompareAndJumpIfTrue(expr: PtBinaryExpression, jump: PtJump) {
@ -1374,7 +1411,7 @@ $repeatLabel lda $counterVar
}
}
private fun testNonzeroComparisonAndJump(
internal fun testNonzeroComparisonAndJump(
left: PtExpression,
operator: String,
right: PtExpression,

View File

@ -250,7 +250,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, fcall.position, null, asmgen)
val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
asmgen.translateNormalAssignment(assign, fcall.definingISub())
}
private fun funcSqrt16(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
@ -998,7 +998,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, value.position, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
asmgen.translateNormalAssignment(assign, scope)
}
conv.reg != null -> {
val src = when (conv.dt) {
@ -1016,7 +1016,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, value.position, null, asmgen)
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
asmgen.translateNormalAssignment(assign, scope)
}
else -> throw AssemblyError("callconv")
}

View File

@ -478,15 +478,15 @@ internal class ExpressionsAsmGen(private val program: PtProgram,
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
return true
}
var left = amount
while (left >= 7) {
var amountLeft = amount
while (amountLeft >= 7) {
asmgen.out(" jsr math.shift_right_uw_7")
left -= 7
amountLeft -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
if (amountLeft in 0..2)
repeat(amountLeft) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_uw_$left")
asmgen.out(" jsr math.shift_right_uw_$amountLeft")
}
DataType.WORD -> {
if(amount>=16) {
@ -503,15 +503,15 @@ internal class ExpressionsAsmGen(private val program: PtProgram,
+""")
return true
}
var left = amount
while (left >= 7) {
var amountLeft = amount
while (amountLeft >= 7) {
asmgen.out(" jsr math.shift_right_w_7")
left -= 7
amountLeft -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
if (amountLeft in 0..2)
repeat(amountLeft) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_w_$left")
asmgen.out(" jsr math.shift_right_w_$amountLeft")
}
else -> throw AssemblyError("weird type")
}
@ -531,15 +531,15 @@ internal class ExpressionsAsmGen(private val program: PtProgram,
asmgen.out(" sta P8ESTACK_LO+1,x")
}
} else {
var left = amount
while (left >= 7) {
var amountLeft = amount
while (amountLeft >= 7) {
asmgen.out(" jsr math.shift_left_w_7")
left -= 7
amountLeft -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
if (amountLeft in 0..2)
repeat(amountLeft) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
else
asmgen.out(" jsr math.shift_left_w_$left")
asmgen.out(" jsr math.shift_left_w_$amountLeft")
}
return true
}

View File

@ -200,7 +200,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
// we need to sign extend the source, do this via temporary word variable
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, Position.DUMMY)
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register, null, Position.DUMMY)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
@ -221,7 +221,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY))
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY), sub)
}
}
}

View File

@ -1,35 +1,36 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen6502Internal
import prog8.codegen.cpu6502.VariableAllocator
import prog8.codegen.cpu6502.returnsWhatWhere
import java.util.*
internal class AssignmentAsmGen(private val program: PtProgram,
symbolTable: SymbolTable,
private val symbolTable: SymbolTable,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
private val rpnAssignmentAsmGen = RpnExpressionAsmGen(program, symbolTable, asmgen)
fun translate(assignment: PtAssignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment.target, assignment.definingISub(), asmgen)
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(source, target, program.memsizer, assignment.position)
translateNormalAssignment(assign)
translateNormalAssignment(assign, assignment.definingISub())
}
fun translate(augmentedAssign: PtAugmentedAssign) {
val target = AsmAssignTarget.fromAstAssignment(augmentedAssign.target, augmentedAssign.definingISub(), asmgen)
val source = AsmAssignSource.fromAstSource(augmentedAssign.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAugmentedAssignment(source, augmentedAssign.operator, target, program.memsizer, augmentedAssign.position)
augmentableAsmGen.translate(assign)
augmentableAsmGen.translate(assign, augmentedAssign.definingISub())
}
fun translateNormalAssignment(assign: AsmAssignment) {
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
when(assign.source.kind) {
SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number
@ -164,7 +165,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
SourceStorageKind.EXPRESSION -> {
assignExpression(assign)
assignExpression(assign, scope)
}
SourceStorageKind.REGISTER -> {
asmgen.assignRegister(assign.source.register!!, assign.target)
@ -176,7 +177,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignExpression(assign: AsmAssignment) {
private fun assignExpression(assign: AsmAssignment, scope: IPtSubroutine?) {
when(val value = assign.source.expression!!) {
is PtAddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
@ -289,17 +290,17 @@ internal class AssignmentAsmGen(private val program: PtProgram,
AsmAssignment(
AsmAssignSource.fromAstSource(value.value, program, asmgen),
assign.target, program.memsizer, assign.position
)
), scope
)
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(assign, true)
"~" -> inplaceInvert(assign)
"-" -> inplaceNegate(assign, true, scope)
"~" -> inplaceInvert(assign, scope)
"not" -> throw AssemblyError("not should have been replaced in the Ast by ==0")
else -> throw AssemblyError("invalid prefix operator")
}
} else {
assignPrefixedExpressionToArrayElt(assign)
assignPrefixedExpressionToArrayElt(assign, scope)
}
}
is PtContainmentCheck -> {
@ -316,7 +317,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
is PtRpn -> {
if(!rpnAssignmentAsmGen.attemptAssignOptimizedExpr(assign)) {
if(!attemptAssignOptimizedExprRPN(assign, scope!!)) {
// All remaining binary expressions just evaluate via the stack for now.
// TODO: For RPN expressions this should never occur anymore and the eval stack should be removed when we achieve this
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
@ -328,7 +329,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment) {
private fun assignPrefixedExpressionToArrayElt(assign: AsmAssignment, scope: IPtSubroutine?) {
require(assign.source.expression is PtPrefix)
if(assign.source.datatype==DataType.FLOAT) {
// floatarray[x] = -value ... just use FAC1 to calculate the expression into and then store that back into the array.
@ -340,7 +341,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val assignToTempvar = AsmAssignment(assign.source,
AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, assign.target.datatype, assign.target.scope, assign.target.position,
variableAsmName=tempvar, origAstTarget = assign.target.origAstTarget), program.memsizer, assign.position)
asmgen.translateNormalAssignment(assignToTempvar)
asmgen.translateNormalAssignment(assignToTempvar, scope)
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, tempvar)
in WordDatatypes -> assignVariableWord(assign.target, tempvar)
@ -361,6 +362,221 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
private fun attemptAssignOptimizedExprRPN(assign: AsmAssignment, scope: IPtSubroutine): Boolean {
val value = assign.source.expression as PtRpn
val (left, oper, right) = value.finalOperation()
if(oper.operator in ComparisonOperators) {
assignRPNComparison(assign, value)
return true
}
fun simpleLogicalBytesExpr() {
// both left and right expression operands are simple.
require(left is PtExpression && right is PtExpression)
if (right is PtNumber || right is PtIdentifier)
assignLogicalWithSimpleRightOperandByte(assign.target, left, oper.operator, right)
else if (left is PtNumber || left is PtIdentifier)
assignLogicalWithSimpleRightOperandByte(assign.target, right, oper.operator, left)
else {
assignExpressionToRegister(left, RegisterOrPair.A, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
assignExpressionToVariable(right, "P8ZP_SCRATCH_B1", DataType.UBYTE, scope)
asmgen.restoreRegisterStack(CpuRegister.A, false)
when (oper.operator) {
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterByte(assign.target, CpuRegister.A)
}
}
fun simpleLogicalWordsExpr() {
// both left and right expression operands are simple.
require(left is PtExpression && right is PtExpression)
if (right is PtNumber || right is PtIdentifier)
assignLogicalWithSimpleRightOperandWord(assign.target, left, oper.operator, right)
else if (left is PtNumber || left is PtIdentifier)
assignLogicalWithSimpleRightOperandWord(assign.target, right, oper.operator, left)
else {
assignExpressionToRegister(left, RegisterOrPair.AY, false)
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
assignExpressionToVariable(right, "P8ZP_SCRATCH_W1", DataType.UWORD, scope)
when (oper.operator) {
"&", "and" -> asmgen.out(" pla | and P8ZP_SCRATCH_W1+1 | tay | pla | and P8ZP_SCRATCH_W1")
"|", "or" -> asmgen.out(" pla | ora P8ZP_SCRATCH_W1+1 | tay | pla | ora P8ZP_SCRATCH_W1")
"^", "xor" -> asmgen.out(" pla | eor P8ZP_SCRATCH_W1+1 | tay | pla | eor P8ZP_SCRATCH_W1")
else -> throw AssemblyError("invalid operator")
}
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
}
if(value.children.size==3 && oper.operator in setOf("&", "|", "^", "and", "or", "xor")) {
if(left is PtExpression && right is PtExpression) {
if (left.type in ByteDatatypes && right.type in ByteDatatypes) {
if (right.isSimple()) {
simpleLogicalBytesExpr()
return true
}
}
if (left.type in WordDatatypes && right.type in WordDatatypes) {
if (right.isSimple()) {
simpleLogicalWordsExpr()
return true
}
}
}
}
// TODO RPN add +,-,<<,>> and perhaps even == and != special behaviors of BinExpr
val asmExtra = asmgen.subroutineExtra(scope)
val evalVars = mutableMapOf (
DataType.UBYTE to Stack<String>(),
DataType.UWORD to Stack<String>(),
DataType.FLOAT to Stack<String>()
)
fun getVarDt(dt: DataType) =
when(dt) {
in ByteDatatypes -> DataType.UBYTE
in WordDatatypes, in PassByReferenceDatatypes -> DataType.UWORD
else -> dt
}
fun evalVarName(dt: DataType, depth: Int): String {
val name: String
val varDt: DataType
when(dt) {
in ByteDatatypes -> {
name = "p8_rpn_eval_byte_$depth"
varDt = DataType.UBYTE
}
in WordDatatypes, in PassByReferenceDatatypes -> {
name = "p8_rpn_eval_word_$depth"
varDt = DataType.UWORD
}
DataType.FLOAT -> {
name = "p8_rpn_eval_float_$depth"
varDt = DataType.FLOAT
}
else -> throw AssemblyError("weird dt")
}
evalVars.getValue(varDt).push(name)
if(!asmExtra.extraVars.any { it.second==name }) {
val stScope = symbolTable.lookup((scope as PtNamedNode).scopedName)!!
val dummyNode = PtVariable(name, varDt, ZeropageWish.DONTCARE, null, null, Position.DUMMY)
dummyNode.parent = scope
stScope.add(StStaticVariable(name, varDt, null, null, null, null, ZeropageWish.DONTCARE, dummyNode))
asmExtra.extraVars.add(Triple(dt, name, null))
}
return name
}
var depth=0
value.children.forEach {
when (it) {
is PtRpnOperator -> {
val varDt = getVarDt(it.type)
val rightvar = evalVars.getValue(varDt).pop()
val leftvar = evalVars.getValue(varDt).pop()
depth-=2
val resultVarname = evalVarName(it.type, depth)
depth++
require(resultVarname==leftvar)
// TODO no longer needed? symbolTable.resetCachedFlat()
if(it.operator in ComparisonOperators) {
val scopeName = (scope as PtNamedNode).scopedName
val comparison = PtRpn(DataType.UBYTE, assign.position)
comparison.addRpnNode(PtIdentifier("$scopeName.$resultVarname", it.type, value.position))
comparison.addRpnNode(PtIdentifier("$scopeName.$rightvar", it.type, value.position))
comparison.addRpnNode(PtRpnOperator(it.operator, it.type, it.leftType, it.rightType, it.position))
comparison.parent = scope
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = comparison)
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, scope, assign.position, variableAsmName = resultVarname)
val normalAssign = AsmAssignment(src, target, program.memsizer, assign.position)
assignRPNComparison(normalAssign, comparison)
} else {
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, variableAsmName = rightvar)
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UBYTE, scope, assign.position, variableAsmName = resultVarname)
val augAssign = AsmAugmentedAssignment(src, it.operator+"=", target, program.memsizer, assign.position)
augmentableAsmGen.translate(augAssign, scope)
}
}
is PtExpression -> {
val varname = evalVarName(it.type, depth)
assignExpressionToVariable(it, varname, it.type, scope)
depth++
}
else -> throw AssemblyError("weird rpn node")
}
}
require(depth==1) { "unbalanced RPN: $depth ${value.position}" }
val resultVariable = evalVars.getValue(getVarDt(value.type)).pop()
when(assign.target.datatype) {
in ByteDatatypes -> assignVariableByte(assign.target, resultVariable)
in WordDatatypes -> assignVariableWord(assign.target, resultVariable)
DataType.FLOAT -> assignVariableFloat(assign.target, resultVariable)
else -> throw AssemblyError("weird dt")
}
require(evalVars.all { it.value.isEmpty() }) { "invalid rpn evaluation" }
return true
}
private fun assignRPNComparison(assign: AsmAssignment, comparison: PtRpn) {
val (left, oper, right) = comparison.finalOperation()
val constRight = (right as PtExpression).asConstInteger()
if(constRight == 0) {
if(oper.operator == "==" || oper.operator == "!=") {
when(assign.target.datatype) {
in ByteDatatypes -> {
if(attemptAssignToByteCompareZeroRPN(comparison, assign))
return
}
in WordDatatypes -> {
assignConstantWord(assign.target, 0)
if(attemptAssignToByteCompareZeroRPN(comparison, assign))
return
}
else -> {
// do nothing, this is handled by a type cast.
}
}
}
}
if(comparison.children.size>3) {
TODO("RPN comparison too complex ${comparison.position}")
}
require(left is PtExpression)
val leftNum = left as? PtNumber
val rightNum = right as? PtNumber
val jumpIfFalseLabel = asmgen.makeLabel("cmp")
when(assign.target.datatype) {
in ByteDatatypes -> assignConstantByte(assign.target, 0)
in WordDatatypes -> assignConstantWord(assign.target, 0)
DataType.FLOAT -> assignConstantFloat(assign.target, 0.0)
else -> throw AssemblyError("invalid dt")
}
asmgen.testNonzeroComparisonAndJump(left, oper.operator, right, jumpIfFalseLabel, leftNum, rightNum)
when(assign.target.datatype) {
in ByteDatatypes -> assignConstantByte(assign.target, 1)
in WordDatatypes -> assignConstantWord(assign.target, 1)
DataType.FLOAT -> assignConstantFloat(assign.target, 1.0)
else -> throw AssemblyError("invalid dt")
}
asmgen.out(jumpIfFalseLabel)
asmgen.out("; cmp done")
}
private fun attemptAssignOptimizedBinexpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
if(expr.operator in ComparisonOperators) {
if(expr.right.asConstInteger() == 0) {
@ -743,6 +959,80 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignRegisterpairWord(target, RegisterOrPair.AY)
}
private fun attemptAssignToByteCompareZeroRPN(expr: PtRpn, assign: AsmAssignment): Boolean {
val (left, oper, right) = expr.finalOperation()
if(expr.children.size!=3 || left !is PtExpression)
return false
when (oper.operator) {
"==" -> {
when(val dt = left.type) {
in ByteDatatypes -> {
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
in WordDatatypes -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
asmgen.out("""
sty P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_B1
beq +
lda #0
beq ++
+ lda #1
+""")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
DataType.FLOAT -> {
assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN | and #1 | eor #1")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
else->{
return false
}
}
}
"!=" -> {
when(val dt = left.type) {
in ByteDatatypes -> {
assignExpressionToRegister(left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out(" beq + | lda #1")
asmgen.out("+")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
in WordDatatypes -> {
assignExpressionToRegister(left, RegisterOrPair.AY, dt==DataType.WORD)
asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1")
asmgen.out(" beq + | lda #1")
asmgen.out("+")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
DataType.FLOAT -> {
assignExpressionToRegister(left, RegisterOrPair.FAC1, true)
asmgen.out(" jsr floats.SIGN")
assignRegisterByte(assign.target, CpuRegister.A)
return true
}
else->{
return false
}
}
}
else -> return false
}
}
private fun attemptAssignToByteCompareZero(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when (expr.operator) {
"==" -> {
@ -1030,7 +1320,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
// have to typecast the float number on the fly down to an integer
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes, target.position)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes, origTypeCastExpression.definingISub(), target.position)
return
} else {
if(!(valueDt isAssignableTo targetDt)) {
@ -1135,7 +1425,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
lsb.add(value)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, program.memsizer, value.position)
translateNormalAssignment(assign)
translateNormalAssignment(assign, value.definingISub())
}
private fun assignTypeCastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
@ -2869,7 +3159,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, signed, expr.position, null, asmgen)
val assign = AsmAssignment(src, tgt, program.memsizer, expr.position)
translateNormalAssignment(assign)
translateNormalAssignment(assign, expr.definingISub())
}
internal fun assignExpressionToVariable(expr: PtExpression, asmVarName: String, dt: DataType, scope: IPtSubroutine?) {
@ -2879,18 +3169,18 @@ internal class AssignmentAsmGen(private val program: PtProgram,
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, scope, expr.position, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, program.memsizer, expr.position)
translateNormalAssignment(assign)
translateNormalAssignment(assign, expr.definingISub())
}
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean, pos: Position) {
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, pos: Position) {
val tgt = AsmAssignTarget.fromRegisters(register, signed, pos, null, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)
translateNormalAssignment(assign, scope)
}
internal fun inplaceInvert(assign: AsmAssignment) {
internal fun inplaceInvert(assign: AsmAssignment, scope: IPtSubroutine?) {
val target = assign.target
when (assign.target.datatype) {
DataType.UBYTE -> {
@ -2935,7 +3225,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign))
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign), scope)
else -> throw AssemblyError("weird target")
}
}
@ -2960,7 +3250,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign))
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("~", assign), scope)
else -> throw AssemblyError("weird target")
}
}
@ -2968,7 +3258,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean, scope: IPtSubroutine?) {
val target = assign.target
val datatype = if(ignoreDatatype) {
when(target.datatype) {
@ -3003,7 +3293,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign))
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
else -> throw AssemblyError("weird target")
}
}
@ -3063,7 +3353,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign))
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
else -> throw AssemblyError("weird target")
}
}
@ -3085,7 +3375,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign))
TargetStorageKind.ARRAY -> assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
else -> throw AssemblyError("weird target for in-place float negation")
}
}

View File

@ -11,16 +11,16 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
private val asmgen: AsmGen6502Internal,
private val allocator: VariableAllocator
) {
fun translate(assign: AsmAugmentedAssignment) {
fun translate(assign: AsmAugmentedAssignment, scope: IPtSubroutine?) {
when(assign.operator) {
"-" -> {
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
assignmentAsmGen.inplaceNegate(a2, false)
assignmentAsmGen.inplaceNegate(a2, false, scope)
}
"~" -> {
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
assignmentAsmGen.inplaceInvert(a2)
assignmentAsmGen.inplaceInvert(a2, scope)
}
"+" -> { /* is a nop */ }
else -> {
@ -44,6 +44,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
"^=" -> inplaceModification(assign.target, "^", srcValue)
"<<=" -> inplaceModification(assign.target, "<<", srcValue)
">>=" -> inplaceModification(assign.target, ">>", srcValue)
"%=" -> inplaceModification(assign.target, "%", srcValue)
else -> throw AssemblyError("invalid augmented assign operator ${assign.operator}")
}
}

View File

@ -1,21 +0,0 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.ast.PtRpn
import prog8.codegen.cpu6502.AsmGen6502Internal
internal class RpnExpressionAsmGen(
val program: PtProgram,
val symbolTable: SymbolTable,
val asmgen: AsmGen6502Internal
) {
fun attemptAssignOptimizedExpr(assign: AsmAssignment): Boolean {
val value = assign.source.expression as PtRpn
println("TODO: RPN: optimized assignment ${value.position} maxdepth=${value.maxDepth()}") // TODO RPN: optimized assignment
// NOTE: don't forgot to evaluate the rest of the RPN expr as well
return false
}
}

View File

@ -898,6 +898,7 @@ class IRCodeGen(
private fun translate(ifElse: PtIfElse): IRCodeChunks {
val condition = ifElse.condition
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
when (condition) {
is PtBinaryExpression -> {
if(condition.operator !in ComparisonOperators)
@ -905,7 +906,6 @@ class IRCodeGen(
val signed = condition.left.type in SignedDatatypes
val irDtLeft = irType(condition.left.type)
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
return when {
goto!=null && ifElse.elseScope.children.isEmpty() -> translateIfFollowedByJustGoto(ifElse, goto, irDtLeft, signed)
constValue(condition.right) == 0.0 -> translateIfElseZeroComparison(ifElse, irDtLeft, signed)
@ -913,52 +913,92 @@ class IRCodeGen(
}
}
is PtRpn -> {
TODO("RPN ifelse (intermediate codegen) $condition")
val (left, oper, right) = condition.finalOperation()
if(oper.operator !in ComparisonOperators)
throw AssemblyError("if condition should only be a binary comparison expression")
val signed = oper.leftType in SignedDatatypes
val irDtLeft = irType(oper.leftType)
return when {
goto!=null && ifElse.elseScope.children.isEmpty() -> translateIfFollowedByJustGotoRPN(ifElse, goto, irDtLeft, signed)
constValue(right as PtExpression) == 0.0 -> translateIfElseZeroComparisonRPN(ifElse, irDtLeft, signed)
else -> translateIfElseNonZeroComparisonRPN(ifElse, irDtLeft, signed)
}
}
else -> {
TODO("weird condition node: $condition")
throw AssemblyError("weird condition node: $condition")
}
}
}
private fun translateIfFollowedByJustGotoRPN(ifElse: PtIfElse, goto: PtJump, irDtLeft: IRDataType, signed: Boolean): MutableList<IRCodeChunkBase> {
TODO("RPN translateIfFollowedByJustGotoRPN")
}
private fun translateIfElseZeroComparisonRPN(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
TODO("RPN translateIfElseZeroComparisonRPN")
}
private fun translateIfElseNonZeroComparisonRPN(ifElse: PtIfElse, irDtLeft: IRDataType, signed: Boolean): IRCodeChunks {
TODO("RPN translateIfElseNonZeroComparisonRPN")
}
private fun translateIfFollowedByJustGoto(ifElse: PtIfElse, goto: PtJump, irDtLeft: IRDataType, signed: Boolean): MutableList<IRCodeChunkBase> {
if(program.binaryExpressionsAreRPN) {
TODO ("RPN (intermediate codegen)")
require(!program.binaryExpressionsAreRPN)
val condition = ifElse.condition as PtBinaryExpression
val result = mutableListOf<IRCodeChunkBase>()
if (irDtLeft == IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
result += IRCodeChunk(null, null).also {
val compResultReg = registers.nextFree()
it += IRInstruction(
Opcode.FCOMP,
IRDataType.FLOAT,
reg1 = compResultReg,
fpReg1 = leftTr.resultFpReg,
fpReg2 = rightTr.resultFpReg
)
val gotoOpcode = when (condition.operator) {
"==" -> Opcode.BZ
"!=" -> Opcode.BNZ
"<" -> Opcode.BLEZS
">" -> Opcode.BGEZS
"<=" -> Opcode.BLZS
">=" -> Opcode.BGZS
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(
gotoOpcode,
IRDataType.BYTE,
reg1 = compResultReg,
value = goto.address?.toInt()
)
else if (goto.generatedLabel != null)
IRInstruction(
gotoOpcode,
IRDataType.BYTE,
reg1 = compResultReg,
labelSymbol = goto.generatedLabel
)
else
IRInstruction(
gotoOpcode,
IRDataType.BYTE,
reg1 = compResultReg,
labelSymbol = goto.identifier!!.name
)
}
return result
} else {
val result = mutableListOf<IRCodeChunkBase>()
val condition = ifElse.condition as PtBinaryExpression
if(irDtLeft==IRDataType.FLOAT) {
val leftTr = expressionEval.translateExpression(condition.left)
addToResult(result, leftTr, -1, leftTr.resultFpReg)
val rightTr = expressionEval.translateExpression(condition.right)
addToResult(result, rightTr, -1, rightTr.resultFpReg)
result += IRCodeChunk(null,null).also {
val compResultReg = registers.nextFree()
it += IRInstruction(Opcode.FCOMP, IRDataType.FLOAT, reg1=compResultReg, fpReg1 = leftTr.resultFpReg, fpReg2 = rightTr.resultFpReg)
val gotoOpcode = when (condition.operator) {
"==" -> Opcode.BZ
"!=" -> Opcode.BNZ
"<" -> Opcode.BLEZS
">" -> Opcode.BGEZS
"<=" -> Opcode.BLZS
">=" -> Opcode.BGZS
else -> throw AssemblyError("weird operator")
}
it += if (goto.address != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, value = goto.address?.toInt())
else if (goto.generatedLabel != null)
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, labelSymbol = goto.generatedLabel)
else
IRInstruction(gotoOpcode, IRDataType.BYTE, reg1 = compResultReg, labelSymbol = goto.identifier!!.name)
}
return result
} else {
val rightConst = condition.right.asConstInteger()
return if(rightConst==0)
ifZeroIntThenJump(result, ifElse, signed, irDtLeft, goto)
else {
ifNonZeroIntThenJump(result, ifElse, signed, irDtLeft, goto)
}
val rightConst = condition.right.asConstInteger()
return if (rightConst == 0)
ifZeroIntThenJump(result, ifElse, signed, irDtLeft, goto)
else {
ifNonZeroIntThenJump(result, ifElse, signed, irDtLeft, goto)
}
}
}

View File

@ -21,6 +21,8 @@ class BinExprSplitter(private val program: Program, private val options: Compila
if(options.compTarget.name == VMTarget.NAME)
return noModifications // don't split expressions when targeting the vm codegen, it handles nested expressions well
if(options.useRPN) // TODO RPN does this make a difference?
return noModifications
if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions)
return noModifications

View File

@ -0,0 +1,45 @@
package prog8tests.codegeneration
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import prog8.code.ast.PtAssignment
import prog8.code.ast.PtBuiltinFunctionCall
import prog8.code.ast.PtRpn
import prog8.code.core.DataType
import prog8.code.target.C64Target
import prog8tests.helpers.compileText
class TestRPNCodeGen: FunSpec({
test("rpn 6502") {
val text="""
main {
sub start() {
uword pointer = 4000
uword a = 11
uword b = 22
uword c = 33
cx16.r0 = peekw(pointer+a+b*c+42)
pokew(pointer+a+b*c+42, 4242)
}
}"""
val result = compileText(C64Target(), false, text, writeAssembly = true, useRPN = true)!!
val ast = result.codegenAst!!
val statements = ast.entrypoint()!!.children
statements.size shouldBe 11
val peekw = (statements[8] as PtAssignment).value as PtBuiltinFunctionCall
val pokew = (statements[9] as PtBuiltinFunctionCall)
val rpn1 = peekw.args.first() as PtRpn
val rpn2 = pokew.args.first() as PtRpn
rpn1.children.size shouldBe 7
val depth1 = rpn1.maxDepth()
depth1.getValue(DataType.UBYTE) shouldBe 0
depth1.getValue(DataType.UWORD) shouldBe 3
depth1.getValue(DataType.FLOAT) shouldBe 0
rpn2.children.size shouldBe 7
val depth2 = rpn2.maxDepth()
depth2.getValue(DataType.UBYTE) shouldBe 0
depth2.getValue(DataType.UWORD) shouldBe 3
depth2.getValue(DataType.FLOAT) shouldBe 0
}
})

View File

@ -17,7 +17,8 @@ internal fun compileFile(
outputDir: Path = prog8tests.helpers.outputDir,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
optFloatExpr: Boolean = true
optFloatExpr: Boolean = true,
useRPN: Boolean = false
) : CompilationResult? {
val filepath = fileDir.resolve(fileName)
assumeReadableFile(filepath)
@ -31,7 +32,7 @@ internal fun compileFile(
asmListfile = false,
experimentalCodegen = false,
varsHigh = false,
useRPN = false,
useRPN = useRPN,
platform.name,
evalStackBaseAddress = null,
symbolDefs = emptyMap(),
@ -52,11 +53,12 @@ internal fun compileText(
sourceText: String,
errors: IErrorReporter? = null,
writeAssembly: Boolean = true,
optFloatExpr: Boolean = true
optFloatExpr: Boolean = true,
useRPN: Boolean = false
) : CompilationResult? {
val filePath = outputDir.resolve("on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) + ".p8")
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
filePath.toFile().writeText(sourceText)
return compileFile(platform, optimize, filePath.parent, filePath.name,
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr)
errors=errors, writeAssembly=writeAssembly, optFloatExpr = optFloatExpr, useRPN=useRPN)
}

View File

@ -1,8 +1,8 @@
TODO
====
BRANCH: Write some unit tests for the RPN.
BRANCH: Fix the TODO RPN routines to be optimized assembly in RpnExpressionAsmGen.kt
BRANCH: Fix the TODO RPN routines to be optimized assembly in RpnExpressionAsmGen.kt
BRANCH: check BinExprSplitter disablement any effect for RPN?
BRANCH: Implement RPN codegen for IR.
For next minor release

View File

@ -1,36 +1,17 @@
%import textio
%import test_stack
%import math
%zeropage basicsafe
%option no_sysinit
; $1e4 size
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
uword xx=4000
ubyte a=11
ubyte b=22
ubyte c=33
sub start() {
ubyte[255] BC
bool[255] DX
; cx16.r0 = peekw(xx+a+b+c)
; cx16.r1 = peekw(xx+a+b+c+42)
; pokew(xx+a+b+c, xx)
; pokew(xx+a+b+c+42, xx)
if a and a & $40 == 0
cx16.r0++
; if cx16.r0L in "derp" {
; xx++
; }
;
; xx = xx+(3*func(xx)+xx*2*cx16.r0L)
; txt.print_uw(xx)
}
; sub func(uword value) -> uword {
; value ++
; return value
; }
BC[2] = math.rnd() & 15
BC[3] = math.rnd() & 15
BC[4] = math.rnd() & 15
;DX[2] = math.rnd() & 1
}
}