mirror of
https://github.com/KarolS/millfork.git
synced 2024-09-10 11:54:30 +00:00
8- and 16-bit shifting for Z80
This commit is contained in:
parent
f7f22767e4
commit
893515b649
@ -194,10 +194,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
|||||||
???
|
???
|
||||||
case "<<" =>
|
case "<<" =>
|
||||||
val (l, r, size) = assertBinary(ctx, params)
|
val (l, r, size) = assertBinary(ctx, params)
|
||||||
???
|
size match {
|
||||||
|
case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = true))
|
||||||
|
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = true)
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
case ">>" =>
|
case ">>" =>
|
||||||
val (l, r, size) = assertBinary(ctx, params)
|
val (l, r, size) = assertBinary(ctx, params)
|
||||||
???
|
size match {
|
||||||
|
case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = false))
|
||||||
|
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = false)
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
case "<<'" =>
|
case "<<'" =>
|
||||||
assertAllBytes("Long shift ops not supported", ctx, params)
|
assertAllBytes("Long shift ops not supported", ctx, params)
|
||||||
val (l, r, 1) = assertBinary(ctx, params)
|
val (l, r, 1) = assertBinary(ctx, params)
|
||||||
@ -251,10 +259,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
|||||||
Nil
|
Nil
|
||||||
case "<<=" =>
|
case "<<=" =>
|
||||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||||
???
|
size match {
|
||||||
|
case 1 => Z80Shifting.compile8BitShiftInPlace(ctx, l, r, left = true)
|
||||||
|
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = true) ++ storeHL(ctx, l, signedSource = false)
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
case ">>=" =>
|
case ">>=" =>
|
||||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||||
???
|
size match {
|
||||||
|
case 1 => Z80Shifting.compile8BitShiftInPlace(ctx, l, r, left = false)
|
||||||
|
case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = false) ++ storeHL(ctx, l, signedSource = false)
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
case "<<'=" =>
|
case "<<'=" =>
|
||||||
val (l, r, size) = assertAssignmentLike(ctx, params)
|
val (l, r, size) = assertAssignmentLike(ctx, params)
|
||||||
???
|
???
|
||||||
@ -352,6 +368,20 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def calculateAddressToAppropriatePointer(ctx: CompilationContext, expr: LhsExpression): Option[(LocalVariableAddressOperand, List[ZLine])] = {
|
||||||
|
val env = ctx.env
|
||||||
|
expr match {
|
||||||
|
case VariableExpression(name) =>
|
||||||
|
env.get[Variable](name) match {
|
||||||
|
case v:VariableInMemory => Some(LocalVariableAddressViaHL -> List(ZLine.ldImm16(ZRegister.HL, v.toAddress)))
|
||||||
|
case v:StackVariable => Some(LocalVariableAddressViaIX(v.baseOffset) -> Nil)
|
||||||
|
}
|
||||||
|
case i:IndexedExpression => Some(LocalVariableAddressViaHL -> calculateAddressToHL(ctx, i))
|
||||||
|
case _:SeparateBytesExpression => None
|
||||||
|
case _ => ???
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def calculateAddressToHL(ctx: CompilationContext, i: IndexedExpression): List[ZLine] = {
|
def calculateAddressToHL(ctx: CompilationContext, i: IndexedExpression): List[ZLine] = {
|
||||||
val env = ctx.env
|
val env = ctx.env
|
||||||
env.getPointy(i.name) match {
|
env.getPointy(i.name) match {
|
||||||
|
188
src/main/scala/millfork/compiler/z80/Z80Shifting.scala
Normal file
188
src/main/scala/millfork/compiler/z80/Z80Shifting.scala
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package millfork.compiler.z80
|
||||||
|
|
||||||
|
import millfork.CompilationFlag
|
||||||
|
import millfork.assembly.z80.{NoRegisters, ZLine, ZOpcode}
|
||||||
|
import millfork.compiler.CompilationContext
|
||||||
|
import millfork.env.NumericConstant
|
||||||
|
import millfork.node.{Expression, LhsExpression, ZRegister}
|
||||||
|
|
||||||
|
import scala.collection.GenTraversableOnce
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karol Stasiak
|
||||||
|
*/
|
||||||
|
object Z80Shifting {
|
||||||
|
|
||||||
|
private def fixAfterShiftIfNeeded(extendedOps: Boolean, left: Boolean, i: Long): List[ZLine] =
|
||||||
|
if (extendedOps) {
|
||||||
|
Nil
|
||||||
|
} else if (left) {
|
||||||
|
List(ZLine.imm8(ZOpcode.AND, 0xff & (0xff << i)))
|
||||||
|
} else {
|
||||||
|
List(ZLine.imm8(ZOpcode.AND, 0xff & (0xff >> i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
def compile8BitShift(ctx: CompilationContext, lhs: Expression, rhs: Expression, left: Boolean): List[ZLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)
|
||||||
|
val op =
|
||||||
|
if (extendedOps) {
|
||||||
|
if (left) ZOpcode.SLA else ZOpcode.SRL
|
||||||
|
} else {
|
||||||
|
if (left) ZOpcode.RLC else ZOpcode.RRC
|
||||||
|
}
|
||||||
|
val l = Z80ExpressionCompiler.compileToA(ctx, lhs)
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(i, _)) =>
|
||||||
|
if (i <= 0) {
|
||||||
|
l
|
||||||
|
} else if (i >= 8) {
|
||||||
|
l :+ ZLine.ldImm8(ZRegister.A, 0)
|
||||||
|
} else {
|
||||||
|
l ++ List.tabulate(i.toInt)(_ => ZLine.register(op, ZRegister.A)) ++ fixAfterShiftIfNeeded(extendedOps, left, i)
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
val calcCount = Z80ExpressionCompiler.compileToA(ctx, rhs) :+ ZLine.ld8(ZRegister.B, ZRegister.A)
|
||||||
|
val l = Z80ExpressionCompiler.stashBCIfChanged(Z80ExpressionCompiler.compileToA(ctx, lhs))
|
||||||
|
val loopBody = ZLine.register(op, ZRegister.A) :: fixAfterShiftIfNeeded(extendedOps, left, 1)
|
||||||
|
val label = Z80Compiler.nextLabel("sh")
|
||||||
|
calcCount ++ l ++ List(ZLine.label(label)) ++ loopBody :+ ZLine.djnz(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compile8BitShiftInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, left: Boolean): List[ZLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)
|
||||||
|
val op =
|
||||||
|
if (extendedOps) {
|
||||||
|
if (left) ZOpcode.SLA else ZOpcode.SRL
|
||||||
|
} else {
|
||||||
|
if (left) ZOpcode.RLC else ZOpcode.RRC
|
||||||
|
}
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(i, _)) =>
|
||||||
|
if (i <= 0) {
|
||||||
|
Z80ExpressionCompiler.compileToA(ctx, lhs)
|
||||||
|
} else if (i >= 8) {
|
||||||
|
ZLine.ldImm8(ZRegister.A, 0) :: Z80ExpressionCompiler.storeA(ctx, lhs, signedSource = false)
|
||||||
|
} else {
|
||||||
|
Z80ExpressionCompiler.calculateAddressToAppropriatePointer(ctx, lhs) match {
|
||||||
|
case Some((register, l)) =>
|
||||||
|
// SLA A = 8 cycles
|
||||||
|
// SLA (HL) = 15 cycles
|
||||||
|
// LD A,(HL) or LD (HL),A = 7 cycles
|
||||||
|
// SLA (IX+d) = 23 cycles
|
||||||
|
// LD A,(IX+d) or LD (IX+d),A = 19 cycles
|
||||||
|
// when using A is profitable?
|
||||||
|
// for HL:
|
||||||
|
// 15x > 7 + 8x + 7
|
||||||
|
// 7x > 14
|
||||||
|
// x > 2
|
||||||
|
// for IX+d
|
||||||
|
// 23x > 19 + 8x + 19
|
||||||
|
// 15x > 38
|
||||||
|
// x > 2.5
|
||||||
|
// so:
|
||||||
|
// - for x <= 2, don't use A
|
||||||
|
// - for x >= 3, use A
|
||||||
|
if (extendedOps && i <= 2) {
|
||||||
|
l ++ List.tabulate(i.toInt)(_ => ZLine.register(op, register))
|
||||||
|
} else {
|
||||||
|
l ++ List(ZLine.ld8(ZRegister.A, register)) ++
|
||||||
|
List.tabulate(i.toInt)(_ => ZLine.register(op, ZRegister.A)) ++
|
||||||
|
fixAfterShiftIfNeeded(extendedOps, left, i) ++
|
||||||
|
List(ZLine.ld8(register, ZRegister.A))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
val calcCount = Z80ExpressionCompiler.compileToA(ctx, rhs) :+ ZLine.ld8(ZRegister.B, ZRegister.A)
|
||||||
|
val l = Z80ExpressionCompiler.stashBCIfChanged(Z80ExpressionCompiler.compileToA(ctx, lhs))
|
||||||
|
val loopBody = ZLine.register(op, ZRegister.A) :: fixAfterShiftIfNeeded(extendedOps, left, 1)
|
||||||
|
val label = Z80Compiler.nextLabel("sh")
|
||||||
|
calcCount ++ l ++ List(ZLine.label(label)) ++ loopBody ++ List(ZLine.djnz(label)) ++ Z80ExpressionCompiler.storeA(ctx, lhs, signedSource = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def compile16BitShift(ctx: CompilationContext, lhs: Expression, rhs: Expression, left: Boolean): List[ZLine] = {
|
||||||
|
val env = ctx.env
|
||||||
|
val extendedOps = ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)
|
||||||
|
val l = Z80ExpressionCompiler.compileToHL(ctx, lhs)
|
||||||
|
env.eval(rhs) match {
|
||||||
|
case Some(NumericConstant(i, _)) =>
|
||||||
|
if (i <= 0) {
|
||||||
|
l
|
||||||
|
} else if (i >= 16) {
|
||||||
|
l :+ ZLine.ldImm16(ZRegister.HL, 0)
|
||||||
|
// } else if (i > 8) { // TODO: optimize shifts larger than 8
|
||||||
|
// ???
|
||||||
|
} else {
|
||||||
|
if (left) {
|
||||||
|
l ++ List.tabulate(i.toInt)(_ => ZLine.registers(ZOpcode.ADD_16, ZRegister.HL, ZRegister.HL))
|
||||||
|
} else {
|
||||||
|
if (extendedOps) {
|
||||||
|
l ++ (0L until i).flatMap(_ => List(
|
||||||
|
ZLine.register(ZOpcode.SRL, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.L)
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
l ++ (1L until i).flatMap(_ => List(
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.H, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.L),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.L, ZRegister.A)
|
||||||
|
)) ++ List(
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
ZLine.imm8(ZOpcode.AND, (0xff >> i) & 0xff),
|
||||||
|
ZLine.ld8(ZRegister.H, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.L),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
// ZLine.imm8(ZOpcode.AND, (0xff << (i - 8)) & 0xff), // TODO: properly mask the low byte!!!
|
||||||
|
ZLine.ld8(ZRegister.L, ZRegister.A)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
val calcCount = Z80ExpressionCompiler.compileToA(ctx, rhs) :+ ZLine.ld8(ZRegister.B, ZRegister.A)
|
||||||
|
val loopBody =
|
||||||
|
if (extendedOps) {
|
||||||
|
if (left) {
|
||||||
|
List(
|
||||||
|
ZLine.register(ZOpcode.SLA, ZRegister.L),
|
||||||
|
ZLine.register(ZOpcode.RLC, ZRegister.H))
|
||||||
|
} else {
|
||||||
|
List(
|
||||||
|
ZLine.register(ZOpcode.SRL, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.L))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (left) {
|
||||||
|
List(
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.L),
|
||||||
|
ZLine.register(ZOpcode.RLC, ZRegister.A),
|
||||||
|
ZLine.imm8(ZOpcode.AND, 0xfe),
|
||||||
|
ZLine.ld8(ZRegister.L, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RLC, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.H, ZRegister.A))
|
||||||
|
} else {
|
||||||
|
List(
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.H),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
ZLine.imm8(ZOpcode.AND, 0x7f),
|
||||||
|
ZLine.ld8(ZRegister.H, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.A, ZRegister.L),
|
||||||
|
ZLine.register(ZOpcode.RRC, ZRegister.A),
|
||||||
|
ZLine.ld8(ZRegister.L, ZRegister.A))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val label = Z80Compiler.nextLabel("sh")
|
||||||
|
calcCount ++ l ++ List(ZLine.label(label)) ++ loopBody :+ ZLine.djnz(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package millfork.test
|
package millfork.test
|
||||||
import millfork.test.emu.{EmuBenchmarkRun, EmuUltraBenchmarkRun, EmuUnoptimizedRun}
|
import millfork.CpuFamily
|
||||||
|
import millfork.test.emu._
|
||||||
import org.scalatest.{FunSuite, Matchers}
|
import org.scalatest.{FunSuite, Matchers}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,18 +9,18 @@ import org.scalatest.{FunSuite, Matchers}
|
|||||||
class ShiftSuite extends FunSuite with Matchers {
|
class ShiftSuite extends FunSuite with Matchers {
|
||||||
|
|
||||||
test("In-place shifting") {
|
test("In-place shifting") {
|
||||||
EmuUnoptimizedRun("""
|
EmuUnoptimizedCrossPlatformRun(CpuFamily.M6502, CpuFamily.I80)("""
|
||||||
| array output [3] @$c000
|
| array output [3] @$c000
|
||||||
| void main () {
|
| void main () {
|
||||||
| output[0] = 1
|
| output[0] = 1
|
||||||
| output[1] = 3
|
| output[1] = 3
|
||||||
| output[output[0]] <<= 2
|
| output[output[0]] <<= 2
|
||||||
| }
|
| }
|
||||||
""".stripMargin).readByte(0xc001) should equal(12)
|
""".stripMargin){_.readByte(0xc001) should equal(12)}
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Byte shifting") {
|
test("Byte shifting") {
|
||||||
EmuBenchmarkRun("""
|
EmuCrossPlatformBenchmarkRun(CpuFamily.M6502, CpuFamily.I80)("""
|
||||||
| byte output @$c000
|
| byte output @$c000
|
||||||
| void main () {
|
| void main () {
|
||||||
| byte a
|
| byte a
|
||||||
@ -73,7 +74,7 @@ class ShiftSuite extends FunSuite with Matchers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test("Variable shifting") {
|
test("Variable shifting") {
|
||||||
EmuBenchmarkRun("""
|
EmuCrossPlatformBenchmarkRun(CpuFamily.M6502, CpuFamily.I80)("""
|
||||||
| word output0 @$c000
|
| word output0 @$c000
|
||||||
| word output2 @$c002
|
| word output2 @$c002
|
||||||
| byte output4 @$c004
|
| byte output4 @$c004
|
||||||
|
Loading…
Reference in New Issue
Block a user