Скажем, у меня есть это промежуточное представление некоторого кода:
t1 = 1
t2 = 2
t3 = 3
t4 = t1 + t2
t5 = t3 + t4
use t5
Конечная цель состоит в том, чтобы назначить регистр, используя только два регистра ARM, r0
и r1
, и, возможно, пролить некоторые символы.
Первым шагом является вычисление текущих диапазонов для каждой команды:
t1 = 1 |
t2 = 2 | t1
t3 = 3 | t1, t2
t4 = t1 + t2 | t1, t2, t3 (this is going to become a problem)
t5 = t3 + t4 | t3, t4
use t5 | t5
Регистрация графа помех
t2
/ \
/ \
/ \
t1----------t3
|
|
t5 t4
Регистрация назначения с помощью раскраски графа
Теперь используйте алгоритм Чейтина, чтобы раскрасить его с помощью двух регистров:
- Найти узел с менее чем 2 ребрами
- Найдено =>
t5
- Удалите его из графика
- Pu sh поместите в стек:
[t5]
- Найдите узел с менее чем 2 ребрами
- Найдено =>
t4
- Удалить его из графика, pu sh:
[t5, t4]
- Нет узлов с менее чем 2 ребрами! (мы смотрим на
t1 - t2 - t3
треугольник) - Выберите один случайным образом (или с наивысшей степенью (2 для всех оставшихся узлов)) =>
t3
(это может быть проблемой) - Удалить его из графика, pu sh:
[t5, t4, t3]
- Найти узел с менее чем 2 ребрами
- Найдено
t2
- Удалите его из графика, pu sh:
[t5, t4, t3, t2]
- Pu sh последний узел:
[t5, t4, t3, t2, t1]
Теперь назначьте регистры в обратном порядке:
- Символ Pop =>
t1
- Конфликтующие узлы: нет
- Назначить:
t1 => r0
- Put
t1
обратно в график
- Pop
t2
- Конфликтующие узлы:
{t1: r0}
- У нас есть еще один бесплатный регистр
- Назначить:
t2 => r1
- Поместить
t2
обратно в график
- Pop
t3
- Конфликтующие узлы:
{t1: r0, t2: r1}
- ОЙ! Больше никаких бесплатных регистров!
- Spill it =>
t3 => m0
- Поместите
t3
обратно в график
- Больше никаких разливов не требуется!
Окончательное назначение:
t1 -> r0
t2 -> r1
t3 -> m0 (spilled)
t4 -> r0 (or r1)
t5 -> r0 (or r1)
Вставка разливов
Теперь, согласно это (слайд 29) и это (слайд 4):
Перед каждой операцией, которая использует [символ разлива t3
], вставлять t3 := load m0
После каждой операции, которая определяет [символ разлива t3
], вставьте store t3, m0
Давайте сделаем это, а также вычислим живые диапазоны:
t1 = 1 |
t2 = 2 | t1
t3 = 3 | t1, t2
// STORE
store t3, m0 | t1, t2, t3 (STILL 3 SYMBOLS!)
t4 = t1 + t2 | t1, t2
// LOAD
t3 = load m0 | t4
t5 = t3 + t4 | t3, t4
use t5 | t5
Задача
Как видите, у нас еще есть инструкция, в которой вмешиваются три символа, и мы также получим ту же буровую установку, что и раньше.
Итак, разлив не сработал!
Теперь я перебил всех возможных кандидатов на разлив. вручную и обнаружил, что буровая установка остается не 2-раскрашиваемой, если мы пролили какой-либо один символ, но она становится 2-раскрашиваемой, если мы вылим любую из этих пар символов: * 1 153 *
... а также t1
, t2
и t3
, но пролить только два символа выглядит проще.
Актуальный вопрос
Как мне решить, что разлить? Какую эвристику c использовать? Мне также хотелось бы, чтобы алгоритм выполнял глобальное назначение регистров, поэтому простые живые диапазоны и линейное сканирование (в отличие от построения RIG) кажутся довольно громоздким подходом.
Кроме того, если я повторно запустите тот же алгоритм для кода с разливами, результатом будет снова «spill t3
», даже если это уже сделано, поэтому код будет l oop навсегда. Более того, если я продолжу проливать t3
и вставлять код load
/ store
, я получу все больше и больше инструкций, где мешают t1
, t2
и t3
, поэтому ситуация будет ухудшаться .
И это при условии, что я могу просто t3 = load m0
, в то время как на ARM (на который я нацеливаюсь) m0
сначала нужно сохранить в своем собственном регистре.