Дизайн компилятора - генерация кода для выражений с несколькими операндами - PullRequest
4 голосов
/ 25 февраля 2011

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

Я прошел синтаксическую и семантическую проверку и начинаю фазу генерации кода.

Конечный код, который я должен сгенерировать, должен быть в 3-х адресной архитектуре загрузки / хранения. Мне разрешено использовать неограниченный набор регистров и пространство памяти 32 М для стека и системной памяти.

Прямо сейчас, когда я начинаю генерировать код, я начинаю с предположения, что действительно большой массив int int R[2000000] обозначает набор регистров. И когда я сталкиваюсь с объявлением переменной (через анализатор / семантический анализатор), я выделяю регистр для этой конкретной переменной.

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

Мой вопрос сейчас такой: предположим, у нас есть такое утверждение -

a := b + c + e / f *h;

И я сохранил a, b, c, e, f, h в R1, R2, R3, R4, R5, R6 соответственно, окончательно сгенерированный код будет (при условии, что следующие доступные регистры начинаются с R7 .. .)

R9 = R5 * R6;
R8 = R4 / R9;
R7 = R3 + R8;
R1 = R2 + R7;

Каков подход к «запоминанию», каким был предыдущий регистр и операция?

Если это не правильный способ сделать это, кто-нибудь может дать мне несколько советов о том, как это реализовать?

Любое предложение было бы замечательно. Спасибо

Ответы [ 4 ]

3 голосов
/ 25 февраля 2011

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

В реальной ситуации назначение регистров обычно выполняется с использованием «раскраски графа».В простом случае граф состоит из узлов, представляющих переменные (включая временные значения), и дуг, представляющих конфликты между переменными.Цель состоит в том, чтобы назначить каждой переменной цвет (представляющий номер регистра), чтобы никакие два конфликтующих регистра не имели одинаковый цвет.(Исторически эта проблема аналогична проблеме составления карты стран мира, отсюда и использование термина «цвет».)

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

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

Другие вещи, которыевам может потребоваться учитывать соглашение о вызовах, в котором параметры и возвращаемые значения функции должны быть помещены в конкретные регистры.

1 голос
/ 25 февраля 2011

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

0 голосов
/ 24 января 2019

Привязка к регистру должна быть самым последним шагом.

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

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, сдвиг влево вместо умножения.

0 голосов
/ 25 февраля 2011

Такое выражение часто изображается деревом. Каждый узел соответствует операции, и каждый узел содержит индекс регистра, который содержит результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...