1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-03-28 08:30:13 +00:00

Support breakpoints in the label file ()

This commit is contained in:
Karol Stasiak 2020-03-15 23:48:27 +01:00
parent 3cc9996531
commit 83b85ef0fc
14 changed files with 106 additions and 15 deletions

@ -6,6 +6,8 @@
* Various improvements to the C64 libraries (thanks to @bsutherland)
* Added `breakpoint` macro (#44).
* 6502: Fixed undocumented mnemonics.
* Create output directories when needed (#21)

@ -48,6 +48,12 @@ The extension and the file format are platform-dependent.
* `-G fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `-fbreakpoints`, `-fno-breakpoints`
Whether the compiler should use the `breakpoint` macro.
When enabled, breakpoints become memory barriers and the label file will contain the breakpoints if the format supports them.
Currently, the only formats that supports breakpoints are `vice` and `sym`.
`.ini` equivalent: `breakpoints`. Default: yes.
* `-I <dir>;<dir>` The include directories.
Those directories are searched for modules and platform definitions.
When searching for modules, the directory containing the file currently being compiled is also searched.

@ -94,4 +94,14 @@ The optimizer should not optimize any memory accesses across that macro.
Available for: all targets.
#### `macro void breakpoint()`
If the `-fbreakpoints` option is selected (default), then it emits a memory barrier,
and also outputs a breakpoint to the label file (if the format of the label file allows it).
If the `-fno-breakpoints` option is selected, then it does nothing.
Available for: all targets.

@ -408,7 +408,7 @@ object Cpu extends Enumeration {
import CompilationFlag._
private val alwaysDefaultFlags = Set(
VariableOverlap, CompactReturnDispatchParams, FunctionFallthrough, RegisterVariables, FunctionDeduplication
VariableOverlap, CompactReturnDispatchParams, FunctionFallthrough, RegisterVariables, FunctionDeduplication, EnableBreakpoints,
)
private val mosAlwaysDefaultFlags = alwaysDefaultFlags
@ -536,7 +536,7 @@ object Cpu extends Enumeration {
object CompilationFlag extends Enumeration {
val
// common compilation options:
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly,
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly, EnableBreakpoints,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitSC02Opcodes, EmitRockwellOpcodes, EmitWdcOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
@ -608,6 +608,7 @@ object CompilationFlag extends Enumeration {
"prevent_jmp_indirect_bug" -> PreventJmpIndirectBug,
"compact_dispatch_params" -> CompactReturnDispatchParams,
"lenient_encoding" -> LenientTextEncoding,
"breakpoints" -> EnableBreakpoints,
)
}

@ -14,17 +14,35 @@ object DebugOutputFormat {
"sym" -> SymDebugOutputFormat)
}
sealed trait DebugOutputFormat extends Function[(String, (Int, Int)), String] {
sealed trait DebugOutputFormat {
def apply(labelAndValue: (String, (Int, Int))): String = formatLine(labelAndValue._1, labelAndValue._2._1, labelAndValue._2._2)
def formatAll(labels: Seq[(String, (Int, Int))], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLineTupled).mkString("\n") + "\n"
if (breakpoints.isEmpty) {
labelPart
} else {
labelPart + breakpointsHeader + breakpoints.flatMap(formatBreakpointTupled).mkString("\n") + "\n"
}
}
final def formatLineTupled(labelAndValue: (String, (Int, Int))): String = formatLine(labelAndValue._1, labelAndValue._2._1, labelAndValue._2._2)
def formatLine(label: String, bank: Int, value: Int): String
final def formatBreakpointTupled(value: (Int, Int)): Seq[String] = formatBreakpoint(value._1, value._2).toSeq
def formatBreakpoint(bank: Int, value: Int): Option[String]
def fileExtension(bank: Int): String
def filePerBank: Boolean
//noinspection MutatorLikeMethodIsParameterless
def addOutputExtension: Boolean
def labelsHeader: String = ""
def breakpointsHeader: String = ""
}
object ViceDebugOutputFormat extends DebugOutputFormat {
@ -38,6 +56,8 @@ object ViceDebugOutputFormat extends DebugOutputFormat {
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
override def formatBreakpoint(bank: Int, value: Int): Option[String] = Some(s"break ${value.toHexString}")
}
object NesasmDebugOutputFormat extends DebugOutputFormat {
@ -50,6 +70,8 @@ object NesasmDebugOutputFormat extends DebugOutputFormat {
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
}
object SymDebugOutputFormat extends DebugOutputFormat {
@ -62,6 +84,12 @@ object SymDebugOutputFormat extends DebugOutputFormat {
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
override def formatBreakpoint(bank: Int, value: Int): Option[String] = Some(f"$bank%02x:$value%04x")
override def labelsHeader: String = "[labels]\n"
override def breakpointsHeader: String = "[breakpoints]\n"
}
object FceuxDebugOutputFormat extends DebugOutputFormat {
@ -74,4 +102,6 @@ object FceuxDebugOutputFormat extends DebugOutputFormat {
override def filePerBank: Boolean = true
override def addOutputExtension: Boolean = true
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
}

@ -119,20 +119,24 @@ object Main {
else 0
}
val sortedLabels = result.labels.groupBy(_._2).values.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
val sortedBreakpoints = result.breakpoints
val format = c.outputLabelsFormatOverride.getOrElse(platform.outputLabelsFormat)
val basename = if (format.addOutputExtension) output + platform.fileExtension else output
if (format.filePerBank) {
sortedLabels.groupBy(_._2._1).foreach{ case (bank, labels) =>
val banks = sortedLabels.map(_._2._1).toSet ++ sortedBreakpoints.map(_._2).toSet
banks.foreach{ bank =>
val labels = sortedLabels.filter(_._2._1.==(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, labels.map(format).mkString("\n").getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(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, sortedLabels.map(format).mkString("\n").getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
}
}
val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension
@ -410,6 +414,10 @@ object Main {
c.copy(outputLabels = true, outputLabelsFormatOverride = Some(f))
}.description("Generate also the label file in the given format. Available options: vice, nesasm, sym.")
boolean("-fbreakpoints", "-fno-breakpoints").action((c,v) =>
c.changeFlag(CompilationFlag.EnableBreakpoints, v)
).description("Include breakpoints in the label file. Requires either -g or -G.")
parameter("-t", "--target").placeholder("<platform>").action { (p, c) =>
assertNone(c.platform, "Platform already defined")
c.copy(platform = Some(p))

@ -36,7 +36,7 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] {
}
(eval ++ rts) -> Nil
case M6809AssemblyStatement(opcode, addrMode, expression, elidability) =>
ctx.env.evalForAsm(expression) match {
ctx.env.evalForAsm(expression, opcode) match {
case Some(e) => List(MLine(opcode, addrMode, e, elidability)) -> Nil
case None =>
println(statement)
@ -84,7 +84,7 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] {
case s:ContinueStatement =>
compileContinueStatement(ctx, s) -> Nil
case M6809AssemblyStatement(opcode, addrMode, expression, elidability) =>
ctx.env.evalForAsm(expression) match {
ctx.env.evalForAsm(expression, opcode) match {
case Some(param) =>
List(MLine(opcode, addrMode, param, elidability)) -> Nil
case None =>

@ -339,7 +339,7 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
x match {
// TODO: hmmm
case VariableExpression(name) =>
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL || OpcodeClasses.HudsonTransfer(o)) {
if (OpcodeClasses.ShortBranching(o) || o == JMP || o == LABEL || o == CHANGED_MEM || OpcodeClasses.HudsonTransfer(o)) {
MemoryAddressConstant(Label(name))
} else {
env.evalForAsm(x).getOrElse(env.get[ThingInMemory](name, x.position).toAddress)

@ -228,7 +228,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
val param: Constant = expression match {
// TODO: hmmm
case VariableExpression(name) =>
if (Seq(JP, JR, DJNZ, LABEL).contains(op)) {
if (Seq(JP, JR, DJNZ, LABEL, CHANGED_MEM).contains(op)) {
MemoryAddressConstant(Label(name))
} else {
env.evalForAsm(expression).orElse(env.maybeGet[ThingInMemory](name).map(_.toAddress)).getOrElse(MemoryAddressConstant(Label(name)))

@ -849,7 +849,16 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
def evalForAsm(e: Expression): Option[Constant] = {
def evalForAsm(e: Expression, op: MOpcode.Value = MOpcode.NOP): Option[Constant] = {
e match {
// TODO: hmmm
case VariableExpression(name) =>
import MOpcode._
if (MOpcode.Branching(op) || op == LABEL || op == CHANGED_MEM) {
return Some(MemoryAddressConstant(Label(name)))
}
case _ =>
}
e match {
case LiteralExpression(value, size) => Some(NumericConstant(value, size))
case ConstantArrayElementExpression(c) => Some(c)
@ -906,7 +915,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
private def constantOperationForAsm(op: MathOperator.Value, params: List[Expression]) = {
params.map(evalForAsm).reduceLeft[Option[Constant]] { (oc, om) =>
params.map(e => evalForAsm(e)).reduceLeft[Option[Constant]] { (oc, om) =>
for {
c <- oc
m <- om
@ -2139,6 +2148,21 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case _ => ???
})
}
if (!things.contains("breakpoint")) {
val p = get[VariableType]("pointer")
if (options.flag(CompilationFlag.EnableBreakpoints)) {
things("breakpoint") = MacroFunction("breakpoint", v, NormalParamSignature(Nil), this, CpuFamily.forType(options.platform.cpu) match {
case CpuFamily.M6502 => List(MosAssemblyStatement(Opcode.CHANGED_MEM, AddrMode.DoesNotExist, VariableExpression("..brk"), Elidability.Fixed))
case CpuFamily.I80 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, VariableExpression("..brk"), Elidability.Fixed))
case CpuFamily.I86 => List(Z80AssemblyStatement(ZOpcode.CHANGED_MEM, NoRegisters, None, VariableExpression("..brk"), Elidability.Fixed))
case CpuFamily.M6809 => List(M6809AssemblyStatement(MOpcode.CHANGED_MEM, NonExistent, VariableExpression("..brk"), Elidability.Fixed))
case _ => ???
})
} else {
things("breakpoint") = MacroFunction("breakpoint", v, NormalParamSignature(Nil), this, Nil)
}
}
}
def hintTypo(name: String): Unit = {

@ -19,7 +19,7 @@ import scala.collection.mutable.ArrayBuffer
* @author Karol Stasiak
*/
case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, (Int, Int))])
case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, (Int, Int))], breakpoints: List[(Int, Int)])
abstract class AbstractAssembler[T <: AbstractCode](private val program: Program,
private val rootEnv: Environment,
@ -35,6 +35,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
val mem = new CompiledMemory(platform.bankNumbers.toList, platform.bankFill, platform.isBigEndian)
val labelMap: mutable.Map[String, (Int, Int)] = mutable.Map()
val breakpointSet: mutable.Set[(Int, Int)] = mutable.Set()
private val bytesToWriteLater = mutable.ListBuffer[(String, Int, Constant)]()
private val wordsToWriteLater = mutable.ListBuffer[(String, Int, Constant)]()
@ -668,7 +669,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
case OutputStyle.Single | OutputStyle.LUnix => List("default")
case OutputStyle.PerBank => platform.bankNumbers.keys.toList
}).map(b => b -> platform.outputPackager.packageOutput(mem, b)).toMap
AssemblerOutput(code, assembly.toArray, labelMap.toList)
AssemblerOutput(code, assembly.toArray, labelMap.toList, breakpointSet.toList.sorted)
}
private def printArrayToAssemblyOutput(assembly: ArrayBuffer[String], name: String, elementType: Type, items: Seq[Expression]): Unit = {

@ -87,6 +87,9 @@ class M6809Assembler(program: Program,
index
case MLine0(op, NonExistent, _) if MOpcode.NoopDiscard(op) =>
index
case MLine0(CHANGED_MEM, NonExistent, MemoryAddressConstant(Label(l))) if l.contains("..brk") =>
breakpointSet += mem.banks(bank).index -> index
index
case MLine0(CHANGED_MEM, _, _) =>
index
case MLine0(TFR, TwoRegisters(source, target), param)

@ -43,6 +43,9 @@ class MosAssembler(program: Program,
val bank0 = mem.banks(bank)
labelMap(labelName) = bank0.index -> index
index
case AssemblyLine0(CHANGED_MEM, DoesNotExist, MemoryAddressConstant(Label(l))) if l.contains("..brk") =>
breakpointSet += mem.banks(bank).index -> index
index
case AssemblyLine0(_, DoesNotExist, _) =>
index
case AssemblyLine0(op, Implied, _) =>

@ -85,6 +85,9 @@ class Z80Assembler(program: Program,
case ZLine0(BYTE, NoRegisters, param) =>
writeByte(bank, index, param)
index + 1
case ZLine0(CHANGED_MEM, NoRegisters, MemoryAddressConstant(Label(l))) if l.contains("..brk") =>
breakpointSet += mem.banks(bank).index -> index
index
case ZLine0(DISCARD_F | DISCARD_HL | DISCARD_BC | DISCARD_DE | DISCARD_IX | DISCARD_IY | DISCARD_A | CHANGED_MEM, NoRegisters, _) =>
index
case ZLine0(LABEL | BYTE | DISCARD_F | DISCARD_HL | DISCARD_BC | DISCARD_DE | DISCARD_IX | DISCARD_IY | DISCARD_A | CHANGED_MEM, _, _) =>