Продвинутый векторный транслятор: документация (страница 2)

[Назад]

структуры

Структура – композитный тип данных, инкапсулирующий набор значений разных типов данных. Доступ к значению осуществляется по идентификатору поля, в котором оно хранится. Структура может быть унаследована от другой структуры: в таком случае станут доступны поля родительской структуры. Все структуры в ПВТ унаследованы от System.Pointer (структура Pointer, объявленная в модуле System и не содержащая полей). Объявления полей структуры, как и элементов модуля, могут начинаться с ключевого слова public, что сделает их доступными из других модулей. Допускается так же объявление анонимных полей для резервирования областей памяти. Поля могут иметь тип данных самой структуры, что позволяет создавать связанные списки. Идентификаторы структур рекомендуется записывать с заглавной буквы, а идентификаторы полей – со строчной. Вот примеры структур:

public struct ListItem /* унаследована от System.Pointer */
{
    /* ссылка на следующий элемент списка */
    ListItem next;
}

public struct Window(ListItem) /* унаследована от ListItem */
{
    /* доступность и видимость окна */
    public boolean enabled;
    public boolean visible;
    /* положение окна на экране */
    public int left;
    public int top;
    /* размеры окна */
    public int width;
    public int height;
    /* заголовок окна */
    public char[] caption;
    /* события окна */
    public void(Window /*this*/) hideNotify;
    public void(Window /*this*/) showNotify;
    public void(Window /*this*/, boolean /*active*/) paint;
    public void(Window /*this*/, int /*keyCode*/, int /*charCode*/) keyPressed;
    public void(Window /*this*/, int /*keyCode*/, int /*charCode*/) keyRepeated;
    public void(Window /*this*/, int /*keyCode*/) keyReleased;
    public void(Window /*this*/, int /*x*/, int /*y*/) pointerPressed;
    public void(Window /*this*/, int /*x*/, int /*y*/) pointerDragged;
    public void(Window /*this*/, int /*x*/, int /*y*/) pointerReleased;
    /* зарезервировано */
    public Pointer;
    public Pointer;
    public Pointer;
}

Применение структур довольно обширно и с помощью них можно имитировать объектно-ориентированные возможности. Объявление функций внутри структур невозможно в ПВТ, зато можно объявлять поля, ссылающиеся на функции. Взять, например, поле paint из примера выше. Его тип данных, если опустить все комментарии, будет void(Window, boolean). Это означает, что в это поле можно записать ссылку на функцию, которая не имеет возвращаемого значения (тип возвращаемого значения – void) и принимает два аргумента: первый – ссылка на структуру Window, а второй – значение типа boolean.

переменные

Переменные бывают двух видов: глобальные и локальные. Глобальные переменные объявляются в модулях и могут быть помечены ключевым словом public. Локальные переменные объявляются внутри функций и доступны только внутри этих функций. Локальные переменные могут быть сразу же проинициализированы.

Идентификаторы переменных рекомендуется записывать со строчной буквы. В отличие от некоторых других языков программирования в ПВТ не допускается объявление сразу нескольких переменных с одним типом данных, вроде этого:

int i, j, k = 0;

Такой код у вас не скомпилируется. Вместо этого следует написать так:

int i;
int j;
int k = 0;

Из-за этого, кстати, немного изменился синтаксис цикла for. Но об этом будет сказано в разделе «Управляющие конструкции».

исключения

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

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

Исключения языка программирования могут быть возбуждены с помощью управляющей конструкции throw и команд ассемблера. Обрабатываются такие исключения управляющей конструкцией try…catch.

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

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

Для исключений в ПВТ характерно наследование, как и для структур. Базовое исключение называется System.Throwable. Если родительское исключение не указано, то оно становится System.Exception. Идентификаторы исключений рекомендуется записывать с заглавной буквы. Вот примеры исключений:

public exception MonitorStateException; /* унаследовано от System.Exception */
public exception IllegalArgumentException(RuntimeException);
public exception NumberFormatException(IllegalArgumentException);

функции

Функции в ПВТ играют самую важную роль: именно в них находится весь функционал вашей программы. Все функции имеют возвращаемое значение и аргументы определённого типа. Если функция не возвращает значение, то её возвращаемый тип данных будет void.

Функции могу быть написаны либо на языке высокого уровня, с использованием выражений и управляющих конструкций ПВТ, либо на языке инструкций ассемблера. В последнем случае перед типом возвращаемого значения следует написать ключевое слово assembler или pureassembler, а тело функции будет полностью написано на языке flat assembler. Текст таких функций будет просто вставлен компилятором в целевой файл без каких-либо изменений и проверок синтаксиса. Если указан assembler, то компилятор вставит в начале функции инструкцию enter, а в конце – пару инструкций leave и ret. Если указан pureassembler, то таких вставок не будет.

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

Идентификаторы функций рекомендуется записывать со строчной буквы. Вот примеры функций:

public boolean isNaN(real x)
{
        return x != x;
}

public int scalarProductu(ultra a, ultra b)
{
        return (a ****= b)[0] + a[1] + a[2] + a[3];
}

public float scalarProductx(xvector a, xvector b)
{
        return (a ****= b)[0] + a[1] + a[2] + a[3];
}

public assembler float sqrtf(float x)
{
        movss   xmm0, [.x]
        sqrtss  xmm0, xmm0
}

public assembler double sqrtd(double x)
{
        movsd   xmm0, [.x]
        sqrtsd  xmm0, xmm0
}

public assembler real sqrtr(real x)
{
        fld     tbyte[.x]
        fsqrt
}

Перегрузка функций недопустима в ПВТ, поэтому функции в пределах одного модуля должны иметь разные идентификаторы.

прерывания

Прерывания представляют интерес для разработчиков ядер операционных систем и программ под DOS. Обработчик прерывания в ПВТ – функция, помеченная ключевым словом interrupt и обладающая следующими свойствами:

  • тип возвращаемого значения – всегда void;
  • аргументы – регистры общего назначения;
  • инструкция возврата – iret (iretd, iretq).

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

Разработчику ядра операционной системы на заметку: поскольку регистры FPU и SSE не сохраняются на стеке, – некоторым типам обработчиков прерываний нельзя ими пользоваться.

Некоторые обработчики прерываний имеют код ошибки. В таком случае его следует объявить среди аргументов. Способы объявления обработчиков прерываний в зависимости от разрядности программы и наличия или отсутствия кода ошибки можно посмотреть здесь:

/* Способы объявления обработчиков прерываний в 16-битных программах */

/* Без кода ошибки */
interrupt void <идентификатор>(char flags, char cs, char ip, short ax, short cx, short dx, short bx, short si, short di, short bp) { <тело обработчика> }

/* С кодом ошибки */
interrupt void <идентификатор>(char flags, char cs, char ip, short errorCode, short ax, short cx, short dx, short bx, short si, short di, short bp) { <тело обработчика> }
/* Способы объявления обработчиков прерываний в 32-битных программах */

/* Без кода ошибки */
interrupt void <идентификатор>(int eflags, char cs, int eip, int eax, int ecx, int edx, int ebx, int esi, int edi, int ebp) { <тело обработчика> }

/* С кодом ошибки */
interrupt void <идентификатор>(int eflags, char cs, int eip, int errorCode, int eax, int ecx, int edx, int ebx, int esi, int edi, int ebp) { <тело обработчика> }
/* Способы объявления обработчиков прерываний в 64-битных программах */

/* Без кода ошибки */
interrupt void <идентификатор>(char ss, long rsp, long rflags, char cs, long rip, InterruptContext registers) { <тело обработчика> }

/* С кодом ошибки */
interrupt void <идентификатор>(char ss, long rsp, long rflags, char cs, long rip, long errorCode, InterruptContext registers) { <тело обработчика> }

Ключевое слово public, если оно требуется, следует помещать перед interrupt. Структуру InterruptContext (только 64-битные программы) следует объявить в модуле System следующим образом:

public struct InterruptContext
{
    public long rbp;
    public long r15;
    public long r14;
    public long r13;
    public long r12;
    public long r11;
    public long r10;
    public long r9;
    public long r8;
    public long r7; /* rdi */
    public long r6; /* rsi */
    public long r3; /* rbx */
    public long r2; /* rdx */
    public long r1; /* rcx */
    public long r0; /* rax */
}

Вы не сможете вызвать обработчик прерывания напрямую, как обычную функцию. Однако идентификатор обработчика возвращает его адрес памяти в виде значения типа short, int или long, которое можно использовать лишь с одной единственной целью: поместить его в таблицу дескрипторов прерываний (IDT).

инициализация и финализация

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

initialization
{
    <код инициализации модуля>
}

finalization
{
    <код финализации модуля>
}

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

точка входа в программу

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

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

Выполнение программы всегда начинается с точки входа. Если вы создаёте двоичный файл, то вам достаточно передать управление на нулевой байт кода, чтобы начать выполнение программы. В этом случае выполнение начнётся с точки входа.

соглашения по вызову функций

При написании функций, особенно это касается функций на ассемблере, следует знать порядок вызова функций:

  • все аргументы, независимо от типа, помещаются на стек; порядок – слева направо, первый аргумент – на дне стека, последний – на вершине стека; каждый аргумент занимает целое количество машинных слов;
  • возвращаемое значение, в зависимости от типа, хранится в одном из следующих регистров: ax или eax (boolean, char, byte, short, int, ссылочные типы), rax (long, ссылочные типы в 64-битных программах), st0 (real, st1–st7 должны быть помечены как пустые), xmm0 (float, double, ultra, xvector);
  • извлечение аргументов из стека производится вызванной функцией с помощью инструкции ret <размер аргументов>.

В 16-битных программах возвращаемые значения типов boolean, char, byte, short и ссылочных типов помещаются в регистр ax. В 32-битных программах к этим типам добавляется int, а возвращаемые значения этих типов помещаются в регистр eax. Если регистр ax или eax слишком велик для размещения значения, то значение расширяется инструкцией movsx или movzx.

[Далее]