You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无Zbb扩展的32位RISC-V无分支全范围前导零计数优化问询

无分支32位RISC-V前导零计数(CLZ)优化需求

需求背景

为无浮点硬件支持、无高级位操作Zbb扩展的32位RISC-V平台,实现符合IEEE-754标准的抗侧信道单精度平方根。硬件支持整数乘法指令(MUL、MULHU)且延迟固定。对非规格化操作数进行归一化时需前导零计数(CLZ),因抗侧信道设计要求CLZ实现为无分支类型。

初始ARMv4t移植实现

最初使用20年前ARMv4t平台的32位全范围CLZ实现(输入为0时返回32),C99代码如下:

uint32_t cntlz (uint32_t a)
{ 
    uint32_t n = 0;
#if 0
    n = __builtin_clz (a);
#else
    n = !a + 1;
    if (a < 0x00010000u) { n |= 16;  a <<= 16; }
    if (a < 0x01000000u) { n |=  8;  a <<=  8; }
    if (a < 0x10000000u) { n |=  4;  a <<=  4; }
    if (a < 0x40000000u) { n +=  2;  a <<=  2; }
    n = n - (a >> 31);
#endif
    return n;
}

ARMv4t编译验证

用Clang 18.1以-marm -march=armv4t编译,生成16条无返回指令的代码,比已知最优ARMv4t实现多1条指令:

cntlz:
        mov     r1, #1
        cmp     r0, #0
        moveq   r1, #2
        cmp     r0, #65536
        lsllo   r0, r0, #16
        orrlo   r1, r1, #16
        cmp     r0, #16777216
        lsllo   r0, r0, #8
        orrlo   r1, r1, #8
        cmp     r0, #268435456
        lsllo   r0, r0, #4
        orrlo   r1, r1, #4
        cmp     r0, #1073741824
        addlo   r1, r1, #2
        lsllo   r0, r0, #2
        add     r0, r1, r0, asr #31
        bx      lr

RISC-V编译结果对比

因无RISC-V开发平台,使用Compiler Explorer以Clang 18.1的-march=rv32gc编译初始代码,生成24条无返回指令的汇编代码:

cntlz:                                  # @cntlz
        seqz    a1, a0
        srli    a2, a0, 16
        seqz    a2, a2
        slli    a2, a2, 4
        or      a1, a1, a2
        sll     a0, a0, a2
        srli    a2, a0, 24
        seqz    a2, a2
        slli    a2, a2, 3
        or      a1, a1, a2
        sll     a0, a0, a2
        srli    a2, a0, 28
        seqz    a2, a2
        slli    a2, a2, 2
        or      a1, a1, a2
        sll     a0, a0, a2
        srli    a2, a0, 30
        seqz    a2, a2
        slli    a2, a2, 1
        or      a1, a1, a2
        sll     a0, a0, a2
        srai    a0, a0, 31
        add     a0, a0, a1
        addi    a0, a0, 1             
        ret

该代码无微指令融合适用场景(参考Christopher Celio等人的技术报告*"The Renewed Case for the Reduced Instruction Set Computer: Avoiding ISA Bloat with Macro-Op Fusion for RISC-V"*),预计执行24周期。

启用__builtin_clz()生成的代码更慢,包含31条指令:

srli    a1, a0, 1
        or      a0, a0, a1
        srli    a1, a0, 2
        or      a0, a0, a1
        srli    a1, a0, 4
        or      a0, a0, a1
        srli    a1, a0, 8
        or      a0, a0, a1
        srli    a1, a0, 16
        or      a0, a0, a1
        not     a0, a0          // a0 now left-aligned mask of 1-bits
        srli    a1, a0, 1
        lui     a2, 349525
        addi    a2, a2, 1365
        and     a1, a1, a2
        sub     a0, a0, a1
        lui     a1, 209715
        addi    a1, a1, 819
        and     a2, a0, a1
        srli    a0, a0, 2
        and     a0, a0, a1
        add     a0, a0, a2
        srli    a1, a0, 4
        add     a0, a0, a1
        lui     a1, 61681
        addi    a1, a1, -241
        and     a0, a0, a1
        lui     a1, 4112
        addi    a1, a1, 257
        mul     a0, a0, a1
        srli    a0, a0, 24
        ret

核心问询

尝试多种CLZ变体均未得到少于24条指令的实现,网络搜索也未找到更优方案。现正式询问:
是否存在针对32位RISC-V平台的无分支全范围前导零计数实现,其执行周期少于24?保守假设无微指令融合(低端微控制器通常无此特性),也接受基于现有RISC-V微指令融合的方案。

注意: 查表法不适用,因缓存缺失易被利用进行侧信道攻击。

2024年6月9日更新:22指令变体实现

经研究找到可将指令数从24减至22的变体,但部分编译器无法生成期望代码。核心思路是利用RISC-V ISA的SET类指令特性,将正操作数转换为负数以直接使用“小于”比较,避免每次比较的反转操作。对应的可移植C++11代码及注释如下:

// Only applies right shift to non-negative values to avoid implementation-defined behavior
int32_t sra (int32_t x, int32_t y)
{
     return (x < 0) ? (~(~x >> y)) : (x >> y);
}

uint32_t cntlz_rv32 (uint32_t a)
{
    uint32_t n, t;
    int32_t as;
    n = !a;                                           // 1: seqz
    t = ((a >> 16)!=0)*16; n = n - t; a = a >> t;     // 5: srli, snez, slli, sub, srl
    as = (int32_t)(~a);                               // 1: not
    t = (as < -256) * 8; n = n - t; as = sra (as, t); // 4: slti, slli, sub, sra
    t = (as <  -16) * 4; n = n - t; as = sra (as, t); // 4: slti, slli, sub, sra
    t = (as <   -4) * 2; n = n - t; as = sra (as, t); // 4: slti, slli, sub, sra
    t = (as <   -2) * 1; n = n - t;                   // 2: slti, sub
    n += 31;                                          // 1: addi
    return n;                                         // 22 instructions total w/o ret
}

编译结果差异

使用GCC 13.3可生成22条无返回指令的无分支代码:

cntlz_rv32(unsigned int):
        srli    a3,a0,16
        snez    a3,a3
        slli    a3,a3,4
        srl     a4,a0,a3
        not     a4,a4
        slti    a1,a4,-256
        slli    a1,a1,3
        sra     a4,a4,a1
        slti    a2,a4,-16
        slli    a2,a2,2
        seqz    a5,a0
        addi    a5,a5,31
        sra     a0,a4,a2
        slti    a4,a0,-4
        sub     a5,a5,a3
        slli    a4,a4,1
        sub     a5,a5,a1
        sub     a5,a5,a2
        sra     a0,a0,a4
        sub     a5,a5,a4
        slti    a0,a0,-2
        sub     a0,a5,a0
        ret

但GCC 14.1无法生成预期的SLTI与-256组合的指令,Clang 18.1则生成33条指令的低效代码。


内容的提问来源于stack exchange,提问作者njuffa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:55:53