Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Episode 28: Enabling debugging #161

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions samples/hello/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/hello.dll",
"args": [],
"cwd": "${workspaceFolder}/bin/Debug",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
"console": "externalTerminal",
"stopAtEntry": true,
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}
24 changes: 24 additions & 0 deletions samples/hello/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "shell",
"args": [
"build",
// Ask dotnet build to generate full paths for file names.
"/property:GenerateFullPaths=true",
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
"/consoleloggerparameters:NoSummary"
],
"group": "build",
"presentation": {
"reveal": "silent"
},
"problemMatcher": "$msCompile"
}
]
}
1 change: 1 addition & 0 deletions src/Minsk/CodeAnalysis/Binding/BoundNodeKind.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal enum BoundNodeKind
ConditionalGotoStatement,
ReturnStatement,
ExpressionStatement,
SequencePointStatement,

// Expressions
ErrorExpression,
Expand Down
27 changes: 27 additions & 0 deletions src/Minsk/CodeAnalysis/Binding/BoundNodePrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using Minsk.CodeAnalysis.Symbols;
using Minsk.CodeAnalysis.Syntax;
using Minsk.CodeAnalysis.Text;
using Minsk.IO;

namespace Minsk.CodeAnalysis.Binding
Expand Down Expand Up @@ -54,6 +55,9 @@ public static void WriteTo(this BoundNode node, IndentedTextWriter writer)
case BoundNodeKind.ReturnStatement:
WriteReturnStatement((BoundReturnStatement)node, writer);
break;
case BoundNodeKind.SequencePointStatement:
WriteSequencePointStatement((BoundSequencePointStatement)node, writer);
break;
case BoundNodeKind.ExpressionStatement:
WriteExpressionStatement((BoundExpressionStatement)node, writer);
break;
Expand Down Expand Up @@ -255,6 +259,29 @@ private static void WriteReturnStatement(BoundReturnStatement node, IndentedText
writer.WriteLine();
}

private static void WriteSequencePointStatement(BoundSequencePointStatement node, IndentedTextWriter writer)
{
var sourceText = node.Location.Text;
var span = node.Location.Span;

var startLine = sourceText.GetLineIndex(span.Start);
var endLine = sourceText.GetLineIndex(span.End - 1);

for (var i = startLine; i <= endLine; i++)
{
var line = sourceText.Lines[i];
var start = Math.Max(line.Start, span.Start);
var end = Math.Min(line.End, span.End);
var lineSpan = TextSpan.FromBounds(start, end);

var text = sourceText.ToString(lineSpan);
writer.WriteComment(text);
writer.WriteLine();
}

node.Statement.WriteTo(writer);
}

private static void WriteExpressionStatement(BoundExpressionStatement node, IndentedTextWriter writer)
{
node.Expression.WriteTo(writer);
Expand Down
20 changes: 20 additions & 0 deletions src/Minsk/CodeAnalysis/Binding/BoundSequencePointStatement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Minsk.CodeAnalysis.Syntax;
using Minsk.CodeAnalysis.Text;

namespace Minsk.CodeAnalysis.Binding
{
internal sealed class BoundSequencePointStatement : BoundStatement
{
public BoundSequencePointStatement(SyntaxNode syntax, BoundStatement statement, TextLocation location)
: base(syntax)
{
Statement = statement;
Location = location;
}

public override BoundNodeKind Kind => BoundNodeKind.SequencePointStatement;

public BoundStatement Statement { get; }
public TextLocation Location { get; }
}
}
11 changes: 11 additions & 0 deletions src/Minsk/CodeAnalysis/Binding/BoundTreeRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public virtual BoundStatement RewriteStatement(BoundStatement node)
return RewriteReturnStatement((BoundReturnStatement)node);
case BoundNodeKind.ExpressionStatement:
return RewriteExpressionStatement((BoundExpressionStatement)node);
case BoundNodeKind.SequencePointStatement:
return RewriteSequencePointStatement((BoundSequencePointStatement)node);
default:
throw new Exception($"Unexpected node: {node.Kind}");
}
Expand Down Expand Up @@ -160,6 +162,15 @@ protected virtual BoundStatement RewriteExpressionStatement(BoundExpressionState
return new BoundExpressionStatement(node.Syntax, expression);
}

private BoundStatement RewriteSequencePointStatement(BoundSequencePointStatement node)
{
var statement = RewriteStatement(node.Statement);
if (statement == node.Statement)
return node;

return new BoundSequencePointStatement(node.Syntax, statement, node.Location);
}

public virtual BoundExpression RewriteExpression(BoundExpression node)
{
switch (node.Kind)
Expand Down
2 changes: 2 additions & 0 deletions src/Minsk/CodeAnalysis/Binding/ControlFlowGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public List<BasicBlock> Build(BoundBlockStatement block)
break;
case BoundNodeKind.NopStatement:
case BoundNodeKind.VariableDeclaration:
case BoundNodeKind.SequencePointStatement:
case BoundNodeKind.ExpressionStatement:
_statements.Add(statement);
break;
Expand Down Expand Up @@ -192,6 +193,7 @@ public ControlFlowGraph Build(List<BasicBlock> blocks)
case BoundNodeKind.NopStatement:
case BoundNodeKind.VariableDeclaration:
case BoundNodeKind.LabelStatement:
case BoundNodeKind.SequencePointStatement:
case BoundNodeKind.ExpressionStatement:
if (isLastStatementInBlock)
Connect(current, next);
Expand Down
65 changes: 64 additions & 1 deletion src/Minsk/CodeAnalysis/Emit/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Minsk.CodeAnalysis.Binding;
using Minsk.CodeAnalysis.Symbols;
using Minsk.CodeAnalysis.Syntax;
using Minsk.CodeAnalysis.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
Expand All @@ -28,6 +30,7 @@ internal sealed class Emitter
private readonly MethodReference _convertToBooleanReference;
private readonly MethodReference _convertToInt32Reference;
private readonly MethodReference _convertToStringReference;
private readonly MethodReference _debuggableAttributeCtorReference;
private readonly TypeReference _randomReference;
private readonly MethodReference _randomCtorReference;
private readonly MethodReference _randomNextReference;
Expand All @@ -39,6 +42,7 @@ internal sealed class Emitter

private TypeDefinition _typeDefinition;
private FieldDefinition? _randomFieldDefinition;
private Dictionary<SourceText, Document> _documents = new Dictionary<SourceText, Document>();

// TOOD: This constructor does too much. Resolution should be factored out.
private Emitter(string moduleName, string[] references)
Expand Down Expand Up @@ -161,6 +165,7 @@ MethodReference ResolveMethod(string typeName, string methodName, string[] param
_randomReference = ResolveType(null, "System.Random");
_randomCtorReference = ResolveMethod("System.Random", ".ctor", Array.Empty<string>());
_randomNextReference = ResolveMethod("System.Random", "Next", new [] { "System.Int32" });
_debuggableAttributeCtorReference = ResolveMethod("System.Diagnostics.DebuggableAttribute", ".ctor", new [] { "System.Boolean", "System.Boolean" });

var objectType = _knownTypes[TypeSymbol.Any];
if (objectType != null)
Expand Down Expand Up @@ -197,7 +202,25 @@ public ImmutableArray<Diagnostic> Emit(BoundProgram program, string outputPath)
if (program.MainFunction != null)
_assemblyDefinition.EntryPoint = _methods[program.MainFunction];

_assemblyDefinition.Write(outputPath);
// TODO: We should not emit this attribute unless we produce a debug build
var debuggableAttribute = new CustomAttribute(_debuggableAttributeCtorReference);
debuggableAttribute.ConstructorArguments.Add(new CustomAttributeArgument(_knownTypes[TypeSymbol.Bool], true));
debuggableAttribute.ConstructorArguments.Add(new CustomAttributeArgument(_knownTypes[TypeSymbol.Bool], true));
_assemblyDefinition.CustomAttributes.Add(debuggableAttribute);

// TODO: We should not be computing paths in here.
var symbolsPath = Path.ChangeExtension(outputPath, ".pdb");

// TODO: We should support not emitting symbols
using (var outputStream = File.Create(outputPath))
using (var symbolsStream = File.Create(symbolsPath))
{
var writerParameters = new WriterParameters();
writerParameters.WriteSymbols = true;
writerParameters.SymbolStream = symbolsStream;
writerParameters.SymbolWriterProvider = new PortablePdbWriterProvider();
_assemblyDefinition.Write(outputStream, writerParameters);
}

return _diagnostics.ToImmutableArray();
}
Expand Down Expand Up @@ -241,6 +264,18 @@ private void EmitFunctionBody(FunctionSymbol function, BoundBlockStatement body)
}

method.Body.OptimizeMacros();

// TODO: Only emit this when emitting symbols

method.DebugInformation.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.Last());

foreach (var local in _locals)
{
var symbol = local.Key;
var definition = local.Value;
var debugInfo = new VariableDebugInformation(definition, symbol.Name);
method.DebugInformation.Scope.Variables.Add(debugInfo);
}
}

private void EmitStatement(ILProcessor ilProcessor, BoundStatement node)
Expand Down Expand Up @@ -268,6 +303,9 @@ private void EmitStatement(ILProcessor ilProcessor, BoundStatement node)
case BoundNodeKind.ExpressionStatement:
EmitExpressionStatement(ilProcessor, (BoundExpressionStatement)node);
break;
case BoundNodeKind.SequencePointStatement:
EmitSequencePointStatement(ilProcessor, (BoundSequencePointStatement)node);
break;
default:
throw new Exception($"Unexpected node kind {node.Kind}");
}
Expand Down Expand Up @@ -325,6 +363,31 @@ private void EmitExpressionStatement(ILProcessor ilProcessor, BoundExpressionSta
ilProcessor.Emit(OpCodes.Pop);
}

private void EmitSequencePointStatement(ILProcessor ilProcessor, BoundSequencePointStatement node)
{
var index = ilProcessor.Body.Instructions.Count;
EmitStatement(ilProcessor, node.Statement);

var instruction = ilProcessor.Body.Instructions[index];

if (!_documents.TryGetValue(node.Location.Text, out var document))
{
var fullPath = Path.GetFullPath(node.Location.Text.FileName);
document = new Document(fullPath);
_documents.Add(node.Location.Text, document);
}

var sequencePoint = new SequencePoint(instruction, document)
{
StartLine = node.Location.StartLine + 1,
StartColumn = node.Location.StartCharacter + 1,
EndLine = node.Location.EndLine + 1,
EndColumn = node.Location.EndCharacter + 1
};

ilProcessor.Body.Method.DebugInformation.SequencePoints.Add(sequencePoint);
}

private void EmitExpression(ILProcessor ilProcessor, BoundExpression node)
{
if (node.ConstantValue != null)
Expand Down
13 changes: 11 additions & 2 deletions src/Minsk/CodeAnalysis/Evaluator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Minsk.CodeAnalysis.Binding;
using Minsk.CodeAnalysis.Symbols;

Expand Down Expand Up @@ -57,11 +58,19 @@ public Evaluator(BoundProgram program, Dictionary<VariableSymbol, object> variab
labelToIndex.Add(l.Label, i + 1);
}

var statements = body.Statements.ToArray();

for (int i = 0; i < statements.Length; i++)
{
if (statements[i] is BoundSequencePointStatement s)
statements[i] = s.Statement;
}

var index = 0;

while (index < body.Statements.Length)
while (index < statements.Length)
{
var s = body.Statements[index];
var s = statements[index];

switch (s.Kind)
{
Expand Down
12 changes: 12 additions & 0 deletions src/Minsk/CodeAnalysis/Lowering/Lowerer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,18 @@ protected override BoundStatement RewriteConditionalGotoStatement(BoundCondition
return base.RewriteConditionalGotoStatement(node);
}

protected override BoundStatement RewriteVariableDeclaration(BoundVariableDeclaration node)
{
var rewrittenNode = base.RewriteVariableDeclaration(node);
return new BoundSequencePointStatement(rewrittenNode.Syntax, rewrittenNode, rewrittenNode.Syntax.Location);
}

protected override BoundStatement RewriteExpressionStatement(BoundExpressionStatement node)
{
var rewrittenNode = base.RewriteExpressionStatement(node);
return new BoundSequencePointStatement(rewrittenNode.Syntax, rewrittenNode, rewrittenNode.Syntax.Location);
}

protected override BoundExpression RewriteCompoundAssignmentExpression(BoundCompoundAssignmentExpression node)
{
var newNode = (BoundCompoundAssignmentExpression) base.RewriteCompoundAssignmentExpression(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Minsk.CodeAnalysis.Syntax
{
public sealed partial class AssignmentExpressionSyntax : ExpressionSyntax
{
public AssignmentExpressionSyntax(SyntaxTree syntaxTree, SyntaxToken identifierToken, SyntaxToken assignmentToken, ExpressionSyntax expression)
internal AssignmentExpressionSyntax(SyntaxTree syntaxTree, SyntaxToken identifierToken, SyntaxToken assignmentToken, ExpressionSyntax expression)
: base(syntaxTree)
{
IdentifierToken = identifierToken;
Expand Down
8 changes: 8 additions & 0 deletions src/Minsk/IO/TextWriterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ public static void WriteSpace(this TextWriter writer)
writer.WritePunctuation(" ");
}

public static void WriteComment(this TextWriter writer, string text)
{
writer.SetForeground(ConsoleColor.DarkGreen);
writer.Write("// ");
writer.Write(text);
writer.ResetColor();
}

public static void WritePunctuation(this TextWriter writer, SyntaxKind kind)
{
var text = SyntaxFacts.GetText(kind);
Expand Down