Я ищу способ выделения локальных переменных в регистры. Я знаю несколько серьезных методов для этого (а именно упомянутые
Корректность - алгоритм, который будет генерировать правильный код независимо от количества локальных переменных. Простота - то, что я могу понять, не имея необходимости читать слишком много литературы. Эффективность - она должна быть лучше, чем текущий метод:Перевести операцию x = y # z
на:
movl y, %eax
movl z, %ebx
op %ebx, %eax
movl %eax, x
Поскольку я настроен на Intel 386, некоторые соответствующие ограничения:
Двоичные операции принимают два аргумента, один из которых является источником и пунктом назначения. Унарные операции принимают один аргумент. Операции могут иметь доступ только к одной ячейке памяти; поэтому для двоичных операций требуется хотя бы один аргумент в регистре. Доступно максимум шесть регистров:%eax
%ebx
%ecx
%edx
%esi
%edi
. (%ebp
также может быть включено в качестве последнего средства.)
Существуют специальные случаи, например, для регистров с целым делением и возвратом, но я могу их игнорировать пока.
В настоящий момент компилятор проходит три этапа:
i386ification: все операции преобразуются в формуa = a # b
(или a = #a
для унарных операций).
Анализ жизнедеятельности: определяются группы живых переменных до и после каждой операции.
Распределение регистров: построено и окрашено граф помех.
И затем компилятор бросает свои карандаши в воздух и не знает, что делать дальше.
Пример
public int mf(int cr, int ci) {
int i = 0;
int zr = 0;
int zi = 0;
while (i < 100 && zr*zr + zi*zi < 4) {
int t = zr * zr - zi * zi + cr;
zi = 2 * zr * zi + ci;
zr = t;
i = i + 1;
}
return i;
}
...
a = a + b
...
Если все переменные могут быть окрашены цветами r
, отлично!
В противном случае разливайте цвета и связанные с ними переменные.
Если существует операция, которая обращается к двум пролитым переменным, проливает другой цвет и использует запасной регистр для временного хранения для всех таких операций.
Конкретные проблемы
Как определить, куда вставлять инструкции загрузки/хранения, для правильности (и, что менее важно, эффективности)? Могу ли я разлить переменную только для той части своего жизненного цикла, когда она не находится в непосредственном использовании, а затем нераспределить ее позже? Так что все инструкции действуют на непереполненных регистрах. Переменная может жить в разных регистрах в разное время. Могу ли я быть более эффективным в особых случаях. Например, для возвращаемого значения используется%eax
, поэтому было бы неплохо, если бы переменная, которая должна быть возвращена, была назначена этому регистру к моменту возврата. Аналогичным образом, некоторые регистры являются "callee-save", поэтому, если бы меньшее количество переменных оказалось живым во время вызова функции, если они были распределены для регистров с сохранением недействительности, это означало бы, что я могу избежать хранения этих регистров.
Помогла ли SSA помощь (если вообще)? Возможность устранения общих подвыражений и оценки констант может уменьшить (?) Давление в регистре, но в противном случае это имело бы какой-либо эффект?
Аспекты, которые меня не волнуют (прямо сейчас):
Распределение и оптимизация стека: она реализована наивно уже и может быть оптимизирована с использованием графика помех, если это необходимо. Эффективность времени компиляции до тех пор, пока она завершается. (NP-полнота не означает, что данный алгоритм следует избегать.)Update
Я нашел очень приятную презентацию (PPT, к сожалению):
Я постараюсь сделать некоторую фактическую работу в ближайшее время и, надеюсь, закрыть вопрос.