外国primop的cmm调用格式(integer-gmp示例)

Sal*_*Sal 63 haskell ghc

我一直在检查integer-gmp源代码,以了解如何根据GHC Primops页面上记录的cmm实现外部primops .我知道使用llvm hack或fvia-C/gcc 实现它们的技术- 这对我来说是理解interger-gmp库使用的第三种方法的更多学习经验.

所以,我抬头一看CMM教程MSFT页(PDF链接),经历了GHC CMM页,仍然存在一些悬而未决的问题(很难保持在头所有这些概念没有挖掘到CMM这就是我现在所做的).这是来自integer-bmp cmm文件的代码片段:

integer_cmm_int2Integerzh (W_ val)
{
   W_ s, p; /* to avoid aliasing */

   ALLOC_PRIM_N (SIZEOF_StgArrWords + WDS(1), integer_cmm_int2Integerzh, val);

   p = Hp - SIZEOF_StgArrWords;
   SET_HDR(p, stg_ARR_WORDS_info, CCCS);
   StgArrWords_bytes(p) = SIZEOF_W;

   /* mpz_set_si is inlined here, makes things simpler */
   if (%lt(val,0)) {
        s  = -1;
        Hp(0) = -val;
   } else {
     if (%gt(val,0)) {
        s = 1;
        Hp(0) = val;
     } else {
        s = 0;
     }
  }

   /* returns (# size  :: Int#,
                 data  :: ByteArray#
               #)
   */
   return (s,p);
}
Run Code Online (Sandbox Code Playgroud)

ghc cmm标头中所定义:

W_ is alias for word.
ALLOC_PRIM_N is a function for allocating memory on the heap for primitive object.
Sp(n) and Hp(n) are defined as below (comments are mine):
#define WDS(n) ((n)*SIZEOF_W) //WDS(n) calculates n*sizeof(Word)
#define Sp(n)  W_[Sp + WDS(n)]//Sp(n) points to Stackpointer + n word offset?
#define Hp(n)  W_[Hp + WDS(n)]//Hp(n) points to Heap pointer + n word offset?
Run Code Online (Sandbox Code Playgroud)

我不明白第5-9行(如果你有1/0的混乱,第1行是开始).进一步来说:

  • 为什么ALLOC_PRIM_N(bytes,fun,arg)的函数调用格式是这样的?
  • 为什么p会这样操纵?

据我所知(从看着函数签名函数Prim.hs)采用int,并返回一个(整型,字节阵列)(存储在s,p分别在代码).

对于任何想要内联调用的人来说if block,它是gmp mpz_init_si函数的 cmm实现.我的猜测是,如果通过ccall调用目标文件中定义的函数,则无法内联(这是有意义的,因为它是对象代码,而不是中间代码 - LLVM方法似乎更适合通过LLVM IR内联).因此,优化是定义要内联的函数的cmm表示.如果这个猜测是错误的,请纠正我.

将非常感谢第5-9行的解释.关于integer-gmp文件中定义的其他宏,我有更多的问题,但在一篇文章中提问可能太多了.如果您可以使用Haskell维基页面或博客回答问题(您可以将链接发布为答案),那将非常感激(如果您这样做,我也会欣赏一个整数的逐步演练) -gmp cmm宏如GMP_TAKE2_RET1).

Rei*_*ton 5

这些行在Haskell堆上分配一个新的ByteArray#,因此要了解它们,首先需要了解一下GHC堆的管理方式.

  • 每个功能(=执行Haskell代码的OS线程)都有自己的专用托儿所,它是一个正常的小区域,像这样的小分配.对象简单地从低地址到高地址顺序分配到该区域,直到能力尝试进行超过托儿所中剩余空间的分配,这会触发垃圾收集器.

  • 所有堆对象都与字大小的倍数对齐,即32位系统上的4个字节和64位系统上的8个字节.

  • Cmm级寄存器Hp指向已在托儿所中分配的最后一个字(的开头).HpLim指向可以在托儿所分配的最后一个单词.(HpLim也可以被另一个线程设置为0来停止GC的世界,或发送异步异常.)

  • https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/HeapObjects提供有关各个堆对象布局的信息.值得注意的是,每个堆对象都以一个info指针开头,该指针(除其他外)标识它是什么类型的堆对象.

Haskell类型ByteArray#是使用堆对象类型ARR_WORDS实现的.ARR_WORDS对象只包含(后跟一个信息指针)一个大小(以字节为单位),后跟任意数据(有效负载).GC不解释有效负载,因此它不能存储指向Haskell堆对象的指针,但它可以存储任何其他内容.SIZEOF_StgArrWords是所有ARR_WORDS堆对象共有的头的大小,在这种情况下,有效负载只是一个单词,因此SIZEOF_StgArrWords + WDS(1)是我们需要分配的空间量.

ALLOC_PRIM_N(SIZEOF_StgArrWords + WDS(1),integer_cmm_int2Integerzh,val)扩展为类似

Hp = Hp + (SIZEOF_StgArrWords + WDS(1));
if (Hp > HpLim) {
    HpAlloc = SIZEOF_StgArrWords + WDS(1);
    goto stg_gc_prim_n(integer_cmm_int2Integerzh, val);
}
Run Code Online (Sandbox Code Playgroud)

第一行将Hp增加要分配的数量.第二行检查堆溢出.第三行记录了我们尝试分配的金额,因此GC可以撤消它.第四行称为GC.

第四行是最有趣的.参数告诉GC如何在垃圾收集完成后重新启动线程:它应该使用参数val重新调用integer_cmm_int2Integerzh.stg_gc_prim_n中的"_n"(以及ALLOC_PRIM_N中的"_N")表示val是非指针参数(在本例中为Int#).如果val是指向Haskell堆对象的指针,则GC需要知道它是活动的(因此它不会被收集)并使用对象的新地址重新调用我们的函数.在那种情况下,我们使用_p变体.还有一些变体,如_pp用于多个指针参数,_d用于Double#参数等.

在第5行之后,我们已经成功分配了一个SIZEOF_StgArrWords + WDS(1)字节的块,并且记住,Hp指向它的最后一个字.因此,p = Hp - SIZEOF_StgArrWords将p设置为该块的开头.第8行填充p的info指针,将新创建的堆对象标识为ARR_WORDS.CCCS是当前的成本中心堆栈,仅用于分析.启用性能分析时,每个堆对象都包含一个额外的字段,该字段基本上标识谁负责其分配.在非分析构建中,没有CCCS,SET_HDR只设置信息指针.最后,第9行填写了ByteArray#的大小字段.函数的其余部分填充有效负载并返回符号值和ByteArray#对象指针.

所以,这最终更多的是关于GHC堆而不是关于Cmm语言,但我希望它有所帮助.