Симфайлы как интерфейс операционной системы

                  Кузнецов Д.Н., Недоря А.Е.

     Появление   модульных   языков  (Ada,  Modula-2)  выдвинуло
строгие  требования  к  контролю типов. Возникла необходимость в
контроле  типов  не только внутри единицы компиляции, но и между
единицами  компиляции.  Для осуществления полного контроля типов
каждая  единица  компиляции  должна  иметь полную информацию обо
всех   объектах,   которые   она   использует.   Эта  информация
порождается    при    компиляции   интерфейсной   части   модуля
(определяющего  модуля в языке Modula-2 или видимой части пакета
в  языке  Ada) и нужна при компиляции всех модулей, использующих
данный. Будем называть символьными файлами (симфайлами) файлы, в
которых хранится такая информация.
     Кроме   компиляторов,   информация  об  объектах  программы
необходима  отладчику, работающему в терминах исходного языка, а
также,    возможно,   некоторым   другим   компонентам   системы
программирования.  Отладчику недостаточно информации об объектах
интерфейсной  части  модуля,  ему  должны  быть доступны объекты
реализующего  модуля  и,  возможно,  некоторые другие, например,
таблица  соответствия  строк  текста коду модуля. Будем называть
всю  совокупность  информации,  необходимой  отладчику, таблицей
ссылок.
     При  компиляции  определяющего  модуля компилятор порождает
симфайл,   при   компиляции   реализующего   -  таблицу  ссылок.
Естественно   рассматривать   таблицу   ссылок   как   некоторое
расширение   симфайла.   В   дальнейшем  слову  "симфайл"  будем
придавать   именно  этот  расширенный  смысл.  Здесь  нас  будет
интересовать  не  внутренняя структура симфайла, а использование
симфайлов  и  их  место в системе. Принципы построения симфайлов
для языка Modula-2 и структура симфайлов подробно описаны в [1].
     В  развитых  многоязыковых  системах  использование единого
аппарата  симфайлов позволяет разработать универсальный отладчик
для  всех  языков  системы,  а  также  использовать  библиотеки,
написанные на различных языках программирования.
     Симфайлы  можно  применять и в более нетривиальных случаях,
например,  в  качестве  внутреннего  представления описания базы
данных. В этом случае полиморфные операции работы с базой данных
могут использовать симфайл для настройки на конкретную структуру
базы данных.
     Таким  образом,  давно известные разработчикам компиляторов
симфайлы  стали  средством, связывающим компоненты многоязыковой
системы программирования.
     Наша цель - разработать модуль, который скрывает внутреннюю
структуру  симфайла  и  предоставляет  некоторый  удобный  набор
операций  для порождения и чтения симфайлов. Назовем этот модуль
Процессором  СимФайлов  (ПСФ).  Компиляторы,  отладчики и другие
компоненты  системы  программирования будут использовать ПСФ для
порождения и чтения симфайлов.
     В   одноязыковой   системе  для  ПСФ  может  быть  доступна
структура  таблиц компилятора. Отладчик также может пользоваться
таблицами   компилятора  для  хранения  информации  об  объектах
программы.  Приведем  в качестве примера определяющий модуль ПСФ
из [2].

   DEFINITION MODULE RefFiles;
    FROM DataDefs IMPORT ObjPtr;
    FROM FileSystem IMPORT File;

    VAR ModNo:CARDINAL; (* номер текущего модуля *)
      ModList:ObjPtr;   (* список загруженных модулей *)
      RefFile:File;

    PROCEDURE InitRef;

    PROCEDURE InRef(VAR filename:ARRAY OF CHAR; VAR mod:ObjPtr);
    (* вставляет объекты из из именованного симфайла
       в таблицу символов *)

    PROCEDURE OpenRef; (* создает новый симфайл *)
    PROCEDURE CloseRef(adr:INTEGER;pno:CARDINAL);

    PROCEDURE OutUnit(unit:ObjPtr);
    (* выводит локальные объекты процедуры или модуля *)
    PROCEDURE OutPos(sourcepos,pc:CARDINAL);
   END RefFiles.
     Этот  модуль  определяет операции чтения симфайла, создания
нового симфайла и записи в симфайл локальных объектов модуля или
процедур и информации о соответствии строк коду.
     Для  многоязыковой  системы такой способ не подходит. Мы не
умеем   строить  достаточно  эффективные  универсальные  таблицы
символов,  пригодные  для  нескольких  языков. Попытка построить
универсальную   таблицу   символов  обсуждалась  в  [3].  Каждый
компилятор   или  другая  компонента,  использующая  ПСФ,  имеет
некоторое внутреннее представления объектов (I-представление). В
симфайле  этот объект будет иметь некоторое другое представление
(F-представление),  и  надо определять два отображения: I -> F и
наоборот.  Но  I-представление  у разных компонент, использующих
ПСФ,   может   различаться,  и  оно  не  доступно  ПСФ.  Поэтому
необходимо    ввести   некоторое   промежуточное   универсальное
представление (U-представление) и реализовать отображение I -> F
как  суперпозицию  I  ->  U  и  U  ->  F.  Аналогично с обратным
отображением.
     Отображения  I  ->  U  и  U -> I определяются компонентами,
использующими  ПСФ.  Возникает вопрос, почему бы не использовать
сразу  отображение  I -> F, просто описав стандартную внутреннюю
структуру  симфайлов.  Основные причины отказа от этого простого
подхода - это требования надежности и переносимости.
     Надежность:   появление   новой   компоненты,  использующей
симфайлы,  не  должно  приводить к ошибкам в других компонентах,
что  может  легко  произойти,  если  компонента  сама работает с
внутренней  структурой  симфайлов.  При  наличии  промежуточного
представления  модуль  ПСФ  гарантирует  правильность внутренней
структуры симфайла.
     Переносимость:  изменение  внутренней  структуры не требует
изменения компонент, использующих ПСФ.
     Приведем  определяющий  модуль  ПСФ  для  ОС Excelsior [5].
Запись  Info - U-представление объекта. Тип Mode определяет виды
объектов,  которые  являются общепринятыми для языков выбранного
класса  (Pascal, Ada, Modula-2, ПОЛЯР). Некоторые несущественные
детали модуля опущены.

DEFINITION MODULE symFiles; (* Ned 18-Feb-86. (c) KRONOS *)

FROM SYSTEM    IMPORT   WORD, ADDRESS;

(*
     Этот  модуль  осуществляет  создание  симфайлов  и работу с
ними.   Симфайл   состоит   из   заголовка,   списка  импорта  и
последовательности  объектов. Объекты группируются в процедуры и
модули.  Заголовок  симфайла  содержит  имя  модуля,  имя  языка
программирования,  время  компиляции  и вид модуля. Вид модуля -
число  в  диапазоне [0..255]. Диапазон [0..31] резервируется под
стандартные виды модулей:

     1: определяюший модуль        2: реализующий модуль

     3: программный модуль         4: модуль представления

     5: интерфейсный модуль        6: задача

     Список импорта содержит для каждого импортированного модуля
имя модуля, время компиляции и вид модуля.
     Объекты симфайла делятся на 4 категории:
     - описываемые объекты (переменные, процедуры и т.д.);
     - подобъекты (поля записей, параметры процедур);
     - типовые значения;
     - странные сегменты.
     Странный   сегмент  дает  возможность  записать  в  симфайл
некоторую   информацию,  структура  которой  неизвестна  данному
модулю.  Для  его записи надо указать номер, длину и информацию.
Диапазон   номеров   [0..31]   резервируется   под   стандартные
странности:

     1: кусок строкового пула.

*)

TYPE (* Виды объектов *)
  Mode  = (
             inv        (* недопустимый объект *)
        (* стандартные типы *)
           , int, bool, card, char, bitset
           , real, adr, byte, word
        (* типы *)
           , enum       (* тип перечисления       *)
           , range      (* тип отрезка            *)
           , ptr        (* тип массива            *)
           , arr        (* тип записи             *)
           , farr       (* тип подвижного массива *)
           , rec        (* тип записи             *)
           , union      (* тип объединения        *)
           , hidden     (* скрытый тип            *)
           , proctype   (* тип процедуры          *)
           , functype   (* тип функции            *)
           , settype    (* тип множества          *)
        (* компоненты *)
           , parm       (* параметр *)
           , field      (* поле записи *)
           , alter      (* альтернатива объединения *)
        (* объекты *)
           , cons       (* константа    *)
           , econs      (* перечислимая константа *)
           , scons      (* структурная константа  *)
           , vari       (* переменная   *)
           , proc       (* процедура    *)
           , modul      (* модуль       *)
           , typ        (* тип          *)
         );

TYPE (* Представление объекта *)
  Info = RECORD
           mode : Mode;      (* вид объекта (все объекты)       *)
           typno: INTEGER;   (* номер типа при чтении           *)
           typ  : INTEGER;   (* тип объекта                     *)
           inx  : INTEGER;   (* индекс  {arr}                   *)
           min  : INTEGER;   (* нижняя  граница {range}         *)
           max  : INTEGER;   (* верхняя граница {range}         *)
           ofs  : INTEGER;   (* смещение или номер              *)
           level: [0..255];  (* уровень {proc,vari}             *)
           modno: [0..255];  (* номер модуля                    *)
           val  : INTEGER;   (* значение, число значений {enum} *)
           size : INTEGER;   (* размер                          *)
           pkind: [0..255];  (* способ передачи параметра{parm} *)
         END;

TYPE (* процедура чтение "странного" сегмента. (см. pStrange) *)
(*stgProc = PROCEDURE (no: CARDINAL; size: CARDINAL; w: ARRAY OF WORD);*)
  stgProc = PROCEDURE (    CARDINAL,       CARDINAL,    ARRAY OF WORD);

(** MODULE Producer **)

PROCEDURE CreateSym(fn: ARRAY OF CHAR): INTEGER;
(* Создание нового симфайла;
   fn -- имя файла
*)

PROCEDURE pHeader(nm,pl: ARRAY OF CHAR; ctime,kind: INTEGER);
(* Запись заголовка симфайла.
   nm -- имя модуля;
   pl -- название языка программирования;
   ctime -- время компиляции;
   kind  -- вид модуля.
*)

PROCEDURE pImport(nm: ARRAY OF CHAR; ctime,kind: INTEGER);
(* Запись заголовка модуля, импортируемого данным.
   Последовательность заголовков не разрывается
   другими объектами и пишется сразу после
   заголовка симфайла.
*)

PROCEDURE enterUnit(m: Mode; no: CARDINAL);
(* Используется при записи локальных процедур и модулей.
   Начало процедуры или модуля.
*)

PROCEDURE exitUnit(CU: BOOLEAN);
(* Конец процедуры или модуля. Если CU,
   то конец единицы компиляции и, следовательно, симфайла.
*)

PROCEDURE pDcl(i: Info; name: ARRAY OF CHAR);
(* Запись в симфайл именованного объекта *)

PROCEDURE pSubObj(i: Info; name: ARRAY OF CHAR);
(* Запись в симфайл компоненты {parm,field,alter} *)

PROCEDURE pType(i: Info): INTEGER;
(* Запись типа; возвращает его номер *)

PROCEDURE linkage(pointer,type: INTEGER);
(* Определение связи между указателем и типом,
   на который он указывает.
*)

PROCEDURE pStrange(no,size: INTEGER; bytes: ARRAY OF WORD);
(* Запись в симфайл "странного" куска; такой кусок
   никак не обрабатывается при записи и чтении симфайла
   и может содержать некоторые дополнительные атрибуты
   объектов, определения новых объектов и другую
   дополнительную информацию (например, строковый пул).
*)

PROCEDURE pPos(pno,deltaPc,tPos: CARDINAL);
(* Пишет в файл позицию оператора (для отладчика).
   pno -- номер процедуры;
   deltaPc -- размер оператора;
   tPos    -- указатель в тексте.
*)

PROCEDURE CloseSym(): INTEGER;  (* Закрывает симфайл *)

(** END Producer **)

(** MODULE Consumer **)

TYPE (* См. комментарий к соответствующим процедурам записи симфайла *)
  Parms = RECORD
            Dcl      : PROCEDURE (Info, ARRAY OF CHAR);
            GenType  : PROCEDURE (Info);
            GenSubObj: PROCEDURE (Info, ARRAY OF CHAR);
            enterUnit: PROCEDURE (Mode,INTEGER);
            exitUnit : PROC;
            gStrange : stgProc;
            linkage  : PROCEDURE (INTEGER,INTEGER);
            gImport  : PROCEDURE (ARRAY OF CHAR,INTEGER,INTEGER);
            gPos     : PROCEDURE (INTEGER,INTEGER,INTEGER);
            alloc    : PROCEDURE (VAR ADDRESS, INTEGER);
          END;

(* Замечание:
   В начале работы ReadSym вызывается процедура GenType
   для всех стандартных типов, что обеспечивает базис
   дерева типов.
*)

PROCEDURE OpenSym(fn: ARRAY OF CHAR): INTEGER; (* Открывает симфайл *)

PROCEDURE ReadHeader(VAR mn,pl: ARRAY OF CHAR; VAR ctime,kind: INTEGER);
(* Чтение заголовка симфайла. Возвращает имя модуля,
   название языка программирования, время компиляции
   и вид модуля.
*)

PROCEDURE ReadSym(p: Parms): INTEGER;
(* Чтение симфайла - процесс, обратный записи. Закрывает симфайл *)

(** END Consumer **)

END symFiles.

     В  настоящее  время  аппаратом  симфайлов  в  ОС  Excelsior
пользуются  компиляторы  с  языков Modula-2 и C, редактор связей
для  языков  без  модульной  структуры [4] и посмертный историк,
который   выдает  историю  аварийно  завершившегося  процесса  в
терминах  имен  модулей,  процедур  и номеров строк. В ближайшем
будущем  этим  аппаратом  будет  пользоваться компилятор с языка
ПОЛЯР-87 и посмертный отладчик.


                      Л и т е р а т у р а

1. J.Gutknecht. Compilation of data structures:
   An new approach to efficient Modula-2 symbol files.
   ETH, July 1985, 64, pp. 23-42.

2. N.Wirth. A fast and compact compiler for Modula-2.
   ETH, July 1985, 64, pp. 1-22.

3. Кузнецов Д.Н., Недоря А.Е. Проектирование таблиц символов
   для языков со сложными правилами видимости.
   В сборнике "Методы трансляции и конструирования программ"
   ВЦ СОАН СССР, Новосибирск, 1986, с. 153-158.

4. А.Е. Осипов. Новая техника порождения кода и редактирования связей.
   В сборнике "Труды конференции ВНСК", Новосибирск (в печати).

5. Кузнецов Д.Н., Недоря А.Е., Осипов А.Е., Тарасов Е.В.
   Процессор КРОНОС в мультипроцессорной системе.
   В сборнике "Вычислительные системы и программное обеспечение"
   ВЦ СОАН СССР, Новосибирск, 1986, c. 13-19.