diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index e850648a..e7e59c1f 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -174,6 +174,7 @@ case class CompilationOptions(platform: Platform, log.error("Either Sharp LR35902 or Intel 8080 opcodes have to be enabled") } case CpuFamily.I86 => + case CpuFamily.M6809 => } } @@ -265,6 +266,7 @@ object CpuFamily extends Enumeration { case Mos | StrictMos | Ricoh | StrictRicoh | Cmos | HuC6280 | CE02 | Sixteen => M6502 case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 => I80 case Intel8086 | Intel80186 => I86 + case Cpu.Motorola6809 => M6809 } } } @@ -339,6 +341,10 @@ object Cpu extends Enumeration { * The Intel 80186 or 80188 processor */ val Intel80186: Cpu.Value = Value + /** + * The Motorola 6809 processor + */ + val Motorola6809: Cpu.Value = Value /** * Processors that can run code for WDC 65C02 @@ -367,6 +373,8 @@ object Cpu extends Enumeration { private val i80AlwaysDefaultFlags = alwaysDefaultFlags + private val m6809AlwaysDefaultFlags = alwaysDefaultFlags + def defaultFlags(x: Cpu.Value): Set[CompilationFlag.Value] = x match { case StrictMos => mosAlwaysDefaultFlags ++ Set(DecimalMode, PreventJmpIndirectBug) @@ -396,6 +404,8 @@ object Cpu extends Enumeration { i80AlwaysDefaultFlags ++ Set(EmitExtended80Opcodes, EmitSharpOpcodes) case Intel8086 | Intel80186 => i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIxForStack, EmitIntel8085Opcodes, EmitIllegals) + case Motorola6809 => + m6809AlwaysDefaultFlags } def fromString(name: String)(implicit log: Logger): Cpu.Value = name match { @@ -453,6 +463,8 @@ object Cpu extends Enumeration { case "intel80286" => Intel80186 case "i80286" => Intel80186 case "80286" => Intel80186 + // disabled for now +// case "6809" => Motorola6809 case _ => log.fatal("Unknown CPU achitecture: " + name) } @@ -460,6 +472,7 @@ object Cpu extends Enumeration { CpuFamily.forType(cpu) match { case CpuFamily.M6502 => 2 case CpuFamily.I80 | CpuFamily.I86 => 4 + case CpuFamily.M6809 => 2 case _ => ??? } } diff --git a/src/main/scala/millfork/assembly/m6809/MAddrMode.scala b/src/main/scala/millfork/assembly/m6809/MAddrMode.scala new file mode 100644 index 00000000..b195a90d --- /dev/null +++ b/src/main/scala/millfork/assembly/m6809/MAddrMode.scala @@ -0,0 +1,36 @@ +package millfork.assembly.m6809 + +import millfork.node.M6809Register + +/** + * @author Karol Stasiak + */ +sealed trait MAddrMode + +case object Inherent extends MAddrMode + +case object InherentA extends MAddrMode + +case object InherentB extends MAddrMode + +case class TwoRegisters(source: M6809Register.Value, target: M6809Register.Value) extends MAddrMode + +case class RegisterSet(registers: Set[M6809Register.Value]) extends MAddrMode + +case class Absolute(indirect: Boolean) extends MAddrMode + +case class Indexed(base: M6809Register.Value, indirect: Boolean) extends MAddrMode + +case class AccumulatorIndexed(base: M6809Register.Value, indirect: Boolean) extends MAddrMode + +case class PostIncremented(base: M6809Register.Value, amount: Int, indirect: Boolean) extends MAddrMode + +case class PreDecremented(base: M6809Register.Value, amount: Int, indirect: Boolean) extends MAddrMode + +case object Relative extends MAddrMode + +case object Immediate extends MAddrMode + +case object NonExistent extends MAddrMode + +case object RawByte extends MAddrMode diff --git a/src/main/scala/millfork/assembly/m6809/MLine.scala b/src/main/scala/millfork/assembly/m6809/MLine.scala new file mode 100644 index 00000000..45b560a6 --- /dev/null +++ b/src/main/scala/millfork/assembly/m6809/MLine.scala @@ -0,0 +1,76 @@ +package millfork.assembly.m6809 + +import millfork.assembly.{AbstractCode, Elidability, SourceLine} +import millfork.env.{Constant, Label} +import millfork.node.Position + +/** + * @author Karol Stasiak + */ + +object MLine0 { + + @inline + def unapply(a: MLine): Some[(MOpcode.Value, MAddrMode, Constant)] = Some(a.opcode, a.addrMode, a.parameter) +} + +object MLine { + + import MOpcode._ + + def label(label: String): MLine = MLine.label(Label(label)) + + def label(label: Label): MLine = MLine(LABEL, NonExistent, label.toAddress) + + def inherent(opcode: MOpcode.Value): MLine = MLine(opcode, Inherent, Constant.Zero) + + def inherentA(opcode: MOpcode.Value): MLine = MLine(opcode, InherentA, Constant.Zero) + + def inherentB(opcode: MOpcode.Value): MLine = MLine(opcode, InherentB, Constant.Zero) +} + +case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant, elidability: Elidability.Value = Elidability.Elidable, source: Option[SourceLine] = None) extends AbstractCode { + + def pos(s: Option[SourceLine]): MLine = if (s.isEmpty || s == source) this else this.copy(source = s) + + def pos(s1: Option[SourceLine], s2: Option[SourceLine]): MLine = pos(Seq(s1, s2)) + + def position(s: Option[Position]): MLine = pos(SourceLine.of(s)) + + def positionIfEmpty(s: Option[Position]): MLine = if (s.isEmpty || source.isDefined) this else pos(SourceLine.of(s)) + + def pos(s: Seq[Option[SourceLine]]): MLine = pos(SourceLine.merge(s)) + + def mergePos(s: Seq[Option[SourceLine]]): MLine = if (s.isEmpty) this else pos(SourceLine.merge(this.source, s)) + + def refersTo(name: String): Boolean = parameter.refersTo(name) + + override def sizeInBytes: Int = 1 // TODO + + override def isPrintable: Boolean = true // TODO + + override def toString: String = { + if (opcode == MOpcode.LABEL) return parameter + ":" + if (opcode == MOpcode.BYTE) return s" FCB $parameter" + val suffix = addrMode match { + case NonExistent => "" + case Inherent => "" + case InherentA => "A" + case InherentB => "B" + case Relative => s" $parameter" + case Absolute(false) => s" $parameter" + case Indexed(base, false) => s" $parameter,$base" + case Indexed(base, true) => s" [$parameter,$base]" + case PreDecremented(base, 1, false) => s" ,-$base" + case PreDecremented(base, 2, false) => s" ,--$base" + case PreDecremented(base, 1, true) => s" [-$base]" + case PreDecremented(base, 2, true) => s" [--$base]" + case PostIncremented(base, 1, false) => s" ,$base+" + case PostIncremented(base, 2, false) => s" ,$base++" + case PostIncremented(base, 1, true) => s" [,$base+]" + case PostIncremented(base, 2, true) => s" [,$base++]" + } + s" $opcode$suffix" + } +} + diff --git a/src/main/scala/millfork/assembly/m6809/MOpcode.scala b/src/main/scala/millfork/assembly/m6809/MOpcode.scala new file mode 100644 index 00000000..324f5e19 --- /dev/null +++ b/src/main/scala/millfork/assembly/m6809/MOpcode.scala @@ -0,0 +1,35 @@ +package millfork.assembly.m6809 + +/** + * @author Karol Stasiak + */ + +object MFlag extends Enumeration { + val Z, C, H, N, V = Value +} + +object MOpcode extends Enumeration { + val ABX, ADCA, ADCB, ADDA, ADDB, ADDD, ANDA, ANDB, ANDCC, ASL, ASR, + BITA, BITB, + BRA, BRN, BHI, BLS, BCC, BCS, BNE, BEQ, BVC, BVS, BPL, BMI, BGE, BLT, BGT, BLE, + CLR, CMPA, CMPB, CMPD, CMPS, CMPU, CMPX, CMPY, COMA, COMB, COM, CWAI, + DAA, DEC, + EORA, EORB, EXG, + INC, + JMP, JSR, + LDA, LDB, LDD, LDS, LDU, LDX, LDY, LEAS, LEAU, LEAX, LEAY, LSR, + LBRA, LBRN, LBHI, LBLS, LBCC, LBCS, LBNE, LBEQ, LBVC, LBVS, LBPL, LBMI, LBGE, LBLT, LBGT, LBLE, + MUL, + NEG, NOP, + ORA, ORB, ORCC, + PSHS, PSHU, PULS, PULU, + ROL, ROR, RTI, RTS, + SBCA, SBCB, SEX, STA, STB, STD, STS, STU, STX, STY, SUBA, SUBB, SUBD, SWI, SWI2, SWI3, SYNC, + TFR, TST, + DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL = Value + + val NoopDiscard: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC) + val PrefixedBy10: Set[MOpcode.Value] = Set(CMPD, CMPY, LDS, LDY, SWI2) // TODO: branches + val PrefixedBy11: Set[MOpcode.Value] = Set(CMPS, CMPU, SWI3) + val Prefixed: Set[MOpcode.Value] = PrefixedBy10 ++ PrefixedBy11 +} diff --git a/src/main/scala/millfork/compiler/m6809/M6809Compiler.scala b/src/main/scala/millfork/compiler/m6809/M6809Compiler.scala new file mode 100644 index 00000000..bc1c77b1 --- /dev/null +++ b/src/main/scala/millfork/compiler/m6809/M6809Compiler.scala @@ -0,0 +1,19 @@ +package millfork.compiler.m6809 + +import millfork.assembly.Elidability +import millfork.assembly.m6809.{Inherent, MLine, MOpcode, NonExistent} +import millfork.compiler.{AbstractCompiler, CompilationContext} +import millfork.env.{Constant, Label, MemoryAddressConstant} + +/** + * @author Karol Stasiak + */ +object M6809Compiler extends AbstractCompiler[MLine] { + override def compile(ctx: CompilationContext): List[MLine] = { + ctx.env.nameCheck(ctx.function.code) + val label = MLine.label(Label(ctx.function.name)).copy(elidability = Elidability.Fixed) + val chunk = packHalves(M6809StatementCompiler.compile(ctx, new M6809StatementPreprocessor(ctx, ctx.function.code)())) + // TODO + label :: chunk + } +} diff --git a/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala b/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala new file mode 100644 index 00000000..eff6dbf7 --- /dev/null +++ b/src/main/scala/millfork/compiler/m6809/M6809StatementCompiler.scala @@ -0,0 +1,50 @@ +package millfork.compiler.m6809 + +import millfork.assembly.BranchingOpcodeMapping +import millfork.assembly.m6809.MLine +import millfork.compiler.{AbstractCompiler, AbstractStatementCompiler, BranchSpec, CompilationContext} +import millfork.node.{ExecutableStatement, Expression, M6809AssemblyStatement, ReturnStatement, VariableExpression, WhileStatement} +import millfork.assembly.m6809.MOpcode._ +import millfork.env.{Label, ThingInMemory} +/** + * @author Karol Stasiak + */ +object M6809StatementCompiler extends AbstractStatementCompiler[MLine] { + def compile(ctx: CompilationContext, statement: ExecutableStatement): (List[MLine], List[MLine]) = { + val code: (List[MLine], List[MLine]) = statement match { + case ReturnStatement(None) => + // TODO: clean stack + // TODO: RTI + List(MLine.inherent(RTS)) -> Nil + case M6809AssemblyStatement(opcode, addrMode, expression, elidability) => + ctx.env.evalForAsm(expression) match { + case Some(e) => List(MLine(opcode, addrMode, e, elidability)) -> Nil + case None => + println(statement) + ??? + } + case WhileStatement(VariableExpression("true"),List(),List(),_) => + Nil -> Nil // TODO + case _ => + println(statement) + ??? + } + code._1.map(_.positionIfEmpty(statement.position)) -> code._2.map(_.positionIfEmpty(statement.position)) + } + + override def labelChunk(labelName: String): List[MLine] = ??? + + override def jmpChunk(label: Label): List[MLine] = ??? + + override def branchChunk(opcode: BranchingOpcodeMapping, labelName: String): List[MLine] = ??? + + override def compileExpressionForBranching(ctx: CompilationContext, expr: Expression, branching: BranchSpec): List[MLine] = ??? + + override def replaceLabel(ctx: CompilationContext, line: MLine, from: String, to: String): MLine = ??? + + override def returnAssemblyStatement: ExecutableStatement = ??? + + override def callChunk(label: ThingInMemory): List[MLine] = ??? + + override def areBlocksLarge(blocks: List[MLine]*): Boolean = ??? +} diff --git a/src/main/scala/millfork/compiler/m6809/M6809StatementPreprocessor.scala b/src/main/scala/millfork/compiler/m6809/M6809StatementPreprocessor.scala new file mode 100644 index 00000000..a452352f --- /dev/null +++ b/src/main/scala/millfork/compiler/m6809/M6809StatementPreprocessor.scala @@ -0,0 +1,13 @@ +package millfork.compiler.m6809 + +import millfork.assembly.m6809.MLine +import millfork.compiler.{AbstractStatementPreprocessor, CompilationContext} +import millfork.node.{ExecutableStatement, ForStatement} + +/** + * @author Karol Stasiak + */ +class M6809StatementPreprocessor(ctx: CompilationContext, statements: List[ExecutableStatement]) + extends AbstractStatementPreprocessor(ctx, statements) { + override def maybeOptimizeForStatement(f: ForStatement): Option[(ExecutableStatement, VV)] = None +} diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 8d6db814..4d174143 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -1,5 +1,6 @@ package millfork.env +import millfork.assembly.m6809.{MOpcode, NonExistent} import millfork.assembly.{BranchingOpcodeMapping, Elidability} import millfork.{env, _} import millfork.assembly.mos.{AddrMode, Opcode} @@ -1806,6 +1807,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa case CpuFamily.M6502 => List(MosAssemblyStatement(Opcode.CHANGED_MEM, AddrMode.DoesNotExist, LiteralExpression(0, 1), Elidability.Fixed)) case CpuFamily.I80 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, LiteralExpression(0, 1), Elidability.Fixed)) case CpuFamily.I86 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, LiteralExpression(0, 1), Elidability.Fixed)) + case CpuFamily.M6809 => List(M6809AssemblyStatement(MOpcode.CHANGED_MEM, NonExistent, LiteralExpression(0, 1), Elidability.Fixed)) case _ => ??? }) } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 7f49648a..5ef4f141 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -1,6 +1,7 @@ package millfork.node import millfork.assembly.Elidability +import millfork.assembly.m6809.{MAddrMode, MOpcode} import millfork.assembly.mos.{AddrMode, Opcode} import millfork.assembly.z80.{ZOpcode, ZRegisters} import millfork.env.{Constant, ParamPassingConvention, Type} @@ -197,6 +198,15 @@ object ZRegister extends Enumeration { val main7Registers: Set[ZRegister.Value] = Set[ZRegister.Value](ZRegister.A, ZRegister.B, ZRegister.C, ZRegister.D, ZRegister.D, ZRegister.E, ZRegister.H, ZRegister.L) } +object M6809Register extends Enumeration { + val A, B, D, DP, X, Y, U, S, PC, CC = Value + + def registerSize(reg: Value): Int = reg match { + case D | X | Y | U | S | PC => 2 + case A | B | DP | CC => 1 + } +} + //case class Indexing(child: Expression, register: Register.Value) extends Expression case class VariableExpression(name: String) extends LhsExpression { @@ -501,6 +511,10 @@ case class Z80AssemblyStatement(opcode: ZOpcode.Value, registers: ZRegisters, of override def getAllExpressions: List[Expression] = List(expression) } +case class M6809AssemblyStatement(opcode: MOpcode.Value, addrMode: MAddrMode, expression: Expression, elidability: Elidability.Value) extends ExecutableStatement { + override def getAllExpressions: List[Expression] = List(expression) +} + case class IfStatement(condition: Expression, thenBranch: List[ExecutableStatement], elseBranch: List[ExecutableStatement]) extends CompoundStatement { override def getAllExpressions: List[Expression] = condition :: (thenBranch ++ elseBranch).flatMap(_.getAllExpressions) diff --git a/src/main/scala/millfork/output/M6809Assembler.scala b/src/main/scala/millfork/output/M6809Assembler.scala new file mode 100644 index 00000000..b7149d02 --- /dev/null +++ b/src/main/scala/millfork/output/M6809Assembler.scala @@ -0,0 +1,103 @@ +package millfork.output + +import millfork.{CompilationOptions, Platform} +import millfork.assembly.m6809.{MOpcode, _} +import millfork.compiler.m6809.M6809Compiler +import millfork.env.{Environment, Label, MemoryAddressConstant, NormalFunction} +import millfork.node.{NiceFunctionProperty, Program} + +import scala.collection.mutable + +/** + * @author Karol Stasiak + */ +class M6809Assembler(program: Program, + rootEnv: Environment, + platform: Platform) extends AbstractAssembler[MLine](program, rootEnv, platform, M6809InliningCalculator, M6809Compiler) { + override def bytePseudoopcode: String = "FCB" + + override def deduplicate(options: CompilationOptions, compiledFunctions: mutable.Map[String, CompiledFunction[MLine]]): Unit = () + + override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[MLine]): List[MLine] = code + + override def quickSimplify(code: List[MLine]): List[MLine] = code + + override def gatherNiceFunctionProperties(niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], functionName: String, code: List[MLine]): Unit = () + + override def performFinalOptimizationPass(f: NormalFunction, actuallyOptimize: Boolean, options: CompilationOptions, code: List[MLine]): List[MLine] = code + + override def emitInstruction(bank: String, options: CompilationOptions, index: Int, instr: MLine): Int = { + import millfork.assembly.m6809.MOpcode._ + instr match { + case MLine0(BYTE, RawByte, c) => + writeByte(bank, index, c) + index + 1 + case MLine0(BYTE, _, _) => log.fatal("BYTE opcode failure") + case MLine0(_, RawByte, _) => log.fatal("BYTE opcode failure") + case MLine0(LABEL, NonExistent, MemoryAddressConstant(Label(labelName))) => + val bank0 = mem.banks(bank) + labelMap(labelName) = bank0.index -> index + index + case MLine0(op, NonExistent, _) if MOpcode.NoopDiscard(op) => + index + case MLine0(op, Inherent, _) if M6809Assembler.inherent.contains(op) => + writeByte(bank, index, M6809Assembler.inherent(op)) + index + 1 + case MLine0(op, InherentA, _) if M6809Assembler.inherentA.contains(op) => + writeByte(bank, index, M6809Assembler.inherentA(op)) + index + 1 + case MLine0(op, InherentB, _) if M6809Assembler.inherentB.contains(op) => + writeByte(bank, index, M6809Assembler.inherentB(op)) + index + 1 + case MLine0(op, Immediate, param) if M6809Assembler.immediate.contains(op) => + writeByte(bank, index, M6809Assembler.immediate(op)) + writeByte(bank, index + 1, param) + index + 1 + case _ => + // TODO + throw new IllegalArgumentException("Not supported: " + instr) + } + } +} + +object M6809Assembler { + + val inherent: mutable.Map[MOpcode.Value, Byte] = mutable.Map[MOpcode.Value, Byte]() + val inherentA: mutable.Map[MOpcode.Value, Byte] = mutable.Map[MOpcode.Value, Byte]() + val inherentB: mutable.Map[MOpcode.Value, Byte] = mutable.Map[MOpcode.Value, Byte]() + val immediate: mutable.Map[MOpcode.Value, Byte] = mutable.Map[MOpcode.Value, Byte]() + + private def inh(op: MOpcode.Value, value: Int): Unit = inherent(op) = value.toByte + + private def inab(op: MOpcode.Value, value: Int): Unit = { + inherentA(op) = value.toByte + inherentB(op) = value.+(0x10).toByte + } + + private def imm(op: MOpcode.Value, value: Int): Unit = immediate(op) = value.toByte + + import MOpcode._ + + inh(ABX, 0x3a) + inh(DAA, 0x19) + inh(MUL, 0x3d) + inh(NOP, 0x12) + inh(RTI, 0x3B) + inh(RTS, 0x39) + inh(SEX, 0x1d) + inh(SWI, 0x3f) + inh(SYNC, 0x13) + + inab(ASL, 0x48) + inab(ASR, 0x47) + inab(CLR, 0x4f) + inab(COM, 0x43) + inab(DEC, 0x4a) + inab(INC, 0x48) + inab(LSR, 0x44) + inab(NEG, 0x40) + inab(ROL, 0x49) + inab(ROR, 0x46) + inab(TST, 0x4d) + +} \ No newline at end of file diff --git a/src/main/scala/millfork/output/M6809InliningCalculator.scala b/src/main/scala/millfork/output/M6809InliningCalculator.scala new file mode 100644 index 00000000..9c382bde --- /dev/null +++ b/src/main/scala/millfork/output/M6809InliningCalculator.scala @@ -0,0 +1,16 @@ +package millfork.output + +import millfork.JobContext +import millfork.assembly.m6809.MLine + +/** + * @author Karol Stasiak + */ +object M6809InliningCalculator extends AbstractInliningCalculator[MLine] { + override def codeForInlining(fname: String, functionsThatCanBeCalledFromInlinedFunctions: Set[String], code: List[MLine]): Option[List[MLine]] = None + + override def inline(code: List[MLine], inlinedFunctions: Map[String, List[MLine]], jobContext: JobContext): List[MLine] = { + if (inlinedFunctions.isEmpty) code + else ??? + } +} diff --git a/src/main/scala/millfork/parser/M6809Parser.scala b/src/main/scala/millfork/parser/M6809Parser.scala new file mode 100644 index 00000000..2f087d47 --- /dev/null +++ b/src/main/scala/millfork/parser/M6809Parser.scala @@ -0,0 +1,31 @@ +package millfork.parser + +import fastparse.all +import millfork.CompilationOptions +import millfork.assembly.m6809.MLine +import millfork.node.{ExecutableStatement, ParameterDeclaration, Position, Statement} +import millfork.output.{MemoryAlignment, NoAlignment, WithinPageAlignment} + +/** + * @author Karol Stasiak + */ +class M6809Parser(filename: String, + input: String, + currentDirectory: String, + options: CompilationOptions, + featureConstants: Map[String, Long], + useIntelSyntax: Boolean) extends MfParser[MLine](filename, input, currentDirectory, options, featureConstants) { + override def allowIntelHexAtomsInAssembly: Boolean = false + + override def asmParamDefinition: all.P[ParameterDeclaration] = ??? + + def fastAlignmentForArrays: MemoryAlignment = WithinPageAlignment + + def fastAlignmentForFunctions: MemoryAlignment = NoAlignment + + override def asmStatement: all.P[ExecutableStatement] = ??? + + override def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]]): Unit = { + // TODO + } +} diff --git a/src/test/scala/millfork/test/BasicSymonTest.scala b/src/test/scala/millfork/test/BasicSymonTest.scala index 55ee7a88..8c181506 100644 --- a/src/test/scala/millfork/test/BasicSymonTest.scala +++ b/src/test/scala/millfork/test/BasicSymonTest.scala @@ -9,7 +9,7 @@ import org.scalatest.{FunSuite, Matchers} */ class BasicSymonTest extends FunSuite with Matchers { test("Empty test") { - EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)( + EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086, Cpu.Motorola6809)( """ | void main () { | diff --git a/src/test/scala/millfork/test/emu/EmuM6809Run.scala b/src/test/scala/millfork/test/emu/EmuM6809Run.scala new file mode 100644 index 00000000..b7a97f9f --- /dev/null +++ b/src/test/scala/millfork/test/emu/EmuM6809Run.scala @@ -0,0 +1,175 @@ +package millfork.test.emu + +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Paths} + +import fastparse.core.Parsed.{Failure, Success} +import millfork._ +import millfork.assembly.AssemblyOptimization +import millfork.assembly.m6809.MLine +import millfork.compiler.m6809.M6809Compiler +import millfork.compiler.{CompilationContext, LabelGenerator} +import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} +import millfork.node.opt.NodeOptimization +import millfork.node.{Program, StandardCallGraph} +import millfork.output.{M6809Assembler, MemoryBank} +import millfork.parser.{PreprocessingResult, Preprocessor, Z80Parser} +import org.scalatest.Matchers + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +/** + * @author Karol Stasiak + */ +object EmuM6809Run { + + private def preload(cpu: millfork.Cpu.Value, filename: String): Option[Program] = { + TestErrorReporting.log.info(s"Loading $filename for $cpu") + val source = Files.readAllLines(Paths.get(filename), StandardCharsets.US_ASCII).asScala.mkString("\n") + val options = CompilationOptions(EmuPlatform.get(cpu), Map( + CompilationFlag.LenientTextEncoding -> true + ), None, 0, Map(), JobContext(TestErrorReporting.log, new LabelGenerator)) + val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, source) + TestErrorReporting.log.debug(s"Features: $features") + TestErrorReporting.log.info(s"Parsing $filename") + val parser = Z80Parser(filename, preprocessedSource, "", options, features, useIntelSyntax = false) + parser.toAst match { + case Success(x, _) => Some(x) + case f: Failure[_, _] => + TestErrorReporting.log.error(f.toString) + TestErrorReporting.log.error(f.extra.toString) + TestErrorReporting.log.error(f.lastParser.toString) + TestErrorReporting.log.error("Syntax error", Some(parser.lastPosition)) + TestErrorReporting.log.error("Parsing error") + ??? + } + } + + private lazy val cache: mutable.Map[(millfork.Cpu.Value, String), Option[Program]] = mutable.Map[(millfork.Cpu.Value, String), Option[Program]]() + private def get(cpu: millfork.Cpu.Value, path: String): Program = + synchronized { cache.getOrElseUpdate(cpu->path, preload(cpu, path)).getOrElse(throw new IllegalStateException()) } + + def cachedMath(cpu: millfork.Cpu.Value): Program = get(cpu, "include/m6809_math.mfk") + def cachedStdio(cpu: millfork.Cpu.Value): Program = get(cpu, "src/test/resources/include/dummy_stdio.mfk") +} + +class EmuM6809Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], assemblyOptimizations: List[AssemblyOptimization[MLine]]) extends Matchers { + def inline: Boolean = false + + def optimizeForSize: Boolean = false + + private val TooManyCycles: Long = 1500000 + + def apply(source: String): MemoryBank = { + apply2(source)._2 + } + + def apply2(source: String): (Timings, MemoryBank) = { + Console.out.flush() + Console.err.flush() + val log = TestErrorReporting.log + println(source) + val platform = EmuPlatform.get(cpu) + val extraFlags = Map( + CompilationFlag.DangerousOptimizations -> true, + CompilationFlag.EnableInternalTestSyntax -> true, + CompilationFlag.InlineFunctions -> this.inline, + CompilationFlag.OptimizeStdlib -> this.inline, + CompilationFlag.OptimizeForSize -> this.optimizeForSize, + CompilationFlag.SubroutineExtraction -> optimizeForSize, + CompilationFlag.EmitIllegals -> false, + CompilationFlag.LenientTextEncoding -> true) + val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap ++ extraFlags, None, 0, Map(), JobContext(log, new LabelGenerator)) + println(cpu) + println(options.flags.filter(_._2).keys.toSeq.sorted) + log.hasErrors = false + log.verbosity = 999 + var effectiveSource = source + if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}" + log.setSource(Some(effectiveSource.linesIterator.toIndexedSeq)) + val PreprocessingResult(preprocessedSource, features, pragmas) = Preprocessor.preprocessForTest(options, effectiveSource) + // tests use Intel syntax only when forced to: + val parserF = Z80Parser("", preprocessedSource, "", options, features, pragmas.contains("intel_syntax")) + parserF.toAst match { + case Success(unoptimized, _) => + log.assertNoErrors("Parse failed") + + + // prepare + val withLibraries = { + var tmp = unoptimized +// tmp += EmuM6809Run.cachedMath(cpu) // TODO: add this only after you implement maths + if (source.contains("import stdio")) { + tmp += EmuM6809Run.cachedStdio(cpu) + } + tmp + } + val program = nodeOptimizations.foldLeft(withLibraries.applyImportantAliases)((p, opt) => p.applyNodeOptimization(opt, options)) + val callGraph = new StandardCallGraph(program, log) + val env = new Environment(None, "", CpuFamily.I80, options) + env.collectDeclarations(program, options) + + val hasOptimizations = assemblyOptimizations.nonEmpty + var unoptimizedSize = 0L + // print unoptimized asm + env.allPreallocatables.foreach { + case f: NormalFunction => + val unoptimized = M6809Compiler.compile(CompilationContext(f.environment, f, 0, options, Set())) + unoptimizedSize += unoptimized.map(_.sizeInBytes).sum + case d: InitializedArray => + unoptimizedSize += d.contents.length + case d: InitializedMemoryVariable => + unoptimizedSize += d.typ.size + } + + log.assertNoErrors("Compile failed") + + + // compile + val env2 = new Environment(None, "", CpuFamily.I80, options) + env2.collectDeclarations(program, options) + val assembler = new M6809Assembler(program, env2, platform) + val output = assembler.assemble(callGraph, assemblyOptimizations, options) + println(";;; compiled: -----------------") + output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("////; DISCARD_")).foreach(println) + println(";;; ---------------------------") + assembler.labelMap.foreach { case (l, (_, addr)) => println(f"$l%-15s $$$addr%04x") } + + val optimizedSize = assembler.mem.banks("default").initialized.count(identity).toLong + if (unoptimizedSize == optimizedSize) { + println(f"Size: $unoptimizedSize%5d B") + } else { + println(f"Unoptimized size: $unoptimizedSize%5d B") + println(f"Optimized size: $optimizedSize%5d B") + println(f"Gain: ${(100L * (unoptimizedSize - optimizedSize) / unoptimizedSize.toDouble).round}%5d%%") + } + + if (log.hasErrors) { + fail("Code generation failed") + } + + val memoryBank = assembler.mem.banks("default") + (0x1f0 until 0x200).foreach(i => memoryBank.readable(i) = true) + (0xff00 to 0xffff).foreach{i => + memoryBank.readable(i) = true + memoryBank.writeable(i) = true + } + + (0x200 until 0x2000).takeWhile(memoryBank.occupied(_)).map(memoryBank.output).grouped(16).map(_.map(i => f"$i%02x").mkString(" ")).foreach(log.debug(_)) + val timings = platform.cpu match { + case _ => + Timings(-1, -1) -> memoryBank + } + log.clearErrors() + timings + case f: Failure[_, _] => + println(f) + println(f.extra.toString) + println(f.lastParser.toString) + log.error("Syntax error", Some(parserF.lastPosition)) + fail("Parsing error") + } + } + +} diff --git a/src/test/scala/millfork/test/emu/EmuUnoptimizedCrossPlatformRun.scala b/src/test/scala/millfork/test/emu/EmuUnoptimizedCrossPlatformRun.scala index 7038b68e..4c6bd4b7 100644 --- a/src/test/scala/millfork/test/emu/EmuUnoptimizedCrossPlatformRun.scala +++ b/src/test/scala/millfork/test/emu/EmuUnoptimizedCrossPlatformRun.scala @@ -14,6 +14,7 @@ object EmuUnoptimizedCrossPlatformRun { val (_, mz) = if (platforms.contains(Cpu.Z80)) EmuUnoptimizedZ80Run.apply2(source) else Timings(-1, -1) -> null val (_, mi) = if (platforms.contains(Cpu.Intel8080)) EmuUnoptimizedIntel8080Run.apply2(source) else Timings(-1, -1) -> null val (_, ms) = if (platforms.contains(Cpu.Sharp)) EmuUnoptimizedSharpRun.apply2(source) else Timings(-1, -1) -> null + val (_, mo) = if (platforms.contains(Cpu.Motorola6809)) EmuUnoptimizedM6809Run.apply2(source) else Timings(-1, -1) -> null val (_, mx) = if (Settings.enableIntel8086Tests && platforms.contains(Cpu.Intel8086)) EmuUnoptimizedIntel8086Run.apply2(source) else Timings(-1, -1) -> null if (Settings.enable6502Tests && platforms.contains(Cpu.Mos)) { println(f"Running 6502") @@ -43,5 +44,9 @@ object EmuUnoptimizedCrossPlatformRun { println(f"Running 8086") verifier(mx) } + if (Settings.enableMotorola6809Tests && platforms.contains(Cpu.Motorola6809)) { + println(f"Running 6809") + verifier(mx) + } } } diff --git a/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala b/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala index 079ea243..6e16c62f 100644 --- a/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala +++ b/src/test/scala/millfork/test/emu/EmuUnoptimizedRun.scala @@ -25,3 +25,5 @@ object EmuUnoptimizedIntel8085Run extends EmuZ80Run(Cpu.Intel8085, Nil, Nil) object EmuUnoptimizedIntel8086Run extends EmuI86Run(Nil, Nil) object EmuUnoptimizedSharpRun extends EmuZ80Run(Cpu.Sharp, Nil, Nil) + +object EmuUnoptimizedM6809Run extends EmuM6809Run(Cpu.Motorola6809, Nil, Nil) diff --git a/src/test/scala/millfork/test/emu/Settings.scala b/src/test/scala/millfork/test/emu/Settings.scala index 8b822afa..d5c923c1 100644 --- a/src/test/scala/millfork/test/emu/Settings.scala +++ b/src/test/scala/millfork/test/emu/Settings.scala @@ -48,6 +48,12 @@ object Settings { */ val enableUnemulatedTests: Boolean = true + /** + * Should the Motorola 6809 tests be enabled? + * Motorola 6809 emulation is not yet implemented, so keep this false for the time being. + */ + val enableMotorola6809Tests: Boolean = false + // the following are the core platforms and they are all tested by default val enable6502Tests: Boolean = true