cmm调用格式为外国primop(整数
我一直在检查integer-gmp源代码,以了解如何根据GHC Primops页面上记录的cmm方式实现外国的primops。 我知道使用llvm hack或fvia-C / gcc来实现它们的技术 - 这对我来说更加了解interger-gmp库使用的第三种方法。
因此,我在MSFT页面上查找了CMM教程(pdf链接),通过了GHC CMM页面,仍然有一些未解答的问题(难以将所有这些概念放在头脑中,而不深入CMM,这正是我现在所做的)。 有这个代码片段从整数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);
}
正如ghc cmm header中所定义的那样:
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?
我不明白第5-9行(第1行是在你有1/0混淆的情况下开始)。 进一步来说:
我理解它的功能(通过查看Prim.hs中的函数签名)接受一个int,并返回一个(int,byte数组)(存储在代码中的s
, p
中)。
对于任何想要在if block
内联调用的人,这是cmp实现gmp mpz_init_si函数。 我的猜测是,如果你通过ccall调用在目标文件中定义的函数,它不能被内联(这是有道理的,因为它是对象代码,而不是中间代码 - LLVM方法似乎更适合通过LLVM IR内联)。 所以,优化是定义一个cmm表示要内联的函数。 如果这个猜测是错误的,请纠正我。
第5-9行的解释将非常感谢。 我对integer-gmp文件中定义的其他宏有更多的疑问,但是在一篇文章中询问可能太多了。 如果你可以用Haskell的wiki页面或博客回答这个问题(你可以将链接发布为答案),这将是非常值得赞赏的(如果你这样做,我也会很感激一步一步地通过一个整数-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具有关于单个堆对象布局的信息。 值得注意的是,每个堆对象都以信息指针开始,该信息指针(除其他外)标识它是什么类型的堆对象。
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);
}
第一行将Hp增加分配的金额。 第二行检查堆溢出。 第三行记录了我们尝试分配的数量,因此GC可以撤消它。 第四行称GC。
第四行是最有趣的。 参数告诉GC如何在垃圾收集完成后重新启动线程:它应该用参数val重新调用integer_cmm_int2Integerzh。 stg_gc_prim_n中的“_n”(以及ALLOC_PRIM_N中的“_N”)表示val是非指针参数(在本例中为Int#)。 如果val是一个指向Haskell堆对象的指针,GC需要知道它是活的(因此它不会被收集),并用对象的新地址重新激活我们的函数。 在这种情况下,我们会使用_p变体。 还有像_pp这样的多个指针参数的变体,用于Double#参数的_d等等。
在第5行之后,我们已经成功地分配了一块SIZEOF_StgArrWords + WDS(1)个字节,并记住Hp指向它的最后一个单词。 因此,p = Hp - SIZEOF_StgArrWords将p设置为该块的开头。 第8行填充p的信息指针,将新创建的堆对象标识为ARR_WORDS。 CCCS是当前的成本中心堆栈,仅用于分析。 启用概要分析时,每个堆对象都包含一个额外字段,该字段基本确定谁负责分配。 在非剖析构建中,没有CCCS,SET_HDR只是设置信息指针。 最后,第9行填写ByteArray#的大小字段。 函数的其余部分填充有效负载并返回符号值和ByteArray#对象指针。
因此,这最终是关于GHC堆而不是Cmm语言,但我希望它有帮助。
所需的知识
为了进行算术和逻辑运算,计算机在其CPU (中央处理单元)中具有称为ALU (算术逻辑单元)的数字电路 。 一个ALU从输入寄存器加载数据。 处理器寄存器是存储器存储在L1高速缓存在SRAM实现(静态随机存取存储器)位于CPU芯片 (3 CPU时钟滴答内的数据的请求)。 处理器通常包含几种寄存器,通常由它们可容纳的位数来区分。
数字以离散位表示可以保存有限数量的值。 通常情况下,编号具有以下基本类型 (在Haskell中):
8 bit numbers = 256 unique representable values
16 bit numbers = 65 536 unique representable values
32 bit numbers = 4 294 967 296 unique representable values
64 bit numbers = 18 446 744 073 709 551 616 unique representable values
这些类型的固定精度算法已经在硬件中实现 。 字大小是指一次可以由计算机的CPU处理的位数。 对于x86架构,这是32位,而x64是64位 。
IEEE 754为{16,32,64,128}位数字定义了浮点数标准。 例如,32位点数(具有4 294 967 296个唯一值) 可保存 近似值 [-3.402823e38至3.402823e38] , 精度至少为7 位浮点数。
此外
首字母缩略词GMP表示GNU多精度算术库,并增加了对软件仿真任意精度算法的支持。 格拉斯哥Haskell编译器整数实现使用这个。
GMP旨在比所有操作数大小的任何其他bignum库更快。 这样做的一些重要因素是:
回答
对于一些Haskell可能有点难以理解的语法,所以这里是JavaScript版本
var integer_cmm_int2Integerzh = function(word) {
return WORDSIZE == 32
? goog.math.Integer.fromInt(word))
: goog.math.Integer.fromBits([word.getLowBits(), word.getHighBits()]);
};
goog是Google Closure所使用的库类位于Math.Integer中。 所谓的功能:
goog.math.Integer.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = goog.math.Integer.IntCache_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
goog.math.Integer.IntCache_[value] = obj;
}
return obj;
};
goog.math.Integer.fromBits = function(bits) {
var high = bits[bits.length - 1];
return new goog.math.Integer(bits, high & (1 << 31) ? -1 : 0);
};
这是不完全正确的,因为返回类型应该是return (s,p);
哪里
为了解决这个GMP包装应该被创建。 Haskell已经在JavaScript编译器项目(源链接)中完成了这个工作。
第5-9行
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;
如下面所述