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);