将 ref T 值固定为 void* 的 IL 方法(用于并行代码中的 Span<T>)

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

在真正的问题之前,一个小的免责声明:

This is a related/follow up question to , 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.

所以,问题是:给定一个 ref T 变量(假设它是数组的第一项),我如何固定它以便 GC 不会在同时引起问题使用不安全指针处理底层数组?

我不确定这在 C# 中是否可行(但我希望我是错的),但是查看 IL 代码以查找仅修复 ref int 变量的方法,我试图提出具有适用于泛型类型的变体。这是我的想法:

假设我在 class "MyTestClass":

中有这个 delegate
public unsafe delegate void UnsafeAction(void* p);

然后,在 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
}

我们的想法是能够像这样使用这个方法:

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?

谢谢!

奖金问题:我已经看到 CoreFX 存储库使用“.ilproj”文件来处理具有 IL classes 的项目,但它们实际上不受支持对比。那是一些扩展还是他们使用自己的自定义脚本来支持该项目格式?我知道有一个 ILProj extension 可用,但它既不是官方的也不与 VS2017 兼容。

编辑:我可能刚刚对如何解决这个问题有了想法,考虑一下:

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?
    }
}

是的,将引用存储在固定本地就足够了。方法执行期间,引用的内存不会被移动,因此指针仍然有效。

你也可以去掉你的方法中的nops(在Debug下编译分析代码的结果),之后将变量设置为零也是不必要的,因为方法在它之后就退出了。

至于如何使用 C# 项目分发 CIL 代码,我会使用 DynamicMethod,但我不知道它是否适用于 .NET Standard。如果不是,将 IL 编译为 .dll.netmodule 并引用它或自动将其链接到主项目并不是什么大问题。