From 5b08d9b30564c305ab333a530123da07205e587e Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Fri, 6 Mar 2015 17:59:52 -0800 Subject: [PATCH] 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. --- .../ExpressionCompiler/EvaluationContext.cs | 59 ++++----- .../SymUnmanagedReaderExtensions.cs | 31 ++++- .../CSharpExpressionCompilerTest.csproj | 1 + .../ExpressionCompilerTests.cs | 20 ++- .../HoistedStateMachineLocalTests.cs | 2 +- .../MethodContextReuseConstraintsTests.cs | 102 +++++++++++++++ .../ExpressionCompiler.csproj | 3 +- .../ExpressionCompiler/MetadataUtilities.cs | 21 ++- .../MethodContextReuseConstraints.cs | 121 ++++++++++++++++++ .../Source/ExpressionCompiler/MethodScope.cs | 51 -------- .../PDB/HoisedLocalScopeRecord.cs | 29 +++++ .../ExpressionCompiler/PDB/MethodDebugInfo.cs | 50 +++++++- .../ExpressionCompiler/PDB/PdbHelpers.cs | 45 +++++-- .../ExpressionCompiler/EvaluationContext.vb | 38 ++++-- .../SymUnmanagedReaderExtensions.vb | 13 +- .../ExpressionCompilerTests.vb | 21 +-- .../HoistedStateMachineLocalTests.vb | 2 +- .../Shared/CustomDebugInfoReader.cs | 28 ---- 18 files changed, 464 insertions(+), 173 deletions(-) create mode 100644 src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MethodContextReuseConstraintsTests.cs create mode 100644 src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodContextReuseConstraints.cs delete mode 100644 src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodScope.cs create mode 100644 src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/HoisedLocalScopeRecord.cs diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs index 4d278cdcb1c..c197a710f6c 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EvaluationContext.cs @@ -26,7 +26,7 @@ internal sealed class EvaluationContext : EvaluationContextBase internal const bool IsLocalScopeEndInclusive = false; internal readonly ImmutableArray 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 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.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.GetInstance(); + var containingScopes = ArrayBuilder.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>.Empty; - var dynamicLocalConstantMap = ImmutableDictionary>.Empty; var inScopeHoistedLocalIndices = ImmutableSortedSet.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.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 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 dynamicFlags; - if (dynamicLocalConstantMap.TryGetValue(constant.Name, out dynamicFlags)) + if (dynamicLocalConstantMap != null && dynamicLocalConstantMap.TryGetValue(constant.Name, out dynamicFlags)) { type = DynamicTypeDecoder.TransformTypeWithoutCustomModifierFlags( type, diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.cs index 2fd13305aa7..ae84620f5c3 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.cs @@ -14,7 +14,8 @@ internal static class SymUnmanagedReaderExtensions public static MethodDebugInfo GetMethodDebugInfo( this ISymUnmanagedReader reader, int methodToken, - int methodVersion) + int methodVersion, + string firstLocalName) { ImmutableArray externAliasStrings; var importStringGroups = reader.GetCSharpGroupedImportStrings(methodToken, methodVersion, out externAliasStrings); @@ -72,9 +73,37 @@ internal static class SymUnmanagedReaderExtensions externAliasRecordBuilder.Add(new NativeExternAliasRecord(alias, targetIdentity)); } + var hoistedLocalScopeRecords = ImmutableArray.Empty; + var dynamicLocalMap = ImmutableDictionary>.Empty; + var dynamicLocalConstantMap = ImmutableDictionary>.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#. } diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj index b2e819c5214..f9c8466ac9c 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/CSharpExpressionCompilerTest.csproj @@ -106,6 +106,7 @@ + diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs index 7b1a89bcf72..a72a5afeb1d 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/ExpressionCompilerTests.cs @@ -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 diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/HoistedStateMachineLocalTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/HoistedStateMachineLocalTests.cs index 62474bbef86..5e4e71b7a28 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/HoistedStateMachineLocalTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/HoistedStateMachineLocalTests.cs @@ -1291,7 +1291,7 @@ .maxstack 1 [WorkItem(1134746, "DevDiv")] [Fact] - public void Caching() + public void CacheInvalidation() { var source = @" using System.Collections.Generic; diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MethodContextReuseConstraintsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MethodContextReuseConstraintsTests.cs new file mode 100644 index 00000000000..b657062365b --- /dev/null +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/MethodContextReuseConstraintsTests.cs @@ -0,0 +1,102 @@ +// 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 diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.csproj b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.csproj index 14264596282..d43054062bd 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.csproj +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/ExpressionCompiler.csproj @@ -39,6 +39,7 @@ + @@ -59,7 +60,7 @@ - + diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs index 5e29b8b0007..34e0538c6e4 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MetadataUtilities.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. /// - internal static void GetScopes(this ISymUnmanagedReader symReader, int methodToken, int methodVersion, int ilOffset, bool isScopeEndInclusive, ArrayBuilder scopes) + internal static void GetScopes( + this ISymUnmanagedReader symReader, + int methodToken, + int methodVersion, + int ilOffset, + bool isScopeEndInclusive, + ArrayBuilder allScopes, + ArrayBuilder 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 scopes, int methodToken, int methodVersion) + internal static MethodContextReuseConstraints GetReuseConstraints(this ArrayBuilder 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 GetLocalNames(this ArrayBuilder scopes) diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodContextReuseConstraints.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodContextReuseConstraints.cs new file mode 100644 index 00000000000..dd211fbdd3e --- /dev/null +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodContextReuseConstraints.cs @@ -0,0 +1,121 @@ +// 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); + } + } + } +} diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodScope.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodScope.cs deleted file mode 100644 index 28e1423e46b..00000000000 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/MethodScope.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 - { - 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))); - } - } -} diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/HoisedLocalScopeRecord.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/HoisedLocalScopeRecord.cs new file mode 100644 index 00000000000..71633ab734e --- /dev/null +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/HoisedLocalScopeRecord.cs @@ -0,0 +1,29 @@ +// 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); + } + } +} diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.cs index ee48e8a8bd4..a8480cdab7a 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.cs @@ -7,22 +7,68 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator { internal struct MethodDebugInfo { + public readonly ImmutableArray HoistedLocalScopeRecords; public readonly ImmutableArray> ImportRecordGroups; - public readonly ImmutableArray ExternAliasRecords; - public readonly string DefaultNamespaceName; + + public readonly ImmutableArray ExternAliasRecords; // C# only. + public readonly ImmutableDictionary> DynamicLocalMap; // C# only. + public readonly ImmutableDictionary> DynamicLocalConstantMap; // C# only. + + public readonly string DefaultNamespaceName; // VB only. public MethodDebugInfo( + ImmutableArray hoistedLocalScopeRecords, ImmutableArray> importRecordGroups, ImmutableArray externAliasRecords, + ImmutableDictionary> dynamicLocalMap, + ImmutableDictionary> 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 GetInScopeHoistedLocalIndices(int ilOffset, ref MethodContextReuseConstraints methodContextReuseConstraints) + { + if (this.HoistedLocalScopeRecords.IsDefault) + { + return ImmutableSortedSet.Empty; + } + + var constraintsBuilder = + new MethodContextReuseConstraints.Builder(methodContextReuseConstraints, ilOffset, areRangesEndInclusive: false); + + var scopesBuilder = ArrayBuilder.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; + } } } diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/PdbHelpers.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/PdbHelpers.cs index 657413f9ab8..be68f56de34 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/PdbHelpers.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/PdbHelpers.cs @@ -6,29 +6,48 @@ namespace Microsoft.CodeAnalysis.ExpressionEvaluator { internal static class PdbHelpers { + /// + /// Test helper. + /// internal static void GetAllScopes(this ISymUnmanagedMethod method, ArrayBuilder builder) { - GetAllScopes(method, builder, offset: -1, isScopeEndInclusive: false); + var unused = ArrayBuilder.GetInstance(); + GetAllScopes(method, builder, unused, offset: -1, isScopeEndInclusive: false); + unused.Free(); } - internal static void GetAllScopes(this ISymUnmanagedMethod method, ArrayBuilder builder, int offset, bool isScopeEndInclusive) + internal static void GetAllScopes( + this ISymUnmanagedMethod method, + ArrayBuilder allScopes, + ArrayBuilder 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 builder, int offset, bool isScopeEndInclusive) + private static void GetAllScopes( + ISymUnmanagedScope root, + ArrayBuilder allScopes, + ArrayBuilder containingScopes, + int offset, + bool isScopeEndInclusive) { - builder.Add(scope); - foreach (var nested in scope.GetScopes()) + var stack = ArrayBuilder.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); } } } diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb index 7ba5f7bc991..df752ac8b4e 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EvaluationContext.vb @@ -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 diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.vb index 7fb3c4b605e..d016f9421eb 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/SymUnmanagedReaderExtensions.vb @@ -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) diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb index 89a58ac8c2a..72d3a30a8e5 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/ExpressionCompilerTests.vb @@ -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 diff --git a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/HoistedStateMachineLocalTests.vb b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/HoistedStateMachineLocalTests.vb index 7c9e11b7e4a..7b80f8e1fbb 100644 --- a/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/HoistedStateMachineLocalTests.vb +++ b/src/ExpressionEvaluator/VisualBasic/Test/ExpressionCompiler/HoistedStateMachineLocalTests.vb @@ -1366,7 +1366,7 @@ End Class - Public Sub Caching() + Public Sub CacheInvalidation() Const source = " Imports System.Collections.Generic diff --git a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs index 8a2b11a9e82..9f1c2d19295 100644 --- a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs +++ b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs @@ -464,34 +464,6 @@ public static ImmutableArray GetVisualBasicImportStrings(this ISymUnmana return importStrings; } - // TODO (acasey): caller should depend on abstraction (GH #702) - public static ImmutableSortedSet GetCSharpInScopeHoistedLocalIndices(byte[] customDebugInfo, int methodToken, int methodVersion, int ilOffset) - { - var record = TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.StateMachineHoistedLocalScopes); - if (record.IsDefault) - { - return ImmutableSortedSet.Empty; - } - - var scopes = DecodeStateMachineHoistedLocalScopesRecord(record); - - ArrayBuilder builder = ArrayBuilder.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 result = builder.ToImmutableSortedSet(); - builder.Free(); - return result; - } - // TODO (acasey): caller should depend on abstraction (GH #702) /// Bad data. public static void GetCSharpDynamicLocalInfo( -- GitLab