IL method to pin a ref T value as void* (to work on Span<T> from parallel code)

随声附和 提交于 2019-12-11 15:57:20

问题


Before the actual question, a small disclaimer:

This is a related/follow up question to this one, but since here I'm talking about a more general issue (how to pin a ref T variable to be able to perform pointer operations on it), and I'm proposing a possible (probably wrong) solution, I opened a separate question.

So, the question is: given a ref T variable (assuming it's the first item of an array), how can I pin it so that the GC won't cause problems while working on the underlying array with unsafe pointers?

I'm not sure this is possible in C# (but I hope I'm wrong), but looking at the IL code for a method that just fixes a ref int variable, I tried to come up with a variant that worked on generic types. Here's my idea:

Assume I have this delegate in the class "MyTestClass":

public unsafe delegate void UnsafeAction(void* p);

Then, in IL:

.method public hidebysig static void
    Foo<valuetype .ctor (class [netstandard]System.ValueType) T>(
        !!0/*T*/& r, 
        class MyTestClass/UnsafeAction action
    ) cil managed
{
    .maxstack 2
    .locals init (
      [0] void* p,
      [1] !!0/*T*/& pinned V_1
    )

    // Load the ref T argument into the pinned variable (as if fixed were used)
    IL_0000: nop          
    IL_0001: ldarg.0      // r
    IL_0002: stloc.1      // V_1

    // Cast the T* pointer to void*
    IL_0003: ldloc.1      // V_1
    IL_0004: conv.u       
    IL_0005: stloc.0      // p

    // Invoke the action passing the p pointer
    IL_0006: ldarg.1      // action
    IL_0007: ldloc.0      // p
    IL_0008: callvirt     instance void MyTestClass/UnsafeAction::Invoke(void*)
    IL_000d: nop          

    // Set the pinned variable V_1 to NULL
    IL_000e: ldc.i4.0     
    IL_000f: conv.u       
    IL_0010: stloc.1      // V_1
    IL_0011: ret
}

The idea would be to be able to use this method like this:

public static void Test<T>(this Span<T> span, Func<T> provider)
{
    void Func(void* p)
    {
        // Do stuff with p, possibly in parallel
        // eg. Unsafe.Write(p, provider());
    }
    Foo(ref span.DangerousGetPinnableReference(), Func);
}

Would something like this work? If so, what's the best way to include such a method written in IL into an existing .NET Standard 2.0 project?

Thanks!

Bonus question: I've seen the CoreFX repo using ".ilproj" files for projects with IL classes, but they're not actually supported by VS. Is that some extension or do they use their own custom scripts to support that project format? I know there's an ILProj extension available, but it isn't either official nor compatible with VS2017.

Edit: I might have just had an idea on how to solve this issue, consider this:

public static void Foo<T>(ref T value)
{
    fixed (void* p = &Unsafe.As<T, byte>(ref value))
    {
        // Shouldn't the ref T be correctly fixed here, since
        // the "dummy" byte ref had the same address?
    }
}

回答1:


Yes, storing the reference in a pinned local is sufficient. During the execution of the method, the referenced memory will not be moved and therefore the pointer will remain valid.

You can also remove the nops from your method (the result of analyzing code compiled under Debug), and setting the variable to zero afterwards is also unnecessary, because the method exists just after it.

As for how to distribute the CIL code with a C# project, I would use DynamicMethod, but I don't know if that is available in .NET Standard. If not, compiling the IL to a .dll or .netmodule and referencing it or automatically linking it to the main project is not such a big issue.



来源:https://stackoverflow.com/questions/48095145/il-method-to-pin-a-ref-t-value-as-void-to-work-on-spant-from-parallel-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!