Научная статья на тему 'Суперкомпиляция функций высших порядков'

Суперкомпиляция функций высших порядков Текст научной статьи по специальности «Математика»

CC BY
118
18
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
СУПЕРКОМПИЛЯЦИЯ / АНАЛИЗ ПРОГРАММ / ФУНКЦИОНАЛЬНОЕ ПРОГРАММИРОВАНИЕ / SUPERCOMPILATION / PROGRAM ANALYSIS / FUNCTIONAL PROGRAMMING

Аннотация научной статьи по математике, автор научной работы — Ключников Илья Григорьевич

В работе описана внутренняя структура экспериментального суперкомпилятора HOSC. Дано полное описание всех существенных понятий и алгоритмов суперкомпилятора, работающего с функциональным языком высшего порядка (подмножеством языка Haskell). Особое внимание уделяется проблемам связанным с обобщением и отношением гомеоморфного вложения для выражений со связанными переменными.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

Higher-Order Supercompilation

The paper describes the internal structure of HOSC, an experimental supercompiler dealing with programs written in a higher-order functional language (a subset of Haskell). A detailed and formal account is given of the concepts and algorithms the supercompiler is based upon. Particular attention is paid to the problems related to generalization and homeomorphic embedding of expressions with bound variables.

Текст научной работы на тему «Суперкомпиляция функций высших порядков»

И. Г. Ключников Суперкомпиляция функций высших порядков

Аннотация. В работе описана внутренняя структура экспериментального суперкомпилятора HOSC. Дано полное описание всех существенных понятий и алгоритмов суперкомпилятора, работающего с функциональным языком высшего порядка (подмножеством языка Haskell). Особое внимание уделяется проблемам связанным с обобщением и отношением гомеоморф-ного вложения для выражений со связанными переменными.

Ключевые слова и фразы: суперкомпиляция, анализ программ, функциональное программирование.

Введение

Суперкомпиляция - метод преобразования программ, предложенный В.Ф. Турчиным в 70-х годах прошлого века [24].

В работе рассматривается специализатор HOSC - экспериментальный суперкомпилятор для простого функционального языка HLL, являющегося подмножеством языка Haskell.

Несмотря на то, что алгоритмы суперкомпиляции для функциональных языков с функциями высших порядков уже описывались ранее [8,16], эти описания не являются исчерпывающими и самодостаточными, поскольку некоторые существенные детали в них опущены или описаны неформально.

Целью данной работы является полное и формальное описание алгоритмов суперкомпилятора HOSC. Данная работа является развитием предыдущих работ автора [9-11].

Исходный код суперкомпилятора HOSC публично доступен в сети Интернет по адресу http://hosc.googlecode.com.

Работа выполнена при поддержке проектов РФФИ № 08-07-00280-a и №09-01-00834-a.

© И. Г. Ключников, 2010

© Программные системы: теория и приложения, 2010

1. Язык HLL: синтаксис и семантика

Входным языком суперкомпилятора HOSC является язык HLL (Higher-order Lazy Language), являющийся подмножеством ядра языка Haskell [6].

1.1. Синтаксис языка HLL

Синтаксис языка HLL представлен на Рис. 1. Программа на языке HLL состоит из определений типов данных и определений.

Левая часть определения типа содержит имя типа (точнее, имя конструктора типа), за которым следует список типовых переменных. Правая часть - декларации конструкторов данных (разделенные вертикальной чертой, то есть dCoHi ^ dConi | ... | dConn).

Выражение - это локальная переменная, конструктор, глобальная переменная, A-абстракция, аппликация, case-выражение, локальное рекурсивное определение (letrec-выражение), локальное определение переменных (let-выражение) или выражение в скобках. Порядок привязок в let-выражениях и порядок ветвей в case-выражениях не является существенным.

Выражение ео в case-выражении называется селектором, выражения ei - ветвями. Мы требуем, чтобы в case-выражении были перечислены все конструкторы соответствующего типа данных - т.е. образцы в case-выражении должны быть полны и ортогональны.

Мы будем использовать две записи для обозначения аппликации: ei е2 и ео ei. В первом случае ei может быть любым выражением. Во втором случае мы требуем, чтобы список аргументов ei был непустым, а выражение eo не было аппликацией.

Чтобы показать, что переменная f определена в программе (то есть в программе есть определение f = e;), мы будем дописывать к переменной индекс g (global) - fg.

Определение 1 (Множества связанных, глобальных и свободных переменных выражения). Множества bv[e], gv[e], fv[e] связанных, глобальных и свободных переменных выражения e определяются по правилам, представленных на Рис. 2, Рис. 3 и Рис. 4 соответственно.

tDef ::= data tCon = dCon*; определение типа

tCon ::= tn tv* конструктор типа

dCon ::= c type* конструктор данных

type ::= tv | tCon | type ^ type | (type) типовое выражение

prog ::= tDef f = e*; программа

e ::= v переменная

| c 6“ конструктор

I fg глобальная переменная

| A v* ^ e A-абстракция

I ei e2 аппликация

| case eo of {pj ^ e*;} case-выражение

| letrec f = e0 in e1 локальное определение

| let Vj = e*; in e let-выражение

I (e) выражение в скобках

p :: = c V“ образец

Рис. 1. Синтаксис языка HLL

Hi's 1 = {}

bv[v] = {}

bv[ce“1 = и Н^1

bv[Av ^ eJ = bv[e] U {v}

bv[ei Є2і = bv[ei] U bv[e2]

bv [case eo of {с* V“k ^ є»;}1 = bv[eoJU (U U (U^fc) = bv[e] U (U U (U v*)

bv [let V* = e*; in e]

bv [letrec f = ei in Є2і = bv[ei]U bv[e2] U {f}

Рис. 2. Множество связанных переменных выражения

Определение 2 (Мультимножество связанных переменных выражения). Правила, определяющие мультимножество Ьг'Це] связанных переменных выражения е, совпадают с правилами на Рис. 2 с учетом того, что результат операции - мультимножество.

£«[/д 1

0«[сеД

#«[Л« ^ е]

5^[в1 е2]

^[саве е о/ {с «¡к ^ е,г;}]

[/е* V* = е*; гп е] д-у[/е*гес / = е1 гп е2]

Рис. 3. Множество глобальных переменных выражения

{/д }

{}

0«И

£4е1] и #«[

5«[еИи (и МеЮ £4е1 и (Ц1£4е*])

#4е1] и #«[

М/д1

М«1

/« [с ё“]|

/«[Л« ^ е]

Ме1 е2] ____________

/«[саве е о/ {с* «¡к ^ е*;}] /«[/е* = е*; гп е]

/«[/е*гес / = е1 гп е2]

= {} = {«}

= II Ме*]

= Ме] \ м

= /«[е1] и и«[е2]

= Ме] и (и Ме*]\{^к})

= (/*И\(и«¡)) и ^Ме]) = Ме1] и Ме2] \ {/}

Рис. 4. Множество свободных переменных выражения

На использование имен переменных в выражениях накладываются следующие ограничения.

Соглашение 3 (Именование переменных). Чтобы избежать конфликта имен, мы требуем, чтобы для любого выражения е множества Ь«[е], 0«[е], /«[е] попарно не пересекались и множество ^[е] не содержало повторных элементов.

Соглашение 3 не только устраняет неоднозначность в классификации переменных, но также накладывает дополнительное ограничение на Хе'Ъ-выражение: локальная переменная, введенная в Хе'Ъ-выражении может использоваться только в теле Хе'Ъ-выражения и не может использоваться в определении другой переменной того же Хе'Ъ-выражения, - это гарантирует независимость локальных переменных Хе'Ъ-выражения друг от друга.

Определение 4 (Замкнутое ИЬЬ-выражение). ИЬЬ-выражение называется замкнутым, если множество его свободных переменных пусто, т.е. /«[е] = 0

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

Определение 5 (Обновление связанных переменных). Обновление связанных переменных выражения е - согласованная замена в выражении е каждой переменной « € Ь«'[е] на новую, ранее не встречавшуюся переменную. Будем обозначать операцию обновления переменных выражения е как /гев^[е].

Язык ИЬЬ типизирован по Хиндли-Милнеру [5,18]. Далее везде предполагается, что рассматриваются только корректно типизированные программы и выражения.

1.2. Подстановка

Определение 6 (Подстановка). Подстановкой будем называть конечный список пар вида в = {«* := е*}, каждая пара в котором связывает переменную с ее значением е*. Область определения в определяется как ¿ошагп(в) = {V}. Область значений в определяется как гапде(в) = {е"}.

Язык ИЬЬ основан на Л-исчислении. В Л-исчислении подстановка является фундаментальной операцией. Чтобы обеспечить корректность подстановки, выражения рассматриваются с точностью до переименования связанных переменных, то есть с точностью до =а ([3], 2.1.11). Таким образом, операция подстановки должна быть корректной на классах =а -эквивалентности ([3], Приложение С). Т.е.

е —а е ? еі —а еі ^ е{гі • еі} —а е • е*}

В контексте суперкомпиляции (точнее - в контексте нахождения тесного обобщения двух выражений) удобно ввести следующее соглашение о подстановках:

Определение 7 (Допустимая ИЬЬ-подстановка). Подстановка в допустима по отношению к выражению е, если

(1) Ьг[е] П ¿ошат(в) = 0

(2) Уе* Є гапде(в) : Ьг[е] П /г[е*] = 0

/гевй[е]

если V := е € в иначе

V

/д в (с е*)в

с (ев)

Лv ^ (ев)

(е1 е2)в

(е1в) (е2в) ______________

саве (ев) о/ {рг ^ (е*в);} /е* V* = (е*в); гп (ев) /е*гес / = (е1в) гп (е2в)

(саве е о/ {р* ^ е*;})в

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

(/е* V* = е*; гп е)в (/е*гес / = е1 гп е2)в

Рис. 5. ИЬЬ: подстановка

Соглашение 8 (Использование подстановок). В дальнейшем мы рассматриваем только допустимые подстановки.

Соглашение 8 сродни так называемому соглашению Барендрегта ([3], 2.1.13). Использование соглашений 3 и 8 позволяет нам обеспечить корректность простого (“наивного”) применения подстановки:

Определение 9 (Применение ИЬЬ-подстановки). Результат применения подстановки в к выражению е, ев, вычисляется по правилам на Рис. 5.

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

1.3. Алгебра ИЬЬ-выражений

Определение 10 (Равенство выражений). Два выражения е1 и е2 считаются равными, если они различаются только именами соответствующих связанных переменных. Равенство выражений е1 и е2 записывается как е1 = е2.

Нужно понимать, что равенство определяется с точностью до перестановок привязок в Хе'Ъ-выражениях и ветвей в саБе-выражениях.

Определение 11 (Частный случай выражения). Выражение е2 называется частным случаем выражения е1, е1 < е2, если существует подстановка в такая, что е1в = е2. Для выражений языка ИЬЬ такая подстановка является единственной и обозначается в = е1 © е2.

Определение 12 (Переименование выражения). Выражение e2 называется переименованием выражения ei, ei ~ в2, если ei < в2, и e2 < ei. Другими словами, ei и е2 различаются только именами свободных переменных.

Определение 13 (Обобщение). Обобщением выражений ei и e2, называется тройка (eg , $i,$2), где eg - выражение, $i и $2 - подстановки, такие, что eg$i = ei и eg$2 = e2. Множество всех обобщений для выражений ei и e2 обозначается как ei ^ e2.

Определение 14 (Тесное обобщение). Обобщение (eg,$i,$2) называется тесным обобщением выражений e1 и e2 , если для любого обобщения (eg, $[,$2) € ei ^ e2 верно, что eg < eg, т.е. eg является частным случаем eg. Мы предполагаем, что определена операция П такая, что ei П e2 = (eg, $i, $2).

1.4. Семантика языка HLL

В данном разделе описывается семантика языка HLL, являющегося функциональным языком с передачей параметров по имени (call-by-name). При рассмотрении выражений языка HLL подразумевается, что задана некоторая программа, по отношению к которой и определяется смысл выражений (и может быть выполнено их вычисление).

Определение 15 (Контекст). Контекст - выражение с дырой ( } вместо одного из подвыражений. C(e) - выражение, полученное из контекста заменой дыры на выражение e.

Определение 16 (Наблюдаемое выражение, контекст редукции, редекс). Грамматика декомпозиции HLL-выражений представлена на Рис. 6. Контекст редукции con является частным случаем контекста.

Любое выражение языка HLL можно представить либо в виде наблюдаемого выражения, либо в виде контекста редукции с помещенным в дыру редексом.

Лемма 17 (Декомпозиция HLL-выражений). Любое корректно типизированное выражений e есть либо наблюдаемое выражение: e = obs, либо представимо в виде контекста редукции con с помещенным в дыру редексом red: e = con(red).

v ei cei

(Av ^ e)

0

con e

case con of {pi ^ ei;} fg

(Av ^ eo) ei case v ej of {pi ^ ei;} case c ej of {pi ^ ei;} let vi = ei; in e letrec f = ei in e2

Рис. 6. HLL: декомпозиция выражений

Определение 18 (Оператор неподвижной точки). Оператор неподвижной точки определяется как специальная функция fix:

fix = Af ^ f(fix f);

Определение 19 (Слабая головная нормальная форма). Выражение e языка HLL находится в слабой головной нормальной форме, если в нем на верхнем уровне находится конструктор (то есть e = ce) или A-абстракция (то есть e = Av ^ ei).

Определение 20 (Раскрытие глобального определения). Пусть в программе есть определение fg =: e, тогда:

unfoldfg } = fg {fg = e}

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

Определение 21 (Шаг редукции). Шаг редукции ^ - наименьшее отношение на множестве замкнутых HLL выражений, удовлетворяющее правилам на Рис. 7.

Определение 22 (Сходимость). Замкнутое выражение e сходится к слабой головной нормальной форме w, e ^ w, если e w.

e ^ обозначает, что существует w такое, что e ^ w.

obs ::=

con ::=

red ::=

cej ^ cej (El)

Avo ^ eo ^ Avo ^ eo (E2)

con(/o} ^ con(un/old(/o)} (E3)

con((Av ^ eo) ei} ^ con(eo{v := ei}} (£4)

con (case Cj e'fc о/ {c ^ e*;}} ^ con(ej {vjfc := e'fc}} (£5)

con(let Vj = ej; in e} ^ con(e{vj := ej}} (Ee)

con(letrec / = e in e'} ^ con((A/ ^ e')(/ix (A/ ^ e))} (£7)

Рис. 7. Операционная семантика HLL data List a = Nil | Cons a (List a);

const = Ax ^ (Ay ^ x);

iterate = Af z ^ Cons z (iterate f (f z));

Рис. 8. Пример программы на языке HLL

Определение 23 (Аварийное завершение с ошибкой времени выполнения). Вычисление замкнутого выражения e завершается с ошибкой времени выполнения, e ft error, если e e', e не является слабой головной формой и к e' не применимо ни одно из правил

Рис. 7.

Введем понятие операционной эквивалентности выражений [19]. Упрощенно говоря, два HLL-выражения эквивалентны, если они взаимозаменяемы в любых контекстах.

Определение 24 (Аппроксимация). Выражение ei является операционной аппроксимацией выражения e2, ei С e2, если в любом контексте C, таком, что C(ei} и C(e2} - замкнутые выражения, из C(ei} ft следует C(e2} ft и из C(ei} ft error следует C(e2} ft error.

Определение 25 (Эквивалентность). Выражение ei операцион-но эквивалентно выражению e2, ei = e2, если ei С e2 и e2 С ei.

В определениях 24 и 25 для упрощения мы считаем, что рассматриваются только такие контексты C, в которых оба выражения C(e1} и C(e2} корректно типизируемы. В [10] рассматривается эквивалентность в общем случае.

Пример программы на языке HLL приведен на Рис. 8.

В соответствии с введенной семантикой результатом вычисления выражения

iterate (const Nil) Nil

будет

Cons Nil (iterate (const Nil) (const Nil Nil))

2. Отношение трансформации HOSC

Понятия “суперкомпиляции” и “суперкомпилятора” не совсем тождественны, поскольку конкретные суперкомпиляторы могут использовать методы анализа и преобразования программ, позаимствованные из других областей информатики (и не являющиеся суперкомпиляцией).

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

В данном разделе описывается методологическая основа суперкомпилятора HOSC в виде отношения трансформации (а не конкретного алгоритма) и обосновываются выбранные методы с точки зрения выбранной цели - трансформационного анализа программ.

2.1. Устранение локальных определений

Непосредственно перед суперкомпиляцией исходная программа р преобразуется методом A-лифтинга [7] в программу p', - в результате в программе р' отсутствуют let-выражения и letrec-выражения. Решено устранять let-выражения по следующим причинам:

(1) Для выражений с let-выражениями понятия переименования, частного случая и обобщения программируются гораздо сложнее, - нужно учитывать возможность перестановки привязок в let-выражениях.

(2) Традиционно let-выражения используются в суперкомпиляции для обозначения результата обобщения. Мы не будем отступать от этой практики. Если не устранять let-выражения, то необходимо различать let-выражения исходной программы и результаты обобщения, что приводит к излишним усложнения алгоритмов.

(3) Поскольку суперкомпилятор HOSC предназначен для анализа программ, устранение let-выражений позволяет агрессивнее распространять позитивную информацию, что обеспечивает более глубокое преобразование программы. Поэтому в дальнейшем, если это особо не оговорено, рассматриваются HLL-выражения без конструкций let и letrec.

При разработке оптимизирующего суперкомпилятора для Haskell ([4,16,17]) устранять let-выражения таким образом недопустимо, так как при таком подходе остаточная программа может дублировать вычисления выражений, которые вычислялись в исходной программе один раз, и размер кода может сильно вырасти по сравнению с размером кода исходной программы.

2.2. Представление множеств

Построение дерева процессов и построение частичного дерева процессов являются метавычислениями. В метавычислениях рассматривают не только одиночное состояние вычисления, но также множества состояний вычислений. Как отмечено в [1], множества должны быть представлены конструктивно - в виде выражений некоторого языка.

Определение 26 (Конфигурация). HLL-выражения (без let-выражений и letrec-выражений) со свободными переменными будем называть HLL-конфигурациями, а свободные переменные - конфигурационными переменными.

Определение 27 (Декомпозиция конфигурации). Let-выражение

e; = let Vj = ei ; m eo

называется декомпозицией HLL-конфигурации e, в; С в, если каждое выражение ej - конфигурация, eo < e, eo ф e и e = eo{vj := ej}.

Соответственно, let-выражения представляют декомпозицию конфигураций.

Существует и другие способы задания языка для представления конфигураций, например в работе “Rethinking Supercompilation” [17] конфигурацией является только выражение в специальной форме. В работе “Supercompilation by Evaluation” [4] в качестве конфигурации рассматривается состояние абстрактной машины, состоящее из кучи, стека и активного выражения.

2.3. Построение частичного дерева процессов

Определение 28 (Дерево процессов). Деревом процессов называется ориентированное (возможно бесконечное) дерево, каждому узлу которого приписана HLL-конфигурация. Дуги дерева процессов будем обозначать ^.

В результате метавычислений в общем случае строится бесконечное дерево процессов. Задача суперкомпиляции - превратить его в конечный (возможно, циклический) граф, который называется частичным деревом процессов.

Определение 29 (Частичное дерево процессов). Частичное дерево процессов является расширением понятия дерева процессов и отличается от обычного дерева процессов следующими свойствами:

• В узлах частичного дерева процессов помимо конфигураций могут присутствовать декомпозиции конфигураций.

• В частичном дереве процессов могут быть циклы: пусть лист в является потомком узла а и выражение в узле в является переименованием выражения в узле а (e.expr ^ a.expr). Тогда можно провести специальную дугу в ^ а из повторного (рекурсивного) узла в в базовый (или функциональный) узел а. Из узла может выходить не более одной специальной дуги ^.

Определение 30 (Обработанный лист). Лист дерева в называется обработанным, если находящееся в нем выражение в.eжpr является константой, конфигурационной переменной или из него выходит специальная дуга ^.

Определение 31 (Завершенное частичное дерево процессов). Частичное дерево процессов является завершенным, если все его листья являются обработанными.

Операции над частичным деревом, которые нам понадобятся в дальнейшем, представлены на Рис. 9 в формальном виде. Некоторые операции не используются в данном разделе, но будут использоваться дальше - в разделе 4.

Остановимся подробно на операциях прогонки - driveo и drive. Операция driveo делает шаг прогонки без распространения позитивной информации. Оператор Do (Рис. 10) принимает в качестве аргумента выражение, находящееся в листе, и возвращает ноль или более выражений ei,..., efc. Результатом выполнения операции driveo (t, в)

Суперкомпиляция функций высших порядков

49

incomplete(t) trivial (node)

children(t, a) addChildren(t, в, es)

replacent, a, expr)

ancestors(t, в) find(ns, в, p)

fold(t, a, в) abstract(t, a, в)

[a A tl

[a ff tl drive(t, a)

driveo (t, a)

drive* (t, a) decompose(t, в)

unprocessedLeaf (t)

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

Возвращает true, если дерево t не является завершенным.

Возвращает true или false в зависимости от того, является данный узел тривиальным или нет.

Возвращает упорядоченный список дочерних узлов узла a дерева t

Подвешивает к узлу в дерева t дочерние узлы и помещает в них выражения es. Количество подвешенных узлов совпадает с количеством элементов списка es.

Заменяет узел a на новый узел в такой, что в.expr = expr. Поддерево, имеющее своим корнем a, удаляется.

Возвращает узел всех предков узла в Находит первый среди узлов ns узел a, такой, что верен предикат p(a.expr, в^х^). “Зацикливает” a и в: в ^ a.

= replacent, a, let Vi := et; in eg),

где (eg,в1,в2) = a.expr П в.expr, eg6i =

eg {vi := et} = let Vi = et; in eg.

Возвращает все повторные узлы a, [a ^ tl = [Pi] : в-i ^ a, или •, если a не является базовым узлом.

Возвращает базовый узел a, [a ff tl = в : a ^ в, или •, если a не является повторным узлом. = addChildren(t, a, Vla.expr]) - делает шаг прогонки c распространением позитивной информации.

= addChildren(t, a, D^a.expr-]) - делает шаг прогонки без распространения позитивной информации.

= choice{drive0(t,a), drive(t, a)}

= replace(t, в, ei), где ei - некоторая декомпозиция конфигурации в.expr.

Возвращает самый левый необработанный лист a дерева t, или •, если все листья обработаны.

Рис. 9. Операции над частичным деревом процессов

Do \v ei] [v, ei] (Do)

Do {с Щ N (D2o)

Do [Avo ^ eo] [eo] (D3)

Do lcon{fo}] [con{unf old(fo)}] D )

Do\con((Avo ^ eo) ei}] [con{eo{vo := ei}}] Г 1 D )

Do[con{case c3 e'k of {ci vik ^ ei;}}] [con{ej{vjk := ek}}J (D6o)

Do[con{case v ej of {pi ^ ei;}}] |v ej, con{ei}j (D?)

О e in e] [e, e] (d8)

Рис. 10. Шаг прогонки без распространения позитивной информации

Div ei] [v, ei] (Di)

D[ce] (D2)

D[Avo ^ eo] [eo] (Ds)

D'[con{fo)1] [con{unfold(fo)}] D)

D\con{(Avo ^ eo) ei}] [con{eo{vo := ei}}] Г 1 (D6)

D\con{case c3 ekk of {ci vik ^ ei;}}] |con{ej{vjk := ek}}J (De)

D\con{case v ej of {pi ^ ei;}}] |v ej, con{ei}{v ej := Pi}j (Dr)

D[let vi = ei; in e]] [e, ei] (Ds)

Рис. 11. Шаг прогонки с распространением позитивной информации

является подвешивание к листу в k дочерних узлов с выражениями ei,..., . Операция drive отличается от операции driveo только

тем, что для получения выражений в дочерних узлах используется оператор D (Рис. 10).

while incomplete(t) do в = unprocessedLeaf (t) t = drive(t, в) end

Рис. 12. HOSC: построение дерева процессов для конфигурации в0

while incomplete(t) do в = unprocessedLeaf (t)

t = choice{drive* (t, в), generalize(t, в), f old(t, в)} end

Рис. 13. HOSC: построение частичного дерева процессов для конфигурации во

Операторы Do и D отличаются только седьмым правилом. При использовании оператора D, когда необходимо сделать шаг прогонки для выражения вида con(case v ej of {pj ^ e^}}, рассматриваются различные ветви с учетом того, что вычисление может достигнуть i-й ветви case-выражения только в том случае, когда v ej имеет вид Pi, - информация об этом распространяется в тело выбранной ветви. Оператор Do не распространяет эту информацию. Особенностью оператора Do является то, что при применении седьмого правила все выражения в дочерних узлах буду строго меньше по размеру, чем выражение в родительском узле - это факт будет использован в разделе 4, для того, чтобы гарантировать завершаемость суперкомпилятора.

Рассмотрим построение дерева процессов для конфигурации eo. Соответствующий алгоритм представлен на Рис. 12. Вначале создается дерево t c единственным узлом, в который помещается стартовая конфигурация: t = [ eo ]. Затем, пока есть хотя бы один необработанный узел, к нему применяются правила прогонки. В большинстве случаев дерево процессов будет бесконечным.

Перейдем к рассмотрению построения частичного дерева процессов для стартовой конфигурации eo. Недетерминированный алгоритм построения частичного дерева процессов для выражения eo приведен

на Рис. 13. Оператор choice используется как оператор недетерминированного выбора - выполняется любая (допустимая) операция из аргументов оператора choice.

Алгоритм построения частичного дерева процесса процессов является расширением алгоритма построения дерева процессов - при построении дерева процессов для необработанного узла осуществляется шаг прогонки с распространением позитивной информации. При построении частичного дерева процессов по алгоритму на Рис. 13 разрешается для необработанного узла либо осуществить шаг прогонки с распространением позитивной информации, либо осуществить шаг прогонки без распространения позитивной информации, либо произвести декомпозицию конфигурации, либо, если среди предков есть совпадающая конфигурация (с точностью до переименования конфигурационных переменных), выбрать любую такую конфигурацию и провести специальную дугу ^.

Можно рекурсивно перечислить все частичные деревья процессов, которые могут быть построены по данному недетерминированному алгоритму для стартовой конфигурации eo и программы p - достаточно на каждом шаге рассмотреть все возможные выборы. Обозначим такое множество завершенных частичных деревьев процессов как Thosc(p, eo). Отметим, что в общем случае среди всех возможных трасс выборов построение частичного дерева процессов завершается только на малой части трасс.

2.4. Генерация остаточного выражения

Любое частичное дерево процессов t G Thosc(p, eo), построенное для стартовой конфигурации eo, является достаточным для вычисления любого замкнутого выражения e G eo в любом контексте. Дерево процессов t также можно преобразовать в остаточное выражение (программу). Однако, как и при построении частичного дерева процессов, у нас есть степени свободы - можно разными способами преобразовать частичное дерево процессов в остаточное выражение, которое будет эквивалентно исходной.

В данном разделе описывается конкретный алгоритм преобразования графа конфигураций t в программу на языке HLL.

C [a]t,E

^ letrec f = Avi ^ (C [a..expr]t a s, )O' infvi если \o. h t\ = • (Cl)

где_

[¡3i] = \a h t\,di = a.expr © @i.expr, v' = domain(U 0i), O' = {v' := vi},

£' = "S U (a, f' vi), f' и vi - новые ^ fsigO если \a ЇЇ t\ = • (C2)

где

fsig =4\a ЇЇ t\),

O = \a ЇЇ t\.expr © a.expr ^ C'[a.expr]t a s иначе (C3)

C'llet vi = ei; in e\t, a, E ^ CiY'}t, e{vi = ^Y'At, s} (Ci)

, а,Е ^ , Е {г Шь,Е

С ег1г,а,Е ^ v С[7г1^Е (С2)

С [с е.г\1а,Е ^ с С[7г!(,Е (Сз)

С'^о ^ еоЛь^Е ^ ХУ° ^ СЩ^Е С)

С'1соп{саве V е' о/ {рг ^ ег;})]ь,аЕ

^ тве СЬ']г,Е,of {Рг ^ СШг,Е';} (С5)

С'1соп{(\щ ^ео) е1)] ^ С1Ь\е (С6)

С'1соп(сазе с еГ {рг ^ ег;})14 а е ^ СЪ'1ь е (С7)

С'[сОпШь,а,Е ^ С [7\е С)

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

Если в правой части правила используется 7г, считается, что:

['Щ = сЬАМгеп(1, а), - в этом случае все дочерние узлы рассматриваются единообразно. Если же в правой части используются 7' и 7г, считается, что: [у', = сЪлМгеп(1, а), - это соглашение используется в случае,

когда дочерний узел заведомо один (в правой части правила используется только 7') или же первый дочерний узел требует “особого рассмотрения”.

Рис. 14. ПОЯС: генерация остаточного выражения

Алгоритм (представленный на Рис. 14) определяется через два взаимно рекурсивных оператора (функции): С и С'. Остаточная программа для графа конфигураций і определяется как

Для построения остаточной программы мы обходим частичное дерево сверху вниз (начиная, естественно, с корня). При обходе базового узла, генерируется определение рекурсивной функции в форме Ы;гес-а. Соответствие между базовым узлом и сигнатурой заносится в Я. Впоследствии Я используется для генерации рекурсивного вызова.

Алгоритм обладает следующими особенностями:

(1) Остаточная программа является одним самодостаточным выражением без глобальных определений - все рекурсивные функции определяются локально.

(2) В остаточной программе есть только рекурсивные функции.

(3) При конструировании рекурсивных функций аргументами становятся только изменившиеся части конфигураций, что позволяет понизить арность рекурсивных функций в остаточной программе.

Как показывают эксперименты [12,14], эти особенности положительно сказываются на способности суперкомпилятора НО£С к распознаванию эквивалентности выражений и улучшающих лемм.

2.5. Отношение транформации ИСБС

Определение 32 (Отношение трансформации НОЙС). Пусть дана программа р, ИЬЬ-выражение е и остаточное выражение р'. Выражения е и е' связаны отношением трансформации (е НО5Ср е'), если существует частичное дерево процессов £ € Тяояс(р, е) такое, что е' = С [¿.гоо^ц.

Отношение трансформации НО^С задает для данной входной программы р и стартового выражения е множество остаточных выражений. Как было отмечено ранее, для целей анализа может быть полезно иметь несколько остаточных выражений, - возможно, что некоторые остаточные выражения будут легче поддаваться анализу, чем другие.

Теорема 33 (Корректность суперкомпилятора И08С). Остаточное выражение, связанное со стартовым выражением отношением трансформации И08С, и стартовое выражение операционно эквивалентны:

е ЯО5Ср е' ^ е = е'

Тесное обобщение

• e' П e" = s(e' П e")

Правило общего функтора

• v П v = _К {}, {})

• c ei П cei' = (c ё“ U 0', U 0''), где

— (e,0',0'') = ei П e"

• el e2 П e" e2' = (ei e2, 0' П 02, П 02'), где

— (ei,0',0'') = ei П ei'

• Av' ^ e' П Av'' ^ e'' = (eg, 0', 0''), если 0' и 0'' допустимы для eg, где

— eg = Av ^ e

— (e, 0', 0'') = e'{v' := v} П e''{v'' := v}

• case eo of {ci v'fc ^ ei;} П case e^ of {c v" ^ ei';} =

(eg ,U0i ,U 0''), если все 0' и 0'' допустимы eg, где

eg case eo of {ci vik ^ ei;}

— (eo,0o, 00) = eo neo________ ___________

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

—Je^ 0', 0'') = ei{v'fc := vifc} П ei'{vi"fc := vifc}

• e1 П e2 = (v, {v := e1}, {v := e2})

Правило общего подвыражения

• s(e, {}, {}) = s(e, {}, {})

• s(e, {vi := e'} U 0', {vi := e''} U 0'') = s'(s(e, 0', 0'')) где

— s'(e, 0', 0") = (e{vi := v2}, 0', 0'')

если 3v2 : {v2 := e'} G 0', {v2 := e''} G 0''

— s'(e, 0', 0'') = (e, {vi := e'} U 0', {vi := e''} U 0'')

в противном случае

Рис. 15. HLL: алгоритм обобщения

3. Гомеоморфное вложение и обобщение конфигураций

В данном разделе в качестве ИЬЬ-выражений рассматриваются только конфигурации (то есть выражения без Хв'Ъ-выражений и letrec-выражений).

3.1. Нахождение тесного обобщения

Алгоритм нахождения тесного обобщения для выражений (конфигураций) первого порядка хорошо описан в литературе по суперкомпиляции. Однако, в работах, описывающих суперкомпиляторы для языков высшего порядка ([8, 16]), вопрос нахождения тесного обобщения выражений со связанными переменными обходится стороной.

Если рассматривать А-абстракции и case-выражения как специальные конструкторы и “наивным” образом расширить алгоритм из [21,23], то в обобщении, найденном таким образом, могут появляться недопустимые подстановки (см. Определение 7). В результате обобщения должна получиться такая тройка (ед, 01, 02), где 01 и 02 - допустимые по отношению к ед подстановки. В свете выбранных нами соглашений о переменных и подстановках, алгоритм нахождения тесного обобщения должен гарантировать допустимость подстановок, полученных в результате обобщения.

Обобщение А-абстракций и case-выражений необходимо рассматривать особым образом - если в результате решения подзадач обобщения соответствующих подвыражений возникает недопустимая подстановка, это означает, что обобщением рассматриваемых выражений является переменная.

Определение 34 (Алгоритм нахождения тесного обобщения конфигураций). е' П е'' = в(е' П е''), где операции П и в определены на Рис. 15.

Алгоритм 34 применим для нахождения тесного обобщения двух любых конфигураций языка ИЬЬ и учитывает требования соглашений 3 и 8. Правила применяются в порядке их перечисления. Переменные V и в 3-м, 4-м и 5-м правилах общего подвыражения -новые, ранее не встречавшиеся, переменные. Наиболее интересные детали алгоритма, учитывающие новые обстоятельства, подчеркнуты. Отметим, что алгоритм 34 сформулирован в рекурсивной форме. В литературе по суперкомпиляции алгоритм обобщения традиционно описывается в итеративной форме. Сформулировать алгоритм обобщения выражений со связанными переменными в итеративной форме представляется крайне затруднительно.

Суперкомпиляция функций высших порядков

57

Вложение

e' < e''

если e' <v e'', e' <c e'' или e' e''

Вложение переменных

fg fg

v <!v v'

Сцепление (Coupling)

c ei Av'

' <c cei'

<C Av"

-i e2 ei

case e' of {ci v

если V* : ei < e" если e' < e'' если V* : ei < ei'

ifc ^ ei;} <c case e'' of {ci v'k ^ ei';}

если e' < e'' и V* : ei < ei'

Погружение (Diving)

e <!d c ei если 3* : e < ei

e <!d Avo ^ eo если e < eo

e <!d ei e2 если 3* : e < ei

e <!d case eo of {c vk ^ ei;} если 3* : e < ei

e

e

Рис. 16. HLL: простое гомеоморфное вложение

Определение 35 (Несопоставимые конфигурации). Конфигурации е1 и е2 называются несопоставимыми, е1 ^ е2, если е1 П е2 = (у, 01, 02), то есть обобщенная конфигурация является переменной.

3.2. Гомеоморфное вложение

В литературе по суперкомпиляции для языков высшего порядка используется классическое гомеоморфное вложение, “адаптированное” для выражений со связанными переменными, - А-абстракции и cаse-выражения рассматриваются как специальные конструкторы и связанные переменные и свободные переменные не различаются.

Определение 36 (Простое гомеоморфное вложение <). Простое вложение ИЬЬ-выражений определяется индуктивно в соответствии с правилами на Рис. 16.

Однако, в контексте суперкомпиляции, простое гомеоморфное вложение обладает существенным недостатком: несопоставимые выражения могут вкладываться через сцепление: е1 <с е2. В результате

нам придется делать декомпозицию одного из выражений без учета истории построения частичного дерева, что противоречит принципу обобщения в суперкопиляции (см. [25]).

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

При проверке двух HLL-выражений на уточненное вложение используется таблица соответствия связанных переменных р:

р = {К, О, ..., «, О}

Определение 37 (Уточненное гомеоморфное вложение <*). Вложение <* HLL-выражений определяется индуктивно в соответствии с правилами на Рис. 17.

При использовании уточненного вложения несопоставимые конфигурации не вкладываются через сцепление.

4. Суперкомпилятор HOSC

В разделе 2 суперкомпилятор для языка HLL был описан в виде отношения трансформации HOSC, - недетерминированного алгоритма получения эквивалентного выражения. В данном разделе будут рассмотрены детерминированные алгоритмы суперкомпиляции для языка, гарантированно завершающиеся на любой входной конфигурации и удовлетворяющие введенному отношению трансформации HOSC.

4.1. Схема суперкомпилятора

Уточненное вложение <* (Рис. 17) является достаточно сильным усложнением простого вложения < (Рис. 16). Чтобы обосновать практичность уточненного гомеоморфного вложения применительно к использованию суперкомпилятора для трансформационного анализа, рассмотрим различные варианты суперкомпилятора (как с использованием адаптированного вложения, так и с использованием уточненного вложения) и сравним их на модельной задаче - доказательстве эквивалентности выражений.

Поскольку при использовании адаптированного вложения < несопоставимые выражения могут вкладываться через сцепление, нам

Вложение

e/ * I II def У e// = e I * /e

e/ I* // def / e// = e/ IC e" |{}

e/ Id // def / e// = e/ Id e" |{}

Вложение с учетом таблицы связанных переменных

e/ I* e" |р если e/ <V e"|Р, e/ Id e"|P или e/ <С e" |Р

Вложение переменных

f IV f

v/ <v v" |p если (v/, v") e P

v/ IV v" |p если v/ e domain(p) и v" e range(p)

Сцепление (Coupling)

c ei cei/ |p если Vi: ej ei/ |p

Av/ ^ e/ I* Av" ^ в" |p если e/ I* e" |pU{«v")}

e/ e2 I* e/ e£ |p если Vi : ej I* ej/ |p

case e/ of {cj v'fc ^ ej;} I* case e// of {Cj v'/fc ^ e";} |p

если e/ I* e" |p и Vi : ej I* ej | ./¡w—тту,

— IP j — j lPU{(v'fc,v'k)}

Погружение (Diving) только если fv(e) П domain(p) = 0 e Id c ej |p если 3i : e I* ej |p

e Id Avo ^ eo |p если e I* eo |pu{(^v<,)}

e Id eo ej |p если 3i : e I* ej |p

e Id case e/ of {cj ^ ej;} |p

если e I* e/ L или 3i : e I* ej I . r7---------------r,

— — 4 lpU{(^,Vifc)>

Рис. 17. HLL: уточненное гомеоморфное вложение

split (t, в, eie2) = replace(t, в, es), где

es = let v/ = e/; v2 = e2; in v/v2 split(t, в, case v of {pj^e;}) = addChildren(t, в, [v,ej])

split(t, в, case e of {pj^ej;}) = replace(t, в, es), где

es = let v = e in case v of {p4 ^ ej7} split(t, в, e) = drive(t, в)

Рис. 18. HLL: расщепление конфигурации

придется делать декомпозицию текущей конфигурации. В случае языка SLL это делается достаточно просто - текущей конфигурацией является вызов функции, и мы отдельно рассматриваем аргументы функции. Такая декомпозиця обладает важным свойством, что размеры всех компонент получившейся декомпозиции меньше размера обобщаемого выражения, что критически важно для того, чтобы гарантировать завершаемость.

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

Определение 38 (Операция декомпозиции конфигурации split). Операция split осуществляется в соответствии с правилами на Рис. 18.

Особенность определенной операции split состоит в работе с case-выражениями. Если селектор case-выражения является переменной, то делаем шаг, похожий на прогонку, - рассматриваем ветви, но не распространяем позитивную информацию - таким образом, размер выражений в новых дочерних узлах будет строго меньше размера расщепляемого выражения (это важно для завершаемости суперкомпилятора). Если селектор не является переменной, то мы обобщаем селектор.

Рассмотрим алгоритм построения частичного дерева процессов, представленный на Рис. 19. Алгоритм является обобщением алгоритмов для языка первого порядка, представленных в [20,22,23], и допускает следующую параметризацию:

(1) При поиске кандидата на зацикливание для листа в рассматриваются не все предки, а только computeRelAncs^).

(2) В качестве свистка рассматривается любой предикат whistle.

Соответственно для конкретной реализации HLL-суперкомпилятора необходимо определить операции computeRelAncs и whistle.

4.2. Суперкомпилятор SCyk

Под суперкомпилятором HOSC далее понимается параметризованный суперкомпилятор SCijk, позволяющий получать в качестве

Суперкомпиляция функций высших порядков

61

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

while incomplete(t) do в = unprocessedLeaf (t) relAncs = computeRelAncs^) a = find(relAncs, в, whistle) if a = • and a.expr ~ в^ж^г then t = fold(t, a, в) else if a = • and a.expr < в^ж^г then t = abstract(t, в, a) else if a = • then

if a.expr ^ в^ж^г then t = split(t, в, в^ж^г) else

t = abstract(t, a, в) end else

t = drive(t, в) end end

Рис. 19. HLL суперкомпилятор

eClass(let vj = ej in e) eClass(v ej) eClass(c ej) eClass(Av ^ e) eClass(con((Av ^ eo) e /}) eClass(con(fg})

eClass(con(case c ej of {pj^^"ejj}}) eClass(con(case v ej of {pj^^"ejT}})

Рис. 20. Типы выражений

результата в общем случае несколько остаточных программ (в зависимости от значений параметров). Наличие нескольких остаточных программ бывает полезным при использовании суперкомпиляции для анализа программ, - например, для распознавания улучшающих лемм [14].

=0

=0

=0

=0

=1

=2

=3

=4

Прежде чем перейти к рассмотрению конкретных функций whistle и compwteRelAncs, реализованных в SCjk, нам необходимо ввести несколько определений.

Определение 39 (Классы HLL-выражений). Все выражения языка HLL разбиваются на 5 классов в соответствии с правилами на Рис. 20.

Определение 40 (Тривиальный узел). Узел в называется тривиальным, если находящееся в нем выражение в^жрг является наблюдаемым или let-выражением. (eClass(e.expr) = 0)

Определение 41 (в-транзитный узел). Узел в называется в-транзитным, если в.ехрг = con((Avo ^ eo) e і}. (eClass^.expr) = 1)

Определение 42 (Глобальный узел). Узел в - глобальный, если в.ехрг = con(case v ej of {p* ^ ei;}}. (eClass^.expr) = 4)

Определение 43 (Локальный узел). Все узлы за исключением глобальных считаются локальными.

Определение 44 (Кандидат на зацикливание). Кандидатом на зацикливание являются все узлы, за исключением тривиальных и в-транзитных узлов. (expClass^.expr) > 1)

Замечание 45 (Обоснование выбора кандидатов). Если включать в-транзитные узлы в число кандидатов на зацикливание, то все рассматриваемые далее суперкомпиляторы показывают неудовлетворительные результаты. Исключение в-транзитных узлов из списка кандидатов является безопасным для завершаемости суперкомпилятора - типизация гарантирует, что в частичном дереве процессов не будет бесконечной ветки, на которой нет ни одного кандидата.

Определение 46 (Релевантные с учетом контроля предки-кандидаты). Пусть в - узел-кандидат. Релевантные с учетом контроля предки-кандидаты узла в определяются следующим образом:

• все глобальные предки ai узла в, являющиеся кандидатами, если

в - глобальный узел

• все локальные предки ai узла в, являющиеся кандидатами, такие, что на пути от a до в не встречается глобальный узел, если

в - локальный узел

Рассмотрим конкретизацию алгоритма - алгоритм SCjjfc. Алгоритм SCjjk зависит от трех параметров:

length (concat xs) = sum (map length xs) (1)

map f (append xs ys) = append (map f xs) (map f ys) (2)

filter p (map f xs) = map f (filter (compose p f) xs)(3)

map f (concat xs) = concat (map (map f) xs) (4)

iterate f (f x) = map f (iterate f x) (5)

map (compose f g) = compose (map f) (map g) (6)

map f xs = join xs (compose return f) (7)

Рис. 21. Тесты

(1) i - Какое гомеоморфное вложение использовать в качестве свистка: IC (+) или Ic (-)?

(2) j - Разделять ли узлы на глобальные и локальные при поиске релевантных кандидатов [23] (+) или не разделять (—)?

(3) k - Требовать ли дополнительно при поиске кандидата совпадение типов выражений (+) или нет ( —)?

Более формально, операции whistle и computeRelAncs для суперкомпилятора SCijk определяются следующим образом:

• whistle(e i, e2) =

— e i Ic e2, если j = — и k = —

— e i IC e2, если j = + и k = —

— e i Ic e2 и eClass(e i) = eClass(e2), если j = — и k = +

— e i IC e2 и eClass(e i) = eClass(e2), если j = + и k = +

• computeRelAncs (в) =

— предки-кандидаты узла в, если j = —

— предки-кандидаты узла в с учетом контроля, если j = + Таким образом, алгоритм SCjjfc описывает восемь вариантов суперкомпилятора.

Теорема 47 (Корректность суперкомпилятора SCijk). Все представленные суперкомпиляторы SCijk корректны в смысле операционной эквивалентности исходной и остаточной программы.

Теорема 48 (Завершаемость суперкомпилятора SCijk). Все представленные суперкомпиляторы SCijk завершаются на любом корректном задании.

Полные доказательства теорем приведены в работах [10,11].

data List a = Nil | Cons a (List a); data Nat = Z | S Nat;

data Boolean = True | False;

data Pair a b = P a b;

compose = Af g x ^ f (g x);

outl = Ap ^ case p of { P a b ^ a;};

outr = Ap ^ case p of { P a b ^ b;};

uncurry = Af p ^ case p of {P x y ^ f x y;};

curry = Af b c ^ f (P b c);

cond = Ap f g a ^ case (p a) of {True ^ fa; False ^ g a;}; foldn = Ac h x ^ case x of {

Z ^ c;

S x1 ^ h (foldn c h x1);

};

plus = foldn (Ax ^ x) (Af y ^ S (f y));

foldr = Ac h xs ^ case xs of {

Nil ^ c;

Cons y ys ^ h y (foldr c h ys);

};

concat = foldr Nil append;

sum = foldr Z plus;

filter = Ap ^ foldr Nil (curry

(cond (compose p outl) (uncurry (Ax xs ^ Cons x xs)) outr)); iterate = Af x ^ Cons x (iterate f (f x)); length = foldr Z (Ax y ^ S y); join = Am k ^ foldr Nil (compose append k) m;

return = Ax ^ Cons x Nil;

map = Af ^ foldr Nil (Ax xs ^ Cons (f x) xs);

append = Axs ys ^ case xs of {

Nil ^ ys;

Cons x1 xs1 ^ Cons x1 (append xs1 ys);

};

Рис. 22. Определения функций для тестов

8е X 0 1 + 1 X 0 1 1 + ®е—++ X 0 + 1 1 йе++— X о + + Хе+++

(1) - - - - - + - +

(2) - + + + - + + +

(3) - + - + - + - +

(4) - - - - - + - +

(5) - - + + - - + +

(6) - + + + - + + +

(7) + + + + + + + +

Рис. 23. Сравнение суперкомпиляторов на тестах

4.3. Сравнение суперкомпиляторов

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

Для сравнения восьми вариантов суперкомпилятора используется модельная задача: доказательство эквивалентности выражений [12]. Рассматриваемые тестовые примеры используют функции высших порядков и оперируют потенциально бесконечными данными. Функции над парами, списками и числами, используемые в тестах, показаны на Рис. 22. Сами тесты представлены на Рис. 21. Тест считается пройденным суперкомпилятором, если он сумел привести оба выражения к одной и той же синтаксической форме, и не пройденным в противном случае.

Результаты эксперимента, представленные на Рис. 23, показывают, что наилучшим сочетанием параметров является использование уточненного гомеоморфного вложения, разделения узлов на локальные и глобальные и разделение выражений на классы в соответствии с типом редекса. То есть дополнительные приемы (разделение узлов на локальные и глобальные, требование совпадения редекса) не могут в полной мере компенсировать учет связанных переменных при использовании уточненного вложения <^.

Рассмотрим особенности уточненного вложения <* на примере преобразования выражения тар f (concat хб) суперкомпиляторами 8е ___++ и 8е+++. После нескольких шагов работы суперкомпилятора Эе_++ срабатывает свисток:

case

case xs of {

Nil ^ Nil;

Cons v54 v55 ^ (append v54) (foldr Nil append v55);

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

}

of {

Nil ^ Nil;

Cons v56 v57 ^ (Av58 v59 ^ Cons (f v58) v59)

v56 (foldr Nil (Av60 v61 ^ Cons (f v60) v61) v57);

}

case

case v54 of {

Nil ^ foldr Nil append v55;

Cons v87 v88 ^ Cons v87 (append v88 (foldr Nil append v55));

}

of {

Nil ^ Nil;

Cons v89 v90 ^ (Av91 v92^ Cons (f v91) v92)

v89 (foldr Nil (Av93 v94^ Cons (f v93) v94) v90);

}

Верхнее выражение обобщается суперкомпилятором Sc_________________++ следую-

щим образом:

let v100 = case xs of {

Nil ^ Nil;

Cons v54 v55 ^ (append v54) (foldr Nil append v55);

}

in case v100 of {

Nil ^ Nil;

Cons v89 v90 ^

(Av91 v92^ Cons (f v91) v92)

v89 (foldr Nil (Av93 v94^ Cons (f v93) v94) v90);

}

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

Результаты работ суперкомпиляторов Sc___________++ и Sc___++ приведены

на Рис. 24 и Рис. 25.

letrec f1 = Az ^

case z of { Nil ^ Nil; Cons p q ^ Cons (f p) (f1 q); }

in

f1

(letrec

g = Ax ^ case x of {

Nil ^ Nil;

Cons k l ^

(letrec h = Ay ^ case y of { Nil ^ g l; Cons s t ^ Cons s (h t); } in (h k));

}

in g xs)

Рис. 24. map f (concat xs): результат работы Sc_

++

letrec

g = Ax ^

case x of {

Nil ^ Nil;

Cons p q ^

letrec h = Ay ^ case y of

{ Nil ^ (g q); Cons s t ^ Cons (f s) (h t); } in h p;

}

in g xs

Рис. 25. concat (map (map f) xs): результат работы Sc+++

5. Обзор литературы

Первоначально суперкомпиляция разрабатывалась для конкретного языка Рефал [24] и была сформулирована в его терминах. В настоящее время, наиболее продвинутым суперкомпилятором для языка Рефал является 8СР4 [15].

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

Первое полное и формальное описание алгоритмов, составляющих необходимую часть любого суперкомпилятора (прогонка, обобщение, генерация остаточной программы), встречается в магистерской работе Сёренсена [20]. В этой работе описан суперкомпилятор простейшего функционального языка первого порядка (семантика вычислений - вызов по имени). Тот же самый суперкомпилятор с небольшими модификациями (расширен входной язык) описан в совместных статьях Глюка и Сёренсена [21,22]. В работе [13] суперкомпилятор Сёренсена приведен в виде работающей программы на языке Scala.

Полное описание алгоритмов для суперкомпилятора языка TSG (плоский функциональный язык первого порядка) представлено у Абрамова [2].

В последние несколько лет возрос интерес к суперкомпиляции функциональных языков с функциями высших порядков [4, 8,16,17] для оптимизации.

В работах [8,16] в качестве свистка используется адаптация классического гомеоморфного вложения < для языка первого порядка [21] на случай функций высшего порядка - связанные переменные рассматриваются таким же образом, как и конфигурационные переменные. Однако, как показывают эксперименты, использование такого вложения в суперкомпиляторе, предназначенном для анализа программ, дает неудовлетворительные результаты.

Использование же предложенного автором уточненного гомеоморфного вложения <*, учитывающего свойства связанных переменных, напротив, позволяет проводить более глубокие и содержательные преобразования программ [12,14].

Более того, в упомянутых работах [8,16] не приведен алгоритм нахождения тесного обобщения выражений со связанными переменными, - лишь сказано, что он является расширением алгоритма для нахождения тесного обобщения для выражений первого порядка. В данной работе полностью и формально описан алгоритм нахождения тесного обобщения для HLL-выражений. В основе алгоритма лежит

выбранное синтаксическое соглашение о допустимой по отношению к выражению подстановки.

Ранее изучалось, как различные аспекты алгоритма суперкомпилятора влияют на качество оптимизации программ. Однако никто ранее не исследовал на практике, как различные детали суперкомпилятора влияют на способность к трансформационному анализу. В данной работе сравниваются восем вариантов суперкомпилятора языка HLL. Для сравнения используется модельная задача: доказательство эквивалентности выражений. Результаты эксперимента, показывают, что наилучшим сочетанием параметров для рассматриваемого суперкомпилятора является использование уточненного гомео-морфного вложения, разделение узлов на локальные и глобальные и разделение выражений на классы в соответствии с типом редекса.

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

Заключение

В работе описана внутренняя структура экспериментального суперкомпилятора HOSC, работающего с ядром языка Haskell.

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

Список литературы

[1] Абрамов С. М. Метавычисления и их применение : Наука. Физматлит, 1995. Î2.2

[2] Абрамов С. М., Пармёнова Л. В. Метавычисления и их применение. Суперкомпиляция : Университет города Переславля, 2006. |5

[3] Barendregt H. P. The lambda calculus: its syntax and semantics : North-Holland, 1984. 11.2, 1.2

[4] Bolingbroke M., Peyton Jones S. Supercompilation by Evaluation // Haskell 2010 Symposium, 2010. |2.1, 2.2, 5

[5] Damas L., Milner R. Principal type-schemes for functional programs // Proceedings of the 9th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, 1982, p. 207-212. |1.1

[6] The GHC Team Haskell 2010 Language Report, 2010, http://haskell.org/ definition/haskell2010.pdf. |1

Johnsson T. Lambda lifting: Transforming programs to recursive equations // Functional Programming Languages and Computer Architecture. LNCS : Springer, 1985. Vol. 201, p. 190-203. |2.1

Jonsson P. A., Nordlander J. Positive Supercompilation for a higher order call-by-value language // IFL 2007, 2007, p. 441-456. T[], 3.1, 5

Klyuchnikov I. Supercompiler HOSC 1.0: under the hood : Preprint. Moscow : Keldysh Institute of Applied Mathematics, 2009, no. 63. T[]

Klyuchnikov I. Supercompiler HOSC: proof of correctness : Preprint. Moscow : Keldysh Institute of Applied Mathematics, 2010, no. 31. T1.4, 4.2 Klyuchnikov I. Supercompiler HOSC 1.1: proof of termination : Preprint. Moscow : Keldysh Institute of Applied Mathematics, 2010, no. 21. T[], 4.2 Klyuchnikov I., Romanenko S. Proving the Equivalence of Higher-Order Terms by Means of Supercompilation // Perspectives of Systems Informatics. LNCS : Springer, 2010. Vol. 5947, p. 193-205. T2.4, 4.3, 5

Klyuchnikov I., Romanenko S. SPSC: a Simple Supercompiler in Scala // PU’09 (International Workshop on Program Understanding), 2009. T5 Klyuchnikov I., Romanenko S. Towards Higher-Level Supercompilation // Second International Workshop on Metacomputation in Russia, 2010. T2.4, 4.2,

5

Nemytykh A. P. The supercompiler SCP4: general structure // PSI 2003. LNCS : Springer, 2003. Vol. 2890, p. 162-170. T5

Mitchell N., Runciman C. A Supercompiler for Core Haskell // Implementation and Application of Functional Languages. LNCS : Springer, 2008. Vol. 5083, p. 147-164. T[], 2.1, 3.1, 5

Mitchell N. Rethinking Supercompilation // ICFP 2010, 2010. T2.1, 2.2, 5 Peyton Jones S. The Implementation of Functional Programming Languages : Prentice-Hall, Inc., 1987. T 1.1

Sands D. Total correctness by local improvement in the transformation of functional programs // ACM Trans. Program. Lang. Syst., 1996. 18, no. 2, p. 175234. T 1.4

S0rensen M.H. Turchin’s Supercompiler Revisited: an Operational Theory of Positive Information Propagation : Master’s thesis, 1994. T4.1, 5 S0rensen M. H., Glück R. An algorithm of generalization in positive supercompilation // Logic Programming: The 1995 International Symposium ed. Lloyd J.W., 1995, p. 465-479. T3.1, 5

S0rensen M. H., Gluück R., Jones N. D. A Positive Supercompiler // Journal of Functional Programming, 1996. 6, no. 6, p. 811-838. T4.1, 5

S0rensen M.H., Glück R. Introduction to Supercompilation // Partial Evaluation. Practice and Theory. LNCS : Springer, 1998. Vol. 1706, p. 246-270. T3.1, 4.1, 2

Turchin V. F. The concept of a supercompiler // ACM Transactions on Programming Languages and Systems (TOPLAS), 1986. 8, no. 3, p. 292-325. T[], 5

Turchin V. F. The algorithm of generalization in the supercompiler // Partial Evaluation and Mixed Computation. Proceedings of the IFIP TC2 Workshop, 1988. T3.2

I. Klyuchnikov. Higher-Order Supercompilation.

Abstract. The paper describes the internal structure of HOSC, an experimental supercompiler dealing with programs written in a higher-order functional language (a subset of Haskell). A detailed and formal account is given of the concepts and algorithms the supercompiler is based upon. Particular attention is paid to the problems related to generalization and homeomorphic embedding of expressions with bound variables.

Key Words and Phrases: supercompilation, program analysis, functional programming.

Поступила в редакцию 03.10.2010. Образец ссылки на статью:

И. Г. Ключников. Суперкомпиляция функций высших порядков // Программные системы: теория и приложения : электрон. научн. журн. 2010. № 3(3), с. 37-71. URL: http://psta.psiras.ru/read/psta2010_3_37-71.pdf (дата обращения: 09.10.2010)

i Надоели баннеры? Вы всегда можете отключить рекламу.