mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
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:
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")
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user