Продвинутый векторный транслятор — документация, страница 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
, в круглых скобках, через точку с запятой записываются:
- инициализация (через запятую записываются выражения и объявления переменных);
- условие повтора (имеет тип
boolean
); - шаг (через запятую записываются выражения).
За круглыми скобками следует управляющая конструкция или блок в фигурных скобках.
Инициализация цикла 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
.