1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-01-08 22:30:34 +00:00

Multiplication u16×u8

This commit is contained in:
Karol Stasiak 2018-12-14 22:50:20 +01:00
parent a73e1eae1e
commit f32d72b51f
13 changed files with 228 additions and 9 deletions

View File

@ -185,6 +185,7 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times.
* `*=`: multiplication in place
`mutable byte *= constant byte`
`mutable byte *= byte` (zpreg)
`mutable word *= unsigned byte` (zpreg)
* `*'=`: decimal multiplication in place
`mutable byte *'= constant byte`

View File

@ -49,3 +49,28 @@ inline asm byte __mul_u8u8u8() {
}
#endif
inline asm word __mul_u16u8u16() {
? LD HL,0
? LD B,8
__mul_u16u8u16_loop:
? ADD HL,HL
? ADC A,A
#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
? JR NC,__mul_u16u8u16_skip
#else
? JP NC,__mul_u16u8u16_skip
#endif
? ADD HL,DE
__mul_u16u8u16_skip:
#if CPUFEATURE_Z80
? DJNZ __mul_u16u8u16_loop
#elseif CPUFEATURE_GAMEBOY
? DEC B
? JR NZ,__mul_u16u8u16_loop
#else
? DEC B
? JP NZ,__mul_u16u8u16_loop
#endif
? RET
}

View File

@ -17,3 +17,29 @@ __mul_u8u8u8_start:
? BNE __mul_u8u8u8_loop
? RTS
}
#if ZPREG_SIZE >= 3
asm byte __mul_u16u8u16() {
? LDA #0
? TAX
? JMP __mul_u16u8u16_start
__mul_u16u8u16_add:
? CLC
? ADC __reg
? TAY
? TXA
? ADC __reg + 1
? TAX
? TYA
__mul_u16u8u16_loop:
? ASL __reg
? ROL __reg + 1
__mul_u16u8u16_start:
? LSR __reg + 2
? BCS __mul_u16u8u16_add
? BNE __mul_u16u8u16_loop
? RTS
}
#endif

View File

@ -12,6 +12,7 @@ object ZeropageRegisterOptimizations {
val functionsThatUsePseudoregisterAsInput: Map[String, Set[Int]] = Map(
"__mul_u8u8u8" -> Set(0, 1),
"__mul_u16u8u16" -> Set(0, 1, 2),
"__adc_decimal" -> Set(2, 3),
"__sbc_decimal" -> Set(2, 3),
"__sub_decimal" -> Set(2, 3))

View File

@ -175,11 +175,11 @@ case class CpuImportance(a: Importance = UnknownImportance,
object ReverseFlowAnalyzer {
val readsA = Set("__mul_u8u8u8")
val readsA = Set("__mul_u8u8u8", "__mul_u16u8u16")
val readsB = Set("")
val readsC = Set("")
val readsD = Set("__mul_u8u8u8")
val readsE = Set("")
val readsD = Set("__mul_u8u8u8","__mul_u16u8u16")
val readsE = Set("__mul_u16u8u16")
val readsH = Set("")
val readsL = Set("")

View File

@ -45,6 +45,23 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
params.map { case (_, expr) => getExpressionType(ctx, expr).size}.max
}
def assertSizesForMultiplication(ctx: CompilationContext, params: List[Expression]): Unit = {
assertAllArithmetic(ctx, params)
//noinspection ZeroIndexToHead
val lSize = getExpressionType(ctx, params(0)).size
val rType = getExpressionType(ctx, params(1))
val rSize = rType.size
if (lSize != 1 && lSize != 2) {
ctx.log.fatal("Long multiplication not supported", params.head.position)
}
if (rSize != 1) {
ctx.log.fatal("Long multiplication not supported", params.head.position)
}
if (rType.isSigned) {
ctx.log.fatal("Signed multiplication not supported", params.head.position)
}
}
def assertAllArithmeticBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = {
assertAllArithmetic(ctx, params)
if (params.exists { expr => getExpressionType(ctx, expr).size != 1 }) {

View File

@ -831,6 +831,21 @@ object BuiltIns {
}
}
def compileInPlaceWordMultiplication(ctx: CompilationContext, v: LhsExpression, addend: Expression): List[AssemblyLine] = {
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
ctx.env.eval(addend) match {
case Some(NumericConstant(0, _)) =>
MosExpressionCompiler.compile(ctx, v, None, NoBranching) ++ MosExpressionCompiler.compileAssignment(ctx, LiteralExpression(0, 2), v)
case Some(NumericConstant(1, _)) =>
MosExpressionCompiler.compile(ctx, v, None, NoBranching)
case _ =>
// TODO: optimize?
PseudoregisterBuiltIns.compileWordMultiplication(ctx, Some(v), addend, storeInRegLo = true) ++
MosExpressionCompiler.compileAssignment(ctx, VariableExpression("__reg.loword"), v)
}
}
def compileByteMultiplication(ctx: CompilationContext, v: Expression, c: Int): List[AssemblyLine] = {
val result = ListBuffer[AssemblyLine]()
// TODO: optimise

View File

@ -1029,9 +1029,14 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
}
case "*=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
BuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
assertSizesForMultiplication(ctx, params)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileInPlaceByteMultiplication(ctx, l, r)
case 2 =>
BuiltIns.compileInPlaceWordMultiplication(ctx, l, r)
}
case "*'=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)

View File

@ -326,6 +326,49 @@ object PseudoregisterBuiltIns {
load ++ calculate
}
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))
return Nil
}
val b = ctx.env.get[Type]("byte")
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(b -> RegisterVariable(MosRegister.A, b)), 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))
} else if (!usesReg2(code1)) {
code2 ++ List(AssemblyLine.zeropage(STA, reg, 2)) ++ code1 ++ List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1))
} else {
code2 ++ List(AssemblyLine.implied(PHA)) ++ code1 ++ List(
AssemblyLine.zeropage(STA, reg),
AssemblyLine.zeropage(STX, reg, 1),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 2)
)
}
case None =>
val code2 = MosExpressionCompiler.compile(ctx, param2, Some(b -> RegisterVariable(MosRegister.A, b)), BranchSpec.None)
if (!usesRegLo(code2) && !usesRegHi(code2)) {
List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) ++ code2 ++ List(AssemblyLine.zeropage(STA, reg, 2))
} else {
List(AssemblyLine.implied(PHA), AssemblyLine.implied(TXA), AssemblyLine.implied(PHA)) ++ code2 ++ List(
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg)
)
}
}
val calculate = AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[FunctionInMemory]("__mul_u16u8u16"), ctx.options) ::
(if (storeInRegLo) List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) else Nil)
load ++ calculate
}
private def simplicity(env: Environment, expr: Expression): Char = {
val constPart = env.eval(expr) match {
case Some(NumericConstant(_, _)) => 'Z'
@ -353,4 +396,10 @@ object PseudoregisterBuiltIns {
case AssemblyLine0(_, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _))) if th.name == "__reg" => true
case _ => false
}
def usesReg2(code: List[AssemblyLine]): Boolean = code.forall{
case AssemblyLine0(JSR | BSR | TCD | TDC, _, _) => true
case AssemblyLine0(_, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(2, _))) if th.name == "__reg" => true
case _ => false
}
}

View File

@ -746,9 +746,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case _ => Z80DecimalBuiltIns.compileInPlaceShiftRight(ctx, l, r, size)
}
case "*=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r)
assertSizesForMultiplication(ctx, params)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r)
case 2 =>
Z80Multiply.compile16And8BitInPlaceMultiply(ctx, l, r)
}
case "*'=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)

View File

@ -18,6 +18,14 @@ object Z80Multiply {
ctx.env.get[ThingInMemory]("__mul_u8u8u8").toAddress))
}
/**
* Compiles A = A * DE
*/
private def multiplication16And8(ctx: CompilationContext): List[ZLine] = {
List(ZLine(ZOpcode.CALL, NoRegisters,
ctx.env.get[ThingInMemory]("__mul_u16u8u16").toAddress))
}
/**
* Calculate A = l * r
*/
@ -92,6 +100,21 @@ object Z80Multiply {
}
}
/**
* Calculate A = l * r
*/
def compile16And8BitInPlaceMultiply(ctx: CompilationContext, l: LhsExpression, r: Expression): List[ZLine] = {
ctx.env.eval(r) match {
case Some(c) =>
Z80ExpressionCompiler.compileToDE(ctx, l) ++ List(ZLine.ldImm8(ZRegister.A, c)) ++ multiplication16And8(ctx) ++ Z80ExpressionCompiler.storeHL(ctx, l, signedSource = false)
case _ =>
val lw = Z80ExpressionCompiler.compileToDE(ctx, l)
val rb = Z80ExpressionCompiler.compileToA(ctx, r)
val loadRegisters = lw ++ Z80ExpressionCompiler.stashDEIfChanged(ctx, rb)
loadRegisters ++ multiplication16And8(ctx) ++ Z80ExpressionCompiler.storeHL(ctx, l, signedSource = false)
}
}
/**
* Calculate A = count * x
*/

View File

@ -12,7 +12,9 @@ object UnusedFunctions extends NodeOptimization {
private val operatorImplementations: List[(String, Int, String)] = List(
("*", 2, "__mul_u8u8u8"),
("*", 3, "__mul_u16u8u16"),
("*=", 2, "__mul_u8u8u8"),
("*=", 2, "__mul_u16u8u16"),
("+'", 4, "__adc_decimal"),
("+'=", 4, "__adc_decimal"),
("-'", 4, "__sub_decimal"),

View File

@ -330,4 +330,54 @@ class WordMathSuite extends FunSuite with Matchers {
m.readWord(0xc000) should equal(5)
}
}
test("Word multiplication 5") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)("""
| word output @$c000
| void main () {
| output = alot()
| output *= five()
| }
| noinline word alot() {
| return 4532
| }
| noinline byte five() {
| return 5
| }
| import zp_reg
""".stripMargin){ m =>
m.readWord(0xc000) should equal(4532 * 5)
}
}
test("In-place word/byte multiplication") {
multiplyCase1(0, 0)
multiplyCase1(0, 1)
multiplyCase1(0, 2)
multiplyCase1(0, 5)
multiplyCase1(1, 0)
multiplyCase1(5, 0)
multiplyCase1(7, 0)
multiplyCase1(2, 5)
multiplyCase1(7, 2)
multiplyCase1(100, 2)
multiplyCase1(1000, 2)
multiplyCase1(54, 4)
multiplyCase1(2, 100)
multiplyCase1(500, 50)
multiplyCase1(4, 54)
}
private def multiplyCase1(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80)(
s"""
| import zp_reg
| word output @$$c000
| void main () {
| output = $x
| output *= $y
| }
""".
stripMargin)(_.readWord(0xc000) should equal(x * y))
}
}