From 4b45dae68a32c21097d70be590dc3de0a11bcb32 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 7 Apr 2026 01:42:33 +0200 Subject: [PATCH] IR codegen: optimized array +/-/* in place --- .qwen/settings.json | 5 +- .../codegen/intermediate/AssignmentGen.kt | 943 +++++++++++++++++- compiler/test/TestCompilerOnExamples.kt | 2 +- compiler/test/vm/TestCompilerVirtual.kt | 91 ++ examples/test.p8 | 546 +++++++++- 5 files changed, 1500 insertions(+), 87 deletions(-) diff --git a/.qwen/settings.json b/.qwen/settings.json index b2b13133f..359990ee1 100644 --- a/.qwen/settings.json +++ b/.qwen/settings.json @@ -20,7 +20,8 @@ "Bash(echo *)", "Bash(cp *)", "Bash(time)", - "Bash(kotlinc *)" + "Bash(kotlinc *)", + "Bash(python3 *)" ] } -} +} \ No newline at end of file diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index ebb042746..350c8dc0a 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -378,7 +378,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen when(operator) { "+" -> { } "-" -> { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val skipCarryLabel = codeGen.createLabelName() if(constIndex!=null) { addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null) @@ -405,7 +405,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } } "~" -> { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(constIndex!=null) { addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null) addInstr(result, IRInstruction(Opcode.INVM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null) @@ -432,7 +432,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen when(operator) { "+" -> { } "-" -> { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(constIndex!=null) { addInstr(result, IRInstruction(Opcode.NEGM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) } else { @@ -446,7 +446,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } } "~" -> { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(constIndex!=null) { addInstr(result, IRInstruction(Opcode.INVM, vmDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) } else { @@ -460,7 +460,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } } "not" -> { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val register = codeGen.registers.next(vmDt) if(constIndex!=null) { result += IRCodeChunk(null, null).also { @@ -788,7 +788,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen private fun operatorAndInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? { if(array!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val result = mutableListOf() val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() @@ -812,10 +812,115 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace word array &") + // Non-const cases - handle split and non-split arrays differently + if(array.splitWords) { + // Split word array: extract operand bytes and AND separately + if(constIndex!=null) { + // Constant index, non-constant value + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.ANDM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result + } + // Non-constant index - use LOADX for each byte array + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + val valLsbReg = codeGen.registers.next(IRDataType.BYTE) + val valMsbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + } + return result + } + // Non-split array cases + if(constIndex!=null) { + // Constant index, non-constant value + val arrayVariableName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.ANDM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/ANDR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVarName = array.variable!!.name + // Multiply index by element size for LOADX/STOREX + if(eltSize > 1) { + result += codeGen.multiplyByConst(DataType.UBYTE, indexReg, eltSize) + } + if(constValue!=null) { + val loadReg = codeGen.registers.next(vmDt) + val constReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target with non-constant address + val result = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(result, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + return result } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place &") val result = mutableListOf() val tr = exprGen.translateExpression(operand) @@ -829,7 +934,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen private fun operatorLogicalAndInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? { if(array!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val result = mutableListOf() val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() @@ -846,10 +951,68 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace word array and") + // Non-const cases for non-split arrays (logical and = bitwise and for arrays) + if(constIndex!=null) { + // Constant index, non-constant value + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.ANDM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + if(constValue!=null) { + val loadReg = codeGen.registers.next(vmDt) + val constReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(Opcode.ANDR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target with non-constant address + val memResult = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.AND, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place and") val result = mutableListOf() val tr = exprGen.translateExpression(operand) @@ -888,7 +1051,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constValue = operand.asConstInteger() val eltSize = codeGen.program.memsizer.memorySize(array.type, null) if(constIndex!=null && constValue!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(array.splitWords) { val valueRegLsb = codeGen.registers.next(IRDataType.BYTE) val valueRegMsb = codeGen.registers.next(IRDataType.BYTE) @@ -907,10 +1070,112 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace word array |") + // Non-const cases - handle split and non-split arrays differently + if(array.splitWords) { + val arrayVariableName = array.variable!!.name + if(constIndex!=null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.ORM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result + } + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + val valLsbReg = codeGen.registers.next(IRDataType.BYTE) + val valMsbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + } + return result + } + // Non-split array cases + if(constIndex!=null) { + val arrayVarName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.ORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/ORR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVarName2 = array.variable!!.name + // Multiply index by element size for LOADX/STOREX + if(eltSize > 1) { + result += codeGen.multiplyByConst(DataType.UBYTE, indexReg, eltSize) + } + if(constValue!=null) { + val loadReg = codeGen.registers.next(vmDt) + val constReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target with non-constant address + val memResult = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place |") val result = mutableListOf() val tr = exprGen.translateExpression(operand) @@ -932,7 +1197,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen if(array.splitWords) { throw AssemblyError("logical or on (split) word array should not happen") } else { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val valueReg = codeGen.registers.next(vmDt) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.LOAD, vmDt, reg1=valueReg, immediate=constValue) @@ -941,10 +1206,73 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace word array or") + // Non-const cases - split words not supported for logical or + if(array.splitWords) { + throw AssemblyError("logical or on (split) word array with non-const operand should not happen") + } + // Non-split array cases + if(constIndex!=null) { + val arrayVarName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.ORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVarName2 = array.variable!!.name + if(constValue!=null) { + val loadReg = codeGen.registers.next(vmDt) + val constReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.ORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target with non-constant address + val memResult = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.OR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place or"") val result = mutableListOf() val tr = exprGen.translateExpression(operand) @@ -1042,7 +1370,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constValue = operand.asConstInteger() if(constIndex!=null && constValue!=null) { if(constValue!=1) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val valueReg=codeGen.registers.next(eltDt) result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue) @@ -1052,7 +1380,47 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace array * non-const") + // Non-const cases for non-split arrays + if(constIndex!=null) { + // Constant index, non-constant value + val arrayVariableName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val opcode = if(signed) Opcode.MULSM else Opcode.MULM + addInstr(result, IRInstruction(opcode, eltDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/MULR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVariableName = array.variable!!.name + // Multiply index by element size for LOADX/STOREX + if(eltSize > 1) { + result += codeGen.multiplyByConst(DataType.UBYTE, indexReg, eltSize) + } + if(constValue!=null) { + val loadReg = codeGen.registers.next(eltDt) + val constReg = codeGen.registers.next(eltDt) + val opcode = if(signed) Opcode.MULSR else Opcode.MULR + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) + it += IRInstruction(opcode, eltDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(eltDt) + val opcode = if(signed) Opcode.MULSR else Opcode.MULR + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(opcode, eltDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } + return result } if(constAddress==null && memory!=null) return null // TODO("optimized memory in-place *"") @@ -1100,7 +1468,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() if(constIndex!=null && constValue!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(constValue==1) { addInstr(result, IRInstruction(Opcode.DECM, eltDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) @@ -1113,10 +1481,116 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace array -") + // Optimized path for non-const cases + if(constIndex!=null) { + // Constant index, non-constant value + val arrayVariableName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.SUBM, eltDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/SUBR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVariableName = array.variable!!.name + // Multiply index by element size for LOADX/STOREX + if(eltSize > 1) { + result += codeGen.multiplyByConst(DataType.UBYTE, indexReg, eltSize) + } + if(constValue!=null) { + // Non-constant index, constant value + val loadReg = codeGen.registers.next(eltDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + val constReg = codeGen.registers.next(eltDt) + it += IRInstruction(Opcode.LOAD, eltDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.SUBR, eltDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } else { + // Non-constant index, non-constant value + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(eltDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(Opcode.SUBR, eltDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, eltDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target: translate address and subtract + val memResult = mutableListOf() + + // Check if address is a constant number + val constMemAddress = memory.address as? PtNumber + if(constMemAddress!=null) { + val addr = constMemAddress.number.toInt() + val operandConstValue = (operand as? PtNumber)?.number + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr) + it += IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr) + } + } else if(operandConstValue==1.0) { + addInstr(memResult, IRInstruction(Opcode.DECM, IRDataType.BYTE, address = addr), null) + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr) + } + } + return memResult + } + + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + // Constant value, non-const address + if(operandConstValue==1.0) { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=loadReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.SUB, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place -"") val constValue = (operand as? PtNumber)?.number val result = mutableListOf() @@ -1155,7 +1629,71 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } private fun operatorMultiplyInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? { - return null // fallback to slow method // TODO("inplace split word array *") + val result = mutableListOf() + val constIndex = array.index.asConstInteger() + val constValue = operand.asConstInteger() + if(constIndex!=null) { + val arrayVariableName = array.variable!!.name + + if(constValue==1) { + // Multiplying by 1 is a no-op + return emptyList() + } else if(constValue!=null) { + // Handle constant value using CONCAT/MUL/LSIGB/MSIGB + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Multiply by constant value + it += IRInstruction(Opcode.MUL, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result + } else { + // Non-constant operand: translate expression, then multiply using CONCAT/MUL + val tr = exprGen.translateExpression(operand) + addToResult(result, tr, tr.resultReg, -1) + if(tr.resultReg<0) { + return null // fallback to slow method + } + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB from array + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Multiply by operand value (use MULR for register-to-register) + it += IRInstruction(Opcode.MULR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result + } + } + return null // fallback to slow method for non-constant index } private fun operatorMinusInplaceSplitArray(array: PtArrayIndexer, operand: PtExpression): IRCodeChunks? { @@ -1163,13 +1701,13 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() if(constIndex!=null) { - val skip = codeGen.createLabelName() + val arrayVariableName = array.variable!!.name + if(constValue==1) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") - + val skip = codeGen.createLabelName() val lsbReg = codeGen.registers.next(IRDataType.BYTE) result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) it += IRInstruction(Opcode.BSTNE, labelSymbol = skip) it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) } @@ -1177,11 +1715,62 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) } return result + } else if(constValue!=null) { + // Handle constant value != 1 using CONCAT/SUB/LSIGB/MSIGB + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Subtract the constant value + it += IRInstruction(Opcode.SUB, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result } else { - return null // fallback to slow method // TODO("inplace split word array -") + // Non-constant operand: translate expression, then subtract using CONCAT/SUB + val tr = exprGen.translateExpression(operand) + addToResult(result, tr, tr.resultReg, -1) + if(tr.resultReg<0) { + return null // fallback to slow method + } + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB from array + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Subtract the operand value (use SUBR for register-to-register) + it += IRInstruction(Opcode.SUBR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result } } - return null // fallback to slow method // TODO("inplace split word array -") + return null // fallback to slow method for non-constant index } private fun operatorPlusInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, @@ -1195,7 +1784,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() if(constIndex!=null && constValue!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(constValue==1) { addInstr(result, IRInstruction(Opcode.INCM, elementDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) @@ -1208,10 +1797,118 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace array +") + // Optimized path for non-const cases + if(constIndex!=null) { + // Constant index, non-constant value + val arrayVariableName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.ADDM, elementDt, reg1=valueTr.resultReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/ADDR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVariableName = array.variable!!.name + // Multiply index by element size for LOADX/STOREX + if(eltSize > 1) { + result += codeGen.multiplyByConst(DataType.UBYTE, indexReg, eltSize) + } + if(constValue!=null) { + // Non-constant index, constant value + val loadReg = codeGen.registers.next(elementDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + val constReg = codeGen.registers.next(elementDt) + it += IRInstruction(Opcode.LOAD, elementDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.ADDR, elementDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } else { + // Non-constant index, non-constant value + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(elementDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + it += IRInstruction(Opcode.ADDR, elementDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, elementDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVariableName) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target: translate address and add + val memResult = mutableListOf() + + // Check if address is a constant number + val constMemAddress = memory.address as? PtNumber + if(constMemAddress!=null) { + // Direct constant address - shouldn't normally happen but handle it + val addr = constMemAddress.number.toInt() + val operandConstValue = (operand as? PtNumber)?.number + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr) + it += IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr) + } + } else if(operandConstValue==1.0) { + addInstr(memResult, IRInstruction(Opcode.INCM, IRDataType.BYTE, address = addr), null) + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1=loadReg, address = addr) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=loadReg, address = addr) + } + } + return memResult + } + + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + // LOADI current value, ADD, STOREI back + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + // Constant value, non-const address + if(operandConstValue==1.0) { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=loadReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.ADD, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place +"") val result = mutableListOf() val constValue = (operand as? PtNumber)?.number @@ -1255,10 +1952,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constIndex = array.index.asConstInteger() val constValue = operand.asConstInteger() if(constIndex!=null) { - val skip = codeGen.createLabelName() + val arrayVariableName = array.variable!!.name + if(constValue==1) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") - + val skip = codeGen.createLabelName() result += IRCodeChunk(null, null).also { it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) it += IRInstruction(Opcode.BSTNE, labelSymbol = skip) @@ -1266,11 +1963,62 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } result += IRCodeChunk(skip, null) return result + } else if(constValue!=null) { + // Handle constant value != 1 using CONCAT/ADD/LSIGB/MSIGB + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Add the constant value + it += IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = wordReg, immediate = constValue) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result } else { - return null // fallback to slow method // TODO("inplace split word array +") + // Non-constant operand: translate expression, then add using CONCAT/ADD + val tr = exprGen.translateExpression(operand) + addToResult(result, tr, tr.resultReg, -1) + if(tr.resultReg<0) { + return null // fallback to slow method + } + val wordReg = codeGen.registers.next(IRDataType.WORD) + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + + result += IRCodeChunk(null, null).also { + // Load current LSB and MSB from array + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + // Concatenate into word register + it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1 = wordReg, reg2 = msbReg, reg3 = lsbReg) + // Add the operand value (use ADDR for register-to-register) + it += IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = wordReg, reg2 = tr.resultReg) + // Extract bytes back into NEW registers + val newLsbReg = codeGen.registers.next(IRDataType.BYTE) + val newMsbReg = codeGen.registers.next(IRDataType.BYTE) + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1 = newLsbReg, reg2 = wordReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1 = newMsbReg, reg2 = wordReg) + // Store back + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = newMsbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result } } - return null // fallback to slow method // TODO("inplace split word array +") + return null // fallback to slow method for non-constant index } private fun operatorShiftRightInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression, signed: Boolean): IRCodeChunks? { @@ -1280,7 +2028,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constValue = operand.asConstInteger() if(constIndex!=null && constValue!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(array.splitWords) { repeat(constValue) { @@ -1339,7 +2087,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen val constValue = operand.asConstInteger() if(constIndex!=null && constValue!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name if(array.splitWords) { repeat(constValue) { @@ -1391,7 +2139,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen private fun operatorXorInplace(symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType, operand: PtExpression): IRCodeChunks? { if(array!=null) { - val arrayVariableName = array.variable?.name ?: TODO("support for ptr indexing ${array.position}") + val arrayVariableName = array.variable!!.name val result = mutableListOf() val constIndex = array.index.asConstInteger() @@ -1416,10 +2164,107 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen } return result } - return null // TODO("optimized inplace word array xor") + // Non-const cases - handle split and non-split arrays differently + if(array.splitWords) { + if(constIndex!=null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=lsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=msbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex) + it += IRInstruction(Opcode.XORM, IRDataType.BYTE, reg1=msbReg, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex) + } + return result + } + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + if(valueTr.resultReg < 0) return null + val lsbReg = codeGen.registers.next(IRDataType.BYTE) + val msbReg = codeGen.registers.next(IRDataType.BYTE) + val valLsbReg = codeGen.registers.next(IRDataType.BYTE) + val valMsbReg = codeGen.registers.next(IRDataType.BYTE) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=valLsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.MSIGB, IRDataType.WORD, reg1=valMsbReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + it += IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=lsbReg, reg2=valLsbReg) + it += IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=msbReg, reg2=valMsbReg) + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=lsbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_lsb") + it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=msbReg, reg2=indexReg, labelSymbol = arrayVariableName+"_msb") + } + return result + } + // Non-split array cases + if(constIndex!=null) { + val arrayVarName = array.variable!!.name + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + addInstr(result, IRInstruction(Opcode.XORM, vmDt, reg1=valueTr.resultReg, labelSymbol = arrayVarName, symbolOffset = constIndex*eltSize), null) + return result + } + // Non-constant index - use LOADX/XORR/STOREX + val indexTr = exprGen.translateExpression(array.index) + addToResult(result, indexTr, indexTr.resultReg, -1) + val indexReg = indexTr.resultReg + val arrayVarName2 = array.variable!!.name + if(constValue!=null) { + val loadReg = codeGen.registers.next(vmDt) + val constReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.LOAD, vmDt, reg1=constReg, immediate = constValue) + it += IRInstruction(Opcode.XORR, vmDt, reg1=loadReg, reg2=constReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } else { + val valueTr = exprGen.translateExpression(operand) + addToResult(result, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(vmDt) + result += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + it += IRInstruction(Opcode.XORR, vmDt, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREX, vmDt, reg1=loadReg, reg2=indexReg, labelSymbol = arrayVarName2) + } + } + return result + } + if(constAddress==null && memory!=null) { + // Memory target with non-constant address + val memResult = mutableListOf() + val addrTr = exprGen.translateExpression(memory.address) + addToResult(memResult, addrTr, addrTr.resultReg, -1) + val addressReg = addrTr.resultReg + val operandConstValue = (operand as? PtNumber)?.number + + if(operandConstValue==null) { + val valueTr = exprGen.translateExpression(operand) + addToResult(memResult, valueTr, valueTr.resultReg, -1) + val loadReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=loadReg, reg2=valueTr.resultReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } else { + val loadReg = codeGen.registers.next(IRDataType.BYTE) + val valueReg = codeGen.registers.next(IRDataType.BYTE) + memResult += IRCodeChunk(null, null).also { + it += IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=valueReg, immediate = operandConstValue.toInt()) + it += IRInstruction(Opcode.XOR, IRDataType.BYTE, reg1=loadReg, reg2=valueReg) + it += IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1=loadReg, reg2=addressReg, immediate = 0) + } + } + return memResult } - if(constAddress==null && memory!=null) - return null // TODO("optimized memory in-place xor"") val result = mutableListOf() val tr = exprGen.translateExpression(operand) diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 8c35942fd..0e5ae1d70 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -197,7 +197,7 @@ class TestCompilerOnExamplesCx16: FunSpec({ listOf( ExampleSizes("vtui/testvtui", cx16SizeOptimized=3041, cx16SizeUnoptimized=3041), ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36645), - ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5653, cx16SizeUnoptimized=7976), + ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5643, cx16SizeUnoptimized=7958), ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1852, cx16SizeUnoptimized=3836), ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1566, cx16SizeUnoptimized=3545), ExampleSizes("pcmaudio/vumeter", cx16SizeOptimized=3892, cx16SizeUnoptimized=6466), diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index 71d268d14..71af29200 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -849,4 +849,95 @@ main { vm.memory.getUW(allocations["main.ey2"]!!) shouldBe 0u } } + + test("nosplit word array += and -= with variable index and value") { + // This test verifies the fix for LOADX/STOREX with element size multiplication + // Previously failed with -noopt because index wasn't multiplied by element size + val src = """ +%zeropage basicsafe +%option no_sysinit + +main { + sub start() { + ; Test += with variable index, constant value + uword[] @nosplit arr1 = [100, 200, 300] + ubyte idx1 = 1 + arr1[idx1] += 100 + + ; Test += with variable index, variable value + uword[] @nosplit arr2 = [100, 200, 300] + ubyte idx2 = 2 + uword val2 = 25 + arr2[idx2] += val2 + + ; Test -= with variable index, variable value + uword[] @nosplit arr3 = [500, 400, 300] + ubyte idx3 = 1 + uword val3 = 150 + arr3[idx3] -= val3 + + ; Test *= with variable index, constant value + uword[] @nosplit arr4 = [10, 20, 30] + ubyte idx4 = 2 + arr4[idx4] *= 5 + + ; Store results for verification + main.r1 = arr1[0] + main.r2 = arr1[1] + main.r3 = arr1[2] + main.r4 = arr2[0] + main.r5 = arr2[1] + main.r6 = arr2[2] + main.r7 = arr3[0] + main.r8 = arr3[1] + main.r9 = arr3[2] + main.r10 = arr4[0] + main.r11 = arr4[1] + main.r12 = arr4[2] + } + + uword @shared r1 + uword @shared r2 + uword @shared r3 + uword @shared r4 + uword @shared r5 + uword @shared r6 + uword @shared r7 + uword @shared r8 + uword @shared r9 + uword @shared r10 + uword @shared r11 + uword @shared r12 +}""" + // Test with optimizations OFF to ensure the non-optimized path works + val result = compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly = true)!! + val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") + val irContent = virtfile.readText() + + // Parse and run + val irProgram = IRFileReader().read(irContent) + val allocations = prog8.vm.VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget).allocations + + VmRunner().runAndTestProgram(irContent) { vm -> + // arr1: [100, 200+100, 300] = [100, 300, 300] + vm.memory.getUW(allocations["main.r1"]!!) shouldBe 100u + vm.memory.getUW(allocations["main.r2"]!!) shouldBe 300u + vm.memory.getUW(allocations["main.r3"]!!) shouldBe 300u + + // arr2: [100, 200, 300+25] = [100, 200, 325] + vm.memory.getUW(allocations["main.r4"]!!) shouldBe 100u + vm.memory.getUW(allocations["main.r5"]!!) shouldBe 200u + vm.memory.getUW(allocations["main.r6"]!!) shouldBe 325u + + // arr3: [500, 400-150, 300] = [500, 250, 300] + vm.memory.getUW(allocations["main.r7"]!!) shouldBe 500u + vm.memory.getUW(allocations["main.r8"]!!) shouldBe 250u + vm.memory.getUW(allocations["main.r9"]!!) shouldBe 300u + + // arr4: [10, 20, 30*5] = [10, 20, 150] + vm.memory.getUW(allocations["main.r10"]!!) shouldBe 10u + vm.memory.getUW(allocations["main.r11"]!!) shouldBe 20u + vm.memory.getUW(allocations["main.r12"]!!) shouldBe 150u + } + } }) \ No newline at end of file diff --git a/examples/test.p8 b/examples/test.p8 index fe7119c38..7f04d15a4 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,44 +1,520 @@ -%import textio %zeropage basicsafe -%encoding petscii +%option no_sysinit +%import textio main { sub start() { - bool bb - ubyte ub - uword uw + txt.iso() + + ; Test split word array += with constant value 1 + uword[] arr1 = [100, 200, 300] + arr1[0] += 1 + arr1[1] += 1 + arr1[2] += 1 + + if arr1[0] == 101 and arr1[1] == 201 and arr1[2] == 301 { + txt.print("PASS: += 1\n") + } else { + txt.print("FAIL: += 1 (expected 101,201,301 got ") + txt.print_uw(arr1[0]) + txt.print(",") + txt.print_uw(arr1[1]) + txt.print(",") + txt.print_uw(arr1[2]) + txt.print(")\n") + } + + ; Test split word array += with constant value > 1 + uword[] arr2 = [100, 200, 300] + arr2[0] += 50 + arr2[1] += 100 + arr2[2] += 1000 + + if arr2[0] == 150 and arr2[1] == 300 and arr2[2] == 1300 { + txt.print("PASS: += const\n") + } else { + txt.print("FAIL: += const (expected 150,300,1300 got ") + txt.print_uw(arr2[0]) + txt.print(",") + txt.print_uw(arr2[1]) + txt.print(",") + txt.print_uw(arr2[2]) + txt.print(")\n") + } + + ; Test split word array += with variable + uword[] arr3 = [100, 200, 300] + uword addval = 25 + arr3[0] += addval + arr3[1] += addval * 2 + arr3[2] += addval * 3 + + if arr3[0] == 125 and arr3[1] == 250 and arr3[2] == 375 { + txt.print("PASS: += var\n") + } else { + txt.print("FAIL: += var (expected 125,250,375 got ") + txt.print_uw(arr3[0]) + txt.print(",") + txt.print_uw(arr3[1]) + txt.print(",") + txt.print_uw(arr3[2]) + txt.print(")\n") + } + + ; Test split word array -= with constant value 1 + uword[] arr4 = [100, 200, 300] + arr4[0] -= 1 + arr4[1] -= 1 + arr4[2] -= 1 + + if arr4[0] == 99 and arr4[1] == 199 and arr4[2] == 299 { + txt.print("PASS: -= 1\n") + } else { + txt.print("FAIL: -= 1 (expected 99,199,299 got ") + txt.print_uw(arr4[0]) + txt.print(",") + txt.print_uw(arr4[1]) + txt.print(",") + txt.print_uw(arr4[2]) + txt.print(")\n") + } + + ; Test split word array -= with constant value > 1 + uword[] arr5 = [100, 200, 300] + arr5[0] -= 50 + arr5[1] -= 100 + arr5[2] -= 150 + + if arr5[0] == 50 and arr5[1] == 100 and arr5[2] == 150 { + txt.print("PASS: -= const\n") + } else { + txt.print("FAIL: -= const (expected 50,100,150 got ") + txt.print_uw(arr5[0]) + txt.print(",") + txt.print_uw(arr5[1]) + txt.print(",") + txt.print_uw(arr5[2]) + txt.print(")\n") + } + + ; Test split word array -= with variable + uword[] arr6 = [100, 200, 300] + uword subval = 25 + arr6[0] -= subval + arr6[1] -= subval * 2 + arr6[2] -= subval * 3 + + if arr6[0] == 75 and arr6[1] == 150 and arr6[2] == 225 { + txt.print("PASS: -= var\n") + } else { + txt.print("FAIL: -= var (expected 75,150,225 got ") + txt.print_uw(arr6[0]) + txt.print(",") + txt.print_uw(arr6[1]) + txt.print(",") + txt.print_uw(arr6[2]) + txt.print(")\n") + } + + ; Test carry/borrow across byte boundary + uword[] arr7 = [255, 256, 1] + arr7[0] += 1 ; Should become 256 (carry from LSB to MSB) + arr7[1] += 1 ; Should become 257 + arr7[2] += 65534 ; Should become 65535 (max uword) + + if arr7[0] == 256 and arr7[1] == 257 and arr7[2] == 65535 { + txt.print("PASS: carry test\n") + } else { + txt.print("FAIL: carry test (expected 256,257,65535 got ") + txt.print_uw(arr7[0]) + txt.print(",") + txt.print_uw(arr7[1]) + txt.print(",") + txt.print_uw(arr7[2]) + txt.print(")\n") + } + + ; Test borrow across byte boundary + uword[] arr8 = [256, 257, 65535] + arr8[0] -= 1 ; Should become 255 + arr8[1] -= 1 ; Should become 256 + arr8[2] -= 1 ; Should become 65534 - uw = 9999 - txt.print_uw(uw) - txt.spc() - uw, void = thing2() - txt.print_uw(uw) - txt.nl() + if arr8[0] == 255 and arr8[1] == 256 and arr8[2] == 65534 { + txt.print("PASS: borrow test\n") + } else { + txt.print("FAIL: borrow test (expected 255,256,65534 got ") + txt.print_uw(arr8[0]) + txt.print(",") + txt.print_uw(arr8[1]) + txt.print(",") + txt.print_uw(arr8[2]) + txt.print(")\n") + } - uw = 9999 - txt.print_uw(uw) - txt.spc() - uw, bb = thing2() - txt.print_uw(uw) - txt.nl() + ; ============================================================ + ; Non-split word array tests + ; ============================================================ -; uw = 9999 -; txt.print_uw(uw) -; txt.spc() -; uw, ub = thing2() -; txt.print_uw(uw) -; txt.nl() + ; Test non-split array += with constant index, non-constant value + uword[] @nosplit arr9 = [100, 200, 300] + uword addval9 = 50 + arr9[0] += addval9 + arr9[1] += addval9 * 2 + + if arr9[0] == 150 and arr9[1] == 300 { + txt.print("PASS: nosplit += const-idx, var\n") + } else { + txt.print("FAIL: nosplit += const-idx, var (expected 150,300 got ") + txt.print_uw(arr9[0]) + txt.print(",") + txt.print_uw(arr9[1]) + txt.print(")\n") + } + + ; Test non-split array -= with constant index, non-constant value + uword[] @nosplit arr10 = [500, 400, 300] + uword subval10 = 50 + arr10[0] -= subval10 + arr10[1] -= subval10 * 2 + + if arr10[0] == 450 and arr10[1] == 300 { + txt.print("PASS: nosplit -= const-idx, var\n") + } else { + txt.print("FAIL: nosplit -= const-idx, var (expected 450,300 got ") + txt.print_uw(arr10[0]) + txt.print(",") + txt.print_uw(arr10[1]) + txt.print(")\n") + } + + ; Test non-split array += with non-constant index, constant value + uword[] @nosplit arr11 = [100, 200, 300] + ubyte idx11 = 1 + arr11[idx11] += 100 + + if arr11[0] == 100 and arr11[1] == 300 and arr11[2] == 300 { + txt.print("PASS: nosplit += var-idx, const\n") + } else { + txt.print("FAIL: nosplit += var-idx, const (expected 100,300,300 got ") + txt.print_uw(arr11[0]) + txt.print(",") + txt.print_uw(arr11[1]) + txt.print(",") + txt.print_uw(arr11[2]) + txt.print(")\n") + } + + ; Test non-split array += with non-constant index, non-constant value + uword[] @nosplit arr12 = [100, 200, 300] + ubyte idx12 = 2 + uword val12 = 25 + arr12[idx12] += val12 + + if arr12[0] == 100 and arr12[1] == 200 and arr12[2] == 325 { + txt.print("PASS: nosplit += var-idx, var\n") + } else { + txt.print("FAIL: nosplit += var-idx, var (expected 100,200,325 got ") + txt.print_uw(arr12[0]) + txt.print(",") + txt.print_uw(arr12[1]) + txt.print(",") + txt.print_uw(arr12[2]) + txt.print(")\n") + } + + ; Test non-split array -= with non-constant index, constant value + uword[] @nosplit arr13 = [500, 400, 300] + ubyte idx13 = 0 + arr13[idx13] -= 200 + + if arr13[0] == 300 and arr13[1] == 400 and arr13[2] == 300 { + txt.print("PASS: nosplit -= var-idx, const\n") + } else { + txt.print("FAIL: nosplit -= var-idx, const (expected 300,400,300 got ") + txt.print_uw(arr13[0]) + txt.print(",") + txt.print_uw(arr13[1]) + txt.print(",") + txt.print_uw(arr13[2]) + txt.print(")\n") + } + + ; Test non-split array -= with non-constant index, non-constant value + uword[] @nosplit arr14 = [500, 400, 300] + ubyte idx14 = 1 + uword val14 = 150 + arr14[idx14] -= val14 + + if arr14[0] == 500 and arr14[1] == 250 and arr14[2] == 300 { + txt.print("PASS: nosplit -= var-idx, var\n") + } else { + txt.print("FAIL: nosplit -= var-idx, var (expected 500,250,300 got ") + txt.print_uw(arr14[0]) + txt.print(",") + txt.print_uw(arr14[1]) + txt.print(",") + txt.print_uw(arr14[2]) + txt.print(")\n") + } + + ; ============================================================ + ; Split word array *= tests + ; ============================================================ + + ; Test split word array *= with constant value 1 (no-op) + uword[] arr15 = [100, 200, 300] + arr15[0] *= 1 + arr15[1] *= 1 + arr15[2] *= 1 + + if arr15[0] == 100 and arr15[1] == 200 and arr15[2] == 300 { + txt.print("PASS: *= 1\n") + } else { + txt.print("FAIL: *= 1 (expected 100,200,300 got ") + txt.print_uw(arr15[0]) + txt.print(",") + txt.print_uw(arr15[1]) + txt.print(",") + txt.print_uw(arr15[2]) + txt.print(")\n") + } + + ; Test split word array *= with constant value > 1 + uword[] arr16 = [10, 20, 100] + arr16[0] *= 5 + arr16[1] *= 10 + arr16[2] *= 600 + + if arr16[0] == 50 and arr16[1] == 200 and arr16[2] == 60000 { + txt.print("PASS: *= const\n") + } else { + txt.print("FAIL: *= const (expected 50,200,60000 got ") + txt.print_uw(arr16[0]) + txt.print(",") + txt.print_uw(arr16[1]) + txt.print(",") + txt.print_uw(arr16[2]) + txt.print(")\n") + } + + ; Test split word array *= with variable + uword[] arr17 = [10, 20, 30] + uword mulval = 7 + arr17[0] *= mulval + arr17[1] *= mulval * 2 + arr17[2] *= 3 + + if arr17[0] == 70 and arr17[1] == 280 and arr17[2] == 90 { + txt.print("PASS: *= var\n") + } else { + txt.print("FAIL: *= var (expected 70,280,90 got ") + txt.print_uw(arr17[0]) + txt.print(",") + txt.print_uw(arr17[1]) + txt.print(",") + txt.print_uw(arr17[2]) + txt.print(")\n") + } + + ; Test *= overflow (upper bits should be truncated to 16 bits) + uword[] arr18 = [500, 256, 65535] + arr18[0] *= 200 ; 500 * 200 = 100000, truncates to 100000 % 65536 = 34464 + arr18[1] *= 300 ; 256 * 300 = 76800, truncates to 76800 % 65536 = 11264 + arr18[2] *= 2 ; 65535 * 2 = 131070, truncates to 131070 % 65536 = 65534 + + if arr18[0] == 34464 and arr18[1] == 11264 and arr18[2] == 65534 { + txt.print("PASS: *= overflow\n") + } else { + txt.print("FAIL: *= overflow (expected 34464,11264,65534 got ") + txt.print_uw(arr18[0]) + txt.print(",") + txt.print_uw(arr18[1]) + txt.print(",") + txt.print_uw(arr18[2]) + txt.print(")\n") + } + + ; ============================================================ + ; Bitwise AND tests + ; ============================================================ + + ; Test split word array &= with constant value + uword[] arr19 = [$FFFF, $F0F0, $FF00] + arr19[0] &= $FF00 + arr19[1] &= $0F0F + arr19[2] &= $00FF + + if arr19[0] == $FF00 and arr19[1] == $0000 and arr19[2] == $0000 { + txt.print("PASS: &= const\n") + } else { + txt.print("FAIL: &= const (expected 65280,0,0 got ") + txt.print_uw(arr19[0]) + txt.print(",") + txt.print_uw(arr19[1]) + txt.print(",") + txt.print_uw(arr19[2]) + txt.print(")\n") + } + + ; Test split word array &= with variable + uword[] arr20 = [$FFFF, $AAAA, $5555] + uword maskval = $FF00 + arr20[0] &= maskval + arr20[1] &= $00FF + arr20[2] &= $FFFF + + if arr20[0] == $FF00 and arr20[1] == $00AA and arr20[2] == $5555 { + txt.print("PASS: &= var\n") + } else { + txt.print("FAIL: &= var (expected 65280,170,21845 got ") + txt.print_uw(arr20[0]) + txt.print(",") + txt.print_uw(arr20[1]) + txt.print(",") + txt.print_uw(arr20[2]) + txt.print(")\n") + } + + ; ============================================================ + ; Bitwise OR tests + ; ============================================================ + + ; Test split word array |= with constant value + uword[] arr21 = [$0000, $0F0F, $F0F0] + arr21[0] |= $FFFF + arr21[1] |= $F0F0 + arr21[2] |= $0F0F + + if arr21[0] == $FFFF and arr21[1] == $FFFF and arr21[2] == $FFFF { + txt.print("PASS: |= const\n") + } else { + txt.print("FAIL: |= const (expected 65535,65535,65535 got ") + txt.print_uw(arr21[0]) + txt.print(",") + txt.print_uw(arr21[1]) + txt.print(",") + txt.print_uw(arr21[2]) + txt.print(")\n") + } + + ; Test split word array |= with variable + uword[] arr22 = [$0001, $0010, $0100] + uword orval = $FF00 + arr22[0] |= orval + arr22[1] |= $00FF + arr22[2] |= $F00F + + if arr22[0] == $FF01 and arr22[1] == $00FF and arr22[2] == $F10F { + txt.print("PASS: |= var\n") + } else { + txt.print("FAIL: |= var (expected 65281,255,61711 got ") + txt.print_uw(arr22[0]) + txt.print(",") + txt.print_uw(arr22[1]) + txt.print(",") + txt.print_uw(arr22[2]) + txt.print(")\n") + } + + ; ============================================================ + ; Bitwise XOR tests + ; ============================================================ + + ; Test split word array ^= with constant value + uword[] arr23 = [$FFFF, $0000, $AAAA] + arr23[0] ^= $FF00 + arr23[1] ^= $FFFF + arr23[2] ^= $5555 + + if arr23[0] == $00FF and arr23[1] == $FFFF and arr23[2] == $FFFF { + txt.print("PASS: ^= const\n") + } else { + txt.print("FAIL: ^= const (expected 255,65535,65535 got ") + txt.print_uw(arr23[0]) + txt.print(",") + txt.print_uw(arr23[1]) + txt.print(",") + txt.print_uw(arr23[2]) + txt.print(")\n") + } + + ; Test split word array ^= with variable + uword[] arr24 = [$1234, $5678, $9ABC] + uword xorval = $FFFF + arr24[0] ^= xorval + arr24[1] ^= $0000 + arr24[2] ^= $FFFF + + if arr24[0] == $EDCB and arr24[1] == $5678 and arr24[2] == $6543 { + txt.print("PASS: ^= var\n") + } else { + txt.print("FAIL: ^= var (expected 60875,22136,25923 got ") + txt.print_uw(arr24[0]) + txt.print(",") + txt.print_uw(arr24[1]) + txt.print(",") + txt.print_uw(arr24[2]) + txt.print(")\n") + } + + ; ============================================================ + ; Non-split array *= with non-const tests + ; ============================================================ + + ; Test non-split array *= with constant index, non-const value + uword[] @nosplit arr25 = [10, 20, 30] + uword mulval25 = 7 + arr25[0] *= mulval25 + arr25[1] *= 3 + + if arr25[0] == 70 and arr25[1] == 60 and arr25[2] == 30 { + txt.print("PASS: nosplit *= const-idx, var\n") + } else { + txt.print("FAIL: nosplit *= const-idx, var (expected 70,60,30 got ") + txt.print_uw(arr25[0]) + txt.print(",") + txt.print_uw(arr25[1]) + txt.print(",") + txt.print_uw(arr25[2]) + txt.print(")\n") + } + + ; Test non-split array *= with non-constant index, constant value + uword[] @nosplit arr26 = [10, 20, 30] + ubyte idx26 = 2 + arr26[idx26] *= 5 + + if arr26[0] == 10 and arr26[1] == 20 and arr26[2] == 150 { + txt.print("PASS: nosplit *= var-idx, const\n") + } else { + txt.print("FAIL: nosplit *= var-idx, const (expected 10,20,150 got ") + txt.print_uw(arr26[0]) + txt.print(",") + txt.print_uw(arr26[1]) + txt.print(",") + txt.print_uw(arr26[2]) + txt.print(")\n") + } + + ; Test non-split array *= with non-constant index, non-constant value + uword[] @nosplit arr27 = [5, 10, 15] + ubyte idx27 = 1 + uword mulval27 = 8 + arr27[idx27] *= mulval27 + + if arr27[0] == 5 and arr27[1] == 80 and arr27[2] == 15 { + txt.print("PASS: nosplit *= var-idx, var\n") + } else { + txt.print("FAIL: nosplit *= var-idx, var (expected 5,80,15 got ") + txt.print_uw(arr27[0]) + txt.print(",") + txt.print_uw(arr27[1]) + txt.print(",") + txt.print_uw(arr27[2]) + txt.print(")\n") + } + + sys.poweroff_system() } - - sub thing2() -> ubyte, bool { - cx16.r0L=42 - return cx16.r0L, cx16.r0L==0 - } -; asmsub thing2() -> ubyte @A, bool @Pc { -; %asm {{ -; lda #42 -; sec -; rts -; }} -; } }