mirror of
https://github.com/KarolS/millfork.git
synced 2024-06-12 22:29:33 +00:00
225 lines
9.6 KiB
Scala
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
|
|
}
|
|
}
|
|
|
|
|