一行终端输入包含终止换行符是正常的。如果 RARS 不允许用户在没有换行符的情况下“提交”输入,您可以将最后一个字节归零。但是 RARS 读取字符串 ecall 非常不方便地不返回长度,因此搜索 \0 并不比仅搜索 \n 更好。
(Unix read 系统调用将返回一个长度:RARS 有它作为ecall #63 read 它返回一个在a0 中的长度,所以如果它允许你可以使用它来读取输入fd=0 表示标准输入。)
循环效率
你每次循环迭代只做一个字节;您唯一要节省的就是每次迭代都加载一个字节 (lb),代价是更多的 ALU 工作。
简单的方法看起来像这样,并且在大多数现实世界的 RISC-V 机器上可能更快。 (特别是如果它们有任何缓存,这使得执行多个附近的加载而不是一个更广泛的加载变得便宜。)展开一些以隐藏加载延迟对于高性能有序机器可能是一个好主意,如果你真的关心优化这个循环用于潜在的大输入。 (对于这个用例,你不应该这样做,因为它每个用户输入只运行一次,所以只需保持紧凑的代码大小。)
li t1, '\n'
.loop: # do{
lbu t0, (a0)
addi a0, a0, 1
bne t0, t1, loop # }while(*p != '\n')
# assume the string will *always* contain a newline,
# otherwise check for 0 as well
sb zero, -1(a0)
# a0 points to one-past-the-end of the terminating 0
# so if you want the string length, you can get it by subtracting
但是关于一次单词循环的设计选择还有很多话要说:
由于 RISC-V 有字节存储指令,因此您无需屏蔽找到换行符的单词并存储整个单词,只需 sb x0, (position) 在找到换行符的位置即可,即使您通过为每个内部循环移位计数增加一个计数器来找到该位置(这也应该简化该循环)。
此外,如果您的缓冲区不是对齐的整数的整数,那么存储整个单词尤其糟糕:您不想在缓冲区末尾执行非原子 RMW 字节。这对于线程安全来说是一个非常糟糕的习惯。 (另请参阅 Erik 的回答:一般来说一次单词的可能缺点和Is it safe to read past the end of a buffer within the same page on x86 and x64?)
(如果您要屏蔽一个单词并存储它,请使用not 而不是neg / addi -1 来反转掩码中的位。not 是xori 的伪指令-1。一般来说,你可以向编译器询问类似的东西,例如,https://godbolt.org/z/EPGYGosKd 显示了 clang 如何为 RISC-V 实现 x & ~mask。)
一次快速的单词
要真正快速一次检查整个单词的换行字节,请执行 word ^ 0x0a0a0a0a 将该字节值映射为 0,并将其他值映射为非零。 strong> 然后使用 bithack 来查找单词是否具有零字节 https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord。 (就像 glibc 的可移植 C 后备 strlen 所做的那样:Why does glibc's strlen need to be so complicated to run quickly?)。 IIRC,这不是一个精确的测试(可能存在误报匹配),因此您需要快速检查整个单词,然后遍历字节一次检查一个以确保。如果没有,请返回单词循环。
当然,如果您有一些 SIMD 支持,使用 RV32 P(打包 SIMD)或 RV32 V(向量)扩展并行执行 4 或 8(或 16)字节比较,那就更好了。
如果您在未分配的缓冲区上执行此操作,您可能希望执行一次未对齐的加载(在checking it's not going to cross a page or maybe cache-line boundary 之后),然后到达对齐边界以进行对齐的字加载。或者一次循环一个字节,直到一个字边界。 (或 RV64 上的双字)。