Практика программирования система команд меркурий

Электронные заметки по теоретической информатике 30 № 4 (2000) URL: http://www.elsevier.nl/locate/entcs/volume30.html 20 страниц Технология реализации отладчика Mercury Золтан Сомоги и Фергюс Хендерсон Факультет компьютерных наук и программной инженерии Мельбурнского университета, Парквилл, 3052 Виктория. Австралия Абстрактный Каждый язык программирования нуждается в отладчике. Теперь у Mercury есть три отладчика: простой процедурный отладчик. Похожий на системы трассировки реализаций Prolog. Прототип декларативного отладчика и отладчик. Основанный на идее автоматического анализа трассировки.

В этой статье мы представим общую инфраструктуру. Лежащую в основе трех отладчиков. И опишем реализацию процедурного отладчика. Мы приводим причины каждого из наших основных проектных решений и показываем. Как некоторые из этих решений коренятся в нашем опыте отладки больших программ. Работающих с большими структурами данных.

1 Введение

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

Сильные системы типов. Модусов и детерминизма языка дополнительно гарантируют. Что некоторые другие классы ошибок. Напр. передача несвязанной переменной в предикат. Ожидающий основной термин. Или забывание обрабатывать один из функциональных символов. К которому может быть привязан входной аргумент. Будет поймана и точно определена компилятором. В результате большинство ошибок в программе Mercury обнаруживаются и исправляются до создания первого исполняемого файла.

Конечно, компилятор не может поймать все ошибки; некоторые ошибки все равно должны быть отслежены программистом.

В Mercury наличие поддержки этой задачи в виде отладчика более важно. Чем это было бы в другом языке: если в C или Prolog можно вставлять диагностические printfs или записи в программу в произвольных точках. То в Mercury это возможно только в предикатах. Которым передается параметр io__state. Который в большинстве программ составляет лишь небольшое меньшинство всех предикатов. (Можно злоупотреблять интерфейсом Mercury C. Чтобы обойти это ограничение. Ноого говоря. Реализация Mercury не гарантирует. Что такие трюки будут работать в

©2000 Опубликовано Elsevier Science B. V.

будущее, особенно при высоких уровнях оптимизации.)

В начале проекта Mercury мы. Как разработчики компилятора Mercury. Написанного на самом Mercury. Компенсировали отсутствие отладчика Mercury тем. Что написали весь наш код на пересечении Mercury и Prolog (первоначально NU-Prolog. Позже SICStus Prolog) и использовали систему Prolog для выполнения и отладки наших программ. Однако по мере того. Как Меркурий созревал. Это становилось все более и более обременительным ограничением. Потому что это мешало нам использовать новые функции. Которые мы добавляли к Меркурию. Такие как функции. Классы типов и экзистенциальные типы.

Поэтому мы уделили большое внимание созданию собственного отладчика Mercury и начали работать над ним. Как только получили ресурсы.

Изначально мы стремились к простому процедурному отладчику, похожему на системы трассировки реализаций Prolog, Мирей Дюассе и Эрван Джахье из IRIS A/INS A в Eennes начали работать над Morphine1, адаптацией системы анализа следов опиума для Ртути. Примерно в то же время. Когда мы начали работу над нашим процедурным отладчиком.

Этот факт сильно повлиял на наш подход к проектированию. Потому что мы хотели убедиться. Что если программа Mercury была скомпилирована с включенной отладкой. То полученный исполняемый файл может быть отлажен либо с помощью нашего процедурного отладчика. Либо с помощью Morphine. Поэтому два отладчика построены на основе одних и тех же идей и одной и той же базовой инфраструктуры. Позже мы начали работу над прототипом декларативного отладчика. Который реализован как расширение нашего процедурного отладчика. Поэтому вместо одного отладчика для Mercury мы получили три. Которые. Несмотря на несопоставимые функциональные возможности и пользовательские интерфейсы. Совместно используют значительные объемы кода друг с другом.

Эта статья описывает процедурный отладчик Mercury и его инфраструктуру; она не охватывает Morphine или декларативный отладчик. Для получения информации о морфине см. [11]; для получения информации о нашем прототипе декларативного отладчика Mercury см. [3],

Остальная часть этой статьи организована следующим образом. Раздел 2 знакомит с идеями событий и трассировок событий. А также описывает. Как компилятор Mercury организует события; это базовая инфраструктура. На которой построены все различные варианты отладчиков Mercury. В разделе 3 описываются основные функции процедурного отладчика Mercury. Поддерживаемые им нетрадиционные стратегии отладки и их реализация. В разделе 4 представлены измерения влияния включения отладки как на размеры исполняемых файлов. Так и на скорость их выполнения. Сравнения со смежными работами распределены по этим трем основным разделам. В разделе 5 представлены наши выводы.

Мы предполагаем знакомство с основными понятиями Mercury. Включая типы. Режимы и детерминизмы; они введены в учебник Mercury [1] и подробно описаны в справочном руководстве Mercury language [9]. Знание основных идей алгоритма выполнения Mercury [12] также было бы преимуществом. Но оно не является существенным. За исключением одного факта. Который заключается в том. Что Mercury Language reference manual также является преимуществом.

Морфин первоначально был известен как Опиум-М; название было изменено. Чтобы избежать возможных юридических осложнений.

генерирует отдельный код для каждого режима предиката. Мы называем каждый режим предиката процедурой; сущности. Отлаживаемые программистами. Являются процедурами. А не предикатами,

2 Трассировки событий

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

• компилятор аннотирует сгенерированный объектный код различными способами. Например записывает. Какие инструкции реализуют. Какие строки кода и в каком регистре или слоте стека хранится данная переменная;

• отладчик использует эти аннотации. Чтобы понять состояние машины и позволить ей изменять код запущенной программы, например. Вставлять точки останова.

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

Переносимость всегда была одной из основных целей реализации Mercury; именно по этой причине компилятор Mercury переводит Mercury в C. А не в объектный код. Принятие традиционного подхода к реализации отладчика Mercury вынудило бы нас отказаться от этой цели и выделить скудные ресурсы программиста либо на написание одного или нескольких генераторов объектного кода. Либо на расширение существующего портативного компилятора. Такого как gcc. Для записи отладочной информации об исходной программе Mercury, и то. И другое было бы довольно большой работой для нашей небольшой исследовательской группы. И оба они далеко вышли бы за пределы нашей области внимания (логическое программирование). И мы вряд ли получили бы деньги ни на то. Ни на другое из наших обычных источников финансирования.

Одной из рассматриваемых нами альтернатив было написание интерпретатора для формы bvteeode высокоуровневого промежуточного представления. Создаваемого интерфейсом компилятора. К сожалению. На самом деле невозможно реализовать взаимодействие между скомпилированным кодом и интерпретируемым кодом таким образом. Чтобы оно было переносимо как в операционных системах. Так и в архитектурах. Поэтому нам пришлось отказаться и от этого подхода.

Подход. Который мы выбрали. Основан на идее рассматривать выполнение программы как последовательность или след событий. При каждом событии компилятор вставляет дополнительный код. Вызывающий отладчик; затем отладчик может временно приостановить выполнение программы. Чтобы позволить программисту проверить состояние программы и выдать команды отладчику. Например, это делает основанный на трассировке отладчик для языка Си с именем cdb [8]. В качестве альтернативы трассировка может быть проанализирована. По крайней мере частично. Автоматически. Как в основанном на трассировке отладчике пролога Opium [6].

Конструктор системы отладки на основе трассировки может выбрать. Должен ли весь код отладчика быть связан с отлаживаемым исполняемым файлом или основная часть отладчика должна находиться во втором процессе. Взаимодействуя с небольшим ядром внутри отладчика через межпроцессный com-

каналы связи. Такие как трубы или розетки. Преимущества первого подхода. Которым пользуются процедурные и декларативные отладчики Mercury. Заключаются в простоте и более высокой производительности. Преимуществами второго подхода. Который использует Морфин [11]. Являются гибкость и более высокая устойчивость перед лицом ошибок дикого указателя,

2.1 Типы событий

Выбор типов событий (также называемых портами) является важным решением в отладчиках на основе трассировки. Поскольку такие отладчики могут передавать управление пользователю только во время событий. События делятся на два класса: те. Которые происходят. Когда управление переходит от одного вызова процедуры к другому, и те. Которые происходят внутри одного вызова. Мы называем первый класс событиями интерфейса. А второй-внутренними событиями.

Отладчик Mercury поддерживает пять типов интерфейсных событий. Четыре из которых соответствуют четырем портам в коробочной модели Берда [4]:

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

exit Событие exit происходит. Когда вызов процедуры завершился успешно. И элемент управления собирается вернуться к вызывающему объекту,

событие повтора происходит. Когда все вычисления справа от вызова процедуры завершились неудачей. И управление собирается вернуться к этому вызову. Чтобы попытаться найти альтернативные решения,

fail Событие fail происходит. Когда у вызова процедуры закончились альтернативы. И управление собирается вернуться к самому правому вычислению слева. У которого все еще есть некоторые, возможно. Успешные альтернативы,

excp Событие исключения возникает. Когда элемент управления покидает вызов процедуры из-за того. Что исключение было вызвано и не поймано где-то внутри вызова или его потомков.

Интерфейсные порты явно важны для понимания поведения программы. Поскольку они описывают взаимодействие процедуры с ее вызывающими объектами. Однако их часто бывает недостаточно. Если программист проверит значения аргументов процедуры p в выходном порту p и обнаружит. Что они неверны. Он или она захочет узнать. Какое предложение p вычисляло этот результат. В то время как программист может часто выводить идентичность этого предложения из событий. Генерируемых процедурами. Вызываемыми p. Есть случаи. В которых это довольно сложно (например. P может иметь несколько предложений. Которые все вызывают одни и те же процедуры с аргументами. Представляющими собой большие структуры данных. Отличающиеся только мелкими деталями). Вот почему система Opium также использовала события unify. Которые происходили сразу после успешного объединения фактических аргументов вызова с заголовком предложения вызываемого предиката,

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

возможные унификации против входных аргументов были успешными. Но это определение было бы неудобно для программистов. Поскольку некоторые такие унификации могут быть перемещены после вызовов в теле предложения. Во-вторых, в то время как преобладание индексации первого аргумента в прологе сильно побуждает программистов писать предикаты в виде последовательностей предложений. Причем большинство предложений являются союзами литералов и только несколько if-then-elses и явных дизъюнкций добавляются. Это не верно для Mercury. Которая с самого начала допускала свободную композицию целей логическими связками.

Вместо создания порта unify. Мы создаем один тип событий для каждого возможного вида решения о потоке управления. Эти внутренние типы событий являются следующими,

затем событие then происходит. Когда выполнение достигает начала части then if-then-else,

иначе Событие else происходит. Когда выполнение достигает начала другой части if-then-else,

disj Событие disj происходит. Когда выполнение достигает начала дизъюнкта в дизъюнкции,

swch Событие switch происходит. Когда выполнение достигает начала одной ветви коммутатора (коммутатор-это дизъюнкция. В которой каждый дизъюнкт объединяет связанную переменную с различным символом функции).

Каждое внутреннее событие связано с целевым путем. Который дает идентификацию подцели. Которую выполнение собирается ввести. Когда происходит событие. Если мы рассматриваем тело процедуры как термин. Состоящий из примитивных целей (объединений и вызовов) в сочетании с различными связями (if-then-else, switch. Дизъюнкция. Конъюнкция и т. Д.), то путь цели-это последовательность компонентов. Причем каждый компонент дает один шаг от корня термина к подтерму. Представляющему цель (которая может быть примитивной или составной); путь цели однозначно идентифицирует эту цель. Например, путь цели dlВнутреннее событие. С которым связан этот путь цели. Должно быть событием disj. Так как его появление означает. Что выполнение собирается ввести этот дизъюнкт,

2.2 Реализация мероприятия

Определившись с типами событий. Следующий вопрос. На который должен ответить разработчик. Заключается в том. Как организовать управление отладчиком при каждом событии. Самый простой подход заключается в использовании преобразования источник-источник. Как показано Dueasse и Nove [7]:

Глава : —

( trace(call. Head) ; trace(fail. Head), fail ),

( след(выход. Голова)

; trace(redo. Head), fail

К сожалению. Это преобразование нарушает один из инвариантов. От которых зависит алгоритм выполнения Mercury. А именно то. Что процедура. Объявленная det (то есть имеющей ровно одно решение) или semidet (то есть имеющей либо ноль. Либо одно решение). Не оставит после себя никаких недетерминированных кадров стека (эквивалент Mercury точек выбора Пролога) при выходе. Попытка обойти это. Просто сказав. Что в режиме отладки все процедуры считаются недетерминированными или множественными (и. Следовательно. Им разрешено иметь более одного решения). Не сработает. В Mercury ввод-вывод разрешен только в контексте det. Так что это изменение фактически запретит ввод-вывод,

Более простой подход. Который мы принимаем. Состоит в том. Чтобы варьировать преобразование в зависимости от детерминизма процедуры. Мы преобразуем процедуры det в

Глава : —

след(звонок. Голова),

след (выход. Голова). и полудеткие к голове : —

след(звонок. Голова),

след(выход. Голова)

trace(fail. Head), fail

Для процедур nondet и multi (процедур. Которые могут быть успешными более одного раза) мы используем вариант преобразования Dueasse и Move. Который имеет ту же операционную семантику. Но приводит к лучшему коду в Mercury. Поскольку алгоритм выполнения Mercury допускает более эффективный код для вложенных дизъюнкций. Чем для соединенных дизъюнкций:

Глава :-

след(звонок. Голова), Тело,

( trace(exit. Head) ; trace(redo. Head), fail )

trace(fail. Head), fail

Наша общая схема сохраняет требуемые инварианты и имеет то преимущество. Что программистам не нужно видеть события. Которые не соответствуют детерминизму процедуры (например, учитывая. Что компилятор доказывает. Что процедуры det и semidet имеют не более одного решения. Нет смысла просить их создать другое решение). Этот подход делает задачу автоматического анализа трассировки. Как в Opium [6]. Немного более сложной. Так как анализатор трассировки теперь должен понимать три различных поведения для отслеживаемых предикатов. Однако это небольшой недостаток. И мы готовы с ним жить. Таким образом. Отладчик Mercury основан на этой идее.

Однако компилятор Mercury не реализует вставку вызовов в систему трассировки как преобразование источника Mereury-to-Mereurv. На это есть две причины. Во-первых, не существует простого расширения этой схемы. Позволяющего системе трассировки получить доступ к значениям живых переменных. Которые не являются аргументами при внутренних событиях и при событиях выхода. Во-вторых, вызовы предиката трассировки должны были бы выделить ячейку памяти для хранения термина. Передаваемого в качестве второго аргумента. И больше памяти для хранения описания типа термина. И еще одну ячейку. Чтобы упаковать их вместе как значение типа univ (Различные вызовы предиката трассировки будут иметь разные аргументы типа. Которые в Mercury имеют разные представления; без описания типа предикат трассировки ничего не мог бы сделать с термином.) Это более высокий уровень накладных расходов. Чем мы готовы принять.

Таким образом. Компилятор Mercury обрабатывает трассировку во время генерации кода. Включая вызовы системы трассировки в код C в generates. Вставленный

фрагменты кода выглядят примерно так: {

Код *MR_jumpaddr;

MR_jumpaddr = MR_trace(

&mercury_data__layout__mercury__main_2_0_il4);

if (MR_jumpaddr != NULL) GOTO(MR_jumpaddr);

MR_trace-это функция в системе Mercury runtime system. Реализующая отладчик. Его возвращаемое значение почти всегда будет равно NULL. Что означает. Что большую часть времени выполнение будет продолжаться нормально (Единственное исключение-когда отладчик только что выполнил команду retry. Которую мы обсудим в следующем разделе.)

Аргумент, переданный MR_trace. Является указателем на статическую структуру данных. Созданную компилятором Mercury. Которую мы называем структурой компоновки меток. Поскольку ее основное назначение-описание имен. Местоположений и типов всех переменных. Действующих в данной точке программы. Описание типов довольно сложно; конструкторы типов могут гнездиться на произвольную глубину. А переменные могут иметь типы. Которые не полностью известны статически (то есть их типы могут включать переменную типа). Подробнее о том. Как реализация Mercury представляет типы и как она гарантирует. Что системные компоненты. Такие как отладчик. Всегда могут узнать конкретный тип любого значения. Смотрите в нашей статье об информации о типе времени выполнения в Mercury [5]. Описание местоположений намного проще; переменная находится либо в n-м регистре абстрактной машины общего назначения. Либо в другом регистре.

смещение m в кадре стека процедуры на любом из двух стеков Mercury. Стеке det или стеке nondet. В каком стеке находится кадр стека процедуры. Зависит от детерминизма процедуры. Таким образом. Структура макета метки также содержит указатель на другую статическую структуру данных. Сгенерированную компилятором. Макет процедуры процедуры. В которой находится метка. Помимо информации. Идентифицирующей процедуру (ее имя. Определяющий модуль. Является ли она предикатом или функцией. А также ее aritv и номер режима). Она также содержит детерминизм процедуры. Размер ее кадра стека и местоположение в кадре стека сохраненного адреса возврата. На самом деле. Для процедур. Которые живут в стеке nondet. Последние два элемента информации не являются необходимыми. Так как они могут быть выведены из фиксированных полей. Которые присутствуют с одинаковым значением во всех кадрах стека nondet. Но кадры в стеке det не имеют таких фиксированных полей.

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

Некоторые виды информации. Необходимой MR_trace о событии. Не могут быть помещены в созданные компилятором структуры данных. Поскольку они динамичны. А не статичны. Порядковый номер вызова-это один экземпляр. Каждый вызов процедуры имеет порядковый номер вызова. Который однозначно идентифицирует этот вызов. И для каждого события MR_trace должен знать порядковый номер вызова. Частью которого он является (например. При выполнении команды ‘retry’ отладчик должен знать. Какой вызов повторяется.) Два других экземпляра-это номер события последнего события перед событием вызова. Создавшим текущий вызов (чтобы отладчик знал. На что сбросить номер события при выполнении команды Мы могли бы передать эти три элемента в MR_trace в качестве дополнительных аргументов. Но это значительно увеличило бы размер вызовов MR_trace. Вместо этого они хранятся в стековых фреймах трассируемых процедур на фиксированных смещениях. И MR_trace обращается к ним там. Эти слоты стека заполняются при вводе трассируемой процедуры с помощью трех глобальных переменных в системе выполнения. Значение глобальной переменной event number просто копируется в слот. Содержащий номер события до события вызова. В то время как глобальные переменные. Содержащие порядковый номер вызова и глубину. Увеличиваются до того. Как они будут назначены соответствующим слотам. Чтобы правильно заполнить слот глубины. Прослеживаемые процедуры сбрасывают глобальный счетчик глубины на значение. Хранящееся в их собственном слоте глубины непосредственно перед вызовом,

2.3 Что следует отслеживать?

Реализация Mercury дает программисту значительную степень контроля над набором событий. Которые будет генерировать скомпилированная программа Mercury. Часть этого контроля осуществляется во время компиляции; два варианта говорят компилятору не выдавать код для внутренних событий и для событий повтора соответственно. Во многих случаях можно обойтись и без них. А программист может

не хотят платить пространственные или временные издержки. Которые они несут. Часть этого контроля осуществляется во время выполнения. Если программист создает исполняемый файл. Содержащий отладочную информацию из (скажем) программы на языке Си. Он ожидает. Что сможет запустить эту программу не только под контролем отладчика. Но и самостоятельно. Без привлечения отладчика. Мы разрешаем этот выбор с помощью логической переменной MR_trace_enabled во время выполнения. Первое, что делает MR_trace. — это консультируется с MR_trace_enabled; если он ложен. MR_trace немедленно возвращается. Этот тест централизован в MR_trace вместо того. Чтобы быть обернутым вокруг каждого вызова MR_trace. Потому что есть много вызовов MR_trace. И стоимость в размере программы будет очень большой (см. раздел 4). Так как тест в MR_trace практически всегда будет находиться в кэше. В то время как при альтернативном подходе многих тестов. Рассеянных по программе, не будет. Наш подход также может быть превосходящим чисто по времени выполнения. Даже если основная память достаточно велика. Чтобы содержать даже большую версию программы.

По умолчанию MR_trace_enabled фактически инициализируется в false. Но процедура инициализации. Включенная в каждую программу Mercury. Устанавливает ее в true. Если переменная окружения MERCURY-OPTIONS включает правый флаг. Отладчик Mercury, mdb. На самом деле является не чем иным. Как сценарием оболочки. Который устанавливает этот флаг в MERCURY_OPTIONS перед выполнением данной команды. Вся фактическая работа отладчика выполняется функциями в системе Mercury runtime. Вызываемыми из MR_trace. Которые связаны с исполняемым файлом. Если код C. Сгенерированный компилятором Mercury. Содержит вызовы MR_trace,

Большинство программ содержат значительное количество процедур. Которые. По мнению программиста. Вряд ли содержат ошибку. Например, большинство программистов доверяют предикатам манипуляции со списком в модуле списка стандартной библиотеки Mercury как потому. Что они относительно просты. Так и потому. Что они использовались много раз раньше. А это означает. Что любые недостатки. Скорее всего. Были найдены и исправлены давным-давно. События внутри деревьев вызовов доверенных процедур по определению не интересны программисту. Однако интерфейсные события таких вызовов все же могут быть интересны. Так как они предоставляют информацию о работе вызывающего.

В [7] Дюкассе и Нойе предлагают. Чтобы код таких доверенных процедур не генерировал события трассировки; вместо этого вызовы этих процедур должны быть обернуты в код для генерации событий интерфейса вызова. Мы отвергли этот подход по двум причинам. Во-первых, доверенные процедуры. Как правило. Являются служебными процедурами. Что означает. Что они вызываются из многих мест; накладные расходы на пространство событий интерфейса один раз на сайт вызова. А не только один раз для определения процедуры. Вряд ли будут хорошим компромиссом. Во-вторых, если процедура меняет статус с доверенного на недоверенный или наоборот. Эта схема потребует перекомпиляции всех модулей. Вызывающих эту процедуру. И это требование мешает отдельной компиляции.

Принятый нами подход позволяет программистам определять свой уровень доверия к коду в модуле. Выбирая уровень трассировки для модуля (Мы также могли бы позволить программистам определять различные уровни трассировки для различных процедур в модуле. Но как программисты мы никогда по-настоящему не чувствовали необходимости в таком уровне гибкости. И обеспечение его потребовало бы значительно большей гибкости).

более сложный пользовательский интерфейс. Чем простой вариант компилятора. Который мы сейчас используем.)

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

Программисты должны скомпилировать модуль с уровнем трассировки none только в том случае. Если они достаточно уверены. Что модуль надежен. И если они считают. Что знание того. Что вызовы других модулей делают с этим модулем. Не принесет им существенной пользы при отладке,

deep Процедура. Скомпилированная с уровнем трассировки deep. Всегда будет генерировать все события. Запрошенные программистом. Если программист явно не отключит внутренние события или не повторит события. Это будут все возможные типы событий.

Программисты должны скомпилировать модуль с глубоким уровнем трассировки. Если они подозревают. Что в модуле может быть ошибка. Или если они думают. Что возможность исследовать то. Что происходит внутри этого модуля. Может помочь им найти ошибку,

shallow Процедура. Скомпилированная с уровнем трассировки shallow. Будет генерировать события интерфейса. Если она вызывается из процедуры. Скомпилированной с уровнем трассировки deep. Но она никогда не будет генерировать никаких внутренних событий. И она также не будет генерировать никаких событий интерфейса. Если она вызывается из процедуры. Скомпилированной с уровнем трассировки shallow. Если он вызывается из процедуры. Скомпилированной с уровнем трассировки none. То его поведение определяется тем. Имеет ли его ближайший предок. Уровень трассировки которого не является none. Уровень трассировки deep или shallow.

Программисты должны скомпилировать модуль с низким уровнем трассировки. Если они считают. Что код модуля надежен и вряд ли имеет ошибки. Но они по-прежнему хотят иметь возможность управлять вызовами и возвращениями из любых предикатов и функций. Определенных в модуле. Для проверки аргументов этих вызовов.

Процедуры. Скомпилированные с уровнем трассировки Процедуры. Скомпилированные с уровнем трассировки ‘shallow’. Перед своими вызовами устанавливают эту же логическую переменную в значение false. Они также сохраняют значение этой переменной при входе в слот стека. Зарезервированный для этой цели. И обращаются к нему позже. Концептуально такие процедуры должны вызывать MR_trace только в том случае. Если сохраненное значение mr_file_deep равно true. На практике по причинам размера программы и эффективности кэша. Которые идентичны причинам. По которым мы помещаем тест для MR_trace_enabled в MR_trace. Мы также помещаем тест сохраненного значения MR_f rom_deep в MR_trace. Мы делаем это. Включая поле в каждый макет процедуры. Которое говорит. Является ли процедура неглубокой трассировкой, и если да. То какой слот стека содержит сохраненное значение флага. И заставляя MR_trace обращаться к этому полю и действовать на его значение сразу после того. Как он обнаружил. Что MR_trace_enabled является истинным. Код для заполнения остальных трех слотов стека отладки. Который должен увеличивать глобальный порядковый номер вызова только в том случае. Если MR_f rom_deep имеет значение true. Не может быть централизован таким образом

без значительных дополнительных затрат (тестирование каждого события в MR_trace. Чтобы увидеть. Является ли оно событием вызова. И заполнение слотов. Если это так). Поэтому для этого действия тест MR_from_deep выполняется в каждой процедуре. Модули, скомпилированные с уровнем трассировки

3 Процедурная отладка

Процедурный отладчик. Который мы построили поверх системы трассировки. Имеет несколько интересных функций. Но в основном является обычным. Наша отправная точка заключалась в том. Что нам нужен был отладчик. Сочетающий в себе лучшие возможности отладчиков для императивных языков (например. Gdb) и отладчиков. Встроенных в системы пролога. Мы также хотели попытаться сделать так. Чтобы программисты привыкли к другим отладчикам и чувствовали себя как дома в максимально возможной степени. Поэтому мы сделали отладчик очень настраиваемым.

Двумя ключевыми компонентами системы. Реализующими настраиваемость. Являются псевдонимы и настраиваемая справочная система. Псевдонимы позволяют программисту сопоставить, например. Команду Затем справочная система позволяет программисту документировать команду

3.1 Изучение состояния программы

Один класс команд отладчика позволяет программисту исследовать состояние программы на событии. Одна команда перечисляет имена переменных. Которые живут в событии; этот список берется прямо из информации RTTI. Содержащейся в структуре макета метки. На которую указывает аргумент MR_trace. Другая команда выводит значения всех этих переменных или просто значение переменной с заданным именем. Эта команда использует структуры данных RTTI для интерпретации состояния машины. Включая значения абстрактных регистров машины Mercury.

Печать терминов Mercury и просмотр внутри них реализованы в Mercury. На самом деле печать и просмотр выполняются одним и тем же кодом. Вызываемым двумя различными способами. Этот код учитывает такие параметры. Как максимальная глубина. На которую должен быть напечатан термин. А также максимальный размер (ширина умножается на высоту). Который может занимать его печатное представление. Опуская субтермы по мере необходимости. Чтобы оставаться в этих пределах (которые, конечно. Настраиваются). Нет команды. Которую пользователь может дать в отладчике для печати значения переменной. Которая не соблюдает эти же пределы. Мы применяем это условие из-за нашего опыта работы с системами Пролога. Которые этого не делают. Одна из программ. В которой мы используем отладчик. — это сам компилятор Mercury. Некоторые из структур данных которого довольно велики: несколько мегабайт данных. Размер печатного представления которых может превышать сотню мегабайт. Случайно выданная команда. Которая печатает такую структуру данных в полном объеме. Может занять более часа при обычных скоростях отображения терминала. Что означает. Что программист имеет

нет лучшего способа действия. Чем прервать программу и начать ее заново с нуля.

Переменные, которые программист может проверить в событии. Включают не только переменные вызова процедуры. К которой относится событие (которые могут находиться в слотах стека или в (сохраненных копиях) регистрах). Но и переменные предков этого вызова (которые могут находиться только в слотах стека). Такая печать на верхнем уровне возможна потому. Что всякий раз. Когда процедура компилируется на уровне трассировки. Отличном от нулевого. Компилятор генерирует не только структуру макета процедуры для процедуры и структуру макета метки для каждой точки выполнения. Связанной с событием. Но также структуру макета метки для каждой точки возврата в процедуре. Которая записывает имена. Типы и местоположения переменных. Сохраненных во время этого вызова (Генерация макетов меток для точек возврата может быть подавлена параметром компилятора).; это уменьшает размер программы за счет отключения печати на верхнем уровне.)

Все макеты меток. Включая те. Которые связаны с событиями трассировки и с точками возврата. Включают указатель на макет процедуры процедуры. Содержащей их. Из макета процедуры отладчик может узнать. В каком стеке находится кадр стека процедуры. Каков размер кадра и где хранится обратный адрес. Чтобы иметь возможность интерпретировать содержимое фрейма стека вызывающего объекта. Отладчик должен иметь возможность найти структуру макета метки метки по сохраненному обратному адресу. Вот почему каждый модуль имеет функцию инициализации. Которая включает в себя код для регистрации в таблице меток среды выполнения соответствия между адресом структуры макета каждой метки. Имеющей таковую. И кодовым адресом метки. Таким образом. Печать на верхнем уровне начинается с того. Что эти функции инициализации были вызваны; они должны быть вызваны только один раз в жизни программы. Затем последовательно находя адрес возврата в своем слоте в текущем кадре стека. Просматривая его структуру макета метки в таблице меток. Следуя ссылке на соответствующую структуру макета процедуры. А затем концептуально выталкивая кадр стека вызываемого объекта из его стека. Отладчик может достичь кадра стека любого заданного предка. Тот факт, что процедура. Которая живет в стеке det. Может вызывать другую процедуру. Которая живет в стеке nondet. И наоборот. Немного усложняет код обхода. Но детерминизм в структурах компоновки процедур позволяет нам разрешить любую такую двусмысленность. Единственный случай. Когда обход стека должен быть преждевременно остановлен. — это когда он сталкивается с кадром стека процедуры. Которая была скомпилирована без отладки и поэтому не имеет ни того. Ни другого вида структуры компоновки.

Предположим. Программист построил большой исполняемый файл без отладки. Что означает. Что все его многочисленные модули были скомпилированы с уровнем трассировки none. Предположим. Что программист теперь обнаруживает. Что исполняемый файл не проходит тестовый случай. Они могут перекомпилировать только те модули. Которые они подозревают. С более высоким уровнем трассировки и отлаживать полученный исполняемый файл, принимая. Что некоторые трассировки стека не будут завершены. Кроме того, они могут перекомпилировать все модули с более высоким уровнем трассировки. Это может занять значительно больше времени и привести к значительно большему исполняемому файлу. Но это гарантирует. Что все трассировки стека будут завершены.

Конечно, программист не может осмысленно сказать, например: Это одна из причин. По которой мы предоставляем команду трассировки стека. Которая выводит идентификаторы всех предков. Учитывая. Что рекурсия может быть очень глубокой в Меркурии (иногда достигая более десяти тысяч уровней). Мы сочли необходимым сжать следы стека. Мы используем простое кодирование длины выполнения. В котором последовательности вложенных вызовов одной и той же процедуры представляются путем именования процедуры только один раз. Но с указанием количества повторений. Это работает только для саморекурсии. А не для взаимной рекурсии. Но мы обнаружили. Что многие случаи взаимной рекурсии нерегулярны (например. P вызывает q вызывает r вызывает p вызывает r). Что затрудняет поиск для них представления. Которое было бы легко понятным и однозначным. А также коротким.

3.2 Контроль форвардного исполнения

Команды отладчика. Которые проверяют состояние программы. Выполняют свою работу и печатают запрос для другой команды отладчика. Не возвращаясь из MR_trace. Другие команды отладчика заставляют MR_trace возвращаться и перенаправлять выполнение для возобновления. Они делают это после указания условия. Которому должен удовлетворять следующий вызов MR_trace. Прежде чем MR_trace напечатает приглашение и снова разрешит взаимодействие с пользователем. Для команд step и goto это условие-достижение события с заданным номером события; команда step указывает этот номер события как смещение от текущего события. В то время как команда goto указывает его непосредственно. Команда goto особенно полезна при поиске причин прерываний выполнения (вызовы предиката Mercury error, который соответствует сбою утверждения в таких языках, как C), Когда исполняемый файл, скомпилированный с отладкой, прерывается, он выводит номер последнего выполненного события, если это число (скажем) 987,654,321, программист может повторно выполнить программу и goto, например событие номер 987,654,000, и начать проверку программы в точке несколько раньше прерывания. При отладке программ. Которые не прерываются. Номера событий также позволяют программистам выполнять реальный двоичный поиск; они могут перейти к точной средней точке подозрительной области (которая изначально является всей трассировкой), проверить. Все ли в порядке. И заменить подозрительную область ее левой или правой половиной в зависимости от ответа. Эти два метода не поддерживаются никаким другим известным нам отладчиком для любого языка.

Команда finish останавливается при следующем событии exit. Fail или exception на заданной глубине. По умолчанию глубина-это глубина текущего события. Поэтому finish идет к концу вызова. Связанного с текущим событием. Но можно также указать. Что, например. Вы хотите перейти к концу третьего предка текущего вызова. Это устраняет проблему. Присутствующую во многих отладчиках Пролога. Которая заключается в том, что. Хотя есть способ перешагнуть через вызов. Нет никакого способа перейти к концу вызова. Как только кто-то (возможно. Случайно) перешагнул через этот вызов.

3.3 Управление точками останова и выводом трассировки

Как и большинство отладчиков. Отладчик Mercury позволяет программистам ставить точки останова на заданную процедуру. Точка останова может применяться только к событию вызова. Ко всем событиям интерфейса или ко всем событиям в именованной процедуре по выбору программиста. Поэтому проверка соответствия события заданной точке останова должна включать в себя проверку типа события. А также проверку того. Совпадает ли процедура. Связанная с событием. С процедурой. Связанной с точкой останова. Для скорости эта последняя проверка выполняется путем сравнения адресов их макетов процедур; это работает. Потому что макеты процедур являются статическими структурами данных.

Для всех команд прямого выполнения возникает вопрос о том. Что должен делать отладчик при событиях. Которые не соответствуют условию текущей команды отладчика. Но которые соответствуют точке останова; должен ли он останавливать прямое выполнение или продолжать его до тех пор. Пока не будет выполнено условие команды отладчика? В отладчике Mercury ответ зависит от строгости текущей команды. Если текущая команда не является строгой. Выполнение будет остановлено и начнется взаимодействие с пользователем; если текущая команда является строгой. Выполнение будет продолжено. Каждая команда движения вперед (шаг и т. Д.) имеет строгость по умолчанию. Которая может быть переопределена параметром. Предоставленным программистом. И существует команда движения вперед continue. Которая не имеет другого условия остановки. Кроме встречи с точкой останова,

3-4 Реализация повторной попытки

Как и отладчики Prolog. Но в отличие от отладчиков для императивных языков. Отладчик Mercury имеет команду retry. Которая позволяет программисту вернуться к более ранней точке выполнения программы. К событию вызова текущего вызова процедуры или любого из его предков. Компоновка процедуры каждой процедуры. Скомпилированной с помощью отладки. Включает указатели на начало кода процедуры и структуру компоновки меток события вызова процедуры. Которая указывает. Какие переменные должны находиться там во время вызова. Отладчик ищет текущие местоположения этих переменных в кадре стека повторного вызова и/или в сохраненных регистрах и копирует их в регистры. Которые они занимали во время вызова (Меркурий передает все входные аргументы в регистры.) Обычно время жизни переменной заканчивается сразу после ее последнего использования. И значение переменной не сохраняется после этого момента. Однако для поддержки retry компилятор пытается продлить время жизни всех входных аргументов. Чтобы они охватывали все время вызова любой процедуры. Скомпилированной с отладкой. Компилятор преуспеет в этом. Если только тело процедуры деструктивно не обновит некоторые из входных аргументов. Команда retry завершится неудачно. Если она будет вызвана из расположения. В котором некоторые входные аргументы уже были обновлены. Поскольку состояния ввода-вывода деструктивно обновляются. Невозможно использовать retry на предикатах. Которые выполняют ввод-вывод. В настоящее время мы реализуем предлагаемое решение этой проблемы. Помимо сброса регистров. Содержащих входные аргументы. Команда retry также должна сбросить остальную часть состояния машины. Включая стек

указатели и регистр обратного адреса. В настоящее время Mercury использует консервативный сборщик мусора Boehm [2] и связанный с ним распределитель. Поэтому нет указателя кучи. Который нужно сбросить. Кроме того, нет логических переменных для сброса в несвязанные; поскольку система режимов знает. Какие переменные несвязаны в любой заданной точке программы. Скомпилированный код не смотрит на несвязанные переменные. А это означает. Что их содержимое не имеет значения. Состояние машины также включает в себя часть состояния отладчика. То есть глобальные переменные. Содержащие следующий номер события. Следующий порядковый номер вызова и глубину вызова; значения для их сброса берутся из фиксированных слотов стека повторяемого вызова (Другие части состояния отладчика. Например список текущих точек останова. Являются постоянными.) Как только состояние машины было сброшено. Мы можем повторить вызов. Мы делаем это. Возвращая адрес точки входа повторенной процедуры из MR_trace; поскольку этот адрес не будет нулевым, код. Вызывающий MR_trace. Перейдет на этот адрес. Код внутри MR_trace не может выполнить сам прыжок. Потому что если бы он это сделал. MR_trace никогда бы не вернулся. И его кадр стека никогда не мог бы быть восстановлен.

Mercury 0,9 выполняет повторные попытки только с порта выхода или отказа выбранного вызова. Потому что только на таком порту правильно настроены стеки. Процесс обхода стека. Который мы описали ранее. Может найти кадр стека выбранного предка. Но из-за использования компилятором временных кадров стека в некоторых случаях обход не может выяснить. В какое состояние должен быть сброшен другой стек (тот. В котором не включен кадр выбранного вызова). Если программист выдает команду повтора с любого другого порта. Отладчик переходит к концу выбранного вызова и. Если вызов не прерывается исключением. Выполняет операцию повтора оттуда. Мы ожидаем, что более поздняя версия исправит это ограничение.

4 Оценка

Чтобы оценить пространственное и временное влияние отладки на программы Mercury, мы провели серию экспериментов на ПК с процессором Pentium III 450 МГц и 256 Мб оперативной памяти под управлением Linux 2,2,12, Тестовой программой, производительность которой мы измеряли, был компилятор Mercury из дистрибутива Mercury 0,9, скомпилированный сам по себе. Компилятор Mercury сам написан на Меркурии; это самая большая программа Mercury. К которой у нас есть доступ. И, вероятно. Самая большая из существующих. Каждый эксперимент состоял из

• компиляция компилятора Mercury с заданным набором опций. Некоторые из которых, но не все. Подготовили результирующий исполняемый файл для отладки.;

• измерение размера результирующего исполняемого файла;

• измерение времени. Затраченного этим исполняемым файлом на выполнение стандартной задачи вне mdb. То есть без отладки;

• измерение времени. Затраченного этим исполняемым файлом на выполнение стандартной задачи внутри mdb. То есть с отладкой. При этом единственной заданной командой отладчика является

Результаты приведены в таблице 1, Каждая строка дает результаты для одного эксперимента. Первая группа столбцов после номера строки описывает варианты компиляции для этого эксперимента. Первый дает уровень трассировки. Используемый для создания исполняемого файла. Второй говорит. Включает ли исполняемый файл код для генерации внутренних событий; код. Скомпилированный с неглубокой трассировкой. Никогда не генерирует внутренние события. Третий говорит о том. Включает ли исполняемый файл структуры данных. Описывающие сайты возврата вызовов. Четвертый дает уровень оптимизации. Используемый для создания исполняемого файла; либо уровень по умолчанию. Либо уровень оптимизации. Который вы получаете после отключения оптимизаций. Которые обычно отключает отладка (Некоторые низкоуровневые оптимизации не могут обрабатывать вызовы MR_trace. В то время как другие являются преобразованиями от источника к источнику. Которые изменяют последовательность событий. Генерируемых программой. Последний тип можно снова включить. Указав опцию компилятора. Поскольку это совсем не типично для того. Как люди отлаживают программы, наши эксперименты не указывали этот вариант.) Следующие три столбца дают размер раздела кода, размер раздела данных только для чтения и общий размер исполняемого файла, как сообщается командой Linux (Обратите внимание, что общий размер не включает таблицы символов и строк, а также другие исполняемые компоненты, которые обычно не загружаются во время выполнения.) Последние две пары столбцов дают время, измеренное в секундах и в процентах от базового времени в строке 2, чтобы использовать данную экспериментальную версию компилятора для компиляции самого большого модуля в самом компиляторе Mercury. Размер которого составляет 6451 строку. Первая пара измеряет время. Затраченное на это вне mdb. В то время как вторая пара измеряет время. Затраченное на это внутри mdb. Для исполняемого файла. Скомпилированного с уровнем трассировки none. Нет никакого различия между ними. Поскольку такие исполняемые файлы не вызывают MR_trace. Для исполняемого файла. Скомпилированного с глубокой или неглубокой трассировкой. Запуск исполняемого файла вне mdb означает. Что MR_trace_enabled всегда ложен. Поэтому все вызовы MR_trace возвращаются немедленно; запуск исполняемого файла внутри mdb означает. Что MR_trace_enabled всегда истинен. Поэтому MR_trace должен проверить макет proc процедуры. Чтобы увидеть. Является ли она неглубокой трассировкой. Если это так. И сохраненное значение MR_from_deep равно false. MR_trace может вернуться; в противном случае он должен интерпретировать текущую команду отладчика. В этом случае после первого вызова MR_trace эта команда отладчика будет

Все сообщенные времена являются средними измерениями, взятыми из четырех запусков; они точны примерно с точностью до секунды или около того (Два раза во втором ряду, 39,9 и 39,8 секунды. Измеряют одну и ту же программу. Выполняемую в эквивалентных условиях; разница обусловлена исключительно врожденной трудностью точного воспроизведения результатов синхронизации.) Все отчетные времена охватывают только время. Проведенное в пользовательском режиме. А не в режиме ядра или выполнения ввода-вывода; поскольку все варианты компилятора должны выполнять одни и те же системные вызовы и выполнять один и тот же ввод-вывод (по модулю печатая одно приглашение отладчика). Сосредоточение внимания на времени пользовательского режима исключает возможный источник нерелевантной временной изменчивости.

# trace int ret opt code rdata total out out in in

уровень уровень кб кб кб сек % сек %

1 нет — — по умолчанию 5097 553 5790 38,3 -4 38,3 -4

2 нет — — отладка 5005 551 5689 39.9 0 39.8 0

3 мелкая нет да отладка 10582 5808 16526 63.6 +59 79.8 +100

4 мелкая нет нет отладка 10582 3155 13873 63.4 +59 79.6 +100

5 глубокая нет да отладка 10372 5808 16317 64.4 +61 109.6 +175

6 глубокая нет нет отладка 10372 3155 13664 64.5 +62 109.5 +175

7 глубокая да да отладка 12497 7742 20375 70.6 +77 140.7 +254

8 глубокая да нет отладка 12497 5294 17927 70.5 +77 141.1 +255

Таблица 1

Результаты бенчмарка на 450 МГц Pentium III

Первые две строки показывают. Что когда компилятор Mercury компилируется без отладки. Его размер составляет около 5,7 Мб; это неудивительно. Если учесть. Что компилятор Mercury и стандартная библиотека (большая часть которой используется компилятором) вместе состоят из более чем 230 000 строк кода Mercury. Что составляет около 8,0 Мб исходного кода.

Включение отладки увеличивает размер исполняемого файла примерно в 2,5-3,5 раза. То есть в диапазоне от 13 до 20 Мб. Размер кода увеличивается примерно с 5 Мб до 10 Мб из-за вызовов MR_trace при событиях интерфейса. Кода при каждой записи процедуры для заполнения слотов стека отладки и кода перед каждым вызовом для сброса глубины и присвоения значения MR_f rom_deep; только незначительная часть увеличения размера для отладки происходит от отключения оптимизаций. Которые отключает отладка. Вызовы MR_trace при внутренних событиях добавляют еще около 2 Мб, мы видим. Что переход от глубокой трассировки без внутренних событий к неглубокой трассировке (которая по определению исключает внутренние события) увеличивает размер кода примерно на 200 Кб; это связано с тестами в коде. Заполняющими слоты стека отладки.

Пропорционально этому размер данных. Доступных только для чтения. Увеличивается еще больше. Сравнение строки 2 со строками 4 и 6 показывает. Что структуры компоновки меток событий интерфейса и структуры компоновки процедур сами по себе добавляют около 2,6 Мб к 0,5 Мб данных только для чтения исходной программы. Строки 3 и 5 показывают. Что макеты меток точек возврата добавляют к этому около 2,7 Мб. Сравнение строк 5 и 7 показывает. Что макеты меток внутренних событий добавляют около 1,9 Мб при наличии макетов возврата. А сравнение строк 6 и 8 показывает. Что они добавляют около 2,1 Мб при их отсутствии. Разница возникает потому. Что макеты меток точек возврата и внутренних событий могут совместно использовать некоторую память. И не только имена переменных. Но и информацию о типе и местоположении живых переменных: довольно часто случается. Что событие происходит сразу после точки возврата, до того. Как какие-либо переменные умерли. Родились или переместились.

Оптимизации. Которые обычно отключает отладка. Действительно увеличивают время работы программы на небольшую. Но значительную величину. Примерно на 4%. Поэтому они включаются по умолчанию. Если уровень трассировки равен нулю. Поскольку отлаженные исполняемые файлы, описанные в строках 3-8, более тесно связаны с исполняемым файлом в строке 2, чем с исполняемым файлом в строке 1, мы использовали время его выполнения как

базовый уровень. С которым сравнивается время. Затраченное отладочными исполняемыми файлами Mercury.

Выполнение отладочных программ Mercury вне отладчика занимает на 5580% больше времени. Чем выполнение той же самой программы. Скомпилированной без отладки. Это более высокие накладные расходы. Чем нам бы хотелось. Но в пределах допустимого диапазона. В то время как включение отладки в программе на языке Си (например. Использование

Источниками этого замедления на 55-80% являются заполнение слотов стека. Сброс счетчика глубины и MR_from_deep перед вызовами. А также вызовы MR_trace. Которые возвращаются сразу же после обнаружения MR_trace_enabled как ложные. Все отладочные исполняемые файлы. Как бы они ни были скомпилированы. Вызывают MR_trace во всех событиях интерфейса. Но не все вызывают MR_trace во внутренних событиях. Сравнение строк 5 и 6 со строками 7 и 8 показывает. Что вызов MR_trace при интерфейсных событиях увеличивает время выполнения примерно на 6 секунд, или на 9%. В нашей тестовой установке каждый запуск программы генерирует около 445 миллионов событий. Если внутренние события включены. И около 286 миллионов событий. Если они не включены. Что означает. Что из этих 445 миллионов событий только 159 миллионов являются внутренними. Если предположить. Что все вызовы MR_trace занимают примерно одинаковое количество времени. То вызовы MR_trace для событий интерфейса занимают около 17 секунд. Сравнивая строку 2 со строками 5 и 6, это означает. Что заполнение слота при вводе процедуры и сброс глобальной переменной перед вызовом вместе занимают около 7 секунд. Неглубоко прослеженный код позволяет избежать заполнения слотов стека отладчика. Если MR_from_deep имеет значение false; в то время как сам тест MR_from_deep требует времени. Сравнение строк 3 и 4 со строками 5 и 6 показывает. Что это все еще хороший компромисс.

Экспериментальные программы. Которые быстрее всего выполняются внутри отладчика. Скомпилированы с неглубокой трассировкой. Для этого есть две причины. Во-первых, в таких исполняемых файлах все вызовы MR_trace. Кроме первого и последнего (события вызова и выхода main/2). Возвращаются после того. Как видят. Что сохраненное значение MR_from_deep равно false. Этот тест занимает примерно на 16 секунд больше. Чем просто тестирование MR_trace_enabled. Однако неглубокие трассируемые исполняемые файлы не должны затем переходить к проверке условия завершения текущей команды отладчика. В то время как вызовы MR_trace в глубоких трассируемых исполняемых файлах делают это. Сравнение строк 3 и 4 со строками 5 и 6 показывает. Что стоимость этих тестов существенна: около 30 секунд. Главным компонентом этой стоимости, вероятно. Является вызов функции. Которая проверяет. Соответствует ли текущее событие точке останова. Наша тестовая установка не создавала никаких точек останова; если бы это было так. Затраты были бы выше. Сравнение строк 5 и 6 со строками 7 и 8 показывает. Что общая стоимость обработки внутренних событий составляет чуть больше 30 секунд. В целом выполнение программы внутри отладчика увеличивает время выполнения в 1,2-1,9 раза по сравнению с отладочной программой и в 2,1-3,7 раза по сравнению с оптимизированной. Не отладочной программой.

Помимо этих сравнений Mereury-to-Mereurv. Мы хотели бы также иметь возможность сравнивать отладочные программы Mercury с отладочными программами в других системах логического программирования (например, Prolog). К сожалению, пока рано

версии компилятора Mercury могут быть выполнены с первым NU-прологом и прологом SICStus. Это не верно для последних версий. Однако мы можем экстраполировать из результатов экспериментов на более ранних версиях компилятора Mercury. О которых мы сообщали в [12]. Эти результаты показали. Что компилятор Mercury. Скомпилированный с помощью SICStus fastcode. Был примерно в 3,4 раза больше той же программы. Скомпилированной с помощью Mercury. И занял примерно в 3,8 раза больше времени для выполнения данной задачи. Хотя точные цифры, вероятно. Изменились с тех пор. Такие изменения вряд ли опровергнут вывод о том. Что скорость и размер программ Mercury. Выполняемых в отладчике. Сопоставимы со скоростью и размером программ Prolog. Выполняемых вне отладчиков.

5 Заключение

Механизмы, используемые системой Mercury для генерации и обработки следов событий. Оказались весьма универсальными. Они были использованы в качестве основы не менее чем трех отладчиков: процедурного отладчика. Описанного в этой статье. Прототипа декларативного отладчика. Описанного в [3]. И Морфина [11], отладчика. Основанного на идее выполнения запросов для анализа трассировки. Генерируемой ошибочной программой. Эти же механизмы были также использованы в качестве основы общего механизма мониторинга выполнения программ по Ртути [10],

Мы обнаружили. Что временные и пространственные затраты на отладку программ Mercury находятся в приемлемых пределах. Просто включение отладки. Но не запуск программы под отладчиком приводит к увеличению времени выполнения примерно на 55-80% ; это ненамного выше замедления. Ожидаемого при компиляции программы на Си с Поскольку мы все время работаем с большой, сложной. Долго работающей программой Mercury. Которая манипулирует многомегабайтными структурами данных. Мы убедились. Что отладчик хорошо работает с такими программами. Описанные нами методы. Позволяющие отладчику делать это. Должны быть применимы и к реализациям других языков.

Все особенности отладчика Mercury. Рассмотренные в данной статье. Присутствуют в текущей версии разработки системы Mercury. Которая доступна в виде выпуска дня из http://www.es,mu.oz.au/mercury/download,html. Большинство из них также присутствовали в предыдущем крупном выпуске Mercury, релизе 0.9, который также доступен по тому же URL.

Мы хотели бы поблагодарить Мирей Дюассе и Эрвана Джахье за несколько плодотворных дискуссий о системах трассировки и за то. Что они впервые выдвинули идею создания отладчика Mercury на основе трассировок событий. А также Тайсона Дауда за его работу над системой Mercury RTTI. Мы хотели бы поблагодарить Австралийский исследовательский совет и Европейское сообщество за их поддержку.

Рекомендации

[1] Ральф Бекет. Меркурий учебник, 1999.

[2] Ганс Бем и Марк Вайзер. Сбор мусора в условиях несговорчивости. Software Practice and Experience, 18:807-820, 1988.

[3] Марк Браун и Золтан Сомогьи. Аннотированные трассировки событий для декларативной отладки, 2000. В процессе подготовки.

[4] Лоуренс Берд. Понимание потока управления пролог-программами. В трудах Семинара по логическому программированию 1980 года, страницы 127-138, Дебрецен, Венгрия. Июль 1980 года.

Тайсон Дауд. Золтан Соможи. Фергус Хендерсон. Томас Конвей и Дэвид Джеффри. Информация о типе времени выполнения в Mercury. В трудах Международной конференции 1999 года по принципам и практике декларативного программирования. Стр. 224-243, Париж. Франция, 1999.

[6] Mireille Ducasse. Opium: расширяемый анализатор трассировок для пролога. Журнал логического программирования. Выходящий в 1999 году.

[7] Мирей Дюкасс и Жак Нойе. Трассировка пролог-программ с помощью исходных инструментов достаточно эффективна. В Трудах семинара JICSLP 98 по технологиям реализации языков программирования, основанных на логике, стр. 46-58, июнь 1998 г.

[8] Д. Р. Хансон и М. Рагхавачари. Машинно-независимый отладчик. Программное обеспечение — Практика и опыт, 26:1277-1299, 1996.

Фергюс Хендерсон. Томас Конвей. Золтан Сомоги и Дэвид Джеффри. Справочное руководство по языку Меркурия. Технический отчет 96/10, Факультет компьютерных наук. Мельбурнский университет, Мельбурн. Австралия, 1996 год.

[10] Эрван Джахье и Мирей Дюкасс. Общий подход к мониторингу выполнения программ. В трудах Шестнадцатой Международной конференции по логическому программированию. Лас-Крусес. Нью-Мексико, 1999.

[11] Эрван Джахье и Мирей Дюкасс. Opium-M 0.1 руководство пользователя и справочники. Технический отчет PI-IRISA-1999 № 1234, IRISA. Ренн, Франция. Март 1999 года.

Золтан Сомоги. Фергюс Хендерсон и Томас Конвей. Алгоритм выполнения Mercury. Эффективный чисто декларативный логический язык программирования. Журнал логического программирования, 26(l-3):17-64, октябрь-декабрь 1996.