Хороший стиль asm довольно универсален для ISA (и разных диалектов asm для одного и того же процессора). Вывод компилятора (например, gcc / clang), как правило, выполняет все вещи, о которых я упоминаю ниже, так что это хорошее руководство. (И вывод компилятора C часто является хорошей отправной точкой для оптимизации небольшой функции.)
Как правило, отступы на один уровень глубже, чем надписи и директивы ассемблера.
Отступы операндов для непротиворечивого столбца (поэтому разная длина мнемоники не оставляет ваш код неровным, и легко сканировать блок и видеть регистр назначения каждой инструкции в качестве первого операнда) 1 .
Вставить комментарии к строкам инструкций в соответствующий столбец справа, далеко за пределами операндов, чтобы избежать визуального шума.
Сгруппируйте блоки связанных инструкций вместе с пустой строкой для их разделения. (Или, если вы оптимизируете центральные процессоры по порядку, планируя инструкции, вы не можете этого сделать и должны использовать комментарии, чтобы отслеживать, над какой частью проблемы работает каждая инструкция. Использование различных уровней отступов для комментарии могут быть полезны тогда)
Сноска 1:
За исключением инструкций хранилища MIPS, например sw $t0, 1234($t1)
, где первый операнд фактически является источником; они решили сделать так, чтобы источник asm использовал один и тот же порядок операндов как для загрузок, так и для хранилищ, возможно, потому что они оба являются инструкциями I-типа в машинном коде. Это типично для asm для архитектур загрузки / хранения RISC, так что к чему-то нужно привыкнуть, если исходить из CISC, где mov eax, [rdi]
- это нагрузка, а mov [rdi], eax
- это хранилище. И add [rdi], eax
есть и то и другое.
Пример: функция atoi
для целых чисел без знака, для реального MIPS с интервалами задержки ветвления. Но не MIPS I, ни слоты с задержкой загрузки. Хотя я все равно старался избегать киосков с нагрузкой. ( Годболт для версии C )
# 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; суперскаляр по порядку, вероятно, выиграл бы от размещения большего количества сдвигов / добавлений между нагрузкой и ветвью, даже если это означает, что на последней итерации выполняется больше избыточной работы. Это, вероятно, хорошо для OoO exec вроде r10k. Современный MIPS32r6 будет использовать lsa
для накопления влево-сдвиг, как это делает gcc с -march=mips32r6
, и будет использовать версии инструкций ветвления без задержки.
Это может быть довольно хорошо на ранних скалярных MIPS, хотя. Приращение указателя заполняет слот после загрузки, избегая остановки внутри цикла. (Непосредственное смещение 1 связано с тем, что мы избежали увеличения очищенной первой итерации).
Заполнение интервала задержки для ветки запуска до .Lloop_entry
было бы возможно, если бы мы хотели вычислить больше материала для следующей итерации после addu $v0, $v0, $t0
внутри основного цикла. Но это потребует зависимости от $v0
, что повредит ILP для суперскалярных процессоров в порядке. (В настоящее время инструкции top к addu
могут выполняться параллельно, затем addu
для получения нового итога может выполняться параллельно с lbu
.)
Было бы хорошо работать со скалярным порядком (например, MIPS I / MIPS II) или с неработающими процессорами.
(Хотя я не уверен, что ранний MIPS должен останавливаться, когда условная ветвь считывает свой ввод из предыдущей инструкции ALU; решение ветвления находится в стадии ID, 1 цикл до EX. Но, вероятно, не потому, что у MIPS у меня буквально не было блокировок трубопроводов для опасностей RAW, поэтому у него был слот задержки загрузки.)