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

Intrinsics analyzer and fixes #85481

Merged
merged 21 commits into from
May 16, 2023

Conversation

davidwrighton
Copy link
Member

As a follow up to #85275, implement an analyzer and an attribute the Crossgen2 will respect which will make it difficult to encounter unexpected behavior at runtime due to quirks of how we compile System.Private.CoreLib.dll with Crossgen2.

See the change to vectors-and-intrinsics.md for details of what the new system will check.

NOTE: I believe that functionally this change is complete. I need to check crossgen2 performance, and the name of the new attribute is terrible. I'm looking for better names.

This analyzer detects the use of all platform intrinsics and checks to
ensure that they are all used either protected by an if statement OR
ternary operator which checks an appropriate IsSupported flag, or that
the intrinsic is used within a method where the behavior of platform
support for the intrinsic is not allowed to vary between compile time
and runtime.

The supported conditional checks are

1. Simple if statement checking IsSupported flag surrounding usage
```
if (PlatformIntrinsicType.IsSupported)
{
    PlatformIntrinsicType.IntrinsicMethod();
}
```

2. If statement check checking a platform intrinsic type which implies
that the intrinsic used is supported.

```
if (Avx2.X64.IsSupported)
{
    Avx2.IntrinsicMethod();
}
```

3. Nested if statement where there is an outer condition which is an
OR'd together series of IsSupported checks for mutually exclusive
conditions and where the inner check is an else clause where some checks
are excluded from applying.

```
if (Avx2.IsSupported || ArmBase.IsSupported)
{
    if (Avx2.IsSupported)
    {
        // Do something
    }
    else
    {
        ArmBase.IntrinsicMethod();
    }
}
```

In addtion, this change adds a new attribute, currently named
System.Runtime.BypassReadyToRunForIntrinsicsHelperUse which can be used
to identify methods which are called expecting to use some particular
set of platform intrinsics. These functions do not need to have explicit
checks, and while this is not yet implemented in the change, the
attribute will change the behavior of crossgen2 so that code will only
be generated if the platform intrinsic support is well known to be
either enabled or disabled at runtime. In addition, the method will only
be compiled or be inlineable if the platform intrinsic support is
enabled that matches at least one of the attributes applied to that
method.
Copy link
Member

@jkoritzinsky jkoritzinsky left a comment

Choose a reason for hiding this comment

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

Can we add unit tests for this just to make it easier to iterate on in the future? Since it's in the libraries test tree, it should be pretty easy to add them.

Comment on lines 28 to 29
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
// See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/Localizing%20Analyzers.md for more on localization

docs/design/coreclr/botr/vectors-and-intrinsics.md Outdated Show resolved Hide resolved

ACCEPTABLE Code
```csharp
using System.Runtime.Intrinsics.X86;
Copy link
Member

Choose a reason for hiding this comment

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

I found this example helpful; could we keep it in the crossgen2 section?

Copy link
Member Author

Choose a reason for hiding this comment

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

The issue is that the example is no longer correct. With the new attribute, the "Unacceptable" code is now legal to use, and the analyzer will ensure that you put the attribute on it.

Copy link
Member

Choose a reason for hiding this comment

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

The unacceptable code is still illegal without an attribute if I understand correctly - I found it helpful in understanding the problem that the attribute solves.

InstructionSetInfo vlInstructionSet = null;
foreach (var potentialVLinstructionSet in _instructionSets)
{
if (instructionSet.Architecture != architecture) continue;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (instructionSet.Architecture != architecture) continue;
if (potentialVLinstructionSet.Architecture != architecture) continue;

Copy link
Member

Choose a reason for hiding this comment

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

This check still looks redundant with the check above.

@@ -141,7 +141,7 @@ private void RootMethod(MethodDesc method)

try
{
if (!CorInfoImpl.ShouldSkipCompilation(method))
if (!CorInfoImpl.ShouldSkipCompilation(null, method))
Copy link
Member

Choose a reason for hiding this comment

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

Why don't we pass the InstructionSetSupport everywhere this is called?

Comment on lines 812 to 814
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(Sse))]
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(AdvSimd))]
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(PackedSimd))]
Copy link
Member

Choose a reason for hiding this comment

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

Nit:

Suggested change
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(Sse))]
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(AdvSimd))]
[System.Runtime.BypassReadyToRunForIntrinsicsHelperUse(typeof(PackedSimd))]
[BypassReadyToRunForIntrinsicsHelperUse(typeof(Sse))]
[BypassReadyToRunForIntrinsicsHelperUse(typeof(AdvSimd))]
[BypassReadyToRunForIntrinsicsHelperUse(typeof(PackedSimd))]

Should it be Sse2 instead of Sse here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. This is actually showing one of the limitations of the analyzer, which is that if you follow the pattern of having a helper which uses multiple variants which are explicitly checked, it isn't good at requiring the exact right attribute.

Copy link
Member Author

Choose a reason for hiding this comment

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

I should be able to make the analyzer detect this situation and issue a slightly different diagnostic, which could be suppressed with a pragma warning disable for legit cases.

- These use a peculiar construct but its legal as its all containing in 1 method. If it got more complex than that, the analysis would be quite a bit harder
- Handle intrinsics calls from nested intrinsics types
- Handle IsSupported checks in the presence of an attribute which explicitly allows an IsSupported check as well as one which doesn't, but is related
- Add suppressions for Avx2 IsSupported checks in IndexOfAnyAsciiSearcher helper functions
…don't pass a null InstructionSetSupport into the ShouldSkipCompilation logic, just feed it around everywhere.
@davidwrighton
Copy link
Member Author

@MihaZupan @sbomer @jkoritzinsky all of you had good feedback. I believe I've addressed it all, and things are working again. Could you please take another look at this?

Copy link
Member

@sbomer sbomer left a comment

Choose a reason for hiding this comment

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

A couple more nits, otherwise LGTM. Thanks!

…of them. Also we needed some new pragma warning disables to make the new logic work
Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

Just questions around where the annotations are needed

Comment on lines +854 to +856
[CompExactlyDependsOn(typeof(Sse2))]
[CompExactlyDependsOn(typeof(AdvSimd))]
[CompExactlyDependsOn(typeof(PackedSimd))]
Copy link
Member

Choose a reason for hiding this comment

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

Are these needed here given that the method doesn't directly use any of them?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. Direct use is not particular important. Its transitive use which is most important. Effectively, we need to a be aware of all possible inlining behaviors.

Copy link
Member

Choose a reason for hiding this comment

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

Effectively, we need to a be aware of all possible inlining behaviors.

Thanks, this part wasn't obvious to me (and pretty much answers all the other questions as well).

@@ -447,7 +482,16 @@ private static int IndexOfAny<TNegator>(ref short searchSpace, short value0, sho
Vector128<byte> packedValue0 = Vector128.Create((byte)value0);
Vector128<byte> packedValue1 = Vector128.Create((byte)value1);

#pragma warning disable IntrinsicsInSystemPrivateCoreLibConditionParsing // A negated IsSupported condition isn't parseable by the intrinsics analyzer, but in this case, it is only used in combination
Copy link
Member

Choose a reason for hiding this comment

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

Nit: can this comment be moved before the pragmas, it's hard to read the way it's indented currently.

Copy link
Member Author

Choose a reason for hiding this comment

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

Feel free to refactor after I check in. This comment style has made it much easier to get the correct pragmas into place as I look at lots of code, but that need will evaporate once everything is in.

Comment on lines +14 to +20
SolutionTransforms.Add((solution, projectId) =>
{
var compilationOptions = solution.GetProject(projectId).CompilationOptions;
solution = solution.WithProjectCompilationOptions(projectId, compilationOptions);

return solution;
});
Copy link
Member

Choose a reason for hiding this comment

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

Does this solution transform do anything? It looks like a no-op.

In any case, there's a CreateCompilationOptions method that you can override to customize the compilation options instead of using a solution transform.

Copy link
Member Author

Choose a reason for hiding this comment

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

Honestly, I have no idea. It came from the default make an analyzer template in VS, and doesn't seem to hurt anything.

Copy link
Member

Choose a reason for hiding this comment

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

Weird. I'm surprised that this is in the template. It's not important one way or the other.

return null;
}

private static INamedTypeSymbol[] GatherAndConditions(SemanticModel model, ExpressionSyntax expressionToDecompose)
Copy link
Member

Choose a reason for hiding this comment

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

This would be better done operating on IOperation instances than syntax, but that can be a later refactoring (as this does work).

@davidwrighton davidwrighton merged commit 8a2aec1 into dotnet:main May 16, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Jun 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants