diff --git a/CHANGELOG.md b/CHANGELOG.md index caddefbc..f763cf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current version +* Added full 16-bit multiplication. + * Added preliminary support for EasyFlash. * Allowed defining custom output padding byte value. diff --git a/docs/lang/operators.md b/docs/lang/operators.md index 429bc7dc..b63c4def 100644 --- a/docs/lang/operators.md +++ b/docs/lang/operators.md @@ -92,6 +92,7 @@ TODO `byte * byte` (zpreg) `word * byte` (zpreg) `byte * word` (zpreg) +`word * word` (zpreg) * `/`, `%%`: unsigned division and unsigned modulo `unsigned byte / unsigned byte` (zpreg) @@ -201,6 +202,7 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times. `mutable byte *= constant byte` `mutable byte *= byte` (zpreg) `mutable word *= unsigned byte` (zpreg) +`mutable word *= word` (zpreg) * `*'=`: decimal multiplication in place `mutable byte *'= constant byte` diff --git a/include/i80_math.mfk b/include/i80_math.mfk index 698dc586..550f7030 100644 --- a/include/i80_math.mfk +++ b/include/i80_math.mfk @@ -83,7 +83,7 @@ __divmod_u16u8u16u8_skip: ? RET } -inline asm word __mul_u16u8u16() { +noinline asm word __mul_u16u8u16() { ? LD HL,0 ? LD B,8 __mul_u16u8u16_loop: @@ -107,3 +107,46 @@ __mul_u16u8u16_skip: #endif ? RET } + +#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY +noinline asm word __mul_u16u16u16() { + ld hl,0 + ld a,16 +__mul_u16u16u16_loop: + 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 +} +#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 +} +noinline asm word __mul_u16u16u16_q(word hl) { + 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 +} +#endif \ No newline at end of file diff --git a/include/zp_reg.mfk b/include/zp_reg.mfk index dc9827c4..f4a491ab 100644 --- a/include/zp_reg.mfk +++ b/include/zp_reg.mfk @@ -128,4 +128,156 @@ noinline asm word call(word ax) { JMP ((__reg + 2)) } +noinline asm word __mul_u16u16u16() { +#if ZPREG_SIZE < 6 + ? LDA __reg+1 + ? PHA + ? LDA __reg + ? PHA + ? TSX +#else + ? LDA __reg + ? STA __reg+4 + ? LDA __reg+1 + ? STA __reg+5 +#endif +#if CPUFEATURE_65C02 && not(CPUFEATURE_65CE02) + ? STZ __reg + ? STZ __reg+1 +#else + ? LDA #0 + ? STA __reg + ? STA __reg+1 +#endif + ? LDY #16 +__mul_u16u16u32_loop: + ? ASL __reg + ? ROL __reg+1 + ? ROL __reg+2 + ? ROL __reg+3 + ? BCC __mul_u16u16u32_skip +#if ZPREG_SIZE < 6 + ? LDA $101,X +#else + ? LDA __reg+4 +#endif + ? CLC + ? ADC __reg + ? STA __reg +#if ZPREG_SIZE < 6 + ? LDA $102,X +#else + ? LDA __reg+5 +#endif + ? ADC __reg+1 + ? STA __reg+1 + ? BCC __mul_u16u16u32_skip + ? INC __reg+2 +__mul_u16u16u32_skip: + ? DEY + ? BNE __mul_u16u16u32_loop +#if ZPREG_SIZE < 6 +#if OPTIMIZE_FOR_SPEED + ? INX + ? INX + ? TXS +#else + ? PLA + ? PLA +#endif +#endif + ? LDA __reg + ? LDX __reg+1 + ? RTS +} + +// divide (__reg[1]:__reg[0])/(__reg[3]:__reg[2]) +// remainder in (__reg[2]:__reg[3]), quotient in (__reg[1]:__reg[0]) + +noinline asm word __divmod_u16u16u16u16() { +#if ZPREG_SIZE < 6 + ? LDA __reg+3 + ? PHA + ? LDA __reg+2 + ? PHA + ? TSX +#else + ? LDA __reg+2 + ? STA __reg+4 + ? LDA __reg+3 + ? STA __reg+5 +#endif +#if CPUFEATURE_65C02 && not(CPUFEATURE_65CE02) + ? STZ __reg+2 + ? STZ __reg+3 +#else + ? LDA #0 + ? STA __reg+2 + ? STA __reg+3 +#endif +#if ZPREG_SIZE < 6 + ? LDA #16 + ? PHA +#else + ? LDX #16 +#endif +__divmod_u16u16u16u16_loop: + ? ASL __reg + ? ROL __reg+1 + ? ROL __reg+2 + ? ROL __reg+3 + ? LDA __reg+2 + sec +#if ZPREG_SIZE < 6 + ? SBC $102,X +#else + ? SBC __reg+4 +#endif + ? TAY + ? LDA __reg+3 +#if ZPREG_SIZE < 6 + ? SBC $103,X +#else + ? SBC __reg+5 +#endif + ? BCC __divmod_u16u16u16u16_skip + ? STA __reg+3 + ? STY __reg+2 + ? INC __reg +__divmod_u16u16u16u16_skip: +#if ZPREG_SIZE < 6 + ? DEC $101,X +#else + ? DEX +#endif + ? BNE __divmod_u16u16u16u16_loop +#if ZPREG_SIZE < 6 +#if OPTIMIZE_FOR_SIZE + ? PLA + ? PLA + ? PLA +#else + ? INX + ? INX + ? INX + ? TXS +#endif +#endif + ? RTS +} + +asm word __div_u16u16u16u16() { + JSR __divmod_u16u16u16u16 + ? LDA __reg + ? LDX __reg+1 + ? RTS +} + +asm word __mod_u16u16u16u16() { + JSR __divmod_u16u16u16u16 + ? LDA __reg+2 + ? LDX __reg+3 + ? RTS +} + #endif diff --git a/src/main/scala/millfork/assembly/mos/opt/ZeropageRegisterOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/ZeropageRegisterOptimizations.scala index dda441ac..b877cff8 100644 --- a/src/main/scala/millfork/assembly/mos/opt/ZeropageRegisterOptimizations.scala +++ b/src/main/scala/millfork/assembly/mos/opt/ZeropageRegisterOptimizations.scala @@ -14,11 +14,14 @@ object ZeropageRegisterOptimizations { val functionsThatUsePseudoregisterAsInput: Map[String, Set[Int]] = Map( "call" -> Set(2, 3), + "__mul_u16u16u16" -> Set(0, 1, 2, 3), "__mul_u8u8u8" -> Set(0, 1), "__mod_u8u8u8u8" -> Set(0, 1), "__div_u8u8u8u8" -> Set(0, 1), "__mod_u16u8u16u8" -> Set(0, 1, 2), "__div_u16u8u16u8" -> Set(0, 1, 2), + "__div_u16u16u16u16" -> Set(0, 1, 2, 3), + "__mod_u16u16u16u16" -> Set(0, 1, 2, 3), "__mul_u16u8u16" -> Set(0, 1, 2), "__adc_decimal" -> Set(2, 3), "__sbc_decimal" -> Set(2, 3), diff --git a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala index 0ef1208b..78b169ee 100644 --- a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala @@ -42,8 +42,9 @@ object CoarseFlowAnalyzer { result } - val preservesB: Set[String] = Set("__mul_u8u8u8") - val preservesC: Set[String] = if (z80) Set("__mul_u8u8u8") else Set() + // TODO: u16u16u16u16 division + val preservesB: Set[String] = Set("__mul_u8u8u8", "__mul_u16u16u16") + val preservesC: Set[String] = if (z80) Set("__mul_u8u8u8", "__mul_u16u16u16") else Set("__mul_u16u16u16") val preservesD: Set[String] = Set() val preservesE: Set[String] = Set("__divmod_u16u8u16u8") val preservesH: Set[String] = Set("__mul_u8u8u8") diff --git a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala index 0f2bf84b..2ffd2e66 100644 --- a/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/ReverseFlowAnalyzer.scala @@ -183,11 +183,12 @@ object ReverseFlowAnalyzer { val cache = new FlowCache[ZLine, CpuImportance]("z80 reverse") + // TODO: u16u16u16u16 division val readsA: Set[String] = Set("__mul_u8u8u8", "__mul_u16u8u16", "call") - val readsB: Set[String] = Set("") - val readsC: Set[String] = Set("") - val readsD: Set[String] = Set("__mul_u8u8u8","__mul_u16u8u16", "__divmod_u16u8u16u8", "call") - val readsE: Set[String] = Set("__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") diff --git a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala index d5896e0f..2a9616b5 100644 --- a/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/AbstractExpressionCompiler.scala @@ -50,32 +50,23 @@ class AbstractExpressionCompiler[T <: AbstractCode] { val lSize = lType.size val rType = getExpressionType(ctx, params(1)) val rSize = rType.size + if (lSize != 1 && lSize != 2) { + ctx.log.error("Long multiplication not supported", params.head.position) + } + if (rSize != 1 && rSize != 2) { + ctx.log.error("Long multiplication not supported", params.head.position) + } if (inPlace) { - if (lSize != 1 && lSize != 2) { - ctx.log.error("Long multiplication not supported", params.head.position) - } - if (rSize != 1) { - ctx.log.error("Long multiplication not supported", params.head.position) - } - if (lSize == 2 && rType.isSigned) { + if (lSize == 2 && rSize == 1 && rType.isSigned) { ctx.log.error("Signed multiplication not supported", params.head.position) } } else { - if (lSize > 2 || rSize > 2 || lSize + rSize > 3) { - ctx.log.error("Long multiplication not supported", params.head.position) - } - if (lSize == 2 && rType.isSigned) { + if (lSize == 2 && rSize == 1 && rType.isSigned) { ctx.log.error("Signed multiplication not supported", params.head.position) } - if (rSize == 2 && lType.isSigned) { + if (rSize == 2 && lSize == 1 && lType.isSigned) { ctx.log.error("Signed multiplication not supported", params.head.position) } - if (lSize + rSize > 2) { - if (params.size != 2) { - ctx.log.error("Cannot multiply more than 2 large numbers at once", params.headOption.flatMap(_.position)) - return - } - } } } diff --git a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala index ddea5935..b1f49359 100644 --- a/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala +++ b/src/main/scala/millfork/compiler/mos/PseudoregisterBuiltIns.scala @@ -574,13 +574,61 @@ object PseudoregisterBuiltIns { private def isPowerOfTwoUpTo15(n: Long): Boolean = if (n <= 0 || n >= 0x8000) false else 0 == ((n-1) & n) + def compileWordWordMultiplication(ctx: CompilationContext, param1OrRegister: Option[Expression], param2: Expression): List[AssemblyLine] = { + if (ctx.options.zpRegisterSize < 4) { + ctx.log.error("Variable word-word multiplication 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) + ) + } + } + load :+ AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[FunctionInMemory]("__mul_u16u16u16"), ctx.options) + } + def compileWordMultiplication(ctx: CompilationContext, param1OrRegister: Option[Expression], param2: Expression, storeInRegLo: Boolean): List[AssemblyLine] = { if (ctx.options.zpRegisterSize < 3) { - ctx.log.error("Variable word multiplication requires the zeropage pseudoregister of size at least 3", param1OrRegister.flatMap(_.position)) + ctx.log.error("Variable word-byte multiplication requires the zeropage pseudoregister of size at least 3", param1OrRegister.flatMap(_.position)) return Nil } (param1OrRegister.fold(2)(e => AbstractExpressionCompiler.getExpressionType(ctx, e).size), AbstractExpressionCompiler.getExpressionType(ctx, param2).size) match { + case (2, 2) => return compileWordWordMultiplication(ctx, param1OrRegister, param2) case (1, 2) => return compileWordMultiplication(ctx, Some(param2), param1OrRegister.get, storeInRegLo) case (2 | 1, 1) => // ok case _ => ctx.log.fatal("Invalid code path", param2.position) @@ -717,6 +765,12 @@ object PseudoregisterBuiltIns { case _ => false } + def usesReg3(code: List[AssemblyLine]): Boolean = code.exists{ + case AssemblyLine0(JSR | BSR | TCD | TDC, _, _) => true + case AssemblyLine0(_, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(3, _))) if th.name == "__reg" => true + case _ => false + } + def doesMemoryAccessOverlap(l1: List[AssemblyLine], l2: List[AssemblyLine]): Boolean = { for{ a1 <- l1 diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index b5291826..e1622cb4 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -897,7 +897,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { targetifyA(ctx, target, Z80Multiply.compile8BitMultiply(ctx, params), isSigned = false) case 2 => //noinspection ZeroIndexToHead - targetifyHL(ctx, target, Z80Multiply.compile16And8BitMultiplyToHL(ctx, params(0), params(1))) + targetifyHL(ctx, target, Z80Multiply.compile16BitMultiplyToHL(ctx, params(0), params(1))) } case "|" => getArithmeticParamMaxSize(ctx, params) match { @@ -1073,7 +1073,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case 1 => Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r) case 2 => - Z80Multiply.compile16And8BitInPlaceMultiply(ctx, l, r) + Z80Multiply.compile16BitInPlaceMultiply(ctx, l, r) } case "/=" | "%%=" => assertSizesForDivision(ctx, params, inPlace = true) diff --git a/src/main/scala/millfork/compiler/z80/Z80Multiply.scala b/src/main/scala/millfork/compiler/z80/Z80Multiply.scala index b4b25c40..d1b99241 100644 --- a/src/main/scala/millfork/compiler/z80/Z80Multiply.scala +++ b/src/main/scala/millfork/compiler/z80/Z80Multiply.scala @@ -29,6 +29,14 @@ object Z80Multiply { ctx.env.get[ThingInMemory]("__mul_u16u8u16").toAddress)) } + /** + * Compiles HL = BC * DE + */ + private def multiplication16And16(ctx: CompilationContext): List[ZLine] = { + List(ZLine(ZOpcode.CALL, NoRegisters, + ctx.env.get[ThingInMemory]("__mul_u16u16u16").toAddress)) + } + /** * Calculate A = l * r */ @@ -256,13 +264,35 @@ object Z80Multiply { result.toList } + def compile16x16BitMultiplyToHL(ctx: CompilationContext, l: Expression, r: Expression): List[ZLine] = { + (ctx.env.eval(l), ctx.env.eval(r)) match { + case (None, Some(c)) => + Z80ExpressionCompiler.compileToDE(ctx, l) ++ List(ZLine.ldImm16(ZRegister.BC, c)) ++ multiplication16And16(ctx) + case (Some(c), None) => + Z80ExpressionCompiler.compileToDE(ctx, r) ++ List(ZLine.ldImm16(ZRegister.BC, c)) ++ multiplication16And16(ctx) + case (Some(c), Some(d)) => + List(ZLine.ldImm16(ZRegister.HL, CompoundConstant(MathOperator.Times, c, d).quickSimplify.subword(0))) + case _ => + val ld = Z80ExpressionCompiler.compileToDE(ctx, l) + val rb = Z80ExpressionCompiler.compileToBC(ctx, r) + val loadRegisters = (ld.exists(Z80ExpressionCompiler.changesBC), rb.exists(Z80ExpressionCompiler.changesDE)) match { + case (true, true) => ld ++ Z80ExpressionCompiler.stashDEIfChanged(ctx, rb) + case (false, true) => rb ++ ld + case (true, false) => ld ++ rb + case (false, false) => ld ++ rb + } + loadRegisters ++ multiplication16And16(ctx) + } + } + /** * Calculate HL = l * r */ - def compile16And8BitMultiplyToHL(ctx: CompilationContext, l: Expression, r: Expression): List[ZLine] = { + def compile16BitMultiplyToHL(ctx: CompilationContext, l: Expression, r: Expression): List[ZLine] = { (AbstractExpressionCompiler.getExpressionType(ctx, l).size, AbstractExpressionCompiler.getExpressionType(ctx, r).size) match { - case (1, 2) => return compile16And8BitMultiplyToHL(ctx, r, l) + case (2, 2) => return compile16x16BitMultiplyToHL(ctx, l, r) + case (1, 2) => return compile16BitMultiplyToHL(ctx, r, l) case (2 | 1, 1) => // ok case _ => ctx.log.fatal("Invalid code path", l.position) } @@ -280,8 +310,8 @@ object Z80Multiply { /** * Calculate l = l * r */ - def compile16And8BitInPlaceMultiply(ctx: CompilationContext, l: LhsExpression, r: Expression): List[ZLine] = { - compile16And8BitMultiplyToHL(ctx, l, r) ++ Z80ExpressionCompiler.storeHL(ctx, l, signedSource = false) + def compile16BitInPlaceMultiply(ctx: CompilationContext, l: LhsExpression, r: Expression): List[ZLine] = { + compile16BitMultiplyToHL(ctx, l, r) ++ Z80ExpressionCompiler.storeHL(ctx, l, signedSource = false) } /** diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index 375c2758..ce745d43 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -13,8 +13,10 @@ object UnusedFunctions extends NodeOptimization { private val operatorImplementations: List[(String, Int, String)] = List( ("*", 2, "__mul_u8u8u8"), ("*", 3, "__mul_u16u8u16"), + ("*", 4, "__mul_u16u16u16"), ("*=", 2, "__mul_u8u8u8"), ("*=", 2, "__mul_u16u8u16"), + ("*=", 4, "__mul_u16u16u16"), ("/=", 0, "__divmod_u16u8u16u8"), ("/", 0, "__divmod_u16u8u16u8"), ("%%=", 0, "__divmod_u16u8u16u8"), @@ -31,6 +33,7 @@ object UnusedFunctions extends NodeOptimization { ("/", 2, "__mod_u16u8u16u8"), ("/=", 2, "__div_u16u8u16u8"), ("/", 2, "__div_u16u8u16u8"), + // TODO: u16u16u16u16 division ("+'", 4, "__adc_decimal"), ("+'=", 4, "__adc_decimal"), ("-'", 4, "__sub_decimal"), diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index b02ae1e3..4377752f 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -274,7 +274,11 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program } } - val objectsThatMayBeUnused = Set("__mul_u8u8u8", "__constant8", "identity$", "__mul_u16u8u16", "__divmod_u16u8u16u8", "__mod_u8u8u8u8", "__div_u8u8u8u8") ++ + val objectsThatMayBeUnused = Set("__constant8", "identity$", + "__mul_u8u8u8", "__mul_u16u8u16", "__mul_u16u16u16", + "__mod_u8u8u8u8", "__div_u8u8u8u8", + "__divmod_u16u8u16u8", "__mod_u16u8u16u8", "__div_u16u8u16u8", + "__divmod_u16u16u16u16", "__mod_u16u16u16u16", "__div_u16u16u16u16") ++ compiledFunctions.keySet.filter(_.endsWith(".trampoline")) val unusedRuntimeObjects = objectsThatMayBeUnused.filterNot(name =>{ compiledFunctions.exists{ diff --git a/src/test/scala/millfork/test/WordMathSuite.scala b/src/test/scala/millfork/test/WordMathSuite.scala index 6c9cd0ef..e47b8a34 100644 --- a/src/test/scala/millfork/test/WordMathSuite.scala +++ b/src/test/scala/millfork/test/WordMathSuite.scala @@ -1,6 +1,6 @@ package millfork.test import millfork.Cpu -import millfork.test.emu.{EmuBenchmarkRun, EmuCmosBenchmarkRun, EmuCrossPlatformBenchmarkRun} +import millfork.test.emu.{EmuBenchmarkRun, EmuCmosBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun} import org.scalatest.{AppendedClues, FunSuite, Matchers} /** @@ -628,4 +628,40 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues { m.readByte(0xc006) should equal(x % y) withClue s"= $x %% $y" } } + + test("Word/word multiplication 1") { + multiplyCaseWW1(0, 0) + multiplyCaseWW1(0, 5) + multiplyCaseWW1(7, 0) + multiplyCaseWW1(2, 5) + multiplyCaseWW1(7, 2) + multiplyCaseWW1(100, 2) + multiplyCaseWW1(1000, 2) + multiplyCaseWW1(2, 1000) + multiplyCaseWW1(1522, 1000) + multiplyCaseWW1(54, 4) + multiplyCaseWW1(35000, 9) + multiplyCaseWW1(43, 35000) + multiplyCaseWW1(53459, 48233) + } + + private def multiplyCaseWW1(x: Int, y: Int): Unit = { + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080)( + s""" + | import zp_reg + | word output0 @$$c000 + | word output1 @$$c002 + | word output2 @$$c004 + | void main () { + | output0 = $x + | output0 *= word($y) + | output1 = id($x) * id($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)" + } + } } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index 397a919d..7339119d 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -37,9 +37,16 @@ object EmuRun { ), None, 4, Map(), JobContext(TestErrorReporting.log, new LabelGenerator)) val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, source) TestErrorReporting.log.info(s"Parsing $filename") - MosParser("", preprocessedSource, "", options, features).toAst match { + val parser = MosParser("", preprocessedSource, "", options, features) + parser.toAst match { case Success(x, _) => Some(x) - case _ => None + case f: Failure[_, _] => + TestErrorReporting.log.error(f.toString) + TestErrorReporting.log.error(f.extra.toString) + TestErrorReporting.log.error(f.lastParser.toString) + TestErrorReporting.log.error("Syntax error", Some(parser.lastPosition)) + TestErrorReporting.log.error("Parsing error") + None } }