diff --git a/LLVMSharp.slnx b/LLVMSharp.slnx
index 0767f61..d0d5668 100644
--- a/LLVMSharp.slnx
+++ b/LLVMSharp.slnx
@@ -57,6 +57,7 @@
+
diff --git a/samples/KaleidoscopeTutorial/Chapter6/Parser.cs b/samples/KaleidoscopeTutorial/Chapter6/Parser.cs
index 9b1b64a..56cf859 100644
--- a/samples/KaleidoscopeTutorial/Chapter6/Parser.cs
+++ b/samples/KaleidoscopeTutorial/Chapter6/Parser.cs
@@ -39,6 +39,8 @@ public class Parser(Lexer lexer, IDictionary binaryOpPrecedence) : Ch
// ::= 'binary' [precedence] '(' id id ')'
protected override PrototypeAST? ParsePrototype()
{
+ int line = CurrentLocation.Line;
+
string functionName;
int kind; // 0 = identifier, 1 = unary operator, 2 = binary operator.
int binaryPrecedence = 30;
@@ -123,7 +125,7 @@ public class Parser(Lexer lexer, IDictionary binaryOpPrecedence) : Ch
return LogErrorProto("Invalid number of operands for operator");
}
- var prototype = new PrototypeAST(functionName, argNames, IsOperator: kind != 0, binaryPrecedence);
+ var prototype = new PrototypeAST(functionName, argNames, IsOperator: kind != 0, binaryPrecedence) { Line = line };
// Make a user-defined binary operator usable in the input that follows its definition.
if (prototype.IsBinaryOperator)
diff --git a/samples/KaleidoscopeTutorial/Chapter9/Chapter9.csproj b/samples/KaleidoscopeTutorial/Chapter9/Chapter9.csproj
new file mode 100644
index 0000000..ccf466c
--- /dev/null
+++ b/samples/KaleidoscopeTutorial/Chapter9/Chapter9.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+
+
+
+
+
+
diff --git a/samples/KaleidoscopeTutorial/Chapter9/CodeGenVisitor.cs b/samples/KaleidoscopeTutorial/Chapter9/CodeGenVisitor.cs
new file mode 100644
index 0000000..6f59884
--- /dev/null
+++ b/samples/KaleidoscopeTutorial/Chapter9/CodeGenVisitor.cs
@@ -0,0 +1,157 @@
+// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
+
+using Kaleidoscope.AST;
+using LLVMSharp.Interop;
+
+namespace Kaleidoscope.Chapter9;
+
+///
+/// Chapter 9 code generation delta — DWARF debug information. This builds on chapter 7's mutable
+/// variables and adds the metadata a debugger needs: a compile unit and file, a subprogram per
+/// function, a local variable per parameter, and a source location on every expression. The frontend
+/// already stamps each AST node with a ; here we translate those into
+/// !dbg attachments via the .
+///
+public sealed class CodeGenVisitor : Chapter7.CodeGenVisitor
+{
+ // DWARF attribute encoding for a 4-byte-aligned IEEE float (DW_ATE_float). Kaleidoscope's only
+ // type is 'double', so a single basic type covers every value, parameter, and return.
+ private const uint DwarfAteFloat = 0x04;
+
+ private LLVMDIBuilderRef _diBuilder;
+ private LLVMMetadataRef _file;
+ private LLVMMetadataRef _doubleType;
+ private LLVMMetadataRef _currentScope;
+
+ ///
+ /// Supplies the debug-info builder and the file all subprograms are scoped to. The compile unit is
+ /// created and owned by the program; it references this file, so we only need the file here. Must be
+ /// called before the driver generates any code.
+ ///
+ public void InitializeDebugInfo(LLVMDIBuilderRef diBuilder, LLVMMetadataRef file)
+ {
+ _diBuilder = diBuilder;
+ _file = file;
+ }
+
+ // The debug type for 'double', created lazily so it lands after InitializeDebugInfo.
+ private unsafe LLVMMetadataRef DoubleType
+ {
+ get
+ {
+ if (_doubleType.Handle == IntPtr.Zero)
+ {
+ using var name = new MarshaledString("double");
+ _doubleType = LLVM.DIBuilderCreateBasicType(_diBuilder, name, (UIntPtr)name.Length, 64, DwarfAteFloat, LLVMDIFlags.LLVMDIFlagZero);
+ }
+
+ return _doubleType;
+ }
+ }
+
+ ///
+ /// Attaches the current expression's source position to the builder so subsequent instructions are
+ /// tagged with a !dbg location. Untracked nodes (or code emitted outside a function) clear
+ /// the location instead, which is what LLVM expects for prologue and synthetic instructions.
+ ///
+ protected override unsafe void EmitLocation(ExprAST node)
+ {
+ if (_currentScope.Handle == IntPtr.Zero || node.Location.Line == 0)
+ {
+ LLVM.SetCurrentDebugLocation2(Builder, null);
+ return;
+ }
+
+ LLVMMetadataRef location = LLVM.DIBuilderCreateDebugLocation(
+ Module.Context, (uint)node.Location.Line, (uint)node.Location.Column, _currentScope, null);
+ LLVM.SetCurrentDebugLocation2(Builder, location);
+ }
+
+ public override unsafe LLVMValueRef CodegenFunction(FunctionAST node)
+ {
+ // Remember the prototype (so recursive references resolve) then get-or-declare the function.
+ RegisterPrototype(node.Proto);
+ LLVMValueRef function = GetFunction(node.Proto.Name);
+
+ if (function.BasicBlocksCount != 0)
+ {
+ throw new InvalidOperationException($"Function '{node.Proto.Name}' cannot be redefined");
+ }
+
+ LLVMBasicBlockRef entry = function.AppendBasicBlock("entry");
+ Builder.PositionAtEnd(entry);
+
+ // Describe the function to the debugger and make its subprogram the active scope.
+ uint line = (uint)(node.Proto.Line == 0 ? 1 : node.Proto.Line);
+ LLVMMetadataRef subprogram = CreateFunctionScope(node.Proto, line);
+ LLVM.SetSubprogram(function, subprogram);
+ _currentScope = subprogram;
+
+ // The prologue (parameter spills) should not have a source location.
+ LLVM.SetCurrentDebugLocation2(Builder, null);
+ CreateDebugParameterBindings(function, node.Proto, entry, subprogram, line);
+
+ try
+ {
+ LLVMValueRef body = Codegen(node.Body);
+ Builder.BuildRet(body);
+ function.VerifyFunction(LLVMVerifierFailureAction.LLVMPrintMessageAction);
+ return function;
+ }
+ catch
+ {
+ // Remove the half-built function so the driver can keep going after an error.
+ function.DeleteFunction();
+ throw;
+ }
+ finally
+ {
+ _currentScope = default;
+ }
+ }
+
+ // Creates the DISubprogram (and its subroutine type) describing one function definition.
+ private LLVMMetadataRef CreateFunctionScope(PrototypeAST proto, uint line)
+ {
+ LLVMMetadataRef subroutineType = CreateSubroutineType(proto.Arguments.Count);
+
+ return _diBuilder.CreateFunction(
+ _file, proto.Name, LinkageName: "", _file, line, subroutineType,
+ IsLocalToUnit: 1, IsDefinition: 1, ScopeLine: line, LLVMDIFlags.LLVMDIFlagPrototyped, IsOptimized: 0);
+ }
+
+ // Element 0 is the return type; the rest are the parameters. Every value is a 'double'.
+ private LLVMMetadataRef CreateSubroutineType(int argumentCount)
+ {
+ var parameterTypes = new LLVMMetadataRef[argumentCount + 1];
+ Array.Fill(parameterTypes, DoubleType);
+ return _diBuilder.CreateSubroutineType(_file, parameterTypes, LLVMDIFlags.LLVMDIFlagZero);
+ }
+
+ // Chapter 7 already spills every parameter into an alloca; here we additionally describe each one
+ // to the debugger with a DILocalVariable and a dbg.declare record pointing at its slot.
+ private unsafe void CreateDebugParameterBindings(LLVMValueRef function, PrototypeAST proto, LLVMBasicBlockRef entry, LLVMMetadataRef scope, uint line)
+ {
+ NamedValues.Clear();
+
+ LLVMMetadataRef emptyExpression = LLVM.DIBuilderCreateExpression(_diBuilder, null, (UIntPtr)0);
+ LLVMMetadataRef declareLocation = LLVM.DIBuilderCreateDebugLocation(Module.Context, line, 0, scope, null);
+
+ for (int i = 0; i < proto.Arguments.Count; i++)
+ {
+ string name = proto.Arguments[i];
+
+ LLVMValueRef alloca = CreateEntryBlockAlloca(function, name);
+ Builder.BuildStore(function.GetParam((uint)i), alloca);
+
+ using var marshaledName = new MarshaledString(name);
+ LLVMMetadataRef variable = LLVM.DIBuilderCreateParameterVariable(
+ _diBuilder, scope, marshaledName, (UIntPtr)marshaledName.Length, (uint)(i + 1), _file, line,
+ DoubleType, AlwaysPreserve: 1, LLVMDIFlags.LLVMDIFlagZero);
+
+ _ = LLVM.DIBuilderInsertDeclareRecordAtEnd(_diBuilder, alloca, variable, emptyExpression, declareLocation, entry);
+
+ NamedValues[name] = alloca;
+ }
+ }
+}
diff --git a/samples/KaleidoscopeTutorial/Chapter9/Program.cs b/samples/KaleidoscopeTutorial/Chapter9/Program.cs
new file mode 100644
index 0000000..985453a
--- /dev/null
+++ b/samples/KaleidoscopeTutorial/Chapter9/Program.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
+
+using Kaleidoscope;
+using Kaleidoscope.Chapter8;
+using Kaleidoscope.Chapter9;
+using LLVMSharp.Interop;
+
+// Chapter 9 is chapter 8's batch compiler with DWARF debug information layered on top. It reads a whole
+// Kaleidoscope program, generates every definition into one module — now carrying a compile unit, a
+// subprogram per function, local variables for parameters, and a source location on every expression —
+// and emits a native object file that a debugger can step through.
+var binaryOpPrecedence = new Dictionary
+{
+ ['='] = 2,
+ ['<'] = 10,
+ ['+'] = 20,
+ ['-'] = 20,
+ ['*'] = 40,
+};
+
+string sourceName = args.Length > 0 ? Path.GetFileName(args[0]) : "stdin.ks";
+string sourceDirectory = args.Length > 0 ? (Path.GetDirectoryName(Path.GetFullPath(args[0])) ?? ".") : ".";
+string outputPath = args.Length > 1 ? args[1] : "output.o";
+
+using TextReader reader = args.Length > 0 ? new StreamReader(args[0]) : Console.In;
+
+var lexer = new Lexer(reader, binaryOpPrecedence);
+var parser = new Kaleidoscope.Chapter7.Parser(lexer, binaryOpPrecedence);
+var visitor = new CodeGenVisitor();
+
+LlvmSupport.EnsureTargetsInitialized();
+LLVMTargetMachineRef targetMachine = LlvmSupport.CreateHostTargetMachine();
+
+LLVMContextRef context = LLVMContextRef.Create();
+LLVMModuleRef module = context.CreateModuleWithName("KaleidoscopeModule");
+LlvmSupport.PrepareModuleForEmit(module, targetMachine);
+
+// Tell the backend a module-level debug-info version is present. DWARF is the default on the tutorial's
+// targets; on Windows/CodeView these flags are still valid and harmless.
+module.AddModuleFlag("Debug Info Version", LLVMModuleFlagBehavior.LLVMModuleFlagBehaviorWarning, LLVM.DebugMetadataVersion());
+module.AddModuleFlag("Dwarf Version", LLVMModuleFlagBehavior.LLVMModuleFlagBehaviorWarning, 4u);
+
+LLVMDIBuilderRef diBuilder = module.CreateDIBuilder();
+LLVMMetadataRef file = diBuilder.CreateFile(sourceName, sourceDirectory);
+
+// Kaleidoscope has no notion of C, but claiming C lets debuggers apply sensible defaults, matching the
+// upstream tutorial.
+LLVMMetadataRef compileUnit = diBuilder.CreateCompileUnit(
+ LLVMDWARFSourceLanguage.LLVMDWARFSourceLanguageC, file, "Kaleidoscope Compiler",
+ IsOptimized: 0, Flags: "", RuntimeVersion: 0, SplitName: "", LLVMDWARFEmissionKind.LLVMDWARFEmissionFull,
+ DWOld: 0, SplitDebugInlining: 0, DebugInfoForProfiling: 0, SysRoot: "", SDK: "");
+
+visitor.InitializeDebugInfo(diBuilder, file);
+
+var driver = new ObjectFileDriver(lexer, parser, visitor, module);
+driver.Run();
+
+// Resolve all temporary debug metadata now that every function has been generated.
+diBuilder.DIBuilderFinalize();
+
+Console.WriteLine("=== Module IR ===");
+Console.Write(module.PrintToString());
+Console.WriteLine();
+
+if (targetMachine.TryEmitToFile(module, outputPath, LLVMCodeGenFileType.LLVMObjectFile, out string message))
+{
+ Console.WriteLine($"Wrote object file to '{outputPath}' (target {module.Target}).");
+}
+else
+{
+ Console.Error.WriteLine($"Failed to emit object file: {message}");
+ Environment.Exit(1);
+}
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs
index 494142a..f9bada8 100644
--- a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/ExprAST.cs
@@ -3,7 +3,14 @@
namespace Kaleidoscope.AST;
/// Base class for all expression nodes.
-public abstract record ExprAST;
+public abstract record ExprAST
+{
+ ///
+ /// Where this node came from in the source. The parser stamps it (chapter 9); a
+ /// of 0 means untracked, and earlier chapters simply ignore it.
+ ///
+ public SourceLocation Location { get; init; }
+}
/// A numeric literal, e.g. 1.0 (chapter 3).
public sealed record NumberExprAST(double Value) : ExprAST;
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs
index 10b0ff9..60543c8 100644
--- a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/PrototypeAST.cs
@@ -16,6 +16,9 @@ public sealed record PrototypeAST(
public bool IsBinaryOperator => IsOperator && Arguments.Count == 2;
+ /// Source line the definition starts on, stamped by the parser (chapter 9); 0 if untracked.
+ public int Line { get; init; }
+
/// The operator character for an operator prototype (the last char of the name).
public char OperatorName => Name[^1];
}
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/SourceLocation.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/SourceLocation.cs
new file mode 100644
index 0000000..3c6b25e
--- /dev/null
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Ast/SourceLocation.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information.
+
+namespace Kaleidoscope.AST;
+
+///
+/// A 1-based source position (chapter 9). A of 0 means "no location was recorded",
+/// which is how the code generator tells tracked nodes apart from untracked ones.
+///
+public readonly record struct SourceLocation(int Line, int Column);
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs
index 8a07b91..8843896 100644
--- a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/CodeGenVisitorBase.cs
@@ -34,18 +34,32 @@ public void SetModule(LLVMModuleRef module)
}
/// Generates code for an expression, dispatching to the chapter-specific override.
- public LLVMValueRef Codegen(ExprAST node) => node switch
+ public LLVMValueRef Codegen(ExprAST node)
{
- NumberExprAST n => CodegenNumber(n),
- VariableExprAST n => CodegenVariable(n),
- BinaryExprAST n => CodegenBinary(n),
- UnaryExprAST n => CodegenUnary(n),
- CallExprAST n => CodegenCall(n),
- IfExprAST n => CodegenIf(n),
- ForExprAST n => CodegenFor(n),
- VarExprAST n => CodegenVar(n),
- _ => throw Unsupported(node),
- };
+ // Chapter 9 overrides this to attach a debug location before the node's code is emitted.
+ EmitLocation(node);
+
+ return node switch
+ {
+ NumberExprAST n => CodegenNumber(n),
+ VariableExprAST n => CodegenVariable(n),
+ BinaryExprAST n => CodegenBinary(n),
+ UnaryExprAST n => CodegenUnary(n),
+ CallExprAST n => CodegenCall(n),
+ IfExprAST n => CodegenIf(n),
+ ForExprAST n => CodegenFor(n),
+ VarExprAST n => CodegenVar(n),
+ _ => throw Unsupported(node),
+ };
+ }
+
+ ///
+ /// Hook called for every expression just before its code is generated. The debug-info chapter
+ /// overrides it to set the builder's current debug location; other chapters leave it a no-op.
+ ///
+ protected virtual void EmitLocation(ExprAST node)
+ {
+ }
/// Emits a declaration for an extern and remembers it for later modules.
public LLVMValueRef CodegenExtern(PrototypeAST node)
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs
index a39a8ca..7cc4f22 100644
--- a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Lexer.cs
@@ -2,6 +2,7 @@
using System.Globalization;
using System.Text;
+using Kaleidoscope.AST;
namespace Kaleidoscope;
@@ -19,6 +20,8 @@ public sealed class Lexer
private readonly StringBuilder _builder = new();
private int _lastChar = ' ';
+ private int _line = 1;
+ private int _column;
public Lexer(TextReader reader, IReadOnlyDictionary binaryOpPrecedence)
{
@@ -29,6 +32,12 @@ public Lexer(TextReader reader, IReadOnlyDictionary binaryOpPrecedenc
/// The most recently scanned token. Positive values are literal characters.
public int CurrentToken { get; private set; }
+ ///
+ /// Source location of the token in (chapter 9). Only the debug-info
+ /// chapter consults it; every other chapter ignores it.
+ ///
+ public SourceLocation TokenLocation { get; private set; } = new(1, 0);
+
/// The identifier text for the most recent .
public string LastIdentifier { get; private set; } = string.Empty;
@@ -58,9 +67,12 @@ private int ReadToken()
// Skip any whitespace.
while (char.IsWhiteSpace((char)_lastChar))
{
- _lastChar = _reader.Read();
+ _lastChar = Advance();
}
+ // Record where this token begins so chapter 9 can attach debug locations.
+ TokenLocation = new SourceLocation(_line, _column);
+
// identifier: [a-zA-Z][a-zA-Z0-9]*
if (char.IsLetter((char)_lastChar))
{
@@ -68,7 +80,7 @@ private int ReadToken()
do
{
_builder.Append((char)_lastChar);
- _lastChar = _reader.Read();
+ _lastChar = Advance();
}
while (char.IsLetterOrDigit((char)_lastChar));
@@ -97,7 +109,7 @@ private int ReadToken()
do
{
_builder.Append((char)_lastChar);
- _lastChar = _reader.Read();
+ _lastChar = Advance();
}
while (char.IsDigit((char)_lastChar) || _lastChar == '.');
@@ -110,7 +122,7 @@ private int ReadToken()
{
do
{
- _lastChar = _reader.Read();
+ _lastChar = Advance();
}
while (_lastChar != Eof && _lastChar != '\n' && _lastChar != '\r');
@@ -128,7 +140,25 @@ private int ReadToken()
// Otherwise, return the character as its ASCII value.
int thisChar = _lastChar;
- _lastChar = _reader.Read();
+ _lastChar = Advance();
return thisChar;
}
+
+ /// Reads the next character, tracking line and column for .
+ private int Advance()
+ {
+ int next = _reader.Read();
+
+ if (next == '\n')
+ {
+ _line++;
+ _column = 0;
+ }
+ else if (next != Eof)
+ {
+ _column++;
+ }
+
+ return next;
+ }
}
diff --git a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs
index 8630bd8..3ac9a76 100644
--- a/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs
+++ b/samples/KaleidoscopeTutorial/Kaleidoscope.Common/Parser.cs
@@ -19,6 +19,9 @@ public class Parser
protected Lexer Lexer { get; }
+ /// Source location of the token the lexer is currently sitting on (chapter 9).
+ protected SourceLocation CurrentLocation => Lexer.TokenLocation;
+
// definition ::= 'def' prototype expression
public FunctionAST? ParseDefinition()
{
@@ -51,7 +54,7 @@ public class Parser
}
// Make an anonymous prototype so a top-level expression can be JIT-compiled and called.
- var proto = new PrototypeAST(AnonymousExpressionName, Array.Empty());
+ var proto = new PrototypeAST(AnonymousExpressionName, Array.Empty()) { Line = expr.Location.Line };
return new FunctionAST(proto, expr);
}
@@ -70,8 +73,17 @@ public class Parser
// expression ::= unary binoprhs
protected ExprAST? ParseExpression()
{
+ SourceLocation location = CurrentLocation;
+
ExprAST? lhs = ParseUnary();
- return lhs is null ? null : ParseBinOpRHS(0, lhs);
+ if (lhs is null)
+ {
+ return null;
+ }
+
+ // Stamp the outermost node (e.g. a chapter-6 unary) with the start of the expression when a
+ // more specific production hasn't already recorded a location.
+ return StampLocation(ParseBinOpRHS(0, lhs), location);
}
// primary
@@ -81,13 +93,15 @@ public class Parser
// ::=
protected virtual ExprAST? ParsePrimary()
{
+ SourceLocation location = CurrentLocation;
+
switch (Lexer.CurrentToken)
{
case (int)Token.Identifier:
- return ParseIdentifierExpr();
+ return StampLocation(ParseIdentifierExpr(), location);
case (int)Token.Number:
- return ParseNumberExpr();
+ return StampLocation(ParseNumberExpr(), location);
case '(':
return ParseParenExpr();
@@ -95,13 +109,20 @@ public class Parser
default:
if (TryParseKeywordPrimary(Lexer.CurrentToken, out ExprAST? result))
{
- return result;
+ return StampLocation(result, location);
}
return LogError("unknown token when expecting an expression");
}
}
+ ///
+ /// Records on unless it already carries one.
+ /// Chapter 9 uses these locations to emit debug info; earlier chapters ignore them entirely.
+ ///
+ protected static ExprAST? StampLocation(ExprAST? node, SourceLocation location) =>
+ node is null || node.Location.Line != 0 ? node : node with { Location = location };
+
/// Hook for chapters that add primary forms (if/for in ch5, var in ch7).
protected virtual bool TryParseKeywordPrimary(int token, out ExprAST? result)
{
@@ -126,6 +147,7 @@ protected virtual bool TryParseKeywordPrimary(int token, out ExprAST? result)
}
int binaryOp = Lexer.CurrentToken;
+ SourceLocation operatorLocation = CurrentLocation;
Lexer.GetNextToken(); // eat binop.
ExprAST? rhs = ParseUnary();
@@ -145,13 +167,15 @@ protected virtual bool TryParseKeywordPrimary(int token, out ExprAST? result)
}
}
- lhs = new BinaryExprAST((char)binaryOp, lhs, rhs);
+ lhs = new BinaryExprAST((char)binaryOp, lhs, rhs) { Location = operatorLocation };
}
}
// prototype ::= id '(' id* ')'. Chapter 6 overrides this to parse operator prototypes.
protected virtual PrototypeAST? ParsePrototype()
{
+ int line = CurrentLocation.Line;
+
if (Lexer.CurrentToken != (int)Token.Identifier)
{
return LogErrorProto("Expected function name in prototype");
@@ -177,7 +201,7 @@ protected virtual bool TryParseKeywordPrimary(int token, out ExprAST? result)
}
Lexer.GetNextToken(); // eat ')'.
- return new PrototypeAST(fnName, argNames);
+ return new PrototypeAST(fnName, argNames) { Line = line };
}
// numberexpr ::= number
diff --git a/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx b/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx
index a1e16ef..0dcd732 100644
--- a/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx
+++ b/samples/KaleidoscopeTutorial/KaleidoscopeTutorial.slnx
@@ -11,4 +11,5 @@
+
diff --git a/samples/KaleidoscopeTutorial/README.md b/samples/KaleidoscopeTutorial/README.md
index 7100a10..0390fb3 100644
--- a/samples/KaleidoscopeTutorial/README.md
+++ b/samples/KaleidoscopeTutorial/README.md
@@ -21,11 +21,13 @@ Chapter5/ Add control flow: if/then/else and for loops.
Chapter6/ Add user-defined unary and binary operators.
Chapter7/ Add mutable variables (alloca + mem2reg) and assignment.
Chapter8/ Compile a whole program to a native object file.
+Chapter9/ Emit DWARF debug information alongside the object file.
```
Each chapter references the previous one, so `Chapter7` transitively sees `Chapter3`–`Chapter6` and
`Kaleidoscope.Common`. Chapters 4 and 8 are purely a new driver over the previous chapter's frontend;
-Chapters 5–7 add grammar and codegen.
+Chapters 5–7 add grammar and codegen. Chapter 9 is chapter 8's batch compiler with debug info layered
+on, so it references both Chapter 7 (frontend + codegen) and Chapter 8 (the object-file driver).
### Chapter ↔ tutorial mapping
@@ -37,8 +39,12 @@ Chapters 5–7 add grammar and codegen.
| 6 | Ch. 6 | User-defined operators, operator precedence |
| 7 | Ch. 7 | Mutable variables, `var`/`in`, `=` assignment |
| 8 | Ch. 8 | Native object-file emission |
+| 9 | Ch. 9 | DWARF debug information (compile unit, subprograms, locals, locations) |
-The tutorial's Chapter 9 (debug info / DWARF) and Chapter 10 (conclusion) are not ported.
+The tutorial's [Chapter 10](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl10.html) is a
+prose conclusion (properties of SSA, tail calls, garbage collection, exception handling, and other places
+to take a language) rather than a code delta, so there is no `Chapter10` project — see
+[Where to go from here](#where-to-go-from-here) below.
## Running
@@ -78,6 +84,30 @@ echo "def average(x y) (x + y) * 0.5;" | dotnet run --project Chapter8 -c Releas
The resulting object exports `average` with C ABI (`double average(double, double)`), so it can be
linked into a C/C++ program.
+Chapter 9 is the same batch compiler with debug information: the emitted IR carries a `DICompileUnit`,
+one `DISubprogram` per function, a `DILocalVariable` per parameter, and a `!dbg` source location on every
+instruction, so the resulting object can be stepped through in a debugger.
+
+```
+echo "def fib(n) if n < 3 then 1 else fib(n - 1) + fib(n - 2);" | dotnet run --project Chapter9 -c Release
+```
+
+## Where to go from here
+
+The upstream tutorial's Chapter 10 has no accompanying code — it is a conclusion pointing at directions a
+real language would explore next. All of these are expressible with LLVMSharp's interop:
+
+- **Global variables, typed values, and aggregates** — Kaleidoscope only has `double`; a real frontend
+ tracks types and builds structs/arrays with `LLVMTypeRef`/`BuildGEP2`.
+- **Garbage collection** — LLVM's statepoint/`gc` intrinsics and stack maps are exposed through the C API.
+- **Exception handling** — `invoke`/`landingpad` and the `llvm.eh.*` intrinsics.
+- **Debugger integration and optimization** — build on Chapter 9's debug info and tune the pass pipeline
+ in `Kaleidoscope.Common`.
+
+See the upstream
+[Chapter 10 write-up](https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl10.html) for the full
+discussion.
+
## Tests
These samples are part of the root `LLVMSharp.slnx`, so they build and are validated in CI. The
@@ -99,5 +129,8 @@ dotnet test -c Release --no-build --filter "FullyQualifiedName~KaleidoscopeTests
so the tutorial's `printstar`/`printd` examples work.
- **Optimizer.** Chapters 4+ run the new pass-manager pipeline
(`mem2reg,instcombine,reassociate,gvn,simplifycfg`) via `LLVM.RunPasses`.
+- **Debug info.** Chapter 9 threads a `SourceLocation` through the shared lexer/parser/AST and translates
+ it into DWARF via `LLVMDIBuilderRef`. The location plumbing lives in `Kaleidoscope.Common` (an inert
+ `EmitLocation` hook on the base `CodeGenVisitor`), so chapters 3–8 are unaffected.
- The interop under `../../sources/LLVMSharp.Interop/llvm` is auto-generated; these samples only use the
hand-written friendly wrappers and the raw ORC C API.
diff --git a/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs b/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs
index ce76d2f..0d9f316 100644
--- a/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs
+++ b/tests/LLVMSharp.KaleidoscopeTests/ChapterTests.cs
@@ -135,6 +135,34 @@ public void Chapter8_EmitsObjectFile()
}
}
+ [Test]
+ public void Chapter9_EmitsDebugInfo()
+ {
+ var workingDirectory = Directory.CreateTempSubdirectory("kaleidoscope-ch9-").FullName;
+
+ try
+ {
+ var output = RunChapter("Chapter9", "def fib(n) if n < 3 then 1 else fib(n - 1) + fib(n - 2);", workingDirectory);
+ var objectFile = Path.Combine(workingDirectory, "output.o");
+
+ Assert.Multiple(() =>
+ {
+ // The emitted IR carries DWARF metadata: a compile unit, a subprogram, a parameter
+ // variable, and a source location on the function's instructions.
+ Assert.That(output, Does.Contain("!DICompileUnit"));
+ Assert.That(output, Does.Contain("!DISubprogram(name: \"fib\""));
+ Assert.That(output, Does.Contain("!DILocalVariable(name: \"n\""));
+ Assert.That(output, Does.Contain("!dbg"));
+ Assert.That(File.Exists(objectFile), Is.True, "expected output.o to be emitted");
+ Assert.That(new FileInfo(objectFile).Length, Is.GreaterThan(0), "output.o should not be empty");
+ });
+ }
+ finally
+ {
+ Directory.Delete(workingDirectory, recursive: true);
+ }
+ }
+
private static string RunChapter(string chapter, string input, string? workingDirectory = null)
{
var assembly = LocateChapterAssembly(chapter);