1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 06:29:34 +00:00
millfork/src/main/scala/millfork/compiler/z80/Z80StatementCompiler.scala
2023-01-27 18:27:53 +01:00

356 lines
18 KiB
Scala

package millfork.compiler.z80
import millfork.CompilationFlag
import millfork.assembly.{BranchingOpcodeMapping, Elidability}
import millfork.assembly.z80._
import millfork.compiler._
import millfork.env._
import millfork.node._
import millfork.assembly.z80.ZOpcode._
import millfork.error.ConsoleLogger
/**
* @author Karol Stasiak
*/
object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
def compile(ctx: CompilationContext, statement: ExecutableStatement): (List[ZLine], List[ZLine])= {
val options = ctx.options
val env = ctx.env
val ret = Z80Compiler.restoreRegistersAndReturn(ctx)
val code: (List[ZLine], List[ZLine]) = statement match {
case EmptyStatement(stmts) =>
stmts.foreach(s => compile(ctx, s))
Nil -> Nil
case ReturnStatement(None) =>
fixStackOnReturn(ctx) ++ (ctx.function.returnType match {
case _: BooleanType =>
List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
case t => t.size match {
case 0 =>
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
case _ =>
ctx.log.warn("Returning without a value", statement.position)
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
}
}) -> Nil
case ReturnStatement(Some(e)) =>
(ctx.function.returnType match {
case t: BooleanType => t.size match {
case 0 =>
ctx.log.error("Cannot return anything from a void function", statement.position)
fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
case 1 =>
Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
case 2 =>
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE)) ++ ret
case _ =>
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BC)) ++ ret
}
case FatBooleanType =>
Z80ExpressionCompiler.compileToFatBooleanInA(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
case t =>
AbstractExpressionCompiler.checkAssignmentType(ctx, e, ctx.function.returnType)
t.size match {
case 0 =>
ctx.log.error("Cannot return anything from a void function", statement.position)
fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
case 1 =>
Z80ExpressionCompiler.compileToA(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
case 2 =>
Z80ExpressionCompiler.compileToHL(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
case 3 =>
Z80ExpressionCompiler.compileToEHL(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BC), ZLine.implied(RET))
case 4 =>
Z80ExpressionCompiler.compileToDEHL(ctx, e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_BC), ZLine.implied(RET))
case _ =>
if (ctx.function.hasElidedReturnVariable) {
fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
} else {
Z80ExpressionCompiler.storeLarge(ctx, VariableExpression(ctx.function.name + ".return"), e) ++ fixStackOnReturn(ctx) ++
List(ZLine.implied(DISCARD_F), ZLine.implied(DISCARD_A), ZLine.implied(DISCARD_HL), ZLine.implied(DISCARD_BC), ZLine.implied(DISCARD_DE), ZLine.implied(RET))
}
}
}) -> Nil
case s: GotoStatement =>
env.eval(s.target) match {
case Some(e) => List(ZLine(JP, NoRegisters, e)) -> Nil
case None =>
Z80ExpressionCompiler.compileToHL(ctx, s.target) ++ List(ZLine(JP, OneRegister(ZRegister.HL), Constant.Zero)) -> Nil
}
case s: LabelStatement =>
List(ZLine.label(env.prefix + s.name)) -> Nil
case Assignment(destination, source) =>
if (destination == BlackHoleExpression) return Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.NOTHING, NoBranching) -> Nil
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
val targetType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
AbstractExpressionCompiler.checkAssignmentType(ctx, source, targetType)
(sourceType.size match {
case 0 =>
sourceType match {
case _:ConstantBooleanType =>
Z80ExpressionCompiler.compileToA(ctx, source) ++ Z80ExpressionCompiler.storeA(ctx, destination, signedSource = false)
case _:BooleanType =>
// TODO: optimize
Z80ExpressionCompiler.compileToFatBooleanInA(ctx, source) ++ Z80ExpressionCompiler.storeA(ctx, destination, signedSource = false)
case _ =>
ctx.log.error("Cannot assign a void expression", statement.position)
Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.NOTHING, BranchSpec.None) ++
Z80ExpressionCompiler.compile(ctx, destination, ZExpressionTarget.NOTHING, BranchSpec.None)
}
case 1 => Z80ExpressionCompiler.compileToA(ctx, source) ++ Z80ExpressionCompiler.storeA(ctx, destination, sourceType.isSigned)
case 2 =>
ctx.env.eval(source) match {
case Some(constantWord) =>
Z80ExpressionCompiler.storeConstantWord(ctx, destination, constantWord, sourceType.isSigned)
case _ =>
val load = Z80ExpressionCompiler.compileToHL(ctx, source)
val store = Z80ExpressionCompiler.storeHL(ctx, destination, sourceType.isSigned)
load ++ store
}
case 3 =>
val load = Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.EHL, BranchSpec.None)
val store = Z80ExpressionCompiler.storeEHL(ctx, destination, sourceType.isSigned)
load ++ store
case 4 =>
val load = Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.DEHL, BranchSpec.None)
val store = Z80ExpressionCompiler.storeDEHL(ctx, destination, sourceType.isSigned)
load ++ store
case s => Z80ExpressionCompiler.storeLarge(ctx, destination, source)
}) -> Nil
case s: IfStatement =>
compileIfStatement(ctx, s)
case s: WhileStatement =>
compileWhileStatement(ctx, s)
case s: DoWhileStatement =>
compileDoWhileStatement(ctx, s)
case s: ReturnDispatchStatement =>
Z80ReturnDispatch.compile(ctx, s) -> Nil
case f:MemsetStatement =>
Z80BulkMemoryOperations.compileMemset(ctx, f) -> Nil
case f@ForStatement(_, _, _, _, List(Assignment(target: IndexedExpression, source: IndexedExpression)), Nil) =>
Z80BulkMemoryOperations.compileMemcpy(ctx, target, source, f) -> Nil
case f@ForStatement(variable, _, _, _, List(Assignment(target: IndexedExpression, source: Expression)), Nil) if ctx.env.overlapsVariable(variable, source) =>
Z80BulkMemoryOperations.compileMemset(ctx, target, source, f) -> Nil
case f@ForStatement(variable, _, _, _, List(ExpressionStatement(FunctionCallExpression(
operator@("+=" | "-=" | "|=" | "&=" | "^=" | "+'=" | "-'=" | "<<=" | ">>="),
List(target: IndexedExpression, source: Expression)
))), Nil) =>
Z80BulkMemoryOperations.compileMemtransform(ctx, target, operator, source, f) -> Nil
case f@ForStatement(variable, _, _, _, List(
ExpressionStatement(FunctionCallExpression(
operator1@("+=" | "-=" | "|=" | "&=" | "^=" | "+'=" | "-'=" | "<<=" | ">>="),
List(target1: IndexedExpression, source1: Expression)
)),
ExpressionStatement(FunctionCallExpression(
operator2@("+=" | "-=" | "|=" | "&=" | "^=" | "+'=" | "-'=" | "<<=" | ">>="),
List(target2: IndexedExpression, source2: Expression)
))
), Nil) =>
Z80BulkMemoryOperations.compileMemtransform2(ctx, target1, operator1, source1, target2, operator2, source2, f) -> Nil
case f@ForStatement(variable, _, _, _, List(
Assignment(target1: IndexedExpression, source1: Expression),
ExpressionStatement(FunctionCallExpression(
operator2@("+=" | "-=" | "|=" | "&=" | "^=" | "+'=" | "-'=" | "<<=" | ">>="),
List(target2: IndexedExpression, source2: Expression)
))
), Nil) =>
Z80BulkMemoryOperations.compileMemtransform2(ctx, target1, "=", source1, target2, operator2, source2, f) -> Nil
case f@ForStatement(variable, _, _, _, List(
ExpressionStatement(FunctionCallExpression(
operator1@("+=" | "-=" | "|=" | "&=" | "^=" | "+'=" | "-'=" | "<<=" | ">>="),
List(target1: IndexedExpression, source1: Expression)
)),
Assignment(target2: IndexedExpression, source2: Expression)
), Nil) =>
Z80BulkMemoryOperations.compileMemtransform2(ctx, target1, operator1, source1, target2, "=", source2, f) -> Nil
case f@ForStatement(variable, _, _, _, List(
Assignment(target1: IndexedExpression, source1: Expression),
Assignment(target2: IndexedExpression, source2: Expression)
), Nil) =>
Z80BulkMemoryOperations.compileMemtransform2(ctx, target1, "=", source1, target2, "=", source2, f) -> Nil
case f: ForStatement =>
compileForStatement(ctx, f)
case f:ForEachStatement =>
compileForEachStatement(ctx, f)
case s: BreakStatement =>
compileBreakStatement(ctx, s) -> Nil
case s: ContinueStatement =>
compileContinueStatement(ctx, s) -> Nil
case ExpressionStatement(e@FunctionCallExpression(name, params)) =>
env.maybeGet[Type](name) match {
case Some(_) =>
params.flatMap(p => Z80ExpressionCompiler.compile(ctx, p, ZExpressionTarget.NOTHING)) -> Nil
case _ =>
env.lookupFunction(name, params.map(p => Z80ExpressionCompiler.getExpressionType(ctx, p) -> p)) match {
case Some(i: MacroFunction) =>
val (paramPreparation, inlinedStatements) = Z80MacroExpander.inlineFunction(ctx, i, params, e.position)
val (main, extra) = compile(ctx.withInlinedEnv(i.environment, ctx.nextLabel("en")), inlinedStatements)
paramPreparation ++ main -> extra
case _ =>
Z80ExpressionCompiler.compile(ctx, e, ZExpressionTarget.NOTHING) -> Nil
}
}
case ExpressionStatement(e) =>
Z80ExpressionCompiler.compile(ctx, e, ZExpressionTarget.NOTHING) -> Nil
case RawBytesStatement(contents, _) =>
env.extractArrayContents(contents).map { expr =>
env.eval(expr) match {
case Some(c) => ZLine(BYTE, NoRegisters, c, elidability = Elidability.Fixed)
case None =>
ctx.log.error("Non-constant raw byte", position = statement.position)
ZLine(BYTE, NoRegisters, Constant.Zero, elidability = Elidability.Fixed)
}
} -> Nil
case Z80AssemblyStatement(op, reg, offset, expression, elidability) =>
val silent = (Seq(JP, JR, DJNZ, LABEL, CHANGED_MEM).contains(op))
val param = ctx.env.evalForAsm(expression, silent = silent) match {
case Some(e) => e
case None =>
expression match {
case VariableExpression(name) =>
env.maybeGet[ThingInMemory](name).map(_.toAddress).getOrElse {
val fqName = if (name.startsWith(".")) env.prefix + name else name
MemoryAddressConstant(Label(fqName))
}
case _ =>
ctx.log.error("Invalid parameter", statement.position)
Constant.Zero
}
}
val registers = (reg, offset) match {
case (OneRegister(r), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => OneRegisterOffset(r, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)
reg
case None =>
ctx.log.error("Inlining failed due to non-constant things", o.position)
reg
}
case (TwoRegisters(t, s), Some(o)) => env.evalForAsm(o) match {
case Some(NumericConstant(v, _)) => TwoRegistersOffset(t, s, v.toInt)
case Some(_) =>
ctx.log.error("Non-numeric constant", o.position)
reg
case None =>
ctx.log.error("Inlining failed due to non-constant things", o.position)
reg
}
case _ => reg
}
List(ZLine(op, registers, param, elidability)) -> Nil
}
code._1.map(_.positionIfEmpty(statement.position)) -> code._2.map(_.positionIfEmpty(statement.position))
}
private def fixStackOnReturn(ctx: CompilationContext): List[ZLine] = {
if (ctx.function.stackVariablesSize > 0) {
import ZRegister._
val localVariableArea = ctx.function.stackVariablesSize.&(1).+(ctx.function.stackVariablesSize)
if (ctx.options.flags(CompilationFlag.UseIxForStack)) {
if (ctx.function.returnType.size == 2) {
List(
ZLine.ldImm16(IX, localVariableArea),
ZLine.registers(ADD_16, IX, SP),
ZLine.ld16(SP, IX),
ZLine.register(POP, IX))
} else {
List(
ZLine.ldImm16(HL, localVariableArea),
ZLine.registers(ADD_16, HL, SP),
ZLine.ld16(SP, HL),
ZLine.register(POP, IX))
}
} else if (ctx.options.flags(CompilationFlag.UseIyForStack)) {
if (ctx.function.returnType.size == 2) {
List(
ZLine.ldImm16(IY, localVariableArea),
ZLine.registers(ADD_16, IY, SP),
ZLine.ld16(SP, IY),
ZLine.register(POP, IY))
} else {
List(
ZLine.ldImm16(HL, localVariableArea),
ZLine.registers(ADD_16, HL, SP),
ZLine.ld16(SP, HL),
ZLine.register(POP, IY))
}
} else {
if (ctx.function.returnType.size == 2) {
if (ctx.options.flags(CompilationFlag.EmitSharpOpcodes)) {
List(ZLine.imm8(ADD_SP, localVariableArea))
} else if (localVariableArea == 2) {
List(ZLine.register(INC_16, SP), ZLine.register(INC_16, SP))
} else if (ctx.options.flags(CompilationFlag.EmitIntel8080Opcodes)) {
List(
ZLine.implied(EX_DE_HL),
ZLine.ldImm16(HL, localVariableArea),
ZLine.registers(ADD_16, HL, SP),
ZLine.ld16(SP, HL),
ZLine.implied(EX_DE_HL))
} else {
???
}
} else if (localVariableArea == 2) {
List(ZLine.register(POP, HL))
} else {
List(
ZLine.ldImm16(HL, localVariableArea),
ZLine.registers(ADD_16, HL, SP),
ZLine.ld16(SP, HL))
}
}
} else Nil
}
def labelChunk(labelName: String) = List(ZLine.label(Label(labelName)))
def jmpChunk(label: Label) = List(ZLine.jump(label))
def branchChunk(opcode: BranchingOpcodeMapping, labelName: String) = List(ZLine.jump(Label(labelName), opcode.z80Flags))
def areBlocksLarge(blocks: List[ZLine]*): Boolean = false
override def compileExpressionForBranching(ctx: CompilationContext, expr: Expression, branching: BranchSpec): List[ZLine] = {
if (AbstractExpressionCompiler.getExpressionType(ctx, expr) == FatBooleanType) {
val prepareA = Z80ExpressionCompiler.compile(ctx, expr, ZExpressionTarget.A, branching)
if (Z80ExpressionCompiler.areSZFlagsBasedOnA(prepareA)) prepareA
else prepareA :+ ZLine.register(OR, ZRegister.A)
} else {
Z80ExpressionCompiler.compile(ctx, expr, ZExpressionTarget.NOTHING, branching)
}
}
override def replaceLabel(ctx: CompilationContext, line: ZLine, from: String, to: String): ZLine = line.parameter match {
case MemoryAddressConstant(Label(l)) if l == from => line.copy(parameter = MemoryAddressConstant(Label(to)))
case _ => line
}
override def returnAssemblyStatement: ExecutableStatement = Z80AssemblyStatement(RET, NoRegisters, None, LiteralExpression(0,1), Elidability.Elidable)
override def callChunk(label: ThingInMemory): List[ZLine] = List(ZLine(CALL, NoRegisters, label.toAddress))
}