Skip to content
This repository was archived by the owner on Jan 14, 2021. It is now read-only.

Commit 2d4981a

Browse files
authored
Add wasm debug info (dotnet#6186)
* Add source line debug metadata. Works with javascript output. * Add names of arguments and locals when they're stored on the stack (and thus easily visible in the debugger) * Add debugging information to documentation
1 parent 6fdbbba commit 2d4981a

9 files changed

Lines changed: 248 additions & 11 deletions

‎Documentation/how-to-build-WebAssembly.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This is Windows only for now.
3737

3838
# Useful tips #
3939
* To manually make ILC compile to WebAssembly, add ```--wasm``` to the command line.
40+
* To debug C# source, add ```-g4``` to the emcc command line and change ```-s WASM=1``` to ```-s WASM=0```. This will generate a JavaScript source map that browser debuggers and Visual Studio Code can work with. Using Visual Studio Code's Chrome debugger works particularly well.
4041
* Add ```-g3``` to the emcc command line to generate more debuggable output and a .wast file with the text form of the WebAssembly.
41-
* Omit ```-s WASM=1``` from the emcc command line to generate asm.js. Browser debuggers currently work better with asm.js and it's often a bit more readable than wast.
42+
* Change ```-s WASM=1``` to ```-s WASM=0``` in the emcc command line to generate asm.js. Browser debuggers currently work better with asm.js and it's often a bit more readable than wast.
4243
* Add ```-O2 --llvm-lto 2``` to the emcc command line to enable optimizations. This makes the generated WebAssembly as much as 75% smaller as well as more efficient.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Text;
8+
using LLVMSharp;
9+
10+
namespace ILCompiler.WebAssembly
11+
{
12+
class DebugMetadata
13+
{
14+
public DebugMetadata(LLVMMetadataRef file, LLVMMetadataRef compileUnit)
15+
{
16+
File = file;
17+
CompileUnit = compileUnit;
18+
}
19+
20+
public LLVMMetadataRef CompileUnit { get; }
21+
public LLVMMetadataRef File { get; }
22+
}
23+
}

‎src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs‎

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Diagnostics;
8+
using System.IO;
9+
using System.Linq;
810

911
using Internal.TypeSystem;
1012
using ILCompiler;
1113
using LLVMSharp;
1214
using ILCompiler.CodeGen;
1315
using ILCompiler.DependencyAnalysis;
1416
using ILCompiler.DependencyAnalysisFramework;
17+
using ILCompiler.WebAssembly;
1518
using Internal.TypeSystem.Ecma;
16-
using System.Linq;
1719

1820
namespace Internal.IL
1921
{
@@ -35,6 +37,7 @@ public IEnumerable<object> GetDependencies()
3537
}
3638

3739
public LLVMModuleRef Module { get; }
40+
public LLVMContextRef Context { get; }
3841
private readonly MethodDesc _method;
3942
private readonly MethodIL _methodIL;
4043
private readonly MethodSignature _signature;
@@ -49,6 +52,8 @@ public IEnumerable<object> GetDependencies()
4952
private List<SpilledExpressionEntry> _spilledExpressions = new List<SpilledExpressionEntry>();
5053
private int _pointerSize;
5154
private readonly byte[] _ilBytes;
55+
private MethodDebugInformation _debugInformation;
56+
private LLVMMetadataRef _debugFunction;
5257

5358
/// <summary>
5459
/// Stack of values pushed onto the IL stack: locals, arguments, values, function pointer, ...
@@ -106,6 +111,10 @@ public ILImporter(WebAssemblyCodegenCompilation compilation, MethodDesc method,
106111
_llvmFunction = GetOrCreateLLVMFunction(mangledName, method.Signature);
107112
_builder = LLVM.CreateBuilder();
108113
_pointerSize = compilation.NodeFactory.Target.PointerSize;
114+
115+
_debugInformation = _compilation.GetDebugInfo(_methodIL);
116+
117+
Context = LLVM.GetModuleContext(Module);
109118
}
110119

111120
public void Import()
@@ -174,22 +183,57 @@ private void GenerateProlog()
174183
signatureIndex++;
175184
}
176185

186+
string[] argNames = null;
187+
if (_debugInformation != null)
188+
{
189+
argNames = _debugInformation.GetParameterNames()?.ToArray();
190+
}
191+
177192
for (int i = 0; i < _signature.Length; i++)
178193
{
179194
if (CanStoreTypeOnStack(_signature[i]))
180195
{
181-
LLVMValueRef argStackSlot = LLVM.BuildAlloca(_builder, GetLLVMTypeForTypeDesc(_signature[i]), $"arg{i + thisOffset}_");
196+
string argName = String.Empty;
197+
if (argNames != null && argNames[i] != null)
198+
{
199+
argName = argNames[i] + "_";
200+
}
201+
argName += $"arg{i + thisOffset}_";
202+
203+
LLVMValueRef argStackSlot = LLVM.BuildAlloca(_builder, GetLLVMTypeForTypeDesc(_signature[i]), argName);
182204
LLVM.BuildStore(_builder, LLVM.GetParam(_llvmFunction, (uint)signatureIndex), argStackSlot);
183205
_argSlots[i] = argStackSlot;
184206
signatureIndex++;
185207
}
186208
}
187209

210+
string[] localNames = new string[_locals.Length];
211+
if (_debugInformation != null)
212+
{
213+
foreach (ILLocalVariable localDebugInfo in _debugInformation.GetLocalVariables() ?? Enumerable.Empty<ILLocalVariable>())
214+
{
215+
// Check whether the slot still exists as the compiler may remove it for intrinsics
216+
int slot = localDebugInfo.Slot;
217+
if (slot < localNames.Length)
218+
{
219+
localNames[localDebugInfo.Slot] = localDebugInfo.Name;
220+
}
221+
}
222+
}
223+
188224
for (int i = 0; i < _locals.Length; i++)
189225
{
190226
if (CanStoreLocalOnStack(_locals[i].Type))
191227
{
192-
LLVMValueRef localStackSlot = LLVM.BuildAlloca(_builder, GetLLVMTypeForTypeDesc(_locals[i].Type), $"local{i}_");
228+
string localName = String.Empty;
229+
if (localNames[i] != null)
230+
{
231+
localName = localNames[i] + "_";
232+
}
233+
234+
localName += $"local{i}_";
235+
236+
LLVMValueRef localStackSlot = LLVM.BuildAlloca(_builder, GetLLVMTypeForTypeDesc(_locals[i].Type), localName);
193237
_localSlots[i] = localStackSlot;
194238
}
195239
}
@@ -398,10 +442,69 @@ private void EndImportingBasicBlock(BasicBlock basicBlock)
398442

399443
private void StartImportingInstruction()
400444
{
445+
if (_debugInformation != null)
446+
{
447+
bool foundSequencePoint = false;
448+
ILSequencePoint curSequencePoint = default;
449+
foreach (var sequencePoint in _debugInformation.GetSequencePoints() ?? Enumerable.Empty<ILSequencePoint>())
450+
{
451+
if (sequencePoint.Offset == _currentOffset)
452+
{
453+
curSequencePoint = sequencePoint;
454+
foundSequencePoint = true;
455+
break;
456+
}
457+
else if (sequencePoint.Offset < _currentOffset)
458+
{
459+
curSequencePoint = sequencePoint;
460+
foundSequencePoint = true;
461+
}
462+
}
463+
464+
if (!foundSequencePoint)
465+
{
466+
return;
467+
}
468+
469+
// LLVM can't process empty string file names
470+
if (String.IsNullOrWhiteSpace(curSequencePoint.Document))
471+
{
472+
return;
473+
}
474+
475+
DebugMetadata debugMetadata;
476+
if (!_compilation.DebugMetadataMap.TryGetValue(curSequencePoint.Document, out debugMetadata))
477+
{
478+
string fullPath = curSequencePoint.Document;
479+
string fileName = Path.GetFileName(fullPath);
480+
string directory = Path.GetDirectoryName(fullPath) ?? String.Empty;
481+
LLVMMetadataRef fileMetadata = LLVMPInvokes.LLVMDIBuilderCreateFile(_compilation.DIBuilder, fullPath, fullPath.Length,
482+
directory, directory.Length);
483+
484+
// todo: get the right value for isOptimized
485+
LLVMMetadataRef compileUnitMetadata = LLVMPInvokes.LLVMDIBuilderCreateCompileUnit(_compilation.DIBuilder, LLVMDWARFSourceLanguage.LLVMDWARFSourceLanguageC,
486+
fileMetadata, "ILC", 3, isOptimized: false, String.Empty, 0, 1, String.Empty, 0, LLVMDWARFEmissionKind.LLVMDWARFEmissionFull, 0, false, false);
487+
LLVM.AddNamedMetadataOperand(Module, "llvm.dbg.cu", LLVM.MetadataAsValue(Context, compileUnitMetadata));
488+
489+
debugMetadata = new DebugMetadata(fileMetadata, compileUnitMetadata);
490+
_compilation.DebugMetadataMap[fullPath] = debugMetadata;
491+
}
492+
493+
if (_debugFunction.Pointer == IntPtr.Zero)
494+
{
495+
_debugFunction = LLVM.DIBuilderCreateFunction(_compilation.DIBuilder, debugMetadata.CompileUnit, _method.Name, String.Empty, debugMetadata.File,
496+
(uint)_debugInformation.GetSequencePoints().FirstOrDefault().LineNumber, default(LLVMMetadataRef), 1, 1, 1, 0, IsOptimized: 0, _llvmFunction);
497+
}
498+
499+
LLVMMetadataRef currentLine = LLVMPInvokes.LLVMDIBuilderCreateDebugLocation(Context, (uint)curSequencePoint.LineNumber, 0, _debugFunction, default(LLVMMetadataRef));
500+
LLVM.SetCurrentDebugLocation(_builder, LLVM.MetadataAsValue(Context, currentLine));
501+
}
401502
}
402503

403504
private void EndImportingInstruction()
404505
{
506+
// Reset the debug position so it doesn't end up applying to the wrong instructions
507+
LLVM.SetCurrentDebugLocation(_builder, default(LLVMValueRef));
405508
}
406509

407510
private void ImportNop()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Runtime.InteropServices;
6+
using LLVMSharp;
7+
8+
namespace ILCompiler.WebAssembly
9+
{
10+
// LLVM P/Invokes copied from LLVMSharp that match the current LLVM surface area.
11+
// If we get a new version of LLVMSharp containing these, this file should be removed.
12+
internal class LLVMPInvokes
13+
{
14+
const string libraryPath = "libLLVM";
15+
[DllImport(libraryPath, CallingConvention = CallingConvention.Cdecl)]
16+
public static extern LLVMDIBuilderRef LLVMCreateDIBuilder(LLVMModuleRef M);
17+
18+
[DllImport(libraryPath, EntryPoint = "LLVMDIBuilderCreateCompileUnit", CallingConvention = CallingConvention.Cdecl)]
19+
public static extern LLVMMetadataRef LLVMDIBuilderCreateCompileUnit(LLVMDIBuilderRef @Builder, LLVMDWARFSourceLanguage @Lang, LLVMMetadataRef @FileRef, [MarshalAs(UnmanagedType.LPStr)] string @Producer, size_t @ProducerLen, LLVMBool @isOptimized, [MarshalAs(UnmanagedType.LPStr)] string @Flags, size_t @FlagsLen, uint @RuntimeVer, [MarshalAs(UnmanagedType.LPStr)] string @SplitName, size_t @SplitNameLen, LLVMDWARFEmissionKind @Kind, uint @DWOId, LLVMBool @SplitDebugInlining, LLVMBool @DebugInfoForProfiling);
20+
21+
[DllImport(libraryPath, EntryPoint = "LLVMDIBuilderCreateFile", CallingConvention = CallingConvention.Cdecl)]
22+
public static extern LLVMMetadataRef LLVMDIBuilderCreateFile(LLVMDIBuilderRef @Builder, [MarshalAs(UnmanagedType.LPStr)] string @Filename, size_t @FilenameLen, [MarshalAs(UnmanagedType.LPStr)] string @Directory, size_t @DirectoryLen);
23+
24+
[DllImport(libraryPath, EntryPoint = "LLVMDIBuilderCreateDebugLocation", CallingConvention = CallingConvention.Cdecl)]
25+
public static extern LLVMMetadataRef LLVMDIBuilderCreateDebugLocation(LLVMContextRef @Ctx, uint @Line, uint @Column, LLVMMetadataRef @Scope, LLVMMetadataRef @InlinedAt);
26+
}
27+
28+
internal enum LLVMDWARFSourceLanguage : int
29+
{
30+
@LLVMDWARFSourceLanguageC89 = 0,
31+
@LLVMDWARFSourceLanguageC = 1,
32+
@LLVMDWARFSourceLanguageAda83 = 2,
33+
@LLVMDWARFSourceLanguageC_plus_plus = 3,
34+
@LLVMDWARFSourceLanguageCobol74 = 4,
35+
@LLVMDWARFSourceLanguageCobol85 = 5,
36+
@LLVMDWARFSourceLanguageFortran77 = 6,
37+
@LLVMDWARFSourceLanguageFortran90 = 7,
38+
@LLVMDWARFSourceLanguagePascal83 = 8,
39+
@LLVMDWARFSourceLanguageModula2 = 9,
40+
@LLVMDWARFSourceLanguageJava = 10,
41+
@LLVMDWARFSourceLanguageC99 = 11,
42+
@LLVMDWARFSourceLanguageAda95 = 12,
43+
@LLVMDWARFSourceLanguageFortran95 = 13,
44+
@LLVMDWARFSourceLanguagePLI = 14,
45+
@LLVMDWARFSourceLanguageObjC = 15,
46+
@LLVMDWARFSourceLanguageObjC_plus_plus = 16,
47+
@LLVMDWARFSourceLanguageUPC = 17,
48+
@LLVMDWARFSourceLanguageD = 18,
49+
@LLVMDWARFSourceLanguagePython = 19,
50+
@LLVMDWARFSourceLanguageOpenCL = 20,
51+
@LLVMDWARFSourceLanguageGo = 21,
52+
@LLVMDWARFSourceLanguageModula3 = 22,
53+
@LLVMDWARFSourceLanguageHaskell = 23,
54+
@LLVMDWARFSourceLanguageC_plus_plus_03 = 24,
55+
@LLVMDWARFSourceLanguageC_plus_plus_11 = 25,
56+
@LLVMDWARFSourceLanguageOCaml = 26,
57+
@LLVMDWARFSourceLanguageRust = 27,
58+
@LLVMDWARFSourceLanguageC11 = 28,
59+
@LLVMDWARFSourceLanguageSwift = 29,
60+
@LLVMDWARFSourceLanguageJulia = 30,
61+
@LLVMDWARFSourceLanguageDylan = 31,
62+
@LLVMDWARFSourceLanguageC_plus_plus_14 = 32,
63+
@LLVMDWARFSourceLanguageFortran03 = 33,
64+
@LLVMDWARFSourceLanguageFortran08 = 34,
65+
@LLVMDWARFSourceLanguageRenderScript = 35,
66+
@LLVMDWARFSourceLanguageBLISS = 36,
67+
@LLVMDWARFSourceLanguageMips_Assembler = 37,
68+
@LLVMDWARFSourceLanguageGOOGLE_RenderScript = 38,
69+
@LLVMDWARFSourceLanguageBORLAND_Delphi = 39,
70+
}
71+
72+
internal enum LLVMDWARFEmissionKind : int
73+
{
74+
@LLVMDWARFEmissionNone = 0,
75+
@LLVMDWARFEmissionFull = 1,
76+
@LLVMDWARFEmissionLineTablesOnly = 2,
77+
}
78+
}

‎src/ILCompiler.WebAssembly/src/CodeGen/WebAssemblyObjectWriter.cs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ private static int GetNumericOffsetFromBaseSymbolValue(ISymbolNode symbol)
144144
// this is the llvm instance.
145145
public LLVMModuleRef Module { get; }
146146

147+
public LLVMDIBuilderRef DIBuilder { get; }
148+
147149
// This is used to build mangled names
148150
private Utf8StringBuilder _sb = new Utf8StringBuilder();
149151

@@ -181,6 +183,9 @@ public void FinishObjWriter()
181183
}
182184

183185
EmitNativeMain();
186+
187+
EmitDebugMetadata();
188+
184189
LLVM.WriteBitcodeToFile(Module, _objectFilePath);
185190
#if DEBUG
186191
LLVM.PrintModuleToFile(Module, Path.ChangeExtension(_objectFilePath, ".txt"), out string unused2);
@@ -190,6 +195,25 @@ public void FinishObjWriter()
190195
//throw new NotImplementedException(); // This function isn't complete
191196
}
192197

198+
private void EmitDebugMetadata()
199+
{
200+
var dwarfVersion = LLVM.MDNode(new[]
201+
{
202+
LLVM.ConstInt(LLVM.Int32Type(), 2, false),
203+
LLVM.MDString("Dwarf Version", 13),
204+
LLVM.ConstInt(LLVM.Int32Type(), 4, false)
205+
});
206+
var dwarfSchemaVersion = LLVM.MDNode(new[]
207+
{
208+
LLVM.ConstInt(LLVM.Int32Type(), 2, false),
209+
LLVM.MDString("Debug Info Version", 18),
210+
LLVM.ConstInt(LLVM.Int32Type(), 3, false)
211+
});
212+
LLVM.AddNamedMetadataOperand(Module, "llvm.module.flags", dwarfVersion);
213+
LLVM.AddNamedMetadataOperand(Module, "llvm.module.flags", dwarfSchemaVersion);
214+
LLVM.DIBuilderFinalize(DIBuilder);
215+
}
216+
193217
public static LLVMValueRef GetConstZeroArray(int length)
194218
{
195219
var int8Type = LLVM.Int8Type();
@@ -645,6 +669,7 @@ public WebAssemblyObjectWriter(string objectFilePath, NodeFactory factory, WebAs
645669
_nodeFactory = factory;
646670
_objectFilePath = objectFilePath;
647671
Module = compilation.Module;
672+
DIBuilder = compilation.DIBuilder;
648673
}
649674

650675
public void Dispose()

‎src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilation.cs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using ILCompiler.DependencyAnalysis;
1010
using ILCompiler.DependencyAnalysisFramework;
1111
using LLVMSharp;
12+
using ILCompiler.WebAssembly;
1213

1314
namespace ILCompiler
1415
{
@@ -17,18 +18,23 @@ public sealed class WebAssemblyCodegenCompilation : Compilation
1718
internal WebAssemblyCodegenConfigProvider Options { get; }
1819
internal LLVMModuleRef Module { get; }
1920
public new WebAssemblyCodegenNodeFactory NodeFactory { get; }
21+
internal LLVMDIBuilderRef DIBuilder { get; }
22+
internal Dictionary<string, DebugMetadata> DebugMetadataMap { get; }
2023
internal WebAssemblyCodegenCompilation(
2124
DependencyAnalyzerBase<NodeFactory> dependencyGraph,
2225
WebAssemblyCodegenNodeFactory nodeFactory,
2326
IEnumerable<ICompilationRootProvider> roots,
27+
DebugInformationProvider debugInformationProvider,
2428
Logger logger,
2529
WebAssemblyCodegenConfigProvider options)
26-
: base(dependencyGraph, nodeFactory, GetCompilationRoots(roots, nodeFactory), null, null, logger)
30+
: base(dependencyGraph, nodeFactory, GetCompilationRoots(roots, nodeFactory), debugInformationProvider, null, logger)
2731
{
2832
NodeFactory = nodeFactory;
2933
Module = LLVM.ModuleCreateWithName("netscripten");
3034
LLVM.SetTarget(Module, "asmjs-unknown-emscripten");
3135
Options = options;
36+
DIBuilder = LLVMPInvokes.LLVMCreateDIBuilder(Module);
37+
DebugMetadataMap = new Dictionary<string, DebugMetadata>();
3238
}
3339

3440
private static IEnumerable<ICompilationRootProvider> GetCompilationRoots(IEnumerable<ICompilationRootProvider> existingRoots, NodeFactory factory)

‎src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilationBuilder.cs‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ public override ICompilation ToCompilation()
3434
var interopStubManager = new CompilerGeneratedInteropStubManager(_compilationGroup, _context, new InteropStateManager(_context.GeneratedAssembly));
3535
WebAssemblyCodegenNodeFactory factory = new WebAssemblyCodegenNodeFactory(_context, _compilationGroup, _metadataManager, interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider);
3636
DependencyAnalyzerBase<NodeFactory> graph = CreateDependencyGraph(factory, new ObjectNode.ObjectNodeComparer(new CompilerComparer()));
37-
38-
return new WebAssemblyCodegenCompilation(graph, factory, _compilationRoots, _logger, _config);
37+
return new WebAssemblyCodegenCompilation(graph, factory, _compilationRoots, _debugInformationProvider, _logger, _config);
3938
}
4039
}
4140

0 commit comments

Comments
 (0)