提交 5b08d9b3 编写于 作者: A Andrew Casey

Improve EvaluationContext cache invalidation

Ignoring references, which are obviously the high-order bit, the old
strategy was to confirm that the methodToken, methodVersion, and smallest
containing scope were the same as the current context.  This had two
problems:

1) C# hoisted locals are not reflected in scopes and so we were not
invalidating the cache when we stepped across scope boundaries in async
and iterator methods.

2) We had to access the PDB in order to check whether the cache was still
valid (to figure out the smallest containing scope).

We propose a new approach: compute the largest span around the current IL
offset in which there are no scope boundaries (including hoisted local
scopes).  Then, when check cache validity by doing a simple span
containment check (plus methodToken and methodVersion, obviously).

Downside 1: We need all scopes in the method, not just the containing
ones.

Downside 2: If you somehow step around a nested scope (e.g. using
breakpoints), the new approach will invalidate the cache, whereas the old
one did not.
上级 19983a3f
......@@ -26,7 +26,7 @@ internal sealed class EvaluationContext : EvaluationContextBase
internal const bool IsLocalScopeEndInclusive = false;
internal readonly ImmutableArray<MetadataBlock> MetadataBlocks;
internal readonly MethodScope MethodScope;
internal readonly MethodContextReuseConstraints? MethodContextReuseConstraints;
internal readonly CSharpCompilation Compilation;
private readonly MetadataDecoder _metadataDecoder;
......@@ -37,7 +37,7 @@ internal sealed class EvaluationContext : EvaluationContextBase
private EvaluationContext(
ImmutableArray<MetadataBlock> metadataBlocks,
MethodScope methodScope,
MethodContextReuseConstraints? methodContextReuseConstraints,
CSharpCompilation compilation,
MetadataDecoder metadataDecoder,
MethodSymbol currentFrame,
......@@ -48,7 +48,7 @@ internal sealed class EvaluationContext : EvaluationContextBase
Debug.Assert(inScopeHoistedLocalIndices != null);
this.MetadataBlocks = metadataBlocks;
this.MethodScope = methodScope;
this.MethodContextReuseConstraints = methodContextReuseConstraints;
this.Compilation = compilation;
_metadataDecoder = metadataDecoder;
_currentFrame = currentFrame;
......@@ -121,18 +121,15 @@ internal sealed class EvaluationContext : EvaluationContextBase
{
Debug.Assert(MetadataTokens.Handle(methodToken).Kind == HandleKind.MethodDefinition);
var typedSymReader = (ISymUnmanagedReader)symReader;
var scopes = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
typedSymReader.GetScopes(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive, scopes);
var scope = scopes.GetMethodScope(methodToken, methodVersion);
// Re-use the previous compilation if possible.
CSharpCompilation compilation;
if (metadataBlocks.HaveNotChanged(previous))
{
// Re-use entire context if method scope has not changed.
var previousContext = previous.EvaluationContext;
if ((scope != null) && (previousContext != null) && scope.Equals(previousContext.MethodScope))
if (previousContext != null &&
previousContext.MethodContextReuseConstraints.HasValue &&
previousContext.MethodContextReuseConstraints.GetValueOrDefault().AreSatisfied(methodToken, methodVersion, ilOffset))
{
return previousContext;
}
......@@ -143,10 +140,15 @@ internal sealed class EvaluationContext : EvaluationContextBase
compilation = metadataBlocks.ToCompilation();
}
var localNames = scopes.GetLocalNames();
var typedSymReader = (ISymUnmanagedReader)symReader;
var allScopes = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
var containingScopes = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
typedSymReader.GetScopes(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive, allScopes, containingScopes);
var methodContextReuseConstraints = allScopes.GetReuseConstraints(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive);
allScopes.Free();
var localNames = containingScopes.GetLocalNames();
var dynamicLocalMap = ImmutableDictionary<int, ImmutableArray<bool>>.Empty;
var dynamicLocalConstantMap = ImmutableDictionary<string, ImmutableArray<bool>>.Empty;
var inScopeHoistedLocalIndices = ImmutableSortedSet<int>.Empty;
var methodDebugInfo = default(MethodDebugInfo);
......@@ -154,26 +156,9 @@ internal sealed class EvaluationContext : EvaluationContextBase
{
try
{
var cdi = typedSymReader.GetCustomDebugInfoBytes(methodToken, methodVersion);
if (cdi != null)
{
CustomDebugInfoReader.GetCSharpDynamicLocalInfo(
cdi,
methodToken,
methodVersion,
localNames.FirstOrDefault(),
out dynamicLocalMap,
out dynamicLocalConstantMap);
inScopeHoistedLocalIndices = CustomDebugInfoReader.GetCSharpInScopeHoistedLocalIndices(
cdi,
methodToken,
methodVersion,
ilOffset);
}
// TODO (acasey): switch on the type of typedSymReader and call the appropriate helper. (GH #702)
methodDebugInfo = typedSymReader.GetMethodDebugInfo(methodToken, methodVersion);
methodDebugInfo = typedSymReader.GetMethodDebugInfo(methodToken, methodVersion, localNames.FirstOrDefault());
inScopeHoistedLocalIndices = methodDebugInfo.GetInScopeHoistedLocalIndices(ilOffset, ref methodContextReuseConstraints);
}
catch (InvalidOperationException)
{
......@@ -188,15 +173,15 @@ internal sealed class EvaluationContext : EvaluationContextBase
var localInfo = metadataDecoder.GetLocalInfo(localSignatureToken);
var localBuilder = ArrayBuilder<LocalSymbol>.GetInstance();
var sourceAssembly = compilation.SourceAssembly;
GetLocals(localBuilder, currentFrame, localNames, localInfo, dynamicLocalMap, sourceAssembly);
GetConstants(localBuilder, currentFrame, scopes.GetConstantSignatures(), metadataDecoder, dynamicLocalConstantMap, sourceAssembly);
scopes.Free();
GetLocals(localBuilder, currentFrame, localNames, localInfo, methodDebugInfo.DynamicLocalMap, sourceAssembly);
GetConstants(localBuilder, currentFrame, containingScopes.GetConstantSignatures(), metadataDecoder, methodDebugInfo.DynamicLocalConstantMap, sourceAssembly);
containingScopes.Free();
var locals = localBuilder.ToImmutableAndFree();
return new EvaluationContext(
metadataBlocks,
scope,
methodContextReuseConstraints,
compilation,
metadataDecoder,
currentFrame,
......@@ -478,7 +463,7 @@ internal CompilationContext CreateCompilationContext(CSharpSyntaxNode syntax)
}
ImmutableArray<bool> dynamicFlags;
if (dynamicLocalMap.TryGetValue(i, out dynamicFlags))
if (dynamicLocalMap != null && dynamicLocalMap.TryGetValue(i, out dynamicFlags))
{
type = DynamicTypeDecoder.TransformTypeWithoutCustomModifierFlags(
type,
......@@ -510,7 +495,7 @@ internal CompilationContext CreateCompilationContext(CSharpSyntaxNode syntax)
var type = info.Type;
ImmutableArray<bool> dynamicFlags;
if (dynamicLocalConstantMap.TryGetValue(constant.Name, out dynamicFlags))
if (dynamicLocalConstantMap != null && dynamicLocalConstantMap.TryGetValue(constant.Name, out dynamicFlags))
{
type = DynamicTypeDecoder.TransformTypeWithoutCustomModifierFlags(
type,
......
......@@ -14,7 +14,8 @@ internal static class SymUnmanagedReaderExtensions
public static MethodDebugInfo GetMethodDebugInfo(
this ISymUnmanagedReader reader,
int methodToken,
int methodVersion)
int methodVersion,
string firstLocalName)
{
ImmutableArray<string> externAliasStrings;
var importStringGroups = reader.GetCSharpGroupedImportStrings(methodToken, methodVersion, out externAliasStrings);
......@@ -72,9 +73,37 @@ internal static class SymUnmanagedReaderExtensions
externAliasRecordBuilder.Add(new NativeExternAliasRecord<AssemblySymbol>(alias, targetIdentity));
}
var hoistedLocalScopeRecords = ImmutableArray<HoistedLocalScopeRecord>.Empty;
var dynamicLocalMap = ImmutableDictionary<int, ImmutableArray<bool>>.Empty;
var dynamicLocalConstantMap = ImmutableDictionary<string, ImmutableArray<bool>>.Empty;
byte[] customDebugInfoBytes = reader.GetCustomDebugInfoBytes(methodToken, methodVersion);
if (customDebugInfoBytes != null)
{
var customDebugInfoRecord = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfoBytes, CustomDebugInfoKind.StateMachineHoistedLocalScopes);
if (!customDebugInfoRecord.IsDefault)
{
hoistedLocalScopeRecords = CustomDebugInfoReader.DecodeStateMachineHoistedLocalScopesRecord(customDebugInfoRecord)
.SelectAsArray(s => HoistedLocalScopeRecord.FromNative(s.StartOffset, s.EndOffset));
}
CustomDebugInfoReader.GetCSharpDynamicLocalInfo(
customDebugInfoBytes,
methodToken,
methodVersion,
firstLocalName,
out dynamicLocalMap,
out dynamicLocalConstantMap);
}
return new MethodDebugInfo(
hoistedLocalScopeRecords,
importRecordGroupBuilder.ToImmutableAndFree(),
externAliasRecordBuilder.ToImmutableAndFree(),
dynamicLocalMap,
dynamicLocalConstantMap,
defaultNamespaceName: ""); // Unused in C#.
}
......
......@@ -106,6 +106,7 @@
<Compile Include="LocalsTests.cs" />
<Compile Include="NoPIATests.cs" />
<Compile Include="PseudoVariableTests.cs" />
<Compile Include="MethodContextReuseConstraintsTests.cs" />
<Compile Include="UsingDebugInfoTests.cs" />
<Compile Include="WinMdTests.cs" />
</ItemGroup>
......
......@@ -355,14 +355,14 @@ static void G()
Assert.Null(previous);
previous = new CSharpMetadataContext(context);
// At end of outer scope.
// At end of outer scope - not reused because of the nested scope.
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, symReader, moduleVersionId, methodToken, methodVersion, endOffset, localSignatureToken);
Assert.Equal(context, previous.EvaluationContext);
Assert.NotEqual(context, previous.EvaluationContext); // Not required, just documentary.
// At type context.
context = EvaluationContext.CreateTypeContext(previous, methodBlocks, moduleVersionId, typeToken);
Assert.NotEqual(context, previous.EvaluationContext);
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope);
Assert.Null(context.MethodContextReuseConstraints);
Assert.Equal(context.Compilation, previous.Compilation);
// Step through entire method.
......@@ -371,6 +371,12 @@ static void G()
for (int offset = startOffset; offset <= endOffset; offset++)
{
var scope = scopes.GetInnermostScope(offset);
var constraints = previous.EvaluationContext.MethodContextReuseConstraints;
if (constraints.HasValue)
{
Assert.Equal(scope == previousScope, constraints.GetValueOrDefault().AreSatisfied(methodToken, methodVersion, offset));
}
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, symReader, moduleVersionId, methodToken, methodVersion, offset, localSignatureToken);
if (scope == previousScope)
{
......@@ -380,9 +386,9 @@ static void G()
{
// Different scope. Should reuse compilation.
Assert.NotEqual(context, previous.EvaluationContext);
if (previous != null)
if (previous.EvaluationContext != null)
{
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope);
Assert.NotEqual(context.MethodContextReuseConstraints, previous.EvaluationContext.MethodContextReuseConstraints);
Assert.Equal(context.Compilation, previous.Compilation);
}
}
......@@ -399,7 +405,7 @@ static void G()
// Different references. No reuse.
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, symReader, moduleVersionId, methodToken, methodVersion, endOffset, localSignatureToken);
Assert.NotEqual(context, previous.EvaluationContext);
Assert.Equal(context.MethodScope, previous.EvaluationContext.MethodScope);
Assert.True(previous.EvaluationContext.MethodContextReuseConstraints.Value.AreSatisfied(methodToken, methodVersion, endOffset));
Assert.NotEqual(context.Compilation, previous.Compilation);
previous = new CSharpMetadataContext(context);
......@@ -407,7 +413,7 @@ static void G()
GetContextState(runtime, "C.G", out methodBlocks, out moduleVersionId, out symReader, out methodToken, out localSignatureToken);
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, symReader, moduleVersionId, methodToken, methodVersion, ilOffset: 0, localSignatureToken: localSignatureToken);
Assert.NotEqual(context, previous.EvaluationContext);
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope);
Assert.False(previous.EvaluationContext.MethodContextReuseConstraints.Value.AreSatisfied(methodToken, methodVersion, 0));
Assert.Equal(context.Compilation, previous.Compilation);
// No EvaluationContext. Should reuse Compilation
......
......@@ -1291,7 +1291,7 @@ .maxstack 1
[WorkItem(1134746, "DevDiv")]
[Fact]
public void Caching()
public void CacheInvalidation()
{
var source = @"
using System.Collections.Generic;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.ExpressionEvaluator;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
public class MethodContextReuseConstraintsTests : ExpressionCompilerTestBase
{
[Fact]
public void AreSatisfied()
{
const int methodToken = 0x06000001;
const int methodVersion = 1;
const uint startOffset = 1;
const uint endOffsetExclusive = 3;
var constraints = MethodContextReuseConstraints.CreateTestInstance(
methodToken,
methodVersion,
startOffset,
endOffsetExclusive);
Assert.True(constraints.AreSatisfied(methodToken, methodVersion, (int)startOffset));
Assert.True(constraints.AreSatisfied(methodToken, methodVersion, (int)endOffsetExclusive - 1));
Assert.False(constraints.AreSatisfied(methodToken + 1, methodVersion, (int)startOffset));
Assert.False(constraints.AreSatisfied(methodToken, methodVersion + 1, (int)startOffset));
Assert.False(constraints.AreSatisfied(methodToken, methodVersion, (int)startOffset - 1));
Assert.False(constraints.AreSatisfied(methodToken, methodVersion, (int)endOffsetExclusive));
}
[Fact]
public void EndInclusive()
{
const int methodToken = 0x06000001;
const int methodVersion = 1;
var builder = new MethodContextReuseConstraints.Builder(methodToken, methodVersion, ilOffset: 5, areRangesEndInclusive: true);
Assert.True(builder.Build().HasExpectedSpan(0u, uint.MaxValue));
builder.AddRange(1, 9);
Assert.True(builder.Build().HasExpectedSpan(1, 10));
builder.AddRange(2, 8);
Assert.True(builder.Build().HasExpectedSpan(2, 9));
builder.AddRange(1, 3);
Assert.True(builder.Build().HasExpectedSpan(4, 9));
builder.AddRange(7, 9);
Assert.True(builder.Build().HasExpectedSpan(4, 7));
}
[Fact]
public void EndExclusive()
{
const int methodToken = 0x06000001;
const int methodVersion = 1;
var builder = new MethodContextReuseConstraints.Builder(methodToken, methodVersion, ilOffset: 5, areRangesEndInclusive: false);
Assert.True(builder.Build().HasExpectedSpan(0u, uint.MaxValue));
builder.AddRange(1, 9);
Assert.True(builder.Build().HasExpectedSpan(1, 9));
builder.AddRange(2, 8);
Assert.True(builder.Build().HasExpectedSpan(2, 8));
builder.AddRange(1, 3);
Assert.True(builder.Build().HasExpectedSpan(3, 8));
builder.AddRange(7, 9);
Assert.True(builder.Build().HasExpectedSpan(3, 7));
}
[Fact]
public void Cumulative()
{
const int methodToken = 0x06000001;
const int methodVersion = 1;
var builder = new MethodContextReuseConstraints.Builder(methodToken, methodVersion, ilOffset: 5, areRangesEndInclusive: false);
Assert.True(builder.Build().HasExpectedSpan(0u, uint.MaxValue));
builder.AddRange(1, 10);
Assert.True(builder.Build().HasExpectedSpan(1, 10));
builder = new MethodContextReuseConstraints.Builder(builder.Build(), ilOffset: 5, areRangesEndInclusive: true);
builder.AddRange(2, 8);
Assert.True(builder.Build().HasExpectedSpan(2, 9));
builder = new MethodContextReuseConstraints.Builder(builder.Build(), ilOffset: 5, areRangesEndInclusive: false);
builder.AddRange(1, 3);
Assert.True(builder.Build().HasExpectedSpan(3, 9));
builder = new MethodContextReuseConstraints.Builder(builder.Build(), ilOffset: 5, areRangesEndInclusive: true);
builder.AddRange(7, 9);
Assert.True(builder.Build().HasExpectedSpan(3, 7));
}
}
}
\ No newline at end of file
......@@ -39,6 +39,7 @@
<Compile Include="FrameDecoder.cs" />
<Compile Include="InstructionDecoder.cs" />
<Compile Include="LanguageInstructionDecoder.cs" />
<Compile Include="PDB\HoisedLocalScopeRecord.cs" />
<Compile Include="PDB\ExternAliasRecord.cs" />
<Compile Include="PDB\MethodDebugInfo.cs" />
<Compile Include="PDB\ImportRecord.cs" />
......@@ -59,7 +60,7 @@
<Compile Include="MetadataBlock.cs" />
<Compile Include="MetadataUtilities.cs" />
<Compile Include="AbstractTypeParameterChecker.cs" />
<Compile Include="MethodScope.cs" />
<Compile Include="MethodContextReuseConstraints.cs" />
<Compile Include="NamedLocalConstant.cs" />
<Compile Include="PseudoVariableUtilities.cs" />
<Compile Include="Resources.Designer.cs">
......
......@@ -246,7 +246,14 @@ internal static bool IsWindowsAssemblyIdentity(this AssemblyIdentity assemblyIde
/// Get the set of nested scopes containing the
/// IL offset from outermost scope to innermost.
/// </summary>
internal static void GetScopes(this ISymUnmanagedReader symReader, int methodToken, int methodVersion, int ilOffset, bool isScopeEndInclusive, ArrayBuilder<ISymUnmanagedScope> scopes)
internal static void GetScopes(
this ISymUnmanagedReader symReader,
int methodToken,
int methodVersion,
int ilOffset,
bool isScopeEndInclusive,
ArrayBuilder<ISymUnmanagedScope> allScopes,
ArrayBuilder<ISymUnmanagedScope> containingScopes)
{
if (symReader == null)
{
......@@ -259,17 +266,17 @@ internal static void GetScopes(this ISymUnmanagedReader symReader, int methodTok
return;
}
symMethod.GetAllScopes(scopes, ilOffset, isScopeEndInclusive);
symMethod.GetAllScopes(allScopes, containingScopes, ilOffset, isScopeEndInclusive);
}
internal static MethodScope GetMethodScope(this ArrayBuilder<ISymUnmanagedScope> scopes, int methodToken, int methodVersion)
internal static MethodContextReuseConstraints GetReuseConstraints(this ArrayBuilder<ISymUnmanagedScope> scopes, int methodToken, int methodVersion, int ilOffset, bool isEndInclusive)
{
if (scopes.Count == 0)
var builder = new MethodContextReuseConstraints.Builder(methodToken, methodVersion, ilOffset, isEndInclusive);
foreach (ISymUnmanagedScope scope in scopes)
{
return null;
builder.AddRange((uint)scope.GetStartOffset(), (uint)scope.GetEndOffset());
}
var scope = scopes.Last();
return new MethodScope(methodToken, methodVersion, scope.GetStartOffset(), scope.GetEndOffset());
return builder.Build();
}
internal static ImmutableArray<string> GetLocalNames(this ArrayBuilder<ISymUnmanagedScope> scopes)
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal struct MethodContextReuseConstraints
{
private readonly int _methodToken;
private readonly int _methodVersion;
private readonly uint _startOffset;
private readonly uint _endOffsetExclusive;
private MethodContextReuseConstraints(int methodToken, int methodVersion, uint startOffset, uint endOffsetExclusive)
{
Debug.Assert(MetadataTokens.Handle(methodToken).Kind == HandleKind.MethodDefinition);
Debug.Assert(methodVersion >= 1);
Debug.Assert(startOffset <= endOffsetExclusive);
_methodToken = methodToken;
_methodVersion = methodVersion;
_startOffset = startOffset;
_endOffsetExclusive = endOffsetExclusive;
}
public bool AreSatisfied(int methodToken, int methodVersion, int ilOffset)
{
return methodToken == _methodToken &&
methodVersion == _methodVersion &&
ilOffset >= _startOffset &&
ilOffset < _endOffsetExclusive;
}
internal static MethodContextReuseConstraints CreateTestInstance(int methodToken, int methodVersion, uint startOffset, uint endOffsetExclusive)
{
return new MethodContextReuseConstraints(methodToken, methodVersion, startOffset, endOffsetExclusive);
}
internal bool HasExpectedSpan(uint startOffset, uint endOffsetExclusive)
{
return _startOffset == startOffset && _endOffsetExclusive == endOffsetExclusive;
}
public override string ToString()
{
return $"0x{_methodToken:x8}v{_methodVersion} [{_startOffset}, {_endOffsetExclusive})";
}
public class Builder
{
private readonly int _methodToken;
private readonly int _methodVersion;
private readonly int _ilOffset;
private readonly bool _areRangesEndInclusive;
private uint _startOffset;
private uint _endOffsetExclusive;
public Builder(int methodToken, int methodVersion, int ilOffset, bool areRangesEndInclusive)
{
Debug.Assert(MetadataTokens.Handle(methodToken).Kind == HandleKind.MethodDefinition);
Debug.Assert(methodVersion >= 1);
Debug.Assert(ilOffset >= 0);
_methodToken = methodToken;
_methodVersion = methodVersion;
_ilOffset = ilOffset;
_areRangesEndInclusive = areRangesEndInclusive;
_startOffset = 0;
_endOffsetExclusive = uint.MaxValue;
}
public Builder(MethodContextReuseConstraints existingConstraints, int ilOffset, bool areRangesEndInclusive)
{
_methodToken = existingConstraints._methodToken;
_methodVersion = existingConstraints._methodVersion;
_ilOffset = ilOffset;
_areRangesEndInclusive = areRangesEndInclusive;
_startOffset = existingConstraints._startOffset;
_endOffsetExclusive = existingConstraints._endOffsetExclusive;
}
public void AddRange(uint startOffset, uint endOffset)
{
Debug.Assert(startOffset >= 0);
Debug.Assert(startOffset <= endOffset);
Debug.Assert(!_areRangesEndInclusive || endOffset < int.MaxValue);
uint endOffsetExclusive = _areRangesEndInclusive ? (endOffset + 1) : endOffset;
if (_ilOffset < startOffset)
{
_endOffsetExclusive = Math.Min(_endOffsetExclusive, startOffset);
}
else if (_ilOffset >= endOffsetExclusive)
{
_startOffset = Math.Max(_startOffset, endOffsetExclusive);
}
else
{
_startOffset = Math.Max(_startOffset, startOffset);
_endOffsetExclusive = Math.Min(_endOffsetExclusive, endOffsetExclusive);
}
}
public MethodContextReuseConstraints Build()
{
return new MethodContextReuseConstraints(
_methodToken,
_methodVersion,
_startOffset,
_endOffsetExclusive);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal sealed class MethodScope : IEquatable<MethodScope>
{
internal readonly int MethodToken;
internal readonly int MethodVersion;
internal readonly int StartOffset;
internal readonly int EndOffset;
internal MethodScope(int methodToken, int methodVersion, int startOffset, int endOffset)
{
Debug.Assert(MetadataTokens.Handle(methodToken).Kind == HandleKind.MethodDefinition);
Debug.Assert((startOffset >= 0) == (endOffset >= 0));
this.MethodToken = methodToken;
this.MethodVersion = methodVersion;
this.StartOffset = startOffset;
this.EndOffset = endOffset;
}
public bool Equals(MethodScope other)
{
if (other == null)
{
return false;
}
return (this.MethodToken == other.MethodToken) &&
(this.MethodVersion == other.MethodVersion) &&
(this.StartOffset == other.StartOffset) &&
(this.EndOffset == other.EndOffset);
}
public override bool Equals(object obj)
{
return Equals(obj as MethodScope);
}
public override int GetHashCode()
{
return Hash.Combine(this.MethodToken, Hash.Combine(this.MethodVersion, Hash.Combine(this.StartOffset, this.EndOffset)));
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal struct HoistedLocalScopeRecord
{
public readonly uint StartOffset;
public readonly uint Length;
private HoistedLocalScopeRecord(uint startOffset, uint length)
{
this.StartOffset = startOffset;
this.Length = length;
}
public static HoistedLocalScopeRecord FromNative(int startOffset, int endOffsetInclusive)
{
return new HoistedLocalScopeRecord((uint)startOffset, (uint)(endOffsetInclusive - startOffset + 1));
}
public static HoistedLocalScopeRecord FromPortable(uint startOffset, uint length)
{
return new HoistedLocalScopeRecord(startOffset, length);
}
}
}
......@@ -7,22 +7,68 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal struct MethodDebugInfo
{
public readonly ImmutableArray<HoistedLocalScopeRecord> HoistedLocalScopeRecords;
public readonly ImmutableArray<ImmutableArray<ImportRecord>> ImportRecordGroups;
public readonly ImmutableArray<ExternAliasRecord> ExternAliasRecords;
public readonly string DefaultNamespaceName;
public readonly ImmutableArray<ExternAliasRecord> ExternAliasRecords; // C# only.
public readonly ImmutableDictionary<int, ImmutableArray<bool>> DynamicLocalMap; // C# only.
public readonly ImmutableDictionary<string, ImmutableArray<bool>> DynamicLocalConstantMap; // C# only.
public readonly string DefaultNamespaceName; // VB only.
public MethodDebugInfo(
ImmutableArray<HoistedLocalScopeRecord> hoistedLocalScopeRecords,
ImmutableArray<ImmutableArray<ImportRecord>> importRecordGroups,
ImmutableArray<ExternAliasRecord> externAliasRecords,
ImmutableDictionary<int, ImmutableArray<bool>> dynamicLocalMap,
ImmutableDictionary<string, ImmutableArray<bool>> dynamicLocalConstantMap,
string defaultNamespaceName)
{
Debug.Assert(!importRecordGroups.IsDefault);
Debug.Assert(!externAliasRecords.IsDefault);
Debug.Assert(defaultNamespaceName != null);
Debug.Assert(!hoistedLocalScopeRecords.IsDefault);
HoistedLocalScopeRecords = hoistedLocalScopeRecords;
ImportRecordGroups = importRecordGroups;
ExternAliasRecords = externAliasRecords;
DynamicLocalMap = dynamicLocalMap;
DynamicLocalConstantMap = dynamicLocalConstantMap;
DefaultNamespaceName = defaultNamespaceName;
}
public ImmutableSortedSet<int> GetInScopeHoistedLocalIndices(int ilOffset, ref MethodContextReuseConstraints methodContextReuseConstraints)
{
if (this.HoistedLocalScopeRecords.IsDefault)
{
return ImmutableSortedSet<int>.Empty;
}
var constraintsBuilder =
new MethodContextReuseConstraints.Builder(methodContextReuseConstraints, ilOffset, areRangesEndInclusive: false);
var scopesBuilder = ArrayBuilder<int>.GetInstance();
int i = 0;
foreach (var record in this.HoistedLocalScopeRecords)
{
constraintsBuilder.AddRange(record.StartOffset, record.StartOffset + record.Length);
var delta = ilOffset - record.StartOffset;
if (0 <= delta && delta < record.Length)
{
scopesBuilder.Add(i);
}
i++;
}
methodContextReuseConstraints = constraintsBuilder.Build();
var result = scopesBuilder.ToImmutableSortedSet();
scopesBuilder.Free();
return result;
}
}
}
......@@ -6,29 +6,48 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal static class PdbHelpers
{
/// <remarks>
/// Test helper.
/// </remarks>
internal static void GetAllScopes(this ISymUnmanagedMethod method, ArrayBuilder<ISymUnmanagedScope> builder)
{
GetAllScopes(method, builder, offset: -1, isScopeEndInclusive: false);
var unused = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
GetAllScopes(method, builder, unused, offset: -1, isScopeEndInclusive: false);
unused.Free();
}
internal static void GetAllScopes(this ISymUnmanagedMethod method, ArrayBuilder<ISymUnmanagedScope> builder, int offset, bool isScopeEndInclusive)
internal static void GetAllScopes(
this ISymUnmanagedMethod method,
ArrayBuilder<ISymUnmanagedScope> allScopes,
ArrayBuilder<ISymUnmanagedScope> containingScopes,
int offset,
bool isScopeEndInclusive)
{
ISymUnmanagedScope scope = method.GetRootScope();
GetAllScopes(scope, builder, offset, isScopeEndInclusive);
GetAllScopes(method.GetRootScope(), allScopes, containingScopes, offset, isScopeEndInclusive);
}
private static void GetAllScopes(ISymUnmanagedScope scope, ArrayBuilder<ISymUnmanagedScope> builder, int offset, bool isScopeEndInclusive)
private static void GetAllScopes(
ISymUnmanagedScope root,
ArrayBuilder<ISymUnmanagedScope> allScopes,
ArrayBuilder<ISymUnmanagedScope> containingScopes,
int offset,
bool isScopeEndInclusive)
{
builder.Add(scope);
foreach (var nested in scope.GetScopes())
var stack = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
stack.Push(root);
while (stack.Any())
{
if ((offset < 0) || nested.IsInScope(offset, isScopeEndInclusive))
var scope = stack.Pop();
allScopes.Add(scope);
if (offset >= 0 && scope.IsInScope(offset, isScopeEndInclusive))
{
containingScopes.Add(scope);
}
foreach (var nested in scope.GetScopes())
{
GetAllScopes(nested, builder, offset, isScopeEndInclusive);
if (offset >= 0)
{
return;
}
stack.Push(nested);
}
}
}
......
......@@ -29,7 +29,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Friend Const IsLocalScopeEndInclusive = True
Friend ReadOnly MetadataBlocks As ImmutableArray(Of MetadataBlock)
Friend ReadOnly MethodScope As MethodScope
Friend ReadOnly MethodContextReuseConstraints As MethodContextReuseConstraints?
Friend ReadOnly Compilation As VisualBasicCompilation
Private ReadOnly _metadataDecoder As MetadataDecoder
......@@ -40,7 +40,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Private Sub New(
metadataBlocks As ImmutableArray(Of MetadataBlock),
methodScope As MethodScope,
methodContextReuseConstraints As MethodContextReuseConstraints?,
compilation As VisualBasicCompilation,
metadataDecoder As MetadataDecoder,
currentFrame As MethodSymbol,
......@@ -49,7 +49,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
methodDebugInfo As MethodDebugInfo)
Me.MetadataBlocks = metadataBlocks
Me.MethodScope = methodScope
Me.MethodContextReuseConstraints = methodContextReuseConstraints
Me.Compilation = compilation
_metadataDecoder = metadataDecoder
_currentFrame = currentFrame
......@@ -123,17 +123,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Debug.Assert(MetadataTokens.Handle(methodToken).Kind = HandleKind.MethodDefinition)
Dim typedSymReader = DirectCast(symReader, ISymUnmanagedReader)
Dim scopes = ArrayBuilder(Of ISymUnmanagedScope).GetInstance()
typedSymReader.GetScopes(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive, scopes)
Dim scope = scopes.GetMethodScope(methodToken, methodVersion)
' Re-use the previous compilation if possible.
Dim compilation As VisualBasicCompilation
If metadataBlocks.HaveNotChanged(previous) Then
' Re-use entire context if method scope has not changed.
Dim previousContext = previous.EvaluationContext
If (scope IsNot Nothing) AndAlso (previousContext IsNot Nothing) AndAlso scope.Equals(previousContext.MethodScope) Then
If previousContext IsNot Nothing AndAlso
previousContext.MethodContextReuseConstraints.HasValue AndAlso
previousContext.MethodContextReuseConstraints.GetValueOrDefault().AreSatisfied(methodToken, methodVersion, ilOffset) Then
Return previousContext
End If
compilation = previous.Compilation
......@@ -141,18 +138,25 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
compilation = metadataBlocks.ToCompilation()
End If
Dim typedSymReader = DirectCast(symReader, ISymUnmanagedReader)
Dim allScopes = ArrayBuilder(Of ISymUnmanagedScope).GetInstance()
Dim containingScopes = ArrayBuilder(Of ISymUnmanagedScope).GetInstance()
typedSymReader.GetScopes(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive, allScopes, containingScopes)
Dim reuseConstraints = allScopes.GetReuseConstraints(methodToken, methodVersion, ilOffset, IsLocalScopeEndInclusive)
allScopes.Free()
Dim methodHandle = CType(MetadataTokens.Handle(methodToken), MethodDefinitionHandle)
Dim currentFrame = compilation.GetMethod(moduleVersionId, methodHandle)
Debug.Assert(currentFrame IsNot Nothing)
Dim metadataDecoder = New MetadataDecoder(DirectCast(currentFrame.ContainingModule, PEModuleSymbol), currentFrame)
Dim hoistedLocalFieldNames As ImmutableHashSet(Of String) = Nothing
Dim localNames = GetLocalNames(scopes, hoistedLocalFieldNames)
Dim localNames = GetLocalNames(containingScopes, hoistedLocalFieldNames)
Dim localInfo = metadataDecoder.GetLocalInfo(localSignatureToken)
Dim localBuilder = ArrayBuilder(Of LocalSymbol).GetInstance()
GetLocals(localBuilder, currentFrame, localNames, localInfo)
GetStaticLocals(localBuilder, currentFrame, methodHandle, metadataDecoder)
GetConstants(localBuilder, currentFrame, scopes.GetConstantSignatures(), metadataDecoder)
scopes.Free()
GetConstants(localBuilder, currentFrame, containingScopes.GetConstantSignatures(), metadataDecoder)
containingScopes.Free()
Dim locals = localBuilder.ToImmutableAndFree()
Dim methodDebugInfo As MethodDebugInfo
......@@ -167,7 +171,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Return New EvaluationContext(
metadataBlocks,
scope,
reuseConstraints,
compilation,
metadataDecoder,
currentFrame,
......@@ -283,7 +287,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
Dim importRecordGroups = ImmutableArray.Create(projectLevelImportRecords, fileLevelImportRecords)
Return New MethodDebugInfo(importRecordGroups, ImmutableArray(Of ExternAliasRecord).Empty, defaultNamespaceName:="")
Return New MethodDebugInfo(
hoistedLocalScopeRecords:=ImmutableArray(Of HoistedLocalScopeRecord).Empty,
importRecordGroups:=importRecordGroups,
defaultNamespaceName:="",
externAliasRecords:=ImmutableArray(Of ExternAliasRecord).Empty,
dynamicLocalMap:=ImmutableDictionary(Of Integer, ImmutableArray(Of Boolean)).Empty,
dynamicLocalConstantMap:=ImmutableDictionary(Of String, ImmutableArray(Of Boolean)).Empty)
End Function
Friend Function CreateCompilationContext(syntax As ExecutableStatementSyntax) As CompilationContext
......
......@@ -2,7 +2,6 @@
Imports System.Collections.Immutable
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.ExpressionEvaluator
Imports Microsoft.VisualStudio.SymReaderInterop
Imports ImportScope = Microsoft.VisualStudio.SymReaderInterop.ImportScope
......@@ -74,7 +73,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
projectLevelImportRecords.ToImmutableAndFree(),
fileLevelImportRecords.ToImmutableAndFree())
Return New MethodDebugInfo(importRecordGroups, ImmutableArray(Of ExternAliasRecord).Empty, defaultNamespaceName)
' TODO (acasey): portable format overload (GH #702)
' Somehow construct hoistedLocalScopeRecords.
Dim hoistedLocalScopeRecords = ImmutableArray(Of HoistedLocalScopeRecord).Empty
Return New MethodDebugInfo(
hoistedLocalScopeRecords,
importRecordGroups,
defaultNamespaceName:=defaultNamespaceName,
externAliasRecords:=ImmutableArray(Of ExternAliasRecord).Empty,
dynamicLocalMap:=ImmutableDictionary(Of Integer, ImmutableArray(Of Boolean)).Empty,
dynamicLocalConstantMap:=ImmutableDictionary(Of String, ImmutableArray(Of Boolean)).Empty)
End Function
' TODO (acasey): portable format overload (GH #702)
......
......@@ -258,14 +258,14 @@ End Class"
Assert.Null(previous)
previous = new VisualBasicMetadataContext(context)
' At end of outer scope.
' At end of outer scope - not reused because of the nested scope.
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, MakeDummyLazyAssemblyReaders(), symReader, moduleVersionId, methodToken, methodVersion, endOffset, localSignatureToken)
Assert.Equal(context, previous.EvaluationContext)
Assert.NotEqual(context, previous.EvaluationContext) ' Not required, just documentary.
' At type context.
context = EvaluationContext.CreateTypeContext(previous, methodBlocks, moduleVersionId, typeToken)
Assert.NotEqual(context, previous.EvaluationContext)
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope)
Assert.Null(context.MethodContextReuseConstraints)
Assert.Equal(context.Compilation, previous.Compilation)
' Step through entire method.
......@@ -273,14 +273,19 @@ End Class"
previous = new VisualBasicMetadataContext(context)
For offset = startOffset To endOffset - 1
Dim scope = scopes.GetInnermostScope(offset)
Dim constraints = previous.EvaluationContext.MethodContextReuseConstraints
If constraints.HasValue Then
Assert.Equal(scope Is previousScope, constraints.GetValueOrDefault().AreSatisfied(methodToken, methodVersion, offset))
End If
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, MakeDummyLazyAssemblyReaders(), symReader, moduleVersionId, methodToken, methodVersion, offset, localSignatureToken)
If scope Is previousScope Then
Assert.Equal(context, previous.EvaluationContext)
Else
' Different scope. Should reuse compilation.
Assert.NotEqual(context, previous.EvaluationContext)
If previous IsNot Nothing Then
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope)
If previous.EvaluationContext IsNot Nothing Then
Assert.NotEqual(context.MethodContextReuseConstraints, previous.EvaluationContext.MethodContextReuseConstraints)
Assert.Equal(context.Compilation, previous.Compilation)
End If
End If
......@@ -300,9 +305,9 @@ End Class"
GetContextState(runtime, "C.F", methodBlocks, moduleVersionId, symReader, methodToken, localSignatureToken)
' Different references. No reuse.
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, MakeDummyLazyAssemblyReaders(), symReader, moduleVersionId, methodToken, methodVersion, endOffset, localSignatureToken)
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, MakeDummyLazyAssemblyReaders(), symReader, moduleVersionId, methodToken, methodVersion, endOffset - 1, localSignatureToken)
Assert.NotEqual(context, previous.EvaluationContext)
Assert.Equal(context.MethodScope, previous.EvaluationContext.MethodScope)
Assert.True(previous.EvaluationContext.MethodContextReuseConstraints.Value.AreSatisfied(methodToken, methodVersion, endOffset - 1))
Assert.NotEqual(context.Compilation, previous.Compilation)
previous = new VisualBasicMetadataContext(context)
......@@ -310,7 +315,7 @@ End Class"
GetContextState(runtime, "C.G", methodBlocks, moduleVersionId, symReader, methodToken, localSignatureToken)
context = EvaluationContext.CreateMethodContext(previous, methodBlocks, MakeDummyLazyAssemblyReaders(), symReader, moduleVersionId, methodToken, methodVersion, ilOffset:=0, localSignatureToken:=localSignatureToken)
Assert.NotEqual(context, previous.EvaluationContext)
Assert.NotEqual(context.MethodScope, previous.EvaluationContext.MethodScope)
Assert.False(previous.EvaluationContext.MethodContextReuseConstraints.Value.AreSatisfied(methodToken, methodVersion, 0))
Assert.Equal(context.Compilation, previous.Compilation)
' No EvaluationContext. Should reuse Compilation
......
......@@ -1366,7 +1366,7 @@ End Class
<WorkItem(1134746, "DevDiv")>
<Fact>
Public Sub Caching()
Public Sub CacheInvalidation()
Const source = "
Imports System.Collections.Generic
......
......@@ -464,34 +464,6 @@ public static ImmutableArray<string> GetVisualBasicImportStrings(this ISymUnmana
return importStrings;
}
// TODO (acasey): caller should depend on abstraction (GH #702)
public static ImmutableSortedSet<int> GetCSharpInScopeHoistedLocalIndices(byte[] customDebugInfo, int methodToken, int methodVersion, int ilOffset)
{
var record = TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.StateMachineHoistedLocalScopes);
if (record.IsDefault)
{
return ImmutableSortedSet<int>.Empty;
}
var scopes = DecodeStateMachineHoistedLocalScopesRecord(record);
ArrayBuilder<int> builder = ArrayBuilder<int>.GetInstance();
for (int i = 0; i < scopes.Length; i++)
{
StateMachineHoistedLocalScope scope = scopes[i];
// NB: scopes are end-inclusive.
if (ilOffset >= scope.StartOffset && ilOffset <= scope.EndOffset)
{
builder.Add(i);
}
}
ImmutableSortedSet<int> result = builder.ToImmutableSortedSet();
builder.Free();
return result;
}
// TODO (acasey): caller should depend on abstraction (GH #702)
/// <exception cref="InvalidOperationException">Bad data.</exception>
public static void GetCSharpDynamicLocalInfo(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册