From bef1678c2aed77db8a044d1d3532488a89e80451 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 4 Aug 2026 12:43:23 +0200 Subject: [PATCH] IR: divmod no longer uses stack to return the quotient and remainer --- .../codegen/intermediate/BuiltinFuncGen.kt | 19 ++-- .../src/prog8/codegen/m68k/InstrArithmetic.kt | 91 ++++++++----------- .../prog8/codegen/new6502/InstrArithmetic.kt | 28 +++--- docs/source/libraries.rst | 2 +- examples/test.p8 | 91 ++++++++----------- .../src/prog8/intermediate/IRInstructions.kt | 20 ++-- intermediate/test/TestInstructions.kt | 4 +- m68k-potential-codegen-bugs.md | 46 ++++------ virtualmachine/src/prog8/vm/VirtualMachine.kt | 24 ++--- virtualmachine/src/prog8/vm/VmArithmetic.kt | 40 ++++---- 10 files changed, 160 insertions(+), 205 deletions(-) diff --git a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt index bbe9928d4..82db8ffbf 100644 --- a/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt @@ -238,31 +238,30 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe if(divident is PtNumber) { val tr = exprGen.translateExpression(number) addToResult(result, tr, tr.resultReg, -1) - addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, immediate = divident.number.toInt()), null) - divisionReg = tr.resultReg remainderReg = codeGen.registers.next(type) + addInstr(result, IRInstruction(divmodOpcode, type, reg1 = tr.resultReg, reg2 = remainderReg, immediate = divident.number.toInt()), null) + divisionReg = tr.resultReg } else { val numTr = exprGen.translateExpression(number) addToResult(result, numTr, numTr.resultReg, -1) val dividentTr = exprGen.translateExpression(divident) addToResult(result, dividentTr, dividentTr.resultReg, -1) - addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2=dividentTr.resultReg), null) - divisionReg = numTr.resultReg remainderReg = dividentTr.resultReg + addInstr(result, IRInstruction(divmodrOpcode, type, reg1 = numTr.resultReg, reg2 = dividentTr.resultReg), null) + divisionReg = numTr.resultReg } - // DIVMOD instruction result convention: on value stack, division and remainder on top. - addInstr(result, IRInstruction(Opcode.POP, type, reg1=remainderReg), null) - addInstr(result, IRInstruction(Opcode.POP, type, reg1=divisionReg), null) - // return the 2 result values: division and remainder division in AY, remainder in R15 + // DIVMOD result convention: quotient in reg1, remainder in reg2. + // Then route to return locations: STOREHR for quotient (AY slot), STOREM for remainder (cx16.r15). + // Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it. if(type==IRDataType.BYTE) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0) it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15") + it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0) } } else if(type==IRDataType.WORD) { result += IRCodeChunk(null, null).also { - it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4) it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15") + it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4) } } else throw AssemblyError("invalid type for DIVMOD") return ExpressionCodeResult(result, type, -1, -1) diff --git a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt index 1a84e8da0..79d07db49 100644 --- a/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt +++ b/codeGenM68k/src/prog8/codegen/m68k/InstrArithmetic.kt @@ -254,7 +254,7 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.DIVMOD -> { - emitDivModOp(r1 ?: error("DIVMOD needs reg1"), null, type, unsigned=true, imm=imm ?: error("DIVMOD needs immediate")) + emitDivModOp(r1 ?: error("DIVMOD needs reg1"), r2 ?: error("DIVMOD needs reg2"), type, unsigned=true, imm=imm ?: error("DIVMOD needs immediate")) } Opcode.SDIVMODR -> { @@ -262,7 +262,7 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.SDIVMOD -> { - emitDivModOp(r1 ?: error("SDIVMOD needs reg1"), null, type, unsigned=false, imm=imm ?: error("SDIVMOD needs immediate")) + emitDivModOp(r1 ?: error("SDIVMOD needs reg1"), r2 ?: error("SDIVMOD needs reg2"), type, unsigned=false, imm=imm ?: error("SDIVMOD needs immediate")) } // --- Integer sqrt/square --- @@ -588,72 +588,53 @@ private fun AsmGen.emitModOp(dstReg: Int, srcReg: Int?, type: IRDataType, unsign } } -private fun AsmGen.emitDivModOp(dstReg: Int, srcReg: Int?, type: IRDataType, unsigned: Boolean, imm: Int?) { - val op = if (unsigned) "divul.l" else "divsl.l" +private fun AsmGen.emitDivModOp(dstReg: Int, remainderReg: Int, type: IRDataType, unsigned: Boolean, imm: Int?) { + val opLong = if (unsigned) "divul.l" else "divsl.l" when (type) { IRDataType.BYTE -> { - // divul: Dq=dividend→quotient, Dr=remainder. Pack remainder lo, quotient hi. - when { - srcReg != null -> { - emitLine("move.b ${regAddr(dstReg)}, d0") - if (unsigned) emitLine($$"and.l #$ff, d0") else emitSignExtendByteToLong("d0") - emitLine("move.b ${regAddr(srcReg)}, d2") - if (unsigned) emitLine($$"and.l #$ff, d2") else emitSignExtendByteToLong("d2") - } - imm != null -> { - emitLine("move.b ${regAddr(dstReg)}, d0") - if (unsigned) emitLine($$"and.l #$ff, d0") else emitSignExtendByteToLong("d0") - emitLine("moveq #${imm.and(0xff)}, d2") - if (!unsigned) emitSignExtendByteToLong("d2") - } + val opDiv = if (unsigned) "divu.w" else "divs.w" + emitLine("move.b ${regAddr(dstReg)}, d0") + if (unsigned) emitLine($$"and.l #$ff, d0") else emitSignExtendByteToLong("d0") + if(imm!=null) { + emitLine("moveq #${imm.and(0xff)}, d1") + if (!unsigned) emitSignExtendByteToLong("d1") + } else { + emitLine("move.b ${regAddr(remainderReg)}, d1") + if (unsigned) emitLine($$"and.l #$ff, d1") else emitSignExtendByteToLong("d1") } - emitLine("$op d2, d1:d0") - emitLine("move.b d1, ${regAddrByte(dstReg, 0)}", "remainder lo") - emitLine("move.b d0, ${regAddrByte(dstReg, 1)}", "quotient hi") + emitLine("$opDiv d1, d0") + emitLine("move.b d0, ${regAddr(dstReg)}", "quotient") + emitLine("swap d0") + emitLine("move.b d0, ${regAddr(remainderReg)}", "remainder") } IRDataType.WORD -> { - // divu.w packs quotient in low word, remainder in high word natively - // note: clear d0 first because move.w preserves upper word on 68030 val opDiv = if (unsigned) "divu.w" else "divs.w" - when { - srcReg != null -> { - emitLine("moveq #0, d0", "clear upper word for divu.w") - emitLine("move.w ${regAddr(dstReg)}, d0") - emitLine("$opDiv ${regAddr(srcReg)}, d0") - } - imm != null -> { - emitLine("moveq #0, d0", "clear upper word for divu.w") - emitLine("move.w ${regAddr(dstReg)}, d0") - emitLine("$opDiv #${imm.and(0xffff)}, d0") - } + emitLine("moveq #0, d0", "clear upper word") + emitLine("move.w ${regAddr(dstReg)}, d0") + if (!unsigned) emitLine("ext.l d0", "sign-extend for divs.w") + if(imm!=null) { + emitLine("$opDiv #${imm.and(0xffff)}, d0") + } else { + emitLine("$opDiv ${regAddr(remainderReg)}, d0") } - // After: low word = quotient, high word = remainder - // Store: low word = quotient, next word = remainder? Or vice versa? - // Convention: remainder in low part, quotient in high part - emitLine("move.w d0, ${regAddr(dstReg)}", "quotient in low word") - emitLine("swap d0", "remainder to low word") - emitLine("move.w d0, ${regAddrByte(dstReg, 2)}", "remainder in next word") + emitLine("move.w d0, ${regAddr(dstReg)}", "quotient") + emitLine("swap d0") + emitLine("move.w d0, ${regAddr(remainderReg)}", "remainder") } IRDataType.LONG -> { if(program.options.compTarget.cpu < CpuType.M68020) - TODO("long division needs at least a 68020 cpu at this time") - when { - srcReg != null -> { - emitLine("move.l ${regAddr(dstReg)}, d0", "dividend") - emitLine("$op ${regAddr(srcReg)}, d1:d0") - emitLine("move.l d1, ${regAddr(dstReg)}", "remainder") - emitLine("move.l d0, ${regAddrByte(dstReg, 4)}", "quotient in next 4 bytes") - } - imm != null -> { - emitLine("move.l ${regAddr(dstReg)}, d0", "dividend") - emitLine("move.l #${imm}, d2", "divisor") - emitLine("$op d2, d1:d0") - emitLine("move.l d1, ${regAddr(dstReg)}", "remainder") - emitLine("move.l d0, ${regAddrByte(dstReg, 4)}", "quotient in next 4 bytes") - } + TODO("long divmod needs at least a 68020 cpu at this time") + emitLine("move.l ${regAddr(dstReg)}, d0", "dividend") + if(imm!=null) { + emitLine("move.l #${imm}, d1", "divisor") + } else { + emitLine("move.l ${regAddr(remainderReg)}, d1", "divisor") } + emitLine("$opLong d1, d0") + emitLine("move.l d0, ${regAddr(dstReg)}", "quotient") + emitLine("move.l d1, ${regAddr(remainderReg)}", "remainder") } else -> TODO("DIVMOD for ${type.name}") diff --git a/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt b/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt index 511170043..e4e8b319d 100644 --- a/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt +++ b/codeGenNew6502/src/prog8/codegen/new6502/InstrArithmetic.kt @@ -154,7 +154,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.DIVMOD -> { val value = imm ?: error("DIVMOD needs immediate") - divModImmediate(r1 ?: error("DIVMOD needs reg1"), value, type) + val r2val = r2 ?: error("DIVMOD needs reg2") + divModImmediate(r1 ?: error("DIVMOD needs reg1"), r2val, value, type) } Opcode.SDIVMODR -> { val r2val = r2 ?: error("SDIVMODR needs reg2") @@ -162,7 +163,8 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) { } Opcode.SDIVMOD -> { val value = imm ?: error("SDIVMOD needs immediate") - sdivModImmediate(r1 ?: error("SDIVMOD needs reg1"), value, type) + val r2val = r2 ?: error("SDIVMOD needs reg2") + sdivModImmediate(r1 ?: error("SDIVMOD needs reg1"), r2val, value, type) } Opcode.CMP -> { @@ -965,14 +967,14 @@ internal fun AsmGen.modSignedImmediate(dstReg: Int, value: Int, type: IRDataType // === DivMod === internal fun AsmGen.divModRegisters(dstReg: Int, srcReg: Int, type: IRDataType) { - // division and modulo combined: dstReg = dstReg/srcReg, dstReg+1 = dstReg%srcReg + // division and modulo combined: quotient in dstReg, remainder in srcReg when (type) { IRDataType.BYTE -> { emitLine("lda ${regAddrLo(dstReg)}") emitLine("ldy ${regAddrLo(srcReg)}") emitLine("jsr prog8_math.divmod_ub_asm") emitLine("sty ${regAddrLo(dstReg)}") - emitLine("sta ${regAddrByte(dstReg, 1)}") + emitLine("sta ${regAddrLo(srcReg)}") } IRDataType.WORD -> { emitLine("lda ${regAddrLo(dstReg)}") @@ -985,9 +987,9 @@ internal fun AsmGen.divModRegisters(dstReg: Int, srcReg: Int, type: IRDataType) emitLine("sta ${regAddrLo(dstReg)}") emitLine("sty ${regAddrHi(dstReg)}") emitLine("lda P8ZP_SCRATCH_W2") - emitLine("sta ${regAddrByte(dstReg, 2)}") + emitLine("sta ${regAddrLo(srcReg)}") emitLine("lda P8ZP_SCRATCH_W2+1") - emitLine("sta ${regAddrByte(dstReg, 3)}") + emitLine("sta ${regAddrHi(srcReg)}") } else -> TODO("DIVMODR r$dstReg, r$srcReg ${type.name}") } @@ -997,14 +999,14 @@ internal fun sdivModRegisters(dstReg: Int, srcReg: Int, type: IRDataType) { TODO("SDIVMODR r$dstReg, r$srcReg (signed)") } -internal fun AsmGen.divModImmediate(dstReg: Int, value: Int, type: IRDataType) { +internal fun AsmGen.divModImmediate(dstReg: Int, remainderReg: Int, value: Int, type: IRDataType) { when (type) { IRDataType.BYTE -> { emitLine("lda ${regAddrLo(dstReg)}") emitLine("ldy #${value and 0xff}") emitLine("jsr prog8_math.divmod_ub_asm") emitLine("sty ${regAddrLo(dstReg)}") - emitLine("sta ${regAddrByte(dstReg, 1)}") + emitLine("sta ${regAddrLo(remainderReg)}") } IRDataType.WORD -> { emitLine("lda ${regAddrLo(dstReg)}") @@ -1017,17 +1019,17 @@ internal fun AsmGen.divModImmediate(dstReg: Int, value: Int, type: IRDataType) { emitLine("sta ${regAddrLo(dstReg)}") emitLine("sty ${regAddrHi(dstReg)}") emitLine("lda P8ZP_SCRATCH_W2") - emitLine("sta ${regAddrByte(dstReg, 2)}") + emitLine("sta ${regAddrLo(remainderReg)}") emitLine("lda P8ZP_SCRATCH_W2+1") - emitLine("sta ${regAddrByte(dstReg, 3)}") + emitLine("sta ${regAddrHi(remainderReg)}") } else -> TODO("DIVMOD r$dstReg, $value ${type.name}") } } -internal fun AsmGen.sdivModImmediate(dstReg: Int, value: Int, type: IRDataType) { - emitLine("; SDIVMOD r$dstReg, $value (signed, using unsigned)") - divModImmediate(dstReg, value, type) +internal fun AsmGen.sdivModImmediate(dstReg: Int, remainderReg: Int, value: Int, type: IRDataType) { + emitLine("; SDIVMOD r$dstReg, r$remainderReg, $value (signed, using unsigned)") + divModImmediate(dstReg, remainderReg, value, type) } // === Compare === diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 8dfe87950..d0c512d58 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -67,7 +67,7 @@ Math Returns quotient and remainder of the division as two ubyte or uword values. Performs the division only once. Using '/' and '%' separately would perform the division twice, so using divmod is much more efficient for this. - **Note:** Clobbers ``cx16.r15`` (the remainder is stored there for the word variant). + **Clobbers:** ``cx16.r15`` (stores the remainder there). The ``%`` operator on its own used to also clobber ``cx16.r15`` but now preserves it. :index:`gcd` (a, b) diff --git a/examples/test.p8 b/examples/test.p8 index 72e3b4487..533f82e6d 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,60 +1,49 @@ %import textio %zeropage basicsafe -; Reproduces the m68k "DIVM/DIVSM swaps operands" bug in codeGenM68k/InstrArithmetic.kt. -; The IR instruction DIVM reg, addr means: memory[addr] = memory[addr] / reg -; (memory is the dividend, reg is the divisor, result is stored back to memory). -; The m68k backend instead computes reg / memory and stores into the register. -; Run on the virtual target for the correct (expected) results, then on -; -target amiga500 to see the wrong results. - main { sub start() { - ; unsigned byte case (DIVM) - ubyte @shared bvar = 100 - bvar /= 3 - txt.print("ubyte 100 / 3 = ") - txt.print_ub(bvar) - txt.print(" (expected 33)") - txt.nl() + check_unsigned(40000, 7, 5714, 2) + check_unsigned(30000, 13, 2307, 9) + ;check_signed(-1000, 7, -142, -6) + ;check_signed(-255, 16, -15, -15) + txt.print("done\n") + ;sys.poweroff_system() + } - ; unsigned word case (DIVM) - uword @shared wvar = 1000 - wvar /= 7 - txt.print("uword 1000 / 7 = ") - txt.print_uw(wvar) - txt.print(" (expected 142)") - txt.nl() + sub check_unsigned(uword a, uword b, uword exp_q, uword exp_r) { + uword q + uword r + q, r = divmod(a, b) + txt.print_uw(a) + txt.print(" / ") + txt.print_uw(b) + txt.print(" u: q=") + txt.print_uw(q) + txt.print(" r=") + txt.print_uw(r) + txt.print(" (expected q=") + txt.print_uw(exp_q) + txt.print(" r=") + txt.print_uw(exp_r) + txt.print(")\n") + } - ; signed byte case (DIVSM) - byte @shared sbvar = -100 - sbvar /= 3 - txt.print("byte -100 / 3 = ") - txt.print_b(sbvar) - txt.print(" (expected -33)") - txt.nl() - - ; signed word case (DIVSM) - word @shared svar = -1000 - svar /= 7 - txt.print("word -1000 / 7 = ") - txt.print_w(svar) - txt.print(" (expected -142)") - txt.nl() - - ; MULM: memory *= register - ubyte @shared mbvar = 5 - mbvar *= 7 - txt.print("ubyte 5 * 7 = ") - txt.print_ub(mbvar) - txt.print(" (expected 35)") - txt.nl() - - uword @shared mwvar = 500 - mwvar *= 10 - txt.print("uword 500 * 10 = ") - txt.print_uw(mwvar) - txt.print(" (expected 5000)") - txt.nl() + sub check_signed(word a, word b, word exp_q, word exp_r) { + word q + word r + q, r = divmod(a, b) + txt.print_w(a) + txt.print(" / ") + txt.print_w(b) + txt.print(" s: q=") + txt.print_w(q) + txt.print(" r=") + txt.print_w(r) + txt.print(" (expected q=") + txt.print_w(exp_q) + txt.print(" r=") + txt.print_w(exp_r) + txt.print(")\n") } } diff --git a/intermediate/src/prog8/intermediate/IRInstructions.kt b/intermediate/src/prog8/intermediate/IRInstructions.kt index 72e132ebd..0a7f71eee 100644 --- a/intermediate/src/prog8/intermediate/IRInstructions.kt +++ b/intermediate/src/prog8/intermediate/IRInstructions.kt @@ -222,10 +222,10 @@ modr reg1, reg2 - remainder (modulo) of unsigned div mod reg1, value - remainder (modulo) of unsigned division reg1 %= value note: division by zero yields max signed int $ff/$ffff modsr reg1, reg2 - remainder (modulo) of signed division reg1 %= reg2 note: division by zero yields max signed long mods reg1, value - remainder (modulo) of signed division reg1 %= value note: division by zero yields max signed long -divmodr reg1, reg2 - unsigned division reg1/reg2, storing division and remainder on value stack (so need to be POPped off) -divmod reg1, value - unsigned division reg1/value, storing division and remainder on value stack (so need to be POPped off) -sdivmodr reg1, reg2 - signed division reg1/reg2, storing division and remainder on value stack (so need to be POPped off) -sdivmod reg1, value - signed division reg1/value, storing division and remainder on value stack (so need to be POPped off) +divmodr reg1, reg2 - unsigned division reg1/reg2, storing quotient in reg1 and remainder in reg2 +divmod reg1, reg2, value - unsigned division reg1/value, storing quotient in reg1 and remainder in reg2 +sdivmodr reg1, reg2 - signed division reg1/reg2, storing quotient in reg1 and remainder in reg2 +sdivmod reg1, reg2, value - signed division reg1/value, storing quotient in reg1 and remainder in reg2 sqrt reg1, reg2 - reg1 is the square root of reg2 (reg2 can be l.1, .w or .b, result type in reg1 is .w or .b) you can also use it with floating point types, fpreg1 and fpreg2 (result is also .f) square reg1, reg2 - reg1 is the square of reg2 (reg2 can be .w or .b, result type in reg1 is always .b) you can also use it with floating point types, fpreg1 and fpreg2 (result is also .f) sgn reg1, reg2 - reg1.b is the sign of reg2 (or fpreg1, if sgn.f) (0.b, 1.b or -1.b) @@ -601,10 +601,6 @@ val OpcodesWithSideEffects = OpcodesThatBranch + setOf( Opcode.POP, Opcode.PUSHST, Opcode.POPST, - Opcode.DIVMOD, - Opcode.DIVMODR, - Opcode.SDIVMOD, - Opcode.SDIVMODR, Opcode.BREAKPOINT ) @@ -788,10 +784,10 @@ val instructionFormats = mutableMapOf( Opcode.MOD to InstructionFormat.from("BW,<>r1,r1,r1,r1,r1,r1,r1,r1,<>r2"), + Opcode.DIVMOD to InstructionFormat.from("BW,<>r1,>r2,r1,<>r2"), + Opcode.SDIVMOD to InstructionFormat.from("BWL,<>r1,>r2,r1, - require(format.reg2==OperandDirection.UNUSED || format.reg2==OperandDirection.READ || format.reg2==OperandDirection.READWRITE) {"reg2 can only be used as input or readwrite"} - require(format.fpReg2==OperandDirection.UNUSED || format.fpReg2==OperandDirection.READ || format.fpReg2==OperandDirection.READWRITE) {"fpReg2 can only be used as input or readwrite"} + require(format.reg2==OperandDirection.UNUSED || format.reg2==OperandDirection.READ || format.reg2==OperandDirection.WRITE || format.reg2==OperandDirection.READWRITE) {"reg2 can only be used as input, output, or readwrite"} + require(format.fpReg2==OperandDirection.UNUSED || format.fpReg2==OperandDirection.READ || format.fpReg2==OperandDirection.WRITE || format.fpReg2==OperandDirection.READWRITE) {"fpReg2 can only be used as input, output, or readwrite"} } } } diff --git a/m68k-potential-codegen-bugs.md b/m68k-potential-codegen-bugs.md index 9eeef733e..6de408663 100644 --- a/m68k-potential-codegen-bugs.md +++ b/m68k-potential-codegen-bugs.md @@ -12,35 +12,23 @@ Targets: Amiga500 (M68000), Qemu68k (M68020). m68k is big-endian with ## Bugs -- **DIVMOD return convention is broken on m68k** (two distinct problems): - - **(a) Results are not pushed onto the stack** (`InstrArithmetic.kt:578-646`, - `emitDivModOp`). The `DIVMOD`/`DIVMODR` IR instruction must push the - quotient then the remainder onto the value stack (the VM reference does - `valueStack.add(division)` then `valueStack.add(remainder)`), so the two - following `POP`s recover them. Instead `emitDivModOp` writes quotient and - remainder into `reg1`'s regfile slots (e.g. `regAddr(dstReg)` and - `regAddrByte(dstReg,2/4)`) and never pushes. The two `POP`s then read `sp` - and overwrite those exact slots with stack garbage, so `q`/`r` end up wrong. - Fix: after computing `d0`=quotient, `d1`=remainder, emit - `move.s d0,-(sp)` then `move.s d1,-(sp)` (matching type width) and delete - the regfile writes. - - **(b) The builtin's return locations are cx16-specific** - (`BuiltinFuncGen.kt:230-268`, `funcDivmod`). After the `POP`s it returns - the quotient via `STOREHR` into the 6502 hardware register `AY` (slot `s4` - for word, `s0`/`A` for byte) and the remainder via `STOREM` into the cx16 - memory symbol **`cx16.r15`**. Neither exists on m68k: - - `m68kSlotRegister` (`InstrControl.kt:632`) originally handled only slots - 10-32 (`D0-D7`/`A0-A6`/`FP0-FP7`) and crashed with - `unknown calling convention slot: s4` on the `STOREHR`. *Fixed:* slots - 0-7 (`A`/`X`/`Y`/`AX`/`AY`/`XY`/`FAC1`/`FAC2`) now map to - `d0`-`d5`/`fp0`/`fp1`. - - `cx16.r15` is still undefined on m68k, so assembly fails with - `undefined symbol ` at the `STOREM` (`InstrLoadStore.kt:125-130`). - The m68k target needs its own remainder home (e.g. a dedicated BSS - symbol) instead of the cx16 scratch register. - - Repro: `examples/test.p8` (`-target amiga500`) - currently reaches the - `cx16.r15` assembler error; once that is resolved the push bug (a) will - show up as wrong printed quotient/remainder. +- **DIVMOD return convention is broken on m68k**: + - The `DIVMOD`/`DIVMODR` IR instructions now write quotient to `reg1` and + remainder to `reg2` directly (no value stack round-trip). The builtin + `funcDivmod()` then routes results via `STOREHR` (quotient to AY slot) + and `STOREM` (remainder to `cx16.r15` memory address). + - **`STOREM` into `cx16.r15` is undefined on m68k** (`BuiltinFuncGen.kt:253-267`, + `funcDivmod`). The `cx16.r15` memory symbol does not exist on m68k, so + assembly fails with `undefined symbol ` at the `STOREM` + (`InstrLoadStore.kt:84-90`). The m68k target needs its own remainder + home (e.g. a dedicated BSS symbol) instead of the cx16 scratch register. + - **`STOREHR` slot mapping may not work correctly on m68k** + (`InstrLoadStore.kt:125-130`). Slots 0-7 (A/X/Y/AX/AY/XY/FAC1/FAC2) + map to `d0`-`d5`/`fp0`/`fp1`, but the 6502 slot semantics (A=low byte, + Y=high byte for word values) may not translate correctly to M68k + register conventions. + - Repro: `examples/test.p8` (`-target amiga500`) - currently fails with + `cx16.r15` assembler error. --- diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index 2a50ff367..04dac17ac 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -1463,9 +1463,9 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsDIVMODR(i: IRInstruction) { when(i.type!!) { - IRDataType.BYTE -> divAndModUByte(i.reg1!!, i.reg2!!) // division+remainder results on value stack - IRDataType.WORD -> divAndModUWord(i.reg1!!, i.reg2!!) // division+remainder results on value stack - IRDataType.POINTER -> divAndModUWord(i.reg1!!, i.reg2!!) // division+remainder results on value stack + IRDataType.BYTE -> divAndModUByte(i.reg1!!, i.reg2!!) + IRDataType.WORD -> divAndModUWord(i.reg1!!, i.reg2!!) + IRDataType.POINTER -> divAndModUWord(i.reg1!!, i.reg2!!) IRDataType.LONG -> throw IllegalArgumentException("divmodr unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1474,9 +1474,9 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsDIVMOD(i: IRInstruction) { when(i.type!!) { - IRDataType.BYTE -> divAndModConstUByte(i.reg1!!, i.immediate!!.toUByte()) // division+remainder results on value stack - IRDataType.WORD -> divAndModConstUWord(i.reg1!!, i.immediate!!.toUShort()) // division+remainder results on value stack - IRDataType.POINTER -> divAndModConstUWord(i.reg1!!, i.immediate!!.toUShort()) // division+remainder results on value stack + IRDataType.BYTE -> divAndModConstUByte(i.reg1!!, i.reg2!!, i.immediate!!.toUByte()) + IRDataType.WORD -> divAndModConstUWord(i.reg1!!, i.reg2!!, i.immediate!!.toUShort()) + IRDataType.POINTER -> divAndModConstUWord(i.reg1!!, i.reg2!!, i.immediate!!.toUShort()) IRDataType.LONG -> throw IllegalArgumentException("divmod unsigned long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1485,9 +1485,9 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsSDIVMODR(i: IRInstruction) { when(i.type!!) { - IRDataType.BYTE -> divAndModSByte(i.reg1!!, i.reg2!!) // signed division+remainder results on value stack - IRDataType.WORD -> divAndModSWord(i.reg1!!, i.reg2!!) // signed division+remainder results on value stack - IRDataType.POINTER -> divAndModSWord(i.reg1!!, i.reg2!!) // signed division+remainder results on value stack + IRDataType.BYTE -> divAndModSByte(i.reg1!!, i.reg2!!) + IRDataType.WORD -> divAndModSWord(i.reg1!!, i.reg2!!) + IRDataType.POINTER -> divAndModSWord(i.reg1!!, i.reg2!!) IRDataType.LONG -> throw IllegalArgumentException("divmodr signed long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } @@ -1496,9 +1496,9 @@ class VirtualMachine(irProgram: IRProgram) { private fun InsSDIVMOD(i: IRInstruction) { when(i.type!!) { - IRDataType.BYTE -> divAndModConstSByte(i.reg1!!, i.immediate!!.toByte()) // signed division+remainder results on value stack - IRDataType.WORD -> divAndModConstSWord(i.reg1!!, i.immediate!!.toShort()) // signed division+remainder results on value stack - IRDataType.POINTER -> divAndModConstSWord(i.reg1!!, i.immediate!!.toShort()) // signed division+remainder results on value stack + IRDataType.BYTE -> divAndModConstSByte(i.reg1!!, i.reg2!!, i.immediate!!.toByte()) + IRDataType.WORD -> divAndModConstSWord(i.reg1!!, i.reg2!!, i.immediate!!.toShort()) + IRDataType.POINTER -> divAndModConstSWord(i.reg1!!, i.reg2!!, i.immediate!!.toShort()) IRDataType.LONG -> throw IllegalArgumentException("divmod signed long not supported") IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") } diff --git a/virtualmachine/src/prog8/vm/VmArithmetic.kt b/virtualmachine/src/prog8/vm/VmArithmetic.kt index 26dd5c512..1468343bc 100644 --- a/virtualmachine/src/prog8/vm/VmArithmetic.kt +++ b/virtualmachine/src/prog8/vm/VmArithmetic.kt @@ -247,16 +247,16 @@ internal fun VirtualMachine.divAndModUByte(reg1: Int, reg2: Int) { val right = registers.getUB(reg2) val division = if(right==0.toUByte()) 0xffu else left / right val remainder = if(right==0.toUByte()) 0u else left % right - valueStack.add(division.toUByte()) - valueStack.add(remainder.toUByte()) + registers.setUB(reg1, division.toUByte()) + registers.setUB(reg2, remainder.toUByte()) } -internal fun VirtualMachine.divAndModConstUByte(reg1: Int, value: UByte) { +internal fun VirtualMachine.divAndModConstUByte(reg1: Int, remainderReg: Int, value: UByte) { val left = registers.getUB(reg1) val division = if(value==0.toUByte()) 0xffu else left / value val remainder = if(value==0.toUByte()) 0u else left % value - valueStack.add(division.toUByte()) - valueStack.add(remainder.toUByte()) + registers.setUB(reg1, division.toUByte()) + registers.setUB(remainderReg, remainder.toUByte()) } internal fun VirtualMachine.divAndModUWord(reg1: Int, reg2: Int) { @@ -264,16 +264,16 @@ internal fun VirtualMachine.divAndModUWord(reg1: Int, reg2: Int) { val right = registers.getUW(reg2) val division = if(right==0.toUShort()) 0xffffu else left / right val remainder = if(right==0.toUShort()) 0u else left % right - valueStack.pushw(division.toUShort()) - valueStack.pushw(remainder.toUShort()) + registers.setUW(reg1, division.toUShort()) + registers.setUW(reg2, remainder.toUShort()) } -internal fun VirtualMachine.divAndModConstUWord(reg1: Int, value: UShort) { +internal fun VirtualMachine.divAndModConstUWord(reg1: Int, remainderReg: Int, value: UShort) { val left = registers.getUW(reg1) val division = if(value==0.toUShort()) 0xffffu else left / value val remainder = if(value==0.toUShort()) 0u else left % value - valueStack.pushw(division.toUShort()) - valueStack.pushw(remainder.toUShort()) + registers.setUW(reg1, division.toUShort()) + registers.setUW(remainderReg, remainder.toUShort()) } internal fun VirtualMachine.divAndModSByte(reg1: Int, reg2: Int) { @@ -281,16 +281,16 @@ internal fun VirtualMachine.divAndModSByte(reg1: Int, reg2: Int) { val right = registers.getSB(reg2) val division = if(right==0.toByte()) 127 else left / right val remainder = if(right==0.toByte()) 0 else left % right - valueStack.add(division.toUByte()) - valueStack.add(remainder.toUByte()) + registers.setUB(reg1, division.toUByte()) + registers.setUB(reg2, remainder.toUByte()) } -internal fun VirtualMachine.divAndModConstSByte(reg1: Int, value: Byte) { +internal fun VirtualMachine.divAndModConstSByte(reg1: Int, remainderReg: Int, value: Byte) { val left = registers.getSB(reg1) val division = if(value==0.toByte()) 127 else left / value val remainder = if(value==0.toByte()) 0 else left % value - valueStack.add(division.toUByte()) - valueStack.add(remainder.toUByte()) + registers.setUB(reg1, division.toUByte()) + registers.setUB(remainderReg, remainder.toUByte()) } internal fun VirtualMachine.divAndModSWord(reg1: Int, reg2: Int) { @@ -298,16 +298,16 @@ internal fun VirtualMachine.divAndModSWord(reg1: Int, reg2: Int) { val right = registers.getSW(reg2) val division = if(right==0.toShort()) 32767 else left / right val remainder = if(right==0.toShort()) 0 else left % right - valueStack.pushw(division.toUShort()) - valueStack.pushw(remainder.toUShort()) + registers.setUW(reg1, division.toUShort()) + registers.setUW(reg2, remainder.toUShort()) } -internal fun VirtualMachine.divAndModConstSWord(reg1: Int, value: Short) { +internal fun VirtualMachine.divAndModConstSWord(reg1: Int, remainderReg: Int, value: Short) { val left = registers.getSW(reg1) val division = if(value==0.toShort()) 32767 else left / value val remainder = if(value==0.toShort()) 0 else left % value - valueStack.pushw(division.toUShort()) - valueStack.pushw(remainder.toUShort()) + registers.setUW(reg1, division.toUShort()) + registers.setUW(remainderReg, remainder.toUShort()) } internal fun VirtualMachine.divModByteUnsignedInplace(operator: String, reg1: Int, address: UInt) {