Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c5c87ef
adding initial version of StackTrace collector with tests
vadym-kl Oct 27, 2019
07b8ff5
adding more tests
vadym-kl Oct 28, 2019
db21e9f
adding placeholders; work in progress
vadym-kl Oct 28, 2019
3af694e
work in progress
vadym-kl Oct 29, 2019
24350c5
wip
vadym-kl Oct 29, 2019
4cb1647
added PDB source extraction supoort
vadym-kl Oct 29, 2019
40f7916
extending tests; fixing url issues
vadym-kl Oct 30, 2019
f098a43
adding portablePdbreader
vadym-kl Oct 30, 2019
28ea1b8
fixing code generation tests to have base one line numbers
vadym-kl Oct 30, 2019
1701ba1
incorporating feedback
vadym-kl Oct 30, 2019
6408fe2
refactoring
vadym-kl Oct 30, 2019
013bea6
refactoring
vadym-kl Oct 30, 2019
12edebc
refactoring
vadym-kl Oct 30, 2019
973cea5
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 5, 2019
7d37053
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 11, 2019
3a6bccb
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 13, 2019
52101ee
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 19, 2019
852b4ec
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
bettinaheim Nov 20, 2019
ac0bfa2
addressing review; part1
vadym-kl Nov 20, 2019
96839dd
addressing reviewer feedback; part 2
vadym-kl Nov 21, 2019
9960314
addressing reviewer comments; part3
vadym-kl Nov 21, 2019
ebff325
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 21, 2019
0e4d977
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
bettinaheim Nov 22, 2019
d77eac4
Always enable stack trace. Some extra tests. Minor clean up.
anpaz Nov 23, 2019
6e5b18e
Merge remote-tracking branch 'origin/master' into vadym-kl/70-qsharp-…
anpaz Nov 23, 2019
cad26d9
fixing out of bound issue when Q# and C# stack traces are merged
vadym-kl Nov 24, 2019
11a9fbd
Merge branch 'master' into vadym-kl/70-qsharp-call-stack
vadym-kl Nov 24, 2019
f722105
Update src/Simulation/Common/PortablePDBReader.cs
anpaz Nov 25, 2019
b39f5a6
Merge branch 'vadym-kl/70-qsharp-call-stack' of https://github.com/mi…
anpaz Nov 25, 2019
e01d0d8
Minor renames.
anpaz Nov 25, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Reflection.Metadata" Version="1.7.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Core\Microsoft.Quantum.Simulation.Core.csproj" />
<ProjectReference Include="..\Intrinsic\Microsoft.Quantum.Intrinsic.csproj" />
Expand Down
396 changes: 396 additions & 0 deletions src/Simulation/Common/PortablePDBReader.cs

Large diffs are not rendered by default.

49 changes: 44 additions & 5 deletions src/Simulation/Common/SimulatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ public abstract class SimulatorBase : AbstractFactory<AbstractCallable>, IOperat

public abstract string Name { get; }


/// <summary>
/// If the execution finishes in failure, this method returns the call-stack of the Q# operations
/// executed up to the point when the failure happened.
/// If the execution was successful, this method returns null.
/// </summary>
/// <remarks>
/// Only Q# operations are tracked and reported in the stack trace. Q# functions or calls from
/// classical hosts like C# or Python are not included.
/// </remarks>
public StackFrame[] CallStack { get; private set; }

public SimulatorBase(IQubitManager qubitManager = null)
{
this.QubitManager = qubitManager;
Expand Down Expand Up @@ -98,14 +110,41 @@ public override AbstractCallable CreateInstance(Type t)
return result;
}

public virtual O Execute<T, I, O>(I args) where T : AbstractCallable, ICallable
{
StackTraceCollector stackTraceCollector = new StackTraceCollector(this);
var op = Get<ICallable, T>();

try
{
var result = op.Apply<O>(args);
this.CallStack = null;
return result;
}
catch (Exception e) // Dumps q# call-stack in case of exception if CallStack tracking was enabled
{
this.CallStack = stackTraceCollector.CallStack;
OnLog?.Invoke($"Unhandled exception. {e.GetType().FullName}: {e.Message}");
bool first = true;
foreach (StackFrame sf in this.CallStack)
{
var msg = (first ? " ---> " : " at ") + sf.ToStringWithBestSourceLocation();
OnLog?.Invoke(msg);
first = false;
}
OnLog?.Invoke("");

throw;
}
finally
{
stackTraceCollector.Dispose();
}
}

public virtual Task<O> Run<T, I, O>(I args) where T : AbstractCallable, ICallable
{
return Task<O>.Run(() =>
{
var op = Get<ICallable, T>();
return op.Apply<O>(args);
});
return Task<O>.Run(() => Execute<T, I, O>(args));
}

/// <summary>
Expand Down
299 changes: 299 additions & 0 deletions src/Simulation/Common/StackTrace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Quantum.Simulation.Core;
using System.Diagnostics;
using System.Linq;

namespace Microsoft.Quantum.Simulation.Common
{
/// <summary>
/// Stores information about Q# stack frames. During successful execution keeps track only of Callable and Argument.
/// When the exception happens, the rest of the information is populated by <see cref="PopulateSourceLocations(Stack{StackFrame}, System.Diagnostics.StackFrame[])"/>
/// method.
/// </summary>
[Serializable]
public class StackFrame
{
/// <summary>
/// Callable corresponding to the stack frame
/// </summary>
public ICallable Callable { get; private set; }

/// <summary>
/// Arguments passed to the callable in the stack frame
/// </summary>
public IApplyData Argument { get; private set; }

/// <summary>
/// The path to the source where operation is defined
/// </summary>
public string SourceFile { get; private set; }

/// <summary>
/// One based line number in the operation that resulted in failure. Note that for automatically derived Adjoint and Controlled
/// variants of the operation, the line always points to the operation declaration
/// </summary>
public int FailedLineNumber { get; private set; }

/// <summary>
/// One based line number where the declaration starts.
/// </summary>
public int DeclarationStartLineNumber { get; private set; }

/// <summary>
/// One based line number of the first line after the declaration.
/// The value -1, if the declaration ends on the last line of the file.
/// </summary>
public int DeclarationEndLineNumber { get; private set; }

public StackFrame(ICallable callable, IApplyData argument)
{
Callable = callable;
Argument = argument;
}

/// <summary>
/// Uses PortablePDBs and SourceLink to get the source of failed operation.
/// </summary>
public string GetOperationSourceFromPDB()
{
string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable);
return PortablePDBEmbeddedFilesCache.GetEmbeddedFileRange(
pdbFileLocation,
SourceFile,
DeclarationStartLineNumber,
DeclarationEndLineNumber,
showLineNumbers: true, markedLine: FailedLineNumber);
}

/// <summary>
/// Uses PortablePDBs and SourceLink to get URL for file and line number.
/// </summary>
/// <returns></returns>
public string GetURLFromPDB()
{
string pdbFileLocation = PortablePdbSymbolReader.GetPDBLocation(Callable);
string result = PortablePDBSourceLinkInfoCache.TryGetFileUrl(pdbFileLocation, SourceFile);
return PortablePdbSymbolReader.TryFormatGitHubUrl(result, FailedLineNumber);
}

private const string messageFormat = "{0} on {1}";

public override string ToString()
{
return string.Format(messageFormat, Callable.FullName, $"{SourceFile}:line {FailedLineNumber}");
}

/// <summary>
/// The same as <see cref="ToString"/>, but tries to point to best source location.
/// If the source is not available on local machine, source location will be replaced
/// by URL pointing to GitHub repository.
/// This is more costly than <see cref="ToString"/> because it checks if source file exists on disk.
/// If the file does not exist it calls <see cref="GetURLFromPDB"/> to get the URL
/// which is also more costly than <see cref="ToString"/>.
/// </summary>
public virtual string ToStringWithBestSourceLocation()
{
string message = ToString();
if (System.IO.File.Exists(SourceFile))
{
return message;
}
else
{
string url = GetURLFromPDB();
if (url == null)
{
return message;
}
else
{
return string.Format(messageFormat, Callable.FullName, url);
}
}
}

/// <summary>
/// Finds correspondence between Q# and C# stack frames and populates Q# stack frame information from C# stack frames
/// </summary>
public static StackFrame[] PopulateSourceLocations(Stack<StackFrame> qsharpCallStack, System.Diagnostics.StackFrame[] csharpCallStack)
{
foreach (StackFrame currentFrame in qsharpCallStack)
{
ICallable op = currentFrame.Callable.UnwrapCallable();
object[] locations = op.GetType().GetCustomAttributes(typeof(SourceLocationAttribute), true);
foreach (object location in locations)
{
SourceLocationAttribute sourceLocation = (location as SourceLocationAttribute);
if (sourceLocation != null && sourceLocation.SpecializationKind == op.Variant)
{
currentFrame.SourceFile = System.IO.Path.GetFullPath(sourceLocation.SourceFile);
currentFrame.DeclarationStartLineNumber = sourceLocation.StartLine;
currentFrame.DeclarationEndLineNumber = sourceLocation.EndLine;
}
}
}

StackFrame[] qsharpStackFrames = qsharpCallStack.ToArray();
int qsharpStackFrameId = 0;
for (int csharpStackFrameId = 0; csharpStackFrameId < csharpCallStack.Length; ++csharpStackFrameId)
{
string fileName = csharpCallStack[csharpStackFrameId].GetFileName();
if (fileName != null)
{
fileName = System.IO.Path.GetFullPath(fileName);
int failedLineNumber = csharpCallStack[csharpStackFrameId].GetFileLineNumber();
StackFrame currentQsharpStackFrame = qsharpStackFrames[qsharpStackFrameId];
if (fileName == currentQsharpStackFrame.SourceFile &&
currentQsharpStackFrame.DeclarationStartLineNumber <= failedLineNumber &&
(
(failedLineNumber < currentQsharpStackFrame.DeclarationEndLineNumber) ||
(currentQsharpStackFrame.DeclarationEndLineNumber == -1)
)
)
{
currentQsharpStackFrame.FailedLineNumber = failedLineNumber;
qsharpStackFrameId++;
if (qsharpStackFrameId == qsharpStackFrames.Length) break;
}
}
}
return qsharpStackFrames;
}
}

/// <summary>
/// Tracks Q# operations call-stack till the first failure resulting in <see cref="SimulatorBase.OnFail"/>
/// event invocation.
/// </summary>
/// <remarks>
/// Only Q# operations are tracked and reported in the stack trace. Q# functions or calls from
/// classical hosts like C# or Python are not included.
/// </remarks>
public class StackTraceCollector : IDisposable
{
private readonly Stack<StackFrame> callStack;
private readonly SimulatorBase sim;
private System.Diagnostics.StackFrame[] frames = null;
StackFrame[] stackFramesWithLocations = null;
bool hasFailed = false;

public StackTraceCollector(SimulatorBase sim)
{
callStack = new Stack<StackFrame>();
this.sim = sim;

this.Start();
}

private void Start()
{
sim.OnOperationStart += this.OnOperationStart;
sim.OnOperationEnd += this.OnOperationEnd;
sim.OnFail += this.OnFail;
}

private void Stop()
{
sim.OnOperationStart -= this.OnOperationStart;
sim.OnOperationEnd -= this.OnOperationEnd;
sim.OnFail -= this.OnFail;
}

void OnOperationStart(ICallable callable, IApplyData arg)
{
if (!hasFailed)
{
callStack.Push(new StackFrame(callable, arg));
}
}

void OnOperationEnd(ICallable callable, IApplyData arg)
{
if (!hasFailed)
{
callStack.Pop();
}
}

void OnFail(System.Runtime.ExceptionServices.ExceptionDispatchInfo exceptionInfo)
{
if (!hasFailed)
{
hasFailed = true;
}

System.Diagnostics.StackTrace stackTrace = new System.Diagnostics.StackTrace(exceptionInfo.SourceException, 0, true);
System.Diagnostics.StackFrame[] currentFrames = stackTrace.GetFrames();

if (frames == null)
{
frames = currentFrames;
}
else
{
// When the exception is thrown, OnFail can be called multiple times.
// With every next call we see bigger part of the call stack, so we save the biggest call stack
if (currentFrames.Length > frames.Length)
{
Debug.Assert((frames.Length == 0) || (frames[0].ToString() == currentFrames[0].ToString()));
frames = currentFrames;
}
}
}



/// <summary>
/// If failure has happened returns the call-stack at time of failure.
/// Returns null if the failure has not happened.
/// </summary>
public StackFrame[] CallStack
{
get
{
if (hasFailed)
{
if( stackFramesWithLocations == null )
{
stackFramesWithLocations = StackFrame.PopulateSourceLocations(callStack, frames);
}
return stackFramesWithLocations;
}
else
{
return null;
}
}
}

#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
this.Stop();
}

disposedValue = true;
}
}

// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
#endregion
}
}
3 changes: 2 additions & 1 deletion src/Simulation/Core/Generics/Adjoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ public interface IAdjointable : ICallable
/// input Type is not resolved until it gets Applied at runtime.
/// </summary>
[DebuggerTypeProxy(typeof(GenericAdjoint.DebuggerProxy))]
public class GenericAdjoint : GenericCallable, IApplyData
public class GenericAdjoint : GenericCallable, IApplyData, IOperationWrapper
{
public GenericAdjoint(GenericCallable baseOp) : base(baseOp.Factory, null)
{
this.BaseOp = baseOp;
}

public GenericCallable BaseOp { get; }
ICallable IOperationWrapper.BaseOperation => BaseOp;

IEnumerable<Qubit> IApplyData.Qubits => ((IApplyData)this.BaseOp)?.Qubits;

Expand Down
Loading