Привязка к регистру должна быть самым последним шагом.
Чтобы сгенерировать код для вашего выражения выше, вы должны начать с преобразования его в обратную польскую запись, которая выглядит следующим образом:
a b c + e f / h * + :=
Теперь, чтобы сгенерировать код, мы проходим через это и всякий раз, когда мы сталкиваемся с операцией, мы генерируем код, поддерживая стек промежуточных результатов. Итак, начнем. Пройдите до первой операции:
a b c +
Итак, мы должны добавить две переменные и сохранить немедленный результат. В x86 дополнение можно добавлять только между регистрами или регистром и памятью. Поэтому мы должны загрузить b
в регистр и добавить к нему c
:
MOV R1, [&b]
ADD R1, [&c]
На данный момент мы не знаем, какой регистр использовать, поэтому я пометил его как R1.
Теперь наш стек:
a R1
Продолжите и добавьте следующие несколько элементов до следующей операции.
a R1 e f /
В x86 деление сложное, дивиденды сохраняются в 64-битной паре EDX: EAX, частное будет в EAX. Предполагая, что вы работаете с 32-разрядными целыми числами со знаком, мы должны загрузить e
в EAX и подписать расширение до EDX и выполнить деление. Таким образом, этот код должен быть сгенерирован:
MOV EAX, [&e]
CDQ
IDIV [&f]
К счастью, EDX и EAX бесплатны, поэтому мы можем их использовать. Если они уже использовались, нам нужно было бы сохранить их значение в другом регистре, чтобы освободить их, нам не нужно делать это сейчас.
Результат в EAX. Итак, наш стек сейчас:
a R1 EAX
Продолжай:
a R1 EAX h *
Первый операнд - это регистр, поэтому нам не нужно ничего загружать, просто сделайте умножение сразу:
IMUL EAX, [&h]
Результат в EAX, поэтому стек теперь:
a R1 EAX
Продолжение:
a R1 EAX +
Снова регистр для регистрации операции, только одна инструкция сделает это:
ADD R1, EAX
Результат в R1. Стек сейчас:
a R1
И, наконец, задание:
MOV [&a], R1
Таким образом, почти окончательный код будет:
MOV R1, [&b]
ADD R1, [&c]
MOV EAX, [&e]
CDQ
IDIV [&f]
IMUL EAX, [&h]
ADD R1, EAX
MOV [&a], R1
Теперь пришло время заменить R1. Какой регистр это может быть?
Основное правило заключается в том, что время жизни регистра не должно совпадать.
Время жизни регистра, которое начинается с первой полной перезаписи (например, MOV) и заканчивается последним использованием перед следующей полной перезаписью (если есть).
MOV R1, [&b] ; R1 lifetime starts
ADD R1, [&c]
MOV EAX, [&e] ; EAX lifetime starts.
CDQ ; EDX lifetime starts
IDIV [&f] ; EDX lifetime ends.
IMUL EAX, [&h]
ADD R1, EAX ; EAX lifetime ends.
MOV [&a], R1 ; R1 lifetime ends
В нашем случае время жизни R1 перекрывается с EAX и EDX. Поэтому мы не можем использовать их для R1, но EBX не используется, поэтому мы можем использовать его:
MOV EBX, [&b]
ADD EBX, [&c]
MOV EAX, [&e]
CDQ
IDIV [&f]
IMUL EAX, [&h]
ADD EBX, EAX
MOV [&a], EBX
И после этого все регистры свободны.
И это будет код вашего выражения. Видно, что каждая переменная загружается только один раз. Таким образом, нет особой выгоды хранить их в регистрах в этом конкретном примере. Но если ваша функция имеет несколько операторов (и, конечно, будет делать), наиболее часто используемые переменные следует загружать в регистры, если вы все еще можете найти свободную переменную после генерации кода для всей функции.
Вывод заключается в том, что у вас должен быть план для каждого возможного сценария, здесь мы видели сценарий от переменной к переменной, но иногда у вас есть такие компоненты, как 4 * a, где вы можете загрузить a, сдвиг влево вместо умножения.