Сборочные нормы и стандарты кодирования - PullRequest
5 голосов
/ 31 января 2010

Я знаю 8086 Assembly, и теперь я изучаю MIPS Assembly, читая книги Программирование на языке MIPS Assembly и См. MIPS Run , но я никогда не задумывался о стандарты кодирования / лучшие практики сборки. Я хочу превращать меня в лучшего разработчика каждый день, а затем хочу знать это, чтобы улучшить себя. Как я могу узнать больше о стандартах кодирования сборки и передовых методах?

Ответы [ 2 ]

3 голосов
/ 31 января 2010

Лучшая практика - это социальный феномен, в зависимости от того, в каком обществе вы будете работать, поэтому лучшим решением будет чтение существующего кода MIPS asm из любой среды, с которой вы ожидаете взаимодействовать.

Примерами, которые приходят мне в голову, являются секции ассемблера ядра Linux, код запуска MIPS из GCC или фрагменты ассемблера порта MIPS glibc.

Если вы будете в первую очередь взаимодействовать с другими проектами, лучше всего освоить и подражать методам кодирования этого сообщества.

1 голос
/ 09 июня 2019

Хороший стиль 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, поэтому у него был слот задержки загрузки.)

...