diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index f0a1bde7d..c2265992c 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -528,11 +528,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, elementTr, elementTr.resultReg, -1) val iterableTr = translateExpression(haystackVar) addToResult(result, iterableTr, iterableTr.resultReg, -1) - val lengthReg = codeGen.registers.next(IRDataType.BYTE) + val lengthReg = codeGen.registers.next(IRDataType.WORD) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null) val resultReg = codeGen.registers.next(IRDataType.BYTE) - result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) + result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg) addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -541,12 +541,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, elementTr, elementTr.resultReg, -1) val iterableTr = translateExpression(haystackVar) addToResult(result, iterableTr, iterableTr.resultReg, -1) - val lengthReg = codeGen.registers.next(IRDataType.BYTE) + val lengthReg = codeGen.registers.next(IRDataType.WORD) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) + addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null) val resultReg = codeGen.registers.next(IRDataType.BYTE) val syscall = if(haystackVar.type.isSplitWordArray(codeGen.options.compTarget)) IMSyscall.SPLIT_WORDARRAY_CONTAINS else IMSyscall.WORDARRAY_CONTAINS - result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) + result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg) addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -555,11 +555,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, elementTr, elementTr.resultReg, -1) val iterableTr = translateExpression(haystackVar) addToResult(result, iterableTr, iterableTr.resultReg, -1) - val lengthReg = codeGen.registers.next(IRDataType.BYTE) + val lengthReg = codeGen.registers.next(IRDataType.WORD) val resultReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) - result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) + addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null) + result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg) addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } @@ -568,11 +568,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { addToResult(result, elementTr, -1, elementTr.resultFpReg) val iterableTr = translateExpression(haystackVar) addToResult(result, iterableTr, iterableTr.resultReg, -1) - val lengthReg = codeGen.registers.next(IRDataType.BYTE) + val lengthReg = codeGen.registers.next(IRDataType.WORD) val resultReg = codeGen.registers.next(IRDataType.BYTE) val iterableLength = codeGen.symbolTable.getLength(haystackVar.name) - addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null) - result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg) + addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null) + result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg) addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null) return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1) } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt index 9d3183dcd..e31442893 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrSyscall.kt @@ -11,6 +11,7 @@ import prog8.intermediate.CallSite import prog8.intermediate.IMSyscall import prog8.intermediate.IRDataType import prog8.intermediate.RegisterOperand +import prog8.intermediate.floatNumber import prog8.intermediate.intNumber @@ -18,8 +19,12 @@ import prog8.intermediate.intNumber internal fun AsmGen.translateSyscall(num: Int, site: CallSite) { when (num) { + IMSyscall.STRING_CONTAINS.number -> translateSyscallStringContains(site) + IMSyscall.BYTEARRAY_CONTAINS.number -> translateSyscallBytearrayContains(site) IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(site) + IMSyscall.SPLIT_WORDARRAY_CONTAINS.number -> translateSyscallSplitWordarrayContains(site) IMSyscall.LONGARRAY_CONTAINS.number -> translateSyscallLongarrayContains(site) + IMSyscall.FLOATARRAY_CONTAINS.number -> translateSyscallFloatarrayContains(site) IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(site) IMSyscall.CLAMP_BYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, true) IMSyscall.CLAMP_UBYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, false) @@ -60,6 +65,9 @@ private fun AsmGen.loadStringArg(reg: Int, dt: IRDataType, dreg: String) { } } +// Shared helper for array contains syscalls that scan a contiguous array. +// The length argument is a word, so arrays up to the target's ARRAY_SIZE_LIMIT +// are scanned correctly (m68k: 32768 elements, VM: 65535 elements). private fun AsmGen.translateSyscallArrayContains(site: CallSite, dt: IRDataType) { val regElem = site.argument(0).intNumber val regArr = site.argument(1).intNumber @@ -72,12 +80,12 @@ private fun AsmGen.translateSyscallArrayContains(site: CallSite, dt: IRDataType) emitLoadD0(regElem, dt) emitLine("move.l ${regAddr(regArr)}, a0") - emitLine("moveq.l #0, d1") - emitLine("move.b ${regAddrByte(regLen, 0)}, d1") + emitLine("move.w ${regAddr(regLen)}, d1") emitLine("subq.w #1, d1") emitLine("bmi $labelDone ; length was 0 -> not found") emitRaw("$labelLoop:") val cmpInstr = when (dt) { + IRDataType.BYTE -> "cmp.b" IRDataType.WORD -> "cmp.w" IRDataType.LONG -> "cmp.l" else -> error("array contains: unsupported element type $dt") @@ -92,6 +100,10 @@ private fun AsmGen.translateSyscallArrayContains(site: CallSite, dt: IRDataType) emitRaw("$labelDone:") } +private fun AsmGen.translateSyscallBytearrayContains(site: CallSite) { + translateSyscallArrayContains(site, IRDataType.BYTE) +} + private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) { translateSyscallArrayContains(site, IRDataType.WORD) } @@ -100,6 +112,88 @@ private fun AsmGen.translateSyscallLongarrayContains(site: CallSite) { translateSyscallArrayContains(site, IRDataType.LONG) } +private fun AsmGen.translateSyscallSplitWordarrayContains(site: CallSite) { + val regElem = site.argument(0).intNumber + val regArr = site.argument(1).intNumber + val regLen = site.argument(2).intNumber + val resultReg = site.resultRegister(0).intNumber + + val labelLoop = makeLabel(".swc_loop") + val labelFound = makeLabel(".swc_found") + val labelDone = makeLabel(".swc_done") + + emitLoadD0(regElem, IRDataType.WORD) + emitLine("move.l ${regAddr(regArr)}, a0") // a0 = lsb array start + emitLine("move.w ${regAddr(regLen)}, d1") // d1 = element count + emitLine("move.w d1, d2") + emitLine("add.l a0, d2") // d2 = msb array start (lsb + length) + emitLine("subq.w #1, d1") + emitLine("bmi $labelDone ; length was 0 -> not found") + emitRaw("$labelLoop:") + emitLine("move.b (a0,d1.w), d3") // d3 = lsb of element at index d1 + emitLine("move.b (d2,d1.w), d4") // d4 = msb of element at index d1 + emitLine("lsl.w #8, d4") + emitLine("or.w d3, d4") // d4 = reconstructed word + emitLine("cmp.w d0, d4") + emitLine("beq $labelFound") + emitLine("dbra d1, $labelLoop") + emitLine("move.b #0, ${regAddrByte(resultReg, 0)}") + emitLine("bra $labelDone") + emitRaw("$labelFound:") + emitLine("move.b #1, ${regAddrByte(resultReg, 0)}") + emitRaw("$labelDone:") +} + +private fun AsmGen.translateSyscallFloatarrayContains(site: CallSite) { + val regNeedle = site.argument(0).floatNumber + val regArr = site.argument(1).intNumber + val regLen = site.argument(2).intNumber + val resultReg = site.resultRegister(0).intNumber + + val labelLoop = makeLabel(".fac_loop") + val labelFound = makeLabel(".fac_found") + val labelDone = makeLabel(".fac_done") + + emitLine("fmove.s ${floatRegFileAddr(regNeedle)}, fp0") + emitLine("move.l ${regAddr(regArr)}, a0") + emitLine("move.w ${regAddr(regLen)}, d1") + emitLine("subq.w #1, d1") + emitLine("bmi $labelDone ; length was 0 -> not found") + emitRaw("$labelLoop:") + emitLine("fcmp.s (a0)+, fp0") + emitLine("fbeq $labelFound") + emitLine("dbra d1, $labelLoop") + emitLine("move.b #0, ${regAddrByte(resultReg, 0)}") + emitLine("bra $labelDone") + emitRaw("$labelFound:") + emitLine("move.b #1, ${regAddrByte(resultReg, 0)}") + emitRaw("$labelDone:") +} + +private fun AsmGen.translateSyscallStringContains(site: CallSite) { + val regChar = site.argument(0).intNumber + val regStr = site.argument(1).intNumber + val resultReg = site.resultRegister(0).intNumber + + val labelLoop = makeLabel(".sc_loop") + val labelFound = makeLabel(".sc_found") + val labelDone = makeLabel(".sc_done") + + emitLine("move.b ${regAddrByte(regChar, 0)}, d0") + emitLine("move.l ${regAddr(regStr)}, a0") + emitRaw("$labelLoop:") + emitLine("move.b (a0)+, d1") + emitLine("beq $labelDone ; reached null terminator -> not found") + emitLine("cmp.b d0, d1") + emitLine("beq $labelFound") + emitLine("bra $labelLoop") + emitRaw("$labelFound:") + emitLine("move.b #1, ${regAddrByte(resultReg, 0)}") + emitLine("bra $labelDone") + emitRaw("$labelDone:") + emitLine("move.b #0, ${regAddrByte(resultReg, 0)}") +} + private fun AsmGen.translateSyscallClamp(site: CallSite, dt: IRDataType, signed: Boolean) { val valueReg = site.argument(0).intNumber val minReg = site.argument(1).intNumber diff --git a/compiler/test/codegeneration/TestM68k.kt b/compiler/test/codegeneration/TestM68k.kt index 5c2f16462..1b5426b99 100644 --- a/compiler/test/codegeneration/TestM68k.kt +++ b/compiler/test/codegeneration/TestM68k.kt @@ -640,4 +640,33 @@ main { asm.contains("; ---- Subroutine: p8b_adpcm.p8s_decode_block_stereo ----") shouldBe true asm.contains("link a5,#") shouldBe true } + + test("containment check on arrays larger than 255 elements uses word-length counter") { + // Regression test: the IR used a byte-length register for array contains syscalls, + // which crashed for lengths > 255 and produced false negatives on VM/m68k targets. + val src = """ +main { + bool @shared bfound + bool @shared wfound + + sub start() { + ubyte[300] @shared barr + barr[260] = 42 + bfound = 42 in barr + + word[300] @shared warr + warr[280] = 3000 + wfound = 3000 in warr + } +} +""" + val result = compileText(Qemu68kTarget(), optimize = false, src, outputDir, writeAssembly = true, assemble = false) + result shouldNotBe null + val asm = result!!.compilationOptions.outputDir.resolve("${result.compilerAst.name}.asm").toFile().readText() + val lines = asm.lines().map { it.substringBefore(';').trim() } + // The m68k backend must load the length as a word and use dbra (word counter), + // not a single-byte loop counter. + lines.any { it.startsWith("move.w") && it.endsWith(",d1") } shouldBe true + lines.count { it.startsWith("dbra d1,.ac_loop_") } shouldBe 2 + } }) diff --git a/compiler/test/vm/TestCompilerVirtual.kt b/compiler/test/vm/TestCompilerVirtual.kt index ebf867424..626d91b64 100644 --- a/compiler/test/vm/TestCompilerVirtual.kt +++ b/compiler/test/vm/TestCompilerVirtual.kt @@ -1917,5 +1917,39 @@ main { } } + test("containment check scans arrays larger than 255 elements") { + // Regression test: the IR used a byte-length register for array contains syscalls, + // which crashed for lengths > 255 and produced false negatives on VM/m68k targets. + val src = """ +main { + bool @shared bfound + bool @shared bnotfound + bool @shared wfound + bool @shared wnotfound + + sub start() { + ubyte[300] @shared barr + barr[260] = 42 + bfound = 42 in barr + bnotfound = 99 in barr + + word[300] @shared warr + warr[280] = 3000 + wfound = 3000 in warr + wnotfound = 9999 in warr + } +}""" + val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = true)!! + val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") + val irProgram = IRFileReader().read(virtfile) + val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget).allocations + VmRunner().runAndTestProgram(virtfile.readText(), false) { vm -> + vm.memory.getUB(allocations["main.bfound"]!!) shouldBe 1u + vm.memory.getUB(allocations["main.bnotfound"]!!) shouldBe 0u + vm.memory.getUB(allocations["main.wfound"]!!) shouldBe 1u + vm.memory.getUB(allocations["main.wnotfound"]!!) shouldBe 0u + } + } + }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 90729fd2f..3e267528d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,8 +5,6 @@ amiga_gfx: make scroll work, fix draw/mouseclick offsets for windowed mode and Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ -- BUG: the ``in`` (contains) operator on arrays silently scans only ``length AND 255`` elements: the IR materializes the haystack length as a ``BYTE`` register (``ExpressionGen.kt``) and all backends (6502, m68k, VM) loop on a single byte counter. Arrays can legally exceed 255 elements on m68k (``ARRAY_SIZE_LIMIT`` 32768) and VM (65535) targets, producing false negatives with no diagnostic. Fixing it means widening the length operand to ``WORD`` in the IR and using 16-bit loop counters in every backend. - possible problems also in translateSyscallArrayContains in m68k InstrSyscall.kt - extend the ``-gendoc`` command to generate user reference documentation from Markdown docstrings; see ``ideas/markdown-docstrings-and-reference-docs.md`` for the plan. - add documentation for more library modules instead of just linking to the source code - add float support to the configurable compiler targets. Restrictions: just have "cbm-style floats" as an option (to that it can slot into the current float codegen), where "all" you have to specify is the addresses of AYINT and GIVAYF and FADDT and all their friends. diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index aad47e731..8e0ded28e 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -398,7 +398,7 @@ object SysCalls { } Syscall.BYTEARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) - var length = lengthV as UByte + var length = lengthV as UShort var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getUB(array)==value) @@ -410,7 +410,7 @@ object SysCalls { } Syscall.WORDARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) - var length = lengthV as UByte + var length = lengthV as UShort var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getUW(array)==value) @@ -422,7 +422,7 @@ object SysCalls { } Syscall.SPLIT_WORDARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) - var length = lengthV as UByte + var length = lengthV as UShort val array = (arrayV as Int).toUInt() val msbOffset = length.toUInt() while(length > 0u) { @@ -437,7 +437,7 @@ object SysCalls { } Syscall.LONGARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) - var length = lengthV as UByte + var length = lengthV as UShort val array = (arrayV as Int).toUInt() while(length > 0u) { length-- @@ -449,7 +449,7 @@ object SysCalls { } Syscall.FLOATARRAY_CONTAINS -> { val (value, arrayV, lengthV) = getArgValues(callspec.arguments, vm) - var length = lengthV as UByte + var length = lengthV as UShort var array = (arrayV as Int).toUInt() while(length>0u) { if(vm.memory.getFloat(array)==value)