Продвинутый векторный транслятор — документация, страница 4

Приоритет операций

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

Приоритет Операции
1. Операции new new
2. Унарные постфиксные [] () . ++ --
3. Унарные префиксные ~ ! + - ++ -- @@@@ #### ^^^^ ++++ ---- @@..@@ ##..## ^^..^^ ++..++ --..-- (тип)
4. Операции умножения и деления * / % // %% **** ***^ ***| //// **..** **..*^ **..*|
5. Операции сложения и вычитания + - ++++ +++| +++# ---- ---| ---# ++..++ ++..+| ++..+# --..-- --..-| --..-#
6. Операции сдвига >> >>> << >>>> >>>>> <<<< >>..>> >>..>>> <<..<<
7. Операции отношения > >= < <= >>|| >=|| <<|| <=|| >>|..| >=|..| <<|..| <=|..|
8. Операции сравнения == != ==|| !=|| ==|..| !=|..|
9. Побитовое «И» &
10. Побитовое «Исключающее ИЛИ» ^
11. Побитовое «ИЛИ» |
12. Логическое «И» &&
13. Логическое «ИЛИ» ||
14. Условная операция ?:
15. Операции присвоения = *= /= %= //= %%= ****= ***^= ***|= ////= **..**= **..*^= **..*|= += -= ++++= +++|= +++#= ----= ---|= ---#= ++..++= ++..+|= ++..+#= --..--= --..-|= --..-#= >>= >>>= <<= >>>>= >>>>>= <<<<= >>..>>= >>..>>>= <<..<<= >>||= >=||= <<||= <=||= >>|..|= >=|..|= <<|..|= <=|..|= ==||= !=||= ==|..|= !=|..|= &= ^= |=

Управляющие конструкции

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

int i;
int j = 7;

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

Оператор dispose удаляет из кучи созданный операцией new объект: структуру или массив. После ключевого слова dispose идёт выражение, ссылающееся на структуру или дескриптор массива, затем — точка с запятой. Примеры создания и удаления объектов:

Window wnd = new Window {
    left = 100,
    top = 100,
    width = 640,
    height = 480
};
int[][] iarray = new int[][] {
    new int[] {
        5, -3, 0
    },
    new int[10]
}
dispose iarray[0];
dispose iarray[1];
dispose iarray;
dispose wnd;

Оператор with позволяет использовать поля структуры как локальные переменные. После ключевого слова with, в круглых скобках, записываются через запятую выражения типа «ссылка на структуру», за круглыми скобками — управляющая конструкция или блок в фигурных скобках. Пример использования:

Window wnd;
with(wnd = new Window)
{
    left = 100;
    top = 100;
    height = (width = 640) - 160;
}

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

if(negative)
{
    value = -value;
}
if(order < 0)
{
    buf[len++] = '-';
    order = -order;
} else
{
    buf[len++] = '+';
}

Стандартный оператор switch позволяет создать ветвление по значению типа int (short в 16-битных программах). После ключевого слова switch, в круглых скобках, записывается выражение типа int (или short), за круглыми скобками — блок в фигурных скобках, внутри которого — управляющие конструкции, некоторые из которых помечены ключевым словом case с постоянным значением типа int (или short) и двоеточием или ключевым словом default с двоеточием. При этом первая управляющая конструкция обязана быть помеченной. На каждый оператор switch допускается всего одна метка default. Пример:

public xvector setElementx(xvector value, int index, float element)
{
    switch(index)
    {
    case 0:
        return new xvector { element, value[1], value[2], value[3] };
    case 1:
        return new xvector { value[0], element, value[2], value[3] };
    case 2:
        return new xvector { value[0], value[1], element, value[3] };
    case 3:
        return new xvector { value[0], value[1], value[2], element };
    default:
        throw CompoundIndexOutOfBoundsException;
    }
}

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

За круглыми скобками следует управляющая конструкция или блок в фигурных скобках.

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

byte[] palette = new byte[3 * 0x40];
for(short j = 0, byte i = 0; i < 0x40; i++)
{
    palette[j++] = i;
    palette[j++] = i;
    palette[j++] = i;
}

Стандартный оператор while — тот же for, но без инициализации и шага. Вот аналогичный пример, но с использованием оператора while:

byte[] palette = new byte[3 * 0x40];
{
    short j = 0;
    byte i = 0;
    while(i < 0x40)
    {
        palette[j++] = i;
        palette[j++] = i;
        palette[j++] = i;
        i++;
    }
}

Стандартный оператор do позволяет создавать циклы с постусловием. После ключевого слова do следует управляющая конструкция или блок в фигурных скобках, затем — ключевое слово while, условие повтора (имеет тип boolean) в круглых скобках, точка с запятой. Вот похожий, но не идентичный, пример использования оператора do:

byte[] palette = new byte[3 * 0x40];
{
    short j = 0;
    byte i = 0;
    do
    {
        palette[j++] = i;
        palette[j++] = i;
        palette[j++] = i;
    } while(++i < 0x40);
}

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

Оператор break позволяет выходить из операторов switch, for, while, do. Может быть с меткой — в таком случае выход будет производиться из управляющей конструкции или блока в фигурных скобках, помеченного меткой, идентификатор которой указан после ключевого слова break. Синтаксис:

break;
break <метка>;

Оператор continue позволяет начать новую итерацию операторов цикла for, while и do. Как и оператор break, может быть с меткой, однако такой меткой обязательно должен быть помечен оператор for, while или do. Синтаксис аналогичен оператору break:

continue;
continue <метка>;

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

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

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

return; /* для функций, возвращающих void, и точки входа */
return <выражение>;

Оператор throw выполняет возбуждение исключения. Процессор просматривает стек вызовов и ищет в ближайшей функции оператор try…catch, который может отловить возбужденное исключение. Управление передаётся первому блоку catch, в котором содержится обработчик исключения. Синтаксис:

throw <идентификатор исключения>;

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

Оператор try…catch имеет следующий синтаксис:

try
{
    <операторы>
}
catch(<идентификатор исключения>)
{
    <операторы>
}
catch(<идентификатор исключения>)
{
    <операторы>
}
<…>
catch(<идентификатор исключения>)
{
    <операторы>
}

Оператор try…finally позволяет гарантировано выполнить код блока finally при выходе из блока try. Имеет следующий синтаксис:

try
{
    <операторы>
}
finally
{
    <операторы>
}

Использование ассемблера

В этом разделе описаны особенности использования ассемблера в программах на ПВТ. Обратите внимание на то, что в функциях на ассемблере вы пользуетесь синтаксисом flat assembler, а не ПВТ. Поэтому даже комментарии придётся начинать с точки с запятой. Целые числа лучше всего записывать в шестнадцатеричной системе счисления с префиксом доллара «$». Для 64-битных программ ПВТ генерирует позиционно-независимый код, поэтому инструкция lea в некоторых случаях будет предпочтительнее, чем mov.

Аргументы функции. Чтобы получить доступ к аргументу функции, достаточно поставить точку и написать его идентификатор, например:

fld     tbyte[.x] ; загрузка аргумента x в регистр st0

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

fld     Math.E ; загрузка константы E из модуля Math в регистр st0

Поля структуры. Здесь так же следует использовать полное имя, например:

mov     eax, [rax+System.Array.length] ; чтение длины массива

Метки и локальные символы. Их следует объявить с префиксом «точка», а их идентификаторы не должны совпадать с идентификаторами аргументов функции и другими метками и локальными символами.

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

r0 (rax)    r0d (eax)    r0w (ax)    r0b (al)
r1 (rcx)    r1d (ecx)    r1w (cx)    r1b (cl)
r2 (rdx)    r2d (edx)    r2w (dx)    r2b (dl)
r3 (rbx)    r3d (ebx)    r3w (bx)    r3b (bl)
            r4d (esp)    r4w (sp)    r4b (spl)
            r5d (ebp)    r5w (bp)    r5b (bpl)
r6 (rsi)    r6d (esi)    r6w (si)    r6b (sil)
r7 (rdi)    r7d (edi)    r7w (di)    r7b (dil)

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

; для функций, имеющих хотя бы один аргумент
lea     r1, [модуль.исключение]
mov     r2, [rbp+$08] ; r2 = адрес возврата
dec     r2
leave
jmp     r15
; для функций без аргументов
lea     r1, [модуль.исключение]
mov     r2, [rsp] ; r2 = адрес возврата
dec     r2
jmp     r15

Естественно, слова «модуль.исключение» следует заменить на полное имя исключения, например System.ArrayIndexOutOfBoundsException.

Если функция на ассемблере всё-таки вызывает другую функцию или саму себя, то ключевое слово assembler следует заменить на pureassembler, а тело функции должно выглядеть так:

{
        push    r15
        lea     r15, [.L.EH]
        enter   $0000, $00
        <ваш код>
        leave
        pop     r15
        ret     <размер аргументов>
.L.EH:  emms
        leave
        pop     r15
        pop     r2
        dec     r2
        jmp     r15
}

Код возбуждения исключения теперь поменяется на следующий:

lea     r1, [модуль.исключение]
jmp     .L.EH

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

Как посчитать размер аргументов функции? Если функция не имеет аргументов, то их размер равен нулю. В противном случае нужно просуммировать размеры всех аргументов, выравнивая их по границе в 8 байт. Например, требуется посчитать размер аргументов такой функции:

public pureassembler int testFunction(int a, real b, ultra c, byte[] d)
Тип аргумента Размер Размер после выравнивания
int 4 8
real 10 16
ultra 16 16
byte[] 8 8
Всего 48

Следовательно, такая функция будет возвращаться инструкцией ret $30.