使用手写程序集调用本机代码

时间:2011-10-23 08:46:18

标签: assembly x86 sse

我正在尝试从托管程序集调用本机函数。我已经在预编译的库上完成了这一切,一切都进展顺利。此刻我正在建立自己的图书馆,我无法完成这项工作。

本机DLL源代码如下:

#define DERM_SIMD_EXPORT        __declspec(dllexport)

#define DERM_SIMD_API           __cdecl

extern "C" {

    DERM_SIMD_EXPORT void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right);

}

void DERM_SIMD_API Matrix4x4_Multiply_SSE(float *result, float *left, float *right) {
    __asm {
       ....
    }
}

此后我们有托管代码,它加载库并从函数指针创建委托。

public unsafe class Simd
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void MatrixMultiplyDelegate(float* result, float* left, float* right);

    public static MatrixMultiplyDelegate MatrixMultiply;

    public static void LoadSimdExtensions()
    {
        string assemblyPath = "Derm.Simd.dll";

        IntPtr address = GetProcAddress.GetAddress(assemblyPath, "Matrix4x4_Multiply_SSE");

        if (address != IntPtr.Zero) {
            MatrixMultiply = (MatrixMultiplyDelegate)Marshal.GetDelegateForFunctionPointer(address, typeof(MatrixMultiplyDelegate));
        }
    }
}

使用上面的代码运行时没有错误(获取了函数指针,实际创建了委托。

当我调用委托时问题就出现了:它被执行了(我也可以调试它!),但是在函数退出时,托管应用程序引发了一个 System.ExecutionEngineException (当它没有退出,没有例外)。

实际问题是函数实现:它包含带有SSE指令的 asm 块;如果我删除 asm 块,代码就可以正常工作。

我怀疑我错过了一些注册表保存/恢复程序集,但我在这一方面完全无知。

奇怪的是,如果我将调用约定更改为__stdcall,则调试版本“似乎”可以正常工作,而发布版本的行为就像使用了__cdecl调用会话一样。

(只是因为我们在这里,你能澄清呼叫对话是否重要吗?)


好的,感谢 David Heffernan 评论我发现造成问题的错误指示如下:

 movups result[ 0], xmm4;
 movups result[16], xmm5;

movups 指令将16个字节移入(未对齐的)内存。

该函数由以下代码调用:

 unsafe {
    float* prodFix = (float*)prod.MatrixBuffer.AlignedBuffer.ToPointer();
    float* m1Fix = (float*)m2.MatrixBuffer.AlignedBuffer.ToPointer();
    float* m2Fix = (float*)m1.MatrixBuffer.AlignedBuffer.ToPointer();

    if (Simd.Simd.MatrixMultiply == null) {
                    // ... unsafe C# code
    } else {
        Simd.Simd.MatrixMultiply(prodFix, m1Fix, m2Fix);
    }
}

其中 MatrixBuffer 是我的一类;其成员 AlignedBuffer 按以下方式分配:

// Allocate unmanaged buffer
mUnmanagedBuffer = Marshal.AllocHGlobal(new IntPtr((long)(size + alignment - 1)));

// Align buffer pointer
long misalignment = mUnmanagedBuffer.ToInt64() % alignment;
if (misalignment != 0)
    mAlignedBuffer = new IntPtr(mUnmanagedBuffer.ToInt64() + misalignment);
else
    mAlignedBuffer = mUnmanagedBuffer;

错误可能是由 Marshal.AllocHGlobal IntPtr 黑魔法引起的?


这是发现错误的最小来源:

void Matrix4x4_Multiply_SSE(float *result, float *left, float *right)
{
    __asm {
        movups xmm0,    right[ 0];

        movups result, xmm0;
    }
}


int main(int argc, char *argv[])
{
    float r0[16];
    float m1[16], m2[16];

    m1[ 0] = 1.0f; m1[ 4] = 0.0f; m1[ 8] = 0.0f; m1[12] = 0.0f;
    m1[ 1] = 0.0f; m1[ 5] = 1.0f; m1[ 9] = 0.0f; m1[13] = 0.0f;
    m1[ 2] = 0.0f; m1[ 6] = 0.0f; m1[10] = 1.0f; m1[14] = 0.0f;
    m1[ 3] = 0.0f; m1[ 7] = 0.0f; m1[11] = 0.0f; m1[15] = 1.0f;

    m2[ 0] = 1.0f; m2[ 4] = 0.0f; m2[ 8] = 0.0f; m2[12] = 0.0f;
    m2[ 1] = 0.0f; m2[ 5] = 1.0f; m2[ 9] = 0.0f; m2[13] = 0.0f;
    m2[ 2] = 0.0f; m2[ 6] = 0.0f; m2[10] = 1.0f; m2[14] = 0.0f;
    m2[ 3] = 0.0f; m2[ 7] = 0.0f; m2[11] = 0.0f; m2[15] = 1.0f;

    r0[ 0] = 0.0f; r0[ 4] = 0.0f; r0[ 8] = 0.0f; r0[12] = 0.0f;
    r0[ 1] = 0.0f; r0[ 5] = 0.0f; r0[ 9] = 0.0f; r0[13] = 0.0f;
    r0[ 2] = 0.0f; r0[ 6] = 0.0f; r0[10] = 0.0f; r0[14] = 0.0f;
    r0[ 3] = 0.0f; r0[ 7] = 0.0f; r0[11] = 0.0f; r0[15] = 0.0f;

    Matrix4x4_Multiply_SSE(r0, m1, m2);
    Matrix4x4_Multiply_SSE(r0, m1, m2);

    return (0);
}

在第二个 movups 之后,堆栈会更改结果值(存储在堆栈中),并存储 xmm0 的值存储在结果中的修改(和错误)地址。

从* Matrix4x4_Multiply_SSE *退出后,原始内存未被修改。

我缺少什么?

3 个答案:

答案 0 :(得分:2)

对齐校正错误。您需要添加alignment-misalignment来更正对齐方式。所以代码应该是:

mAlignedBuffer = 
    new IntPtr(mUnmanagedBuffer.ToInt64() + alignment - misalignment);

但是,我建议您先在本机设置中测试该功能。一旦你知道它在那里工作,你就可以转移到托管设置,并知道任何问题都是由托管代码引起的。

答案 1 :(得分:1)

你的装配是有缺陷的。

之间存在差异
void DoSomething(int *x)
{
    __asm
    {
        mov x[0], 10   // wrong
            mov [x], 10    // also wrong
        mov esi,x      // first get address
        mov [esi],500  // then assign - correct
    }
}

前两个示例没有写入指向指针的内存位置,而是写入指针本身的存储位置。由于参数来自堆栈,因此您使用movups指令覆盖了堆栈。调用时,您可以在调试器窗口中看到此信息。

int x=0;
DoSomething(&x);

使用mov [x],10你不会将x设置为10但是你会写入你的堆栈。

答案 2 :(得分:0)

我找到了解决方案。在CPU寄存器上加载指针值,并使用寄存器重定向到内存:

mov esi, result;
movups [esi][ 0], xmm0;

使用这些说明可使代码按预期工作。


但问题仍然没有完全解决,因为 movups 指令可以作为内存地址的第一个参数;所以,如果有人知道发生了什么,我很高兴看到最好的答案。