Печать от одного до десяти дюймов (GAS / AT&T x86 / x64) сборки

Я начал свой путь изучения сборки на моем Mac. Для начала я решил написать программу, которая выводит на консоль числа от одного до десяти, а затем последнюю строку, обозначающую конец программы. Я хочу поскорее избавиться от вредных привычек и знать, могу ли я внести какие-то улучшения в этот код. Любые отзывы приветствуются!

loop_10.s

# Author: Ben Antonellis
# Start of stack: offset=-20(%rbp)
# Short: Prints the numbers 1-10, then "End of program."

    .section  __TEXT,__text
    .globl _main
_main:
    # Stack construction
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $32, %rsp

    movl    $0, -20(%rbp)           # int i = 0;
Loop_Condition_Compare:
    cmpl    $10, -20(%rbp)          # Check if i equals 10
    jne     Loop_Inside             # True: Continue loop
    jmp     Loop_Exit               # False: Exit loop

Loop_Inside:
    # Add one to i
    movl    -20(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -20(%rbp)

    # Print i
    movl    -20(%rbp), %esi
    leaq    integer(%rip), %rdi
    movb    $0, %al
    callq   _printf

    # Move to compare i
    jmp     Loop_Condition_Compare

Loop_Exit:
    # Print "End of program."
    leaq    end_string(%rip), %rdi
    movb    $0, %al
    callq   _printf

    # Stack deconstruction
    xorl    %eax, %eax
    addq    $32, %rsp
    popq    %rbp
    retq

.data

    integer: .asciz "%dn"
    end_string: .asciz "End of program.n"

А вот как я запускаю свой код:

run.sh

FILE=loop_10

gcc -o $FILE -fno-pie $FILE.s
./$FILE
rm $FILE

1 ответ
1

Я хочу быстро избавиться от вредных привычек и знать, есть ли какие-нибудь оптимизации, которые я могу внести в этот код.

Конечно. Я проигнорирую возможность печати всего текста сразу без цикла, хотя это было бы оптимизацией, но это не было бы так поучительно или интересно. Оставшиеся детали незначительны по сравнению со стоимостью печати чего-либо, но пусть будет так.

movb $0, %al

В общем по возможности рекомендую xorl %eax, %eax (который вы использовали позже). Запись в 8-битные регистры имеет сложные вопросы. Вынимая высокие биты eax поскольку «побочный ущерб» здесь не проблема, я бы выбрал старый добрый xorl %eax, %eax, у которого нет сложных проблем и в целом рекомендуемый способ обнуления реестра.

шаблоны петель

В шаблоне петли много прыжков и ответвлений, все в петле. Зацикливание известного ненулевого количества раз можно выполнить с помощью всего одной ветви. Даже в общем можно сделать одной веткой и прыжком за пределами цикла (так он выполняется реже).

Например, для общего случая:

    jmp Loop_Condition_Compare
Loop_Inside:
    ; stuff
Loop_Condition_Compare:
    ; some comparison
    jcc Loop_Inside
Loop_Exit:

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

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

Вы также можете выровнять цикл, но когда это помогает и насколько, предсказать непросто.

Предпочитать регистры стеку

Положив i в стеке не требуется, вместо этого он может быть в регистре сохранения вызываемого абонента (он же энергонезависимый). Для x64 SysV ABI (используется x64 Mac и x64 Linux и, как правило, почти всем, кроме Windows), это rbx, rbp, r12, r13, r14 и r15. Сохранение одного из них (в прологе функции) и использование его для i также будет означать, что i выдерживает вызовы функций и экономит на хранении и загрузке.

В качестве альтернативы вы можете оставить i в удобном регистре большую часть времени и храните его в стеке только непосредственно перед вызовом, а затем загружайте обратно сразу после вызова.

Я не знаю, возможно, вы следуете ментальной модели «локальные переменные помещаются в стек» (в которой я бы абсолютно не стал винить вас, это обычное упрощение, которое можно найти в большинстве учебников, учебных сайтов и т. Д.), но это вещь для компиляторов в -O0 режим. Мой общий совет: лучше записывать значения в регистры. Я намеренно пишу «ценности», потому что Переменная не обязательно находиться в одном и том же месте все время, он может быть в одном месте в одно время и в другом месте в другое время, если это лучше работает в коде, поэтому выбор места для переменной на самом деле не является правильным / полный способ взглянуть на него (это может привести к тому, что вы упустите возможности оптимизации). Компиляторы также стараются быть немного умными с подобными вещами, когда их нет в -O0 режим.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *