fix struct size truncation to 256 bytes on targets that allow larger structs (m68k)

This commit is contained in:
Irmen de Jong committed 2026-09-24 00:22:23 +02:00
1 parent 2050fcb4ee
commit 976b64185d
11 files changed
+106 -54

No files matched your search

@@ -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<DataType, UByte>
val field: Pair<DataType, Int>
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
@@ -362,20 +362,20 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
private fun loadfield(
inplaceInstrs: MutableList<IRCodeChunkBase>,
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)
}
}
@@ -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<IRCodeChunkBase>()
val field: Pair<DataType, UByte>
val field: Pair<DataType, Int>
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<IRCodeChunks, UByte> {
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, Int> {
// 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<IRCodeChunkBase>()
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<DataType, UByte>, pointerReg: Int): IRCodeChunk {
private fun updatePointerFromField(fieldinfo: Pair<DataType, Int>, 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))
}
}
@@ -2181,7 +2181,7 @@ class IRCodeGen(
return chunk
}
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, UByte> {
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, Int> {
// 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<IRCodeChunkBase>, addressReg: Int, offset: UByte, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) {
if(offset<=0u) {
internal fun storeValueAtPointersLocation(result: MutableList<IRCodeChunkBase>, 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)
}
}
@@ -19,7 +19,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, (args: List<Expression>
"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<Expression>, 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<Expression>, position: Position, program: Program): NumericLiteral {
@@ -184,7 +184,7 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
}
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
private fun builtinLen(args: List<Expression>, 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)
@@ -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) {
+33
View File
@@ -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
+27
View File
@@ -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 {
@@ -591,13 +591,13 @@ class StructDecl(override val name: String, val fields: Array<StructField>, 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)
}
+1 -8
View File
@@ -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
^^^^^^^^^
+3 -4
View File
@@ -271,19 +271,18 @@ class StStruct(
val isUnion: Boolean = false
) : StNode(name, StNodeType.STRUCT, astNode), ISubType {
fun getField(name: String, sizer: IMemSizer): Pair<DataType, UByte> {
fun getField(name: String, sizer: IMemSizer): Pair<DataType, Int> {
// 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)