diff --git a/src/main/wacc/backend/LabelGenerator.scala b/src/main/wacc/backend/LabelGenerator.scala index fd0006f..8eb9a22 100644 --- a/src/main/wacc/backend/LabelGenerator.scala +++ b/src/main/wacc/backend/LabelGenerator.scala @@ -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 _ => + val customLabel = if name == "main" then Chain.empty else Chain(LabelDef(name)) + permittedFuncFile = Some(pos.file.getCanonicalPath) + customLabel ++ Chain( + 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) + } } diff --git a/src/main/wacc/backend/asmGenerator.scala b/src/main/wacc/backend/asmGenerator.scala index efcce9a..4b18a6c 100644 --- a/src/main/wacc/backend/asmGenerator.scala +++ b/src/main/wacc/backend/asmGenerator.scala @@ -36,11 +36,14 @@ object asmGenerator { given labelGenerator: LabelGenerator = LabelGenerator() val Program(funcs, main) = microProg - val progAsm = Chain(LabelDef("main")).concatAll( + val mainLabel = LabelDef("main") + val mainAsm = labelGenerator.getDebugFunc(microProg.pos, "main", mainLabel) + 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 +54,7 @@ object asmGenerator { Directive.Global("main"), Directive.RoData ).concatAll( + labelGenerator.generateDebug, labelGenerator.generateConstants, Chain.one(Directive.Text), progAsm @@ -75,7 +79,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 +90,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 +170,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 { @@ -261,7 +272,7 @@ object asmGenerator { asm += stack.push(KnownType.String.size, RAX) case ty => asm ++= generateCall( - microWacc.Call(Builtin.Malloc, List(IntLiter(array.heapSize))), + microWacc.Call(Builtin.Malloc, List(IntLiter(array.heapSize)))(array.pos), isTail = false ) asm += stack.push(KnownType.Array(?).size, RAX) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index d265358..0d20356 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -199,18 +199,49 @@ 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" - case Global(name) => s".globl $name" - case Text => ".text" - case RoData => ".section .rodata" - case Int(value) => s"\t.int $value" - case Asciz(string) => s"\t.asciz \"$string\"" + case IntelSyntax => ".intel_syntax noprefix" + case Global(name) => s".globl $name" + case Text => ".text" + 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}" } } diff --git a/src/main/wacc/frontend/ast.scala b/src/main/wacc/frontend/ast.scala index e39f931..e050ada 100644 --- a/src/main/wacc/frontend/ast.scala +++ b/src/main/wacc/frontend/ast.scala @@ -223,7 +223,9 @@ object ast { val pos: Position } - sealed trait RValue + sealed trait RValue { + val pos: Position + } case class ArrayLiter(elems: List[Expr])(val pos: Position) extends RValue object ArrayLiter extends ParserBridgePos1[List[Expr], ArrayLiter] case class NewPair(fst: Expr, snd: Expr)(val pos: Position) extends RValue diff --git a/src/main/wacc/frontend/microWacc.scala b/src/main/wacc/frontend/microWacc.scala index 36c5d16..5a60ed7 100644 --- a/src/main/wacc/frontend/microWacc.scala +++ b/src/main/wacc/frontend/microWacc.scala @@ -3,6 +3,7 @@ package wacc import cats.data.Chain object microWacc { + import wacc.ast.Position import wacc.types._ sealed trait CallTarget(val retTy: SemType) @@ -13,7 +14,7 @@ object microWacc { case class IntLiter(v: Int) extends Expr(KnownType.Int) case class BoolLiter(v: Boolean) extends Expr(KnownType.Bool) case class CharLiter(v: Char) extends Expr(KnownType.Char) - case class ArrayLiter(elems: List[Expr])(ty: SemType) extends Expr(ty) + case class ArrayLiter(elems: List[Expr])(ty: SemType, val pos: Position) extends Expr(ty) case class NullLiter()(ty: SemType) extends Expr(ty) case class Ident(name: String, uid: Int)(identTy: SemType) extends Expr(identTy) @@ -65,7 +66,9 @@ object microWacc { } // Statements - sealed trait Stmt + sealed trait Stmt { + val pos: Position + } case class Builtin(val name: String)(retTy: SemType) extends CallTarget(retTy) { override def toString(): String = name @@ -79,13 +82,16 @@ object microWacc { object PrintCharArray extends Builtin("printCharArray")(?) } - case class Assign(lhs: LValue, rhs: Expr) extends Stmt - case class If(cond: Expr, thenBranch: Chain[Stmt], elseBranch: Chain[Stmt]) extends Stmt - case class While(cond: Expr, body: Chain[Stmt]) extends Stmt - case class Call(target: CallTarget, args: List[Expr]) extends Stmt with Expr(target.retTy) - case class Return(expr: Expr) extends Stmt + case class Assign(lhs: LValue, rhs: Expr)(val pos: Position) extends Stmt + case class If(cond: Expr, thenBranch: Chain[Stmt], elseBranch: Chain[Stmt])(val pos: Position) + extends Stmt + case class While(cond: Expr, body: Chain[Stmt])(val pos: Position) extends Stmt + case class Call(target: CallTarget, args: List[Expr])(val pos: Position) + extends Stmt + with Expr(target.retTy) + case class Return(expr: Expr)(val pos: Position) extends Stmt // Program - case class FuncDecl(name: Ident, params: List[Ident], body: Chain[Stmt]) - case class Program(funcs: Chain[FuncDecl], stmts: 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])(val pos: Position) } diff --git a/src/main/wacc/frontend/renamer.scala b/src/main/wacc/frontend/renamer.scala index b16df04..a15a31f 100644 --- a/src/main/wacc/frontend/renamer.scala +++ b/src/main/wacc/frontend/renamer.scala @@ -200,7 +200,7 @@ object renamer { (chunks :+ func, errors ++ scope.add(name, public = true)) } // ...and main body. - val mainBodyIdent = Ident(MAIN, ty = FuncType(?, Nil))(prog.pos) + val mainBodyIdent = Ident(MAIN, ty = FuncType(?, Nil))(main.head.pos) val mainBodyErrors = scope.add(mainBodyIdent, public = false) val mainBodyChunk = FuncDecl(IntType()(prog.pos), mainBodyIdent, Nil, main)(prog.pos) diff --git a/src/main/wacc/frontend/semantics.scala b/src/main/wacc/frontend/semantics.scala index dcc5b94..9578e8a 100644 --- a/src/main/wacc/frontend/semantics.scala +++ b/src/main/wacc/frontend/semantics.scala @@ -36,7 +36,7 @@ object semantics { case Some((head, tail)) => (head.body, tail) case None => (Chain.empty, Chain.empty) } - } yield (microWacc.Program(funcs, typedMain), globalErrors ++ errors) + } yield (microWacc.Program(funcs, typedMain)(main.pos), globalErrors ++ errors) } } diff --git a/src/main/wacc/frontend/typeChecker.scala b/src/main/wacc/frontend/typeChecker.scala index 60fd924..4957718 100644 --- a/src/main/wacc/frontend/typeChecker.scala +++ b/src/main/wacc/frontend/typeChecker.scala @@ -95,7 +95,7 @@ object typeChecker { microWacc.Ident(ident.v, ident.guid)(ty) }, body - ), + )(func.pos), bodyErrors ) } @@ -126,7 +126,7 @@ object typeChecker { microWacc.Assign( microWacc.Ident(name.v, name.guid)(expectedTy.asInstanceOf[SemType]), typedValue - ) + )(stmt.pos) ), valueErrors ) @@ -141,7 +141,10 @@ object typeChecker { ) case _ => Chain.empty } - (Chain.one(microWacc.Assign(lhsTyped, rhsTyped)), lhsErrors ++ rhsErrors ++ unknownError) + ( + Chain.one(microWacc.Assign(lhsTyped, rhsTyped)(stmt.pos)), + lhsErrors ++ rhsErrors ++ unknownError + ) case ast.Read(dest) => val (destTyped, destErrors) = checkLValue(dest, Constraint.Unconstrained) val (destTy, destTyErrors) = destTyped.ty match { @@ -170,13 +173,13 @@ object typeChecker { microWacc.Builtin.Read, List( destTy match { - case KnownType.Int => " %d".toMicroWaccCharArray - case KnownType.Char | _ => " %c".toMicroWaccCharArray + case KnownType.Int => " %d".toMicroWaccCharArray(stmt.pos) + case KnownType.Char | _ => " %c".toMicroWaccCharArray(stmt.pos) }, destTyped ) - ) - ) + )(dest.pos) + )(stmt.pos) ), destErrors ++ destTyErrors ) @@ -189,14 +192,14 @@ object typeChecker { "free must be applied to an array or pair" ) ) - (Chain.one(microWacc.Call(microWacc.Builtin.Free, List(lhsTyped))), lhsErrors) + (Chain.one(microWacc.Call(microWacc.Builtin.Free, List(lhsTyped))(stmt.pos)), lhsErrors) case ast.Return(expr) => val (exprTyped, exprErrors) = checkValue(expr, returnConstraint) - (Chain.one(microWacc.Return(exprTyped)), exprErrors) + (Chain.one(microWacc.Return(exprTyped)(stmt.pos)), exprErrors) case ast.Exit(expr) => val (exprTyped, exprErrors) = checkValue(expr, Constraint.Is(KnownType.Int, "exit value must be int")) - (Chain.one(microWacc.Call(microWacc.Builtin.Exit, List(exprTyped))), exprErrors) + (Chain.one(microWacc.Call(microWacc.Builtin.Exit, List(exprTyped))(stmt.pos)), exprErrors) case ast.Print(expr, newline) => // This constraint should never fail, the scope-checker should have caught it already val (exprTyped, exprErrors) = checkValue(expr, Constraint.Unconstrained) @@ -212,10 +215,10 @@ object typeChecker { microWacc.Call( func, List( - s"$exprFormat${if newline then "\n" else ""}".toMicroWaccCharArray, + s"$exprFormat${if newline then "\n" else ""}".toMicroWaccCharArray(stmt.pos), value ) - ) + )(stmt.pos) ) } ( @@ -224,9 +227,9 @@ object typeChecker { Chain.one( microWacc.If( exprTyped, - printfCall(microWacc.Builtin.Printf, "true".toMicroWaccCharArray), - printfCall(microWacc.Builtin.Printf, "false".toMicroWaccCharArray) - ) + printfCall(microWacc.Builtin.Printf, "true".toMicroWaccCharArray(stmt.pos)), + printfCall(microWacc.Builtin.Printf, "false".toMicroWaccCharArray(stmt.pos)) + )(stmt.pos) ) case KnownType.Array(KnownType.Char) => printfCall(microWacc.Builtin.PrintCharArray, exprTyped) @@ -240,14 +243,14 @@ object typeChecker { val (thenStmtTyped, thenErrors) = thenStmt.foldMap(checkStmt(_, returnConstraint)) val (elseStmtTyped, elseErrors) = elseStmt.foldMap(checkStmt(_, returnConstraint)) ( - Chain.one(microWacc.If(condTyped, thenStmtTyped, elseStmtTyped)), + Chain.one(microWacc.If(condTyped, thenStmtTyped, elseStmtTyped)(cond.pos)), condErrors ++ thenErrors ++ elseErrors ) case ast.While(cond, body) => val (condTyped, condErrors) = checkValue(cond, Constraint.Is(KnownType.Bool, "while condition must be a bool")) val (bodyTyped, bodyErrors) = body.foldMap(checkStmt(_, returnConstraint)) - (Chain.one(microWacc.While(condTyped, bodyTyped)), condErrors ++ bodyErrors) + (Chain.one(microWacc.While(condTyped, bodyTyped)(cond.pos)), condErrors ++ bodyErrors) case ast.Block(body) => body.foldMap(checkStmt(_, returnConstraint)) case skip @ ast.Skip() => (Chain.empty, Chain.empty) } @@ -277,7 +280,7 @@ object typeChecker { (microWacc.CharLiter(v), errors) case l @ ast.StrLiter(v) => val (_, errors) = KnownType.String.satisfies(constraint, l.pos) - (v.toMicroWaccCharArray, errors) + (v.toMicroWaccCharArray(l.pos), errors) case l @ ast.PairLiter() => val (ty, errors) = KnownType.Pair(?, ?).satisfies(constraint, l.pos) (microWacc.NullLiter()(ty), errors) @@ -296,13 +299,16 @@ object typeChecker { // Start with an unknown param type, make it more specific while checking the elements. .Array(elemTy) .satisfies(constraint, l.pos) - (microWacc.ArrayLiter(elemsTyped)(arrayTy), elemsErrors ++ arrayErrors) + (microWacc.ArrayLiter(elemsTyped)(arrayTy, l.pos), elemsErrors ++ arrayErrors) case l @ ast.NewPair(fst, snd) => val (fstTyped, fstErrors) = checkValue(fst, Constraint.Unconstrained) val (sndTyped, sndErrors) = checkValue(snd, Constraint.Unconstrained) val (pairTy, pairErrors) = KnownType.Pair(fstTyped.ty, sndTyped.ty).satisfies(constraint, l.pos) - (microWacc.ArrayLiter(List(fstTyped, sndTyped))(pairTy), fstErrors ++ sndErrors ++ pairErrors) + ( + microWacc.ArrayLiter(List(fstTyped, sndTyped))(pairTy, l.pos), + fstErrors ++ sndErrors ++ pairErrors + ) case ast.Call(id, args) => val funcTy @ FuncType(retTy, paramTys) = id.ty.asInstanceOf[FuncType] val lenError = @@ -318,7 +324,7 @@ object typeChecker { } val (retTyChecked, retErrors) = retTy.satisfies(constraint, id.pos) ( - microWacc.Call(microWacc.Ident(id.v, id.guid)(retTyChecked), argsTyped), + microWacc.Call(microWacc.Ident(id.v, id.guid)(retTyChecked), argsTyped)(id.pos), lenError ++ argsErrors ++ retErrors ) @@ -480,7 +486,7 @@ object typeChecker { } extension (s: String) { - def toMicroWaccCharArray: microWacc.ArrayLiter = - microWacc.ArrayLiter(s.map(microWacc.CharLiter(_)).toList)(KnownType.String) + def toMicroWaccCharArray(pos: ast.Position): microWacc.ArrayLiter = + microWacc.ArrayLiter(s.map(microWacc.CharLiter(_)).toList)(KnownType.String, pos) } }