1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-07-02 18:29:50 +00:00

Allow complex word-sized LHS for most compound operators, implementing #24

This commit is contained in:
Karol Stasiak 2020-01-03 14:52:35 +01:00
parent 69c82d90a8
commit e69b7084bc
6 changed files with 398 additions and 29 deletions

View File

@ -345,21 +345,21 @@ object M6809ExpressionCompiler extends AbstractExpressionCompiler[MLine] {
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 => M6809Buitins.perform8BitInPlace(ctx, l, r, ANDB)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, ANDB, commutative = true)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, ANDA, ANDB, commutative = true)
case _ => ???
}
case "|=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 => M6809Buitins.perform8BitInPlace(ctx, l, r, ORB)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, ORB, commutative = true)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, ORA, ORB, commutative = true)
case _ => ???
}
case "^=" =>
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 => M6809Buitins.perform8BitInPlace(ctx, l, r, EORB)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, EORB, commutative = true)
case 2 => M6809Buitins.perform16BitInPlace(ctx, l, r, EORA, EORB, commutative = true)
case _ => ???
}
case "<<=" =>

View File

@ -333,9 +333,74 @@ object BuiltIns {
}
def compileInPlaceWordOrLongShiftOps(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, aslRatherThanLsr: Boolean): List[AssemblyLine] = {
if (lhs.isInstanceOf[DerefExpression]) {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs)
lhs match {
case dx: DerefExpression =>
if (ctx.options.zpRegisterSize < 4) {
ctx.log.error("Unsupported shift operation. Consider increasing the size of the zeropage register or simplifying the left hand side expression.", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs)
}
return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, rhs){ (ptr, reg, offset, r) =>
val shiftAmount = r match {
case List(AssemblyLine0(LDA, Immediate, NumericConstant(a, _)), AssemblyLine0(LDX, Immediate, _)) => Some(a.toInt)
case _ => None
}
val loadToR2 =
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(STA, reg, 3))
val storeFromR2 =
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.zeropage(LDA, reg, 2),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.zeropage(LDA, reg, 3),
AssemblyLine.indexedY(STA, ptr))
val shiftR2 =
if (aslRatherThanLsr) List(AssemblyLine.zeropage(ASL, reg, 2), AssemblyLine.zeropage(ROL, reg, 3))
else List(AssemblyLine.zeropage(LSR, reg, 3), AssemblyLine.zeropage(ROR, reg, 2))
shiftAmount match {
case Some(0) => Nil
case Some(1) if aslRatherThanLsr =>
List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(ASL),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(ROL),
AssemblyLine.indexedY(STA, ptr))
case Some(1) if !aslRatherThanLsr =>
List(
AssemblyLine.immediate(LDY, offset + 1),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(LSR),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(DEY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(ROR),
AssemblyLine.indexedY(STA, ptr))
case Some(n) if n >= 1 && n <= 7 => // TODO: pick optimal
loadToR2 ++ List.fill(n)(shiftR2).flatten ++ storeFromR2
case _ =>
val labelSkip = ctx.nextLabel("ss")
val labelRepeat = ctx.nextLabel("sr")
List(AssemblyLine.implied(TAX), AssemblyLine.relative(BEQ, labelSkip)) ++
loadToR2 ++
List(AssemblyLine.label(labelRepeat)) ++
shiftR2 ++ List(
AssemblyLine.implied(DEX),
AssemblyLine.relative(BNE, labelRepeat)) ++
storeFromR2 ++ List(
AssemblyLine.label(labelSkip))
}
}
case _ =>
}
val env = ctx.env
val b = env.get[Type]("byte")
@ -1046,9 +1111,85 @@ object BuiltIns {
private def isPowerOfTwoUpTo15(n: Long): Boolean = if (n <= 0 || n >= 0x8000) false else 0 == ((n-1) & n)
def compileInPlaceWordMultiplication(ctx: CompilationContext, v: LhsExpression, addend: Expression): List[AssemblyLine] = {
if (v.isInstanceOf[DerefExpression]) {
ctx.log.error("Too complex left-hand-side expression", v.position)
return MosExpressionCompiler.compileToAX(ctx, v) ++ MosExpressionCompiler.compileToAX(ctx, addend)
v match {
case dx: DerefExpression =>
// this is ugly, needs a rewrite
return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, addend){(ptr, reg, offset, r) =>
val constR = r match {
case List(AssemblyLine0(LDA, Immediate, l), AssemblyLine0(LDX, Immediate, h)) =>
h.asl(8).+(l).quickSimplify match {
case NumericConstant(n, _) => Some(n.toInt & 0xffff)
case _ => None
}
case _ => None
}
constR match {
case Some(1) => Nil
case Some(0) => List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(STA, ptr))
case Some(2) => List(
AssemblyLine.immediate(LDA, 0),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(ASL),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.implied(ROL),
AssemblyLine.indexedY(STA, ptr))
// TODO: other powers of two
case _ if reg.toAddress == ptr => List(
AssemblyLine.implied(PHA),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, reg),
AssemblyLine.zeropage(STA, reg, 3),
AssemblyLine.implied(PLA),
AssemblyLine.implied(TAY),
AssemblyLine.zeropage(LDA, reg, 1),
AssemblyLine.implied(PHA),
AssemblyLine.zeropage(LDA, reg),
AssemblyLine.implied(PHA),
AssemblyLine.zeropage(STY, reg),
AssemblyLine.zeropage(STX, reg, 1),
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("__mul_u16u16u16")),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg),
AssemblyLine.implied(PLA),
AssemblyLine.zeropage(STA, reg, 1),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.zeropage(LDA, reg, 2),
AssemblyLine.indexedY(STA, reg),
AssemblyLine.implied(INY),
AssemblyLine.implied(TXA),
AssemblyLine.indexedY(STA, reg),
)
case _ => List(
AssemblyLine.zeropage(STA, reg),
AssemblyLine.zeropage(STX, reg, 1),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(STA, reg, 3),
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("__mul_u16u16u16")),
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.implied(TXA),
AssemblyLine.indexedY(STA, ptr),
)
}
}
case _ =>
}
val b = ctx.env.get[Type]("byte")
val w = ctx.env.get[Type]("word")
@ -1301,14 +1442,74 @@ object BuiltIns {
ctx.env.evalVariableAndConstantSubParts(indexExpr)._1.map(v => MosExpressionCompiler.getExpressionType(ctx, v).size).sum
}
@inline
private def handleWordOrLongInPlaceModificationViaDeref(ctx: CompilationContext, lhs: DerefExpression, rhs: Expression)(footer: (Constant, VariableInMemory, Int, List[AssemblyLine]) => List[AssemblyLine]): List[AssemblyLine] = {
if (ctx.options.zpRegisterSize < 2) {
ctx.log.error("Unsupported operation. Consider increasing the size of the zeropage register.", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs)
}
val env = ctx.env
val targetType = MosExpressionCompiler.getExpressionType(ctx, lhs)
if (targetType.size == 2 ) {
val reg = env.get[VariableInMemory]("__reg")
var l = MosExpressionCompiler.compileToZReg(ctx, lhs.inner)
val ptr = l match {
case List(AssemblyLine0(LDA, ZeroPage, p), AssemblyLine0(STA, ZeroPage, _), AssemblyLine0(LDA, ZeroPage, q), AssemblyLine0(STA, ZeroPage, _)) if p.succ == q =>
l = Nil
p
case List(AssemblyLine0(LDA, ZeroPage, p), AssemblyLine0(LDX, ZeroPage, q), AssemblyLine0(STA, ZeroPage, _), AssemblyLine0(STX, ZeroPage, _)) if p.succ == q =>
l = Nil
p
case _ =>
reg.toAddress
}
val r = MosExpressionCompiler.compileToAX(ctx, rhs)
val s = footer(ptr, reg, lhs.offset, r)
if (MosExpressionCompiler.changesZpreg(r, 0) || MosExpressionCompiler.changesZpreg(r, 1)) {
r ++ MosExpressionCompiler.preserveRegisterIfNeeded(ctx, MosRegister.AX, l) ++ s
} else {
l ++ r ++ s
}
} else {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, rhs)
}
}
def compileInPlaceWordOrLongAddition(ctx: CompilationContext, lhs: 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.", lhs.position)
return compileInPlaceWordOrLongAddition(ctx, lhs, addend, subtract, decimal = false)
}
if (lhs.isInstanceOf[DerefExpression]) {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, addend)
lhs match {
case dx: DerefExpression =>
if (subtract && ctx.options.zpRegisterSize < 3) {
ctx.log.error("Too complex left hand side. Consider increasing the size of the zeropage register.", lhs.position)
return compileInPlaceWordOrLongAddition(ctx, lhs, addend, subtract = false, decimal = false)
}
return if (subtract) handleWordOrLongInPlaceModificationViaDeref(ctx, dx, addend)((ptr, reg, offset, _) => wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.implied(SEC),
AssemblyLine.zeropage(STA, reg, 2),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(SBC, reg, 2),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(INY),
AssemblyLine.zeropage(STX, reg, 2),
AssemblyLine.indexedY(LDA, ptr),
AssemblyLine.zeropage(SBC, reg, 2),
AssemblyLine.indexedY(STA, ptr),
))) else handleWordOrLongInPlaceModificationViaDeref(ctx, dx, addend)((ptr, _, offset, _) => wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.implied(CLC),
AssemblyLine.indexedY(ADC, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(ADC, ptr),
AssemblyLine.indexedY(STA, ptr),
)))
case _ =>
}
val env = ctx.env
val b = env.get[Type]("byte")
@ -1656,9 +1857,17 @@ object BuiltIns {
def compileInPlaceWordOrLongBitOp(ctx: CompilationContext, lhs: LhsExpression, param: Expression, operation: Opcode.Value): List[AssemblyLine] = {
if (lhs.isInstanceOf[DerefExpression]) {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
return MosExpressionCompiler.compileToAX(ctx, lhs) ++ MosExpressionCompiler.compileToAX(ctx, param)
lhs match {
case dx: DerefExpression => return handleWordOrLongInPlaceModificationViaDeref(ctx, dx, param)((ptr, _, offset, _) => List(
AssemblyLine.immediate(LDY, offset),
AssemblyLine.indexedY(operation, ptr),
AssemblyLine.indexedY(STA, ptr),
AssemblyLine.implied(TXA),
AssemblyLine.implied(INY),
AssemblyLine.indexedY(operation, ptr),
AssemblyLine.indexedY(STA, ptr),
))
case _ =>
}
val env = ctx.env
val b = env.get[Type]("byte")

View File

@ -135,6 +135,9 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
}
val cmos = ctx.options.flag(CompilationFlag.EmitCmosOpcodes)
if (register == MosRegister.AX && !code.exists(_.concernsX)) {
return preserveRegisterIfNeeded(ctx, MosRegister.A, code)
}
if (states.exists(state => AssemblyLine.treatment(code, state) != Treatment.Unchanged)) {
register match {
case MosRegister.A => AssemblyLine.implied(PHA) +: fixTsx(code) :+ AssemblyLine.implied(PLA)

View File

@ -467,9 +467,89 @@ object ZBuiltIns {
def performLongInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcodeFirst: ZOpcode.Value, opcodeLater: ZOpcode.Value, size: Int, decimal: Boolean = false): List[ZLine] = {
if (lhs.isInstanceOf[DerefExpression]) {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
return Z80ExpressionCompiler.compile(ctx, lhs, ZExpressionTarget.NOTHING) ++ Z80ExpressionCompiler.compile(ctx, rhs, ZExpressionTarget.NOTHING)
lhs match {
case dx@DerefExpression(inner, offset, targetType) =>
val env = ctx.env
if (targetType.size == 2) {
if (opcodeFirst == ADD && !decimal && ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) {
val l = Z80ExpressionCompiler.compileToBC(ctx, inner #+# offset)
val r = Z80ExpressionCompiler.compileToDE(ctx, rhs)
val s = List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC),
ZLine.ld8(ZRegister.L, ZRegister.A),
ZLine.register(INC_16, ZRegister.BC),
ZLine.ld8(ZRegister.A, ZRegister.MEM_BC),
ZLine.ld8(ZRegister.H, ZRegister.A),
ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE),
ZLine.ld8(ZRegister.A, ZRegister.H),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A),
ZLine.register(DEC_16, ZRegister.BC),
ZLine.ld8(ZRegister.A, ZRegister.L),
ZLine.ld8(ZRegister.MEM_BC, ZRegister.A),
)
if (!r.exists(Z80ExpressionCompiler.changesBC)) {
return l ++ r ++ s
} else if (!l.exists(Z80ExpressionCompiler.changesDE)) {
return r ++ l ++ s
} else {
return l ++ Z80ExpressionCompiler.stashBCIfChanged(ctx, r) ++ s
}
} else {
val l = Z80ExpressionCompiler.compileDerefPointer(ctx, dx)
val r = Z80ExpressionCompiler.compileToDE(ctx, rhs)
val s = if (opcodeFirst == SUB && decimal) {
if (ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)) {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(SUB, ZRegister.E),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(SBC, ZRegister.D),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
)
} else {
ctx.log.error("Decimal subtraction from such a complex LHS is not yet supported", dx.position)
Nil
}
} else if (opcodeFirst == ADD && decimal) {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(ADD, ZRegister.E),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(ADC, ZRegister.D),
ZLine.implied(DAA),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
)
} else {
List(
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(opcodeFirst, ZRegister.E),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.A, ZRegister.MEM_HL),
ZLine.register(opcodeLater, ZRegister.D),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.A),
)
}
if (!r.exists(Z80ExpressionCompiler.changesHL)) {
return l ++ r ++ s
} else if (!l.exists(Z80ExpressionCompiler.changesDE)) {
return r ++ l ++ s
} else {
return l ++ Z80ExpressionCompiler.stashHLIfChanged(ctx, r) ++ s
}
}
} else {
ctx.log.error("Too complex left-hand-side expression", lhs.position)
return Z80ExpressionCompiler.compile(ctx, lhs, ZExpressionTarget.NOTHING) ++ Z80ExpressionCompiler.compile(ctx, rhs, ZExpressionTarget.NOTHING)
}
case _ =>
}
if (size == 2 && !decimal) {
// n × INC HL

View File

@ -464,17 +464,6 @@ class ArraySuite extends FunSuite with Matchers with AppendedClues {
| a[0] += 2
| }
""".stripMargin)
ShouldNotCompile(
"""
| array(word) a[7] @$c000
| void main () {
| // the 6809 backend is apparently too smart right now and this actually works!:
| a[0] += 2
| #if ARCH_6809
| a[0]*'a[1]
| #endif
| }
""".stripMargin)
}
test("Various large assignments involving arrays") {

View File

@ -339,4 +339,92 @@ class PointerSuite extends FunSuite with Matchers with AppendedClues {
m.readWord(0xc000) should equal(555)
}
}
test("Modifying a word via a pointer") {
EmuUnoptimizedCrossPlatformRun(/*Cpu.Mos, Cpu.Z80,*/ Cpu.Motorola6809)(
"""
|word output @$c000
|void main () {
| byte constant
| constant = $50
| output = $850
|
| pointer.word value_pointer
| value_pointer = output.pointer
| value_pointer[0] += constant
| value_pointer[0] |= constant
| value_pointer[0] <<= f(2)
| value_pointer[0] >>= f(2)
|}
|noinline byte f(byte i) = i
|
""".stripMargin
) { m =>
m.readWord(0xc000) should equal(0x850.+(0x50).|(0x50))
}
}
test("Modifying a word via a pointer 2") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80) (
"""
|import zp_reg
|word output2 @$c002
|array(word) tmp[6] @$c100
|void main () {
| byte constant
| constant = $50
| output2 = 0
| pointer.word value_pointer
| value_pointer = output2.pointer
| value_pointer[0] +'= constant
| tmp[0] = output2
| value_pointer[0] +'= constant
| tmp[1] = output2
| value_pointer[0] -'= constant
| tmp[2] = output2
| value_pointer[0] <<= f(1)
| tmp[3] = output2
| value_pointer[0] *= f(1)
| tmp[4] = output2
| value_pointer[0] /= f(1)
| tmp[5] = output2
|}
|noinline byte f(byte i) = i
|
""".stripMargin
){ m =>
println(m.readWord(0xc100).toHexString)
println(m.readWord(0xc102).toHexString)
println(m.readWord(0xc104).toHexString)
println(m.readWord(0xc106).toHexString)
println(m.readWord(0xc108).toHexString)
println(m.readWord(0xc10a).toHexString)
m.readWord(0xc002) should equal(0xA0)
}
}
test("Modifying a word via a pointer 3") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809) (
"""
|struct entity_t {
| word x, byte y,
| byte frame_i, byte frame_tick,
| int24 padding // TODO: remove padding after 6809 gets multiplication support
|}
|array(entity_t) enemy[7] @$c100
|
|void main() {
| enemy[0].x = 0x3ff
| byte i
| i = f(0)
| enemy[i].x += 1
|}
|noinline byte f(byte x) = x
|
|""".stripMargin
){ m =>
m.readWord(0xc100) should equal(0x400)
}
}
}