1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-01-11 12:29:46 +00:00

Add unsigned 16-bit division

This commit is contained in:
Karol Stasiak 2019-09-15 19:47:19 +02:00
parent f036de0ee1
commit 635870585e
11 changed files with 385 additions and 77 deletions

View File

@ -6,7 +6,7 @@
* Added `-R` option for specifying extra commandline parameters for emulators. * 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. * Added preliminary support for EasyFlash.

View File

@ -97,6 +97,7 @@ TODO
* `/`, `%%`: unsigned division and unsigned modulo * `/`, `%%`: unsigned division and unsigned modulo
`unsigned byte / unsigned byte` (zpreg) `unsigned byte / unsigned byte` (zpreg)
`word / unsigned byte` (zpreg) `word / unsigned byte` (zpreg)
`word / word` (zpreg)
`constant word / constant word` `constant word / constant word`
`constant long / constant long` `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 * `/=`, `%%=`: unsigned division and modulo in place
`mutable unsigned byte /= unsigned byte` (zpreg) `mutable unsigned byte /= unsigned byte` (zpreg)
`mutable word /= unsigned byte` (zpreg) `mutable word /= unsigned byte` (zpreg)
`mutable word /= word` (zpreg)
There are no `||=`, `&&=` or `>>>>=` operators. There are no `||=`, `&&=` or `>>>>=` operators.

View File

@ -110,43 +110,217 @@ __mul_u16u8u16_skip:
#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY #if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
noinline asm word __mul_u16u16u16() { noinline asm word __mul_u16u16u16() {
ld hl,0 LD HL,0
ld a,16 LD A,16
__mul_u16u16u16_loop: __mul_u16u16u16_loop:
add hl,hl ADD HL,HL
rl e RL E
rl d RL D
jr nc,__mul_u16u16u16_skip JR NC,__mul_u16u16u16_skip
add hl,bc ADD HL,BC
__mul_u16u16u16_skip: __mul_u16u16u16_skip:
dec a DEC A
jr nz,__mul_u16u16u16_loop JR NZ,__mul_u16u16u16_loop
ret RET
} }
#else #else
noinline asm word __mul_u16u16u16() { noinline asm word __mul_u16u16u16() {
ld hl,0 ld hl,0
call __mul_u16u16u16_q CALL __mul_u16u16u16_q
call __mul_u16u16u16_q CALL __mul_u16u16u16_q
call __mul_u16u16u16_q CALL __mul_u16u16u16_q
jp __mul_u16u16u16_q JP __mul_u16u16u16_q
} }
noinline asm word __mul_u16u16u16_q(word hl) { noinline asm word __mul_u16u16u16_q(word hl) {
call __mul_u16u16u16_s CALL __mul_u16u16u16_s
call __mul_u16u16u16_s CALL __mul_u16u16u16_s
call __mul_u16u16u16_s CALL __mul_u16u16u16_s
jp __mul_u16u16u16_s JP __mul_u16u16u16_s
} }
noinline asm word __mul_u16u16u16_s(word hl) { noinline asm word __mul_u16u16u16_s(word hl) {
add hl,hl ADD HL,HL
ld a,e LD A,E
add a,e ADD A,E
ld e,a LD E,A
ld a,d LD A,D
adc a,d ADC A,D
ld d,a LD D,A
ret nc RET NC
add hl,bc ADD HL,BC
ret RET
} }
#endif #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

View File

@ -200,7 +200,6 @@ noinline asm word __divmod_u16u16u16u16() {
? PHA ? PHA
? LDA __reg+2 ? LDA __reg+2
? PHA ? PHA
? TSX
#else #else
? LDA __reg+2 ? LDA __reg+2
? STA __reg+4 ? STA __reg+4
@ -218,6 +217,7 @@ noinline asm word __divmod_u16u16u16u16() {
#if ZPREG_SIZE < 6 #if ZPREG_SIZE < 6
? LDA #16 ? LDA #16
? PHA ? PHA
? TSX
#else #else
? LDX #16 ? LDX #16
#endif #endif

View File

@ -187,10 +187,10 @@ object ReverseFlowAnalyzer {
val readsA: Set[String] = Set("__mul_u8u8u8", "__mul_u16u8u16", "call") val readsA: Set[String] = Set("__mul_u8u8u8", "__mul_u16u8u16", "call")
val readsB: Set[String] = Set("__mul_u16u16u16") val readsB: Set[String] = Set("__mul_u16u16u16")
val readsC: 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 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") val readsE: Set[String] = Set("__mul_u16u8u16", "call", "__mul_u16u16u16", "__divmod_u16u16u16u16")
val readsH: Set[String] = Set("__divmod_u16u8u16u8", "call") val readsH: Set[String] = Set("__divmod_u16u8u16u8", "call", "__divmod_u16u16u16u16")
val readsL: Set[String] = Set("__divmod_u16u8u16u8", "call") val readsL: Set[String] = Set("__divmod_u16u8u16u8", "call", "__divmod_u16u16u16u16")
val preservesH: Set[String] = Set("__mul_u8u8u8") val preservesH: Set[String] = Set("__mul_u8u8u8")
val preservesL: Set[String] = Set("__mul_u8u8u8") val preservesL: Set[String] = Set("__mul_u8u8u8")

View File

@ -77,15 +77,24 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
val lSize = lType.size val lSize = lType.size
val rType = getExpressionType(ctx, params(1)) val rType = getExpressionType(ctx, params(1))
val rSize = rType.size val rSize = rType.size
if (lSize > 2 || rSize > 2) { if (lSize != 1 && lSize != 2) {
ctx.log.error("Long division not supported", params.head.position) ctx.log.error("Long division not supported", params.head.position)
} }
if (rSize > 1) { if (rSize != 1 && rSize != 2) {
ctx.log.error("Division by words not supported", params.head.position) ctx.log.error("Long division not supported", params.head.position)
} }
if (lType.isSigned || rType.isSigned) { if (inPlace) {
if (lSize == 2 && rSize == 1 && rType.isSigned) {
ctx.log.error("Signed division not supported", params.head.position) 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)
}
}
} }
def assertAllArithmeticBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = { def assertAllArithmeticBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = {

View File

@ -683,9 +683,58 @@ object PseudoregisterBuiltIns {
load ++ calculate 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] = { def compileUnsignedWordByByteDivision(ctx: CompilationContext, param1: Expression, param2: Expression, modulo: Boolean): List[AssemblyLine] = {
(AbstractExpressionCompiler.getExpressionType(ctx, param1).size, (AbstractExpressionCompiler.getExpressionType(ctx, param1).size,
AbstractExpressionCompiler.getExpressionType(ctx, param2).size) match { AbstractExpressionCompiler.getExpressionType(ctx, param2).size) match {
case (2 | 1, 2) => return compileWordWordDivision(ctx, Some(param1), param2, modulo)
case (2 | 1, 1) => // ok case (2 | 1, 1) => // ok
case _ => ctx.log.fatal("Invalid code path", param2.position) case _ => ctx.log.fatal("Invalid code path", param2.position)
} }

View File

@ -1092,27 +1092,29 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
Nil Nil
} }
case 2 => 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 { calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match {
case Some((LocalVariableAddressViaHL, List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), addr)))) => 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.ldAbs8(addr, ZRegister.A),
ZLine.register(XOR, ZRegister.A), ZLine.register(XOR, ZRegister.A),
ZLine.ldAbs8(addr+1, ZRegister.A) ZLine.ldAbs8(addr+1, ZRegister.A)
) )
case Some((lvo@LocalVariableAddressViaHL, code)) => 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.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.register(INC_16, ZRegister.HL), ZLine.register(INC_16, ZRegister.HL),
ZLine.ldImm8(ZRegister.MEM_HL, 0) ZLine.ldImm8(ZRegister.MEM_HL, 0)
) )
case Some((lvo@LocalVariableAddressViaIX(offset), code)) => 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.ldViaIx(offset, ZRegister.A),
ZLine.ld0ViaIx(offset + 1) ZLine.ld0ViaIx(offset + 1)
) )
case Some((lvo@LocalVariableAddressViaIY(offset), code)) => 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.ldViaIy(offset, ZRegister.A),
ZLine.ld0ViaIy(offset + 1) ZLine.ld0ViaIy(offset + 1)
) )
@ -1125,7 +1127,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case Some((lvo@LocalVariableAddressViaHL, code)) => case Some((lvo@LocalVariableAddressViaHL, code)) =>
code ++ code ++
stashHLIfChanged(ctx, 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)) 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)) 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)) => case Some((lvo@LocalVariableAddressViaIX(offset), code)) =>
code ++ code ++
Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = false) ++ Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo, rhsWord) ++
storeHLViaIX(ctx, offset, 2, signedSource = false) storeHLViaIX(ctx, offset, 2, signedSource = false)
case Some((lvo@LocalVariableAddressViaIY(offset), code)) => case Some((lvo@LocalVariableAddressViaIY(offset), code)) =>
code ++ code ++
Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r, modulo = false) ++ Z80Multiply.compileUnsignedWordDivision(ctx, Left(lvo), r, modulo, rhsWord) ++
storeHLViaIY(ctx, offset, 2, signedSource = false) storeHLViaIY(ctx, offset, 2, signedSource = false)
case _ => case _ =>
ctx.log.error("Invalid left-hand side", l.position) ctx.log.error("Invalid left-hand side", l.position)
@ -1153,14 +1155,15 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
assertSizesForDivision(ctx, params, inPlace = false) assertSizesForDivision(ctx, params, inPlace = false)
val (l, r, size) = assertArithmeticBinary(ctx, params) val (l, r, size) = assertArithmeticBinary(ctx, params)
val modulo = f.functionName == "%%" val modulo = f.functionName == "%%"
val rhsWord = getExpressionType(ctx, r).size == 2
size match { size match {
case 1 => case 1 =>
targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, Right(l), r, modulo), isSigned = false) targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, Right(l), r, modulo), isSigned = false)
case 2 => case 2 =>
if (modulo) { if (modulo && !rhsWord) {
targetifyA(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r, modulo = true), isSigned = false) targetifyA(ctx, target, Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo = true, rhsWord = false), isSigned = false)
} else { } else {
targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r, modulo = false)) targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordDivision(ctx, Right(l), r, modulo, rhsWord))
} }
} }
case "*'=" => case "*'=" =>

View File

@ -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 { val pb = p match {
case Right(pp) => Z80ExpressionCompiler.compileToHL(ctx, pp) case Right(pp) => Z80ExpressionCompiler.compileToHL(ctx, pp)
case Left(LocalVariableAddressViaHL) => List( case Left(LocalVariableAddressViaHL) => List(
@ -136,14 +136,16 @@ object Z80Multiply {
return pb return pb
case Some(NumericConstant(1, _)) => case Some(NumericConstant(1, _)) =>
if (modulo) { 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 { } else {
return pb return pb
} }
case Some(NumericConstant(qc, _)) if qc <= 255 && isPowerOfTwoUpTo15(qc) => case Some(NumericConstant(qc, _)) if qc <= 255 && isPowerOfTwoUpTo15(qc) =>
val count = Integer.numberOfTrailingZeros(qc.toInt) val count = Integer.numberOfTrailingZeros(qc.toInt)
if (modulo) { 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 { } else {
val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes) val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)
val shiftHL = if (extendedOps) { val shiftHL = if (extendedOps) {
@ -164,8 +166,14 @@ object Z80Multiply {
} }
return pb ++ shiftHL 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 _ => case _ =>
} }
if (!rhsWord) {
val qb = Z80ExpressionCompiler.compileToA(ctx, q) val qb = Z80ExpressionCompiler.compileToA(ctx, q)
val load = if (qb.exists(Z80ExpressionCompiler.changesHL)) { val load = if (qb.exists(Z80ExpressionCompiler.changesHL)) {
pb ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, qb) ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) pb ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, qb) ++ List(ZLine.ld8(ZRegister.D, ZRegister.A))
@ -175,6 +183,26 @@ object Z80Multiply {
pb ++ qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A)) pb ++ qb ++ List(ZLine.ld8(ZRegister.D, ZRegister.A))
} }
load :+ ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u8u16u8").toAddress) load :+ ZLine(ZOpcode.CALL, NoRegisters, ctx.env.get[FunctionInMemory]("__divmod_u16u8u16u8").toAddress)
} else {
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))
}
}
} }
/** /**
@ -216,7 +244,7 @@ object Z80Multiply {
compileUnsignedByteDivisionImpl(ctx, p, qq.toInt, modulo) compileUnsignedByteDivisionImpl(ctx, p, qq.toInt, modulo)
} }
case _ => case _ =>
val call = compileUnsignedWordByByteDivision(ctx, p, q, modulo = modulo) val call = compileUnsignedWordDivision(ctx, p, q, modulo = modulo, rhsWord = false)
if (modulo) { if (modulo) {
call call
} else { } else {

View File

@ -17,23 +17,30 @@ object UnusedFunctions extends NodeOptimization {
("*=", 2, "__mul_u8u8u8"), ("*=", 2, "__mul_u8u8u8"),
("*=", 2, "__mul_u16u8u16"), ("*=", 2, "__mul_u16u8u16"),
("*=", 4, "__mul_u16u16u16"), ("*=", 4, "__mul_u16u16u16"),
("/=", 0, "__divmod_u16u8u16u8"),
("/", 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, "__mod_u8u8u8u8"),
("/", 2, "__mod_u16u8u16u8"), ("%%", 4, "__divmod_u16u16u16u16"),
("/=", 2, "__div_u16u8u16u8"), ("%%", 4, "__mod_u16u16u16u16"),
("%%=", 0, "__divmod_u16u8u16u8"),
("%%=", 2, "__mod_u16u8u16u8"),
("%%=", 2, "__mod_u8u8u8u8"),
("%%=", 4, "__divmod_u16u16u16u16"),
("%%=", 4, "__mod_u16u16u16u16"),
("/", 0, "__divmod_u16u8u16u8"),
("/", 2, "__div_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, "__adc_decimal"), ("+'=", 4, "__adc_decimal"),
("-'", 4, "__sub_decimal"), ("-'", 4, "__sub_decimal"),

View File

@ -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)" 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)"
}
}
} }