Стандарты кодирования сборки / передовой опыт

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


person Nathan Campos    schedule 31.01.2010    source источник
comment
Хорошие цели ... Вы можете сформулировать вопрос, на который хотите получить ответ?   -  person Sam Post    schedule 31.01.2010


Ответы (2)


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

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

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

person martinwguy    schedule 31.01.2010

Хороший стиль 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, никаких слотов задержки загрузки. Хотя я все равно старался избегать киосков с нагрузкой. (Godbolt для 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-исполнителя, такого как r10k. Современный MIPS32r6 будет использовать lsa для сдвига влево-накопления, как gcc делает с -march=mips32r6, и будет использовать версии инструкций ветвления без задержки перехода.

Тем не менее, это может быть неплохо для ранних скалярных MIPS. Указатель-инкремент заполняет слот после загрузки, избегая остановки внутри цикла. (Непосредственное смещение 1 связано с тем, что мы избежали приращения в очищенной первой итерации).

Заполнение слота задержки для ветки запуска до .Lloop_entry было бы возможно, если бы мы хотели вычислить больше материала для следующей итерации после addu $v0, $v0, $t0 внутри основного цикла. Но для этого потребуется зависимость от $v0, что повредит ILP для суперскалярных процессоров в порядке очереди. (В настоящее время инструкции от начала до addu могут выполняться параллельно, затем addu для получения новой суммы может выполняться параллельно с lbu.)

Это было бы нормально для скалярных в порядке (например, MIPS I / MIPS II) или для ЦП, вышедших из строя.

(Хотя я не уверен, нужно ли на раннем этапе MIPS останавливаться, когда условная ветвь считывает входные данные из предыдущей инструкции ALU; решение о ветвлении находится на этапе ID, за 1 цикл до EX. Но, вероятно, не потому, что MIPS I буквально не имел блокировок конвейера для опасностей RAW; поэтому у него был слот задержки загрузки.)

person Peter Cordes    schedule 09.06.2019