Add support for Mesen label file format by tracking file-relative positions of segments (#128)

This commit is contained in:
Karol Stasiak 2021-11-03 21:48:45 +01:00
parent b168818bab
commit 73a223b9b6
22 changed files with 273 additions and 102 deletions

1
.gitignore vendored
View File

@ -35,6 +35,7 @@ issue*.mfk
*.nl
*.fns
*.sym
*.mlb
*.deb
*.xex
*.nes

View File

@ -48,6 +48,8 @@ The extension and the file format are platform-dependent.
* `-G fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `-G mesen` format used by the Mesen emulator. The extension is `.mlb`.
* `-G raw` Millfork-specific format. The extension is '.labels'. Each row contains bank number, start address, end address (if known), object type, and Millfork-specific object identifier.
* `-fbreakpoints`, `-fno-breakpoints`

View File

@ -267,3 +267,5 @@ Default: `main,*`
* `sym` format used by the WLA/DX assembler. The extension is `.sym`.
* `fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `mesen` format used by the Mesen emulator. The extension is `.mlb`.

View File

@ -1,5 +1,10 @@
package millfork
import millfork.output.{BankLayoutInFile, FormattableLabel}
import java.util.regex.Pattern
import scala.util.control.Breaks.{break, breakable}
/**
* @author Karol Stasiak
*/
@ -12,13 +17,16 @@ object DebugOutputFormat {
"fns" -> NesasmDebugOutputFormat,
"fceux" -> FceuxDebugOutputFormat,
"nl" -> FceuxDebugOutputFormat,
"mlb" -> MesenOutputFormat,
"mesen" -> MesenOutputFormat,
"asm6f" -> MesenOutputFormat,
"sym" -> SymDebugOutputFormat)
}
sealed trait DebugOutputFormat {
def formatAll(labels: Seq[(String, Int, Int, Char, Option[Int])], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLineTupled).mkString("\n") + "\n"
def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLine).mkString("\n") + "\n"
if (breakpoints.isEmpty) {
labelPart
} else {
@ -26,10 +34,7 @@ sealed trait DebugOutputFormat {
}
}
final def formatLineTupled(labelAndValue: (String, Int, Int, Char, Option[Int])): String =
formatLine(labelAndValue._1, labelAndValue._2, labelAndValue._3, labelAndValue._4, labelAndValue._5)
def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue: Option[Int]): String
def formatLine(label: FormattableLabel): String
final def formatBreakpointTupled(value: (Int, Int)): Seq[String] = formatBreakpoint(value._1, value._2).toSeq
@ -48,8 +53,8 @@ sealed trait DebugOutputFormat {
}
object RawDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue: Option[Int]): String = {
f"$bank%02X:$startValue%04X:${endValue.fold("")(_.formatted("%04X"))}%s:$category%s:$label%s"
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02X:${label.startValue}%04X:${label.endValue.fold("")(_.formatted("%04X"))}%s:${label.category}%s:$label%s"
}
override def fileExtension(bank: Int): String = ".labels"
@ -63,9 +68,9 @@ object RawDebugOutputFormat extends DebugOutputFormat {
}
object ViceDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue: Option[Int]): String = {
val normalized = label.replace('$', '_').replace('.', '_')
s"al ${startValue.toHexString} .$normalized"
override def formatLine(label: FormattableLabel): String = {
val normalized = label.labelName.replace('$', '_').replace('.', '_')
s"al ${label.startValue.toHexString} .$normalized"
}
override def fileExtension(bank: Int): String = ".lbl"
@ -78,8 +83,8 @@ object ViceDebugOutputFormat extends DebugOutputFormat {
}
object NesasmDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue: Option[Int]): String = {
label + " = $" + startValue.toHexString
override def formatLine(label: FormattableLabel): String = {
label.labelName + " = $" + label.startValue.toHexString
}
override def fileExtension(bank: Int): String = ".fns"
@ -92,8 +97,8 @@ object NesasmDebugOutputFormat extends DebugOutputFormat {
}
object SymDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue:Option[Int]): String = {
f"$bank%02x:$startValue%04x $label%s"
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02x:${label.startValue}%04x ${label.labelName}%s"
}
override def fileExtension(bank: Int): String = ".sym"
@ -110,8 +115,8 @@ object SymDebugOutputFormat extends DebugOutputFormat {
}
object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, startValue: Int, category: Char, endValue: Option[Int]): String = {
f"$$$startValue%04x#$label%s#"
override def formatLine(label: FormattableLabel): String = {
f"$$${label.startValue}%04x#${label.labelName}%s#"
}
override def fileExtension(bank: Int): String = if (bank == 0xff) ".ram.nl" else s".$bank.nl"
@ -122,3 +127,52 @@ object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
}
object MesenOutputFormat extends DebugOutputFormat {
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val allStarts = labels.groupBy(_.bankName).mapValues(_.map(_.startValue).toSet)
labels.flatMap{ l =>
val mesenShift = b.getMesenShift(l.bankName)
val shiftedStart = l.startValue + mesenShift
var shiftedEnd = l.endValue.map(_ + mesenShift).filter(_ != shiftedStart)
l.endValue match {
case None =>
case Some(e) =>
// Mesen does not like labels of form XXX-XXX, where both ends are equal
breakable {
for (i <- l.startValue.+(1) to e) {
if (allStarts.getOrElse(l.bankName, Set.empty).contains(i)) {
shiftedEnd = None
break
}
}
}
}
if (shiftedStart >= 0 && shiftedEnd.forall(_ >= 0)) {
val normalized = l.labelName.replace('$', '_').replace('.', '_')
val comment = (l.category match {
case 'F' => "function "
case 'A' => "initialized array "
case 'V' => "initialized variable "
case 'a' => "array "
case 'v' => "variable "
case _ => ""
}) + l.labelName
Some(f"${l.mesenSymbol}%s:${shiftedStart}%04X${shiftedEnd.fold("")(e => f"-$e%04X")}%s:$normalized%s:$comment%s")
} else {
None
}
}.mkString("\n")
}
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
override def fileExtension(bank: Int): String = ".mlb"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
}

View File

@ -119,33 +119,35 @@ object Main {
else if (l.startsWith("__")) 7
else 0
}
val sortedLabels: Seq[(String, Int, Int, Char, Option[Int])] =
val sortedLabels: Seq[FormattableLabel] =
result.labels.groupBy(_._2).values
.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
.map { case (l, (b, s)) =>
val bankNumber = options.platform.bankNumbers.getOrElse(b, 0)
val mesenCategory = options.platform.getMesenLabelCategory(b, s)
result.endLabels.get(l) match {
case Some((c, e)) => (l, b, s, c, Some(e))
case _ => (l, b, s, 'x', None)
case Some((c, e)) => FormattableLabel(l, b, bankNumber, s, Some(e), c, mesenCategory)
case _ => FormattableLabel(l, b, bankNumber, s, None, 'x', mesenCategory)
}
}
val sortedBreakpoints = result.breakpoints
val format = c.outputLabelsFormatOverride.getOrElse(platform.outputLabelsFormat)
val basename = if (format.addOutputExtension) output + platform.fileExtension else output
if (format.filePerBank) {
val banks: Set[Int] = sortedLabels.map(_._2).toSet ++ sortedBreakpoints.map(_._1).toSet
val banks: Set[Int] = sortedLabels.map(_.bankNumber).toSet ++ sortedBreakpoints.map(_._1).toSet
banks.foreach{ bank =>
val labels = sortedLabels.filter(_._2.==(bank))
val labels = sortedLabels.filter(_.bankNumber.==(bank))
val breakpoints = sortedBreakpoints.filter(_._1.==(bank))
val labelOutput = basename + format.fileExtension(bank)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(labels, breakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, labels, breakpoints).getBytes(StandardCharsets.UTF_8))
}
} else {
val labelOutput = basename + format.fileExtension(0)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
}
}
val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension

View File

@ -42,6 +42,7 @@ class Platform(
val outputLabelsFormat: DebugOutputFormat,
val outputStyle: OutputStyle.Value
) {
def hasZeroPage: Boolean = cpuFamily == CpuFamily.M6502
def cpuFamily: CpuFamily.Value = CpuFamily.forType(this.cpu)
@ -60,6 +61,18 @@ class Platform(
val e2 = bankEndBefore(bank2)
e2 > s1 && s2 < e1
}
lazy val banksExplicitlyWrittenToOutput: Set[String] = bankNumbers.keySet.filter(b => defaultOutputPackager.writes(b)).toSet
def getMesenLabelCategory(bank: String, address: Int): Char = {
// see: https://www.mesen.ca/docs/debugging/debuggerintegration.html#mesen-label-files-mlb
val start = bankStart(bank)
val end = bankEndBefore(bank)
if (start > address || address >= end) 'G'
else if (banksExplicitlyWrittenToOutput(bank)) 'P'
else if (bank == "default") 'R'
else 'W' // TODO: distinguish between W and S
}
}
object Platform {

View File

@ -10,7 +10,7 @@ import millfork.node.NiceFunctionProperty
* @author Karol Stasiak
*/
case class OptimizationContext(options: CompilationOptions,
labelMap: Map[String, (Int, Int)],
labelMap: Map[String, (String, Int)],
zreg: Option[ThingInMemory],
niceFunctionProperties: Set[(NiceFunctionProperty, String)]) {
@inline

View File

@ -101,7 +101,7 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
}
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (Int, Int)],
val labelMap: Map[String, (String, Int)],
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {
@inline

View File

@ -102,7 +102,7 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
}
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (Int, Int)],
val labelMap: Map[String, (String, Int)],
val zeropageRegister: Option[ThingInMemory],
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {

View File

@ -117,7 +117,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
callGraph: CallGraph,
allocators: Map[String, VariableAllocator],
options: CompilationOptions,
onEachVariable: (String, (Int, Int)) => Unit,
onEachVariable: (String, (String, Int)) => Unit,
onEachVariableEnd: (String, (Char, Int)) => Unit,
pass: Int,
forZpOnly: Boolean): Unit = {
@ -184,7 +184,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
val addr =
allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Zeropage, alignment = m.alignment)
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
@ -208,7 +208,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case None => Nil
case Some(addr) =>
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p)
@ -220,7 +220,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
} else {
val addr = allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Either, alignment = m.alignment)
if (log.traceEnabled) log.trace("addr $" + addr.toHexString)
onEachVariable(m.name, bank0.index -> addr)
onEachVariable(m.name, bank -> addr)
onEachVariableEnd(m.name, (if (m.isInstanceOf[MfArray])'a' else 'v') -> (addr + m.sizeInBytes - 1))
List(
ConstantThing(graveName, NumericConstant(addr, 2), p)

View File

@ -19,7 +19,12 @@ import scala.collection.mutable.ArrayBuffer
* @author Karol Stasiak
*/
case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, (Int, Int))], endLabels: Map[String, (Char, Int)], breakpoints: List[(Int, Int)])
case class AssemblerOutput(code: Map[String, Array[Byte]],
bankLayoutInFile: BankLayoutInFile,
asm: Array[String],
labels: List[(String, (String, Int))],
endLabels: Map[String, (Char, Int)],
breakpoints: List[(Int, Int)])
abstract class AbstractAssembler[T <: AbstractCode](private val program: Program,
private val rootEnv: Environment,
@ -33,9 +38,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
var initializedVariablesSize: Int = 0
protected val log: Logger = rootEnv.log
val labelMap: mutable.Map[String, (Int, Int)] = mutable.Map()
val labelMap: mutable.Map[String, (String, Int)] = mutable.Map()
val endLabelMap: mutable.Map[String, (Char, Int)] = mutable.Map()
val unimportantLabelMap: mutable.Map[String, (Int, Int)] = mutable.Map()
val unimportantLabelMap: mutable.Map[String, (String, Int)] = mutable.Map()
val mem = new CompiledMemory(platform.bankNumbers.toList, platform.bankFill, platform.isBigEndian, labelMap, log)
val breakpointSet: mutable.Set[(Int, Int)] = mutable.Set()
private val bytesToWriteLater = mutable.ListBuffer[(String, Int, Constant, Option[Position])]()
@ -116,7 +121,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
try {
if (labelMap.contains(th.name)) return labelMap(th.name)._2
if (labelMap.contains(th.name + "`")) return labelMap(th.name)._2
if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[(Int, Int)](th.name, labelMap(th.name + ".array"))._2
if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[(String, Int)](th.name, labelMap(th.name + ".array"))._2
if (unimportantLabelMap.contains(th.name)) return labelMap(th.name)._2
val x1 = env.maybeGet[ConstantThing](th.name).map(_.value)
val x2 = env.maybeGet[ConstantThing](th.name + "`").map(_.value)
@ -247,11 +252,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case tim: VariableInMemory =>
tim.toAddress match {
case NumericConstant(n, _) =>
val m = mem.banks(tim.bank(options))
val bank = tim.bank(options)
val m = mem.banks(bank)
for (i <- 0 until tim.typ.size) {
m.occupied(i + n.toInt) = true
}
labelMap.put(tim.name, m.index -> n.toInt)
labelMap.put(tim.name, bank -> n.toInt)
endLabelMap.put(tim.name,
(if (tim.isInstanceOf[InitializedMemoryVariable]) 'V' else 'v') ->
(n.toInt + tim.typ.size - 1))
@ -260,11 +266,12 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case arr: MfArray =>
arr.toAddress match {
case NumericConstant(n, _) =>
val m = mem.banks(arr.bank(options))
val bank = arr.bank(options)
val m = mem.banks(bank)
for (i <- 0 until arr.sizeInBytes) {
m.occupied(i + n.toInt) = true
}
labelMap.put(arr.name, m.index -> n.toInt)
labelMap.put(arr.name, bank -> n.toInt)
endLabelMap.put(arr.name,
(if (arr.isInstanceOf[InitializedArray]) 'A' else 'a') ->
(n.toInt + arr.sizeInBytes - 1))
@ -401,7 +408,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val index = f.address.get.asInstanceOf[NumericConstant].value.toInt
compiledFunctions(f.name) match {
case NormalCompiledFunction(_, functionCode, _, _, _) =>
labelMap(f.name) = bank0.index -> index
labelMap(f.name) = bank -> index
val end = outputFunction(bank, functionCode, index, assembly, options)
endLabelMap(f.name) = 'F' -> (end - 1)
for (i <- index until end) {
@ -451,7 +458,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val size = functionCode.map(_.sizeInBytes).sum
val bank0 = mem.banks(bank)
val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'F' -> (index + size - 1)
justAfterCode += bank -> outputFunction(bank, functionCode, index, assembly, options)
case _ =>
@ -486,7 +493,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
if (bank != "default") ???
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = m.alignment)
labelMap(name) = bank0.index -> (index + 1)
labelMap(name) = bank -> (index + 1)
val altName = m.name.stripPrefix(env.prefix) + "`"
val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array")
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
@ -537,7 +544,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, thing.sizeInBytes, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'A' -> (index + thing.sizeInBytes - 1)
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
@ -568,7 +575,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
val bank0 = mem.banks(bank)
var index = codeAllocators(bank).allocateBytes(bank0, options, typ.alignedSize, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment)
labelMap(name) = bank0.index -> index
labelMap(name) = bank -> index
endLabelMap(name) = 'V' -> (index + typ.size - 1)
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
@ -610,9 +617,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
if (size < 0) log.fatal("Negative writable memory size. It's a compiler bug.")
val ivAddr = codeAllocators(ivBank).allocateBytes(ib, options, size, initialized = true, writeable = false, AllocationLocation.High, NoAlignment)
unimportantLabelMap += "__rwdata_init_start" -> (ib.index -> ivAddr)
unimportantLabelMap += "__rwdata_init_end" -> (ib.index -> (ivAddr + size))
unimportantLabelMap += "__rwdata_size" -> (ib.index -> size)
unimportantLabelMap += "__rwdata_init_start" -> (ivBank -> ivAddr)
unimportantLabelMap += "__rwdata_init_end" -> (ivBank -> (ivAddr + size))
unimportantLabelMap += "__rwdata_size" -> (ivBank -> size)
for (i <- 0 until size) {
ib.output(ivAddr + i) = db.output(rwDataStart + i)
db.outputted(rwDataStart + i) = true
@ -650,30 +657,30 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
if (platform.freeZpBytes.nonEmpty) {
val zpUsageOffset = platform.freeZpBytes.min
val zeropageOccupation = zpOccupied.slice(zpUsageOffset, platform.freeZpBytes.max + 1)
unimportantLabelMap += "__zeropage_usage" -> (defaultBank, zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1)
unimportantLabelMap += "__zeropage_first" -> (defaultBank, zpUsageOffset + (zeropageOccupation.indexOf(true) max 0))
unimportantLabelMap += "__zeropage_last" -> (defaultBank, zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0))
unimportantLabelMap += "__zeropage_end" -> (defaultBank, zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1)
unimportantLabelMap += "__zeropage_usage" -> ("default", zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1)
unimportantLabelMap += "__zeropage_first" -> ("default", zpUsageOffset + (zeropageOccupation.indexOf(true) max 0))
unimportantLabelMap += "__zeropage_last" -> ("default", zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0))
unimportantLabelMap += "__zeropage_end" -> ("default", zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1)
} else {
unimportantLabelMap += "__zeropage_usage" -> (defaultBank -> 0)
unimportantLabelMap += "__zeropage_first" -> (defaultBank -> 3)
unimportantLabelMap += "__zeropage_last" -> (defaultBank -> 2)
unimportantLabelMap += "__zeropage_end" -> (defaultBank -> 3)
unimportantLabelMap += "__zeropage_usage" -> ("default" -> 0)
unimportantLabelMap += "__zeropage_first" -> ("default" -> 3)
unimportantLabelMap += "__zeropage_last" -> ("default" -> 2)
unimportantLabelMap += "__zeropage_end" -> ("default" -> 3)
}
unimportantLabelMap += "__rwdata_start" -> (defaultBank -> rwDataStart)
unimportantLabelMap += "__rwdata_end" -> (defaultBank -> rwDataEnd)
unimportantLabelMap += "__heap_start" -> (defaultBank -> variableAllocators("default").heapStart)
unimportantLabelMap += "__rwdata_start" -> ("default" -> rwDataStart)
unimportantLabelMap += "__rwdata_end" -> ("default" -> rwDataEnd)
unimportantLabelMap += "__heap_start" -> ("default" -> variableAllocators("default").heapStart)
for (segment <- platform.bankNumbers.keys) {
val variableAllocator = options.platform.variableAllocators(segment)
val codeAllocator = options.platform.codeAllocators(segment)
unimportantLabelMap += s"segment.$segment.start" -> (defaultBank -> codeAllocator.startAt)
unimportantLabelMap += s"segment.$segment.codeend" -> (defaultBank -> (codeAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.datastart" -> (defaultBank -> variableAllocator.startAt)
unimportantLabelMap += s"segment.$segment.heapstart" -> (defaultBank -> variableAllocator.heapStart)
unimportantLabelMap += s"segment.$segment.end" -> (defaultBank -> (variableAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.length" -> (defaultBank -> (variableAllocator.endBefore - codeAllocator.startAt))
unimportantLabelMap += s"segment.$segment.bank" -> (defaultBank -> platform.bankNumbers(segment))
unimportantLabelMap += s"segment.$segment.fill" -> (defaultBank -> platform.bankFill(segment))
unimportantLabelMap += s"segment.$segment.start" -> ("default" -> codeAllocator.startAt)
unimportantLabelMap += s"segment.$segment.codeend" -> ("default" -> (codeAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.datastart" -> ("default" -> variableAllocator.startAt)
unimportantLabelMap += s"segment.$segment.heapstart" -> ("default" -> variableAllocator.heapStart)
unimportantLabelMap += s"segment.$segment.end" -> ("default" -> (variableAllocator.endBefore - 1))
unimportantLabelMap += s"segment.$segment.length" -> ("default" -> (variableAllocator.endBefore - codeAllocator.startAt))
unimportantLabelMap += s"segment.$segment.bank" -> ("default" -> platform.bankNumbers(segment))
unimportantLabelMap += s"segment.$segment.fill" -> ("default" -> platform.bankFill(segment))
}
env = rootEnv.allThings
@ -754,13 +761,18 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
assembly += f" ; $$$v%04X = $l%s"
}
var bankLayoutInFile = BankLayoutInFile.empty
// TODO:
val code = (platform.outputStyle match {
case OutputStyle.Single | OutputStyle.LUnix => List("default")
case OutputStyle.PerBank => platform.bankNumbers.keys.toList
}).map{b =>
val outputPackager = platform.outputPackagers.getOrElse(b, platform.defaultOutputPackager)
b -> outputPackager.packageOutput(mem, b)
val tuple = outputPackager.packageOutputAndLayout(mem, b)
if (b == "default") {
bankLayoutInFile = tuple._2
}
b -> tuple._1
}.toMap
if (options.flag(CompilationFlag.DataMissingInOutputWarning)) {
for (bank <- mem.banks.keys.toSeq.sorted) {
@ -771,7 +783,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
}
}
AssemblerOutput(code, assembly.toArray, labelMap.toList, endLabelMap.toMap, breakpointSet.toList.sorted)
AssemblerOutput(code, bankLayoutInFile, assembly.toArray, labelMap.toList, endLabelMap.toMap, breakpointSet.toList.sorted)
}
private def printArrayToAssemblyOutput(assembly: ArrayBuffer[String], name: String, elementType: Type, items: Seq[Expression]): Unit = {
@ -804,13 +816,13 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
}
}
def injectLabels(labelMap: Map[String, (Int, Int)], code: List[T]): List[T]
def injectLabels(labelMap: Map[String, (String, Int)], code: List[T]): List[T]
private def compileFunction(f: NormalFunction,
optimizations: Seq[AssemblyOptimization[T]],
options: CompilationOptions,
inlinedFunctions: Map[String, List[T]],
labelMap: Map[String, (Int, Int)],
labelMap: Map[String, (String, Int)],
niceFunctionProperties: Set[(NiceFunctionProperty, String)]): List[T] = {
log.debug("Compiling: " + f.name, f.position)
val unoptimized: List[T] =

View File

@ -0,0 +1,32 @@
package millfork.output
/**
* @author Karol Stasiak
*/
object BankLayoutInFile{
def empty: BankLayoutInFile = new BankLayoutInFile(Map.empty, Map.empty)
}
class BankLayoutInFile(startInFile: Map[String, Int], firstAddress: Map[String, Int]) {
val fileHeaderSize: Int = if (startInFile.isEmpty) 0 else startInFile.values.min
// Mesen represents all label values as offsets from the start of ROM data in the .nes file,
// not as actual addresses
def getMesenShift(bank: String): Int = {
val s = startInFile.getOrElse(bank, 0)
val f = firstAddress.getOrElse(bank, 0)
// for example, a typical 32K NROM PRG has:
// s = 16
// h = 16
// f = 0x8000
// addresses = 0x8000-0xffff
// Mesen Label values = 0x0000-0x7fff
// shift = -0x8000
s - fileHeaderSize - f
}
def wasWritten(bank: String): Boolean = startInFile.contains(bank)
def Ld65Offset(bank: String): Int = startInFile(bank)
}

View File

@ -7,7 +7,7 @@ import scala.collection.mutable
/**
* @author Karol Stasiak
*/
class CompiledMemory(bankNames: List[(String, Int)], bankFills: Map[String, Int], bigEndian: Boolean, labels: mutable.Map[String, (Int, Int)], log: Logger) {
class CompiledMemory(bankNames: List[(String, Int)], bankFills: Map[String, Int], bigEndian: Boolean, labels: mutable.Map[String, (String, Int)], log: Logger) {
var programName = "MILLFORK"
val banks: mutable.Map[String, MemoryBank] = mutable.Map(bankNames.map{p =>
val bank = new MemoryBank(p._2, bigEndian)

View File

@ -23,7 +23,7 @@ object D88Output extends OutputPackager {
).map(_.toByte)
override def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
override def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val start = b.start
val header = new D88Header(mem.programName.take(16))

View File

@ -0,0 +1,14 @@
package millfork.output
/**
* @author Karol Stasiak
*/
case class FormattableLabel(
labelName: String,
bankName: String,
bankNumber: Int,
startValue: Int,
endValue: Option[Int],
category: Char,
mesenSymbol: Char
)

View File

@ -20,7 +20,7 @@ class M6809Assembler(program: Program,
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 injectLabels(labelMap: Map[String, (String, Int)], code: List[MLine]): List[MLine] = code
override def quickSimplify(code: List[MLine]): List[MLine] = code
@ -105,7 +105,7 @@ class M6809Assembler(program: Program,
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
labelMap(labelName) = bank -> index
index
case MLine0(op, NonExistent, _) if MOpcode.NoopDiscard(op) =>
index

View File

@ -42,7 +42,7 @@ class MosAssembler(program: Program,
case AssemblyLine0(_, RawByte, _) => log.fatal("BYTE opcode failure")
case AssemblyLine0(LABEL, _, MemoryAddressConstant(Label(labelName))) =>
val bank0 = mem.banks(bank)
labelMap(labelName) = bank0.index -> index
labelMap(labelName) = bank -> index
index
case AssemblyLine0(CHANGED_MEM, DoesNotExist, MemoryAddressConstant(Label(l))) if l.contains("..brk") =>
breakpointSet += mem.banks(bank).index -> index
@ -112,7 +112,7 @@ class MosAssembler(program: Program,
}
}
override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[AssemblyLine]): List[AssemblyLine] = {
override def injectLabels(labelMap: Map[String, (String, Int)], code: List[AssemblyLine]): List[AssemblyLine] = {
import Opcode._
code.map {
case l@AssemblyLine(LDA | STA | CMP |

View File

@ -3,60 +3,98 @@ package millfork.output
import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import java.util.Locale
import scala.collection.mutable
/**
* @author Karol Stasiak
*/
trait OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte]
def packageOutputAndLayout(mem: CompiledMemory, bank: String): (Array[Byte], BankLayoutInFile) = {
val startInFile = mutable.Map[String, Int]()
val firstAddress = mutable.Map[String, Int]()
val fileLayoutCollector = new FileLayoutCollector(startInFile, firstAddress)
val output = packageOutput(fileLayoutCollector, mem, bank)
output -> fileLayoutCollector.toBankLayoutInFile
}
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte]
def writes(b: String): Boolean = false
}
class FileLayoutCollector(startInFile: mutable.Map[String, Int],
firstAddress: mutable.Map[String, Int],
offset: Int = 0) {
def reportBankChunk(bank: String, extraOffset: Int, addr: Int): Unit = {
if (!startInFile.contains(bank)) {
println(s"bank $bank starts at $offset + $extraOffset")
startInFile(bank) = offset + extraOffset
}
if (!firstAddress.contains(bank)) {
firstAddress(bank) = addr
}
}
def +(deltaOffset: Int) = new FileLayoutCollector(startInFile, firstAddress, offset + deltaOffset)
def toBankLayoutInFile = new BankLayoutInFile(startInFile.toMap, firstAddress.toMap)
}
case class SequenceOutput(children: List[OutputPackager]) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val baos = new ByteArrayOutputStream
children.foreach { c =>
val a = c.packageOutput(mem, bank)
var f = flc
for (c <- children) {
val a = c.packageOutput(f, mem, bank)
baos.write(a, 0, a.length)
f += a.length
}
baos.toByteArray
}
override def writes(b: String): Boolean = children.exists(_.writes(b))
}
case class ConstOutput(byte: Byte) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = Array(byte)
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = Array(byte)
}
case class CurrentBankFragmentOutput(start: Int, end: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
b.markAsOutputted(start, end + 1)
flc.reportBankChunk(bank, 0, start)
b.output.slice(start, end + 1)
}
}
case class BankFragmentOutput(alwaysBank: String, start: Int, end: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(alwaysBank)
b.markAsOutputted(start, end + 1)
flc.reportBankChunk(alwaysBank, 0, start)
b.output.slice(start, end + 1)
}
override def writes(b: String): Boolean = b == alwaysBank
}
case class ProgramNameOutput(length: Int) extends OutputPackager {
def isAlphanum(c: Char): Boolean = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
mem.programName.toUpperCase(Locale.ROOT).filter(isAlphanum).take(length).padTo(length, ' ').getBytes(StandardCharsets.US_ASCII)
}
}
case class StringOutput(string: String) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
string.getBytes(StandardCharsets.US_ASCII)
}
}
case class StartAddressOutput(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = b.start + bonus
Array(x.toByte, x.>>(8).toByte)
@ -64,7 +102,7 @@ case class StartAddressOutput(bonus: Int) extends OutputPackager {
}
case class StartAddressOutputBe(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = b.start + bonus
Array(x.>>(8).toByte, x.toByte)
@ -72,14 +110,14 @@ case class StartAddressOutputBe(bonus: Int) extends OutputPackager {
}
object StartPageOutput extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
Array(b.start.>>(8).toByte)
}
}
case class EndAddressOutput(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = b.end + bonus
Array(x.toByte, x.>>(8).toByte)
@ -87,7 +125,7 @@ case class EndAddressOutput(bonus: Int) extends OutputPackager {
}
case class EndAddressOutputBe(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = b.end + bonus
Array(x.>>(8).toByte, x.toByte)
@ -96,7 +134,7 @@ case class EndAddressOutputBe(bonus: Int) extends OutputPackager {
case class SymbolAddressOutput(symbol: String, bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = mem.getAddress(symbol) + bonus
Array(b.end.toByte, b.end.>>(8).toByte)
@ -104,7 +142,7 @@ case class SymbolAddressOutput(symbol: String, bonus: Int) extends OutputPackage
}
case class SymbolAddressOutputBe(symbol: String, bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val x = mem.getAddress(symbol) + bonus
Array(x.>>(8).toByte, x.toByte)
@ -112,7 +150,7 @@ case class SymbolAddressOutputBe(symbol: String, bonus: Int) extends OutputPacka
}
object PageCountOutput extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val e = mem.banks(bank).end.>>(8)
val s = mem.banks(bank).start.>>(8)
Array((e - s + 1).toByte)
@ -120,15 +158,16 @@ object PageCountOutput extends OutputPackager {
}
object AllocatedDataOutput extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
b.markAsOutputted(b.start, b.end + 1)
flc.reportBankChunk(bank, 0, b.start)
b.output.slice(b.start, b.end + 1)
}
}
case class AllocatedDataLength(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val size = b.end - b.start + 1 + bonus
Array(size.toByte, size.>>(8).toByte)
@ -136,7 +175,7 @@ case class AllocatedDataLength(bonus: Int) extends OutputPackager {
}
case class AllocatedDataLengthBe(bonus: Int) extends OutputPackager {
def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val size = b.end - b.start + 1 + bonus
Array(size.>>(8).toByte, size.toByte)

View File

@ -9,7 +9,7 @@ class TapOutput(val symbol: String) extends OutputPackager {
def isAlphanum(c: Char): Boolean = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
override def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
override def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val filteredName: String = mem.programName.filter(isAlphanum)
val b = mem.banks(bank)
val code = b.output.slice(b.start, b.end + 1)

View File

@ -4,7 +4,7 @@ package millfork.output
* @author Karol Stasiak
*/
class TrsCmdOutput(symbol: String) extends OutputPackager {
override def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = {
override def packageOutput(flc: FileLayoutCollector, mem: CompiledMemory, bank: String): Array[Byte] = {
val b = mem.banks(bank)
val start = b.start
val run = mem.getAddress(symbol)

View File

@ -97,7 +97,7 @@ class Z80Assembler(program: Program,
try { instr match {
case ZLine0(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName))) =>
val bank0 = mem.banks(bank)
labelMap(labelName) = bank0.index -> index
labelMap(labelName) = bank -> index
index
case ZLine0(BYTE, NoRegisters, param) =>
writeByte(bank, index, param)
@ -846,7 +846,7 @@ class Z80Assembler(program: Program,
}
}
override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[ZLine]): List[ZLine] = code // TODO
override def injectLabels(labelMap: Map[String, (String, Int)], code: List[ZLine]): List[ZLine] = code // TODO
override def quickSimplify(code: List[ZLine]): List[ZLine] = code.map(a => a.copy(parameter = a.parameter.quickSimplify))

View File

@ -22,7 +22,7 @@ class Z80ToX86Crossassembler(program: Program,
} else code
}
override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[ZLine]): List[ZLine] = code // TODO
override def injectLabels(labelMap: Map[String, (String, Int)], code: List[ZLine]): List[ZLine] = code // TODO
override def quickSimplify(code: List[ZLine]): List[ZLine] = code.map(a => a.copy(parameter = a.parameter.quickSimplify))
@ -83,7 +83,7 @@ class Z80ToX86Crossassembler(program: Program,
instr match {
case ZLine0(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName))) =>
val bank0 = mem.banks(bank)
labelMap(labelName) = bank0.index -> index
labelMap(labelName) = bank -> index
index
case ZLine0(BYTE, NoRegisters, param) =>
writeByte(bank, index, param)