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

时间:2013-04-17 18:34:03

标签: haskell ghc

我一直在检查integer-gmp源代码,以了解如GHC Primops page所记载的cmm如何实现外国初学者。我知道使用llvm hack或fvia-C / gcc实现它们的技术 - 这对我来说是理解interger-gmp库使用的第三种方法的更多学习经验。

所以,我查看了MSFT页面(pdf link)上的CMM教程,经历了GHC CMM page,仍然有一些未解决的问题(很难将所有这些概念保持在头脑而不深入CMM这是什么我现在在做)。这个代码片段来自integer-bmp cmm file

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/0混淆,第1行就是开头)。更具体地说:

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

我理解的功能(通过查看Prim.hs中的函数签名)接受一个int,并返回一个(int,byte数组)(分别存储在sp在代码中)。

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

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

2 个答案:

答案 0 :(得分: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);
}

第一行将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语言,但我希望它有所帮助。

答案 1 :(得分:3)

enter image description here

所需知识

为了执行算术和逻辑操作,计算机的 CPU <数字电路称为 ALU (算术逻辑单元)< / strong>(中央处理单位)。 ALU从输入寄存器加载数据。 处理器寄存器 L1缓存内存存储 <在 SRAM 中实现的3个CPU时钟周期内的数据请求<随机存取存储器)位于 CPU芯片。处理器通常包含几种寄存器,通常由它们可以容纳的位数区分。

数字以离散位表示,可以保存有限数量的值。通常,数字具有以下编程语言(in 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 浮点数的

enter image description here

另外

首字母缩略词 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 library类位于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);其中

  • s是值
  • p是标志

为了解决这个问题,应该创建GMP包装器。这已在Haskell to JavaScript compiler项目(source link)中完成。

第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;

如下

  • 将空格分配为新单词
  • 创建指向它的指针
  • 设置指针值
  • 设置指针类型大小