1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-12 22:29:33 +00:00
millfork/src/test/scala/millfork/test/emu/EmuM6809Run.scala
2021-09-21 00:09:59 +02:00

225 lines
9.6 KiB
Scala

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.assembly.m6809.opt.VeryLateM6809AssemblyOptimizations
import millfork.compiler.m6809.M6809Compiler
import millfork.compiler.{CompilationContext, LabelGenerator}
import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction}
import millfork.error.ConsoleLogger
import millfork.node.opt.NodeOptimization
import millfork.node.{Program, StandardCallGraph}
import millfork.output.{M6809Assembler, MemoryBank}
import millfork.parser.{M6809Parser, PreprocessingResult, Preprocessor}
import org.roug.usim.mc6809.MC6809
import org.roug.usim.BusStraight
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.UseUForStack -> true,
CompilationFlag.LenientTextEncoding -> true
), None, 0, Map(), EmuPlatform.textCodecRepository, 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 = M6809Parser(filename, preprocessedSource, "", options, features)
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/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 apply(source: String): MemoryBank = {
apply2(source)._2
}
def emitIllegals = false
def inline = false
def blastProcessing = false
def optimizeForSize = false
private val TooManyCycles: Long = 1000000
private def formatBool(b: Int, c: Char): Char = if (b != 0) c else '-'
def apply2(source: String): (Timings, MemoryBank) = {
Console.out.flush()
Console.err.flush()
val log = TestErrorReporting.log
println(source)
val platform = EmuPlatform.get(cpu)
val options = CompilationOptions(platform, Map(
CompilationFlag.UseOptimizationHints -> true,
CompilationFlag.EnableInternalTestSyntax -> true,
CompilationFlag.DecimalMode -> true,
CompilationFlag.LenientTextEncoding -> true,
CompilationFlag.EmitIllegals -> this.emitIllegals,
CompilationFlag.InlineFunctions -> this.inline,
CompilationFlag.OptimizeStdlib -> this.inline,
CompilationFlag.UseUForStack -> true,
CompilationFlag.InterproceduralOptimization -> true,
CompilationFlag.CompactReturnDispatchParams -> true,
CompilationFlag.SubroutineExtraction -> optimizeForSize,
CompilationFlag.OptimizeForSize -> optimizeForSize,
CompilationFlag.OptimizeForSpeed -> blastProcessing,
// CompilationFlag.SourceInAssembly -> true,
// CompilationFlag.LineNumbersInAssembly -> true,
CompilationFlag.OptimizeForSonicSpeed -> blastProcessing
// CompilationFlag.CheckIndexOutOfBounds -> true,
), None, 0, Map(), EmuPlatform.textCodecRepository, JobContext(log, new LabelGenerator))
log.hasErrors = false
log.verbosity = 999
var effectiveSource = source
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
if (source.contains("call(")) effectiveSource += "\nnoinline asm word call(word d) {\nJMP ,X\n}\n"
log.setSource(Some(effectiveSource.linesIterator.toIndexedSeq))
val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, effectiveSource)
val parserF = M6809Parser("", preprocessedSource, "", options, features)
parserF.toAst match {
case Success(unoptimized, _) =>
log.assertNoErrors("Parse failed")
// prepare
val withLibraries = {
var tmp = unoptimized
if(source.contains("import stdio"))
tmp += EmuM6809Run.cachedStdio(cpu)
tmp += EmuM6809Run.cachedMath(cpu)
tmp
}
val program = nodeOptimizations.foldLeft(withLibraries.applyImportantAliases)((p, opt) => p.applyNodeOptimization(opt, options))
program.checkSegments(log, platform.codeAllocators.keySet)
log.assertNoErrors("Failed")
val callGraph = new StandardCallGraph(program, log)
val env = new Environment(None, "", CpuFamily.M6809, 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.M6809, options)
env2.collectDeclarations(program, options)
val assembler = new M6809Assembler(program, env2, platform)
val output = assembler.assemble(callGraph, assemblyOptimizations, options, VeryLateM6809AssemblyOptimizations.All)
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${assembler.endLabelMap.get(l)match{case Some((c,e)) => f"-$$$e%04x $c%s"; case _ => ""}}%s") }
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")
if (source.contains("return [")) {
for (_ <- 0 until 10; i <- 0xfffe.to(0, -1)) {
if (memoryBank.readable(i)) memoryBank.readable(i + 1) = true
}
}
if (source.contains("w&x")) {
for (i <- 0 until 0x10000) {
memoryBank.writeable(i) = true
}
}
val timings = run(log, memoryBank, platform.codeAllocators("default").startAt)
log.clearErrors()
timings
case f: Failure[_, _] =>
println(f)
println(f.extra.toString)
println(f.lastParser.toString)
log.error("Syntax error", Some(parserF.lastPosition))
fail("syntax error")
}
}
private def debugState(q: MC6809): Unit =
println(f"D=${q.d.get()}%04X X=${q.x.get()}%04X Y=${q.y.get()}%04X U=${q.u.get()}%04X S=${q.s.get()}%04X PC=${q.pc.get()}%04X " +
formatBool(q.cc.bit_e, 'E') +formatBool(q.cc.bit_f, 'F') + formatBool(q.cc.bit_h, 'H') +
formatBool(q.cc.bit_i, 'I') + formatBool(q.cc.bit_n, 'N') + formatBool(q.cc.bit_z, 'Z') +
formatBool(q.cc.bit_v, 'V') + formatBool(q.cc.bit_c, 'C'))
def run(log: ConsoleLogger, memoryBank: MemoryBank, startAt: Int): (Timings, MemoryBank) = {
(0x200 until 0x2000).takeWhile(memoryBank.occupied(_)).map(memoryBank.output).grouped(16).map(_.map(i => f"$i%02x").mkString(" ")).foreach(log.debug(_))
val bus = new BusStraight()
bus.addMemorySegment(new M6809Memory(memoryBank, startAt))
val cpu = new MC6809(bus)
bus.clearNMI()
cpu.pc.set(startAt)
cpu.s.set(0xfff0)
// val method = classOf[MC6809].getDeclaredMethod("setTraceInstructions", classOf[Boolean])
// method.setAccessible(true)
// method.invoke(cpu, true.asInstanceOf[AnyRef])
// debugState(cpu)
while (cpu.pc.get() > 2) {
cpu.execute()
// debugState(cpu)
bus.getCycleCounter should be < TooManyCycles
}
Timings(bus.getCycleCounter, bus.getCycleCounter) -> memoryBank
}
}