diff --git a/src/main/scala/millfork/assembly/z80/ZLine.scala b/src/main/scala/millfork/assembly/z80/ZLine.scala index 596a7e9d..0804fde2 100644 --- a/src/main/scala/millfork/assembly/z80/ZLine.scala +++ b/src/main/scala/millfork/assembly/z80/ZLine.scala @@ -102,6 +102,10 @@ object ZLine { def ldAbs8(target: Constant, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegisters(MEM_ABS_8, source), target) def ldAbs16(target: Constant, source: ZRegister.Value): ZLine = ZLine(LD_16, TwoRegisters(MEM_ABS_16, source), target) + + def ldViaIx(target: ZRegister.Value, sourceOffset: Int): ZLine = ZLine(LD, TwoRegisters(target, ZRegister.MEM_IX_D), NumericConstant(sourceOffset, 1)) + + def ldViaIx(targetOffset: Int, source: ZRegister.Value): ZLine = ZLine(LD, TwoRegisters(ZRegister.MEM_IX_D, source), NumericConstant(targetOffset, 1)) } case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Constant, elidable: Boolean = true) extends AbstractCode { diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index a10e9e95..f2356918 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -1,5 +1,6 @@ package millfork.compiler.z80 +import millfork.CompilationFlag import millfork.assembly.z80._ import millfork.compiler._ import millfork.env._ @@ -88,12 +89,22 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { def stashHLIfChanged(lines: List[ZLine]): List[ZLine] = if (lines.exists(changesHL)) ZLine.register(PUSH, ZRegister.HL) :: (lines :+ ZLine.register(POP, ZRegister.HL)) else lines - def targetifyA(target: ZExpressionTarget.Value, lines: List[ZLine]): List[ZLine] = target match { + def targetifyA(target: ZExpressionTarget.Value, lines: List[ZLine], isSigned: Boolean): List[ZLine] = target match { case ZExpressionTarget.NOTHING | ZExpressionTarget.A => lines - case ZExpressionTarget.HL => lines ++ List( - ZLine.ld8(ZRegister.L, ZRegister.A), - ZLine.ldImm8(ZRegister.H, 0) - ) + case ZExpressionTarget.HL => lines ++ (if (isSigned) { + val label = Z80Compiler.nextLabel("sx") + List( + ZLine.ld8(ZRegister.L, ZRegister.A), + ZLine.ldImm8(ZRegister.H, 0xff), + ZLine.imm8(OR, 0x7f), + ZLine.jump(label, IfFlagSet(ZFlag.S)), // TODO: gameboy has no S flag + ZLine.ldImm8(ZRegister.H, 0), + ZLine.label(label)) + } else { + List( + ZLine.ld8(ZRegister.L, ZRegister.A), + ZLine.ldImm8(ZRegister.H, 0)) + }) } def targetifyHL(target: ZExpressionTarget.Value, lines: List[ZLine]): List[ZLine] = target match { @@ -139,7 +150,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } case SumExpression(params, decimal) => getParamMaxSize(ctx, params.map(_._2)) match { - case 1 => targetifyA(target, ZBuiltIns.compile8BitSum(ctx, params, decimal)) + case 1 => targetifyA(target, ZBuiltIns.compile8BitSum(ctx, params, decimal), isSigned = false) case 2 => targetifyHL(target, ZBuiltIns.compile16BitSum(ctx, params, decimal)) } case f@FunctionCallExpression(name, params) => @@ -147,8 +158,47 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case "not" => assertBool(ctx, "not", params, 1) compile(ctx, params.head, target, branches.flip) - case "hi" | "lo" => ??? // TODO - case "nonet" => ??? // TODO + case "hi" => + if (params.length != 1) { + ErrorReporting.error("Too many parameters for hi/lo", f.position) + Nil + } else { + compileToHL(ctx, params.head) ++ (target match { + case ZExpressionTarget.NOTHING => Nil + case ZExpressionTarget.A=> List(ZLine.ld8(ZRegister.A, ZRegister.H)) + case ZExpressionTarget.HL=> List(ZLine.ld8(ZRegister.L, ZRegister.H), ZLine.ldImm8(ZRegister.H, 0)) + }) + } + case "lo" => + if (params.length != 1) { + ErrorReporting.error("Too many parameters for hi/lo", f.position) + Nil + } else { + compileToHL(ctx, params.head) ++ (target match { + case ZExpressionTarget.NOTHING => Nil + case ZExpressionTarget.A => List(ZLine.ld8(ZRegister.A, ZRegister.L)) + case ZExpressionTarget.HL => List(ZLine.ldImm8(ZRegister.H, 0)) + }) + } + case "nonet" => + if (params.length != 1) { + ErrorReporting.error("Invalid number of parameters", f.position) + Nil + } else { + compileToA(ctx, params.head) ++ (target match { + case ZExpressionTarget.NOTHING => Nil + case ZExpressionTarget.A => Nil + case ZExpressionTarget.HL => + if (ctx.options.flag(CompilationFlag.EmitExtended80Opcodes)) { + List( + ZLine.ld8(ZRegister.L, ZRegister.A), + ZLine.ldImm8(ZRegister.H, 0), + ZLine.register(RL, ZRegister.H)) + } else { + ??? + } + }) + } case "&&" => assertBool(ctx, "&&", params) branches match { @@ -175,18 +225,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case "&" => getParamMaxSize(ctx, params) match { - case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, AND, params)) + case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, AND, params), isSigned = false) case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, AND, params)) } case "*" => ??? case "|" => getParamMaxSize(ctx, params) match { - case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, OR, params)) + case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, OR, params), isSigned = false) case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, OR, params)) } case "^" => getParamMaxSize(ctx, params) match { - case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, XOR, params)) + case 1 => targetifyA(target, ZBuiltIns.compile8BitOperation(ctx, XOR, params), isSigned = false) case 2 => targetifyHL(target, ZBuiltIns.compile16BitOperation(ctx, XOR, params)) } case ">>>>" => @@ -195,14 +245,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case "<<" => val (l, r, size) = assertBinary(ctx, params) size match { - case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = true)) + case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = true), isSigned = false) case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = true) case _ => ??? } case ">>" => val (l, r, size) = assertBinary(ctx, params) size match { - case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = false)) + case 1 => targetifyA(target, Z80Shifting.compile8BitShift(ctx, l, r, left = false), isSigned = false) case 2 => Z80Shifting.compile16BitShift(ctx, l, r, left = false) case _ => ??? } @@ -266,25 +316,25 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, ADD) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size, decimal = false) } case "-=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, SUB) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size, decimal = false) } case "+'=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, ADD, decimal = true) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, ADD, ADC, size, decimal = true) } case "-'=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, SUB, decimal = true) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, SUB, SBC, size, decimal = true) } Nil case "<<=" => @@ -319,19 +369,19 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, AND) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, AND, AND, size, decimal = false) } case "^=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, XOR) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, XOR, XOR, size, decimal = false) } case "|=" => val (l, r, size) = assertAssignmentLike(ctx, params) size match { case 1 => ZBuiltIns.perform8BitInPlace(ctx, l, r, OR) - case _ => ??? + case _ => ZBuiltIns.performLongInPlace(ctx, l, r, OR, OR, size, decimal = false) } case _ => env.maybeGet[Type](f.functionName) match { @@ -351,7 +401,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { failed = true } return sourceType.size match { - case 1 => targetifyA(target, compileToA(ctx, params.head)) + case 1 => targetifyA(target, compileToA(ctx, params.head), isSigned = sourceType.isSigned) case 2 => targetifyHL(target, compileToHL(ctx, params.head)) case _ => ??? } @@ -390,7 +440,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { } } ++ List(ZLine(CALL, NoRegisters, function.toAddress)) } - result + function.returnType.size match { + case 1 => + targetifyA(target, result, isSigned = function.returnType.isSigned) + case 2 => + targetifyHL(target, result) + case _ => + result + } } } @@ -560,4 +617,107 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { compile(ctx, conjunction, target, branches) } } + + def compileByteReads(ctx: CompilationContext, rhs: Expression, size: Int): List[List[ZLine]] = { + if (size == 1) throw new IllegalArgumentException + val env = ctx.env + env.eval(rhs) match { + case Some(constant) => + List.tabulate(size)(i => List(ZLine.ldImm8(ZRegister.A, constant.subbyte(i)))) + case None => + rhs match { + case VariableExpression(vname) => + env.get[Variable](vname) match { + case v: VariableInMemory => + List.tabulate(size) { i => + if (i < size) { + List(ZLine.ldAbs8(ZRegister.A, v.toAddress + i)) + } else if (v.typ.isSigned) { + ??? + } else { + List(ZLine.ldImm8(ZRegister.A, 0)) + } + } + case v: StackVariable => + List.tabulate(size) { i => + if (i < size) { + List(ZLine.ldViaIx(ZRegister.A, v.baseOffset + i)) + } else if (v.typ.isSigned) { + ??? + } else { + List(ZLine.ldImm8(ZRegister.A, 0)) + } + } + } + case SeparateBytesExpression(hi, lo) => + List.tabulate(size) { i => + if (i == 0) { + compileToA(ctx, lo) + } else if (i == 1) { + compileToA(ctx, hi) + } else { + List(ZLine.ldImm8(ZRegister.A, 0)) + } + } + case _ => + List.tabulate(size) { i => + if (i == 0) { + compileToHL(ctx, rhs) :+ ZLine.ld8(ZRegister.A, ZRegister.L) + } else if (i == 1) { + List(ZLine.ld8(ZRegister.A, ZRegister.H)) + } else { + // TODO: signed words? + List(ZLine.ldImm8(ZRegister.A, 0)) + } + } + } + } + } + + + def compileByteStores(ctx: CompilationContext, lhs: LhsExpression, size: Int): List[List[ZLine]] = { + if (size == 1) throw new IllegalArgumentException + val env = ctx.env + lhs match { + case VariableExpression(vname) => + env.get[Variable](vname) match { + case v: VariableInMemory => + if (v.typ.size < size) { + ErrorReporting.error(s"Variable `$vname` is too small", lhs.position) + } + List.tabulate(size) { i => + if (i < size) { + List(ZLine.ldAbs8(v.toAddress + i, ZRegister.A)) + } else { + Nil + } + } + case v: StackVariable => + if (v.typ.size < size) { + ErrorReporting.error(s"Variable `$vname` is too small", lhs.position) + } + List.tabulate(size) { i => + if (i < size) { + List(ZLine.ldViaIx(v.baseOffset + i, ZRegister.A)) + } else { + Nil + } + } + } + case SeparateBytesExpression(hi: LhsExpression, lo: LhsExpression) => + if (size > 2) { + ErrorReporting.error(s"Left hand side is too small", lhs.position) + } + List.tabulate(size) { i => + if (i == 0) { + storeA(ctx, lo, signedSource = false) + } else if (i == 1) { + storeA(ctx, hi, signedSource = false) + } else { + Nil + } + } + } + } + } diff --git a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala index 7cbb8daf..e375ae7b 100644 --- a/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala +++ b/src/main/scala/millfork/compiler/z80/ZBuiltIns.scala @@ -1,6 +1,6 @@ package millfork.compiler.z80 -import millfork.assembly.z80.{ZLine, ZOpcode} +import millfork.assembly.z80.{TwoRegisters, ZLine, ZOpcode} import millfork.compiler.CompilationContext import millfork.node._ import ZOpcode._ @@ -47,7 +47,7 @@ object ZBuiltIns { def compile16BitOperation(ctx: CompilationContext, op: ZOpcode.Value, params: List[Expression]): List[ZLine] = { var const = op match { - case AND => NumericConstant(0xffff, 1) + case AND => NumericConstant(0xffff, 2) case OR | XOR => Constant.Zero } var hasConst = false @@ -157,7 +157,7 @@ object ZBuiltIns { } def compile16BitSum(ctx: CompilationContext, params: List[(Boolean, Expression)], decimal: Boolean): List[ZLine] = { - var const = Constant.Zero + var const: Constant = NumericConstant(0, 2) var hasConst = false var result = mutable.ListBuffer[ZLine]() if (decimal) { @@ -239,8 +239,7 @@ object ZBuiltIns { } if (hasConst) { result ++= List( - ZLine.ldImm8(ZRegister.D, const.hiByte), - ZLine.ldImm8(ZRegister.E, const.loByte), + ZLine.ldImm16(ZRegister.DE, const), ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE) ) } @@ -322,7 +321,7 @@ object ZBuiltIns { ZLine.register(XOR, ZRegister.MEM_HL), ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) } - case XOR => + case OR => constantRight match { case Some(NumericConstant(0, _)) => calculateAddress @@ -330,7 +329,7 @@ object ZBuiltIns { calculateAddress :+ ZLine.ldImm8(ZRegister.MEM_HL, 0xff) case _ => setup ++ List( - ZLine.register(XOR, ZRegister.MEM_HL), + ZLine.register(OR, ZRegister.MEM_HL), ZLine.ld8(ZRegister.MEM_HL, ZRegister.A)) } case AND => @@ -347,4 +346,34 @@ object ZBuiltIns { } } + + def performLongInPlace(ctx: CompilationContext, lhs: LhsExpression, rhs: Expression, opcodeFirst: ZOpcode.Value, opcodeLater: ZOpcode.Value, size: Int, decimal: Boolean = false): List[ZLine] = { + if (size == 2 && !decimal) { + if (opcodeFirst == ZOpcode.ADD) { + val loadRight = Z80ExpressionCompiler.compileToHL(ctx, rhs) ++ List(ZLine.ld8(ZRegister.D, ZRegister.H), ZLine.ld8(ZRegister.E, ZRegister.L)) + val loadLeft = Z80ExpressionCompiler.stashDEIfChanged(Z80ExpressionCompiler.compileToHL(ctx, lhs)) + val calculateAndStore = ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE) :: Z80ExpressionCompiler.storeHL(ctx, lhs, signedSource = false) + return loadRight ++ loadLeft ++ calculateAndStore + } + if (opcodeFirst == ZOpcode.SUB && ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) { + val loadRight = Z80ExpressionCompiler.compileToHL(ctx, rhs) ++ List(ZLine.ld8(ZRegister.D, ZRegister.H), ZLine.ld8(ZRegister.E, ZRegister.L)) + val loadLeft = Z80ExpressionCompiler.stashDEIfChanged(Z80ExpressionCompiler.compileToHL(ctx, lhs)) + // OR A clears carry before SBC + val calculateAndStore = List( + ZLine.register(OR, ZRegister.A), + ZLine.registers(SBC_16, ZRegister.HL, ZRegister.DE)) ++ + Z80ExpressionCompiler.storeHL(ctx, lhs, signedSource = false) + return loadRight ++ loadLeft ++ calculateAndStore + } + } + val store = Z80ExpressionCompiler.compileByteStores(ctx, lhs, size) + val loadLeft = Z80ExpressionCompiler.compileByteReads(ctx, lhs, size) + val loadRight = Z80ExpressionCompiler.compileByteReads(ctx, rhs, size) + List.tabulate(size) {i => + // TODO: stash things correctly? + val firstPhase = loadRight(i) ++ List(ZLine.ld8(ZRegister.E, ZRegister.A)) ++ (loadLeft(i) :+ ZLine.register(if (i==0) opcodeFirst else opcodeLater, ZRegister.E)) + val secondPhase = if (decimal) firstPhase :+ ZLine.implied(ZOpcode.DAA) else firstPhase + secondPhase ++ store(i) + }.flatten + } }