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

6502: Optimize some word operations

This commit is contained in:
Karol Stasiak 2019-07-30 01:40:13 +02:00
parent b68e4b67c8
commit 5c3637e2ea
5 changed files with 161 additions and 4 deletions

View File

@ -1085,9 +1085,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
val store = expressionStorageFromAW(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
} else {
val calculate = PseudoregisterBuiltIns.compileWordAdditionToAX(ctx, params, decimal = decimal)
val store = expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
calculate ++ store
PseudoregisterBuiltIns.compileWordAdditionViaAX(ctx, exprTypeAndVariable, expr.position, params, decimal = decimal)
}
case _ =>
ctx.log.error("Non-in-place addition or subtraction of variables larger than 2 bytes is not supported", expr.position)

View File

@ -9,11 +9,87 @@ import millfork.env._
import millfork.error.ConsoleLogger
import millfork.node._
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
/**
* @author Karol Stasiak
*/
object PseudoregisterBuiltIns {
def compileWordAdditionViaAX(ctx: CompilationContext, target: Option[(Type, Variable)], position: Option[Position], params: List[(Boolean, Expression)], decimal: Boolean): List[AssemblyLine] = {
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
val store = MosExpressionCompiler.expressionStorageFromAX(ctx, target, position)
if (!decimal && params.length == 2) {
store match {
case List(AssemblyLine0(STA, Absolute | ZeroPage, _), AssemblyLine0(STX, Absolute | ZeroPage, _)) =>
val reads = params.map(p => p._1 -> MosExpressionCompiler.compileToAX(ctx, p._2))
fastBinaryWordAddition(reads, store.map(x => List(x.copy(opcode = STA)))).foreach(return _)
case _ =>
}
}
compileWordAdditionToAX(ctx, params, decimal = decimal) ++ store
}
private def fastBinaryWordAddition(reads: List[(Boolean, List[AssemblyLine])], stores: List[List[AssemblyLine]]): Option[List[AssemblyLine]] = {
val countYs = reads.count(_._2.exists(_.concernsY))
if (countYs > 1 || countYs == 1 && stores.exists(_.exists(_.concernsY))) return None
val result = ListBuffer[AssemblyLine]()
var hard = Option.empty[List[AssemblyLine]]
val niceReads = mutable.ListBuffer[(List[AssemblyLine], List[AssemblyLine])]()
var constant = Constant.Zero
var counter = 0
for ((subtract, read) <- reads) {
read match {
case List(AssemblyLine0(LDA, Immediate, l), AssemblyLine0(LDX, Immediate, h)) =>
if (subtract) constant -= h.asl(8).+(l).quickSimplify
else constant += h.asl(8).+(l).quickSimplify
case List(l@AssemblyLine0(LDA, Absolute | ZeroPage | Immediate, _), h@AssemblyLine0(LDX, Absolute | ZeroPage | Immediate, _)) =>
if (subtract) niceReads.+=(List(AssemblyLine.implied(SEC), l.copy(opcode = SBC)) -> List(h.copy(opcode = SBC)))
else niceReads += (List(AssemblyLine.implied(CLC), l.copy(opcode = ADC)) -> List(h.copy(opcode = ADC)))
counter += 1
case _ =>
if (hard.isDefined || subtract) return None
hard = Some(read)
counter += 1
}
}
if (!constant.isProvablyZero) {
counter += 1
}
if (counter > 2) {
return None
}
hard match {
case Some(ax) =>
niceReads.prepend(ax -> List(AssemblyLine.implied(TXA)))
if (!constant.isProvablyZero) {
if (constant.isQuiteNegative) {
niceReads += List(AssemblyLine.implied(CLC), AssemblyLine.immediate(ADC, constant.loByte)) -> List(AssemblyLine.immediate(ADC, constant.hiByte))
} else {
val negC = Constant.Zero.-(constant).quickSimplify.quickSimplify
niceReads += List(AssemblyLine.implied(SEC), AssemblyLine.immediate(SBC, negC.loByte)) -> List(AssemblyLine.immediate(SBC, negC.hiByte))
}
}
case None =>
if (constant.isProvablyZero) {
if (niceReads.nonEmpty && niceReads.head._1.head.opcode == SEC) return None
niceReads(0) = (List(niceReads.head._1.last.copy(opcode = LDA)) -> List(niceReads.head._2.last.copy(opcode = LDA)))
} else {
niceReads.prepend(List(AssemblyLine.immediate(LDA, constant.loByte)) -> List(AssemblyLine.immediate(LDA, constant.hiByte)))
}
}
for (b <- 0 to 1) {
for (read <- niceReads) {
if (b == 0) result ++= read._1
else result ++= read._2
}
result ++= stores(b)
}
Some(result.toList)
}
def compileWordAdditionToAX(ctx: CompilationContext, params: List[(Boolean, Expression)], decimal: Boolean): List[AssemblyLine] = {
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
@ -200,6 +276,27 @@ object PseudoregisterBuiltIns {
}
private def compileFastWordBitOpsToAX(reads: List[List[AssemblyLine]], op: Opcode.Value): Option[List[AssemblyLine]] = {
val resultLo = mutable.ListBuffer[AssemblyLine]()
val resultHi = mutable.ListBuffer[AssemblyLine]()
for(read <- reads) {
read match {
case List(l@AssemblyLine0(LDA, Immediate | Absolute | ZeroPage, _), h@AssemblyLine0(LDX, Immediate | Absolute | ZeroPage, _)) =>
if (resultHi.isEmpty) {
resultHi += h.copy(opcode = LDA)
resultLo += l
} else {
resultHi += h.copy(opcode = op)
resultLo += l.copy(opcode = op)
}
case _ => return None
}
}
resultHi += AssemblyLine.implied(TAX)
resultHi ++= resultLo
Some(resultHi.toList)
}
def compileWordBitOpsToAX(ctx: CompilationContext, params: List[Expression], op: Opcode.Value): List[AssemblyLine] = {
if (ctx.options.zpRegisterSize < 2) {
ctx.log.error("Word bit operation requires the zeropage pseudoregister", params.headOption.flatMap(_.position))
@ -208,6 +305,8 @@ object PseudoregisterBuiltIns {
if (params.isEmpty) {
return List(AssemblyLine.immediate(LDA, 0), AssemblyLine.immediate(LDX, 0))
}
compileFastWordBitOpsToAX(params.map(p => MosExpressionCompiler.compileToAX(ctx, p)), op).foreach(return _)
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
val reg = ctx.env.get[VariableInMemory]("__reg")
@ -292,6 +391,31 @@ object PseudoregisterBuiltIns {
ctx.env.eval(r) match {
case Some(NumericConstant(0, _)) =>
List(AssemblyLine.zeropage(LDA, reg), AssemblyLine.zeropage(LDX, reg, 1))
case Some(NumericConstant(1, _)) if (firstParamCompiled match {
case List(
AssemblyLine0(LDA, ZeroPage | Absolute | Immediate, _),
AssemblyLine0(STA, ZeroPage, _),
AssemblyLine0(LDA, ZeroPage | Absolute | Immediate, _),
AssemblyLine0(STA, ZeroPage, _)) => true
case _ => false
}) =>
if (left) {
List(
firstParamCompiled(0),
AssemblyLine.implied(ASL),
AssemblyLine.zeropage(STA, reg),
firstParamCompiled(2),
AssemblyLine.implied(ROL),
AssemblyLine.implied(TAX),
AssemblyLine.zeropage(LDA, reg))
} else {
List(
firstParamCompiled(2),
AssemblyLine.implied(LSR),
AssemblyLine.implied(TAX),
firstParamCompiled(0),
AssemblyLine.implied(ROR))
}
case Some(NumericConstant(v, _)) if v > 0 && unrollShift(ctx, v, 2, 4) =>
if (ctx.options.flag(CompilationFlag.EmitNative65816Opcodes)) {
firstParamCompiled ++

View File

@ -20,6 +20,7 @@ sealed trait Constant {
def toIntelString: String
def isQuiteNegative: Boolean = false
def isProvablyZero: Boolean = false
def isProvably(value: Int): Boolean = false
def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = false
@ -105,10 +106,10 @@ sealed trait Constant {
def fitsInto(typ: Type): Boolean = true // TODO
final def succ: Constant = (this + 1).quickSimplify
}
case class AssertByte(c: Constant) extends Constant {
override def isQuiteNegative: Boolean = c.isQuiteNegative
override def isProvablyGreaterOrEqualThan(other: Constant): Boolean = c.isProvablyGreaterOrEqualThan(other)
override def isProvablyZero: Boolean = c.isProvablyZero
override def isProvably(i: Int): Boolean = c.isProvably(i)
@ -185,6 +186,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
throw new IllegalArgumentException(s"The constant $value is too big")
}
}
override def isQuiteNegative: Boolean = value < 0
override def isProvablyGreaterOrEqualThan(other: Constant): Boolean = value >= 0 && (other match {
case NumericConstant(o, _) if o >= 0 => value >= o
case _ => false
@ -327,6 +329,13 @@ object MathOperator extends Enumeration {
case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Constant) extends Constant {
override def isQuiteNegative: Boolean = operator match {
case MathOperator.Minus =>
lhs.isQuiteNegative || lhs.isInstanceOf[NumericConstant] && rhs.isInstanceOf[MemoryAddressConstant]
case _ =>
false
}
override def isProvablyNonnegative: Boolean = {
import MathOperator._
operator match {

View File

@ -59,4 +59,17 @@ class BitOpSuite extends FunSuite with Matchers {
| }
""".stripMargin)(_.readLong(0xc000) should equal(6))
}
test("Word AND 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| void main () {
| word v, w
| v = $ee0e
| w = $d0dd
| barrier()
| output = v & w & v & w
| }
| noinline void barrier(){}
""".stripMargin)(_.readWord(0xc000) should equal(0xc00c))
}
}

View File

@ -80,6 +80,19 @@ class ShiftSuite extends FunSuite with Matchers {
""".stripMargin)(_.readWord(0xc000) should equal(0x180))
}
test("Word shifting via pseudoregister 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos/*, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086*/)("""
| word output @$c000
| void main () {
| word w
| w = three()
| output = w << 1
| }
| word three() { return 3 }
| word identity(word w) { return w }
""".stripMargin)(_.readWord(0xc000) should equal(6))
}
test("Variable shifting") {
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
| word output0 @$c000