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

时间:2015-11-10 02:25:23

标签: haskell llvm gmp integer-arithmetic

我之前question的回答表明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,但这会停止内联,并且我怀疑与小型(即&lt; = 256位)操作数的内联代码相比相对较慢。

我发现&#39; clang&#39;有__builtin_addc,这是一个三个参数加法的形式,它不仅需要两个数字而且还包含一个进位,但是GHC不能通过clang进行编译,所以我不知道它是如何有用的。

1 个答案:

答案 0 :(得分:1)

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

然而,今天不要期望用LLVM生成好的代码。请参阅http://llvm.org/PR20748,其中记录了我们目前在x86上为这样的琐碎模式生成的绝对可怕的代码。但是一旦修复了这个bug,就应该得到你想要的生成代码。