Язык CIL (Common
Инструкции CIL можно разделить на четыре основные группы. В первую группу входят инструкции общего назначения, которые служат для организации вычислений. Вторая группа содержит инструкции для работы с объектной моделью. Третья служит для генерации и обработки исключений. В четвертую мы относим неверифицируемые инструкции, которые генерируются, главным образом, компилятором языка C для представления небезопасных конструкций языка.
Разработчики набора инструкций CIL уделили большое внимание компактности CIL-кода. Для этого в набор инструкций было введено большое количество сокращенных вариантов инструкций, представляющих собой частные случаи других инструкций и кодирующихся меньшим количеством байт.
Если сравнить
Разные инструкции CIL кодируются последовательностями байт различной длины. Размер каждой инструкции, а также порядок и смысл составляющих ее байт определяется описанием инструкции, которое можно найти в [3].
Последовательность байт, кодирующая инструкцию CIL, начинается с кода инструкции. Часто используемые инструкции имеют однобайтовые коды. Инструкции, которые используются реже, имеют двухбайтовые коды (при этом первый байт всегда равен 0xFE).
В разделе, посвященном виртуальной системе выполнения VES, говорилось о том, что операнды инструкций CIL размещаются на стеке вычислений. Тем не менее, многие инструкции имеют дополнительные встроенные операнды (inline
| Операнд | Размер в байтах | Описание |
|---|---|---|
none |
0 | У некоторых инструкций встроенные операнды отсутствуют |
int8 |
1 | Знаковое 8-битовое целое число |
int32 |
4 | Знаковое 32-битовое целое число |
int64 |
8 | Знаковое 64-битовое целое число |
unsigned int8 |
1 | Беззнаковое 8-битовое целое число |
unsigned int16 |
2 | Беззнаковое 16-битовое целое число |
float32 |
4 | 32-битовое |
float64 |
8 | 64-битовое |
token |
4 | |
switch |
переменный | Массив адресов переходов для инструкции switch |
Особого внимания заслуживает встроенный операнд для инструкции switch. Эта инструкция осуществляет множественный условный переход в зависимости от некоторого целого значения, которое берется из стека вычислений. Ее встроенный операнд представляет собой массив адресов переходов. Он кодируется следующим образом: сначала идет 32-разрядное целое число без знака, обозначающее количество адресов переходов (размер массива), затем следуют сами адреса. При этом каждый адрес кодируется в виде 32-разрядного целого числа со знаком.
Рассмотрим примеры кодирования инструкций CIL:
ldarg.0 загружает на стек вычислений значение первого аргумента метода. Она является сокращенной версией инструкции ldarg, не содержит встроенных операндов и имеет код 0x02:/* 02 */ ldarg.0
arglist загружает на стек вычислений специальный описатель массива переменных параметров метода. Она не содержит встроенных операндов и имеет двухбайтовый код 0xFE 0x00:/* FE 00 */ arglist
ldc.i4.s 16 загружает на стек вычислений целочисленную константу 16. Она является сокращенной версией инструкции ldc.i4, имеет код 0x1F и содержит встроенный операнд типа int8:/* 1F | 10 */ ldc.i4.s 16
ldc.r4 1.0 загружает на стек вычисления число 1.0 (константу с плавающей запятой). Она имеет код 0x22 и содержит встроенный операнд типа float32:/* 22 | 0000803F */ ldc.r4 1.0
isinst System.String служит для динамической проверки типа объекта на стеке вычислений. Она имеет код 0x75 и содержит встроенный операнд типа token, в котором хранится /* 75 | (02)00000F */ isinst System.String
В скобки помещен первый байт токена метаданных, обозначающий номер таблицы метаданных. Обратите внимание, что значение токена метаданных для типа System.String в различных сборках может отличаться.
call System.String::Compare вызывает метод. Ее встроенный операнд содержит /* 28 | (06)0000CD */ call System.String::Compare
В состав набора инструкций CIL входят инструкции для организации условных и безусловных переходов. Встроенные операнды этих инструкций содержат адреса переходов. При этом допустимы только такие адреса, которые указывают на первые байты инструкций в теле данного метода.
Мы будем называть абсолютным адресом инструкции смещение первого байта инструкции относительно начала
В качестве адресов перехода используются не абсолютные адреса целей перехода, а так называемые относительные адреса. Относительный адрес является разностью абсолютного адреса цели перехода и абсолютного адреса инструкции, непосредственно следующей за инструкцией перехода. Для того чтобы лучше понять принцип вычисления адресов перехода, обратимся к следующему примеру:
...
target_addr: add ; цель перехода
...
br rel_addr ; инструкция перехода
next_addr: ...
Здесь используется инструкция безусловного перехода br. При этом в качестве цели перехода выступает инструкция add, расположенная по абсолютному адресу target_addr. Если инструкция, следующая за инструкцией br, имеет абсолютный адрес next_addr, то адрес перехода rel_addr вычисляется следующим образом:
reladdr := target_addr - next_addr
Адреса переходов кодируются во встроенных операндах инструкций перехода в виде 8-битных или 32-битных целых чисел со знаком. При этом 8-битные адреса используются в сокращенных вариантах инструкций перехода.
В спецификации языка CIL в описании каждой инструкции указаны условия, при которых допустимо ее использование. Кроме того, на формирование последовательности инструкций наложен ряд ограничений, позволяющих упростить создание
Упрощение
Давайте сформулируем эти ограничения:
Структура стека вычислений определяется количеством и типами значений, лежащих на стеке.
Для любой инструкции, входящей в
В заголовке метода должна быть указана максимальная глубина стека вычислений. Другими словами, максимальное количество значений, которое может размещаться на стеке вычислений в процессе выполнения метода, должно быть заранее известно еще до
Это ограничение, на первый взгляд, может показаться несколько странным, если принять во внимание, что благодаря ограничению 1
Если при
Чтобы лучше понять данную ситуацию, рассмотрим пример:
... br L2 L1: ldc.0 ; здесь стек считается пустым ... L2: br L1 ; обратный переход на L1 ...
Когда ldc.0, расположенной непосредственно после инструкции безусловного перехода br L2, он не может определить для нее структуру стека вычислений, так как еще не дошел до того места программы, откуда на нее передается управление. В принципе, просканировав дальше программу, это место можно обнаружить (это инструкция br L1 ), но тогда алгоритм
В этом разделе мы рассмотрим ту часть инструкций языка CIL, которая служит для организации вычислений, а именно:
Инструкции для загрузки и сохранения значений предназначены главным образом для обмена значениями между стеком вычислений и памятью, то есть они выполняют копирование значений на стек вычислений и сохранение значений со стека вычислений в память.
Эта группа инструкций (см. таблицу 3.2) служит для загрузки константных значений на стек вычислений. При этом значения кодируются в самих инструкциях в виде их кодов или встроенных операндов.
Диаграмма стека для всех инструкций этой группы выглядит следующим образом:
... -> ... , constant
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x14 | ldnull |
- | Загрузка константы null |
| 0x15 | |
- | Загрузка целого числа -1( int32 ) |
| 0x16 - 0x1E | ldc.0 - ldc.8 |
- | Загрузка целых чисел от 0 до 8 ( int32 ) |
| 0x1F | ldc.s |
int8 |
Загрузка целых чисел от -128 до 127 ( int32 ) |
| 0x20 | ldc.i4 |
int32 |
Загрузка целых чисел ( int32 ) |
| 0x21 | ldc.i8 |
int64 |
Загрузка целых чисел ( int64 ) |
| 0x22 | ldc.r4 |
float32 |
Загрузка чисел с плавающей запятой ( F ) |
| 0x23 | ldc.r8 |
float64 |
Загрузка чисел с плавающей запятой ( F ) |
Локальные переменные и параметры методов имеют номера от 0 до 65534. Существуют три варианта инструкций для работы с переменными и параметрами:
В таблице 3.3 перечислены инструкции, выполняющие загрузку значений переменных и параметров на стек вычислений. Все они имеют следующую диаграмму стека:
... -> ... , value
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x02 - 0x05 | ldarg.0 - ldarg.3 |
- | Загрузка параметров с номерами от 0 до 3 |
| 0x06 - 0x09 | ldloc.0 - ldloc.3 |
- | Загрузка локальных переменных с номерами от 0 до 3 |
| 0x0E | ldarg.s |
unsigned int8 |
Загрузка параметров с номерами от 0 до 255 |
| 0x11 | ldloc.s |
unsigned int8 |
Загрузка локальных переменных с номерами от 0 до 255 |
| 0xFE 0x09 | ldarg |
unsigned int16 |
Загрузка параметров с номерами от 0 до 65534 |
| 0xFE 0x0C | ldloc |
unsigned int16 |
Загрузка локальных переменных от 0 до 65534 |
Кроме инструкций, загружающих значения переменных и параметров, существуют инструкции, загружающие на вершину стека вычислений
... -> ... , address
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x0F | ldarga.s |
unsigned int8 |
Загрузка адресовпараметров с номерами от 0 до 255 |
| 0x12 | ldloca.s |
unsigned int8 |
Загрузка адресов локальных переменных с номерами от 0 до 255 |
| 0xFE 0x0A | ldarga |
unsigned int16 |
Загрузка адресов параметров с номерами от 0 до 65534 |
| 0xFE 0x0D | ldloca |
unsigned int16 |
Загрузка адресов локальных переменных с номерами от 0 до 65534 |
Инструкции, представленные в таблице 3.5, выполняют сохранение значения на вершине стека в переменную или параметр. Они имеют следующую диаграмму стека:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x0A | stloc.0 - stloc.3 |
- | Сохранение значений в локальных переменных с номерами от 0 до 3 |
| 0x10 | starg.s |
unsigned int8 |
Сохранение значений в параметрах с номерами от 0 до 255 |
| 0x13 | stloc.s |
unsigned int8 |
Сохранение значений в локальных переменных с номерами от 0 до 255 |
| 0xFE 0x0B | starg |
unsigned int16 |
Сохранение значений в параметрах с номерами от 0 до 65534 |
| 0xFE 0x0E | stloc |
unsigned int16 |
Сохранение значений в локальных переменных с номерами от 0 до 65534 |
При косвенной загрузке и сохранении значений работа с памятью осуществляется через адреса (управляемые и неуправляемые указатели).
Особенностью инструкций данной группы является наличие разных инструкций для работы со значениями разных типов. Причина в том, что при загрузке или сохранении значения бывает необходимо выполнить его преобразование к другому типу, а так как int8 при загрузке на стек расширяется до int32 ).
В таблице 3.6 перечислены инструкции для косвенной загрузки значений. Обратите внимание, что инструкции ldind.i8 и ldind.u8 являются псевдонимами (имеют один и тот же код). Дело в том, что загрузка любых 64-разрядных целых значений на стек не вызывает их преобразования, ибо хотя на стеке не предусмотрено наличие беззнаковых 64-разрядных значений, их загрузка все равно сводится к простому побитовому копированию. Вышесказанное справедливо также и для 32-разрядных целых значений, но для их загрузки зачем-то зарезервировано сразу две инструкции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x46 | ldind.i1 |
- | Косвенная загрузка значения int8 |
| 0x47 | ldind.u1 |
- | Косвенная загрузка значения unsigned int8 |
| 0x48 | ldind.i2 |
- | Косвенная загрузка значения int16 |
| 0x49 | ldind.u2 |
- | Косвенная загрузка значения unsigned int16 |
| 0x4A | ldind.i4 |
- | Косвенная загрузка значения int32 |
| 0x4B | ldind.u4 |
- | Косвенная загрузка значения unsigned int32 |
| 0x4C | ldind.i8(ldind.u8) |
- | Косвенная загрузка значения int64 и unsigned int64 |
| 0x4D | ldind.i |
- | Косвенная загрузка значения native int |
| 0x4E | ldind.r4 |
- | Косвенная загрузка значения float32 |
| 0x4 | ldind.r8 |
- | Косвенная загрузка значения float64 |
| 0x50 | ldind.ref |
- | Косвенная загрузка объектной ссылки |
Диаграмма стека для инструкций косвенной загрузки выглядит следующим образом:
... , address -> ... , value
Инструкций для косвенного сохранения значений (см. таблицу 3.7) меньше, чем инструкций для косвенной загрузки (можно заметить, что инструкции для сохранения значений беззнаковых целых типов отсутствуют). Причина в том, что сохранение беззнаковых целых ничем не отличается от сохранения знаковых целых.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x51 | stind.ref |
- | Косвенное сохранение объектной ссылки |
| 0x52 | stind.i1 |
- | Косвенное сохранение значения int8 |
| 0x53 | stind.i2 |
- | Косвенное сохранение значения int16 |
| 0x54 | stind.i4 |
- | Косвенное сохранение значения int32 |
| 0x55 | stind.i8 |
- | Косвенное сохранение значения int64 |
| 0x56 | stind.r4 |
- | Косвенное сохранение значения float32 |
| 0x57 | stind.r8 |
- | Косвенное сохранение значения float64 |
| 0xDF | stind.i |
- | Косвенное сохранение значения native int |
Диаграмма стека для инструкций косвенного сохранения выглядит следующим образом:
... , address , value -> ...
В отличие от "железных" стековых процессоров, CLI не содержит развитой системы инструкций для чисто стековых манипуляций. В таблице 3.8 представлены две имеющиеся в наличии инструкции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x25 | dup |
- | Копирование значения на вершине стека: ..., value ->..., value, value |
| 0x26 | pop |
- | Удаление значения с вершины стека . .., value ->... |
Арифметические инструкции можно разделить на четыре категории:
ckfinite, проверяющая конечность значений с плавающей точкой;Бинарные арифметические операции потребляют со стека вычислений два операнда. Соответственно, диаграмма стека для таких операций выглядит следующим образом:
... , value1 , value2 -> ... , result
Действие бинарных операций можно записать как
result := value1 op value2,
то есть например, если op соответствует операции вычитания, то из value1 вычитается value2.
Некоторые бинарные операции могут использоваться для операндов различных типов. Другими словами, в коде инструкции не содержится информации о типах ее операндов, так как эти типы определяются на этапе add можно использовать для сложения как двух целых чисел, так и двух чисел с плавающей запятой. При этом применение бинарной операции не допускается, если тип одного ее операнда - целый, а другого - с плавающей запятой.
Тип результата бинарной операции зависит от типов операндов. Если операнды целые, то и результат будет целый. Если операнды представляют собой
В таблице 3.9 представлены базовые инструкции, выполняющие бинарные операции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x58 | add |
- | Сложение |
| 0x59 | sub |
- | Вычитание |
| 0x5A | mul |
- | Умножение |
| 0x5B | div |
- | Деление |
| 0x5C | div.un |
- | Деление беззнаковых целых чисел |
| 0x5D | |
- | Остаток от деления |
| 0x5E | |
- | Остаток от деления беззнаковых целых чисел |
| 0x5F | and |
- | Побитовое И |
| 0x60 | or |
- | Побитовое ИЛИ |
| 0x61 | xor |
- | Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ |
Инструкции, представленные в таблице 3.10, используются только для целочисленных операндов. Они отличаются от базовых бинарных операций тем, что осуществляют OverflowException ).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xD6 | add. |
- | Сложение целых чисел со знаком с |
| 0xD7 | add. |
- | Сложение целых чисел без знака с |
| 0xD | mul. |
- | Умножение целых чисел со знаком с |
| 0xD | mul. |
- | Умножение целых чисел без знака с |
| 0xD | sub. |
- | Вычитание целых чисел со знаком с |
| 0xD | sub. |
- | Вычитание целых чисел без знака с |
value1 ) в нужную сторону на количество бит, указанное во втором операнде ( value2 ).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x62 | |
- | Сдвиг целых чисел влево |
| 0x63 | shr |
- | Сдвиг целых чисел со знаком вправо |
| 0x64 | shr.un |
- | Сдвиг целых чисел без знака вправо |
int32 ). Число 0 обозначает ложь, а число 1 - истину.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x01 | ceq |
- | Сравнение на равенство. Для целых чисел: I ceq I => 1, иначе Для чисел с плавающей запятой: +inf ceq +inf => 1, -inf ceq -inf => 1, A ceq A => 1, иначе |
| 0xFE 0x02 | cgt |
- | Сравнение на "больше". Для целых чисел: J cgt I => 1, иначе Для чисел с плавающей запятой: A cgt -inf => 1, +inf cgt A => 1, +inf cgt -inf => 1, B cgt A => 1, иначе |
| 0xFE 0x04 | clt |
- | Сравнение на "меньше". Для целых чисел: I clt J => 1, иначе Для чисел с плавающей запятой: A clt +inf => 1, -inf clt A => 1, -inf clt +inf => 1, A clt B => 1, иначе |
| 0xFE 0x03 | cgt.un |
- | Сравнение на "больше" беззнаковых целых чисел или неупорядоченных чисел с плавающей запятой. (Два Для целых чисел: L cgt.un K => 1, иначе Для чисел с плавающей запятой: NaN cgt.un C => 1, C cgt.un NaN => 1, A cgt.un -inf => 1, +inf cgt.un A => 1, +inf cgt.un -inf => 1, B cgt.un A => 1, иначе |
| 0xFE 0x05 | clt.un |
- | Сравнение на "меньше" беззнаковых целых чисел или неупорядоченных чисел с плавающей запятой. Для целых чисел: K clt.un L => 1, иначе Для чисел с плавающей запятой: NaN clt.un C => 1, C clt.un NaN => 1, A clt.un +inf => 1, -inf clt.un A => 1, -inf clt.un +inf => 1, A clt.un B => 1, иначе |
Семантика операций сравнения для чисел с плавающей запятой существенно отличается от их семантики для целых чисел. Дело в том, что +inf (положительная бесконечность), -inf (отрицательная бесконечность) и (Not a Number - не число). Поэтому описание каждой инструкции содержит две части: для целых чисел и для чисел с плавающей запятой. При этом в описании используются следующие обозначения:
I и J - целые числа со знаком, причем I < J ;K и L - целые числа без знака, причем K < L ;A и B - конечные NaN , +inf и -inf ), причем A < B ;C - любое NaN , +inf и -inf ).В таблице 3.13 приведены две инструкции, выполняющие унарные арифметические операции. Диаграмма стека для
... , value -> ... , result
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x65 | neg |
- | Изменение знака числа |
| 0x66 | not |
- | Побитовое НЕ (для целых чисел) |
Инструкция neg применима как для целых чисел, так и для чисел с плавающей запятой и обладает двумя особенностями:
neg использовать sub.ovf .NaN является NaN .Инструкция ckfinite (см. таблицу 3.14) генерирует исключение ArithmeticException, если , +inf или -inf. Если исключение не генерируется, то стек вычислений не меняется, поэтому диаграмма стека выглядит следующим образом:
... , value -> ... , value
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xC3 | ckfinite |
- | Проверка того, что |
Инструкции преобразования значений потребляют один операнд со стека вычислений и преобразуют его к нужному типу. Диаграмма стека для этих инструкций выглядит следующим образом:
... , value -> ... , result
Базовые инструкции преобразования представлены в таблице 3.15. Они обладают следующими особенностями:
float32 может вызывать float32, то результатом преобразования является +inf или -inf.conv.r.un интерпретирует целое значение, лежащее на вершине стека, как не имеющее знака и преобразует его к вещественному типу (либо float32, либо float64 в зависимости от значения).| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x67 | conv.i1 |
- | Преобразовать к int8 |
| 0x68 | conv.i2 |
- | Преобразовать к int16 |
| 0x69 | conv.i4 |
- | Преобразовать к int32 |
| 0x6A | conv.i8 |
- | Преобразовать к int64 |
| 0x6B | conv.r4 |
- | Преобразовать к float32 |
| 0x6C | conv.r8 |
- | Преобразовать к float64 |
| 0x6D | conv.u4 |
- | Преобразовать к unsigned int32 |
| 0x6E | conv.u8 |
- | Преобразовать к unsigned int64 |
| 0x76 | conv.r.un. |
- | Преобразовать беззнаковое целое число в |
| 0xD1 | conv.u2 |
- | Преобразовать к unsigned int16 |
| 0xD2 | conv.u1 |
- | Преобразовать к unsigned int8 |
| 0xD3 | conv.i |
- | Преобразовать к native int |
| 0xE0 | conv.u |
- | Преобразовать к unsigned native int |
В таблице 3.16 приведены инструкции для преобразования значений, имеющих знак, к целым типам с OverflowException.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xB3 | conv. |
- | Преобразование к int8 |
| 0xB4 | conv. |
- | Преобразование к unsigned int8 |
| 0xB5 | conv. |
- | Преобразование к int16 |
| 0xB6 | conv. |
- | Преобразование к unsigned int16 |
| 0xB7 | conv. |
- | Преобразование к int32 |
| 0xB8 | conv. |
- | Преобразование к unsigned int32 |
| 0xB9 | conv. |
- | Преобразование к int64 |
| 0xBA | conv. |
- | Преобразование к unsigned int64 |
| 0xD4 | conv. |
- | Преобразование к native int |
| 0xD5 | conv. |
- | Преобразование к unsigned native int |
Инструкции, представленные в таблице 3.17, используются для преобразования беззнаковых значений к нужному типу и генерируют исключение OverflowException в случае переполнения.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x82 | conv. |
- | Преобразование к int8 |
| 0x83 | conv. |
- | Преобразование к int16 |
| 0x84 | conv. |
- | Преобразование к int32 |
| 0x85 | conv. |
- | Преобразование к int64 |
| 0x86 | conv. |
- | Преобразование к unsigned int8 |
| 0x87 | conv. |
- | Преобразование к unsigned int16 |
| 0x88 | conv. |
- | Преобразование к unsigned int32 |
| 0x89 | conv. |
- | Преобразование к unsigned int64 |
| 0x8A | conv. |
- | Преобразование к native int |
| 0x8B | conv. |
- | Преобразование к unsigned native int |
Инструкции для организации передачи управления можно разделить на пять категорий:
Существуют две инструкции безусловного перехода (см. таблицу 3.18), которые различаются только разрядностью встроенного операнда ( int8 и int32 ). При этом встроенный операнд этих инструкций обозначает относительное смещение цели перехода.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2B | br.s |
int8 |
Короткий безусловный переход |
| 0x38 | br. |
int32 |
Длинный безусловный переход |
Базовые инструкции условного перехода, приведенные в таблице 3.19, потребляют со стека вычислений один операнд и, в зависимости от его значения, осуществляют или не осуществляют переход по указанному во встроенном операнде относительному адресу. Диаграмма стека для этих инструкций выглядит следующим образом:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2C | brfalse.s |
int8 |
Короткий условный переход, если значение равно 0 или null |
| 0x2D | brtrue.s |
int8 |
Короткий условный переход, если значение не равно 0 или null |
| 0x39 | brfalse |
int32 |
Длинный условный переход, если значение равно 0 или null |
| 0x3A | brtrue |
int32 |
Длинный условный переход, если значение не равно 0 или null |
Как и в случае инструкций безусловного перехода, существуют короткий и длинный варианты инструкций условного перехода, которые отличаются только разрядностью встроенного операнда ( int8 и int32 ).
Инструкции brfalse присвоены два псевдонима: brnull и brzero, имеющих одинаковый с ней код. Аналогично, brfalse.s имеет псевдонимы brnull.s и brzero.s.
Кроме базовых, существуют дополнительные инструкции условного перехода, потребляющие сразу два операнда. Каждая из дополнительных инструкций условного перехода является сокращенной записью последовательности из двух инструкций, первая из которых является бинарной операцией сравнения, а вторая - базовой инструкцией условного перехода.
Таким образом, дополнительные инструкции условного перехода имеют следующую диаграмму стека:
... , value1 , value2 -> ...
Длинные варианты дополнительных инструкций условного перехода перечислены в таблице 3.20, а короткие - в таблице 3.21. В описании каждой инструкции приводится эквивалентная ей комбинация бинарной операции сравнения и базовой инструкции условного перехода. В связи с тем, что семантика операций сравнения для целых чисел существенно отличается от их семантики для чисел с плавающей запятой, для инструкций bge, ble, bge.un и ble.un (и для коротких вариантов этих инструкций) приводится по две эквивалентные им комбинации. Комбинация, помеченная меткой ( int ), используется в случае целых операндов, а комбинация, помеченная меткой ( F ), используется в том случае, если операнды представляют собой
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x3B | beq |
int32 |
ceq; brtrue |
| 0x3C | bge |
int32 |
(int): clt; brfalse (F) : clt.un; brfalse |
| 0x3D | bgt |
int32 |
cgt; brtrue |
| 0x3E | ble |
int32 |
(int): cgt; brfalse (F) : cgt.un; brfalse |
| 0x3F | |
int32 |
clt; brtrue |
| 0x40 | bne.un |
int32 |
ceq; brfalse |
| 0x41 | bge.un |
int32 |
(int): clt.un; brfalse (F) : clt; brfalse |
| 0x42 | bgt.un |
int32 |
cgt.un; brtrue |
| 0x43 | ble.un |
int32 |
(int): cgt.un; brfalse (F) : cgt; brfalse |
| 0x44 | |
int32 |
clt.un; brtrue |
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2E | beq.s |
int8 |
ceq; brtrue.s |
| 0x2F | bge.s |
int8 |
(int): clt; brfalse.s (F) : clt.un; brfalse.s |
| 0x30 | bgt.s |
int8 |
cgt; brtrue.s |
| 0x31 | ble.s |
int8 |
(int): cgt; brfalse.s (F) : cgt.un; brfalse.s |
| 0x32 | |
int8 |
clt; brtrue.s |
| 0x33 | bne.un.s |
int8 |
ceq; brfalse.s |
| 0x34 | bge.un.s |
int8 |
(int): clt.un; brfalse.s (F) : clt; brfalse.s |
| 0x35 | bgt.un.s |
int8 |
cgt.un; brtrue.s |
| 0x36 | ble.un.s |
int8 |
(int): cgt.un; brfalse.s (F) : cgt; brfalse.s |
| 0x37 | |
int8 |
clt.un; brtrue.s |
Инструкция множественного выбора switch представлена в таблице 3.22. Встроенный операнд этой инструкции имеет сложный формат: первое 32-разрядное слово содержит размер N таблицы переходов, после чего следует N 32-рязрядных относительных смещений целей перехода.
Инструкция switch берет со стека вычислений один операнд. Обозначим этот операнд через I. Значение I интерпретируется как целое число без знака и сравнивается с N. Если I < N, то управление передается на I-тую цель в таблице переходов (нумерация целей осуществляется с 0). Если I >= N, то управление передается на инструкцию, непосредственно следующую за инструкцией switch.
Для инструкции switch можно записать следующую диаграмму стека:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x45 | switch |
unsigned int32, int32,... int32, |
Осуществляет переход по таблице переходов в соответствии со значением на вершине стека |
Инструкция call (см. таблицу 3.23) выполняет вызов метода, который указан во встроенном операнде инструкции. Встроенный операнд представляет собой call может стоять префикс .tail, который говорит о том, что состояние текущего метода должно быть освобождено перед передачей управления вызываемому методу (хвостовой вызов).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x28 | call |
token |
Выполняет вызов метода |
Информация о методе, на которую указывает call (по сравнению с инструкцией callvirt, которую мы будем рассматривать далее в этой главе) является то, что адрес вызываемого метода вычисляется статически, то есть еще во время
Параметры вызываемого метода должны быть расположены в стеке слева направо, то есть сначала на стек должен быть загружен первый параметр, затем второй и т.д. При вызове экземплярного метода в качестве первого параметра должна выступать объектная ссылка (параметр this ).
Если вызываемый метод возвращает значение, то оно загружается на стек вызывающего метода.
Инструкция (см. таблицу 3.24) осуществляет возврат из метода. Если метод возвращает значение, то оно должно быть загружено на вершину стека вычислений.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2A | |
- | Осуществляет возврат из метода |
Язык CIL (Common
Инструкции CIL можно разделить на четыре основные группы. В первую группу входят инструкции общего назначения, которые служат для организации вычислений. Вторая группа содержит инструкции для работы с объектной моделью. Третья служит для генерации и обработки исключений. В четвертую мы относим неверифицируемые инструкции, которые генерируются, главным образом, компилятором языка C для представления небезопасных конструкций языка.
Разработчики набора инструкций CIL уделили большое внимание компактности CIL-кода. Для этого в набор инструкций было введено большое количество сокращенных вариантов инструкций, представляющих собой частные случаи других инструкций и кодирующихся меньшим количеством байт.
Если сравнить
Разные инструкции CIL кодируются последовательностями байт различной длины. Размер каждой инструкции, а также порядок и смысл составляющих ее байт определяется описанием инструкции, которое можно найти в [3].
Последовательность байт, кодирующая инструкцию CIL, начинается с кода инструкции. Часто используемые инструкции имеют однобайтовые коды. Инструкции, которые используются реже, имеют двухбайтовые коды (при этом первый байт всегда равен 0xFE).
В разделе, посвященном виртуальной системе выполнения VES, говорилось о том, что операнды инструкций CIL размещаются на стеке вычислений. Тем не менее, многие инструкции имеют дополнительные встроенные операнды (inline
| Операнд | Размер в байтах | Описание |
|---|---|---|
none |
0 | У некоторых инструкций встроенные операнды отсутствуют |
int8 |
1 | Знаковое 8-битовое целое число |
int32 |
4 | Знаковое 32-битовое целое число |
int64 |
8 | Знаковое 64-битовое целое число |
unsigned int8 |
1 | Беззнаковое 8-битовое целое число |
unsigned int16 |
2 | Беззнаковое 16-битовое целое число |
float32 |
4 | 32-битовое |
float64 |
8 | 64-битовое |
token |
4 | |
switch |
переменный | Массив адресов переходов для инструкции switch |
Особого внимания заслуживает встроенный операнд для инструкции switch. Эта инструкция осуществляет множественный условный переход в зависимости от некоторого целого значения, которое берется из стека вычислений. Ее встроенный операнд представляет собой массив адресов переходов. Он кодируется следующим образом: сначала идет 32-разрядное целое число без знака, обозначающее количество адресов переходов (размер массива), затем следуют сами адреса. При этом каждый адрес кодируется в виде 32-разрядного целого числа со знаком.
Рассмотрим примеры кодирования инструкций CIL:
ldarg.0 загружает на стек вычислений значение первого аргумента метода. Она является сокращенной версией инструкции ldarg, не содержит встроенных операндов и имеет код 0x02:/* 02 */ ldarg.0
arglist загружает на стек вычислений специальный описатель массива переменных параметров метода. Она не содержит встроенных операндов и имеет двухбайтовый код 0xFE 0x00:/* FE 00 */ arglist
ldc.i4.s 16 загружает на стек вычислений целочисленную константу 16. Она является сокращенной версией инструкции ldc.i4, имеет код 0x1F и содержит встроенный операнд типа int8:/* 1F | 10 */ ldc.i4.s 16
ldc.r4 1.0 загружает на стек вычисления число 1.0 (константу с плавающей запятой). Она имеет код 0x22 и содержит встроенный операнд типа float32:/* 22 | 0000803F */ ldc.r4 1.0
isinst System.String служит для динамической проверки типа объекта на стеке вычислений. Она имеет код 0x75 и содержит встроенный операнд типа token, в котором хранится /* 75 | (02)00000F */ isinst System.String
В скобки помещен первый байт токена метаданных, обозначающий номер таблицы метаданных. Обратите внимание, что значение токена метаданных для типа System.String в различных сборках может отличаться.
call System.String::Compare вызывает метод. Ее встроенный операнд содержит /* 28 | (06)0000CD */ call System.String::Compare
В состав набора инструкций CIL входят инструкции для организации условных и безусловных переходов. Встроенные операнды этих инструкций содержат адреса переходов. При этом допустимы только такие адреса, которые указывают на первые байты инструкций в теле данного метода.
Мы будем называть абсолютным адресом инструкции смещение первого байта инструкции относительно начала
В качестве адресов перехода используются не абсолютные адреса целей перехода, а так называемые относительные адреса. Относительный адрес является разностью абсолютного адреса цели перехода и абсолютного адреса инструкции, непосредственно следующей за инструкцией перехода. Для того чтобы лучше понять принцип вычисления адресов перехода, обратимся к следующему примеру:
...
target_addr: add ; цель перехода
...
br rel_addr ; инструкция перехода
next_addr: ...
Здесь используется инструкция безусловного перехода br. При этом в качестве цели перехода выступает инструкция add, расположенная по абсолютному адресу target_addr. Если инструкция, следующая за инструкцией br, имеет абсолютный адрес next_addr, то адрес перехода rel_addr вычисляется следующим образом:
reladdr := target_addr - next_addr
Адреса переходов кодируются во встроенных операндах инструкций перехода в виде 8-битных или 32-битных целых чисел со знаком. При этом 8-битные адреса используются в сокращенных вариантах инструкций перехода.
В спецификации языка CIL в описании каждой инструкции указаны условия, при которых допустимо ее использование. Кроме того, на формирование последовательности инструкций наложен ряд ограничений, позволяющих упростить создание
Упрощение
Давайте сформулируем эти ограничения:
Структура стека вычислений определяется количеством и типами значений, лежащих на стеке.
Для любой инструкции, входящей в
В заголовке метода должна быть указана максимальная глубина стека вычислений. Другими словами, максимальное количество значений, которое может размещаться на стеке вычислений в процессе выполнения метода, должно быть заранее известно еще до
Это ограничение, на первый взгляд, может показаться несколько странным, если принять во внимание, что благодаря ограничению 1
Если при
Чтобы лучше понять данную ситуацию, рассмотрим пример:
... br L2 L1: ldc.0 ; здесь стек считается пустым ... L2: br L1 ; обратный переход на L1 ...
Когда ldc.0, расположенной непосредственно после инструкции безусловного перехода br L2, он не может определить для нее структуру стека вычислений, так как еще не дошел до того места программы, откуда на нее передается управление. В принципе, просканировав дальше программу, это место можно обнаружить (это инструкция br L1 ), но тогда алгоритм
В этом разделе мы рассмотрим ту часть инструкций языка CIL, которая служит для организации вычислений, а именно:
Инструкции для загрузки и сохранения значений предназначены главным образом для обмена значениями между стеком вычислений и памятью, то есть они выполняют копирование значений на стек вычислений и сохранение значений со стека вычислений в память.
Эта группа инструкций (см. таблицу 3.2) служит для загрузки константных значений на стек вычислений. При этом значения кодируются в самих инструкциях в виде их кодов или встроенных операндов.
Диаграмма стека для всех инструкций этой группы выглядит следующим образом:
... -> ... , constant
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x14 | ldnull |
- | Загрузка константы null |
| 0x15 | |
- | Загрузка целого числа -1( int32 ) |
| 0x16 - 0x1E | ldc.0 - ldc.8 |
- | Загрузка целых чисел от 0 до 8 ( int32 ) |
| 0x1F | ldc.s |
int8 |
Загрузка целых чисел от -128 до 127 ( int32 ) |
| 0x20 | ldc.i4 |
int32 |
Загрузка целых чисел ( int32 ) |
| 0x21 | ldc.i8 |
int64 |
Загрузка целых чисел ( int64 ) |
| 0x22 | ldc.r4 |
float32 |
Загрузка чисел с плавающей запятой ( F ) |
| 0x23 | ldc.r8 |
float64 |
Загрузка чисел с плавающей запятой ( F ) |
Локальные переменные и параметры методов имеют номера от 0 до 65534. Существуют три варианта инструкций для работы с переменными и параметрами:
В таблице 3.3 перечислены инструкции, выполняющие загрузку значений переменных и параметров на стек вычислений. Все они имеют следующую диаграмму стека:
... -> ... , value
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x02 - 0x05 | ldarg.0 - ldarg.3 |
- | Загрузка параметров с номерами от 0 до 3 |
| 0x06 - 0x09 | ldloc.0 - ldloc.3 |
- | Загрузка локальных переменных с номерами от 0 до 3 |
| 0x0E | ldarg.s |
unsigned int8 |
Загрузка параметров с номерами от 0 до 255 |
| 0x11 | ldloc.s |
unsigned int8 |
Загрузка локальных переменных с номерами от 0 до 255 |
| 0xFE 0x09 | ldarg |
unsigned int16 |
Загрузка параметров с номерами от 0 до 65534 |
| 0xFE 0x0C | ldloc |
unsigned int16 |
Загрузка локальных переменных от 0 до 65534 |
Кроме инструкций, загружающих значения переменных и параметров, существуют инструкции, загружающие на вершину стека вычислений
... -> ... , address
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x0F | ldarga.s |
unsigned int8 |
Загрузка адресовпараметров с номерами от 0 до 255 |
| 0x12 | ldloca.s |
unsigned int8 |
Загрузка адресов локальных переменных с номерами от 0 до 255 |
| 0xFE 0x0A | ldarga |
unsigned int16 |
Загрузка адресов параметров с номерами от 0 до 65534 |
| 0xFE 0x0D | ldloca |
unsigned int16 |
Загрузка адресов локальных переменных с номерами от 0 до 65534 |
Инструкции, представленные в таблице 3.5, выполняют сохранение значения на вершине стека в переменную или параметр. Они имеют следующую диаграмму стека:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x0A | stloc.0 - stloc.3 |
- | Сохранение значений в локальных переменных с номерами от 0 до 3 |
| 0x10 | starg.s |
unsigned int8 |
Сохранение значений в параметрах с номерами от 0 до 255 |
| 0x13 | stloc.s |
unsigned int8 |
Сохранение значений в локальных переменных с номерами от 0 до 255 |
| 0xFE 0x0B | starg |
unsigned int16 |
Сохранение значений в параметрах с номерами от 0 до 65534 |
| 0xFE 0x0E | stloc |
unsigned int16 |
Сохранение значений в локальных переменных с номерами от 0 до 65534 |
При косвенной загрузке и сохранении значений работа с памятью осуществляется через адреса (управляемые и неуправляемые указатели).
Особенностью инструкций данной группы является наличие разных инструкций для работы со значениями разных типов. Причина в том, что при загрузке или сохранении значения бывает необходимо выполнить его преобразование к другому типу, а так как int8 при загрузке на стек расширяется до int32 ).
В таблице 3.6 перечислены инструкции для косвенной загрузки значений. Обратите внимание, что инструкции ldind.i8 и ldind.u8 являются псевдонимами (имеют один и тот же код). Дело в том, что загрузка любых 64-разрядных целых значений на стек не вызывает их преобразования, ибо хотя на стеке не предусмотрено наличие беззнаковых 64-разрядных значений, их загрузка все равно сводится к простому побитовому копированию. Вышесказанное справедливо также и для 32-разрядных целых значений, но для их загрузки зачем-то зарезервировано сразу две инструкции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x46 | ldind.i1 |
- | Косвенная загрузка значения int8 |
| 0x47 | ldind.u1 |
- | Косвенная загрузка значения unsigned int8 |
| 0x48 | ldind.i2 |
- | Косвенная загрузка значения int16 |
| 0x49 | ldind.u2 |
- | Косвенная загрузка значения unsigned int16 |
| 0x4A | ldind.i4 |
- | Косвенная загрузка значения int32 |
| 0x4B | ldind.u4 |
- | Косвенная загрузка значения unsigned int32 |
| 0x4C | ldind.i8(ldind.u8) |
- | Косвенная загрузка значения int64 и unsigned int64 |
| 0x4D | ldind.i |
- | Косвенная загрузка значения native int |
| 0x4E | ldind.r4 |
- | Косвенная загрузка значения float32 |
| 0x4 | ldind.r8 |
- | Косвенная загрузка значения float64 |
| 0x50 | ldind.ref |
- | Косвенная загрузка объектной ссылки |
Диаграмма стека для инструкций косвенной загрузки выглядит следующим образом:
... , address -> ... , value
Инструкций для косвенного сохранения значений (см. таблицу 3.7) меньше, чем инструкций для косвенной загрузки (можно заметить, что инструкции для сохранения значений беззнаковых целых типов отсутствуют). Причина в том, что сохранение беззнаковых целых ничем не отличается от сохранения знаковых целых.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x51 | stind.ref |
- | Косвенное сохранение объектной ссылки |
| 0x52 | stind.i1 |
- | Косвенное сохранение значения int8 |
| 0x53 | stind.i2 |
- | Косвенное сохранение значения int16 |
| 0x54 | stind.i4 |
- | Косвенное сохранение значения int32 |
| 0x55 | stind.i8 |
- | Косвенное сохранение значения int64 |
| 0x56 | stind.r4 |
- | Косвенное сохранение значения float32 |
| 0x57 | stind.r8 |
- | Косвенное сохранение значения float64 |
| 0xDF | stind.i |
- | Косвенное сохранение значения native int |
Диаграмма стека для инструкций косвенного сохранения выглядит следующим образом:
... , address , value -> ...
В отличие от "железных" стековых процессоров, CLI не содержит развитой системы инструкций для чисто стековых манипуляций. В таблице 3.8 представлены две имеющиеся в наличии инструкции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x25 | dup |
- | Копирование значения на вершине стека: ..., value ->..., value, value |
| 0x26 | pop |
- | Удаление значения с вершины стека . .., value ->... |
Арифметические инструкции можно разделить на четыре категории:
ckfinite, проверяющая конечность значений с плавающей точкой;Бинарные арифметические операции потребляют со стека вычислений два операнда. Соответственно, диаграмма стека для таких операций выглядит следующим образом:
... , value1 , value2 -> ... , result
Действие бинарных операций можно записать как
result := value1 op value2,
то есть например, если op соответствует операции вычитания, то из value1 вычитается value2.
Некоторые бинарные операции могут использоваться для операндов различных типов. Другими словами, в коде инструкции не содержится информации о типах ее операндов, так как эти типы определяются на этапе add можно использовать для сложения как двух целых чисел, так и двух чисел с плавающей запятой. При этом применение бинарной операции не допускается, если тип одного ее операнда - целый, а другого - с плавающей запятой.
Тип результата бинарной операции зависит от типов операндов. Если операнды целые, то и результат будет целый. Если операнды представляют собой
В таблице 3.9 представлены базовые инструкции, выполняющие бинарные операции.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x58 | add |
- | Сложение |
| 0x59 | sub |
- | Вычитание |
| 0x5A | mul |
- | Умножение |
| 0x5B | div |
- | Деление |
| 0x5C | div.un |
- | Деление беззнаковых целых чисел |
| 0x5D | |
- | Остаток от деления |
| 0x5E | |
- | Остаток от деления беззнаковых целых чисел |
| 0x5F | and |
- | Побитовое И |
| 0x60 | or |
- | Побитовое ИЛИ |
| 0x61 | xor |
- | Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ |
Инструкции, представленные в таблице 3.10, используются только для целочисленных операндов. Они отличаются от базовых бинарных операций тем, что осуществляют OverflowException ).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xD6 | add. |
- | Сложение целых чисел со знаком с |
| 0xD7 | add. |
- | Сложение целых чисел без знака с |
| 0xD | mul. |
- | Умножение целых чисел со знаком с |
| 0xD | mul. |
- | Умножение целых чисел без знака с |
| 0xD | sub. |
- | Вычитание целых чисел со знаком с |
| 0xD | sub. |
- | Вычитание целых чисел без знака с |
value1 ) в нужную сторону на количество бит, указанное во втором операнде ( value2 ).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x62 | |
- | Сдвиг целых чисел влево |
| 0x63 | shr |
- | Сдвиг целых чисел со знаком вправо |
| 0x64 | shr.un |
- | Сдвиг целых чисел без знака вправо |
int32 ). Число 0 обозначает ложь, а число 1 - истину.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xFE 0x01 | ceq |
- | Сравнение на равенство. Для целых чисел: I ceq I => 1, иначе Для чисел с плавающей запятой: +inf ceq +inf => 1, -inf ceq -inf => 1, A ceq A => 1, иначе |
| 0xFE 0x02 | cgt |
- | Сравнение на "больше". Для целых чисел: J cgt I => 1, иначе Для чисел с плавающей запятой: A cgt -inf => 1, +inf cgt A => 1, +inf cgt -inf => 1, B cgt A => 1, иначе |
| 0xFE 0x04 | clt |
- | Сравнение на "меньше". Для целых чисел: I clt J => 1, иначе Для чисел с плавающей запятой: A clt +inf => 1, -inf clt A => 1, -inf clt +inf => 1, A clt B => 1, иначе |
| 0xFE 0x03 | cgt.un |
- | Сравнение на "больше" беззнаковых целых чисел или неупорядоченных чисел с плавающей запятой. (Два Для целых чисел: L cgt.un K => 1, иначе Для чисел с плавающей запятой: NaN cgt.un C => 1, C cgt.un NaN => 1, A cgt.un -inf => 1, +inf cgt.un A => 1, +inf cgt.un -inf => 1, B cgt.un A => 1, иначе |
| 0xFE 0x05 | clt.un |
- | Сравнение на "меньше" беззнаковых целых чисел или неупорядоченных чисел с плавающей запятой. Для целых чисел: K clt.un L => 1, иначе Для чисел с плавающей запятой: NaN clt.un C => 1, C clt.un NaN => 1, A clt.un +inf => 1, -inf clt.un A => 1, -inf clt.un +inf => 1, A clt.un B => 1, иначе |
Семантика операций сравнения для чисел с плавающей запятой существенно отличается от их семантики для целых чисел. Дело в том, что +inf (положительная бесконечность), -inf (отрицательная бесконечность) и (Not a Number - не число). Поэтому описание каждой инструкции содержит две части: для целых чисел и для чисел с плавающей запятой. При этом в описании используются следующие обозначения:
I и J - целые числа со знаком, причем I < J ;K и L - целые числа без знака, причем K < L ;A и B - конечные NaN , +inf и -inf ), причем A < B ;C - любое NaN , +inf и -inf ).В таблице 3.13 приведены две инструкции, выполняющие унарные арифметические операции. Диаграмма стека для
... , value -> ... , result
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x65 | neg |
- | Изменение знака числа |
| 0x66 | not |
- | Побитовое НЕ (для целых чисел) |
Инструкция neg применима как для целых чисел, так и для чисел с плавающей запятой и обладает двумя особенностями:
neg использовать sub.ovf .NaN является NaN .Инструкция ckfinite (см. таблицу 3.14) генерирует исключение ArithmeticException, если , +inf или -inf. Если исключение не генерируется, то стек вычислений не меняется, поэтому диаграмма стека выглядит следующим образом:
... , value -> ... , value
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xC3 | ckfinite |
- | Проверка того, что |
Инструкции преобразования значений потребляют один операнд со стека вычислений и преобразуют его к нужному типу. Диаграмма стека для этих инструкций выглядит следующим образом:
... , value -> ... , result
Базовые инструкции преобразования представлены в таблице 3.15. Они обладают следующими особенностями:
float32 может вызывать float32, то результатом преобразования является +inf или -inf.conv.r.un интерпретирует целое значение, лежащее на вершине стека, как не имеющее знака и преобразует его к вещественному типу (либо float32, либо float64 в зависимости от значения).| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x67 | conv.i1 |
- | Преобразовать к int8 |
| 0x68 | conv.i2 |
- | Преобразовать к int16 |
| 0x69 | conv.i4 |
- | Преобразовать к int32 |
| 0x6A | conv.i8 |
- | Преобразовать к int64 |
| 0x6B | conv.r4 |
- | Преобразовать к float32 |
| 0x6C | conv.r8 |
- | Преобразовать к float64 |
| 0x6D | conv.u4 |
- | Преобразовать к unsigned int32 |
| 0x6E | conv.u8 |
- | Преобразовать к unsigned int64 |
| 0x76 | conv.r.un. |
- | Преобразовать беззнаковое целое число в |
| 0xD1 | conv.u2 |
- | Преобразовать к unsigned int16 |
| 0xD2 | conv.u1 |
- | Преобразовать к unsigned int8 |
| 0xD3 | conv.i |
- | Преобразовать к native int |
| 0xE0 | conv.u |
- | Преобразовать к unsigned native int |
В таблице 3.16 приведены инструкции для преобразования значений, имеющих знак, к целым типам с OverflowException.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0xB3 | conv. |
- | Преобразование к int8 |
| 0xB4 | conv. |
- | Преобразование к unsigned int8 |
| 0xB5 | conv. |
- | Преобразование к int16 |
| 0xB6 | conv. |
- | Преобразование к unsigned int16 |
| 0xB7 | conv. |
- | Преобразование к int32 |
| 0xB8 | conv. |
- | Преобразование к unsigned int32 |
| 0xB9 | conv. |
- | Преобразование к int64 |
| 0xBA | conv. |
- | Преобразование к unsigned int64 |
| 0xD4 | conv. |
- | Преобразование к native int |
| 0xD5 | conv. |
- | Преобразование к unsigned native int |
Инструкции, представленные в таблице 3.17, используются для преобразования беззнаковых значений к нужному типу и генерируют исключение OverflowException в случае переполнения.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x82 | conv. |
- | Преобразование к int8 |
| 0x83 | conv. |
- | Преобразование к int16 |
| 0x84 | conv. |
- | Преобразование к int32 |
| 0x85 | conv. |
- | Преобразование к int64 |
| 0x86 | conv. |
- | Преобразование к unsigned int8 |
| 0x87 | conv. |
- | Преобразование к unsigned int16 |
| 0x88 | conv. |
- | Преобразование к unsigned int32 |
| 0x89 | conv. |
- | Преобразование к unsigned int64 |
| 0x8A | conv. |
- | Преобразование к native int |
| 0x8B | conv. |
- | Преобразование к unsigned native int |
Инструкции для организации передачи управления можно разделить на пять категорий:
Существуют две инструкции безусловного перехода (см. таблицу 3.18), которые различаются только разрядностью встроенного операнда ( int8 и int32 ). При этом встроенный операнд этих инструкций обозначает относительное смещение цели перехода.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2B | br.s |
int8 |
Короткий безусловный переход |
| 0x38 | br. |
int32 |
Длинный безусловный переход |
Базовые инструкции условного перехода, приведенные в таблице 3.19, потребляют со стека вычислений один операнд и, в зависимости от его значения, осуществляют или не осуществляют переход по указанному во встроенном операнде относительному адресу. Диаграмма стека для этих инструкций выглядит следующим образом:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2C | brfalse.s |
int8 |
Короткий условный переход, если значение равно 0 или null |
| 0x2D | brtrue.s |
int8 |
Короткий условный переход, если значение не равно 0 или null |
| 0x39 | brfalse |
int32 |
Длинный условный переход, если значение равно 0 или null |
| 0x3A | brtrue |
int32 |
Длинный условный переход, если значение не равно 0 или null |
Как и в случае инструкций безусловного перехода, существуют короткий и длинный варианты инструкций условного перехода, которые отличаются только разрядностью встроенного операнда ( int8 и int32 ).
Инструкции brfalse присвоены два псевдонима: brnull и brzero, имеющих одинаковый с ней код. Аналогично, brfalse.s имеет псевдонимы brnull.s и brzero.s.
Кроме базовых, существуют дополнительные инструкции условного перехода, потребляющие сразу два операнда. Каждая из дополнительных инструкций условного перехода является сокращенной записью последовательности из двух инструкций, первая из которых является бинарной операцией сравнения, а вторая - базовой инструкцией условного перехода.
Таким образом, дополнительные инструкции условного перехода имеют следующую диаграмму стека:
... , value1 , value2 -> ...
Длинные варианты дополнительных инструкций условного перехода перечислены в таблице 3.20, а короткие - в таблице 3.21. В описании каждой инструкции приводится эквивалентная ей комбинация бинарной операции сравнения и базовой инструкции условного перехода. В связи с тем, что семантика операций сравнения для целых чисел существенно отличается от их семантики для чисел с плавающей запятой, для инструкций bge, ble, bge.un и ble.un (и для коротких вариантов этих инструкций) приводится по две эквивалентные им комбинации. Комбинация, помеченная меткой ( int ), используется в случае целых операндов, а комбинация, помеченная меткой ( F ), используется в том случае, если операнды представляют собой
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x3B | beq |
int32 |
ceq; brtrue |
| 0x3C | bge |
int32 |
(int): clt; brfalse (F) : clt.un; brfalse |
| 0x3D | bgt |
int32 |
cgt; brtrue |
| 0x3E | ble |
int32 |
(int): cgt; brfalse (F) : cgt.un; brfalse |
| 0x3F | |
int32 |
clt; brtrue |
| 0x40 | bne.un |
int32 |
ceq; brfalse |
| 0x41 | bge.un |
int32 |
(int): clt.un; brfalse (F) : clt; brfalse |
| 0x42 | bgt.un |
int32 |
cgt.un; brtrue |
| 0x43 | ble.un |
int32 |
(int): cgt.un; brfalse (F) : cgt; brfalse |
| 0x44 | |
int32 |
clt.un; brtrue |
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2E | beq.s |
int8 |
ceq; brtrue.s |
| 0x2F | bge.s |
int8 |
(int): clt; brfalse.s (F) : clt.un; brfalse.s |
| 0x30 | bgt.s |
int8 |
cgt; brtrue.s |
| 0x31 | ble.s |
int8 |
(int): cgt; brfalse.s (F) : cgt.un; brfalse.s |
| 0x32 | |
int8 |
clt; brtrue.s |
| 0x33 | bne.un.s |
int8 |
ceq; brfalse.s |
| 0x34 | bge.un.s |
int8 |
(int): clt.un; brfalse.s (F) : clt; brfalse.s |
| 0x35 | bgt.un.s |
int8 |
cgt.un; brtrue.s |
| 0x36 | ble.un.s |
int8 |
(int): cgt.un; brfalse.s (F) : cgt; brfalse.s |
| 0x37 | |
int8 |
clt.un; brtrue.s |
Инструкция множественного выбора switch представлена в таблице 3.22. Встроенный операнд этой инструкции имеет сложный формат: первое 32-разрядное слово содержит размер N таблицы переходов, после чего следует N 32-рязрядных относительных смещений целей перехода.
Инструкция switch берет со стека вычислений один операнд. Обозначим этот операнд через I. Значение I интерпретируется как целое число без знака и сравнивается с N. Если I < N, то управление передается на I-тую цель в таблице переходов (нумерация целей осуществляется с 0). Если I >= N, то управление передается на инструкцию, непосредственно следующую за инструкцией switch.
Для инструкции switch можно записать следующую диаграмму стека:
... , value -> ...
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x45 | switch |
unsigned int32, int32,... int32, |
Осуществляет переход по таблице переходов в соответствии со значением на вершине стека |
Инструкция call (см. таблицу 3.23) выполняет вызов метода, который указан во встроенном операнде инструкции. Встроенный операнд представляет собой call может стоять префикс .tail, который говорит о том, что состояние текущего метода должно быть освобождено перед передачей управления вызываемому методу (хвостовой вызов).
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x28 | call |
token |
Выполняет вызов метода |
Информация о методе, на которую указывает call (по сравнению с инструкцией callvirt, которую мы будем рассматривать далее в этой главе) является то, что адрес вызываемого метода вычисляется статически, то есть еще во время
Параметры вызываемого метода должны быть расположены в стеке слева направо, то есть сначала на стек должен быть загружен первый параметр, затем второй и т.д. При вызове экземплярного метода в качестве первого параметра должна выступать объектная ссылка (параметр this ).
Если вызываемый метод возвращает значение, то оно загружается на стек вызывающего метода.
Инструкция (см. таблицу 3.24) осуществляет возврат из метода. Если метод возвращает значение, то оно должно быть загружено на вершину стека вычислений.
| Код | Инструкция | Встроенный операнд | Описание |
|---|---|---|---|
| 0x2A | |
- | Осуществляет возврат из метода |
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.