cmm调用格式为foreign primop(整数-gmp为例)

我一直在检查整数gmp源代码,以了解如何外国的primops可以实现在cmm方面logging在GHC 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头 :

 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混淆的情况下开始)。 进一步来说:

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

我所理解的函数(从Prim.hs中的函数签名)获取一个int,并返回(int,byte数组)(存储在代码中的sp中)。

对于任何想要在if block调用内联函数的人 ,这是gmp mpz_init_si函数的实现 。 我的猜测是,如果你通过ccall调用在目标文件中定义的函数,它不能被内联(这是有道理的,因为它是对象代码,而不是中间代码 – LLVM方法似乎更适合通过LLVM IR内联)。 所以,优化是定义一个内联函数的cmm表示。 如果这个猜测是错误的,请纠正我。

第5-9行的解释将非常感谢。 我对integer-gmp文件中定义的其他macros有更多的疑问,但在一篇文章中可能太多了。 如果你可以用Haskell的wiki页面或博客回答这个问题(你可以把链接作为答案),这将是非常感激(如果你这样做,我也将欣赏一步一步走过一个整数-gmp cmmmacros,如GMP_TAKE2_RET1 )。

这些行在Haskell堆中分配一个新的ByteArray#,所以为了理解它们,首先需要了解一下GHC的堆是如何pipe理的。

  • 每个function(=执行Haskell代码的操作系统线程)都有自己的专用托儿所 ,这是一个正常的堆区,像这样的小分配。 对象只是简单地分配到这个区域从低地址到高地址,直到能力尝试进行分配,超过了托儿所剩余的空间,触发垃圾收集器。

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

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

  • https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Storage/HeapObjects有关于单个堆对象布局的信息。; 值得注意的是,每个堆对象都以一个信息指针开始,该信息指针 (除其他外)标识它是什么types的堆对象。

HaskelltypesByteArray#使用堆对象typesARR_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的金额分配。 第二行检查堆溢出。 第三行logging了我们尝试分配的数量,所以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的信息指针,将新创build的堆对象标识为ARR_WORDS。 CCCS是当前的成本中心堆栈,仅用于分析。 启用分析时,每个堆对象都包含一个额外的字段,该字段基本上标识谁负责分配。 在非剖析构build中,没有CCCS,SET_HDR只是设置信息指针。 最后,第9行填写ByteArray#的大小字段。 函数的其余部分填充有效负载并返回符号值和ByteArray#对象指针。

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

在这里输入图像说明

所需的知识

为了进行算术和逻辑运算,计算机在其CPU (中央处理单元)中具有称为ALU (算术逻辑单元)的数字电路 。 一个ALU从input寄存器加载数据。 处理器寄存器是在位于CPU芯片中的SRAM (静态随机存取存储器)中实现的L1高速缓冲存储器 (3个CPU时钟周期内的数据请求)中的存储器 。 处理器通常包含几种寄存器,通常由它们可以容纳的位数来区分。

数字以离散位表示可以保存有限数量的值。 通常,编号具有以下基本types ( 在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 

这些types的固定精度algorithm已经在硬件上实现字大小是指一次可以由计算机的CPU处理的位数。 对于x86架构,这是32位x6464位

IEEE 754定义了{16,32,64,128}位数的浮点数标准。 例如,32位点数(具有4 294 967 296个唯一值)可以保持近似值 [-3.402823e38至3.402823e38]精度至less为7 浮点数。

在这里输入图像说明

此外

简称GMP意思是GNU Multiple Precision Arithmetic Library,并增加了对软件仿真任意精度algorithm的支持。 格拉斯哥Haskell编译器整数实现使用这个。

GMP旨在比所有操作数大小的任何其他的bignum库更快。 这样做的一些重要因素是:

  • 使用全字作为基本的算术types。
  • 对不同的操作数大小使用不同的algorithm; 对于很大的数字,速度更快的algorithm对于小数字通常会更慢。
  • 针对最重要的内部循环高度优化的汇编语言代码,专用于不同的处理器。

回答

对于一些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 。 所谓的function:

 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); }; 

这是不完全正确的,因为返回types应该是return (s,p); 哪里

  • s是价值
  • p是标志

为了解决这个GMP包装应该创build。 这已经在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; 

如下面所述

  • 分配空间作为新词
  • 创build指针
  • 设置指针值
  • 设置指针types的大小