1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-22 16:31:02 +00:00

Better tracking of the original source

This commit is contained in:
Karol Stasiak 2018-12-17 00:35:32 +01:00
parent 4fb786522d
commit 8fcf628c8f
17 changed files with 126 additions and 52 deletions

View File

@ -49,6 +49,8 @@ Choose syntax for assembly output on 8080-like targets.
* `-fline-numbers`, `-fno-line-numbers` Whether should show source line numbers in assembly output.
* `-fline-numbers`, `-fno-line-numbers` Whether should show the original source in assembly output. Implies `-fline-numbers`
## Verbosity options
* `-q` Suppress all messages except for errors.

View File

@ -50,6 +50,8 @@ You may be also interested in the following:
* `-s` additionally generate assembly output
(if targeting Intel 8080, use `--syntax=intel` or `--syntax=zilog` to choose the preferred assembly syntax)
* `-fsource-in-asm` show original Millfork source in the assembly output
* `-g` additionally generate a label file, in format compatible with VICE emulator
* `-r PROGRAM` automatically launch given program after successful compilation

View File

@ -304,7 +304,7 @@ object Cpu extends Enumeration {
object CompilationFlag extends Enumeration {
val
// common compilation options:
EmitIllegals, DecimalMode, ReadOnlyArrays, LenientTextEncoding, LineNumbersInAssembly,
EmitIllegals, DecimalMode, ReadOnlyArrays, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,

View File

@ -239,6 +239,7 @@ object Main {
result
}
//noinspection NameBooleanParameters
private def parser(errorReporting: Logger): CliParser[Context] = new CliParser[Context] {
fluff("Main options:", "")
@ -302,6 +303,14 @@ object Main {
c.changeFlag(CompilationFlag.LineNumbersInAssembly, v)
).description("Show source line numbers in assembly.")
boolean("-fsource-in-asm", "-fno-source-in-asm").action((c,v) =>
if (v) {
c.changeFlag(CompilationFlag.SourceInAssembly, true).changeFlag(CompilationFlag.LineNumbersInAssembly, true)
} else {
c.changeFlag(CompilationFlag.LineNumbersInAssembly, false)
}
).description("Show source in assembly.")
endOfFlags("--").description("Marks the end of options.")
fluff("", "Verbosity options:", "")

View File

@ -222,6 +222,8 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
def position(s: Option[Position]): ZLine = pos(SourceLine.of(s))
def positionIfEmpty(s: Option[Position]): ZLine = if (s.isEmpty || source.isDefined) this else pos(SourceLine.of(s))
def pos(s: Seq[Option[SourceLine]]): ZLine = pos(SourceLine.merge(s))
def mergePos(s: Seq[Option[SourceLine]]): ZLine = if (s.isEmpty) this else pos(SourceLine.merge(this.source, s))

View File

@ -25,7 +25,7 @@ abstract class MacroExpander[T <: AbstractCode] {
def h(s: String) = if (s == paramName) target.asInstanceOf[VariableExpression].name else s
stmt match {
(stmt match {
case RawBytesStatement(contents) => RawBytesStatement(contents.replaceVariable(paramName, target))
case ExpressionStatement(e) => ExpressionStatement(e.replaceVariable(paramName, target))
case ReturnStatement(e) => ReturnStatement(e.map(f))
@ -45,7 +45,7 @@ abstract class MacroExpander[T <: AbstractCode] {
case _ =>
println(stmt)
???
}
}).pos(stmt.position)
}
def inlineFunction(ctx: CompilationContext, i: MacroFunction, params: List[Expression], position: Option[Position]): (List[T], List[ExecutableStatement]) = {

View File

@ -192,7 +192,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
case _ => reg
}
List(ZLine(op, registers, param, elidability))
}).map(_.position(statement.position))
}).map(_.positionIfEmpty(statement.position))
}
private def fixStackOnReturn(ctx: CompilationContext): List[ZLine] = {

View File

@ -1,5 +1,6 @@
package millfork.error
import millfork.assembly.SourceLine
import millfork.node.Position
import scala.collection.mutable
@ -117,4 +118,8 @@ class ConsoleLogger extends Logger {
sourceLines(filename) = lines
}
override def getLine(line: SourceLine): Option[String] = for {
file <- sourceLines.get(line.moduleName)
line <- file.lift(line.line - 1)
} yield line
}

View File

@ -1,5 +1,6 @@
package millfork.error
import millfork.assembly.SourceLine
import millfork.node.Position
/**
@ -27,4 +28,6 @@ class ErrorsOnlyLogger(inner: Logger) extends Logger {
override def assertNoErrors(msg: String): Unit = inner.assertNoErrors(msg)
override def addSource(filename: String, lines: IndexedSeq[String]): Unit = ()
override def getLine(line: SourceLine): Option[String] = None
}

View File

@ -1,5 +1,6 @@
package millfork.error
import millfork.assembly.SourceLine
import millfork.node.Position
/**
@ -28,4 +29,6 @@ trait Logger {
def assertNoErrors(msg: String): Unit
def addSource(filename: String, lines: IndexedSeq[String]): Unit
def getLine(line: SourceLine): Option[String]
}

View File

@ -82,7 +82,7 @@ case class SeparateBytesExpression(hi: Expression, lo: Expression) extends LhsEx
def replaceVariable(variable: String, actualParam: Expression): Expression =
SeparateBytesExpression(
hi.replaceVariable(variable, actualParam),
lo.replaceVariable(variable, actualParam))
lo.replaceVariable(variable, actualParam)).pos(position)
override def containsVariable(variable: String): Boolean = hi.containsVariable(variable) || lo.containsVariable(variable)
override def isPure: Boolean = hi.isPure && lo.isPure
override def getAllIdentifiers: Set[String] = hi.getAllIdentifiers ++ lo.getAllIdentifiers
@ -90,7 +90,7 @@ case class SeparateBytesExpression(hi: Expression, lo: Expression) extends LhsEx
case class SumExpression(expressions: List[(Boolean, Expression)], decimal: Boolean) extends Expression {
override def replaceVariable(variable: String, actualParam: Expression): Expression =
SumExpression(expressions.map { case (n, e) => n -> e.replaceVariable(variable, actualParam) }, decimal)
SumExpression(expressions.map { case (n, e) => n -> e.replaceVariable(variable, actualParam) }, decimal).pos(position)
override def containsVariable(variable: String): Boolean = expressions.exists(_._2.containsVariable(variable))
override def isPure: Boolean = expressions.forall(_._2.isPure)
override def getAllIdentifiers: Set[String] = expressions.map(_._2.getAllIdentifiers).fold(Set[String]())(_ ++ _)
@ -100,7 +100,7 @@ case class FunctionCallExpression(functionName: String, expressions: List[Expres
override def replaceVariable(variable: String, actualParam: Expression): Expression =
FunctionCallExpression(functionName, expressions.map {
_.replaceVariable(variable, actualParam)
})
}).pos(position)
override def containsVariable(variable: String): Boolean = expressions.exists(_.containsVariable(variable))
override def isPure: Boolean = false // TODO
override def getAllIdentifiers: Set[String] = expressions.map(_.getAllIdentifiers).fold(Set[String]())(_ ++ _) + functionName
@ -108,7 +108,7 @@ case class FunctionCallExpression(functionName: String, expressions: List[Expres
case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends Expression {
override def replaceVariable(variable: String, actualParam: Expression): Expression =
HalfWordExpression(expression.replaceVariable(variable, actualParam), hiByte)
HalfWordExpression(expression.replaceVariable(variable, actualParam), hiByte).pos(position)
override def containsVariable(variable: String): Boolean = expression.containsVariable(variable)
override def isPure: Boolean = expression.isPure
override def getAllIdentifiers: Set[String] = expression.getAllIdentifiers
@ -178,10 +178,11 @@ case class IndexedExpression(name: String, index: Expression) extends LhsExpress
override def replaceVariable(variable: String, actualParam: Expression): Expression =
if (name == variable) {
actualParam match {
case VariableExpression(actualVariable) => IndexedExpression(actualVariable, index.replaceVariable(variable, actualParam))
case VariableExpression(actualVariable) =>
IndexedExpression(actualVariable, index.replaceVariable(variable, actualParam)).pos(position)
case _ => ??? // TODO
}
} else IndexedExpression(name, index.replaceVariable(variable, actualParam))
} else IndexedExpression(name, index.replaceVariable(variable, actualParam)).pos(position)
override def containsVariable(variable: String): Boolean = name == variable || index.containsVariable(variable)
override def isPure: Boolean = index.isPure
override def getAllIdentifiers: Set[String] = index.getAllIdentifiers + name

View File

@ -10,15 +10,15 @@ object UnreachableCode extends NodeOptimization {
override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = nodes match {
case (x:FunctionDeclarationStatement)::xs =>
x.copy(statements = x.statements.map(optimizeStatements(_, options))) :: optimize(xs, options)
x.copy(statements = x.statements.map(optimizeStatements(_, options))).pos(x.position) :: optimize(xs, options)
case (x:IfStatement)::xs =>
x.copy(
thenBranch = optimizeExecutableStatements(x.thenBranch, options),
elseBranch = optimizeExecutableStatements(x.elseBranch, options)) :: optimize(xs, options)
elseBranch = optimizeExecutableStatements(x.elseBranch, options)).pos(x.position) :: optimize(xs, options)
case (x:WhileStatement)::xs =>
x.copy(body = optimizeExecutableStatements(x.body, options)) :: optimize(xs, options)
x.copy(body = optimizeExecutableStatements(x.body, options)).pos(x.position) :: optimize(xs, options)
case (x:DoWhileStatement)::xs =>
x.copy(body = optimizeExecutableStatements(x.body, options)) :: optimize(xs, options)
x.copy(body = optimizeExecutableStatements(x.body, options)).pos(x.position) :: optimize(xs, options)
case (x:ReturnStatement) :: xs =>
x :: Nil
case x :: xs =>

View File

@ -72,7 +72,7 @@ object UnusedGlobalVariables extends NodeOptimization {
params.flatMap {
case VariableExpression(_) => None
case LiteralExpression(_, _) => None
case x => Some(ExpressionStatement(x))
case x => Some(ExpressionStatement(x).pos(s.position))
}
} else Some(s)
case s@Assignment(VariableExpression(n), VariableExpression(_)) =>
@ -80,35 +80,35 @@ object UnusedGlobalVariables extends NodeOptimization {
case s@Assignment(VariableExpression(n), LiteralExpression(_, _)) =>
if (globalsToRemove(n)) Nil else Some(s)
case s@Assignment(VariableExpression(n), expr) =>
if (globalsToRemove(n)) Some(ExpressionStatement(expr)) else Some(s)
case s@Assignment(SeparateBytesExpression(VariableExpression(h), VariableExpression(l)), expr) =>
if (globalsToRemove(n)) Some(ExpressionStatement(expr).pos(s.position)) else Some(s)
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), le@VariableExpression(l)), expr) =>
if (globalsToRemove(h)) {
if (globalsToRemove(l))
Some(ExpressionStatement(expr))
Some(ExpressionStatement(expr).pos(s.position))
else
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, VariableExpression(l)), expr))
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, le).pos(he.position), expr).pos(s.position))
} else {
if (globalsToRemove(l))
Some(Assignment(SeparateBytesExpression(VariableExpression(h), BlackHoleExpression), expr))
Some(Assignment(SeparateBytesExpression(he, BlackHoleExpression).pos(he.position), expr).pos(s.position))
else
Some(s)
}
case s@Assignment(SeparateBytesExpression(h, VariableExpression(l)), expr) =>
if (globalsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression), expr))
case s@Assignment(SeparateBytesExpression(h, le@VariableExpression(l)), expr) =>
if (globalsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression).pos(h.position), expr).pos(s.position))
else Some(s)
case s@Assignment(SeparateBytesExpression(VariableExpression(h), l), expr) =>
if (globalsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l), expr))
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), l), expr) =>
if (globalsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l).pos(he.position), expr).pos(s.position))
else Some(s)
case s: IfStatement =>
Some(s.copy(
thenBranch = removeVariablesFromStatement(s.thenBranch, globalsToRemove).asInstanceOf[List[ExecutableStatement]],
elseBranch = removeVariablesFromStatement(s.elseBranch, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
elseBranch = removeVariablesFromStatement(s.elseBranch, globalsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s: WhileStatement =>
Some(s.copy(
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s: DoWhileStatement =>
Some(s.copy(
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]))
body = removeVariablesFromStatement(s.body, globalsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s => Some(s)
}

View File

@ -80,7 +80,7 @@ object UnusedLocalVariables extends NodeOptimization {
params.flatMap {
case VariableExpression(_) => None
case LiteralExpression(_, _) => None
case x => Some(ExpressionStatement(x))
case x => Some(ExpressionStatement(x).pos(s.position))
}
} else Some(s)
case s@Assignment(VariableExpression(n), VariableExpression(_)) =>
@ -88,35 +88,52 @@ object UnusedLocalVariables extends NodeOptimization {
case s@Assignment(VariableExpression(n), LiteralExpression(_, _)) =>
if (localsToRemove(n)) Nil else Some(s)
case s@Assignment(VariableExpression(n), expr) =>
if (localsToRemove(n)) Some(ExpressionStatement(expr)) else Some(s)
case s@Assignment(SeparateBytesExpression(VariableExpression(h), VariableExpression(l)), expr) =>
if (localsToRemove(n)) Some(ExpressionStatement(expr).pos(s.position)) else Some(s)
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), le@VariableExpression(l)), expr) =>
if (localsToRemove(h)) {
if (localsToRemove(l))
Some(ExpressionStatement(expr))
Some(ExpressionStatement(expr).pos(s.position))
else
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, VariableExpression(l)), expr))
Some(Assignment(
SeparateBytesExpression(
BlackHoleExpression,
VariableExpression(l).pos(le.position)
).pos(he.position),
expr
).pos(s.position))
} else {
if (localsToRemove(l))
Some(Assignment(SeparateBytesExpression(VariableExpression(h), BlackHoleExpression), expr))
Some(Assignment(
SeparateBytesExpression(
VariableExpression(h).pos(he.position),
BlackHoleExpression
).pos(he.position),
expr
).pos(s.position))
else
Some(s)
}
case s@Assignment(SeparateBytesExpression(h, VariableExpression(l)), expr) =>
if (localsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression), expr))
if (localsToRemove(l)) Some(Assignment(
SeparateBytesExpression(h, BlackHoleExpression).pos(h.position),
expr
).pos(s.position))
else Some(s)
case s@Assignment(SeparateBytesExpression(VariableExpression(h), l), expr) =>
if (localsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l), expr))
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), l), expr) =>
if (localsToRemove(h)) Some(Assignment(
SeparateBytesExpression(BlackHoleExpression, l).pos(he.position), expr
).pos(s.position))
else Some(s)
case s: IfStatement =>
Some(s.copy(
thenBranch = removeVariables(s.thenBranch, localsToRemove).asInstanceOf[List[ExecutableStatement]],
elseBranch = removeVariables(s.elseBranch, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
elseBranch = removeVariables(s.elseBranch, localsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s: WhileStatement =>
Some(s.copy(
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s: DoWhileStatement =>
Some(s.copy(
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]))
body = removeVariables(s.body, localsToRemove).asInstanceOf[List[ExecutableStatement]]).pos(s.position))
case s => Some(s)
}

View File

@ -482,7 +482,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
private def outputFunction(bank: String, code: List[T], startFrom: Int, assOut: mutable.ArrayBuffer[String], options: CompilationOptions): Int = {
val printLineNumbers = options.flag(CompilationFlag.LineNumbersInAssembly)
val sourceInAssembly = options.flag(CompilationFlag.SourceInAssembly)
var index = startFrom
assOut.append(" ")
assOut.append("* = $" + startFrom.toHexString)
var lastSource = Option.empty[SourceLine]
for (instr <- code) {
@ -491,9 +493,18 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
lastSource = instr.source
if (printLineNumbers) {
lastSource match {
case Some(SourceLine(moduleName, line)) if line > 0 =>
case Some(sl@SourceLine(moduleName, line)) if line > 0 =>
if (sourceInAssembly) {
assOut.append("; ")
}
assOut.append(s";line:$line:$moduleName")
if (sourceInAssembly) {
log.getLine(sl).foreach(l => assOut.append("; " + l))
}
case _ =>
if (sourceInAssembly) {
assOut.append("; ")
}
assOut.append(s";line")
}
}
@ -517,6 +528,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
index = emitInstruction(bank, options, index, instr)
}
if (printLineNumbers && lastSource.isDefined){
if (sourceInAssembly) assOut.append("; ")
assOut.append(s";line")
}
index

View File

@ -64,17 +64,25 @@ object MosInliningCalculator extends AbstractInliningCalculator[AssemblyLine] {
def inline(code: List[AssemblyLine], inlinedFunctions: Map[String, List[AssemblyLine]], jobContext: JobContext): List[AssemblyLine] = {
code.flatMap {
case AssemblyLine(Opcode.JSR, AddrMode.Absolute | AddrMode.LongAbsolute, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
case callInstr@AssemblyLine(Opcode.JSR, AddrMode.Absolute | AddrMode.LongAbsolute, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
val labelPrefix = jobContext.nextLabel("ai")
inlinedFunctions(p.toString).map {
var inlinedCode = inlinedFunctions(p.toString)
if (inlinedCode.forall(_.source.isEmpty)) {
inlinedCode = inlinedCode.map(_.copy(source = callInstr.source))
}
inlinedCode.map {
case line@AssemblyLine0(_, _, MemoryAddressConstant(Label(label))) =>
val newLabel = MemoryAddressConstant(Label(labelPrefix + label))
line.copy(parameter = newLabel)
case l => l
}
case AssemblyLine(Opcode.JMP, AddrMode.Absolute, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
case callInstr@AssemblyLine(Opcode.JMP, AddrMode.Absolute, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
val labelPrefix = jobContext.nextLabel("ai")
inlinedFunctions(p.toString).map {
var inlinedCode = inlinedFunctions(p.toString)
if (inlinedCode.forall(_.source.isEmpty)) {
inlinedCode = inlinedCode.map(_.copy(source = callInstr.source))
}
inlinedCode.map {
case line@AssemblyLine0(_, _, MemoryAddressConstant(Label(label))) =>
val newLabel = MemoryAddressConstant(Label(labelPrefix + label))
line.copy(parameter = newLabel)

View File

@ -56,24 +56,34 @@ object Z80InliningCalculator extends AbstractInliningCalculator[ZLine] {
override def inline(code: List[ZLine], inlinedFunctions: Map[String, List[ZLine]], jobContext: JobContext): List[ZLine] = {
code.flatMap {
case ZLine(CALL, registers, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
case callInstr@ZLine(CALL, registers, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
val labelPrefix = jobContext.nextLabel("ai")
wrap(registers, jobContext,
inlinedFunctions(p.toString).map {
wrap(registers, jobContext, {
var inlinedCode = inlinedFunctions(p.toString)
if (inlinedCode.forall(_.source.isEmpty)) {
inlinedCode = inlinedCode.map(_.copy(source = callInstr.source))
}
inlinedCode.map {
case line@ZLine0(_, _, MemoryAddressConstant(Label(label))) =>
val newLabel = MemoryAddressConstant(Label(labelPrefix + label))
line.copy(parameter = newLabel)
case l => l
})
case ZLine(JP | JR, registers, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
}
})
case callInstr@ZLine(JP | JR, registers, p, Elidability.Elidable, _) if inlinedFunctions.contains(p.toString) =>
val labelPrefix = jobContext.nextLabel("ai")
wrap(registers, jobContext,
inlinedFunctions(p.toString).map {
wrap(registers, jobContext, {
var inlinedCode = inlinedFunctions(p.toString)
if (inlinedCode.forall(_.source.isEmpty)) {
inlinedCode = inlinedCode.map(_.copy(source = callInstr.source))
}
inlinedCode.map {
case line@ZLine0(_, _, MemoryAddressConstant(Label(label))) =>
val newLabel = MemoryAddressConstant(Label(labelPrefix + label))
line.copy(parameter = newLabel)
case l => l
} :+ ZLine.implied(RET))
} :+ ZLine.implied(RET)
})
case x => List(x)
}
}