快速谷歌搜索会出现
GHC评论:
偶尔,将Haskell闭包视为C函数指针是很方便的。例如,如果我们想在现有的C库中安装Haskell回调,则这非常有用。借助调整器thunk实现了此功能。
调整器thunk是一种动态分配的代码片段,允许将Haskell闭包视为C函数指针。
稳定指针提供了一种方法,使外部世界可以访问并评估Haskell堆对象,并且运行时系统提供了一小组操作来实现这一点。因此,假设我们在C中手头有一个稳定指针,我们可以跳转到Haskell世界并评估回调过程。在某些使用回调的情况下,这可以正常工作,但确实需要外部代码了解稳定指针及其处理方式。我们希望隐藏回调的Haskell特性,并像任何其他C函数指针一样调用它。
进入调整器thunk。调整器thunk是即时生成的小段代码(每个导出的Haskell闭包一个),当使用某些“通用”调用约定(例如,在平台X上的C调用约定)输入时,在调用另一个(静态)C函数存根之前,将推送一个隐式稳定指针(到Haskell回调)。该存根负责通过其稳定指针进入Haskell代码。
调整器thunk在C堆上分配,并在分配给Haskell(IO)操作的函数指针之前,在Haskell内部调用。用户代码不应明确调用它。
调整器thunk与C函数指针在一个方面不同:当代码完成时,必须释放它以释放Haskell和C资源。否则,将导致C和Haskell两侧的内存泄漏。
我记得在某个地方读到过,包装器FFI导入实际上是GHC执行运行时代码生成的唯一位置。
我认为评论所说的是,您的
createFunPtr
在编译时被定义为类似于这样的东西(我设置了
-ddump-simpl
以获取
createFunPtr
的核心代码,并且以下是我的尝试将其反汇编回Haskell)。
createFunPtr fun = do stable <- newStablePtr fun
pkg_ccall stable :: IO (FunPtr (Int -> Int))
newStablePtr
是 StablePtr
API 的一部分,它允许 Haskell 将对 Haskell 对象的引用导出到外部代码。GC 允许在创建 adjustor thunk 后移动传递给 createFunPtr
函数的函数。因此,该 adjustor 需要一个引用到仍然在 GC 后保持的函数,并且 stable pointers 提供了该功能。
pkg_ccall
(实际上相当神奇)在C堆上分配调整器thunk的空间。这个空间必须稍后使用freeHaskellFunPtr
释放,否则会在C堆(保存调整器)和Haskell堆(保存函数闭包,直到稳定指针被释放之前无法进行垃圾回收)上发生内存泄漏。调整器的内容取决于平台以及GHC是否在构建时配置为使用libffi进行调整器。实际的汇编代码可以在相关RTS文件的注释中找到,但要点通常是:
int adjustor(int arg) {
return zdmainzdTzdTzucreateAddPtr($stable, arg);
}
zdmainzdTzdTzucreateAddPtr
是一个存根,它会取消引用给定的稳定指针并调用其中生成的Haskell函数。它是静态的,嵌入到二进制文件中,并且与此类似:(如果您传递GHC -v
和-keep-tmp-files
,则应该能够找到包含真实定义的ghc_<some_num>.c
文件,该文件需要进行一些簿记。)
HsInt zdmainzdTzdTzucreateAddPtr(StgStablePtr ptr, HsInt a) {
HaskellObj fun, arg, app, ret;
fun = deRefStablePtr(ptr);
arg = rts_mkInt(a);
app = rts_apply(fun, arg);
eval(app, &ret);
return rts_getInt(ret);
}