1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-01-09 13:31:32 +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 * `*=`: multiplication in place
`mutable byte *= constant byte` `mutable byte *= constant byte`
`mutable byte *= byte` (zpreg) `mutable byte *= byte` (zpreg)
`mutable word *= unsigned byte` (zpreg)
* `*'=`: decimal multiplication in place * `*'=`: decimal multiplication in place
`mutable byte *'= constant byte` `mutable byte *'= constant byte`

View File

@ -49,3 +49,28 @@ inline asm byte __mul_u8u8u8() {
} }
#endif #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 ? BNE __mul_u8u8u8_loop
? RTS ? 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( val functionsThatUsePseudoregisterAsInput: Map[String, Set[Int]] = Map(
"__mul_u8u8u8" -> Set(0, 1), "__mul_u8u8u8" -> Set(0, 1),
"__mul_u16u8u16" -> Set(0, 1, 2),
"__adc_decimal" -> Set(2, 3), "__adc_decimal" -> Set(2, 3),
"__sbc_decimal" -> Set(2, 3), "__sbc_decimal" -> Set(2, 3),
"__sub_decimal" -> Set(2, 3)) "__sub_decimal" -> Set(2, 3))

View File

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

View File

@ -45,6 +45,23 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
params.map { case (_, expr) => getExpressionType(ctx, expr).size}.max 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 = { def assertAllArithmeticBytes(msg: String, ctx: CompilationContext, params: List[Expression]): Unit = {
assertAllArithmetic(ctx, params) assertAllArithmetic(ctx, params)
if (params.exists { expr => getExpressionType(ctx, expr).size != 1 }) { 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] = { def compileByteMultiplication(ctx: CompilationContext, v: Expression, c: Int): List[AssemblyLine] = {
val result = ListBuffer[AssemblyLine]() val result = ListBuffer[AssemblyLine]()
// TODO: optimise // TODO: optimise

View File

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

View File

@ -326,6 +326,49 @@ object PseudoregisterBuiltIns {
load ++ calculate 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 = { private def simplicity(env: Environment, expr: Expression): Char = {
val constPart = env.eval(expr) match { val constPart = env.eval(expr) match {
case Some(NumericConstant(_, _)) => 'Z' 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 AssemblyLine0(_, _, CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), NumericConstant(1, _))) if th.name == "__reg" => true
case _ => false 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 _ => Z80DecimalBuiltIns.compileInPlaceShiftRight(ctx, l, r, size)
} }
case "*=" => case "*=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params) assertSizesForMultiplication(ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params) val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r) Z80Multiply.compile8BitInPlaceMultiply(ctx, l, r)
case 2 =>
Z80Multiply.compile16And8BitInPlaceMultiply(ctx, l, r)
}
case "*'=" => case "*'=" =>
assertAllArithmeticBytes("Long multiplication not supported", ctx, params) assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params) val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)

View File

@ -18,6 +18,14 @@ object Z80Multiply {
ctx.env.get[ThingInMemory]("__mul_u8u8u8").toAddress)) 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 * 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 * Calculate A = count * x
*/ */

View File

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

View File

@ -330,4 +330,54 @@ class WordMathSuite extends FunSuite with Matchers {
m.readWord(0xc000) should equal(5) 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))
}
} }