汇编编码标准/最佳实践

时间:2010-01-31 14:35:05

标签: assembly coding-style mips

我知道8086大会,现在我正在通过阅读书籍MIPS Assembly Language ProgrammingSee MIPS Run来学习MIPS大会,但我从未停下来思考大会的编码标准/最佳实践。我想每天让我​​成为一个更好的开发人员,然后想要知道这一点来提高自己。如何了解有关汇编编码标准和最佳实践的更多信息?

2 个答案:

答案 0 :(得分:2)

最佳实践是一种社交现象,取决于您将从事的社会,因此您最好的答案是从您希望与之互动的任何环境中读取现有的MIPS asm代码。

我自己的世界想到的例子是Linux内核的汇编部分,GCC的MIPS启动代码或glibc的MIPS端口的汇编程序片段。

如果您主要与其他项目进行互动,最好吸收并模仿该社区的编码实践。

答案 1 :(得分:1)

良好的asm样式在ISA(以及同一CPU的asm的不同方言)中非常普遍。编译器输出(如gcc / clang)通常会完成我在下面提到的所有事情,因此是一个很好的指导原则。 (并且C编译器输出通常是优化小函数的一个很好的起点。)

通常缩进的指令要比标签和汇编程序指令深一层。

将操作数缩进一致的列中(因此助记符长度的变化不会使您的代码参差不齐,而且很容易向下扫描一个块并将每条指令的目标寄存器视为第一个操作数) 1

将指令行上的注释缩进右侧的一致列中,以免出现视觉干扰。

将相关指令的块分组在一起,并用空行将它们分开。 (或者,如果您正在通过调度指令来优化有序CPU,则您无法真正做到这一点,而需要使用注释来跟踪每条指令正在处理的问题的哪一部分。评论可能会有所帮助)


脚注1:
除了MIPS存储指令(例如sw $t0, 1234($t1))之外,第一个操作数实际上是源;他们选择使asm源对装入和存储使用相同的操作数顺序,这可能是因为它们都是机器代码中的I型指令。但是,对于RISC加载/存储体系结构,这是典型的asm,因此,习惯于从mov eax, [rdi]是负载而mov [rdi], eax是存储的CISC来使用。 add [rdi], eax都是。


示例:一个用于无符号整数的atoi函数,用于带有分支延迟槽的实际MIPS。但是没有MIPS I,没有加载延迟插槽。尽管我还是设法避免使用负载的停顿。 (Godbolt for a C version

# unsigned decimal ASCII string to integer
# inputs: char* in $a0 - ASCII string that ends with a non-digit character
# outputs: integer in $v0
# clobbers: $t0, $t1
atoi:
    # peel the first iteration to avoid a 0 * 10 multiply
    lbu    $v0,  0($a0)
    addiu  $v0, $v0,  -'0'          # digit = *p - '0'
    sltu   $t0, $v0,  10
    bnez   $t0, .Lloop_entry        # if unsigned (! digit<10) 
    nop                              # doing work for the next iteration here hurts ILP for in-order CPUs
    #addu   $t2, $v0, $v0            # total * 2  (branch delay slot)

    # invalid non-digit input
    jr     $ra                      # return 0
    move   $v0, $zero


.Lloop:                           # do {
    addu   $v0, $v0, $v0            # total *= 2
    addu   $t0, $t0, $t1            # total*8 + digit

    addu   $v0, $v0, $t0            # total*10 + digit = total*2 + (total*8 + digit)

.Lloop_entry:
    lbu    $t0, 1($a0)
    addui  $a0, $a0, 1              # t0 = *(p++ + 1)

    addiu  $t0, $t0,  -'0'          # t0 = digit
    sltu   $t1, $t0,  10
    bnez   $t1, .Lloop           # while(digit<10);
    sll    $t1, $v0, 3

    jr     $ra
    nop

对于任何特定的MIPS实现,这可能不是最佳选择;有序超标量可能会受益于在负载和分支之间放置更多的移位/添加,即使这意味着在上一次迭代中完成了更多的冗余工作。对于像r10k这样的OoO执行人员来说,这可能很好。现代的MIPS32r6将使用lsa进行左移累加,就像gcc使用-march=mips32r6一样,并且将使用分支指令的无分支延迟版本。

但是,在早期的标量MIPS上,这可能会很好。指针增量在加载后填充插槽,避免了循环内部的停顿。 (1的立即偏移量是因为我们避免了去皮的第一次迭代中的增量)。

如果我们想为主循环内的.Lloop_entry之后的下一次迭代 计算更多的内容,则可以将启动分支的延迟时隙填充到addu $v0, $v0, $t0 。但这将需要依赖$v0,这会损害超标量顺序CPU的ILP。 (当前addu的顶部指令可以并行运行,然后addu以产生新的总数可以与lbu并行运行。)

在标量有序(例如MIPS I / MIPS II)或无序CPU上都可以。

(尽管我不确定当条件分支从上一条ALU指令中读取其输入时,早期的MIPS是否需要停止;分支决策处于ID阶段,即EX之前的1个周期。可能不是因为MIPS我实际上没有针对RAW危险的管道互锁;这就是为什么它具有加载延迟插槽的原因。)