【问题标题】:Using `foreign import prim` with a C function using STG calling convention将`foreign import prim`与使用STG调用约定的C函数一起使用
【发布时间】:2017-05-03 23:03:56
【问题描述】:

我有一个简单的 C 例程,它接受四个单词并返回四个单词,gcc 可以优化并发出 GHC 不支持的一些 primops。我正在尝试对调用此过程的各种方法进行基准测试,但在尝试将技术 described here 调整为使用 foreign import prim 时遇到了麻烦。

下面的意思只是给每个输入单词加 1,但是有段错误。

Main.hs:

{-# LANGUAGE GHCForeignImportPrim #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples  #-}
{-# LANGUAGE UnliftedFFITypes #-}
import Foreign.C
import GHC.Prim
import GHC.Int
import GHC.Word

foreign import prim "sipRound"
  sipRound_c# :: Word# -> Word# -> Word# -> Word# -> (# Word#, Word#, Word#, Word# #)

sipRound_c ::  Word64 -> Word64 -> Word64 -> Word64 -> (Word64, Word64, Word64, Word64)
sipRound_c (W64# v0) (W64# v1) (W64# v2) (W64# v3) = case sipRound_c# v0 v1 v2 v3 of
  (# v0', v1', v2', v3' #) -> (W64# v0', W64# v1', W64# v2', W64# v3')

main = do
  print $ sipRound_c 1 2 3 4

sip.c:

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>



// define a function pointer type that matches the STG calling convention
typedef void (*HsCall)(int64_t*, int64_t*, int64_t*, int64_t, int64_t, int64_t, int64_t,
                       int64_t, int64_t, int64_t*, float, float, float, float, double, double);

extern void
sipRound(
    int64_t* restrict baseReg,
    int64_t* restrict sp,
    int64_t* restrict hp,

    uint64_t v0, // R1
    uint64_t v1, // R2
    uint64_t v2, // R3
    uint64_t v3, // R4
    int64_t r5,
    int64_t r6,

    int64_t* restrict spLim,
    float f1,
    float f2,
    float f3,
    float f4,
    double d1,
    double d2)
{

    v0 += 1;
    v1 += 1;
    v2 += 1;
    v3 += 1;

    // create undefined variables, clang will emit these as a llvm undef literal
    const int64_t iUndef;
    const float fUndef;
    const double dUndef;

    const HsCall fun = (HsCall)sp[0];
    return fun(
            baseReg,
            sp,
            hp,

            v0,
            v1,
            v2,
            v3,
            iUndef,
            iUndef,

            spLim,
            fUndef,
            fUndef,
            fUndef,
            fUndef,
            dUndef,
            dUndef);
}

我真的不知道我在做什么。有没有办法调整该博客文章中的技术?这是个坏主意吗?

【问题讨论】:

  • 这是非常非常低的级别。你真的需要这种水平的性能吗? AFAICS,博客文章使用 clang 生成 LLVM,然后通过将 C 调用约定更改为 LLVM cc10(GHC 之一)来修补输出,然后使用 llc 编译结果。可怕的。这远远超出了舒适区(即,我对这个级别发生的事情知之甚少),但使用 cc10 调用约定似乎至关重要!
  • @chi 我正在对从正常的外国 ccall 返回 4 个单词的结构进行基准测试,但我预计开销太大而不值得(但可能惊讶);对于我正在处理的库,我正在经历所有这些尝试在不使用 LLVM 后端时生成旋转指令。但这也是出于好奇
  • 对,这行不通。正如博客文章所说:“这仍然是一个 ccall 函数,但我们稍后会修复它。目前无法在 clang 中将其定义为 cc10(LLVM 的 GHC 调用约定的内部名称)。” C 调用约定与 GHC 不同。例如,C 认为第一个参数 baseReg 应该在 rdi 中(假设 x86_64),但 GHC 在 r13 中传递 baseReg

标签: c haskell segmentation-fault ghc ffi


【解决方案1】:

如果你愿意手写汇编,你可以这样做(对于 x86_64)。将它放在一个扩展名为 .s 的文件中,并在 ghc 命令行上将其作为参数提供。

.global sipRound
sipRound:
    inc %rbx
    inc %r14
    inc %rsi
    inc %rdi
    jmp *(%rbp)

STG寄存器和机器寄存器之间的映射在https://github.com/ghc/ghc/blob/master/includes/stg/MachRegs.h#L159中定义。

请注意,仍然会涉及函数调用,因此它不会像您从 LLVM 获得的代码那样高效。

【讨论】:

  • 我现在明白了很多,谢谢!两个快速跟进:为了向 ghc 公开它,我们是否需要在 ac 文件中使用内联 asm,例如 void sipRound() { asm ( ... )} 或者是否有更好的方法来做到这一点(这就是你说“将涉及函数调用”的原因吗? )?我可以认为这是一个稳定的 API 吗?看起来映射没有太大变化,并且必须保持稳定以与 llvm 等协调。
  • 你不能使用void sipRound() { asm ( ... )},因为那会添加一个C函数序言。只需像任何其他输入一样提供汇编程序输入。我所说的函数调用是你写给sipRound_c# 的调用。关键是 GHC 不能“内联”sipRound,因为它是在汇编中实现的。 (相比之下,当您调用+# 时,GHC 不会生成对顶级函数的调用;它会发出添加指令。)
  • ABI 变化不大,但不能保证稳定;不同版本的 GHC 之间没有 ABI 兼容性,并且每个版本的 GHC 仅适用于一个版本的 LLVM,因此将修改后的调用约定引入下一个版本的 LLVM 并不难。
  • 是的,C 编译器可能会意识到序言是不必要的并且不插入序言,但不能保证一般情况下可以正常工作。
  • 刚刚发布了一个后续问题:stackoverflow.com/questions/41528208/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-04
  • 2021-11-24
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多