Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Localsinit #24723

Merged
merged 9 commits into from
Feb 22, 2018
Merged

Localsinit #24723

merged 9 commits into from
Feb 22, 2018

Conversation

t-camaia
Copy link
Contributor

@t-camaia t-camaia commented Feb 9, 2018

SkipLocalsInitAttribute can be used on methods to avoid locals initialization.

While decoding Well Known Attributes, SourceMemberMethodSymbol will clear the localsAreZeroed flag if SkipLocalsInitAttribute is found. This flag will be passed later to the corresponding ILBuilder and MethodBody.

The current ILVisualizer completely ignores the localsAreZeroed flag and always initializes locals (see dotnet/metadata-tools#11 and dotnet/metadata-tools#12). The tests will certainly fail until the changes are effected.

Some test scenarios were added in order to assure the expected behavior.

@t-camaia t-camaia requested a review from a team as a code owner February 9, 2018 02:42
@t-camaia t-camaia requested review from agocke and VSadov February 9, 2018 02:42
@@ -102,7 +105,7 @@ private static ImmutableArray<Cci.SequencePoint> GetSequencePoints(SequencePoint

ImmutableArray<Cci.ExceptionHandlerRegion> Cci.IMethodBody.ExceptionRegions => _exceptionHandlers;

bool Cci.IMethodBody.LocalsAreZeroed => true;
bool Cci.IMethodBody.LocalsAreZeroed => _localsAreZeroed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this may vary, it should be another key in the method body cache. Just having same IL is not enough to say that methods may share the body.
Look at SerializeMethodBody

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder how this could be tested. We need to make sure the caching is not interfering.

Perhaps we can use code that relies on the zero init happening or not happening (probably using stackalloc or "out" parameters in VB), then we may just run bunch of similar methods that only differ in this bit and see if results are as expected?


In reply to: 167321124 [](ancestors = 167321124)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, but I haven't tested to see whether it's likely that you get non-zero memory when running stackalloc. Our tests would have a dependency on having non-zero random memory, wouldn't they?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was talking to @agocke about this. We use a body from the cache if we can guarantee that localSignatureHandleOpt.IsNil returns true. localSignatureHandleOpt is obtained from SerializeLocalVariablesSignature, which seems to return a default object only if there are no locals. Maybe adding another check is not necessary. I still don't know how to test it, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AreLocalsZeroed bool is now being used during IL mapping in cache. A test was added to check if method bodies that differ only by SkipLocalsInitAttribute have different Relative Virtual Addresses. The test was failing before the changes in the cache mapping and it's working now.

@@ -358,6 +361,14 @@ public override Symbol AssociatedSymbol
}
}

public bool LocalsAreZeroed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be virtual on MethodSymbol and just return "true" in the default implementation. Then you do not need to type-check in MethodCompiler.
You will need this anyways when dealing with synthesized members.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the name: AreLocalsZeroed. The rationale here is that we should try to prefix boolean member names with a true / false word: skip, is, are, etc ....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All occurrences of LocalsAreZeroed were changed to AreLocalsZeroed.

@VSadov
Copy link
Member

VSadov commented Feb 9, 2018

Done with reviewing iteration 3.
I think we need to make sure method body caching is not interfering with the bit.
Also we need to have some E2E testing where we actually run code that depends on this bit. I think it should be possible.

@@ -472,7 +472,7 @@ private void AddReferencedCompilations(IEnumerable<Compilation> referencedCompil

#region IL Verification

internal abstract string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary<int, string> markers);
internal abstract string VisualizeRealIL(IModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary<int, string> markers, Cci.IMethodBody methodBody);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing the method body, why not just pass the boolean for LocalsAreZeroed? Do we need the method body for anything else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a point.

@@ -102,7 +105,7 @@ private static ImmutableArray<Cci.SequencePoint> GetSequencePoints(SequencePoint

ImmutableArray<Cci.ExceptionHandlerRegion> Cci.IMethodBody.ExceptionRegions => _exceptionHandlers;

bool Cci.IMethodBody.LocalsAreZeroed => true;
bool Cci.IMethodBody.LocalsAreZeroed => _localsAreZeroed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point, but I haven't tested to see whether it's likely that you get non-zero memory when running stackalloc. Our tests would have a dependency on having non-zero random memory, wouldn't they?

@@ -166,6 +166,8 @@ public void EnsureMetadataVirtual()

private OverriddenOrHiddenMembersResult _lazyOverriddenOrHiddenMembers;

protected bool _localsAreZeroed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protected members should not begin with an _. Really protected members should be Pascal cased but somehow other members here are camelCased. Lots of bad examples.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I'll set it to private. I just realized there's no benefit in it being protected.

@@ -358,6 +361,14 @@ public override Symbol AssociatedSymbol
}
}

public bool LocalsAreZeroed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the name: AreLocalsZeroed. The rationale here is that we should try to prefix boolean member names with a true / false word: skip, is, are, etc ....

@@ -1128,6 +1139,10 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut
arguments.Diagnostics.Add(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, arguments.AttributeSyntaxOpt.Location, arguments.AttributeSyntaxOpt.GetErrorDisplayName());
}
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.SkipLocalsInitAttribute))
{
_localsAreZeroed &= false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not set this directly to false here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a point. I don't know why I did that.

@@ -1128,6 +1139,10 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut
arguments.Diagnostics.Add(ErrorCode.ERR_SecurityCriticalOrSecuritySafeCriticalOnAsync, arguments.AttributeSyntaxOpt.Location, arguments.AttributeSyntaxOpt.GetErrorDisplayName());
}
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.SkipLocalsInitAttribute))
{
_localsAreZeroed &= false;
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_localsAreZeroed &= false; [](start = 16, length = 26)

It feels like we should follow the pattern for other well-known attributes and keep this information in attribute data. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made some changes. I followed the pattern from other simple boolean attributes like DynamicSecurityMethodAttribute and SuppressUnmanagedCodeSecurityAttribute.

{
get
{
return _localsAreZeroed;
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_localsAreZeroed [](start = 23, length = 16)

Why is it safe to return this value? Are we certain attributes have been bound and processed? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -1346,7 +1346,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
var localSlotManager = new LocalSlotManager(variableSlotAllocatorOpt);
var optimizations = compilation.Options.OptimizationLevel;

ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations);
ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations, (method as SourceMemberMethodSymbol)?.LocalsAreZeroed ?? true);
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(method as SourceMemberMethodSymbol)?.LocalsAreZeroed ?? true [](start = 94, length = 61)

It looks like this is not going to cover lambdas and local functions. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our main objective right now is making sure that the attribute works on the method where it's directly applied. In future PRs we'll propagate it to nested lambdas and local functions.

}

public bool LocalsAreZeroed => _localsAreZeroed;
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public bool LocalsAreZeroed => _localsAreZeroed; [](start = 8, length = 48)

It looks like it is not necessary to have this property on ILBuilder or have it inside ILBuilder because ILBuilder itself doen't take any advantage of it. #Closed

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind, it looks like ILBuilderToString is using this property.


In reply to: 167670235 [](ancestors = 167670235)

@@ -959,7 +959,7 @@ internal override string VisualizeRealIL(IModuleSymbol peModule, CompilationTest
/// - winmd
/// - global methods
/// </remarks>
internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary<int, string> markers)
internal unsafe static string VisualizeRealIL(PEModuleSymbol peModule, CompilationTestData.MethodData methodData, IReadOnlyDictionary<int, string> markers, Cci.IMethodBody methodBody)
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cci.IMethodBody methodBody [](start = 164, length = 26)

It feels like an overkill to require all consumers to pass an instance of Cci.IMethodBody just so we can get LocalsAreZeroed value. If this is all we need, we should simply have localsAreZeroed as a parameter. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

@@ -1507,7 +1507,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
optimizations = OptimizationLevel.Release
End If

Dim builder As ILBuilder = New ILBuilder(moduleBuilder, localSlotManager, optimizations)
Dim builder As ILBuilder = New ILBuilder(moduleBuilder, localSlotManager, optimizations, localsAreZeroed:=True)
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localsAreZeroed:=True) [](start = 101, length = 22)

Are we planning to add recognition of the attribute to VB as well? #Pending

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only C#, as far as I know.

/// <summary>
/// Returns true if locals are to be initialized
/// </summary>
public virtual bool LocalsAreZeroed
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public virtual bool LocalsAreZeroed [](start = 8, length = 35)

Adding virtual properties at the root is bug prone. We leant to always add abstract properties and have each derived type implement it and then have targeted tests to cover each implementation. I think you'd be better off with the cast in MethodCompiler, otherwise you will need to add and test implementations for retargeted methods, PE methods, etc. Does that worth it? #Closed

Copy link
Contributor

@AlekseyTs AlekseyTs Feb 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we can explicitly throw for all symbols for which we don't expect to call this API. Again this requires this method to have no default implementation. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we keep the virtual property and throw if the default implementation is called? Then only the method in the derived class could be used. I feel like it has the same result with less effort.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with having the property be virtual is future derivations of MethodSymbol. A year from now if a new derivation of MethodSymbol is added the developer may or may not know to re-evaluate the behavior of LocalsAreZeroed in the context of the type. Whether they do or don't comes down to the dillegence of the developer, and code reviewers, involved in the change. By making it abstract though we force the developer to think about this property and make a decision on its value.

Copy link
Contributor

@AlekseyTs AlekseyTs Feb 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, it is an option to throw from default implementation in this particular situation (for non public API used in very specific place and time in the pipeline), then, assuming that we have adequate test coverage, tests should catch derived classes that fail to override the method. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end I set it to abstract. We're returning true in some derived classes for now (mostly because of synthesized members).

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Feb 12, 2018

Done with review pass (iteration 4). #Closed

@@ -163,6 +163,11 @@ public override DllImportData GetDllImportData()
return null;
}

public override bool AreLocalsZeroed
{
get { throw ExceptionUtilities.Unreachable; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not generate any code for this symbol?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's only used in the semantic model, so I suspect not.

@@ -340,6 +340,8 @@ internal override TypeSymbol IteratorElementType

public override DllImportData GetDllImportData() => null;

public override bool AreLocalsZeroed => throw ExceptionUtilities.Unreachable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of these can be replaced with a single override that returns true in SourceMethodSymbol

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just talked with @AlekseyTs and he thinks we should be explicit in each callsite. I think that's a fine decision as well and we can go with that.

However, I would be very surprised if this didn't throw whenever we try to emit local functions, so I think we'll have to provide a real implementation, even if it's accompanied by a prototype comment saying that the full spec will be implemented in another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To answer my own question, by the time we end up at creating a MethodBody, LocalFunctions and similar are all turned into SynthesizedInstance or similar symbols, so throwing is actually the right thing to do here, I think.

@@ -163,6 +163,11 @@ public override DllImportData GetDllImportData()
return null;
}

public override bool AreLocalsZeroed
{
get { throw ExceptionUtilities.Unreachable; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's only used in the semantic model, so I suspect not.


Assert.NotEqual(methodInit.RelativeVirtualAddress, methodSkip.RelativeVirtualAddress);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should also have a few negative tests, like putting the attribute on the class or on the assembly and checking to make sure that this doesn't cause locals init to be emitted, at least as the code is now. Eventually, when you make that work, those tests will fail and we can fix them up to assert the right thing.

@t-camaia t-camaia requested a review from a team as a code owner February 21, 2018 22:11
@@ -2949,7 +2949,7 @@ private int SerializeMethodBody(MethodBodyStreamEncoder encoder, IMethodBody met
// Don't do small body method caching during deterministic builds until this issue is fixed
// https://github.com/dotnet/roslyn/issues/7595
int bodyOffset;
if (!_deterministic && isSmallBody && _smallMethodBodies.TryGetValue(methodBody.IL, out bodyOffset))
if (!_deterministic && isSmallBody && _smallMethodBodies.TryGetValue((methodBody.IL, methodBody.AreLocalsZeroed), out bodyOffset))
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(methodBody.IL, methodBody.AreLocalsZeroed) [](start = 81, length = 43)

Consider extracting this tuple into a local and reusing. #Closed

@@ -4177,5 +4177,20 @@ protected override void AddItem(T item, int index)
_structuralIndex.Add(item, index);
}
}

private class ByteSequenceBoolTupleComparer : IEqualityComparer<(ImmutableArray<byte>, bool)>
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ByteSequenceBoolTupleComparer [](start = 22, length = 29)

Consider adding a private parameter-less constructor #Closed

public class C
{
[System.Runtime.CompilerServices.SkipLocalsInitAttribute]
public unsafe void M_skip()
Copy link
Contributor

@AlekseyTs AlekseyTs Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public unsafe void M_skip() [](start = 4, length = 27)

Consider adding another copy of each method and assert that methods with attribute have their body unified and methods without an attribute have their body unified. Also, consider adding two more methods with slightly different body and one of them with attribute, verify that their bodies are not unified with any other method. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Feb 21, 2018

Done with review pass (iteration 8) #Closed

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (iteration 9)

@t-camaia t-camaia merged commit ad944e6 into dotnet:features/localsinit Feb 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants