From 635870585e04903141f2d5554cef3200b13b8424 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Sun, 15 Sep 2019 19:47:19 +0200 Subject: [PATCH] Add unsigned 16-bit division --- CHANGELOG.md | 2 +- docs/lang/operators.md | 2 + include/i80_math.mfk | 232 +++++++++++++++--- include/zp_reg.mfk | 2 +- .../z80/opt/ReverseFlowAnalyzer.scala | 8 +- .../compiler/AbstractExpressionCompiler.scala | 19 +- .../compiler/mos/PseudoregisterBuiltIns.scala | 49 ++++ .../compiler/z80/Z80ExpressionCompiler.scala | 25 +- .../millfork/compiler/z80/Z80Multiply.scala | 52 +++- .../millfork/node/opt/UnusedFunctions.scala | 35 +-- .../scala/millfork/test/WordMathSuite.scala | 36 +++ 11 files changed, 385 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0207b2df..fd58d197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * Added `-R` option for specifying extra commandline parameters for emulators. -* Added full 16-bit multiplication. +* Added full 16-bit multiplication and unsigned division. * Added preliminary support for EasyFlash. diff --git a/docs/lang/operators.md b/docs/lang/operators.md index b63c4def..4bbe4946 100644 --- a/docs/lang/operators.md +++ b/docs/lang/operators.md @@ -97,6 +97,7 @@ TODO * `/`, `%%`: unsigned division and unsigned modulo `unsigned byte / unsigned byte` (zpreg) `word / unsigned byte` (zpreg) +`word / word` (zpreg) `constant word / constant word` `constant long / constant long` @@ -210,6 +211,7 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times. * `/=`, `%%=`: unsigned division and modulo in place `mutable unsigned byte /= unsigned byte` (zpreg) `mutable word /= unsigned byte` (zpreg) +`mutable word /= word` (zpreg) There are no `||=`, `&&=` or `>>>>=` operators. diff --git a/include/i80_math.mfk b/include/i80_math.mfk index 550f7030..42a6b843 100644 --- a/include/i80_math.mfk +++ b/include/i80_math.mfk @@ -110,43 +110,217 @@ __mul_u16u8u16_skip: #if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY noinline asm word __mul_u16u16u16() { - ld hl,0 - ld a,16 + LD HL,0 + LD A,16 __mul_u16u16u16_loop: - add hl,hl - rl e - rl d - jr nc,__mul_u16u16u16_skip - add hl,bc + ADD HL,HL + RL E + RL D + JR NC,__mul_u16u16u16_skip + ADD HL,BC __mul_u16u16u16_skip: - dec a - jr nz,__mul_u16u16u16_loop - ret + DEC A + JR NZ,__mul_u16u16u16_loop + RET } #else noinline asm word __mul_u16u16u16() { ld hl,0 - call __mul_u16u16u16_q - call __mul_u16u16u16_q - call __mul_u16u16u16_q - jp __mul_u16u16u16_q + CALL __mul_u16u16u16_q + CALL __mul_u16u16u16_q + CALL __mul_u16u16u16_q + JP __mul_u16u16u16_q } noinline asm word __mul_u16u16u16_q(word hl) { - call __mul_u16u16u16_s - call __mul_u16u16u16_s - call __mul_u16u16u16_s - jp __mul_u16u16u16_s + CALL __mul_u16u16u16_s + CALL __mul_u16u16u16_s + CALL __mul_u16u16u16_s + JP __mul_u16u16u16_s } noinline asm word __mul_u16u16u16_s(word hl) { - add hl,hl - ld a,e - add a,e - ld e,a - ld a,d - adc a,d - ld d,a - ret nc - add hl,bc - ret + ADD HL,HL + LD A,E + ADD A,E + LD E,A + LD A,D + ADC A,D + LD D,A + RET NC + ADD HL,BC + RET } -#endif \ No newline at end of file +#endif + +#if CPUFEATURE_Z80 + +// HL/DE = DE rem HL +noinline asm word __divmod_u16u16u16u16() { + LD A,H + LD C,L + LD HL,0 + LD B,16 +__divmod_u16u16u16u16_loop: +#if CPUFEATURE_Z80_ILLEGALS + SLL C +#else + SCF + RL C +#endif + RLA + ADC HL,HL + SBC HL,DE + JR NC,__divmod_u16u16u16u16_skip + ADD HL,DE + DEC C +__divmod_u16u16u16u16_skip: + DJNZ __divmod_u16u16u16u16_loop + LD D,A + LD E,C + RET +} + +#elseif CPUFEATURE_8080 + +// HL/DE = DE rem HL +// ABI could be changed, but the optimizer relies on it +asm word __divmod_u16u16u16u16() { + ? LD B,D + ? LD C,E + ? LD DE,0 +#if OPTIMIZE_FOR_SPEED + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u +#else + ? CALL __divmod_u16u16u16u16_q + ? CALL __divmod_u16u16u16u16_q + ? CALL __divmod_u16u16u16u16_q + ? CALL __divmod_u16u16u16u16_q +#endif + ? EX DE,HL + ? RET +} + +#if not(OPTIMIZE_FOR_SPEED) +asm void __divmod_u16u16u16u16_q(){ + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? JP __divmod_u16u16u16u16_u +} +#endif + +noinline asm void __divmod_u16u16u16u16_u(){ + ADD HL,HL + INC L + + LD A,E + RLA + LD E,A + LD A,D + RLA + LD D,A + + LD A,E + SUB C + LD E,A + LD A,D + SBC A,B + LD D,A + RET NC + + EX DE,HL + ADD HL, BC + EX DE,HL + DEC L + RET +} + +#elseif CPUFEATURE_GAMEBOY + +// HL/DE = DE rem HL +// ABI could be changed, but the optimizer relies on it +asm word __divmod_u16u16u16u16() { + ? LD B,D + ? LD C,E + ? LD D,H + ? LD E,L + ? LD HL,0 +#if OPTIMIZE_FOR_SPEED + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? JP __divmod_u16u16u16u16_u +#else + ? CALL __divmod_u16u16u16u16_q + ? CALL __divmod_u16u16u16u16_q + ? CALL __divmod_u16u16u16u16_q + ? JP __divmod_u16u16u16u16_q +#endif +} + +#if not(OPTIMIZE_FOR_SPEED) +asm void __divmod_u16u16u16u16_q(){ + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? CALL __divmod_u16u16u16u16_u + ? JP __divmod_u16u16u16u16_u +} +#endif + +noinline asm void __divmod_u16u16u16u16_u() { + SCF + RL E + RL D + RL L + RL H + + LD A,L + SUB C + LD L,A + LD A,H + SBC A,B + LD H,A + RET NC + + ADD HL,BC + DEC E + RET +} + +#else + +#warn No implementation of 16-bit division for this target + +#endif diff --git a/include/zp_reg.mfk b/include/zp_reg.mfk index f4a491ab..f01cf886 100644 --- a/include/zp_reg.mfk +++ b/include/zp_reg.mfk @@ -200,7 +200,6 @@ noinline asm word __divmod_u16u16u16u16() { ? PHA ? LDA __reg+2 ? PHA - ? TSX #else ? LDA __reg+2 ? STA __reg+4 @@ -218,6 +217,7 @@ noinline asm word __divmod_u16u16u16u16() { #if ZPREG_SIZE < 6 ? LDA #16 ? PHA + ? TSX #else ? LDX #16 #endif diff --git a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala index 2ffd2e66..fcb4a8c7 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala @@ -187,10 +187,10 @@ object ReverseFlowAnalyzer { val readsA: Set[String] = Set("__mul_u8u8u8", "__mul_u16u8u16", "call") val readsB: Set[String] = Set("__mul_u16u16u16") val readsC: Set[String] = Set("__mul_u16u16u16") - val readsD: Set[String] = Set("__mul_u8u8u8","__mul_u16u8u16", "__divmod_u16u8u16u8", "call", "__mul_u16u16u16") - val readsE: Set[String] = Set("__mul_u16u8u16", "call", "__mul_u16u16u16") - val readsH: Set[String] = Set("__divmod_u16u8u16u8", "call") - val readsL: Set[String] = Set("__divmod_u16u8u16u8", "call") + val readsD: Set[String] = Set("__mul_u8u8u8","__mul_u16u8u16", "__divmod_u16u8u16u8", "call", "__mul_u16u16u16", "__divmod_u16u16u16u16") + val readsE: Set[String] = Set("__mul_u16u8u16", "call", "__mul_u16u16u16", "__divmod_u16u16u16u16") + val readsH: Set[String] = Set("__divmod_u16u8u16u8", "call", "__divmod_u16u16u16u16") + val readsL: Set[String] = Set("__divmod_u16u8u16u8", "call", "__divmod_u16u16u16u16") val preservesH: Set[String] = Set("__mul_u8u8u8") val preservesL: Set[String] = Set("__mul_u8u8u8") diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index 2a9616b5..6604ed62 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -77,14 +77,23 @@ class AbstractExpressionCompiler[T <: AbstractCode] { val lSize = lType.size val rType = getExpressionType(ctx, params(1)) val rSize = rType.size - if (lSize > 2 || rSize > 2) { + if (lSize != 1 && lSize != 2) { ctx.log.error("Long division not supported", params.head.position) } - if (rSize > 1) { - ctx.log.error("Division by words not supported", params.head.position) + if (rSize != 1 && rSize != 2) { + ctx.log.error("Long division not supported", params.head.position) } - if (lType.isSigned || rType.isSigned) { - ctx.log.error("Signed division not supported", params.head.position) + if (inPlace) { + if (lSize == 2 && rSize == 1 && rType.isSigned) { + ctx.log.error("Signed division not supported", params.head.position) + } + } else { + if (lSize == 2 && rSize == 1 && rType.isSigned) { + ctx.log.error("Signed division not supported", params.head.position) + } + if (rSize == 2 && lSize == 1 && lType.isSigned) { + ctx.log.error("Signed division not supported", params.head.position) + } } } diff --git a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala index b1f49359..86645316 100644 --- a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala @@ -683,9 +683,58 @@ object PseudoregisterBuiltIns { load ++ calculate } + def compileWordWordDivision(ctx: CompilationContext, param1OrRegister: Option[Expression], param2: Expression, modulo: Boolean): List[AssemblyLine] = { + if (ctx.options.zpRegisterSize < 4) { + ctx.log.error("Variable word-word division requires the zeropage pseudoregister of size at least 4", param1OrRegister.flatMap(_.position)) + return Nil + } + val w = ctx.env.get[Type]("word") + val reg = ctx.env.get[VariableInMemory]("__reg") + val load: List[AssemblyLine] = param1OrRegister match { + case Some(param1) => + val code1 = MosExpressionCompiler.compile(ctx, param1, Some(w -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None) + val code2 = MosExpressionCompiler.compile(ctx, param2, Some(w -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None) + if (!usesRegLo(code2) && !usesRegHi(code2)) { + code1 ++ List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg, 2), AssemblyLine.zeropage(STX, reg, 3)) + } else if (!usesReg2(code1) && !usesReg3(code1)) { + code2 ++ List(AssemblyLine.zeropage(STA, reg, 2), AssemblyLine.zeropage(STX, reg, 3)) ++ code1 ++ List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) + } else { + code2 ++ List( + AssemblyLine.implied(PHA), + AssemblyLine.implied(TXA), + AssemblyLine.implied(PHA) + ) ++ code1 ++ List( + AssemblyLine.zeropage(STA, reg), + AssemblyLine.zeropage(STX, reg, 1), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 3), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 2) + ) + } + case None => + val code2 = MosExpressionCompiler.compile(ctx, param2, Some(w -> RegisterVariable(MosRegister.AX, w)), BranchSpec.None) + if (!usesRegLo(code2) && !usesRegHi(code2)) { + List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg, 2), AssemblyLine.zeropage(STX, reg, 3)) + } else { + List(AssemblyLine.implied(PHA), AssemblyLine.implied(TXA), AssemblyLine.implied(PHA)) ++ code2 ++ List( + AssemblyLine.zeropage(STA, reg, 2), + AssemblyLine.zeropage(STX, reg, 3), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg, 1), + AssemblyLine.implied(PLA), + AssemblyLine.zeropage(STA, reg) + ) + } + } + val functionName = if(modulo) "__mod_u16u16u16u16" else "__div_u16u16u16u16" + load :+ AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[FunctionInMemory](functionName), ctx.options) + } + def compileUnsignedWordByByteDivision(ctx: CompilationContext, param1: Expression, param2: Expression, modulo: Boolean): List[AssemblyLine] = { (AbstractExpressionCompiler.getExpressionType(ctx, param1).size, AbstractExpressionCompiler.getExpressionType(ctx, param2).size) match { + case (2 | 1, 2) => return compileWordWordDivision(ctx, Some(param1), param2, modulo) case (2 | 1, 1) => // ok case _ => ctx.log.fatal("Invalid code path", param2.position) } diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index e1622cb4..d92741ff 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -1092,27 +1092,29 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { Nil } case 2 => - if (f.functionName == "%%=") { + val modulo = f.functionName == "%%=" + val rhsWord = getExpressionType(ctx, r).size == 2 + if (modulo && !rhsWord) { calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match { case Some((LocalVariableAddressViaHL, List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), addr)))) => - Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r, modulo = true) ++ List( + Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo = true, rhsWord = false) ++ List( ZLine.ldAbs8(addr, ZRegister.A), ZLine.register(XOR, ZRegister.A), ZLine.ldAbs8(addr+1, ZRegister.A) ) case Some((lvo@LocalVariableAddressViaHL, code)) => - code ++ stashHLIfChanged(ctx, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = true)) ++ List( + code ++ stashHLIfChanged(ctx, Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo = true, rhsWord = false)) ++ List( ZLine.ld8(ZRegister.MEM_HL, ZRegister.A), ZLine.register(INC_16, ZRegister.HL), ZLine.ldImm8(ZRegister.MEM_HL, 0) ) case Some((lvo@LocalVariableAddressViaIX(offset), code)) => - code ++ Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = true) ++ List( + code ++ Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo = true, rhsWord = false) ++ List( ZLine.ldViaIx(offset, ZRegister.A), ZLine.ld0ViaIx(offset + 1) ) case Some((lvo@LocalVariableAddressViaIY(offset), code)) => - code ++ Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = true) ++ List( + code ++ Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo = true, rhsWord = false) ++ List( ZLine.ldViaIy(offset, ZRegister.A), ZLine.ld0ViaIy(offset + 1) ) @@ -1125,7 +1127,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case Some((lvo@LocalVariableAddressViaHL, code)) => code ++ stashHLIfChanged(ctx, - Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(LocalVariableAddressViaHL), r, modulo = false) ++ ( + Z80Multiply.compileUnsignedWordDivision(ctx, Left(LocalVariableAddressViaHL), r, modulo, rhsWord) ++ ( if (ctx.options.flags(CompilationFlag.EmitIntel8080Opcodes)) List(ZLine.implied(EX_DE_HL)) else List(ZLine.ld8(ZRegister.E, ZRegister.L), ZLine.ld8(ZRegister.D, ZRegister.H)) ) @@ -1137,11 +1139,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { ) case Some((lvo@LocalVariableAddressViaIX(offset), code)) => code ++ - Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = false) ++ + Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo, rhsWord) ++ storeHLViaIX(ctx, offset, 2, signedSource = false) case Some((lvo@LocalVariableAddressViaIY(offset), code)) => code ++ - Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = false) ++ + Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo, rhsWord) ++ storeHLViaIY(ctx, offset, 2, signedSource = false) case _ => ctx.log.error("Invalid left-hand side", l.position) @@ -1153,14 +1155,15 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { assertSizesForDivision(ctx, params, inPlace = false) val (l, r, size) = assertArithmeticBinary(ctx, params) val modulo = f.functionName == "%%" + val rhsWord = getExpressionType(ctx, r).size == 2 size match { case 1 => targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, Right(l), r, modulo), isSigned = false) case 2 => - if (modulo) { - targetifyA(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r, modulo = true), isSigned = false) + if (modulo && !rhsWord) { + targetifyA(ctx, target, Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo = true, rhsWord = false), isSigned = false) } else { - targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r, modulo = false)) + targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo, rhsWord)) } } case "*'=" => diff --git a/src/main/scala/millfork/compiler/z80/Z80Multiply.scala b/src/main/scala/millfork/compiler/z80/Z80Multiply.scala index d1b99241..5a30237c 100644 --- a/src/main/scala/millfork/compiler/z80/Z80Multiply.scala +++ b/src/main/scala/millfork/compiler/z80/Z80Multiply.scala @@ -116,9 +116,9 @@ object Z80Multiply { } /** - * Calculate HL = p / q and A = p %% q + * Calculate HL = p / q and/or (if rhsWord then HL else A) = p %% q */ - def compileUnsignedWordByByteDivision(ctx: CompilationContext, p: Either[LocalVariableAddressOperand, Expression], q: Expression, modulo: Boolean): List[ZLine] = { + def compileUnsignedWordDivision(ctx: CompilationContext, p: Either[LocalVariableAddressOperand, Expression], q: Expression, modulo: Boolean, rhsWord: Boolean): List[ZLine] = { val pb = p match { case Right(pp) => Z80ExpressionCompiler.compileToHL(ctx, pp) case Left(LocalVariableAddressViaHL) => List( @@ -136,14 +136,16 @@ object Z80Multiply { return pb case Some(NumericConstant(1, _)) => if (modulo) { - return pb :+ ZLine.ldImm8(ZRegister.A, 0) + if (rhsWord) return pb :+ ZLine.ldImm16(ZRegister.HL, 0) + else return pb :+ ZLine.ldImm8(ZRegister.A, 0) } else { return pb } case Some(NumericConstant(qc, _)) if qc <= 255 && isPowerOfTwoUpTo15(qc) => val count = Integer.numberOfTrailingZeros(qc.toInt) if (modulo) { - return pb ++ List(ZLine.ld8(ZRegister.A, ZRegister.L), ZLine.imm8(ZOpcode.AND, qc.toInt - 1)) + if (rhsWord) return pb ++ List(ZLine.ld8(ZRegister.A, ZRegister.L), ZLine.imm8(ZOpcode.AND, qc.toInt - 1), ZLine.ld8(ZRegister.L, ZRegister.A), ZLine.ldImm8(ZRegister.H, 0)) + else return pb ++ List(ZLine.ld8(ZRegister.A, ZRegister.L), ZLine.imm8(ZOpcode.AND, qc.toInt - 1)) } else { val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes) val shiftHL = if (extendedOps) { @@ -164,17 +166,43 @@ object Z80Multiply { } return pb ++ shiftHL } + case Some(NumericConstant(256, _)) if rhsWord => + if (modulo) return pb ++ List(ZLine.ldImm8(ZRegister.H, 0)) + else return pb ++ List(ZLine.ld8(ZRegister.L, ZRegister.H), ZLine.ldImm8(ZRegister.H, 0)) + case Some(NumericConstant(qc, _)) if modulo && rhsWord && qc <= 0xffff && isPowerOfTwoUpTo15(qc) => + return pb ++ List(ZLine.ld8(ZRegister.A, ZRegister.L), ZLine.imm8(ZOpcode.AND, (qc.toInt >> 8) - 1), ZLine.ld8(ZRegister.H, ZRegister.A)) case _ => } - val qb = Z80ExpressionCompiler.compileToA(ctx, q) - val load = if (qb.exists(Z80ExpressionCompiler.changesHL)) { - pb ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, qb) ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) - } else if (pb.exists(Z80ExpressionCompiler.changesDE)) { - qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) ++ Z80ExpressionCompiler.stashDEIfChanged(ctx, pb) + if (!rhsWord) { + val qb = Z80ExpressionCompiler.compileToA(ctx, q) + val load = if (qb.exists(Z80ExpressionCompiler.changesHL)) { + pb ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, qb) ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) + } else if (pb.exists(Z80ExpressionCompiler.changesDE)) { + qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) ++ Z80ExpressionCompiler.stashDEIfChanged(ctx, pb) + } else { + pb ++ qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) + } + load :+ ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u8u16u8").toAddress) } else { - pb ++ qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) + val qb = Z80ExpressionCompiler.compileToDE(ctx, q) + val load = if (qb.exists(Z80ExpressionCompiler.changesHL)) { + pb ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, qb) + } else if (pb.exists(Z80ExpressionCompiler.changesDE)) { + qb ++ Z80ExpressionCompiler.stashDEIfChanged(ctx, pb) + } else { + pb ++ qb + } + if (modulo) { + load :+ ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u16u16u16").toAddress) + } else if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) { + load ++ List(ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u16u16u16").toAddress), ZLine.implied(ZOpcode.EX_DE_HL)) + } else { + load ++ List( + ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u16u16u16").toAddress), + ZLine.ld8(ZRegister.H, ZRegister.D), + ZLine.ld8(ZRegister.L, ZRegister.E)) + } } - load :+ ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u8u16u8").toAddress) } /** @@ -216,7 +244,7 @@ object Z80Multiply { compileUnsignedByteDivisionImpl(ctx, p, qq.toInt, modulo) } case _ => - val call = compileUnsignedWordByByteDivision(ctx, p, q, modulo = modulo) + val call = compileUnsignedWordDivision(ctx, p, q, modulo = modulo, rhsWord = false) if (modulo) { call } else { diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index ce745d43..c58b0a82 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -17,23 +17,30 @@ object UnusedFunctions extends NodeOptimization { ("*=", 2, "__mul_u8u8u8"), ("*=", 2, "__mul_u16u8u16"), ("*=", 4, "__mul_u16u16u16"), - ("/=", 0, "__divmod_u16u8u16u8"), - ("/", 0, "__divmod_u16u8u16u8"), - ("%%=", 0, "__divmod_u16u8u16u8"), ("%%", 0, "__divmod_u16u8u16u8"), - ("%%=", 2, "__mod_u8u8u8u8"), - ("%%", 2, "__mod_u8u8u8u8"), - ("/=", 2, "__mod_u8u8u8u8"), - ("/", 2, "__mod_u8u8u8u8"), - ("/=", 2, "__div_u8u8u8u8"), - ("/", 2, "__div_u8u8u8u8"), - ("%%=", 2, "__mod_u16u8u16u8"), ("%%", 2, "__mod_u16u8u16u8"), - ("/=", 2, "__mod_u16u8u16u8"), - ("/", 2, "__mod_u16u8u16u8"), - ("/=", 2, "__div_u16u8u16u8"), + ("%%", 2, "__mod_u8u8u8u8"), + ("%%", 4, "__divmod_u16u16u16u16"), + ("%%", 4, "__mod_u16u16u16u16"), + ("%%=", 0, "__divmod_u16u8u16u8"), + ("%%=", 2, "__mod_u16u8u16u8"), + ("%%=", 2, "__mod_u8u8u8u8"), + ("%%=", 4, "__divmod_u16u16u16u16"), + ("%%=", 4, "__mod_u16u16u16u16"), + ("/", 0, "__divmod_u16u8u16u8"), ("/", 2, "__div_u16u8u16u8"), - // TODO: u16u16u16u16 division + ("/", 2, "__div_u8u8u8u8"), + ("/", 2, "__mod_u16u8u16u8"), + ("/", 2, "__mod_u8u8u8u8"), + ("/", 4, "__div_u16u16u16u16"), + ("/", 4, "__divmod_u16u16u16u16"), + ("/=", 0, "__divmod_u16u8u16u8"), + ("/=", 2, "__div_u16u8u16u8"), + ("/=", 2, "__div_u8u8u8u8"), + ("/=", 2, "__mod_u16u8u16u8"), + ("/=", 2, "__mod_u8u8u8u8"), + ("/=", 4, "__div_u16u16u16u16"), + ("/=", 4, "__divmod_u16u16u16u16"), ("+'", 4, "__adc_decimal"), ("+'=", 4, "__adc_decimal"), ("-'", 4, "__sub_decimal"), diff --git a/src/test/scala/millfork/test/WordMathSuite.scala b/src/test/scala/millfork/test/WordMathSuite.scala index e47b8a34..eee093b0 100644 --- a/src/test/scala/millfork/test/WordMathSuite.scala +++ b/src/test/scala/millfork/test/WordMathSuite.scala @@ -664,4 +664,40 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues { m.readWord(0xc002) should equal((x * y) & 0xffff) withClue s"= $x * $y (c002)" } } + + test("Word/word division 1") { + divisionCaseWW1(0, 1) + divisionCaseWW1(0, 1000) + divisionCaseWW1(1, 1000) + divisionCaseWW1(1, 1) + divisionCaseWW1(1000, 1) + divisionCaseWW1(1000, 1000) + divisionCaseWW1(1000, 33) + divisionCaseWW1(33000, 999) + divisionCaseWW1(33000, 256) + divisionCaseWW1(33000, 16) + divisionCaseWW1(33000, 1024) + } + + private def divisionCaseWW1(x: Int, y: Int): Unit = { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)( + s""" + | import zp_reg + | word output0 @$$c000 + | word output1 @$$c002 + | void main () { + | output0 = $x + | memory_barrier() + | output0 /= word($y) + | output1 = $x + | memory_barrier() + | output1 %%= word($y) + | } + | noinline word id(word w) = w + """. + stripMargin){m => + m.readWord(0xc000) should equal((x / y) & 0xffff) withClue s"= $x / $y (c000)" + m.readWord(0xc002) should equal((x % y) & 0xffff) withClue s"= $x %% $y (c002)" + } + } }