Skip to content

Guard overloads for nint/nuint, codegen improvements #3573

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
14 commits merged into from
Jan 28, 2021

Conversation

Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Nov 23, 2020

PR Type

What kind of change does this PR introduce?

  • Feature
  • Optimization

What is the current behavior?

There are no nint/nuint overloads for the any Guard APIs. These types have been introduced with C# 9, so it makes sense to add proper support for them given that #3356 added full support for C# 9 and .NET 5 to these packages.
Also there were some codegen bits in Guard.IsCloseTo that were not ideal.
Additionally, the codegen for the Guard APIs didn't leverage compiler support properly.

What is the new behavior?

✅ Added new nint/nuint overloads to Guard APIs
🚀 Improved codegen in a number of Guard.IsCloseTo overloads
🚀 Improved codegen for faulting blocks

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link:
  • Sample in sample app has been added / updated (for bug fixes / features)
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

@Sergio0694 Sergio0694 added feature 💡 DO NOT MERGE ⚠️ optimization ☄ Performance or memory usage improvements .NET Components which are .NET based (non UWP specific) labels Nov 23, 2020
@Sergio0694 Sergio0694 added this to the 7.0 milestone Nov 23, 2020
@ghost ghost added the in progress 🚧 label Nov 23, 2020
@ghost
Copy link

ghost commented Nov 23, 2020

Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌

@Sergio0694
Copy link
Member Author

#3356 has been merged, so I rebased this PR on top of master and removed "DO NOT MERGE", ready for review! 🚀

@Sergio0694
Copy link
Member Author

Made a big codegen improvement in 380f6ae as I noticed we weren't really producing the best assembly possible even though we had tried manually inverting the conditional blocks. The previous setup was done mostly just for our internal convenience - this is a bit trickier as we need to check the order of parameters for the various exceptions, but the end result is much, much better 🚀

Small example when just calling Guard.IsNotNull and returning 42:

; BEFORE
    sub      rsp, 40
    test     rcx, rcx
    jne      SHORT G_M27046_IG04
    mov      rdx, 0xD1FFAB1E
    mov      rdx, gword ptr [rdx]
    mov      rcx, 0xD1FFAB1E
    call     Microsoft.Toolkit.Diagnostics.ThrowHelper:ThrowArgumentNullExceptionForIsNotNull(System.String)
G_M27046_IG04:
    mov      eax, 42
    add      rsp, 40
    ret
; AFTER
    sub      rsp, 40
    test     rcx, rcx
    je       SHORT G_M39878_IG05
    mov      eax, 42
    add      rsp, 40
    ret
G_M39878_IG05:
    mov      rdx, 0xD1FFAB1E
    mov      rdx, gword ptr [rdx]
    mov      rcx, 0xD1FFAB1E
    call     Microsoft.Toolkit.Diagnostics.ThrowHelper:ThrowArgumentNullExceptionForIsNotNull2(System.String)
    int3

Note how the throw path is now correctly put at the end, the JIT recognizes that (int3 after it, as it knows it will always throw), and most importantly the non faulting path will not incur any jumps now, as we're branching to the exception instead. 🎉

@Sergio0694
Copy link
Member Author

I've restructured the internal architecture for the ThrowHelper.Guard methods so that they all now live in a nested private class within the Guard class, instead of just being internal methods in the ThrowHelper class. This is needed so when we publish a source-only version of these APIs (see #3587), consumers will not see those internal helpers for the Guard class in their IntelliSense (as the Guard class would just be part of their compilation). This way instead, all those helpers are just private and only directly visible from inside that class.

@Sergio0694
Copy link
Member Author

@azchohfi The CI is failing in this PR, but looking at the logs I'm not sure the issue is related to these changes.
It's weird because this PR is in sync with master, but other PRs seem to be running fine, so I'm confused 🤔

I'm seeing this:

"D:\a\1\s\UnitTests\UnitTests.UWP\UnitTests.UWP.csproj" (default target) (2) -> (BuildNativePackage target) -> [...] \microsoft.net.native.compiler[...]\Microsoft.NetNative.targets(801,5): error : ILT0005: '[...]\ilc\Tools\nutc_driver.exe @"[...]\ilc\intermediate\MDIL\UnitTests.UWP.rsp"' returned exit code -1073741819

Any ideas? 😄

@Sergio0694 Sergio0694 force-pushed the feature/nint-guard-apis branch from c03141b to 467bc3b Compare December 9, 2020 22:54
@Sergio0694
Copy link
Member Author

Sergio0694 commented Dec 9, 2020

Looking into the CI build error

  • Tried rebasing on top of master just in case something had gone wrong in this branch ❌
  • From the CI build log, I get this:

"D:\a\1\s\UnitTests\UnitTests.UWP\UnitTests.UWP.csproj" (default target) (2) ->
2020-12-09T23:21:15.9226990Z (BuildNativePackage target) ->
2020-12-09T23:21:15.9230614Z C:\Program Files (x86)\Microsoft SDKs\UWPNuGetPackages\microsoft.net.native.compiler\2.2.8-rel-28605-00\tools\Microsoft.NetNative.targets(801,5): error : ILT0005: 'C:\Program Files (x86)\Microsoft SDKs\UWPNuGetPackages\runtime.win10-x86.microsoft.net.native.compiler\2.2.8-rel-28605-00\tools\x86\ilc\Tools\nutc_driver.exe @"D:\a\1\s\UnitTests\UnitTests.UWP\obj\x86\Release\ilc\intermediate\MDIL\UnitTests.UWP.rsp"' returned exit code -1073741819 [D:\a\1\s\UnitTests\UnitTests.UWP\UnitTests.UWP.csproj]

So the error seems to be when building UnitTests.UWP with .NET Native. Trying to repro this locally.

  • Can repro this locally, definitely seems to be some issue with .NET Native. Trying to find the exact culprit.

  • Went back up to 6ace41b and it still crashes, so it looks very probably that commit is causing the crash on .NET Native. Seeing if I can pinpoint exactly what part is triggering the crash and whether I can wiggle the code to avoid it.

  • The error is definitely caused by Guard.IsBitwiseEqualTo. Not sure why exactly, but it's that.

  • Found a minimal repro for the issue! Pasting this into a blank UWP app crashes the .NET Native compiler:

public static unsafe void IsBitwiseEqualTo<T>(T value, T target, string name)
    where T : unmanaged
{
    if (sizeof(T) == 1)
    {
        if (*(byte*)&value == *(byte*)&target)
        {
            return;
        }

        Throw();
    }
    else if (sizeof(T) == 2)
    {
        if (*(ushort*)&value == *(ushort*)&target)
        {
            return;
        }

        Throw();
    }

    static void Throw() => throw new Exception();
}
  • Tested this on the latest version 6.2.11 of the .NET Native compiler, same crash there as well.

Reverting the changes in 6ace41b at this point, as a temporary workaround to unblock this PR. There shouldn't be a noticeable performance difference anyway in this case, though of course I'd have preferred to keep the revised version 😄

This reverts commit 6ace41b to work around a .NET Native compiler bug causing a crash. See more details in CommunityToolkit#3573 (comment).
@Sergio0694
Copy link
Member Author

CI passed! 🎉🎉🎉

@michael-hawker
Copy link
Member

@Sergio0694 did you e-mail this to the dotnetnative @ microsoft address too?

FYI @tommcdon @michals

/// <summary>
/// Helper methods to efficiently throw exceptions.
/// </summary>
private static partial class ThrowHelper
Copy link
Member

Choose a reason for hiding this comment

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

Why is this being wrapped in a class now? I think that's the main thing I'm confused about in the PR (and with all the additional new ThrowHelper wraps below). Is this a copy of the other ThrowHelpers or something else?

Copy link
Member Author

@Sergio0694 Sergio0694 Dec 10, 2020

Choose a reason for hiding this comment

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

All these methods have always been wrapped in a class, it's just that before they were inside the public ThrowHelper class, and internal, while now they're all public and inside a new, private ThrowHelper class that's nested inside Guard. There is no behavioral different, it's just a code reorganization. The rationale for this is to make the code compatible (or rather, to make it better) in relation to the source-only Microsoft.Toolkit.Diagnostics package (#3587). The idea being that if we publish the source-only package and wanted to just copy-paste the Guard and ThrowHelper classes into consuming projects, they would've been able to also see all those internal helpers, which would've just cluttered their IntelliSense. With this structure instead all those internal helpers would still be invisible outside of the Guard class, even if the class was just added as source code directly into your project 🙂

@Sergio0694
Copy link
Member Author

@michael-hawker Yup, sent a full repro to them about an hour ago 😄

@ghost
Copy link

ghost commented Jan 28, 2021

Hello @michael-hawker!

Because this pull request has the auto merge :zap: label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit f41314e into CommunityToolkit:master Jan 28, 2021
@Sergio0694 Sergio0694 deleted the feature/nint-guard-apis branch January 28, 2021 18:20
Sergio0694 added a commit to CommunityToolkit/dotnet that referenced this pull request Oct 29, 2021
This reverts commit f286447 to work around a .NET Native compiler bug causing a crash. See more details in CommunityToolkit/WindowsCommunityToolkit#3573 (comment).
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto merge ⚡ feature 💡 in progress 🚧 .NET Components which are .NET based (non UWP specific) optimization ☄ Performance or memory usage improvements
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants