Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Feature Request Force C# reference implementation for intrinsics features (for debugging)

Discussion in 'Burst' started by Per-Morten, Aug 5, 2021.

  1. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    109
    Hi,
    Would it be possible to add some sort of toggle that forces the managed C# reference implementation of a specific set of intrinsics for debugging? I know you can comment out the if statement checking against a specific feature set, but that gets a bit cumbersome if you use helper functions, as you need to remember to comment out the feature checks in there as well.

    Here's a contrived simplified example of what I mean:
    Code (CSharp):
    1.  
    2.     [BurstCompile]
    3.     public struct BuggyAVXJob
    4.         : IJobParallelFor
    5.     {
    6.         [ReadOnly]
    7.         public NativeArray<int4> Input;
    8.  
    9.         [ReadOnly]
    10.         public NativeArray<int4> Output;
    11.  
    12.         public void Execute(int index)
    13.         {
    14.             if (IsAvx2Supported)
    15.             {
    16.                 var input = Input.ReinterpretLoad<v256>(index * 2);
    17.                 Output.ReinterpretStore<v256>(index * 2, HelperAdd(input, mm256_set1_epi32(5)));
    18.             }
    19.             else
    20.             {
    21.                 Output[index] = Input[index] - 17;
    22.             }
    23.         }
    24.  
    25.         public static v256 HelperAdd(v256 a, v256 b)
    26.         {
    27.             if (IsAvx2Supported)
    28.                 return mm256_add_epi32(a, b);
    29.             return new v256(0);
    30.         }
    31.     }
    32.  

    If I were to debug the Avx2 version of this job I would "obviously" comment out the first IsAvx2Supported if check. However, I would probably forget the one in HelperAdd, and during debugging I would be very confused as to why I only store 0's in the output array. I've already done this mistake multiple times while debugging, and it's not always obvious that's what you've forgotten.

    I thought I could get around this by doing something like the following:
    Code (CSharp):
    1.  
    2.     public static class BurstSupportWrapper
    3.     {
    4. #if ENABLE_AVX2_REFERENCE_IMPLEMENTATION
    5.         public static bool Avx2Supported => true;
    6. #else
    7.         public static bool Avx2Supported => IsAvx2Supported;
    8. #endif
    9.     }
    10.  
    11.     [BurstCompile]
    12.     public struct BuggyAVXJob
    13.         : IJobParallelFor
    14.     {
    15.         [ReadOnly]
    16.         public NativeArray<int4> Input;
    17.  
    18.         [ReadOnly]
    19.         public NativeArray<int4> Output;
    20.  
    21.         public void Execute(int index)
    22.         {
    23.             if (BurstSupportWrapper.Avx2Supported)
    24.             {
    25.                 var input = Input.ReinterpretLoad<v256>(index * 2);
    26.                 Output.ReinterpretStore<v256>(index * 2, HelperAdd(input, mm256_set1_epi32(5)));
    27.             }
    28.             else
    29.             {
    30.                 Output[index] = Input[index] - 17;
    31.             }
    32.         }
    33.  
    34.         public static v256 HelperAdd(v256 a, v256 b)
    35.         {
    36.             // Easy to forget to comment out this which can add confusion when debugging
    37.             if (BurstSupportWrapper.Avx2Supported)
    38.                 return mm256_add_epi32(a, b);
    39.             return new v256(0);
    40.         }
    41.     }
    42.  

    However, burst doesn't seem to recognize the forwarding to the feature check, so even when ENABLE_AVX2_REFERENCE_IMPLEMENTATION is not defined I get the compiler error saying that I cant use the Avx2 instructions.

    Would it be possible to support forcing a managed implementation for debugging? Setting something through a macro, or having support for indirections for feature checks (like I've done above) would be more than enough for me.
     
    Last edited: Aug 6, 2021
  2. Yury-Habets

    Yury-Habets

    Unity Technologies

    Joined:
    Nov 18, 2013
    Posts:
    1,165
    This is a great write-up and a feature request. May I ask why would you want to debug (or have managed fallbacks) for some of the intrinsics? Are you having issues with them - like not translating to a correct CPU instruction? Then this is a bug - please report it and we're happy to fix it!

    You are correct that any wrappers won't work for the intrinsics feature check because it's a strict compile time check.

    Because intrinsic calls are being replaced at compile time based on a dictionary, it would also be a bit problematic exposing the setting somehow publicly. Perhaps playing [BurstDiscard] may help you in your debugging workflow.

    Lastly, in HelperAdd() you are sort of supposed to provide an equivalent managed fallback implementation and not just zeroes :) At least for the sake of debugging working correctly. Of course, for production code you may need an SSE4 implementation too. And if you're targeting Arm platforms too, make sure to have a Neon implementation.
     
  3. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    109
    To clarify, because I think I worded myself poorly. What I want is to make one* of the "IsXXXSupported" feature checks return true so I can step through the code and debug it using the C# reference implementation of the intrinsics. This is *only* for when burst compilation is disabled and I'm doing managed debugging. I'm not asking for any changes in the burst compiled code. I want a way to step through code within 'IsXXXSupported' blocks without having to change the code to skip those feature checks. That's what I was trying to achieve with the BurstSupportWrapper above. My idea was that if I needed to debug some Avx2 stuff I could simply disable burst and define ENABLE_AVX2_REFERENCE_IMPLEMENTATION in project settings in the editor.

    * I said one, but it should probably work as it does in burst today, that later feature levels include previous ones.

    Having a C# reference implementation of the intrinsics is great, it would be awesome if it was even easier to make use of it :)

    I'm not sure I understand completely here, do you mean that I should have returned like: new V256(a.Float0 + b.Float0, a.Float1 + b.Float1, ...)?
     
    Yury-Habets likes this.
  4. Yury-Habets

    Yury-Habets

    Unity Technologies

    Joined:
    Nov 18, 2013
    Posts:
    1,165
    Correct, the managed implementation should basically do the same as the intrinsic implementation, unless it's very complicated to make it work.
     
  5. Yury-Habets

    Yury-Habets

    Unity Technologies

    Joined:
    Nov 18, 2013
    Posts:
    1,165
    Got it. Yes makes sense; if you are trying to debug and want to look into the managed implementation of the intrinsics (with Burst disabled), the IfXXXSupported check will return false and not let you do that. Let me think of that and get back to you.
     
  6. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    109
    Awesome :), looking forward to hearing back on it.

    I see. We only target AVX2 and keep our intrinsic and "non intrinsic" implementations very separate, so I didn't consider it (I don't have much experience with SIMD, been interested but only got into it recently because stuff like the C# reference implementation lowered the barrier enough for me :)). Currently what I've done is to either avoid using helper functions or put an assert IsXXXSupported at the end of them, so I'm at least made aware of it if I've forgotten to comment out the check.
     
  7. Yury-Habets

    Yury-Habets

    Unity Technologies

    Joined:
    Nov 18, 2013
    Posts:
    1,165
    After discussing the issue with the team, here is most viable solution for being able to debug the managed intrinsic implementation:
    In addition to IsAvx2Supported, add a debug flag and set it to true when you want to debug, something like

    Code (CSharp):
    1. const bool kDebugFlag = false;
    2.  
    3. ...
    4.  
    5. if (IsAvx2Supported || kDebugFlag)
    6. {
    7.   // your code
    8. }
    and set kDebugFlag to true in debug builds or whenever you want to debug.
     
  8. Per-Morten

    Per-Morten

    Joined:
    Aug 23, 2019
    Posts:
    109
    Right, yeah, that works, I hadn't tested that directly, only through an indirection, thanks :)

    This fixes 99% of my issue so I'll go with this approach. The only inconvenience is that the variable isn't ignored when burst compilation is enabled, so I can't view the generated assembly of the scalar implementation of the code without changing the variable. In my "dream scenario" I could select the reference implementation I wanted to use, and nothing would change except that when burst was disabled we ran the chosen reference implementation for the intrinsics. Unsure if this is something that could be done if this issue was "fixed" from your side. Anyway, this is nothing more than a minor inconvenience for me, just thought I should mention it in case supporting running reference implementations for debugging like this is discussed in the future.

    Thanks for the help :)
     
    Yury-Habets likes this.