ir: add scaled indexing (S=) to LOADX/STOREX/STOREZX

Remove byte-offset pre-scaling from IR generation and encode
element index + explicit scale in the IR.  Effective address is
base + index*scale + disp (disp = symbolOffset).
This commit is contained in:
Irmen de Jong committed 2026-09-02 00:34:23 +02:00
1 parent 60eeaba791
commit 59c41ad129
12 files changed
+303 -248

No files matched your search

@@ -441,9 +441,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
fun loadIndex(): Int {
val tr = exprGen.translateExpression(array.index)
addToResult(result, tr, tr.resultReg, -1)
val (indexReg, indexDt) = normalizeArrayIndex(result, tr)
if(!array.splitWords && eltSize>1)
result += codeGen.multiplyByConst(indexDt, indexReg, eltSize)
val (indexReg, _) = normalizeArrayIndex(result, tr)
return indexReg
}
@@ -517,9 +515,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
val indexReg = loadIndex()
val register = codeGen.registers.next(vmDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName)
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize)
it += IRInstruction(Opcode.NEG, vmDt, reg1 = register)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize)
}
}
}
@@ -531,9 +529,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
val indexReg = loadIndex()
val register = codeGen.registers.next(vmDt)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName)
it += IRInstruction(Opcode.LOADX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize)
it += IRInstruction(Opcode.INV, vmDt, reg1 = register)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName)
it += IRInstruction(Opcode.STOREX, vmDt, reg1 = register, reg2 = indexReg, labelSymbol = arrayVariableName, scale = eltSize)
}
}
}
@@ -786,6 +784,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
} else {
val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, true, targetArray.splitWords)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexReg), null)
}
codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), true, -1)
@@ -796,6 +796,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
} else {
val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, true, targetArray.splitWords)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexReg), null)
}
val realValueReg = if(targetDt == IRDataType.FLOAT) valueFpRegister else valueRegister
@@ -829,13 +831,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
} else {
val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, codeGen.wordArrayIndex, targetArray.splitWords)
result += code
val scaleZX = if(targetArray.splitWords) 1 else eltSize
result += IRCodeChunk(null, null).also {
if(targetArray.splitWords) {
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, labelSymbol = variable+"_lsb")
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1 = indexReg, labelSymbol = variable+"_msb")
}
else
it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable)
it += IRInstruction(Opcode.STOREZX, targetDt, reg1=indexReg, labelSymbol = variable, scale = scaleZX)
}
}
} else {
@@ -850,7 +853,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, codeGen.wordArrayIndex, targetArray.splitWords)
result += code
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = RegisterNum(valueFpRegister), labelSymbol = variable)
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = indexReg, fpReg1 = RegisterNum(valueFpRegister), labelSymbol = variable, scale = eltSize)
}
}
} else {
@@ -879,7 +882,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1 = lsbmsbReg, reg2=indexReg, immediate = arrayLength, labelSymbol = "${variable}_msb")
}
else
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable)
it += IRInstruction(Opcode.STOREX, targetDt, reg1 = valueRegister, reg2=indexReg, labelSymbol = variable, scale = eltSize)
}
}
}
@@ -918,6 +921,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
// index is an expression
val (code, indexReg) = codeGen.loadIndexReg(targetArray.index, eltSize, true, targetArray.splitWords)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerReg, reg2 = indexReg), null)
if(zero) {
addInstr(result, IRInstruction(Opcode.STOREZI, targetDt, reg1 = pointerReg, immediate = 0), null)
@@ -5,6 +5,7 @@ import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.BaseDataType
import prog8.code.core.DataType
import prog8.intermediate.*
@@ -872,6 +873,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1 = pointerTr.resultReg, reg2 = indexWordReg), null)
if(elementByteOffset > 0)
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.POINTER, reg1 = pointerTr.resultReg, immediate = elementByteOffset), null)
@@ -940,12 +943,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
val indexDt = if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE
result += IRCodeChunk(null, null).also {
val offset = byteOffset(eltSize)
if(offset > 0)
it += IRInstruction(Opcode.ADD, indexDt, reg1 = indexReg, immediate = offset)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = targetVariable.name)
it += IRInstruction(Opcode.STOREZX, IRDataType.BYTE, reg1=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize)
}
}
} else {
@@ -961,12 +961,9 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
} else {
val (code, indexReg) = codeGen.loadIndexReg(target.index, eltSize, codeGen.wordArrayIndex, false)
result += code
val indexDt = if(codeGen.wordArrayIndex) IRDataType.WORD else IRDataType.BYTE
result += IRCodeChunk(null, null).also {
val offset = byteOffset(eltSize)
if(offset > 0)
it += IRInstruction(Opcode.ADD, indexDt, reg1 = indexReg, immediate = offset)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = targetVariable.name)
it += IRInstruction(Opcode.STOREX, IRDataType.BYTE, reg1=valueTr.resultReg, reg2=indexReg, labelSymbol = targetVariable.name, symbolOffset = if(offset!=0) offset else null, scale = eltSize)
}
}
}
@@ -647,13 +647,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun indexByExpression() {
val (code, indexReg) = codeGen.loadIndexReg(arrayIx.index, eltSize, codeGen.wordArrayIndex, arrayIx.splitWords)
result += code
val scale = if(arrayIx.splitWords) 1 else eltSize
if(vmDt==IRDataType.FLOAT) {
resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpRegister), reg1=indexReg, labelSymbol = arrayVarSymbol, scale = scale), null)
}
else {
resultRegister = codeGen.registers.next(vmDt)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexReg, labelSymbol = arrayVarSymbol), null)
addInstr(result, IRInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=indexReg, labelSymbol = arrayVarSymbol, scale = scale), null)
}
}
@@ -682,6 +683,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else {
val (code, indexWordReg) = codeGen.loadIndexReg(index, eltSize, true, false)
result += code
if(eltSize!=1)
result += codeGen.multiplyByConst(DataType.UWORD, indexWordReg, eltSize)
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.POINTER, reg1=pointerReg, reg2=indexWordReg), null)
}
@@ -1885,6 +1888,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
} 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)
@@ -774,19 +774,18 @@ class IRCodeGen(
else -> {
val arrElementDt = iterable.type.elementType()
val elementSize = program.memsizer.memorySize(arrElementDt, null)
val lengthBytes = iterableLength!! * elementSize
val arrElementIR = irType(arrElementDt)
addInstr(result, IRInstruction(Opcode.LOAD, indexRegType, reg1=indexReg, immediate = 0), null)
val loopChunk = IRCodeChunk(loopLabel, null)
loopChunk += IRInstruction(Opcode.LOADX, arrElementIR, reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
loopChunk += IRInstruction(Opcode.LOADX, arrElementIR, reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name, scale = elementSize)
val (storeReg, storeDt) = emitWidening(loopChunk, tmpReg, arrElementIR)
loopChunk += IRInstruction(Opcode.STOREM, storeDt, reg1=storeReg, labelSymbol = loopvarSymbol)
result += loopChunk
result += translateNode(forLoop.statements)
result += addConstToReg(indexReg, elementSize, indexRegType)
result += addConstToReg(indexReg, 1, indexRegType)
result += IRCodeChunk(null, null).also {
if(lengthBytes!=256 || indexRegType==IRDataType.WORD || elementSize!=1)
it += IRInstruction(Opcode.CMPI, indexRegType, reg1=indexReg, immediate = lengthBytes)
if(iterableLength!=256 || indexRegType==IRDataType.WORD)
it += IRInstruction(Opcode.CMPI, indexRegType, reg1=indexReg, immediate = iterableLength!!)
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
}
}
@@ -2251,7 +2250,7 @@ class IRCodeGen(
}
indexReg = newReg
}
result += multiplyByConst(DataType.UWORD, indexReg, itemsize)
// no pre-scaling - scale is encoded in the LOADX/STOREX instruction
return Pair(result, indexReg)
}
@@ -2279,10 +2278,7 @@ class IRCodeGen(
indexReg = newReg
}
if(itemsize==1 || arrayIsSplitWords)
return Pair(result, indexReg)
result += multiplyByConst(if(indexRegType == IRDataType.WORD) DataType.UWORD else DataType.UBYTE, indexReg, itemsize)
// no pre-scaling - scale is encoded in the LOADX/STOREX instruction
return Pair(result, indexReg)
}
@@ -1,5 +1,6 @@
package prog8.codegen.m68k
import prog8.code.core.CpuType
import prog8.intermediate.CallingConventionSlot
import prog8.intermediate.IRDataType
import prog8.intermediate.IRInstruction
@@ -83,10 +84,28 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore
Opcode.LOADX -> {
val dst = r1 ?: error("LOADX needs reg1")
val idx = r2 ?: error("LOADX needs reg2")
val scale = insn.scale
loadIndexToD0(idx)
if(scale!=1) {
if(program.options.compTarget.cpu >= CpuType.M68020) {
if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0")
} else {
when(scale) {
2 -> emitLine("add.w d0,d0")
4 -> emitLine("lsl.w #2, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
}
emitLine("lea $target, a0")
val sx = dtSuffix(type)
emitLine("move$sx (a0,d0.w), d0")
val indexMode = when {
program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)"
else -> "(a0,d0.w)"
}
emitLine("move$sx $indexMode, d0")
emitLine("move$sx d0, ${regAddr(dst)}")
}
@@ -130,11 +149,29 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore
Opcode.STOREX -> {
val value = r1 ?: error("STOREX needs reg1")
val idx = r2 ?: error("STOREX needs reg2")
val scale = insn.scale
loadIndexToD0(idx)
if(scale!=1) {
if(program.options.compTarget.cpu >= CpuType.M68020) {
if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0")
} else {
when(scale) {
2 -> emitLine("add.w d0,d0")
4 -> emitLine("lsl.w #2, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
}
emitLine("lea $target, a0")
val sx = dtSuffix(type)
emitLine("move$sx ${regAddr(value)}, d1")
emitLine("move$sx d1, (a0,d0.w)")
val indexMode = when {
program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)"
else -> "(a0,d0.w)"
}
emitLine("move$sx d1, $indexMode")
}
Opcode.STOREZM -> {
@@ -158,9 +195,27 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction, suppressRegfileStore
Opcode.STOREZX -> {
val idx = r1 ?: error("STOREZX needs reg1")
val scale = insn.scale
loadIndexToD0(idx)
if(scale!=1) {
if(program.options.compTarget.cpu >= CpuType.M68020) {
if(scale!=2 && scale!=4 && scale!=8) emitLine("muls.w #$scale, d0")
} else {
when(scale) {
2 -> emitLine("add.w d0,d0")
4 -> emitLine("lsl.w #2, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
}
emitLine("lea $target, a0")
emitLine("clr$s (a0,d0.w)")
val indexMode = when {
program.options.compTarget.cpu >= CpuType.M68020 && scale==2 -> "(a0,d0.w*2)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==4 -> "(a0,d0.w*4)"
program.options.compTarget.cpu >= CpuType.M68020 && scale==8 -> "(a0,d0.w*8)"
else -> "(a0,d0.w)"
}
emitLine("clr$s $indexMode")
}
Opcode.STOREHR -> {
@@ -236,12 +291,21 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction, target: String,
Opcode.STOREZX -> {
val idx = r1 ?: error("STOREZX.f needs reg1 (index)")
val scale = insn.scale
// full-width d0.l indexing requires zero-extending the word index to 32 bits
emitLine("moveq #0, d0")
emitLine("move.w ${regAddr(idx)}, d0")
if(scale!=1) {
when(scale) {
2 -> emitLine("add.l d0,d0")
4 -> emitLine("lsl.l #2, d0")
8 -> emitLine("lsl.l #3, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
emitLine("lea $target, a0")
emitLine("fmovecr #\$0f, fp0")
emitLine("fmove.s fp0, (0, a0, d0.l)", "index pre-scaled")
emitLine("fmove.s fp0, (0, a0, d0.l)")
}
Opcode.STOREHFACZERO -> {
@@ -295,11 +359,20 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction, target: String,
Opcode.LOADX -> {
val idx = r1 ?: error("LOADX.f needs reg1 (index)")
val scale = insn.scale
// full-width d0.l indexing requires zero-extending the word index to 32 bits
emitLine("moveq #0, d0")
emitLine("move.w ${regAddr(idx)}, d0")
if(scale!=1) {
when(scale) {
2 -> emitLine("add.l d0,d0")
4 -> emitLine("lsl.l #2, d0")
8 -> emitLine("lsl.l #3, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
emitLine("lea $target, a0")
emitLine("fmove.s (0, a0, d0.l), $FP_ACC", "index pre-scaled")
emitLine("fmove.s (0, a0, d0.l), $FP_ACC")
emitLine("fmove.s $FP_ACC, ${floatRegFileAddr(fp1)}")
}
@@ -326,12 +399,21 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction, target: String,
Opcode.STOREX -> {
val idx = r1 ?: error("STOREX.f needs reg1 (index)")
val scale = insn.scale
// full-width d0.l indexing requires zero-extending the word index to 32 bits
emitLine("moveq #0, d0")
emitLine("move.w ${regAddr(idx)}, d0")
if(scale!=1) {
when(scale) {
2 -> emitLine("add.l d0,d0")
4 -> emitLine("lsl.l #2, d0")
8 -> emitLine("lsl.l #3, d0")
else -> emitLine("muls.w #$scale, d0")
}
}
emitLine("lea $target, a0")
emitLine("fmove.s ${floatRegFileAddr(fp1)}, $FP_ACC")
emitLine("fmove.s $FP_ACC, (0, a0, d0.l)", "index pre-scaled")
emitLine("fmove.s $FP_ACC, (0, a0, d0.l)")
}
Opcode.STOREHR -> {
@@ -71,6 +71,8 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) {
Opcode.LOADX -> {
val idxReg = r2 ?: error("LOADX needs reg2")
val baseAddress = resolveAddress(addr, label, offset)
val scale = insn.scale
scaleIndexRegIfNeeded(idxReg, scale)
indexedLoad(r1 ?: error("LOADX needs reg1"), idxReg, baseAddress, type)
}
@@ -92,6 +94,8 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) {
Opcode.STOREX -> {
val r2val = r2 ?: error("STOREX needs reg2")
val target = resolveAddress(addr, label, offset)
val scale = insn.scale
scaleIndexRegIfNeeded(r2val, scale)
storeExchange(r1 ?: error("STOREX needs reg1"), r2val, target, type)
}
@@ -113,7 +117,9 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) {
Opcode.STOREZX -> {
val target = resolveAddress(addr, label, offset)
zeroMemoryIndexed(r1 ?: error("STOREZX needs reg1"), target, type)
val scale = insn.scale
scaleIndexRegIfNeeded(r1 ?: error("STOREZX needs reg1"), scale)
zeroMemoryIndexed(r1, target, type)
}
Opcode.STOREHR -> {
@@ -247,6 +253,102 @@ internal fun AsmGen.translateLoadStore(insn: IRInstruction) {
}
}
private fun AsmGen.scaleIndexRegIfNeeded(idxReg: Int, scale: Int) {
if(scale==1) return
when(scale) {
2 -> {
emitLine("asl ${regAddrLo(idxReg)}")
}
4 -> {
emitLine("asl ${regAddrLo(idxReg)}")
emitLine("asl ${regAddrLo(idxReg)}")
}
else -> {
// generic scale fallback: use the existing 8-bit multiply helper (A * Y -> A)
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("ldy #$scale")
emitLine("jsr prog8_lib.multiply_bytes")
emitLine("sta ${regAddrLo(idxReg)}")
}
}
}
private fun AsmGen.emitFloatIndexScaled(idxReg: Int, scale: Int, ptr: String) {
// index is element index; scale by element size (float = 5 or 8, etc.) then add to ptr
if(scale==1) {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
return
}
// generic scale: idx*scale -> A, then add to ptr
// use simple multiply for small scales; for float Mflpt 5 we do *5 = *4+*1
when(scale) {
2 -> {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("asl a")
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
}
4 -> {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("asl a")
emitLine("asl a")
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
}
5 -> {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("sta P8ZP_SCRATCH_REG") // temp
emitLine("asl a")
emitLine("asl a") // *4
emitLine("clc")
emitLine("adc P8ZP_SCRATCH_REG") // *5
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
}
8 -> {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("asl a")
emitLine("asl a")
emitLine("asl a")
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
}
else -> {
emitLine("lda ${regAddrLo(idxReg)}")
emitLine("ldy #$scale")
emitLine("jsr prog8_lib.multiply_bytes")
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
}
}
}
private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) {
val fpReg1 = insn.fpReg1
val fpReg2 = insn.fpReg2
@@ -374,19 +476,14 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) {
val idxReg = r1 ?: error("LOADX.f needs reg1 (index)")
val baseAddress = resolveAddress(addr, label, offset)
val fpReg = fpReg1 ?: error("LOADX.f needs fpReg1")
val scale = insn.scale
val ptr = ZP_TEMP
emitLine("lda #<$baseAddress")
emitLine("sta $ptr")
emitLine("lda #>$baseAddress")
emitLine("sta ${ptr}+1")
emitLine("lda ${regAddrLo(idxReg)}")
// Note: index is already pre-multiplied by 5 (element size) in the IR codegen.
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
// index is element index; scale by element size via helper
emitFloatIndexScaled(idxReg, scale, ptr)
emitLine("lda $ptr")
emitLine("ldy ${ptr}+1")
emitLine("jsr floats.MOVFM")
@@ -439,19 +536,13 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) {
val idxReg = r1 ?: error("STOREX.f needs reg1 (index)")
val baseAddress = resolveAddress(addr, label, offset)
val fpReg = fpReg1 ?: error("STOREX.f needs fpReg1")
val scale = insn.scale
val ptr = ZP_TEMP
emitLine("lda #<$baseAddress")
emitLine("sta $ptr")
emitLine("lda #>$baseAddress")
emitLine("sta ${ptr}+1")
emitLine("lda ${regAddrLo(idxReg)}")
// Note: index is already pre-multiplied by 5 (element size) in the IR codegen.
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
emitFloatIndexScaled(idxReg, scale, ptr)
emitLine("lda #<${fpRegAddr(fpReg.value)}")
emitLine("ldy #>${fpRegAddr(fpReg.value)}")
emitLine("jsr floats.MOVFM")
@@ -496,19 +587,13 @@ private fun AsmGen.translateFloatLoadStore(insn: IRInstruction) {
Opcode.STOREZX -> {
val idxReg = r1 ?: error("STOREZX.f needs reg1 (index)")
val baseAddress = resolveAddress(addr, label, offset)
val scale = insn.scale
val ptr = ZP_TEMP
emitLine("lda #<$baseAddress")
emitLine("sta $ptr")
emitLine("lda #>$baseAddress")
emitLine("sta ${ptr}+1")
emitLine("lda ${regAddrLo(idxReg)}")
// Note: index is already pre-multiplied by 5 (element size) in the IR codegen.
emitLine("clc")
emitLine("adc $ptr")
emitLine("sta $ptr")
emitLine("bcc +")
emitLine("inc ${ptr}+1")
emitLabel("+")
emitFloatIndexScaled(idxReg, scale, ptr)
val n = floatMemSize - 1
emitLine("ldy #$n")
emitLine("lda #0")
+5 -5
View File
@@ -285,7 +285,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
ExampleSizes("maze", c64SizeOptimized=3082, c64SizeUnoptimized=3269, cx16SizeOptimized=2862, cx16SizeUnoptimized=3243),
ExampleSizes("mandelbrot", c64SizeOptimized=1173, c64SizeUnoptimized=1213, cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
ExampleSizes("mandelbrot-gfx", c64SizeOptimized=1492, c64SizeUnoptimized=1514, cx16SizeOptimized=957, cx16SizeUnoptimized=1250),
ExampleSizes("multitasking", c64SizeOptimized=1900, c64SizeUnoptimized=1955, cx16SizeOptimized=1812, cx16SizeUnoptimized=2137),
ExampleSizes("multitasking", c64SizeOptimized=1900, c64SizeUnoptimized=1955, cx16SizeOptimized=1804, cx16SizeUnoptimized=2137),
ExampleSizes("numbergame", c64SizeOptimized=1034, c64SizeUnoptimized=1050, cx16SizeOptimized=1079, cx16SizeUnoptimized=1382),
ExampleSizes("primes", c64SizeOptimized=520, c64SizeUnoptimized=541, cx16SizeOptimized=565, cx16SizeUnoptimized=870),
ExampleSizes("queens", c64SizeOptimized=1053, c64SizeUnoptimized=1085, cx16SizeOptimized=1103, cx16SizeUnoptimized=1419),
@@ -330,17 +330,17 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val onlyVirtual = cartesianProduct(
listOf(
ExampleSizes("bouncegfx", virtualInstrCountOptimized=227, virtualInstrCountUnoptimized=756, virtualRegCountOptimized=90, virtualRegCountUnoptimized=325),
ExampleSizes("bouncegfx", virtualInstrCountOptimized=219, virtualInstrCountUnoptimized=748, virtualRegCountOptimized=90, virtualRegCountUnoptimized=325),
ExampleSizes("bsieve", virtualInstrCountOptimized=262, virtualInstrCountUnoptimized=1900, virtualRegCountOptimized=74, virtualRegCountUnoptimized=800),
ExampleSizes("fountain", virtualInstrCountOptimized=207, virtualInstrCountUnoptimized=738, virtualRegCountOptimized=80, virtualRegCountUnoptimized=318),
ExampleSizes("fountain", virtualInstrCountOptimized=198, virtualInstrCountUnoptimized=731, virtualRegCountOptimized=80, virtualRegCountUnoptimized=318),
ExampleSizes("pixelshader", virtualInstrCountOptimized=93, virtualInstrCountUnoptimized=218, virtualRegCountOptimized=17, virtualRegCountUnoptimized=60),
ExampleSizes("sincos", virtualInstrCountOptimized=334, virtualInstrCountUnoptimized=834, virtualRegCountOptimized=148, virtualRegCountUnoptimized=357),
ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=450, virtualInstrCountUnoptimized=2089, virtualRegCountOptimized=158, virtualRegCountUnoptimized=882),
ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=670, virtualInstrCountUnoptimized=2259, virtualRegCountOptimized=264, virtualRegCountUnoptimized=968),
ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=696, virtualInstrCountUnoptimized=2260, virtualRegCountOptimized=290, virtualRegCountUnoptimized=987),
ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=690, virtualInstrCountUnoptimized=2254, virtualRegCountOptimized=290, virtualRegCountUnoptimized=987),
ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=419, virtualInstrCountUnoptimized=2422, virtualRegCountOptimized=133, virtualRegCountUnoptimized=1034),
ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=236, virtualInstrCountUnoptimized=771, virtualRegCountOptimized=88, virtualRegCountUnoptimized=320),
ExampleSizes("pointers/sorting", virtualInstrCountOptimized=497, virtualInstrCountUnoptimized=2211, virtualRegCountOptimized=159, virtualRegCountUnoptimized=937)
ExampleSizes("pointers/sorting", virtualInstrCountOptimized=479, virtualInstrCountUnoptimized=2189, virtualRegCountOptimized=159, virtualRegCountUnoptimized=937)
),
listOf(false, true)
)
+1 -2
View File
@@ -5,7 +5,7 @@ Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- Consider extending ``var xx = <value>`` type inference (recently added for for-loop counter variables) to all variable declarations. Investigate pros/cons: desired syntax (``var x = expr`` vs ``var x := expr``), interaction with existing ``ubyte``/``uword``/``word`` inference defaults, const vs var, scope and shadowing, error messages for ambiguous types, documentation, impact on block/sub scope, and whether ``var`` without initializer should be allowed.
- DataType.ARRAY_POINTER depends on the compilation target to be either a split word array or not. This is horrible because now we have to check with the compilation target everywhere to see if a DataType enumeration value is split word array, and PtVariable and PtArrayIndexer need an explicit boolean to tell us if this is the case. See ideas/remove_array_pointer_plan.md for the plan.
- m68k codegen: make use of scaling factors in the indexed instructions on 68020+ ? see ideas/better-addressing-IR.md
- struct-array field access: fold ``arr[i].field`` (``arr:Struct[Sz]``) from ``EXT+MUL #Sz + ADDR + ADD #fieldOff + LOADI`` (5 IR ops) to single ``LOADX/STOREX arr+fieldOff,S=Sz`` — reuses existing ``,S=N`` + ``+off`` encoding; see ideas/better-addressing-IR.md (deferred part)
- split up AssignmentAsmGen.kt in codeGenCpu6502 it is by far the largest file 6000+ lines
- make enums strongly typed instead of just syntactic sugar for ints (see ideas/enum-strong-type.md for the plan)
- extend the ``-gendoc`` command to generate user reference documentation from Markdown docstrings; see ``ideas/markdown-docstrings-and-reference-docs.md`` for the plan.
@@ -44,7 +44,6 @@ Romable (%option romable)
IR/VM
^^^^^
- encode indexed scaling into IR (so that m68k codegen can use scale factor addressing) see ideas/better-addressing-IR.md
- improve IR loop handling to emit ``m68k`` ``dbra`` directly without ``p8_regfile`` peephole see ideas/better-loop-IR.md
**Missing VM Implementations (VirtualMachine.kt)**
+18 -157
View File
@@ -1,171 +1,32 @@
# Better Addressing IR: Scaled Indexing, Struct Fields and Pointer Access
# Better Addressing IR — Deferred: Struct-Array Field Folding
This document generalizes the scaled-indexing proposal to also cover struct field and pointer access, so the `m68k` backend can use its full addressing modes (`(An)`, `(An,d16)`, `(An,Xn*scale,d16)`) while `new6502`/`VM` stay optimal.
> Core scaled indexing (`IRInstruction.scale` / `,S=N`, `VM` and backends `virtual`/`qemu68k`/`amiga500`/`new6502`, removal of `loadIndexReg` pre-scaling) was implemented 2026-09-02. This document now only tracks the deferred optimization.
---
## Remaining: `arr[i].field` single-LOADX
## 1. Current State
`arr:Struct[Sz]` currently lowers as 5 IR ops:
**Array index pre-scaling:** `loadIndexReg` (`codeGenIntermediate/IRCodeGen.kt:2228`) multiplies the index by `itemsize` (`:2243`/`2263` `multiplyByConst`). Consumers assume byte offset:
`EXT+MUL #Sz` (`ExpressionGen.kt:183`) → `ADDR rBase,rIdx` → `ADD #fieldOff` → `LOADI 0`
- `VM` `InsLOADX/STOREX` `VirtualMachine.kt:590/666` adds raw index
- `new6502` `indexedLoad` `InstrLoadStore.kt:675`
- `m68k` `(a0,d0.w)` `codeGenM68k/InstrLoadStore.kt` ("index pre-scaled")
Blocks `68020` `(An,Xn*2/*4)`.
**Struct fields:** always constant byte offset, not scaled:
- `ptr->field` `LOADI/STOREI immediate=fieldOffset` `ExpressionGen.kt:1915` `AssignmentGen.kt:354` (`reg1,(reg2+off)`)
- `var.field` `LOADM/STOREM symbolOffset`
- `arr[i].field` where `arr:Struct[Sz]` is pointer arithmetic: `EXT+MUL #Sz` `ExpressionGen.kt:183` `ADDR rBase,rIdx` `ADD #fieldOff` `LOADI 0` - 5 `IR` ops for one field, cannot fold to `LOADX arr+fieldOff,S=Sz`.
**Pointer arithmetic:** `ptr[i]` `ExpressionGen.kt:681` and `arr[i].field` above keep explicit `MUL` for real address calc, not `scale`.
---
## 2. Design Decision
**`IR` never pre-scales; effective address is always `base + index*scale + disp` where `disp = labelSymbolOffset + fieldOffset` (both constant).**
- `LOADX/STOREX/STOREZX` carry explicit `scale:Int=1` (`B/W/L`), `index` holds **element index** (or `struct` index), `disp` holds `symbolOffset` (+ field offset when applicable). `setlsb/setmsb` `BuiltinFuncGen.kt:934` uses `scale=eltSize`, `disp=byteOff` to express `b @ arr[i]` in `W/L` array (scale from elt size, value `b`).
- **Simple struct field** `ptr->field` stays `LOADI/STOREI disp=fieldOff` (`disp` already optimal `4(a0)` on `m68k`). No new opcode, but document as addressing mode.
- **Struct-array field** `arr[i].field` becomes **one** `LOADX/STOREX` with `base=arr`, `scale=structSize`, `disp=fieldOffset` (`loadx.b r5,r3,arr+4,S=12` for `MyStruct[12].x@4`), not `MUL+ADDR+LOADI`. This reuses `,S=N` + existing `+off` parsing (`Utils.kt:248`) already used for `arr+fieldOff`.
- **Pointer arithmetic** `ptr[i]` where `ptr` is `^^T` stays explicit `MUL`+`ADDR` (true address calc, not array `base`), but when `ptr` is known array symbol `arr` the above `LOADX` form is preferred. No change for generic `ptr[i]`.
No `F=field` metadata - `disp` alone is optimal for `m68k` `(a0,off)`; struct name only needed for future value `Enemy e` `DataTypes.kt:22` `STRUCT_INSTANCE`, kept as future `HLIR` note.
---
## 3. IR Representation
Add `scale:Int=1` to `IRInstruction` (`intermediate/IRInstructions.kt:918`), `scale!=1` only on `LOADX/STOREX/STOREZX`, `scale>=1`.
Serialization `,S=N` always for those three, composes with `+disp`:
Goal: fold to one indexed op reusing the already-implemented `,S=N` + `+off` encoding:
```
loadx.w r5,r2,arr,S=2
loadx.b r5,r3,arr+4,S=12 // arr[i].x struct 12, field 4
storx.b r1,r2,floatarray+1,S=5 // setlsb
loadi.b r5,r2,4 // ptr->field disp 4
loadx.b r5,r3,arr+4,S=12 // arr[i].x struct 12, field 4 (disp=fieldOff, scale=structSize)
storx.b r1,r2,arr+4,S=12
```
`Utils.parseIRCodeLine` strips optional `,S=N`, defaults `1`; `IRFileWriter` round-trips via `toString()`.
* `labelSymbol` = `arr`, `symbolOffset` = `fieldOffset` (`+4`), `scale` = `structSize` (`S=12`)
* Simple `ptr->field` stays `LOADI/STOREI disp=fieldOff` (already optimal `4(a0)` on m68k)
* Generic `ptr[i]` where `ptr:^^T` stays explicit `MUL+ADDR` — only fold when base is a known array symbol (`PtIdentifier` of array-typed variable / struct-array symbol); check `deref.startpointer` type in `ExpressionGen.kt` (9.3)
---
## Where to change
## 4. Scale Values by Target
* `ExpressionGen.kt:183/1863`, `AssignmentGen.kt:759` — detect `Struct[]` symbol + field chain, emit `LOADX/STOREX base=arr+fieldOff,S=structSize` instead of `MUL+ADDR+LOADI`
* Pointer paths `ExpressionGen.kt:681` keep explicit `MUL`
* Backends already handle `,S=N`+`+off` (`m68k` → `move.b 4(a0,d0.w*4),d0`, `new6502` → `asl` helper, `VM` → `base+index*scale+offset`) — no further backend work needed; this just enables the single-op form
| type | cx16/c64/pet32 | m68k/qemu68k | VM |
|------|----------------|--------------|----|
| byte/bool |1|1|1|
| split word|1|1|1|
| word|2|2|2|
| pointer|2 (split 1)|4|4|
| long|4|4|4|
| float|5(Mflpt5)|4|8|
| struct|structSize|structSize|structSize|
## Implementation notes (from original §9)
`6502` `ARRAY_SIZE_LIMIT 256` byte budget `AstChecker.kt:1107` ensures `scaled` fits `byte`; `m68k` `scale 2/4` fits `d0.w*scale`; `68020` `*1/*2/*4` (`*8` via `.l`).
**9.3 Array symbol vs generic pointer:** only fold known array symbols; keep `MUL+ADDR+LOADI` for `PtPointerVariable` / `^^T` results.
---
## 5. Changes by Module
### 5.1 `intermediate`
Add `scale`, `,S=` parsing/validation, update `IRInstructions.kt:724` `IRProgram.kt:318`.
### 5.2 `codeGenIntermediate` - stop pre-scaling, fold struct-array field
`loadIndexReg` `IRCodeGen.kt:2228` delete `multiplyByConst` at `:2243/:2263`, keep `EXT` for `m68k` word index, drop `itemsize` param.
Array sites emit `scale`: `ExpressionGen.kt:646`, `AssignmentGen.kt:802/822/843`, `for` loop `IRCodeGen.kt:774` (`+1` inc, compare `len` not `len*eltSize`), `BuiltinFuncGen.kt:934` (`scale=eltSize`+`disp`).
**Struct-array field** `ExpressionGen.kt:183/1863` `AssignmentGen.kt:759`: if `arr` is `Struct[]` symbol + field chain, emit single `LOADX/STOREX base=arr+fieldOff,S=structSize` instead of `MUL+ADDR+LOADI`. Simple `ptr->field` unchanged `LOADI disp`.
Pointer paths `ExpressionGen.kt:681` keep explicit `MUL` (real `^^^^`).
### 5.3 `virtualmachine`
`InsLOADX:590/STOREX:666/STOREZX:723` `base+index*scale+offset` (`offset` via `VmProgramLoader.kt:177`), `reg2`/`reg1` per `IRInstructions.kt:728`.
### 5.4 `codeGenM68k`
`InstrLoadStore.kt` after `d0` load: `qemu68k` `(a0,d0.w*N)` / `(0,a0,d0.w*N)` + `disp` via `resolveAddress` `(B,a0,d0.w*N)`; `amiga500` `lsl.w #1`/`lsl.l #2`; struct-array field now one `m68k` `move.b 4(a0,d0.w*4),d0` instead of 3 insns.
### 5.5 `codeGenNew6502`
`InstrLoadStore.kt:675/753/845` helper `if(scale==2) asl else if(4) asl;asl` - struct-array field `arr+fieldOff` `base` via `resolveAddress`, `scale` as for word arrays; `LOADI disp` stays `(ptr),y`.
---
## 6. Out of Scope
`codeGenCpu6502` old backend unaffected. `6502` float `LOADX` `TODO`. Pointer `ptr[i]` generic keeps `MUL`. `for` loop `BYTE INC` on `m68k` check `INC` width for >255 `len`.
---
## 7. Tests
`intermediate` `,S=` round-trip; `qemu68k` `arr[i].x` asserts `(a0,d0.w*4+4)`; `amiga500` `lsl`; `new6502` `asl` shift; `VM` word/long/struct-array field with `scale+disp`.
---
## 8. File Map
| Concern | File |
|---------|------|
| IR def, `,S=` | `intermediate/IRInstructions.kt` `Utils.kt` `IRProgram.kt:318` |
| `loadIndexReg`, `for` | `codeGenIntermediate/IRCodeGen.kt` |
| array/struct-array field | `codeGenIntermediate/ExpressionGen.kt:646/183` `AssignmentGen.kt:759/802` |
| `setlsb` | `BuiltinFuncGen.kt:934` |
| VM | `VirtualMachine.kt:590` `VmProgramLoader.kt:177` |
| m68k | `codeGenM68k/InstrLoadStore.kt` `AsmGen.kt:97/233` |
| new6502 | `codeGenNew6502/InstrLoadStore.kt:675` |
| limits | `compiler/AstChecker.kt:1107` |
---
## 9. Implementation Notes for Subagents
### 9.1 `,S=` and `+offset` ordering
`+offset` is parsed into `IRInstruction.symbolOffset` (`labelSymbolOffset`). `,S=N` is a separate `scale` field.
For `arr[i].field`:
- `labelSymbol` = `arr`
- `symbolOffset` = `fieldOffset` (the `+4`)
- `scale` = `structSize` (the `,S=12`)
`parseIRCodeLine` should strip the `,S=N` suffix **after** extracting the operand list, then assign the remaining `arr+4` to `labelSymbol` + `symbolOffset` exactly as today.
### 9.2 `STOREZX` operand order
`STOREZX` (`store zero-extending`) uses the same register convention as `STOREX`: `reg1` is the value register, `reg2` is the index register, and the memory location is `address/labelSymbol + symbolOffset + reg2*scale`.
For float arrays, follow the existing `LOADX` convention: `reg1` is the index, `fpReg1` is the value. Do not introduce new operand mappings.
### 9.3 Array symbol `arr[i]` vs generic pointer `ptr[i]`
Only emit the new `LOADX/STOREX,S=` form when the base is a **known array variable symbol** (`PtIdentifier` of an array-typed variable) or a struct-array symbol.
If the base is a `PtPointerVariable` or the result of pointer arithmetic (`^^T`, address taken, returned pointer, etc.), keep the existing `MUL+ADDR+LOADI` sequence. The decision point is in `ExpressionGen.kt` when translating the array indexer / pointer deref: check `deref.startpointer` type - array symbol -> `LOADX,S=`; pointer expression -> explicit multiply.
### 9.4 Non-power-of-2 scales on `new6502`
For `scale` values that are not 1, 2, or 4, fall back to an explicit multiply of the index before indexing. Example for `scale=5` (Mflpt5 float array on 6502):
```
ldx index
lda #5
jsr multiply_x_by_a ; or inline multiply
sta scaledIndex
; then index with scaledIndex as byte offset
```
Do not silently truncate or use repeated `asl` for scale 3/5/etc.
### 9.5 VM `wordArrayIndex` flag and scaling
In `VirtualMachine.kt`, `wordArrayIndex` selects whether the index register is read as byte or word. Compute the effective address as:
```kotlin
val index = if (wordArrayIndex) registers.getUW(i.reg2!!).toUInt() else registers.getUB(i.reg2!!).toUInt()
val base = i.address!!.value + (i.labelSymbolOffset?.toUInt() ?: 0u)
val effective = base + index * i.scale.toUInt()
```
Apply this same expression in `InsLOADX`, `InsSTOREX`, and `InsSTOREZX`. For float `LOADX`, use `i.reg1` as the index register and `fpReg1` as the destination.
**Original file map for this item:** `codeGenIntermediate/ExpressionGen.kt:646/183` `AssignmentGen.kt:759/802` `IRInstructions.kt` `Utils.kt` `IRProgram.kt:318` `VirtualMachine.kt:590` `VmProgramLoader.kt:177` `codeGenM68k/InstrLoadStore.kt` `codeGenNew6502/InstrLoadStore.kt:675` `AstChecker.kt:1107`.
@@ -107,7 +107,7 @@ All have type b or w or l or f.
load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction)
loadm reg1, address - load reg1 with value at memory address
loadi reg1, reg2, value - load reg1 with value in memory indirect, pointed to by reg2 + offsetvalue 0-65535 (often used to read a field from a pointer to a struct, or with offset=0 just straight from the pointer)
loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
loadx reg1, reg2, address [,S=scale] - load reg1 with value at effective address (address + offset) + reg2*scale (offset is symbolOffset, scale defaults to 1). Text form example: loadx.b r5,r2,arr,S=2 or loadx.b r5,r3,arr+4,S=12 for struct-array field. Index is element index (not byte offset): byte 0-255 on 8-bit targets, word 0-32767 on 32-bit targets. Scale encodes element size / struct size (1..65535).
loadr reg1, reg2 - load reg1 with value in register reg2, "reg1 = reg2"
loadhr reg1, slot - load cpu hardware register from calling convention slot (s0=A, s1=X, s2=Y, s3=AX, s4=AY, s5=XY, s6=FAC1, s7=FAC2) into reg1
loadhfaczero fpreg1 - load "cpu hardware register" fac0 into freg1.f
@@ -118,9 +118,9 @@ storem reg1, address - store reg1 at memory address
storei reg1, reg2, value - store reg1 in memory indirect, pointed to by reg2 + offsetvalue 0-65535 (often used to write a field from a pointer to a struct, or with offset=0 just straight to the pointer)
storeim value, address - store an immediate value (constant) at memory address (the constant goes in the value field, NOT via a register).
storezi reg1, value - store zero at memory pointed to by reg1 + offsetvalue 0-65535 (just like storei, but a shorthand to store a constant 0)
storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
storex reg1, reg2, address [,S=scale] - store reg1 at effective address (address + offset) + reg2*scale (same scaling as loadx). Text form: storx.b r1,r2,arr,S=2 . Index is element index.
storezm address - store zero at memory address
storezx reg1, address - store zero at memory address, indexed by value in reg1 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
storezx reg1, address [,S=scale] - store zero at effective address (address + offset) + reg1*scale (same scaling as loadx). Index is element index: byte 0-255 or word 0-32767. Text form: storezx.b r1,arr,S=4 .
storehr reg1, slot - store reg1 into cpu hardware register for calling convention slot (s0=A, s1=X, s2=Y, s3=AX, s4=AY, s5=XY, s6=FAC1, s7=FAC2)
storehfaczero fpreg1 - store fpreg1.f into "cpu register" fac0
storehfacone fpreg1 - store fpreg1.f into "cpu register" fac1
@@ -953,7 +953,8 @@ data class IRInstruction(
val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!)
private val symbolOffset: Int? = null, // offset to add on labelSymbol (used to index into an array variable)
var branchTarget: IRCodeChunkBase? = null, // Will be linked after loading in IRProgram.linkChunks()! This is the chunk that the branch labelSymbol points to.
val fcallArgs: FunctionCallArgs? = null // will be set for the CALL and SYSCALL instructions.
val fcallArgs: FunctionCallArgs? = null, // will be set for the CALL and SYSCALL instructions.
val scale: Int = 1 // scaling factor for LOADX/STOREX/STOREZX: effective address = base + index*scale + disp
) {
var extSubName: String? = null // optional external subroutine name (for asm comments mainly). NOT part of serialization.
// reg1 and fpreg1 can be IN/OUT/INOUT (all others are readonly INPUT)
@@ -1034,6 +1035,12 @@ data class IRInstruction(
require(immediate != null || immediateFp != null) { "missing immediate value" }
}
// address range is validated by MemoryAddress init block
require(scale >= 1) { "scale must be >=1, got $scale" }
if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX)) {
require(scale in 1..65535) { "scale out of range for $opcode: $scale" }
} else {
require(scale == 1) { "scale only allowed for LOADX/STOREX/STOREZX, got $scale for $opcode" }
}
reg1direction = format.reg1
reg2direction = format.reg2
@@ -1363,8 +1370,12 @@ data class IRInstruction(
if(labelSymbolOffset!=null)
append("+$labelSymbolOffset")
}
if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX) && scale != 1) {
if(isNotEmpty() && last() != ' ') append(',')
append("S=$scale")
}
}
if(last() == ',')
if(isNotEmpty() && last() == ',')
setLength(length - 1)
}.trimEnd()
}
+14 -3
View File
@@ -116,8 +116,19 @@ fun parseIRCodeLine(line: String): ParsedIRLine {
formats.getValue(type)
}
// parse the operands
val operands = if(rest.isBlank()) emptyList() else rest.split(",").map{ it.trim() }.toMutableList()
// parse the operands, detect optional ,S=N suffix for scaled indexing
val operandsRaw = if(rest.isBlank()) emptyList() else rest.split(",").map{ it.trim() }.toMutableList()
var scale = 1
val operands = operandsRaw.toMutableList()
if(opcode in setOf(Opcode.LOADX, Opcode.STOREX, Opcode.STOREZX) && operands.isNotEmpty()) {
val last = operands.last()
if(last.startsWith("S=", ignoreCase = true)) {
val scaleStr = last.substring(2)
scale = scaleStr.toIntOrNull() ?: throw IRParseException("invalid scale suffix $last")
if(scale < 1) throw IRParseException("scale must be >=1")
operands.removeAt(operands.lastIndex)
}
}
var reg1: Int? = null
var reg2: Int? = null
var reg3: Int? = null
@@ -258,7 +269,7 @@ fun parseIRCodeLine(line: String): ParsedIRLine {
}
}
return ParsedIRLine.Instruction(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address?.toAddress(), labelSymbol = labelSymbol, symbolOffset = offset))
return ParsedIRLine.Instruction(IRInstruction(opcode, type, reg1, reg2, reg3, fpReg1, fpReg2, immediateInt, immediateFp, address?.toAddress(), labelSymbol = labelSymbol, symbolOffset = offset, scale = scale))
}
private fun isRegisterName(oper: String): Boolean {
+18 -15
View File
@@ -653,18 +653,19 @@ class VirtualMachine(irProgram: IRProgram) {
}
private fun InsLOADX(i: IRInstruction) {
val scale = i.scale.toUInt()
when (i.type!!) {
IRDataType.FLOAT -> {
val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt()
registers.setFloat(i.fpReg1!!, memory.getFloat(i.address!!.value + index))
registers.setFloat(i.fpReg1!!, memory.getFloat(i.address!!.value + index * scale))
}
else -> {
val index = if(wordArrayIndex) registers.getUW(i.reg2!!).toUInt() else registers.getUB(i.reg2!!).toUInt()
when (i.type!!) {
IRDataType.BYTE -> registers.setUB(i.reg1!!, memory.getUB(i.address!!.value + index))
IRDataType.WORD -> registers.setUW(i.reg1!!, memory.getUW(i.address!!.value + index))
IRDataType.POINTER -> registers.setUL(i.reg1!!, memory.getUL(i.address!!.value + index))
IRDataType.LONG -> registers.setSL(i.reg1!!, memory.getSL(i.address!!.value + index))
IRDataType.BYTE -> registers.setUB(i.reg1!!, memory.getUB(i.address!!.value + index * scale))
IRDataType.WORD -> registers.setUW(i.reg1!!, memory.getUW(i.address!!.value + index * scale))
IRDataType.POINTER -> registers.setUL(i.reg1!!, memory.getUL(i.address!!.value + index * scale))
IRDataType.LONG -> registers.setSL(i.reg1!!, memory.getSL(i.address!!.value + index * scale))
IRDataType.FLOAT -> throw IllegalStateException("unreachable")
}
}
@@ -729,18 +730,19 @@ class VirtualMachine(irProgram: IRProgram) {
}
private fun InsSTOREX(i: IRInstruction) {
val scale = i.scale.toUInt()
when (i.type!!) {
IRDataType.FLOAT -> {
val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt()
memory.setFloat(i.address!!.value + index, registers.getFloat(i.fpReg1!!))
memory.setFloat(i.address!!.value + index * scale, registers.getFloat(i.fpReg1!!))
}
else -> {
val index = if(wordArrayIndex) registers.getUW(i.reg2!!).toUInt() else registers.getUB(i.reg2!!).toUInt()
when (i.type!!) {
IRDataType.BYTE -> memory.setUB(i.address!!.value + index, registers.getUB(i.reg1!!))
IRDataType.WORD -> memory.setUW(i.address!!.value + index, registers.getUW(i.reg1!!))
IRDataType.POINTER -> memory.setUL(i.address!!.value + index, registers.getUL(i.reg1!!))
IRDataType.LONG -> memory.setSL(i.address!!.value + index, registers.getSL(i.reg1!!))
IRDataType.BYTE -> memory.setUB(i.address!!.value + index * scale, registers.getUB(i.reg1!!))
IRDataType.WORD -> memory.setUW(i.address!!.value + index * scale, registers.getUW(i.reg1!!))
IRDataType.POINTER -> memory.setUL(i.address!!.value + index * scale, registers.getUL(i.reg1!!))
IRDataType.LONG -> memory.setSL(i.address!!.value + index * scale, registers.getSL(i.reg1!!))
IRDataType.FLOAT -> throw IllegalStateException("unreachable")
}
}
@@ -787,12 +789,13 @@ class VirtualMachine(irProgram: IRProgram) {
private fun InsSTOREZX(i: IRInstruction) {
val index = if(wordArrayIndex) registers.getUW(i.reg1!!).toUInt() else registers.getUB(i.reg1!!).toUInt()
val scale = i.scale.toUInt()
when (i.type!!) {
IRDataType.BYTE -> memory.setUB(i.address!!.value + index, 0u)
IRDataType.WORD -> memory.setUW(i.address!!.value + index, 0u)
IRDataType.POINTER -> memory.setUL(i.address!!.value + index, 0u)
IRDataType.LONG -> memory.setSL(i.address!!.value + index, 0)
IRDataType.FLOAT -> memory.setFloat(i.address!!.value + index, 0.0)
IRDataType.BYTE -> memory.setUB(i.address!!.value + index * scale, 0u)
IRDataType.WORD -> memory.setUW(i.address!!.value + index * scale, 0u)
IRDataType.POINTER -> memory.setUL(i.address!!.value + index * scale, 0u)
IRDataType.LONG -> memory.setSL(i.address!!.value + index * scale, 0)
IRDataType.FLOAT -> memory.setFloat(i.address!!.value + index * scale, 0.0)
}
nextPc()
}