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