使用LLVM的大数运算(来自Haskell)

对我之前问题的回答表明,Haskell将plusWord2#表示为llvm.uadd.with.overflow 。 我希望能够像x86 ADC指令的工作原理那样进行长时间添加。 该指令不仅增加了两个参数,还增加了进位位的内容。

然后可以添加如下所示的长数字:

ADD x1 y1
ADC x2 y2
ADC x3 y3
...

每个单词产生一个指令(无需任何周围的移动等)。

我查看了GMP库,以及它是如何在其通用C代码中进行长时间添加的。 这是mpn/generic/add_n.c的摘录

sl = ul + vl;
cy1 = sl < ul;
rl = sl + cy;
cy2 = rl < sl;
cy = cy1 | cy2;

注意它保存了原始加法和进位位的进位。 这些操作中只有一个可以运载,所以或之后的运载就足够了。

显然,GMP对特定的平台有特定的汇编代码,但我认为泛型代码将是一个很好的基础,因为它可能会被编写成合理的代码。 plusWord2#原始操作意味着我不需要做傻比较来获取进位,所以我在Haskell中实现了如下的GMP泛型代码:

addWithCarry :: Word# -> Word# -> Word# -> (# Word#, Word# #)
addWithCarry x y c =
  let 
    (# c1, r1 #) = plusWord2# x y
    (# c2, r2 #) = plusWord2# r1 c
  in
    (# or# c1 c2, r2 #)

不幸的是,这会导致x86代码将进位位保存到寄存器中,然后再自行添加进位位,并添加这两个数字,从而导致每个单词三个或四个指令,而不是一个。

所以我想知道的是,我如何将llvm.uadd.with.overflow结合起来,在x86上创建一个ADC指令链来实现多精度算法。 如果我的LLVM代码生成了高效的长时间加法,我希望能够将它转换回Haskell原语操作,以直接从Haskell代码生成有效的加法。

笔记:

我当然可以使用Haskell的FFI调用内联汇编或GMP,但是这会停止内联,并且与小内存(即<= 256位)操作数的内联代码相比,我认为它相对较慢。

我发现'clang'有__builtin_addc ,这是一个三个参数的加法形式,它不仅需要两个数字,而且还有一个进位,但GHC不能通过clang编译,所以我不明白这是如何有用的。


这里要做的正确的事情是确保您的Haskell前端在LLVM的IR中使用Clang在使用__builtin_addc时形成的相同模式来表示您的携带算法。

但是,不要期望今天能够用LLVM生成良好的代码。 请参阅http://llvm.org/PR20748,其中记录了我们当前在x86上生成的绝对可怕的代码,用于处理这类微不足道的模式。 但是一旦修正了这个错误,你应该得到你想要的生成代码。

链接地址: http://www.djcxy.com/p/43219.html

上一篇: Big number arithmetic using LLVM (from Haskell)

下一篇: Repa performance versus lists