ir: canonicalize LOADX/STOREX/STOREZX index width and remove guessed-type workaround

This commit is contained in:
Irmen de Jong committed 2026-08-28 22:23:55 +02:00
1 parent da0df8f133
commit cf50372515
12 files changed
+172 -62

No files matched your search

@@ -12,11 +12,35 @@ import prog8.intermediate.*
internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen: ExpressionGen) {
private fun normalizeArrayIndex(result: MutableList<IRCodeChunkBase>, indexTr: ExpressionCodeResult): Pair<Int, DataType> {
if(codeGen.options.compTarget.POINTER_MEM_SIZE <= 2u || indexTr.dt != IRDataType.BYTE)
return indexTr.resultReg to DataType.UBYTE
val indexReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=indexReg, reg2=indexTr.resultReg), null)
return indexReg to DataType.UWORD
val indexRegType = codeGen.options.compTarget.indexRegType
if(indexTr.dt == indexRegType)
return indexTr.resultReg to if(indexRegType==IRDataType.WORD) DataType.UWORD else DataType.UBYTE
// handle POINTER as its underlying size for the purpose of index canonicalization
val effectiveDt = if(indexTr.dt==IRDataType.POINTER) codeGen.options.compTarget.pointerIRType else indexTr.dt
if(effectiveDt == indexRegType)
return indexTr.resultReg to if(indexRegType==IRDataType.WORD) DataType.UWORD else DataType.UBYTE
val newReg = codeGen.registers.next(indexRegType)
when {
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.BYTE ->
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.POINTER ->
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.WORD ->
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.POINTER -> {
val ptrType = codeGen.options.compTarget.pointerIRType
if(ptrType==IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
}
else -> throw IllegalArgumentException("unexpected index conversion ${indexTr.dt} -> $indexRegType")
}
return newReg to if(indexRegType==IRDataType.WORD) DataType.UWORD else DataType.UBYTE
}
internal fun translate(assignment: PtAssignment): IRCodeChunks {
@@ -890,32 +890,36 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
if(isConstZeroValue) {
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexTr.resultReg, labelSymbol = varName)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = varName)
}
}
} else {
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, -1)
if(constIndex!=null) {
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
val idxType = codeGen.options.compTarget.indexRegType
val offsetReg = codeGen.registers.next(idxType)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.LOAD, idxType, reg1=offsetReg, immediate = constIndex)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=offsetReg, labelSymbol = varName)
}
} else {
val indexTr = exprGen.translateExpression(target.index)
addToResult(result, indexTr, indexTr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, indexTr)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexTr.resultReg, labelSymbol = varName)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = varName)
}
}
}
@@ -621,10 +621,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} else {
val tr = translateExpression(arrayIx.index)
addToResult(result, tr, tr.resultReg, -1)
val indexReg = codeGen.canonicalizeIndexReg(result, tr)
result += IRCodeChunk(null, null).also {
val tmpRegMsb = codeGen.registers.next(IRDataType.BYTE)
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = tr.resultReg, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = tr.resultReg, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpRegMsb, reg2 = indexReg, labelSymbol= "${arrayVarSymbol}_msb")
it += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=resultRegister, reg2 = indexReg, labelSymbol= "${arrayVarSymbol}_lsb")
it += IRInstruction(Opcode.CONCAT, IRDataType.BYTE, reg1=finalResultReg, reg2=tmpRegMsb, reg3=resultRegister)
}
}
@@ -31,7 +31,7 @@ class IRCodeGen(
private set
// on 32-bit targets, LOADX/STOREX/STOREZX use a word index register (0-32767) instead of a byte (0-255)
internal val wordArrayIndex: Boolean = options.compTarget.POINTER_MEM_SIZE > 2u
internal val wordArrayIndex: Boolean = options.compTarget.indexRegType == IRDataType.WORD
/**
* The IR data type to use for a memory address on the current target.
@@ -2259,7 +2259,7 @@ class IRCodeGen(
addToResult(result, byteIndexTr, byteIndexTr.resultReg, -1)
// LOADX/STOREX use word-sized indices on targets with 32-bit pointers.
val indexRegType = if(options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE
val indexRegType = options.compTarget.indexRegType
var indexReg = byteIndexTr.resultReg
var indexDt = byteIndexTr.dt
if(indexDt != indexRegType) {
@@ -2285,6 +2285,37 @@ class IRCodeGen(
return Pair(result, indexReg)
}
internal fun canonicalizeIndexReg(result: MutableList<IRCodeChunkBase>, indexTr: ExpressionCodeResult): Int {
val indexRegType = options.compTarget.indexRegType
if(indexTr.dt == indexRegType)
return indexTr.resultReg
val effectiveDt = if(indexTr.dt==IRDataType.POINTER) options.compTarget.pointerIRType else indexTr.dt
if(effectiveDt == indexRegType)
return indexTr.resultReg
val newReg = registers.next(indexRegType)
when {
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.BYTE ->
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.POINTER ->
addInstr(result, IRInstruction(Opcode.LSIGW, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.WORD ->
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.POINTER -> {
val ptrType = options.compTarget.pointerIRType
if(ptrType==IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.WORD, reg1=newReg, reg2=indexTr.resultReg), null)
else
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1=newReg, reg2=indexTr.resultReg), null)
}
else -> throw IllegalArgumentException("unexpected index conversion ${indexTr.dt} -> $indexRegType")
}
return newReg
}
internal fun irType(type: DataType): IRDataType {
if(type.base.isPassByRef)
return IRDataType.POINTER
@@ -1005,7 +1005,7 @@ jump p8_label_gen_2
// lsigb.? rX,rA + ext.b rY,rX -> loadr.? rY,rA ; and.? rY,#$ff
// Only applied when the intermediate register is not read anywhere else in this chunk.
if(chunk.instructions.size<2) return false
val readCounts = chunk.usedRegisters().readRegs.withDefault { 0 }
val readCounts = chunk.usedRegisters(irprog.options.compTarget.indexRegType).readRegs.withDefault { 0 }
class Mod(val idx: Int, val replacement: IRInstruction?) // null replacement = delete
val mods = mutableListOf<Mod>()
@@ -1,6 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.core.IErrorReporter
import prog8.intermediate.indexRegType
import prog8.intermediate.*
@@ -80,7 +81,7 @@ class IRUnusedCodeRemover(
}
// remove stray loads
val indexRegType = if(irprog.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE
val indexRegType = irprog.options.compTarget.indexRegType
val readRegs = mutableSetOf<Int>()
val readFpRegs = mutableSetOf<Int>()
instructions.forEach { ins ->
@@ -89,8 +90,7 @@ class IRUnusedCodeRemover(
val writeRegsCounts = mutableMapOf<RegisterNum, Int>()
val writeFpRegsCounts = mutableMapOf<RegisterNum, Int>()
val regsTypes = mutableMapOf<RegisterNum, IRDataType>()
val indexGuessRegs = mutableSetOf<RegisterNum>()
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, indexGuessRegs, null, indexRegType)
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null, indexRegType)
readRegs.addAll(readRegsCounts.keys.map { it.value })
readFpRegs.addAll(readFpRegsCounts.keys.map { it.value })
}
@@ -1,5 +1,6 @@
package prog8.codegen.intermediate
import prog8.intermediate.indexRegType
import prog8.intermediate.*
/*
@@ -71,7 +72,7 @@ object RegisterPacker {
data class Interval(val register: Int, val start: Int, val end: Int, val type: IRDataType)
fun pack(irProg: IRProgram) {
val indexRegType = if(irProg.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE
val indexRegType = irProg.options.compTarget.indexRegType
val allRegTypes = mutableMapOf<Int, IRDataType>()
irProg.foreachCodeChunk { chunk ->
for (instr in chunk.instructions) {
@@ -101,7 +102,7 @@ object RegisterPacker {
// This avoids the strict per-instruction validation in usedRegisters() that POINTER↔LONG↔WORD
// cross-type packing can trigger.
fun rebuildTypeMap(irProg: IRProgram): Map<RegisterNum, IRDataType> {
val indexRegType = if(irProg.options.compTarget.POINTER_MEM_SIZE > 2u) IRDataType.WORD else IRDataType.BYTE
val indexRegType = irProg.options.compTarget.indexRegType
val newTypes = mutableMapOf<RegisterNum, IRDataType>()
irProg.foreachCodeChunk { chunk ->
for (instr in chunk.instructions) {