首页 > 解决方案 > function Power(x,y) in assembly ia32 max 32 bit

问题描述

i using IA32 assembly,

I would like to create a function which, given the two input numbers, rests the value of the power, the result must be maximum in 32 bits. The base is unsigned int base always positive while the exponent is int. so both negative and 0. thanks in advance

标签: functionassemblyx86

解决方案


结果也应该是整数吗? x^-nis just ,对于除 之外1/x^n的任何值都舍入为零。例如是。x1pow(16, -2)1/256

对于整数返回值,只需检查正数n或返回10。对于 FP 返回值,您可以使用绝对值的无符号实现,并有条件地取倒数。

对于较大的n量级,您可能希望使用基于 FP exp/log 的实现(请参阅我对问题的评论,以及我如何自己编写幂函数?)而不是循环位实现。


对于具有无符号指数(或有符号正数)的纯整数,一个不错的无分支实现是可能的,使用通常的算法右移指数并在当前位被设置的情况下将结果相乘。(有关算法背后的数学和 Python 代码,请参见https://eli.thegreenplace.net/2009/03/21/efficient-integer-exponentiation-algorithms 。)

我们可以shr对移出的位使用右移和 CMOV,并对剩余的值使用循环分支。

这个版本在 x86-64 System V 相同的寄存器中传递 args,但它可以在 32 位模式下进行组装。您当然可以将其调整为您喜欢的任何调用约定;它需要 4 个寄存器,因此您可能需要在 32 位调用约定中保存/恢复调用保留的 reg。

它类似于但比从 x86-64 C 编译器获得的 Python 实现的直接端口更好。(https://godbolt.org/z/L9Kb98 gcc / clang 用test sil,1/cmov` 构造循环,与 shr 结果上的循环分支分开。)

;; untested
; integer power
; args in EDI,ESI  (like the x86-64 System V calling convention)
; returns in EAX
; clobbers: EDX, EDI, ESI
ipown:   ; (int a (EDI), unsigned n (ESI))
    mov    eax, 1       ; res = 1

    test   edi,edi
    jz    .zero_exponent

.loop:
    mov    edx, eax      ; tmp = res
    imul   eax, edi      ; res *= a  (will be overwritten with old res if n&1 == 0)
    imul   edi, edi      ; a*=a

    shr    esi, 1        ; n>>=1  setting ZF according to result, and CF= bit shifted out (old_n&1)
    cmovnc  eax, edx     ; res = tmp if the bit was zero so we don't do res *= a this time
    jnz   .loop

.zero_exponent:
    ret

在 Broadwell 或更高版本的 Intel CPU 或 AMD Ryzen 上,我们有 1 个周期 CMOV 和 3 个周期延迟imul,这有望在每次迭代中运行 4 个周期(通过 EAX 的 imul -> cmov 依赖链)。

imul在现代 x86 上完全流水线化(或至少在 AMD Bulldozer 系列上充分流水线化),但每个时钟的吞吐量仅为 1,因此imul可能都在等待edi准备就绪的两条指令之间存在潜在的资源冲突。但是通过 EDI 的 3 周期 dep 链应该领先于 4 周期 imul/cmov 链,因此在 animul eax,edi和 animul edi,edi都准备好启动的任何周期中,最旧的就绪优先调度应该做出正确的选择并启动imul eax,edi.

请注意,mov edx,eax不在关键路径上:它可以与imul. 如果我这样做了tmp *= edi,那mov将是关键路径,并且会损害 CPU 的延迟,而无需对整数寄存器进行移动消除。


当然,最大循环跳数只有 32(如果设置了指数的高位),所以乱序执行可以看穿这个直到循环结束(并希望解决循环退出错误预测在乘法到达那里之前)。

循环中的指令很少(与其吞吐量相比),因此它应该能够与之前/之后的独立指令显着重叠。

预期延迟大约4 cycles *trip_count为 = 4 * log2(n),即 4 * 指数中最高设置位的位置。


对于 FP 版本,x87 实际上可能对fcmov. 否则,您可以使用移位和 SSE4blendvps根据另一个寄存器的高位进行混合。 0.0是加法恒等式,而不是乘法恒等式,因此与比较结果进行与运算不仅有效。


推荐阅读