mirror of
https://github.com/KarolS/millfork.git
synced 2025-03-28 08:30:13 +00:00
Support breakpoints in the label file (#44)
This commit is contained in:
parent
3cc9996531
commit
83b85ef0fc
@ -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)))
|
||||
|
28
src/main/scala/millfork/env/Environment.scala
vendored
28
src/main/scala/millfork/env/Environment.scala
vendored
@ -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, _, _) =>
|
||||
|
Loading…
x
Reference in New Issue
Block a user