Найкращою книгою для відповіді на ваше питання, напевно, були б: Купер і Торксон, "Інженерія компілятора", 2003. Якщо у вас є доступ до університетської бібліотеки, ви повинні мати можливість запозичити копію.
У виробничому компіляторі на зразок llvm чи gcc дизайнери докладають усіх зусиль, щоб усі алгоритми збереглися нижче де n - розмір вводу. Для деяких аналізів для етапів "оптимізації" це означає, що вам потрібно використовувати евристику, а не виробляти справді оптимальний код.О ( н.)2)н
Лексер - це машина з кінцевим станом, тому розміром вхідного сигналу (в символах) і виробляє потік O ( n ) лексем, який передається в парсер.O ( n )O ( n )
Для багатьох компіляторів для багатьох мов аналізатор є LALR (1) і, таким чином, обробляє потік токенів за часом у кількості вхідних лексем. Під час розбору вам, як правило, слід відслідковувати таблицю символів, але для багатьох мов це можна обробляти стеком хеш-таблиць ("словники"). Кожен доступ до словника - O ( 1 ) , але, можливо, вам доведеться час від часу ходити стеком, щоб шукати символ. Глибина штабелю дорівнює O ( s ), де s - глибина вкладення областей. (Отже, на мовах подібних С, скільки шарів фігурних брекетів ви перебуваєте всередині.)O ( n )O ( 1 )O ( s )с
Тоді дерево розбору, як правило, "сплющується" в графіку контрольного потоку. Вузли графіка потоку управління можуть бути інструкціями з 3-ма адресами (аналогічно мові збірки RISC), і розмір графіку потоку управління зазвичай буде лінійним за розміром дерева аналізу.
Виведення ( д)гO ( n )н
О ( н.)2)час у розмірі діаграми потоку всієї програми, але це означає, що вам потрібно обійтися без інформації (та покращення програми перетворень), що може бути дорого довести. Класичним прикладом цього є аналіз псевдоніму, де для деякої пари записів пам'яті ви хочете довести, що два записи ніколи не можуть націлюватись на одне місце пам’яті. (Ви можете зробити аналіз псевдоніму, щоб побачити, чи можете ви перенести одну інструкцію над іншою.) Але для отримання точної інформації про псевдоніми вам може знадобитися проаналізувати кожен можливий шлях управління через програму, який є експоненціальним у кількості гілок в програмі (і, таким чином, експоненціальна в кількості вузлів на графіку потоку управління.)
Далі ви потрапляєте в розподіл реєстру. Виділення регістрів може бути виражене як проблема забарвлення графіків, а забарвлення графіка з мінімальною кількістю кольорів, як відомо, є NP-Hard. Тож більшість компіляторів використовують якусь жадібну евристику в поєднанні з розливом регістру з метою максимально скоротити кількість розливів у регістрі за розумні часові рамки.
Нарешті ви переходите до генерації коду. Генерація коду, як правило, виконується максимальним базовим блоком у той час, коли базовий блок - це набір лінійно пов'язаних вузлів діаграми керуючого потоку з одним записом та одним виходом. Це можна переформулювати як графік, що охоплює проблему, де граф, який ви намагаєтеся прикрити, - це графік залежності набору 3-адресних інструкцій у базовому блоці, а ви намагаєтесь накрити набором графіків, які представляють наявну машину інструкції. Ця проблема є експоненціальною в розмірі найбільшого основного блоку (який, в принципі, може бути такого ж порядку, як і розмір всієї програми), тому це, як правило, робиться з евристикою, де лише невеликий підмножина можливих покриттів обстежували.