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

Pass constants to parameters marked as [ConstantExpected] #33771

Closed
terrajobst opened this issue Mar 19, 2020 · 31 comments · Fixed by dotnet/roslyn-analyzers#5766
Closed

Pass constants to parameters marked as [ConstantExpected] #33771

terrajobst opened this issue Mar 19, 2020 · 31 comments · Fixed by dotnet/roslyn-analyzers#5766
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices code-analyzer Marks an issue that suggests a Roslyn analyzer
Milestone

Comments

@terrajobst
Copy link
Member

terrajobst commented Mar 19, 2020

Consider adding an [ConstantExpected] attribute that could be put on parameters to indicate that arguments should be constants rather than variables. See #30740 (comment) for details.

The attribute proposal

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public sealed class ConstantExpectedAttribute : Attribute
    {
        public object? Min { get; set; }
        public object? Max { get; set; }
    }
}

Usage and Diagnostic examples

public static class C
{
    public static void MyBool1([ConstantExpected] bool b) { }
    public static void MyLong1([ConstantExpected] long b) { }
    public static void MyLong2([ConstantExpected(Min = -5, Max = 10)] long b) { }
    public static void MyFloat1([ConstantExpected] float b) { }
    public static void MyFloat2([ConstantExpected(Min = -5.3f, Max = 10.1f)] float b) { }

    // Might want to warn for the negative values and out of range values
    public static void MyInvalidUshort([ConstantExpected(Min = -5, Max = -1)] ushort b) { }
    public static void MyInvalidRange([ConstantExpected(Min = 5, Max = -5)] int b) { }
    // flag any ref type usage as not applicable
    public static void MyString([ConstantExpected] string b) { }

    // Diagnostics examples
    public static void Test(long b, ushort u)
    {
        // OK
        const long a = 10;
        MyLong1(a);
        MyLong2(a);
        MyLong1(1L);
        MyLong2(2L);
        MyInvalidUshort(1);
        const ushort us = 0;
        MyInvalidUshort(us); // Flag

        MyLong1(b); // Flag
        MyLong2(b); // Flag

                
        MyLong2(20); // Flag, out of range
        MyInvalidUshort(u); // Flag
        MyInvalidUshort(10); // Flag, out of the range
    }
}

Category: Reliability
Severity: Info

@terrajobst terrajobst added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime.Intrinsics code-analyzer Marks an issue that suggests a Roslyn analyzer labels Mar 19, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Mar 19, 2020
@dotnet dotnet deleted a comment from Dotnet-GitSync-Bot Mar 19, 2020
@jeffhandley jeffhandley added this to To do in Roslyn Analyzers Mar 20, 2020
@jeffhandley jeffhandley moved this from To do to .NET 5.0 in Roslyn Analyzers Mar 20, 2020
@jeffhandley jeffhandley added this to the 5.0 milestone Mar 20, 2020
@jeffhandley
Copy link
Member

Estimates:

  • Analyzer: Medium
  • Fixer: Not Applicable

@terrajobst terrajobst removed the untriaged New issue has not been triaged by the area owner label Mar 21, 2020
@jeffhandley
Copy link
Member

This issue should capture:

  1. The attribute itself (getting that through the API review)
  2. Creation of the analyzer
  3. Identification of some instances where the attribute should be applied

The work to apply the attribute (especially through intrinsics) could be a separate issue/PR.

@terrajobst terrajobst added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Mar 23, 2020
@jeffhandley
Copy link
Member

@tannergooding Do you think this is a must-have attribute & analyzer for .NET 5 to round out the intrinsics work we've done, or do you think this could be added in .NET 6?

@tannergooding
Copy link
Member

It is not. The hardware intrinsics don't even require constant inputs, they just benefit from them and you will get very poor codegen if you don't.
We shipped .NET Core 3.0 without such an attribute and I'm sure we'll be fine if we do the same for .NET 5.

@jeffhandley
Copy link
Member

Thank you, @tannergooding. I'm going to move this to Future so that we can consider it for .NET 6 then.

@jeffhandley jeffhandley modified the milestones: 5.0, Future May 27, 2020
@jeffhandley jeffhandley moved this from .NET 5.0 to To do in Roslyn Analyzers May 27, 2020
@Mrnikbobjeff
Copy link

How would we catch expressions which are evaluated at compile time? I.e. 2*2*2 would be folded to 8 and I guess emit the same code as if we would manually fold it, but manually incorporating folding rules to detect if the expression may become a constant would be difficult or am I missing something?

@carlossanlop carlossanlop moved this from To do to Needs investigation in Roslyn Analyzers Nov 20, 2020
@carlossanlop
Copy link
Member

@GrabYourPitchforks @tannergooding should we have a separate API proposal for the attribute itself? Or in other words, should we reopen #33814 for that purpose specifically? I don't mind if we can get both the attribute and the analyzer approved in one single issue.

Attribute questions

  • The name ConstantExpected makes more sense. Levi proposed Immediate in the other issue.

  • Here is an example of an API Levi shared in the other issue, which could benefit from having both the new proposed attribute.

namespace System.Runtime.Intrinsics.X86
{
    public abstract class Sse2 : Sse
    {
        public static Vector128<short> ShiftLeftLogical(Vector128<short> value, [ConstantExpected(/* Min = 0, Max = 15 */)] byte count);
    }
}

A proposal of the attribute could be:

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public sealed class ConstantExpectedAttribute : Attribute
    {​​​​
        public int? Min {​​​​ get; set; }​​​​
        public int? Max {​​​​ get; set; }​​​​
    }​​​​
  • @GrabYourPitchforks Do we want the attribute to receive limits as arguments, like you added in your example as a comment (Min/Max)? Is the type only expected to be an integer? Is this even possible if we don't know the specific parameter type? If we have constant number types, we could find the default minimum and maximum values, but for other types like string, which could also be constant, it wouldn't make sense to try to find a min/max.

  • In which assembly/namespace would the attribute live?

Analyzer questions

  • The analyzer should have an Info severity, since it's allowed to pass a non-constant even if the parameter has the attribute. Agreed?

  • Could we make the analyzer mandatory for Intrinsics APIs only? And if we could, would be interested in doing that?

@GrabYourPitchforks
Copy link
Member

If we wanted min / max, they could be typed as object?. This would allow byte, sbyte, int, double, etc. parameters. This isn't being checked at runtime - only compile-time - and it's straightforward enough for the compiler to handle attribute properties typed as object. For string literals, min / max would stay null.

@tannergooding
Copy link
Member

I think that this issue is fine for tracking the API, and I believe that was @terrajobst intent when he tagged it api-needs-work.

@zvrba
Copy link

zvrba commented Nov 22, 2021

I'm late to the discussion, but in relation to use with intrinsics, I'd prefer something like void DoSomething(int runtimeValue, const byte imm). Passing in anything than a compile-time constant for imm would generate a compile error. This extends the use of keyword to a currently invalid context, so nothing would get broken.

It could possibly be extended to have a generic constraint where T : const int and subsequently one could write "metacode" that unrolls loops at compile-time like one does with templates in C++. But I guess there are source generators for that now.

@ghost ghost added the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Nov 22, 2021
@tannergooding
Copy link
Member

That would be a breaking change and go against the goals of hardware intrinsics in .NET. It also has several issues, including that you couldn't write code that works with values that become constant after inlining.

@buyaa-n buyaa-n removed the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label Nov 23, 2021
@buyaa-n buyaa-n moved this from Ready for review to Approved-Future in Roslyn Analyzers Nov 23, 2021
@carlossanlop carlossanlop added the help wanted [up-for-grabs] Good issue for external contributors label Nov 23, 2021
@wzchua
Copy link
Contributor

wzchua commented Dec 5, 2021

I would like to be assigned this.

Does the work start with getting the attribute merged into runtime repo, then writing the analyzer? Are there other steps for the attribute?

@buyaa-n buyaa-n removed the help wanted [up-for-grabs] Good issue for external contributors label Dec 5, 2021
@buyaa-n
Copy link
Member

buyaa-n commented Dec 5, 2021

Does the work start with getting the attribute merged into runtime repo, then writing the analyzer?

Yes start with the attribute getting merged to the runtime repo, for the analyzer you can use a mock attribute for testing until you were able to consume it in the analyzers repo

Are there other steps for the attribute?

I would say no, but not sure about what exactly you are asking, here is a sample PR for adding an attribute

@buyaa-n
Copy link
Member

buyaa-n commented Dec 6, 2021

Also, there could be a step "applying the attribute" to the APIs in the runtime repo but that is not needed to be covered by this issue

@wzchua
Copy link
Contributor

wzchua commented Dec 11, 2021

Do we allow composition of different types? Like what should the analyzer do with something like below:

public static void TestMethod([ConstantExpected(Min=0, Max=5)] uint constant)
{
    TestMethod2(constant);
}
public static void TestMethod2([ConstantExpected(Min=0, Max=10)] int val)
{
}

@wzchua
Copy link
Contributor

wzchua commented Dec 13, 2021

Also, does casting influence the analyzer?

TestMethod((uint)100);

@buyaa-n
Copy link
Member

buyaa-n commented Dec 13, 2021

Do we allow composition of different types? Like what should the analyzer do with something like below:

Not sure I follow the question, though I believe the analyzer should warn on TestMethod2(constant); invocation because it is not passing a constant

public static void TestMethod([ConstantExpected(Min=0, Max=5)] uint constant)
{
    TestMethod2(constant); // should warn here as constant expected for 'val' parameter
}
public static void TestMethod2([ConstantExpected(Min=0, Max=10)] int val)
{
}

Also, does casting influence the analyzer?

TestMethod((uint)100);

Not sure what issue you are having here, but TestMethod((uint)100); and TestMethod(100); in your example should behave the same

@wzchua
Copy link
Contributor

wzchua commented Dec 13, 2021

It seems we want to be able to compose the ConstantExpectedAttribute using another ConstantExpectedAttribute. I'm clarifying what should be done if the types don't match.

I guess I'm asking does casting make it not a constant?
It might be more relevant for the composition case.
I guess it is not an issue for the literal scenario.

@buyaa-n
Copy link
Member

buyaa-n commented Dec 13, 2021

Good question, tagging @tannergooding @GrabYourPitchforks @bartonjs for answer

@wzchua
Copy link
Contributor

wzchua commented Dec 17, 2021

Given,

using System;
using System.Diagnostics.CodeAnalysis;
#nullable enable

public class Test
{
    public interface ITest<T>
    {
        T Add(T operand1, [ConstantExpected] T operand2);
    }
    public abstract class AbstractTest<T>
    {
        public abstract T Add2(T operand1, [ConstantExpected] T operand2);
    }
    public class GenericInt : AbstractTest<int>, ITest<int>
    {
        public int Add(int operand1, int operand2)
        {
            return 0;
        }
        public override int Add2(int operand1, int operand2)
        {
            return 0;
        }
    }
}

I assume the analyzer should warn for not having [ConstantExpected] in the more concrete type parameter?

@tannergooding
Copy link
Member

I assume the analyzer should warn for not having [ConstantExpected] in the more concrete type parameter?

I'd expect so, yes. There is some "wonkiness" here though, in that [ConstantExpected] is "impossible" to fulfill for non-primitive types. So if T were a Guid, for example, we'd need to decide what happens.

@wzchua
Copy link
Contributor

wzchua commented Jan 9, 2022

Ok, i have written the tests for many of the cases.
For cases where the attribute is applied to T, setting Min Max is an error, and warning at callsite only appears if concrete type for T can be constant

@deeprobin
Copy link
Contributor

dotnet/roslyn-analyzers#5766 (comment)
@bartonjs Do we need a VB analyzer?

/cc @wzchua

@bartonjs
Copy link
Member

@deeprobin The general stance is "most analyzers should Just Work for both C# and VB, so you should add VB tests and enjoy that it Just Worked... but if your analyzer requires special VB code it's probably not worth it"

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime.CompilerServices code-analyzer Marks an issue that suggests a Roslyn analyzer
Projects
No open projects
Development

Successfully merging a pull request may close this issue.