Fix in operator on arrays larger than 255 elements

This commit is contained in:
Irmen de Jong committed 2026-10-11 02:05:21 +02:00
1 parent c0267e37b0
commit eda21a6ebf
6 files changed
+176 -21

No files matched your search

@@ -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)
}
@@ -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
+29
View File
@@ -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
}
})
+34
View File
@@ -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
}
}
})
-2
View File
@@ -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.
+5 -5
View File
@@ -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)