From fe85757e00925f6062e3e452e002459c5221b148 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Thu, 5 Jul 2018 00:49:51 +0200 Subject: [PATCH] Initial and very incomplete support for Z80 assembly --- .../compiler/z80/Z80ExpressionCompiler.scala | 7 ++ src/main/scala/millfork/env/Environment.scala | 3 + src/main/scala/millfork/env/Thing.scala | 4 + .../scala/millfork/output/Z80Assembler.scala | 7 ++ src/main/scala/millfork/parser/MfParser.scala | 20 ++++ .../scala/millfork/parser/MosParser.scala | 15 --- .../scala/millfork/parser/Z80Parser.scala | 112 +++++++++++++++++- 7 files changed, 147 insertions(+), 21 deletions(-) diff --git a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala index f2356918..d0e4c6a2 100644 --- a/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/z80/Z80ExpressionCompiler.scala @@ -425,7 +425,14 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] { case _ => () } val result = function.params match { + case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.A, typ2), AssemblyParameterPassingBehaviour.Copy))) + if typ1.size == 1 && typ2.size == 1 => + compileToA(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress) + case AssemblyParamSignature(List(AssemblyParam(typ1, ZRegisterVariable(ZRegister.HL, typ2), AssemblyParameterPassingBehaviour.Copy))) + if typ1.size == 2 && typ2.size == 2 => + compileToHL(ctx, params.head) :+ ZLine(CALL, NoRegisters, function.toAddress) case AssemblyParamSignature(paramConvs) => + // TODO: stop being lazy and implement this ??? case NormalParamSignature(paramVars) => params.zip(paramVars).flatMap { diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index c0253847..2c65065c 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -599,6 +599,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { AssemblyParam(typ, env.get[MemoryVariable](vn), AssemblyParameterPassingBehaviour.Copy) case ByMosRegister(reg) => AssemblyParam(typ, RegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy) + case ByZRegister(reg) => + AssemblyParam(typ, ZRegisterVariable(reg, typ), AssemblyParameterPassingBehaviour.Copy) case ByConstant(vn) => AssemblyParam(typ, Placeholder(vn, typ), AssemblyParameterPassingBehaviour.ByConstant) case ByReference(vn) => @@ -733,6 +735,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { case _ => } case ByMosRegister(_) => () + case ByZRegister(_) => () case ByConstant(name) => val v = ConstantThing(prefix + name, UnexpandedConstant(prefix + name, typ.size), typ) addThing(v, stmt.position) diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index faafe8f0..6a58055a 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -127,6 +127,10 @@ case class RegisterVariable(register: MosRegister.Value, typ: Type) extends Vari def name: String = register.toString } +case class ZRegisterVariable(register: ZRegister.Value, typ: Type) extends Variable { + def name: String = register.toString +} + case class Placeholder(name: String, typ: Type) extends Variable sealed trait UninitializedMemory extends ThingInMemory { diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index 09ea0537..1a126e8f 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -63,6 +63,13 @@ class Z80Assembler(program: Program, index case ZLine(LABEL | BYTE | DISCARD_F | DISCARD_HL | DISCARD_BCDEIX | DISCARD_A, _, _, _) => ??? + case ZLine(RST, NoRegisters, param, _) => + val opcode = param.quickSimplify match { + case NumericConstant(n, _) if n >=0 && n <= 0x38 && n % 8 == 0 => 0xc7 + n.toInt + case _ => ErrorReporting.error("Invalid param for RST"); 0xc7 + } + writeByte(bank, index, opcode) + index + 1 case ZLine(op, NoRegisters, _, _) if implieds.contains(op) => writeByte(bank, index, implieds(op)) index + 1 diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 6c52a84a..e5eef95e 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -191,6 +191,26 @@ abstract class MfParser[T](filename: String, input: String, currentDirectory: St ParameterDeclaration(typ, ByVariable(name)).pos(p) } + def asmExpression: P[Expression] = (position() ~ NoCut( + ("<" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = false)) | + (">" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = true)) | + mfExpression(mathLevel) + )).map { case (p, e) => e.pos(p) } + + def asmExpressionWithParens: P[(Expression, Boolean)] = (position() ~ NoCut( + ("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(_ -> true) | + asmExpression.map(_ -> false) + )).map { case (p, e) => e._1.pos(p) -> e._2 } + + def elidable: P[Boolean] = ("?".! ~/ HWS).?.map(_.isDefined) + + val appcComplex: P[ParamPassingConvention] = P((("const" | "ref").! ~/ AWS).? ~ AWS ~ identifier) map { + case (None, name) => ByVariable(name) + case (Some("const"), name) => ByConstant(name) + case (Some("ref"), name) => ByReference(name) + case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`") + } + def asmParamDefinition: P[ParameterDeclaration] def arrayListElement: P[ArrayContents] = arrayStringContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel).map(e => LiteralContents(List(e))) diff --git a/src/main/scala/millfork/parser/MosParser.scala b/src/main/scala/millfork/parser/MosParser.scala index 3278dafe..2440a24e 100644 --- a/src/main/scala/millfork/parser/MosParser.scala +++ b/src/main/scala/millfork/parser/MosParser.scala @@ -19,12 +19,6 @@ case class MosParser(filename: String, input: String, currentDirectory: String, def asmOpcode: P[Opcode.Value] = (position() ~ letter.rep(exactly = 3).! ~ ("_W" | "_w").?.!).map { case (p, suffix, o) => Opcode.lookup(o + suffix, Some(p)) } - def asmExpression: P[Expression] = (position() ~ NoCut( - ("<" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = false)) | - (">" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = true)) | - mfExpression(mathLevel) - )).map { case (p, e) => e.pos(p) } - private val commaX = HWS ~ "," ~ HWS ~ ("X" | "x") ~ HWS private val commaY = HWS ~ "," ~ HWS ~ ("Y" | "y") ~ HWS private val commaZ = HWS ~ "," ~ HWS ~ ("Z" | "z") ~ HWS @@ -52,8 +46,6 @@ case class MosParser(filename: String, input: String, currentDirectory: String, )).?.map(_.getOrElse(AddrMode.Implied -> LiteralExpression(0, 1))) } - def elidable: P[Boolean] = ("?".! ~/ HWS).?.map(_.isDefined) - def asmInstruction: P[ExecutableStatement] = { val lineParser: P[(Boolean, Opcode.Value, (AddrMode.Value, Expression))] = !"}" ~ elidable ~/ asmOpcode ~/ asmParameter lineParser.map { case (elid, op, param) => @@ -86,13 +78,6 @@ case class MosParser(filename: String, input: String, currentDirectory: String, case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`") } - val appcComplex: P[ParamPassingConvention] = P((("const" | "ref").! ~/ AWS).? ~ AWS ~ identifier) map { - case (None, name) => ByVariable(name) - case (Some("const"), name) => ByConstant(name) - case (Some("ref"), name) => ByReference(name) - case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`") - } - val asmParamDefinition: P[ParameterDeclaration] = for { p <- position() typ <- identifier ~ SWS diff --git a/src/main/scala/millfork/parser/Z80Parser.scala b/src/main/scala/millfork/parser/Z80Parser.scala index 64b18dff..55eec5e7 100644 --- a/src/main/scala/millfork/parser/Z80Parser.scala +++ b/src/main/scala/millfork/parser/Z80Parser.scala @@ -1,17 +1,117 @@ package millfork.parser -import fastparse.all +import java.util.Locale + +import fastparse.all._ +import fastparse.core import millfork.CompilationOptions -import millfork.assembly.z80.ZLine -import millfork.node.{ExecutableStatement, ParameterDeclaration, Position, Statement} +import millfork.assembly.z80.{ZOpcode, _} +import millfork.env.{ByZRegister, Constant, ParamPassingConvention} +import millfork.error.ErrorReporting +import millfork.node._ /** * @author Karol Stasiak */ case class Z80Parser(filename: String, input: String, currentDirectory: String, options: CompilationOptions) extends MfParser[ZLine](filename, input, currentDirectory, options) { - override def asmParamDefinition: all.P[ParameterDeclaration] = ??? - override def asmStatement: all.P[ExecutableStatement] = ??? + private val zero = LiteralExpression(0, 1) - override def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]]): Unit = ??? + val appcSimple: P[ParamPassingConvention] = P("a" | "b" | "c" | "d" | "e" | "hl" | "bc" | "de").!.map { + case "a" => ByZRegister(ZRegister.A) + case "b" => ByZRegister(ZRegister.B) + case "c" => ByZRegister(ZRegister.C) + case "d" => ByZRegister(ZRegister.D) + case "e" => ByZRegister(ZRegister.E) + case "hl" => ByZRegister(ZRegister.HL) + case "bc" => ByZRegister(ZRegister.BC) + case "de" => ByZRegister(ZRegister.DE) + case x => ErrorReporting.fatal(s"Unknown assembly parameter passing convention: `$x`") + } + + override def asmParamDefinition: P[ParameterDeclaration] = for { + p <- position() + typ <- identifier ~ SWS + appc <- appcSimple | appcComplex + } yield ParameterDeclaration(typ, appc).pos(p) + + // TODO: label and instruction in one line + def asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => Z80AssemblyStatement(ZOpcode.LABEL, NoRegisters, VariableExpression(l), elidable = true)) + + def asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall).map(ExpressionStatement) + + private def normalOp8(op: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Expression)] = asmExpressionWithParens.map { + case (VariableExpression("A" | "a"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("B" | "b"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("C" | "c"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("D" | "d"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("E" | "e"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("HL" | "hl"), true) => (op, OneRegister(ZRegister.MEM_HL), zero) + // TODO: IX/IY + case (e, false) => (op, OneRegister(ZRegister.IMM_8), e) + } + + private def modifyOp8(op: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Expression)] = asmExpressionWithParens.map { + case (VariableExpression("A" | "a"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("B" | "b"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("C" | "c"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("D" | "d"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("E" | "e"), false) => (op, OneRegister(ZRegister.A), zero) + case (VariableExpression("HL" | "hl"), true) => (op, OneRegister(ZRegister.MEM_HL), zero) + // TODO: IX/IY + case (e, _) => ErrorReporting.fatal("Invalid parameter for " + op, e.position) + } + + private val jumpCondition: P[ZRegisters] = (HWS ~ identifier ~ HWS).map { + case "Z" | "z" => IfFlagSet(ZFlag.Z) + case "PE" | "pe" => IfFlagSet(ZFlag.P) + case "C" | "c" => IfFlagSet(ZFlag.C) + case "M" | "m" => IfFlagSet(ZFlag.S) + case "NZ" | "nz" => IfFlagClear(ZFlag.Z) + case "PO" | "po" => IfFlagClear(ZFlag.P) + case "NC" | "nc" => IfFlagClear(ZFlag.C) + case "P" | "p" => IfFlagClear(ZFlag.S) + case _ => ErrorReporting.fatal("Invalid condition flag") + } + + private val jumpConditionWithComma: P[ZRegisters] = (jumpCondition ~ "," ~/ HWS).?.map (_.getOrElse(NoRegisters)) + + def asmInstruction: P[ExecutableStatement] = { + import ZOpcode._ + for { + el <- elidable + opcode: String <- identifier ~/ HWS + (actualOpcode, registers, param) <- opcode.toUpperCase(Locale.ROOT) match { + case "RST" => asmExpression.map((RST, NoRegisters, _)) + case "RET" => P("").map(imm(RET)) // TODO: conditionals + case "CALL" => (jumpCondition~asmExpression).map{case (reg, param) => (CALL, reg, param)} + case "JP" => (jumpCondition~asmExpression).map{case (reg, param) => (JP, reg, param)} + case "JR" => (jumpCondition~asmExpression).map{case (reg, param) => (JR, reg, param)} + case "DJNZ" => asmExpression.map((DJNZ, NoRegisters, _)) + case "CP" => normalOp8(CP) + case "AND" => normalOp8(AND) + case "OR" => normalOp8(OR) + case "XOR" => normalOp8(XOR) + case "RL" => modifyOp8(RL) + case "RR" => modifyOp8(RR) + case "RLC" => modifyOp8(RLC) + case "RRC" => modifyOp8(RRC) + case "SLA" => modifyOp8(SLA) + case "SLL" => modifyOp8(SLL) + case "SRA" => modifyOp8(SRA) + case "SRL" => modifyOp8(SRL) + case _ => ErrorReporting.fatal("Unsupported opcode " + opcode) + } + } yield Z80AssemblyStatement(actualOpcode, registers, param, el) + } + + private def imm(opcode: ZOpcode.Value): Any => (ZOpcode.Value, ZRegisters, Expression) = (_: Any) => { + (opcode, NoRegisters, zero) + } + + override def asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros + + override def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]]): Unit = { + // TODO + } }