mirror of
https://github.com/KarolS/millfork.git
synced 2025-03-19 09:31:30 +00:00
Unsigned byte division by a constant
This commit is contained in:
parent
326e9d0585
commit
010b44f23e
@ -20,6 +20,8 @@
|
||||
|
||||
* Added structs and unions.
|
||||
|
||||
* Added unsigned byte division and modulo by a constant.
|
||||
|
||||
* Pointers can now be allocated anywhere.
|
||||
|
||||
* Pointers can now be typed.
|
||||
|
@ -28,6 +28,8 @@ where `11111` is a sequential number and `xx` is the type:
|
||||
|
||||
* `ds` – decimal right shift operation
|
||||
|
||||
* `dv` – division and modulo operations
|
||||
|
||||
* `el` – beginning of the "else" block in an `if` statement
|
||||
|
||||
* `ew` – end of a `while` statement
|
||||
|
@ -52,6 +52,9 @@ In the descriptions below, arguments to the operators are explained as follows:
|
||||
|
||||
* `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,
|
||||
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,
|
||||
@ -91,7 +94,11 @@ TODO
|
||||
`word * byte` (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
|
||||
|
||||
|
@ -62,7 +62,7 @@ class AbstractExpressionCompiler[T <: AbstractCode] {
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
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 = {
|
||||
assertAllArithmetic(ctx, params)
|
||||
if (params.exists { expr => getExpressionType(ctx, expr).size != 1 }) {
|
||||
@ -323,7 +338,7 @@ object AbstractExpressionCompiler {
|
||||
case 1 => b
|
||||
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 2 => w
|
||||
case _ => log.error("Combining values bigger than words", expr.position); w
|
||||
|
@ -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] = {
|
||||
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)
|
||||
|
@ -1308,6 +1308,20 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
case 2 =>
|
||||
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 "*'=" =>
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
|
@ -880,6 +880,26 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
||||
case 2 =>
|
||||
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 "*'=" =>
|
||||
assertAllArithmeticBytes("Long multiplication not supported", ctx, params)
|
||||
val (l, r, 1) = assertArithmeticAssignmentLike(ctx, params)
|
||||
|
@ -1,10 +1,13 @@
|
||||
package millfork.compiler.z80
|
||||
|
||||
import millfork.CompilationFlag
|
||||
import millfork.assembly.z80._
|
||||
import millfork.compiler.{AbstractExpressionCompiler, CompilationContext}
|
||||
import millfork.env._
|
||||
import millfork.node.{ConstantArrayElementExpression, Expression, LhsExpression, ZRegister}
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
6
src/main/scala/millfork/env/Constant.scala
vendored
6
src/main/scala/millfork/env/Constant.scala
vendored
@ -269,6 +269,7 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant {
|
||||
object MathOperator extends Enumeration {
|
||||
val Plus, Minus, Times, Shl, Shr, Shl9, Shr9, Plus9, DecimalPlus9,
|
||||
DecimalPlus, DecimalMinus, DecimalTimes, DecimalShl, DecimalShl9, DecimalShr,
|
||||
Divide, Modulo,
|
||||
And, Or, Exor = Value
|
||||
}
|
||||
|
||||
@ -282,6 +283,7 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
Shl | DecimalShl |
|
||||
Shl9 | DecimalShl9 |
|
||||
Shr | DecimalShr |
|
||||
Divide | Modulo |
|
||||
And | Or | Exor => lhs.isProvablyNonnegative && rhs.isProvablyNonnegative
|
||||
case _ => false
|
||||
}
|
||||
@ -343,6 +345,8 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
case MathOperator.Exor => c
|
||||
case MathOperator.Or => c
|
||||
case MathOperator.And => Constant.Zero
|
||||
case MathOperator.Divide => Constant.Zero
|
||||
case MathOperator.Modulo => Constant.Zero
|
||||
case _ => CompoundConstant(operator, l, r)
|
||||
}
|
||||
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.Or => lv | rv
|
||||
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 =>
|
||||
asDecimal(lv & 0xff, rv & 0xff, _ + _) & 0xff
|
||||
case MathOperator.DecimalMinus if ls == 1 && rs == 1 && lv.&(0xff) >= rv.&(0xff) =>
|
||||
|
@ -730,6 +730,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
constantOperation(MathOperator.DecimalTimes, params)
|
||||
case "*" =>
|
||||
constantOperation(MathOperator.Times, params)
|
||||
case "/" =>
|
||||
constantOperation(MathOperator.Divide, params)
|
||||
case "%%" =>
|
||||
constantOperation(MathOperator.Modulo, params)
|
||||
case "&&" | "&" =>
|
||||
constantOperation(MathOperator.And, params)
|
||||
case "^" =>
|
||||
|
@ -145,6 +145,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
|
||||
case MathOperator.And => l & r
|
||||
case MathOperator.Exor => l ^ r
|
||||
case MathOperator.Or => l | r
|
||||
case MathOperator.Divide => l / r
|
||||
case MathOperator.Modulo => l % r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -657,13 +657,13 @@ object MfParser {
|
||||
}
|
||||
|
||||
val mfOperators = 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)
|
||||
|
||||
|
@ -288,4 +288,46 @@ class ByteMathSuite extends FunSuite with Matchers with AppendedClues {
|
||||
""".
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ class ConstantSuite extends FunSuite with Matchers {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
|
||||
"""
|
||||
| array Sieve[4]
|
||||
| array __screen[4]
|
||||
| const byte two = 2
|
||||
| array __screen[4] = [4 / two, 4 %% two, 0, 0]
|
||||
| byte vic_mem
|
||||
| void main() {
|
||||
| vic_mem = lo( ((Sieve.addr >> 10) & 8) | ((__screen.addr >> 6) & $f0) )
|
||||
|
Loading…
x
Reference in New Issue
Block a user