diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt index cfe149318..8f2013688 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/PointerAssignmentsGen.kt @@ -290,7 +290,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri return "P8ZP_SCRATCH_PTR" to 0u } else - return pointer.startpointer.name to field.second + return pointer.startpointer.name to field.second.toUByte() } else { // have to copy it to temp zp var @@ -300,7 +300,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri return "P8ZP_SCRATCH_PTR" to 0u } else - return "P8ZP_SCRATCH_PTR" to field.second + return "P8ZP_SCRATCH_PTR" to field.second.toUByte() } } } @@ -333,7 +333,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri updateScratchPointer() return "P8ZP_SCRATCH_PTR" to 0u } else - return "P8ZP_SCRATCH_PTR" to fieldinfo.second + return "P8ZP_SCRATCH_PTR" to fieldinfo.second.toUByte() } } @@ -958,7 +958,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri val right = binExpr.right as? PtIdentifier require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"} - val field: Pair + val field: Pair var extraFieldOffset = 0 if(left.type.isStructInstance) { @@ -989,7 +989,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri field = struct.getField(right.name, asmgen.program.memsizer) } - val offset = extraFieldOffset + field.second.toInt() + val offset = extraFieldOffset + field.second // TODO avoid always using a zp scratch reg below if the only thing we dereferenced above is a pointer variable that is already in zeropage itself if(offset>=256) { // add field offset to pointer if it doesn't fit in the index register diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 8c06e329c..f1762521e 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -362,20 +362,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen private fun loadfield( inplaceInstrs: MutableList, addressReg: Int, - fieldOffset: UByte, + fieldOffset: Int, targetDt: IRDataType, oldvalueReg: Int ) { if (targetDt == IRDataType.FLOAT) { addInstr( inplaceInstrs, - IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset.toInt())), + IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset)), null ) } else { addInstr( inplaceInstrs, - IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset.toInt())), + IRInstructions.loadMemory(Opcode.LOADI, targetDt, oldvalueReg, IRMemory.indirect(addressReg, fieldOffset)), null ) } @@ -807,7 +807,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize) addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) } - codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), true, -1) + codeGen.storeValueAtPointersLocation(result, pointerReg, 0, targetIdent.type.dereference(), true, -1) } else { if(constIndex!=null) { val offset = eltSize * constIndex @@ -820,7 +820,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen addInstr(result, IRInstructions.binary(Opcode.ADDR, IRDataType.POINTER, pointerReg, indexReg), null) } val realValueReg = if(targetDt == IRDataType.FLOAT) valueFpRegister else valueRegister - codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), false, realValueReg) + codeGen.storeValueAtPointersLocation(result, pointerReg, 0, targetIdent.type.dereference(), false, realValueReg) } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index 00976615e..f5ad7d340 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -186,7 +186,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { } result += IRCodeChunk(null, null).also { - it += IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, firstField.second.toInt()) + it += IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, firstField.second) if (firstField.first.isPointer) { // get the address stored in the pointer and use that for the rest of the chain // LOADI has an exception to allo reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs @@ -214,19 +214,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // For inline array fields (not pointer fields), return the address (ptr+offset) instead of loading from it if(deref.type.isArray && !deref.derefLast) { - if(offset > 0u) - addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset.toInt()), null) + if(offset > 0) + addInstr(result, IRInstructions.binaryImmediate(Opcode.ADD, IRDataType.POINTER, pointerReg, offset), null) return ExpressionCodeResult(result, IRDataType.POINTER, pointerReg, -1) } return if(deref.type.isFloat) { val resultReg = codeGen.registers.next(IRDataType.FLOAT) - addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultReg, IRMemory.indirect(pointerReg, offset.toInt())), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.FLOAT, resultReg, IRMemory.indirect(pointerReg, offset)), null) ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg) } else { val irdt = codeGen.irType(deref.type) val resultReg = codeGen.registers.next(irdt) - addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, irdt, resultReg, IRMemory.indirect(pointerReg, offset.toInt())), null) + addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, irdt, resultReg, IRMemory.indirect(pointerReg, offset)), null) ExpressionCodeResult(result, irdt, resultReg, -1) } } @@ -1876,7 +1876,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val right = binExpr.right as? PtIdentifier require(binExpr.operator=="." && left!=null && right!=null) {"invalid dereference expression ${binExpr.position}"} val result = mutableListOf() - val field: Pair + val field: Pair val pointerReg: Int var extraFieldOffset = 0 @@ -1886,7 +1886,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val struct = left.type.subType!! as StStruct field = struct.getField(right.name, codeGen.program.memsizer) val fieldVmDt = codeGen.irType(field.first) - val fieldOffset = field.second.toInt() + val fieldOffset = field.second if(left.variable!=null) { val constindex = left.index as? PtNumber if(constindex!=null) { @@ -1940,7 +1940,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // add field offset to pointer and load the value into the result register val fieldVmDt = codeGen.irType(field.first) - val fieldOffset = field.second.toInt() + extraFieldOffset + val fieldOffset = field.second + extraFieldOffset var resultFpReg = -1 var resultReg = -1 if (fieldVmDt == IRDataType.FLOAT) @@ -1955,14 +1955,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { return ExpressionCodeResult(result, fieldVmDt, resultReg, resultFpReg) } - internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair { + internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair { // returns instructions to calculate the pointer address, and the offset into the struct it points to // so that LOADI and STOREFIELD opcodes can be used instead of having an explicit extra ADD val result = mutableListOf() if(targetPointerDeref.chain.isEmpty()) - return result to 0u // nothing to do; there's no deref chain + return result to 0 // nothing to do; there's no deref chain var struct: StStruct? = null if(targetPointerDeref.startpointer.type.subType!=null) @@ -1978,11 +1978,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { val field = targetPointerDeref.chain.last() val fieldinfo = struct!!.getField(field, codeGen.program.memsizer) - if(fieldinfo.second>0u) { + if(fieldinfo.second>0) { if(targetPointerDeref.derefLast) { require(fieldinfo.first.isPointer) result += updatePointerFromField(fieldinfo, pointerReg) - return result to 0u + return result to 0 } else { return result to fieldinfo.second } @@ -1992,13 +1992,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) { // LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs addInstr(result, IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg, 0)), null) } - return result to 0u + return result to 0 } - private fun updatePointerFromField(fieldinfo: Pair, pointerReg: Int): IRCodeChunk { + private fun updatePointerFromField(fieldinfo: Pair, pointerReg: Int): IRCodeChunk { return IRCodeChunk(null, null).also { // LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADs - it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg, fieldinfo.second.toInt())) + it += IRInstructions.loadMemory(Opcode.LOADI, IRDataType.POINTER, pointerReg, IRMemory.indirect(pointerReg, fieldinfo.second)) } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 042c94ca1..b9339a2b5 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -2181,7 +2181,7 @@ class IRCodeGen( return chunk } - internal fun evaluatePointerAddressIntoReg(result: MutableList, deref: PtPointerDeref): Pair { + internal fun evaluatePointerAddressIntoReg(result: MutableList, deref: PtPointerDeref): Pair { // calculates the pointer address and returns the register it's in + remaining offset into the struct (so that LOADI/STOREFIELD instructions can be used) val pointerTr = expressionEval.translateExpression(deref.startpointer) result += pointerTr.chunks @@ -2190,8 +2190,8 @@ class IRCodeGen( return pointerTr.resultReg to offset } - internal fun storeValueAtPointersLocation(result: MutableList, addressReg: Int, offset: UByte, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) { - if(offset<=0u) { + internal fun storeValueAtPointersLocation(result: MutableList, addressReg: Int, offset: Int, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) { + if(offset<=0) { val irdt = irType(type) val instr = if(type.isFloat) { if (valueIsZero) IRInstructions.storeZero(Opcode.STOREZI, IRDataType.FLOAT, IRMemory.indirect(addressReg, 0)) @@ -2208,12 +2208,12 @@ class IRCodeGen( val valueRegister = existingValueRegister val irdt = irType(type) if(valueIsZero && valueRegister<0) { - addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, irdt, IRMemory.indirect(addressReg, offset.toInt())), null) + addInstr(result, IRInstructions.storeZero(Opcode.STOREZI, irdt, IRMemory.indirect(addressReg, offset)), null) } else { val instr = if (type.isFloat) - IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueRegister, IRMemory.indirect(addressReg, offset.toInt())) + IRInstructions.storeMemory(Opcode.STOREI, IRDataType.FLOAT, valueRegister, IRMemory.indirect(addressReg, offset)) else - IRInstructions.storeMemory(Opcode.STOREI, irdt, valueRegister, IRMemory.indirect(addressReg, offset.toInt())) + IRInstructions.storeMemory(Opcode.STOREI, irdt, valueRegister, IRMemory.indirect(addressReg, offset)) addInstr(result, instr, null) } } diff --git a/compiler/src/prog8/compiler/BuiltinFunctions.kt b/compiler/src/prog8/compiler/BuiltinFunctions.kt index 4a3a508b3..67a3f7170 100644 --- a/compiler/src/prog8/compiler/BuiltinFunctions.kt +++ b/compiler/src/prog8/compiler/BuiltinFunctions.kt @@ -19,7 +19,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map "abs__word" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() }) }, "abs__long" to { a, p, prg -> listOf(oneIntArgOutputInt(a, p, prg, true) { abs(it).toDouble() }) }, "abs__float" to { a, p, prg -> listOf(oneFloatArgOutputFloat(a, p, prg) { abs(it) }) }, - "len" to { a, p, prg -> listOf(builtinLen(a, p, prg)) }, + "len" to { a, p, prg -> listOf(builtinLen(a, p)) }, "sizeof" to { a, p, prg -> listOf(builtinSizeof(a, p, prg)) }, "offsetof" to { a, p, prg -> listOf(builtinOffsetof(a, p, prg)) }, "sgn" to { a, p, prg -> listOf(builtinSgn(a, p, prg)) }, @@ -124,7 +124,7 @@ private fun builtinOffsetof(args: List, position: Position, program: val fieldname = identifier.last() val struct = args[0].definingScope.lookup(structname) as? StructDecl ?: throw SyntaxError("cannot find struct '$structname'", args[0].position) val offset = struct.offsetof(fieldname, program.target) ?: throw SyntaxError("no such field '${identifier.joinToString(".")}'", args[0].position) - return NumericLiteral.optimalInteger(offset.toInt(), position) + return NumericLiteral.optimalInteger(offset, position) } private fun builtinSizeof(args: List, position: Position, program: Program): NumericLiteral { @@ -184,7 +184,7 @@ private fun builtinSizeof(args: List, position: Position, program: P } } -private fun builtinLen(args: List, position: Position, program: Program): NumericLiteral { +private fun builtinLen(args: List, position: Position): NumericLiteral { // note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE. if(args.size!=1) throw SyntaxError("len requires one argument", position) diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 5214ea081..0835a2d2c 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -708,7 +708,7 @@ _after: val fieldDt = struct?.getFieldType(fieldName) if(struct!=null && fieldDt!=null) { // (ptr).field --> peekXXX(ptr as uword/long + offsetof(Struct.field)) - val offset = struct.offsetof(fieldName, program.target)!!.toInt() + val offset = struct.offsetof(fieldName, program.target)!! // cast to an integer type of the target's pointer size: keeps the '+' unscaled // if ptr is already a typecast (e.g. t.port as ^^Thing), unwrap to avoid // a WORD->POINTER->LONG chain that confuses the IR register allocator on 32-bit targets @@ -1201,8 +1201,8 @@ _after: return null } var fieldAddr: Expression = structBase - if(offset>0u) - fieldAddr = BinaryExpression(fieldAddr, "+", NumericLiteral.optimalInteger(offset.toInt(), deref.position), deref.position) + if(offset>0) + fieldAddr = BinaryExpression(fieldAddr, "+", NumericLiteral.optimalInteger(offset, deref.position), deref.position) return when { fieldDt.isPointer -> { // the field is itself a pointer: load it, then index into what it points to @@ -1383,7 +1383,7 @@ _after: val ptrVar = deref.definingScope.lookup(ptrName) as? VarDecl if(ptrVar!=null && (ptrVar.datatype.isPointer || ptrVar.datatype.isPointerArray)) { val struct = ptrVar.datatype.subType!! as StructDecl - val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.target)!!.toInt(), deref.position) + val offsetNumber = NumericLiteral.optimalInteger(struct.offsetof(field.first, program.target)!!, deref.position) val pointerIdentifier = IdentifierReference(ptrName, deref.position) val addrType = target.pointerType val address: Expression @@ -1614,7 +1614,7 @@ _after: errors.err("no such field '$fieldName' in struct '${currentStruct.name}'", field.position) return noModifications } - val offset = currentStruct.offsetof(fieldName, program.target)!!.toInt() + val offset = currentStruct.offsetof(fieldName, program.target)!! if(offset>0) address = BinaryExpression(address, "+", NumericLiteral.optimalInteger(offset, field.position), field.position) if(index < fields.size-1) { diff --git a/compiler/test/TestPointers.kt b/compiler/test/TestPointers.kt index 3dba3a663..03f6853dc 100644 --- a/compiler/test/TestPointers.kt +++ b/compiler/test/TestPointers.kt @@ -248,6 +248,39 @@ main { (consts[2].value as NumericLiteral).number shouldBe 0.0 } + test("struct field offsets above 255 are supported") { + // Regression test: on targets that allow structs larger than 256 bytes + // (m68k: ARRAY_SIZE_LIMIT 32768, VM: 65535) a field offset can exceed 255. + // This used to crash with 'Failed requirement' in StStruct.getField. + val src = $$""" +%zeropage basicsafe +main { + struct Big { + ubyte[300] pad + uword val + } + + sub start() { + const uword fieldoff = offsetof(Big.val) + ^^Big b = memory("big", 302, 0) + b.pad[0] = 1 + b.val = $1234 + + if fieldoff != 300 { sys.exit(7) } + if b.val != $1234 { sys.exit(8) } + + ; success marker (exit status is not observable through VmRunner) + pokew($4400, $c0ba) + sys.exit(0) + } +}""" + val result = compileText(VMTarget(), false, src, outputDir)!! + val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir") + VmRunner().runAndTestProgram(virtfile.readText(), true) { vm -> + vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba // all in-program checks passed + } + } + test("float fields in struct and union type definitions get correct 5 byte size in assembly") { val src = """ %zeropage basicsafe diff --git a/compiler/test/codegeneration/TestM68k.kt b/compiler/test/codegeneration/TestM68k.kt index 1f01ed76e..1a9b14d34 100644 --- a/compiler/test/codegeneration/TestM68k.kt +++ b/compiler/test/codegeneration/TestM68k.kt @@ -423,6 +423,33 @@ main { lines.any { it == "mymod.Point.p8v_y equ 1" } shouldBe true } + test("struct field offsets above 255 are emitted correctly on m68k") { + // Regression test: m68k allows structs up to ARRAY_SIZE_LIMIT (32768) bytes, so + // field offsets can exceed 255. This used to crash in StStruct.getField. + val src = """ +main { + struct Big { + ubyte[300] pad + uword val + } + + sub start() { + ^^Big b = 4000 + b.val = 1 + } +} +""" + for (target in listOf(Amiga500Target(), Qemu68kTarget())) { + val result = compileText(target, 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.trim() } + lines.any { it == "p8b_main.p8t_Big_size equ 302" } shouldBe true + lines.any { it == "p8b_main.p8t_Big.p8v_pad equ 0" } shouldBe true + lines.any { it == "p8b_main.p8t_Big.p8v_val equ 300" } shouldBe true + } + } + test("struct type symbols are emitted in the header before program start") { val src = """ main { diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index a2c1d8477..b6d2e9f8c 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -591,13 +591,13 @@ class StructDecl(override val name: String, val fields: Array, val fun getField(name: String): StructField? = fields.firstOrNull { it.name==name } override val scopedNameString by lazy { scopedName.joinToString(".") } - fun offsetof(fieldname: String, sizer: IMemSizer): UByte? { + fun offsetof(fieldname: String, sizer: IMemSizer): Int? { if(isUnion) { - return if(fields.any { it.name == fieldname }) 0.toUByte() else null + return if(fields.any { it.name == fieldname }) 0 else null } fields.fold(0) { offset, field -> if (field.name == fieldname) - return offset.toUByte() + return offset val numElements = if(field.isArray) field.constSize() ?: 1 else 1 offset + sizer.memorySize(field.type, numElements) } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 0eb5c6010..d17d94322 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -14,6 +14,7 @@ Future Things and Ideas - support typed pointer arrays as struct fields, curretly requires untyped pointers arrays. - allow aliasing a whole enum (``alias myEnum = MyEnum``) so that ``myEnum::Member`` works just like ``MyEnum::Member``; currently only aliasing individual enum members is supported. See ideas/alias-enums.md for the plan. - evaluate the feature suggestions in ideas/c16-banked-data.md (cx16 banked data library). +- Status flag extraction clobbers a register return value: in a multi-assign where one call returns both a status flag and a regular register value (e.g. ``-> bool @Pc, ubyte @A`` on new6502, or ``-> bool @Pc, ubyte @D0`` on m68k), the status flag extraction destroys the register return before it is read. The extraction uses a scratch hardware register (``A`` on 6502, ``D0`` on m68k) that can be the same register holding the return value. The old 6502 codegen avoids this with ``pha``/``pla``. See ideas/status-flag-register-clobber.md for the design (reorder the register returns before the flag extraction, protected by register-neutral ``PUSHST``/``POPST``). Won't do's or deferred ^^^^^^^^^^^^^^^^^^^^^^ @@ -54,14 +55,6 @@ Symbol naming in new codegen backends (not a VM issue) Source line tracking in new codegen backends - Improve source line tracking across the IR into the generated assembly code in the new codegen backends (new6502, m68k). Currently, the IR preserves some source position information, but this is not consistently propagated through to the final assembly output. Better tracking would improve debugging experience (e.g., in monitor/debugger tools) and make it easier to correlate generated assembly back to the original Prog8 source code. Consider adding source location metadata to IR instructions and ensuring code generators emit appropriate ``.line`` directives or comments in the assembly output. -Status flag extraction clobbers a register return value -- In a multi-assign where one call returns both a status flag and a regular register value (e.g. ``-> bool @Pc, ubyte @A`` on new6502, or ``-> bool @Pc, ubyte @D0`` on m68k), the status flag extraction destroys the register return before it is read. The extraction uses a scratch hardware register (``A`` on 6502, ``D0`` on m68k) that can be the same register holding the return value. The old 6502 codegen avoids this with ``pha``/``pla``. See ideas/status-flag-register-clobber.md for the design (reorder the register returns before the flag extraction, protected by register-neutral ``PUSHST``/``POPST``). - - -Struct field offsets are limited to 255 on targets that allow larger structs -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- ``ARRAY_SIZE_LIMIT`` caps struct size at 256 bytes on the 6502 targets, but at 32768 bytes on the m68k targets and 65535 on the VM target. The struct-member offset is however carried as a ``UByte``: ``StStruct.getField()`` (simpleAst/src/prog8/code/SymbolTable.kt) has ``require(offset<=255)``, so accessing a field at offset 256 or more on m68k/VM crashes the compiler with ``IllegalArgumentException: Failed requirement`` (the struct declaration itself is accepted, only the member access fails). ``StructDecl.offsetof()`` (compilerAst/src/prog8/ast/statements/AstStatements.kt) has the same 255 limit and silently truncates, but that path is only used by the old 6502 codegen, where the 256-byte cap makes it unreachable. Fix: widen the offset to ``Int``/``UInt`` end-to-end (including the IR instruction operand types). - Libraries ^^^^^^^^^ diff --git a/simpleAst/src/prog8/code/SymbolTable.kt b/simpleAst/src/prog8/code/SymbolTable.kt index 5363f9a5d..94dcd7965 100644 --- a/simpleAst/src/prog8/code/SymbolTable.kt +++ b/simpleAst/src/prog8/code/SymbolTable.kt @@ -271,19 +271,18 @@ class StStruct( val isUnion: Boolean = false ) : StNode(name, StNodeType.STRUCT, astNode), ISubType { - fun getField(name: String, sizer: IMemSizer): Pair { + fun getField(name: String, sizer: IMemSizer): Pair { // returns type and byte offset of the given field if(isUnion) { val field = fields.firstOrNull { it.name == name } if(field!=null) - return field.type to 0u + return field.type to 0 throw NoSuchElementException("field $name not found in struct ${this.name}") } var offset = 0 for(field in fields) { if(name==field.name) { - require(offset<=255) - return field.type to offset.toUByte() + return field.type to offset } val numElements = if(field.isArray) field.arraySize!! else 1 offset += sizer.memorySize(field.type, numElements)