我一直在检查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行是开始).进一步来说:
据我所知(从看着函数签名函数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).
这些行在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语言,但我希望它有所帮助.
| 归档时间: |
|
| 查看次数: |
991 次 |
| 最近记录: |