在组装中检查输入字符最干净的方法是在0〜9之间

问题描述

问题是在RISC-V中将字符串转换为int

如果存在不为0〜9的字符,请立即返回-1

但是我想知道是否有任何方法可以通过使用最小指令来检查它

我的方法是将48和57(​​对应于ASCII中的0〜9)放入临时寄存器,
并使用2个分支,首先检查 = 48

但是它使用了太多指令,并且需要额外的临时寄存器来存储48和57。还有其他有效的方法吗?

解决方法

是的,因为您仍然必须减去'0',所以要这样做,然后对c <= 9c < 10进行无符号比较。有关范围检查技巧,请参见What is the idea behind ^= 32,that converts lowercase letters to upper and vice versa?

我们可以在C语言中进行此操作,然后查看其编译方式,以此作为紧凑型RISC-V实现的起点。这个C的结构类似于NASM Assembly convert input to integer?中的asm,希望GCC或clang将使用类似的循环结构。如果您手动翻译它,则可能需要这种循环结构,或对其进行调整以在有序RISC-V上进行更好的软件流水线处理,尤其是隐藏负载使用延迟。这种循环结构在现代的x86上非常有用,在该x86上,OoO投机执行程序隐藏了分支和负载使用延迟。

// C intentionally written exactly like hand-written asm
// Translate this to asm by hand,including the loop structure.
// or compile it if you want more bloated asm.

unsigned str_to_uint(const unsigned char *p) {
    unsigned dig = *p - '0';
    unsigned total = dig;  // peel first iter,optimize away the  + 0 * 10
    if (total < 10)        // <10 can share a constant with *10
        goto loop_entry;
    else // fall through to the uncommon case of no valid digits
        return 0;

    do {
        total = total*10 + dig;
     loop_entry:            // branch target = loop entry point
        dig = *++p - '0';
    } while(dig < 10);

    return total;
}

在第一次迭代中,我使用taked分支跳过了total * 10 + dig,因此我们最好将其作为循环的入口,以最大程度地减少总代码量。

另一个选择是将另一个循环迭代剥离到循环顶部。当使用-O3-O2进行编译时,这就是GCC和clang所选择的。使用-Os时,gcc将其优化为一个循环,其底部为j,中间为btgu。 (Godbolt compiler explorer)。我不知道要尝试的任何-march= RISC-V拱形或调谐选项。

因此,如果您希望在代码大小和效率之间取得良好的平衡(尤其是对于常见的1或2位数字的情况),则应该手动“编译”它。

GCC使用(x<<3) + (x<<1)乘以10; clang使用mul(并且循环内的确在mulbltu循环分支之间共享一个常数。不幸的是,循环外clang与9比较,例如{{1} },因此它需要两个常量(RISC-V是否有一个9 < total bge比较?IDK,TODO /编辑是否欢迎忽略此优化)。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...