feat: implement .loc, .file and .func debug directives

This commit is contained in:
2025-03-14 15:40:09 +00:00
parent 07f02e61d7
commit 8f7c902ed5
5 changed files with 81 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ package wacc
import scala.collection.mutable
import cats.data.Chain
import wacc.ast.Position
private class LabelGenerator {
import assemblyIR._
@@ -9,7 +10,9 @@ private class LabelGenerator {
import asmGenerator.escaped
private val strings = mutable.HashMap[String, String]()
private val files = mutable.HashMap[String, Int]()
private var labelVal = -1
private var permittedFuncFile: Option[String] = None
/** Get an arbitrary label. */
def getLabel(): String = {
@@ -39,6 +42,25 @@ private class LabelGenerator {
def getLabelArg(src: String, name: String): LabelArg =
LabelArg(strings.getOrElseUpdate(src, s".L.$name.str${strings.size}"))
/** Get a debug directive for a file. */
def getDebugFile(file: java.io.File): Int =
files.getOrElseUpdate(file.getCanonicalPath, files.size)
/** Get a debug directive for a function. */
def getDebugFunc(pos: Position, name: String, label: LabelDef): Chain[AsmLine] = {
permittedFuncFile match {
case Some(f) if f != pos.file.getCanonicalPath => Chain.empty
case _ =>
permittedFuncFile = Some(pos.file.getCanonicalPath)
Chain(
LabelDef(name),
Directive.Location(getDebugFile(pos.file), pos.line, None),
Directive.Type(label, SymbolType.Function),
Directive.Func(name, label)
)
}
}
/** Generate the assembly labels for constants that were labelled using the LabelGenerator. */
def generateConstants: Chain[AsmLine] =
strings.foldLeft(Chain.empty) { case (acc, (str, label)) =>
@@ -47,4 +69,10 @@ private class LabelGenerator {
Directive.Asciz(str.escaped)
)
}
/** Generates debug directives that were created using the LabelGenerator. */
def generateDebug: Chain[AsmLine] =
files.foldLeft(Chain.empty) { case (acc, (file, no)) =>
acc :+ Directive.File(no, file)
}
}

View File

@@ -36,11 +36,18 @@ object asmGenerator {
given labelGenerator: LabelGenerator = LabelGenerator()
val Program(funcs, main) = microProg
val progAsm = Chain(LabelDef("main")).concatAll(
val mainLabel = LabelDef("main")
val mainAsm = main.headOption match {
case Some(stmt) =>
labelGenerator.getDebugFunc(stmt.pos, "$main", mainLabel) + mainLabel
case None => Chain.one(mainLabel)
}
val progAsm = mainAsm.concatAll(
funcPrologue(),
main.foldMap(generateStmt(_)),
Chain.one(Xor(RAX, RAX)),
funcEpilogue(),
Chain(Directive.Size(mainLabel, SizeExpr.Relative(mainLabel)), Directive.EndFunc),
generateBuiltInFuncs(),
RuntimeError.all.foldMap(_.generate),
funcs.foldMap(generateUserFunc(_))
@@ -51,6 +58,7 @@ object asmGenerator {
Directive.Global("main"),
Directive.RoData
).concatAll(
labelGenerator.generateDebug,
labelGenerator.generateConstants,
Chain.one(Directive.Text),
progAsm
@@ -75,7 +83,10 @@ object asmGenerator {
// Setup the stack with param 7 and up
func.params.drop(argRegs.size).foreach(stack.reserve(_))
stack.reserve(Size.Q64) // Reserve return pointer slot
var asm = Chain.one[AsmLine](labelGenerator.getLabelDef(func.name))
val funcLabel = labelGenerator.getLabelDef(func.name)
var asm = labelGenerator.getDebugFunc(func.pos, func.name.name, funcLabel)
val debugFunc = asm.size > 0
asm += funcLabel
asm ++= funcPrologue()
// Push the rest of params onto the stack for simplicity
argRegs.zip(func.params).foreach { (reg, param) =>
@@ -83,6 +94,10 @@ object asmGenerator {
}
asm ++= func.body.foldMap(generateStmt(_))
// No need for epilogue here since all user functions must return explicitly
if (debugFunc) {
asm += Directive.Size(funcLabel, SizeExpr.Relative(funcLabel))
asm += Directive.EndFunc
}
asm
}
@@ -159,8 +174,8 @@ object asmGenerator {
stack: Stack,
labelGenerator: LabelGenerator
): Chain[AsmLine] = {
var asm = Chain.empty[AsmLine]
asm += Comment(stmt.toString)
val fileNo = labelGenerator.getDebugFile(stmt.pos.file)
var asm = Chain.one[AsmLine](Directive.Location(fileNo, stmt.pos.line, None))
stmt match {
case Assign(lhs, rhs) =>
lhs match {

View File

@@ -199,10 +199,15 @@ object assemblyIR {
}
enum Directive extends AsmLine {
case IntelSyntax, RoData, Text
case IntelSyntax, RoData, Text, EndFunc
case Global(name: String)
case Int(value: scala.Int)
case Asciz(string: String)
case File(no: scala.Int, file: String)
case Location(fileNo: scala.Int, lineNo: scala.Int, colNo: Option[scala.Int])
case Func(name: String, label: LabelDef)
case Type(label: LabelDef, symbolType: SymbolType)
case Size(label: LabelDef, expr: SizeExpr)
override def toString(): String = this match {
case IntelSyntax => ".intel_syntax noprefix"
@@ -211,6 +216,32 @@ object assemblyIR {
case RoData => ".section .rodata"
case Int(value) => s"\t.int $value"
case Asciz(string) => s"\t.asciz \"$string\""
case File(no, file) => s".file $no \"${file}\""
case Location(fileNo, lineNo, colNo) =>
s"\t.loc $fileNo $lineNo" + colNo.map(c => s" $c").getOrElse("")
case Func(name, label) =>
s".func $name, ${label.name}"
case EndFunc => ".endfunc"
case Type(label, symbolType) =>
s".type ${label.name}, @${symbolType.toString}"
case Directive.Size(label, expr) =>
s".size ${label.name}, ${expr.toString}"
}
}
enum SymbolType {
case Function
override def toString(): String = this match {
case Function => "function"
}
}
enum SizeExpr {
case Relative(label: LabelDef)
override def toString(): String = this match {
case Relative(label) => s".-${label.name}"
}
}

View File

@@ -92,6 +92,6 @@ object microWacc {
case class Return(expr: Expr)(val pos: Position) extends Stmt
// Program
case class FuncDecl(name: Ident, params: List[Ident], body: Chain[Stmt])
case class FuncDecl(name: Ident, params: List[Ident], body: Chain[Stmt])(val pos: Position)
case class Program(funcs: Chain[FuncDecl], stmts: Chain[Stmt])
}

View File

@@ -95,7 +95,7 @@ object typeChecker {
microWacc.Ident(ident.v, ident.guid)(ty)
},
body
),
)(func.pos),
bodyErrors
)
}