问题是在RISC-V中将字符串转换为int
如果存在非0~9字符,立即返回-1
但是我想知道是否有任何方法可以使用最小指令来检查它
我的方法是将48和57 (对应于ASCII中的0~9 )放入temp寄存器,
并使用2个分支,首先检查<=57,然后检查>=48
但是它使用了太多指令,且需要额外的临时寄存器来存储48和57。有没有其他有效的方法?
发布于 2020-10-21 21:31:12
是的,既然你无论如何都要减去'0',那就这样做,然后对c <= 9或c < 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上进行更好的软件流水线,特别是为了隐藏加载使用延迟。这种循环结构在OoO推测性执行隐藏分支和加载使用延迟的现代x86上非常好用。
// 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;
}我使用taken分支跳过了第一次迭代中的total * 10 + dig,所以我们也可以让它作为我们进入循环的入口,以最小化总代码量。
另一种选择是将另一个循环迭代剥离到循环的顶部。这是GCC和clang在用-O3或-O2编译时所选择的。使用-Os,gcc将其反优化为一个循环,其底部为j,中间为btgu break。(Godbolt compiler explorer)。我不知道有什么-march=、RISC-V、arch或tune选项可以尝试。
因此,如果您想要在代码大小和效率之间取得良好的平衡(特别是对于常见的1位或2位数字的情况),您可能应该手动“编译”它。
GCC使用(x<<3) + (x<<1)乘以10;clang使用mul (在循环内部,mul和bltu循环分支之间共享一个常量。不幸的是,在循环之外,clang会与9进行比较,比如9 < total,所以它需要这两个常量。( RISC-V是否有bge <=比较?IDK,TODO / edit欢迎讨论这是否是一个遗漏的优化)。
https://stackoverflow.com/questions/64456960
复制相似问题