首页 文章

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

提问于
浏览
7

对我之前的question的回答表明,Haskell将plusWord2#表示为llvm.uadd.with.overflow . 我'm looking to do long addition with carry, like how the x86 ADC instruction works. This instruction not only adds it'的两个参数,还添加了进位的内容 .

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

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代码生成有效的加法 .

Notes:

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

我've found that ' clang'有__builtin_addc,这是一个三个参数加法的形式,它不仅需要两个数字而且还包含一个进位,但是GHC并不知道这是如何有用的 .

1 回答

  • 1

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

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

相关问题