1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-25 19:29:49 +00:00

Unsigned byte division by a constant

This commit is contained in:
Karol Stasiak 2019-06-05 18:36:39 +02:00
parent 326e9d0585
commit 010b44f23e
14 changed files with 247 additions and 6 deletions

View File

@ -20,6 +20,8 @@
* Added structs and unions. * Added structs and unions.
* Added unsigned byte division and modulo by a constant.
* Pointers can now be allocated anywhere. * Pointers can now be allocated anywhere.
* Pointers can now be typed. * Pointers can now be typed.

View File

@ -28,6 +28,8 @@ where `11111` is a sequential number and `xx` is the type:
* `ds` decimal right shift operation * `ds` decimal right shift operation
* `dv` division and modulo operations
* `el` beginning of the "else" block in an `if` statement * `el` beginning of the "else" block in an `if` statement
* `ew` end of a `while` statement * `ew` end of a `while` statement

View File

@ -52,6 +52,9 @@ In the descriptions below, arguments to the operators are explained as follows:
* `constant` means a compile-time constant * `constant` means a compile-time constant
* `simpleconstant` means a compile-time constant evaluable at the first compilation pass
(eg. a literal or a combination of literals, not an undefined address)
* `simple` means either: a constant, a non-stack variable, * `simple` means either: a constant, a non-stack variable,
a pointer indexed with a constant, a pointer indexed with a non-stack variable, a pointer indexed with a constant, a pointer indexed with a non-stack variable,
an array indexed with a constant, an array indexed with a non-stack variable, an array indexed with a constant, an array indexed with a non-stack variable,
@ -91,7 +94,11 @@ TODO
`word * byte` (zpreg) `word * byte` (zpreg)
`byte * word` (zpreg) `byte * word` (zpreg)
There are no division, remainder or modulo operators. * `/`, `%%`: unsigned division and unsigned modulo
`byte / simpleconstant byte`
`constant word / constant word`
`constant long / constant long`
## Bitwise operators ## Bitwise operators

View File

@ -62,7 +62,7 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
} }
} else { } else {
if (lSize > 2 || rSize > 2 || lSize + rSize > 3) { if (lSize > 2 || rSize > 2 || lSize + rSize > 3) {
ctx.log.error("Signed multiplication not supported", params.head.position) ctx.log.error("Long multiplication not supported", params.head.position)
} }
if (lSize == 2 && rType.isSigned) { if (lSize == 2 && rType.isSigned) {
ctx.log.error("Signed multiplication not supported", params.head.position) ctx.log.error("Signed multiplication not supported", params.head.position)
@ -79,6 +79,21 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
} }
} }
def assertSizesForDivision(ctx: CompilationContext, params: List[Expression], inPlace: Boolean): Unit = {
assertAllArithmetic(ctx, params)
//noinspection ZeroIndexToHead
val lType = getExpressionType(ctx, params(0))
val lSize = lType.size
val rType = getExpressionType(ctx, params(1))
val rSize = rType.size
if (lSize > 1 || rSize > 1) {
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)
}
}
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 }) {
@ -323,7 +338,7 @@ object AbstractExpressionCompiler {
case 1 => b case 1 => b
case 2 => w case 2 => w
} }
case FunctionCallExpression("*" | "|" | "&" | "^", params) => params.map { e => getExpressionType(env, log, e).size }.max match { case FunctionCallExpression("*" | "|" | "&" | "^" | "/" | "%%", params) => params.map { e => getExpressionType(env, log, e).size }.max match {
case 1 => b case 1 => b
case 2 => w case 2 => w
case _ => log.error("Combining values bigger than words", expr.position); w case _ => log.error("Combining values bigger than words", expr.position); w

View File

@ -1009,6 +1009,68 @@ object BuiltIns {
} }
} }
def compileUnsignedByteDivision(ctx: CompilationContext, p: Expression, q: Expression, modulo: Boolean): List[AssemblyLine] = {
if (ctx.options.zpRegisterSize < 1) {
ctx.log.error("Byte division requires the zeropage pseudoregister", p.position)
return Nil
}
ctx.env.eval(q) match {
case Some(NumericConstant(qq, _)) =>
if (qq < 0) {
ctx.log.error("Unsigned division by negative constant", q.position)
Nil
} else if (qq == 0) {
ctx.log.error("Unsigned division by zero", q.position)
Nil
} else if (qq > 255) {
if (modulo) MosExpressionCompiler.compileToA(ctx, p)
else List(AssemblyLine.immediate(LDA, 0))
} else {
compileUnsignedByteDivision(ctx, p, qq.toInt, modulo)
}
case Some(_) =>
ctx.log.error("Unsigned division by unknown constant", q.position)
Nil
case None =>
ctx.log.error("Unsigned division by a variable expression", q.position)
Nil
}
}
def compileUnsignedByteDivision(ctx: CompilationContext, p: Expression, q: Int, modulo: Boolean): List[AssemblyLine] = {
val reg = ctx.env.get[VariableInMemory]("__reg")
val initP = MosExpressionCompiler.compileToA(ctx, p)
val result = ListBuffer[AssemblyLine]()
if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
result ++= initP
result += AssemblyLine.zeropage(STZ, reg)
} else if (MosExpressionCompiler.changesZpreg(initP, 0)) {
result ++= initP
result += AssemblyLine.implied(PHA)
result += AssemblyLine.immediate(LDA, 0)
result += AssemblyLine.zeropage(STA, reg)
result += AssemblyLine.implied(PLA)
} else {
result += AssemblyLine.immediate(LDA, 0)
result += AssemblyLine.zeropage(STA, reg)
result ++= initP
}
for (i <- 7.to(0, -1)) {
if ((q << i) <= 255) {
val lbl = ctx.nextLabel("dv")
result += AssemblyLine.immediate(CMP, q << i)
result += AssemblyLine.relative(BCC, lbl)
result += AssemblyLine.immediate(SBC, q << i)
result += AssemblyLine.label(lbl)
result += AssemblyLine.zeropage(ROL, reg)
}
}
if (!modulo) {
result += AssemblyLine.zeropage(LDA, reg)
}
result.toList
}
def compileInPlaceByteAddition(ctx: CompilationContext, v: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = { def compileInPlaceByteAddition(ctx: CompilationContext, v: LhsExpression, addend: Expression, subtract: Boolean, decimal: Boolean): List[AssemblyLine] = {
if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode) && ctx.options.zpRegisterSize < 4) { if (decimal && !ctx.options.flag(CompilationFlag.DecimalMode) && ctx.options.zpRegisterSize < 4) {
ctx.log.error("Unsupported decimal operation. Consider increasing the size of the zeropage register.", v.position) ctx.log.error("Unsupported decimal operation. Consider increasing the size of the zeropage register.", v.position)

View File

@ -1308,6 +1308,20 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
case 2 => case 2 =>
BuiltIns.compileInPlaceWordMultiplication(ctx, l, r) BuiltIns.compileInPlaceWordMultiplication(ctx, l, r)
} }
case "/=" | "%%=" =>
assertSizesForDivision(ctx, params, inPlace = true)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
BuiltIns.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%=") ++ compileByteStorage(ctx, MosRegister.A, l)
}
case "/" | "%%" =>
assertSizesForDivision(ctx, params, inPlace = false)
val (l, r, size) = assertArithmeticBinary(ctx, params)
size match {
case 1 =>
BuiltIns.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%")
}
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

@ -880,6 +880,26 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case 2 => case 2 =>
Z80Multiply.compile16And8BitInPlaceMultiply(ctx, l, r) Z80Multiply.compile16And8BitInPlaceMultiply(ctx, l, r)
} }
case "/=" | "%%=" =>
assertSizesForDivision(ctx, params, inPlace = true)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
calculateAddressToAppropriatePointer(ctx, l, forWriting = true) match {
case Some((lvo, code)) =>
code ++ (Z80Multiply.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%=") :+ ZLine.ld8(lvo, ZRegister.A))
case None =>
ctx.log.error("Invalid left-hand side", l.position)
Nil
}
}
case "/" | "%%" =>
assertSizesForDivision(ctx, params, inPlace = false)
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, l, r, f.functionName == "%%"), false)
}
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

@ -1,10 +1,13 @@
package millfork.compiler.z80 package millfork.compiler.z80
import millfork.CompilationFlag
import millfork.assembly.z80._ import millfork.assembly.z80._
import millfork.compiler.{AbstractExpressionCompiler, CompilationContext} import millfork.compiler.{AbstractExpressionCompiler, CompilationContext}
import millfork.env._ import millfork.env._
import millfork.node.{ConstantArrayElementExpression, Expression, LhsExpression, ZRegister} import millfork.node.{ConstantArrayElementExpression, Expression, LhsExpression, ZRegister}
import scala.collection.mutable.ListBuffer
/** /**
* @author Karol Stasiak * @author Karol Stasiak
*/ */
@ -100,6 +103,67 @@ object Z80Multiply {
} }
} }
/**
* Calculate A = p / q or A = p %% q
*/
def compileUnsignedByteDivision(ctx: CompilationContext, p: LhsExpression, q: Expression, modulo: Boolean): List[ZLine] = {
ctx.env.eval(q) match {
case Some(NumericConstant(qq, _)) =>
if (qq < 0) {
ctx.log.error("Unsigned division by negative constant", q.position)
Nil
} else if (qq == 0) {
ctx.log.error("Unsigned division by zero", q.position)
Nil
} else if (qq > 255) {
if (modulo) Z80ExpressionCompiler.compileToA(ctx, p)
else List(ZLine.ldImm8(ZRegister.A, 0))
} else {
compileUnsignedByteDivisionImpl(ctx, p, qq.toInt, modulo)
}
case Some(_) =>
ctx.log.error("Unsigned division by unknown constant", q.position)
Nil
case None =>
ctx.log.error("Unsigned division by a variable expression", q.position)
Nil
}
}
/**
* Calculate A = p / q or A = p %% q
*/
def compileUnsignedByteDivisionImpl(ctx: CompilationContext, p: LhsExpression, q: Int, modulo: Boolean): List[ZLine] = {
import ZRegister._
import ZOpcode._
val result = ListBuffer[ZLine]()
result ++= Z80ExpressionCompiler.compileToA(ctx, p)
result += ZLine.ldImm8(E, 0)
for (i <- 7.to(0, -1)) {
if ((q << i) <= 255) {
val lbl = ctx.nextLabel("dv")
result += ZLine.imm8(CP, q << i)
result += ZLine.jumpR(ctx, lbl, IfFlagSet(ZFlag.C))
result += ZLine.imm8(SUB, q << i)
result += ZLine.label(lbl)
result += ZLine.implied(CCF) // TODO: optimize?
if (ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)) {
result += ZLine.register(RL, E)
} else {
result += ZLine.ld8(D, A)
result += ZLine.ld8(A, E)
result += ZLine.implied(RLA)
result += ZLine.ld8(E, A)
result += ZLine.ld8(A, D)
}
}
}
if (!modulo) {
result += ZLine.ld8(A, E)
}
result.toList
}
/** /**
* Calculate HL = l * r * Calculate HL = l * r
*/ */

View File

@ -269,6 +269,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
object MathOperator extends Enumeration { object MathOperator extends Enumeration {
val Plus, Minus, Times, Shl, Shr, Shl9, Shr9, Plus9, DecimalPlus9, val Plus, Minus, Times, Shl, Shr, Shl9, Shr9, Plus9, DecimalPlus9,
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShl9, DecimalShr, DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShl9, DecimalShr,
Divide, Modulo,
And, Or, Exor = Value And, Or, Exor = Value
} }
@ -282,6 +283,7 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
Shl | DecimalShl | Shl | DecimalShl |
Shl9 | DecimalShl9 | Shl9 | DecimalShl9 |
Shr | DecimalShr | Shr | DecimalShr |
Divide | Modulo |
And | Or | Exor => lhs.isProvablyNonnegative && rhs.isProvablyNonnegative And | Or | Exor => lhs.isProvablyNonnegative && rhs.isProvablyNonnegative
case _ => false case _ => false
} }
@ -343,6 +345,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case MathOperator.Exor => c case MathOperator.Exor => c
case MathOperator.Or => c case MathOperator.Or => c
case MathOperator.And => Constant.Zero case MathOperator.And => Constant.Zero
case MathOperator.Divide => Constant.Zero
case MathOperator.Modulo => Constant.Zero
case _ => CompoundConstant(operator, l, r) case _ => CompoundConstant(operator, l, r)
} }
case (c, NumericConstant(0, 1)) => case (c, NumericConstant(0, 1)) =>
@ -378,6 +382,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
case MathOperator.Exor => (lv ^ rv) & bitmask case MathOperator.Exor => (lv ^ rv) & bitmask
case MathOperator.Or => lv | rv case MathOperator.Or => lv | rv
case MathOperator.And => lv & rv & bitmask case MathOperator.And => lv & rv & bitmask
case MathOperator.Divide if lv >= 0 && rv >= 0 => lv / rv
case MathOperator.Modulo if lv >= 0 && rv >= 0 => lv % rv
case MathOperator.DecimalPlus if ls == 1 && rs == 1 => case MathOperator.DecimalPlus if ls == 1 && rs == 1 =>
asDecimal(lv & 0xff, rv & 0xff, _ + _) & 0xff asDecimal(lv & 0xff, rv & 0xff, _ + _) & 0xff
case MathOperator.DecimalMinus if ls == 1 && rs == 1 && lv.&(0xff) >= rv.&(0xff) => case MathOperator.DecimalMinus if ls == 1 && rs == 1 && lv.&(0xff) >= rv.&(0xff) =>

View File

@ -730,6 +730,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
constantOperation(MathOperator.DecimalTimes, params) constantOperation(MathOperator.DecimalTimes, params)
case "*" => case "*" =>
constantOperation(MathOperator.Times, params) constantOperation(MathOperator.Times, params)
case "/" =>
constantOperation(MathOperator.Divide, params)
case "%%" =>
constantOperation(MathOperator.Modulo, params)
case "&&" | "&" => case "&&" | "&" =>
constantOperation(MathOperator.And, params) constantOperation(MathOperator.And, params)
case "^" => case "^" =>

View File

@ -145,6 +145,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case MathOperator.And => l & r case MathOperator.And => l & r
case MathOperator.Exor => l ^ r case MathOperator.Exor => l ^ r
case MathOperator.Or => l | r case MathOperator.Or => l | r
case MathOperator.Divide => l / r
case MathOperator.Modulo => l % r
} }
} }
} }

View File

@ -657,13 +657,13 @@ object MfParser {
} }
val mfOperators = List( val mfOperators = List(
List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'=", "="), List("+=", "-=", "+'=", "-'=", "^=", "&=", "|=", "*=", "*'=", "<<=", ">>=", "<<'=", ">>'=", "/=", "%%=", "="),
List("||", "^^"), List("||", "^^"),
List("&&"), List("&&"),
List("==", "<=", ">=", "!=", "<", ">"), List("==", "<=", ">=", "!=", "<", ">"),
List(":"), List(":"),
List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"), List("+'", "-'", "<<'", ">>'", ">>>>", "+", "-", "&", "|", "^", "<<", ">>"),
List("*'", "*")) List("*'", "*", "/", "%%"))
val mfOperatorsDropFlatten: IndexedSeq[List[String]] = (0 until mfOperators.length).map(i => mfOperators.drop(i).flatten) val mfOperatorsDropFlatten: IndexedSeq[List[String]] = (0 until mfOperators.length).map(i => mfOperators.drop(i).flatten)

View File

@ -288,4 +288,46 @@ class ByteMathSuite extends FunSuite with Matchers with AppendedClues {
""". """.
stripMargin)(_.readByte(0xc000) should equal(x * y) withClue s"$x * $y") stripMargin)(_.readByte(0xc000) should equal(x * y) withClue s"$x * $y")
} }
test("Byte division 1") {
divisionCase1(0, 1)
divisionCase1(1, 1)
divisionCase1(2, 1)
divisionCase1(250, 1)
divisionCase1(0, 3)
divisionCase1(0, 5)
divisionCase1(1, 5)
divisionCase1(6, 5)
divisionCase1(73, 5)
divisionCase1(75, 5)
divisionCase1(42, 11)
}
private def divisionCase1(x: Int, y: Int): Unit = {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)(
s"""
| import zp_reg
| byte output_q1 @$$c000
| byte output_m1 @$$c001
| byte output_q2 @$$c002
| byte output_m2 @$$c003
| void main () {
| byte a
| a = f()
| //output_q1 = a / $y
| //output_m1 = a %% $y
| output_q2 = a
| output_m2 = a
| output_q2 /= $y
| output_m2 %%= $y
| }
| byte f() {return $x}
""".
stripMargin) { m =>
// m.readByte(0xc000) should equal(x / y) withClue s"$x / $y"
// m.readByte(0xc001) should equal(x % y) withClue s"$x %% $y"
m.readByte(0xc002) should equal(x / y) withClue s"$x / $y"
m.readByte(0xc003) should equal(x % y) withClue s"$x %% $y"
}
}
} }

View File

@ -13,7 +13,8 @@ class ConstantSuite extends FunSuite with Matchers {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)( EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
""" """
| array Sieve[4] | array Sieve[4]
| array __screen[4] | const byte two = 2
| array __screen[4] = [4 / two, 4 %% two, 0, 0]
| byte vic_mem | byte vic_mem
| void main() { | void main() {
| vic_mem = lo( ((Sieve.addr >> 10) & 8) | ((__screen.addr >> 6) & $f0) ) | vic_mem = lo( ((Sieve.addr >> 10) & 8) | ((__screen.addr >> 6) & $f0) )