Skip to content

Add bit shift intrinsic functions #8569

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

Merged
merged 9 commits into from
Apr 7, 2023

Conversation

jrdodds
Copy link
Contributor

@jrdodds jrdodds commented Mar 15, 2023

Fixes #8551

Context

The intrinsic functions include functions for bit And, Or, Xor, and Not operators but not for the bit shift operators.

Changes Made

Added functions LeftShift, RightShift, and RightShiftUnsigned. The functions expect values of type int (32 bit) which matches the existing bit operator functions. long (64 bit) is not supported by any of the bit operator intrinsic functions.

Testing

Tested on Windows 11 and macOS 12 Monterey.

Added to Expander_Tests.cs and ran unit tests.

Created and ran BitShift.proj project file with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <Target Name="Test">
    <PropertyGroup>
      <LeftShiftTest>$([MSBuild]::LeftShift(1, 2))</LeftShiftTest>
      <RightShiftTest>$([MSBuild]::RightShift(-8, 2))</RightShiftTest>
      <RightShiftUnsignedTest>$([MSBuild]::RightShiftUnsigned(-8, 2))</RightShiftUnsignedTest>
    </PropertyGroup>

    <Message Text="LeftShiftTest: $(LeftShiftTest)" />
    <Message Text="RightShiftTest: $(RightShiftTest)" />
    <Message Text="RightShiftUnsignedTest: $(RightShiftUnsignedTest)" />
  </Target>

</Project>

The project produced the following output (which matched the expected results):

Test:
  LeftShiftTest: 4
  RightShiftTest: -2
  RightShiftUnsignedTest: 1073741822

Notes

Copy link
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

Thanks!


internal static int RightShiftUnsigned(int operand, int count)
{
return operand >>> count;
Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't aware this existed. Always nice to learn something new!

@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Mar 20, 2023
Copy link
Member

@rainersigwald rainersigwald left a comment

Choose a reason for hiding this comment

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

Looks good! Can you please add a "fast path" in this block to avoid having to use reflection when this is called?

else if (_receiverType == typeof(IntrinsicFunctions))
{
if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.EnsureTrailingSlash), StringComparison.OrdinalIgnoreCase))
{
if (TryGetArg(args, out string arg0))
{
returnVal = IntrinsicFunctions.EnsureTrailingSlash(arg0);
return true;
}
}

@ladipro
Copy link
Member

ladipro commented Mar 20, 2023

@rainersigwald, the existing bitwise operations are not on the fast path either. Presumably because they're not expected to be used very often and, while making the first call faster, every such special case if incurs a small steady state perf hit.

@rainersigwald
Copy link
Member

In the past we've added any method that is known to be called in the wild to the fast path, but haven't required it, and I suspect the bitwise operations were missed, rather than intentionally omitted.

…se Shouldly because fail messages will show the name of the failing function
@jrdodds
Copy link
Contributor Author

jrdodds commented Mar 20, 2023

I added a "fast path" for each of the bit operator functions:

  • BitwiseOr
  • BitwiseAnd
  • BitwiseXor
  • BitwiseNot
  • LeftShift
  • RightShift
  • RightShiftUnsigned

In Expander_Tests I separated the bit operator functions into their own test method from the math functions, and I changed from Xunit Assert's to Shouldly. (I was motivated to make the unit test change because I had a typo and the Xunit Assert message didn't include the name of the failing function.)

@jrdodds jrdodds requested a review from rainersigwald March 20, 2023 17:44
@ladipro
Copy link
Member

ladipro commented Mar 21, 2023

@rainersigwald, the existing bitwise operations are not on the fast path either. Presumably because they're not expected to be used very often and, while making the first call faster, every such special case if incurs a small steady state perf hit.

Make sense. Also, source generators for the win! It should be a fun project to automatically create efficient lookup tables at compile time.

@rainersigwald
Copy link
Member

Also, source generators for the win! It should be a fun project to automatically create efficient lookup tables at compile time.

I have been fantasizing about doing this for several years now!

@jrdodds
Copy link
Contributor Author

jrdodds commented Mar 21, 2023

I'm looking at the 'Fast Path' TryExecuteWellKnownFunction code for other functions and I have some questions.

IntrinsicFunctions.cs includes Add functions for double and int:

/// <summary>
/// Add two doubles
/// </summary>
internal static double Add(double a, double b)
{
return a + b;
}
/// <summary>
/// Add two longs
/// </summary>
internal static long Add(long a, long b)
{
return a + b;
}

The TryExecuteWellKnownFunction method in Expander.cs tests for but doesn't use the Add functions:

else if (string.Equals(_methodMethodName, nameof(IntrinsicFunctions.Add), StringComparison.OrdinalIgnoreCase))
{
if (TryGetArgs(args, out double arg0, out double arg1))
{
returnVal = arg0 + arg1;
return true;
}
}

Subtract, Multiply, and Divide have the same pattern. The operator is used instead of invoking the function.

There are Modulo functions for double and int and they are not in the TryExecuteWellKnownFunction method.

Shouldn't the TryExecuteWellKnownFunction method always be invoking the appropriate member of IntrinsicFunctions (and never directly using the operator)?

It seems that currently Add, Subtract, Multiply, and Divide are always performed with doubles. How are the int and double overloads intended to resolve? Does the 'slow path' resolve the type overload?

@Forgind
Copy link
Contributor

Forgind commented Mar 21, 2023

Shouldn't the TryExecuteWellKnownFunction method always be invoking the appropriate member of IntrinsicFunctions (and never directly using the operator)?

I don't think this really matters. From a style perspective, it is better to have them all look the same, that is, all call the appropriate member of IntrinsicFunctions as you suggest. From the user's perspective, they're equivalent, and if the call isn't inlined, calling '+' directly may even be (negligibly) faster.

It seems that currently Add, Subtract, Multiply, and Divide are always performed with doubles. How are the int and double overloads intended to resolve? Does the 'slow path' resolve the type overload?

There aren't really "ints," "doubles," etc. in MSBuild; everything is just strings. Even things like TryConvertToInt are kinda misleading, as far as I know, because 'value' won't ever be a double or an int; it'll always be a string, and we'll always parse it as such. With that in mind, if you pass Add two ints, they just turn into doubles, get added, and get converted back to a string that looks like an integer, so people generally don't care too much about whether the arithmetic was actually executed with ints or doubles. Since all ints are doubles, though, we do get them in the fast path.

@jrdodds
Copy link
Contributor Author

jrdodds commented Mar 21, 2023

@Forgind It sounds like the int overloads of Add, Subtract, Multiply, Divide, and Modulo should be removed because they are never used.

@Forgind
Copy link
Contributor

Forgind commented Mar 21, 2023

I wouldn't be opposed to that. The only ways I can think of that someone could actually call them is if they use reflection or MSBuildLocator to specifically load our IntrinsicFunctions class then call the int overload...neither feels at all worthwhile to me, since C# programmers don't need a special function to call something built-in. Can you think of any legitimate reason someone might want to use one of those overloads @rainersigwald?

@jrdodds
Copy link
Contributor Author

jrdodds commented Mar 21, 2023

It might be considered clean up for supporting the source generation @rainersigwald and @ladipro mentioned. 😊

@rainersigwald rainersigwald removed the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Mar 27, 2023
@rainersigwald
Copy link
Member

@jrdodds I'm not sure of the impact of some of those removals, and it's late enough in the 17.6 release cycle that I'm nervous about removing them. Can you separate them into a different PR that we can think about for a bit longer? Adding the shift operators and the fast paths is in good shape, though.

@jrdodds
Copy link
Contributor Author

jrdodds commented Mar 27, 2023

@rainersigwald This PR has been updated.

@rainersigwald rainersigwald added this to the VS 17.7 milestone Mar 28, 2023
@rainersigwald rainersigwald merged commit e2fa2d7 into dotnet:main Apr 7, 2023
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.

[Feature Request]: Bitshift MSBuild Property Function
4 participants