там сверху уже лежит контекст самой первой функции. Мы можем продолжать вычисления. Так происходит
вычисление вложенных функций в императивных языках программирования.
В куче мы храним разные данные. Данные бывают статическими (они нужны нам на протяжении выполне-
ния всей программы) и динамическими (время жизни динамических данных заранее неизвестно, например
это могут быть отложенные вычисления, мы не знаем когда ни нам понадобятся). У кучи также две опера-
ции: выделить блок памяти, эта операция принимает размер блока и возвращает адрес, по которому удалось
выделить память, и освободить память по указанному адресу. Регистры находятся в процессоре. В них можно
записывать и читать данные, при этом операции обращения к регистрам будут происходить очень быстро.
Посмотрим как GHC справляется с переводом процесса редукции синонимов на язык понятный нашему
компьютеру. Язык обновления стека и кучи. Это большая и трудная глава, читайте не спеша. Если покажется
совсем трудно – пропустите, вернётесь потом, когда захочется писать не только красивые, но и эффективные
программы.
10.1 Этапы компиляции
Рассмотрим этапы компиляции программы (рис. 10.1).
На первых трёх этапах происходит проверка ошибок. Сначала мы строим синтаксическое дерево про-
граммы. Если мы нигде не забыли скобки, не ошиблись в простановке ключевых слов, то этот этап успешно
| 155
Файл .hs
Построение синтаксического дерева
Разрешение имён
Проверка типов
Устранение синтаксического сахара
Core
Упрощение Core
Генерация кода для ghci
STG
Генерация Cmm
C
Native
LLVM
Рис. 10.1: Этапы компиляции
завершится. Далее мы приписываем ко всем функциям их полные имена. Дописываем перед всеми опреде-
лениями имя модуля, в котором они определены. Обычно на этом этапе нам сообщают о том, что мы забыли
определить какую-нибудь функцию, часто это связано с простой опечаткой. Следующий этап – самый важ-
ный. Происходит вывод типов для всех значений и проверка программы по типам. Блок кода, отвечающий
за проверку типов, является самым большим в GHC. Haskell имеет очень развитую систему типов. Многих
возможностей мы ещё не коснулись, часть из них мы рассмотрим в главе 17. Допустим, что мы исправили
все ошибки связанные с типами, тогда компилятор начнёт переводить Haskell в Core.
Core – это функциональный язык программирования, который является сильно урезанной версией
Haskell. Помните мы говорили, что в Haskell поддерживается несколько стилей (композиционный и декла-
ративный). Что хорошо для программиста, не очень хорошо для компилятора. Компилятор устраняет весь
синтаксический сахар и выражает все определения через простейшие конструкции языка Core. Далее проис-
ходит серия оптимизаций языка Core. На дереве описания программы выполняется серия функций типа Core
-> Core
. Например происходит замена вызовов коротких функций на их правые части урвнений (встраиваниеили inlining), выражения, которые проводят декомпозицию в case
-выражениях по константам, заменяютсяна соответствующие этим константам выражения. По требованию GHC может провести анализ строгости
(strictness analysis). Он заключается в том, что GHC ищет аргументы функций, которые могут быть вычисле-
ны более эфективно с помощью вычисления по значению и расставляет анотации строгости. И многие многие
другие оптимизации кода. Все они представлены в виде преобразования синтаксического дерева программы.
Также этот этап называют упрощением программы.
После этого Core переводится на STG. Это функциональный язык, повторяющий Core. Он содержит допол-
нительную информацию, которая необходима низкоуровневым бибилиотекам на этапе вычисления програм-
мы. Затем из STG генерируется код языка C
–. Это язык низкого уровня, “портируемый ассемблер”. На этомязыке не пишут программы, он предназначен для автоматической генерации кода. Далее из него получают
другие низкоуровневые коды. Возможна генерация C, LLVM и нативного кода (код, который исполняется
операционной системой).
10.2 Язык STG
STG расшифровывается как Spineless Tagless G-machine. G-machine или Г-машина – это низкоуровневое
описание процесса редукции графов (от Graph). Пока мы называли этот процесс редукцией синонимов.
Spineless и Tagless – это термины специфичные для G-машины, которая была придумана разработчиками
GHC. Tagless относится к особому представлению объектов в куче (объекты представлены единообразно, так
156 | Глава 10: Реализация Haskell в GHC
что им не нужен специальный тег для обозначения типа объекта), а Spineless относится к тому, что в от-
личие от машин-предшественников, которые описывают процесс редукции графов виде последовательности
инструкций, STG является небольшим функциональным языком. На (рис. ??
) представлен синтаксис языкаSTG. Синтаксис упрощён для чтения людьми. Несмотря на упрощения мы сможем посмотреть как происходит
вычисление выражений.
Переменные
Конструкторы
Объявлены в определениях типов
Литералы
::=
Незапакованные целые
или действительные числа
Атомы
::=
Аргументы функций атомарны
Арность функции
::=