codeGenIntermediate: fold struct-array field access to single LOADX/STOREX

Replace arr[idx].field (arr:Struct[Sz], idx variable) from
EXT+MUL #Sz + ADDR + ADD #fieldOff + LOADI/STOREI (5 ops) to
single indexed op: LOADX/STOREX arr+fieldOff,S=Sz and
STOREZX for zero. Reuses existing ,S=N + +off encoding.
This commit is contained in:
Irmen de Jong committed 2026-09-02 01:35:30 +02:00
1 parent 59c41ad129
commit b0f1f655c3
7 files changed
+214 -55

No files matched your search

@@ -675,6 +675,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
return result
}
memory != null -> {
if(tryFoldStructArrayDirectWrite(memory!!.address, zero, valueRegister, targetDt, result)) return result
require(targetDt == IRDataType.BYTE) { "must be byte type ${memory!!.position}"}
if(zero) {
if(memory!!.address is PtNumber) {
@@ -3021,4 +3022,23 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
}
return result
}
private fun tryFoldStructArrayDirectWrite(addressExpr: PtExpression, zero: Boolean, valueReg: Int, targetDt: IRDataType, result: MutableList<IRCodeChunkBase>): Boolean {
val info = extractStructArrayIndexInfo(addressExpr, codeGen) ?: return false
val (idxCode, indexReg) = codeGen.loadIndexReg(info.idxExpr, info.structSize, codeGen.wordArrayIndex, false)
result += idxCode
if(zero) {
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0) it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
}
} else {
if(valueReg<0) return false
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0) it += IRInstruction(Opcode.STOREX, targetDt, reg1=valueReg, reg2=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else it += IRInstruction(Opcode.STOREX, targetDt, reg1=valueReg, reg2=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
}
}
return true
}
}
@@ -563,6 +563,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
private fun funcPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult {
// Try struct-array field folding: pokew(&arr[idx]+fieldOff, value) -> STOREX arr+fieldOff,S=structSize
tryFoldStructArrayPoke(call, dt)?.let { return it }
val result = mutableListOf<IRCodeChunkBase>()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
@@ -973,4 +975,40 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
return ExpressionCodeResult(result, IRDataType.WORD, -1, -1)
}
private fun tryFoldStructArrayPoke(call: PtFunctionCall, dt: IRDataType): ExpressionCodeResult? {
// Detect pokew(&arr[idx]+fieldOff, value) where arr is struct array with variable idx
// and fold to single STOREX/STOREZX with scale=structSize.
val info = extractStructArrayIndexInfo(call.args[0], codeGen) ?: return null
val isZero = codeGen.isZero(call.args[1])
val result = mutableListOf<IRCodeChunkBase>()
val (idxCode, indexReg) = codeGen.loadIndexReg(info.idxExpr, info.structSize, codeGen.wordArrayIndex, false)
result += idxCode
return if(isZero) {
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREZX, dt, reg1=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
if(dt==IRDataType.FLOAT) {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREX, dt, reg1=indexReg, fpReg1=RegisterNum(valueTr.resultFpReg), labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
} else {
if(info.fieldOffset==0)
it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, scale=info.structSize)
else
it += IRInstruction(Opcode.STOREX, dt, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol=info.arrayName, symbolOffset=info.fieldOffset, scale=info.structSize)
}
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
}
}
@@ -1870,29 +1870,49 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(left.type.isStructInstance) {
// indexing on a pointer directly
// fetch pointer address, determine struct and field, add index * structsize
// indexing on a struct array: left is struct instance element
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()
if(left.variable!=null) {
val pointerTr = translateExpression(left.variable!!)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
val constindex = left.index as? PtNumber
if(constindex!=null) {
// const index: keep pointer+loadi path (extraFieldOffset folded into loadi)
val pointerTr = translateExpression(left.variable!!)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
extraFieldOffset = struct.size.toInt() * constindex.number.toInt()
} else if(left.variable!!.type.isArray) {
// variable index: fold to single LOADX with scale=structSize, disp=fieldOffset
// only for known array symbols (not generic pointer derefs)
val (chunks, indexReg) = codeGen.loadIndexReg(left.index, struct.size.toInt(), codeGen.wordArrayIndex, false)
result += chunks
if (fieldVmDt == IRDataType.FLOAT) {
val fpReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, fieldVmDt, fpReg1 = RegisterNum(fpReg), reg1 = indexReg, labelSymbol = left.variable!!.name, symbolOffset = fieldOffset, scale = struct.size.toInt()), null)
return ExpressionCodeResult(result, fieldVmDt, -1, fpReg)
} else {
val r = codeGen.registers.next(fieldVmDt)
addInstr(result, IRInstruction(Opcode.LOADX, fieldVmDt, reg1 = r, reg2 = indexReg, labelSymbol = left.variable!!.name, symbolOffset = fieldOffset, scale = struct.size.toInt()), null)
return ExpressionCodeResult(result, fieldVmDt, r, -1)
}
} else {
// variable index on a pointer variable: compute pointer + index*structSize, then LOADI
val pointerTr = translateExpression(left.variable!!)
result += pointerTr.chunks
pointerReg = pointerTr.resultReg
val (chunks, indexReg) = codeGen.loadIndexReg(left.index, struct.size.toInt(), true, false)
result += chunks
if(struct.size.toInt()!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt())
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg), null)
}
} else if(left.pointerderef!=null) {
TODO("get pointer from deref $left ${left.position}")
} else {
throw AssemblyError("weird arrayindexer $left ${left.position}")
}
val struct = left.type.subType!! as StStruct
val constindex = left.index as? PtNumber
if(constindex!=null) {
extraFieldOffset = struct.size.toInt() * constindex.number.toInt()
} else {
val (chunks, indexReg) = codeGen.loadIndexReg(left.index, struct.size.toInt(), true, false)
result += chunks
if(struct.size.toInt()!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexReg, struct.size.toInt())
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg), null)
}
field = struct.getField(right.name, codeGen.program.memsizer)
} else {
@@ -0,0 +1,52 @@
package prog8.codegen.intermediate
import prog8.code.ast.*
internal data class StructArrayIndexInfo(
val arrayName: String,
val idxExpr: PtExpression,
val structSize: Int,
val fieldOffset: Int
)
internal fun extractStructArrayIndexInfo(
addressExpr: PtExpression,
codeGen: IRCodeGen
): StructArrayIndexInfo? {
fun unwrapCast(expr: PtExpression): PtExpression {
var cur = expr
while(cur is PtTypeCast) cur = cur.value
return cur
}
fun asAddressOf(expr: PtExpression): PtAddressOf? {
return unwrapCast(expr) as? PtAddressOf
}
val baseAddrOf: PtAddressOf
val fieldOffset: Int
val unwrapped = unwrapCast(addressExpr)
when {
unwrapped is PtAddressOf -> {
baseAddrOf = unwrapped
fieldOffset = 0
}
unwrapped is PtBinaryExpression && unwrapped.operator == "+" -> {
val leftAddr = asAddressOf(unwrapped.left) ?: return null
val rightNum = (unwrapCast(unwrapped.right) as? PtNumber)?.number?.toInt() ?: return null
baseAddrOf = leftAddr
fieldOffset = rightNum
}
else -> return null
}
if(!baseAddrOf.isFromArrayElement) return null
val arrayIdent = baseAddrOf.identifier ?: return null
val idxExpr = baseAddrOf.arrayIndexExpr ?: return null
if(idxExpr is PtNumber) return null
val arrayType = arrayIdent.type
if(!arrayType.isArray) return null
val elemType = arrayType.elementType()
if(!elemType.isStructInstance) return null
val structSize = codeGen.program.memsizer.memorySize(elemType, null)
if(structSize <= 0 || structSize > 65535) return null
if(fieldOffset < 0 || fieldOffset > 65535) return null
return StructArrayIndexInfo(arrayIdent.name, idxExpr, structSize, fieldOffset)
}