diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt index 87be7bc45..97e0893e5 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt @@ -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) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index f1e8b587a..a5466be92 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -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) } } } diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt index e26bb884e..f279c4120 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt @@ -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) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt index 1004db9e1..05ec30c70 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt @@ -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) } diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt index a49cbd34b..b20af1923 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt @@ -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 -> { diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt index 686e8373b..9be96d09c 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt @@ -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") diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 7a3866ee6..7029189e8 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -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) ) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index c8ab99dda..f82cb34d0 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -5,7 +5,7 @@ Future Things and Ideas ^^^^^^^^^^^^^^^^^^^^^^^ - Consider extending ``var xx = `` 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)** diff --git a/ideas/better-addressing-IR.md b/ideas/better-addressing-IR.md index f60ad501d..1fb7c0ef0 100644 --- a/ideas/better-addressing-IR.md +++ b/ideas/better-addressing-IR.md @@ -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`. diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 8d1432bf1..3e8b4946e 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -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() } diff --git a/intermediate/src/prog8/intermediate/Utils.kt b/intermediate/src/prog8/intermediate/Utils.kt index 2d9159589..3d5aafce7 100644 --- a/intermediate/src/prog8/intermediate/Utils.kt +++ b/intermediate/src/prog8/intermediate/Utils.kt @@ -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 { diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index caa1692fd..a20132fe2 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -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() }