Daniel_Cortez
31.12.2017, 15:49
Всем привет.
Начиная с версии 3.10.5 в компиляторе Pawn появился оператор __emit. В данной статье я постараюсь рассказать обо всех особенностях этого оператора. За основу будет взят материал из подготовленного мной ранее описания оператора __emit на GitHub (1 (https://github.com/Zeex/pawn/pull/180#issuecomment-323531746), 2 (https://github.com/Zeex/pawn/pull/211#issue-275106280)).
В отличие от директивы #emit, оператор __emit представляет собой более продуманную реализацию встроенного ассемблера для Pawn.
Основные преимущества оператора перед директивой:
2 варианта синтаксиса (одиночный и блоковый).
Возможность применения в макросах и выражениях.
Проверка типов операндов в инструкциях.
Выражения в операндах инструкций.
Возможность указать метку, объявленную после блока __emit.
Возможность указать 2 и более операндов в инструкции.
Автозамена макроинструкций на обычные инструкции.
Универсальные псевдоинструкции.
Варианты синтаксиса
Оператор __emit имеет два варианта синтаксиса: одиночный и блоковый.
Пример одиночного синтаксиса:
__emit const.pri 1;
Здесь всё просто: разрешается только одна инструкция на предложение (под предложением имеется в виду всё то, что идёт до знака ";").
Для внедрения в код более одной инструкции AMX следует использовать либо блоковый синтаксис, либо несколько одиночных предложений __emit:
new a = 4, b = 5;
// Сделаем так, чтобы переменные "a" и "b" обменялись значениями
__emit load.s.pri a;
__emit load.s.alt b;
__emit stor.s alt a;
__emit stor.s.pri b;
// Теперь a = 5, b = 4
С помощью блокового синтаксиса возможно внедрение любого числа инструкций AMX, однако данный синтаксис разделяется на 2 подвида: с круглыми скобками и с фигурными.
В синтаксисе с фигурными скобками на одну строку должна приходиться только одна инструкция.
Примеры:
__emit { const.pri = 1 }
new a = 4, b = 5;
__emit
{
load.s.pri a
load.s.alt b
stor.s.pri b
stor.s.alt a
}
__emit
{
// Да, под "любым числом" я имел в виду не только 1 и более инструкций,
// но и их полное отсутствие тоже.
}
__emit {}
Данный вариант синтаксиса хорошо подходит для простых ассемблерных вставок, как с директивой #emit.
Также есть ещё один вариант блокового синтаксиса, с круглыми скобками.
__emit( push.alt, stack 0, move.pri, pop.alt );
Обратите внимание: в этом варианте не требуется использовать только одну инструкцию на строку, а сами инструкции разделяются между собой запятыми.
Применение в макросах и выражениях
Поскольку __emit является оператором, в отличие от директивы #emit, его можно использовать в макросах и выражениях.
Поскольку у каждого выражения есть результат, будучи в выражении, оператор __emit возвращает значение, которое было в регистре PRI после выполнения последней инструкции AMX в блоке.
new x = __emit( const.pri 1 ); // x = 1
Можно соединять несколько подвыражений __emit в цепочку, разделяя их запятыми, как с вызовами функций:
new x = (__emit stack 0, __emit move.pri);
Благодаря этому можно внедрить в выражение более чем 1 инструкцию AMX.
Но удобнее в таких случаях использовать блоковый синтаксис с круглыми скобками:
new x = __emit( stack 0, move.pri );
Кроме того, можно объединить в цепочку и __emit с круглыми скобками:
__emit nop, __emit( stack 0, move.pri ), __emit( nop, nop );
__emit с одиночным синтаксисом вполне подходит для использования в макросах:
#define DoSomething() __emit const.pri 1
Также в макросах можно указать несколько инструкций AMX с помощью блокового синтаксиса, однако рекомендуется именно вариант с круглыми скобками.
Причину такого выбора проще рассмотреть на примере:
#define DoSomething() __emit { nop }
Вариант с фигурными скобками требует ставить только одну инструкцию на строку. В то же время всё содержимое макроса понимается компилятором как одна строка, поэтому больше 1 инструкции в __emit с фигурными скобками не укажешь.
Мало того, такой вариант __emit нельзя использовать в выражениях, чего может крайне не хватать для макросов, маскирующихся под функции.
Другое дело - вариант с круглыми скобками:
#define GetFreeStackSpace() ( \
__emit( \
lctrl 4, \
heap 0, \
sub \
) \
)
main()
{
// Здесь значение, возвращаемое последним блоком __emit,
// используется как аргумент функции printf().
printf("Free stack/heap space: %d", GetFreeStackSpace());
// И так тоже можно, возвращаемое значение останется неиспользованным.
GetFreeStackSpace();
}
Данный вариант синтаксиса специально задумывался так, чтобы быть похожим на вызов функции, благодаря чему он идеально подходит для написания макросов.
Проверка операндов инструкций
В отличие от одноимённой директивы, оператор __emit проверяет тип и количество операндов у каждой указанной в нём инструкции. К примеру, операндом инструкции перехода (JUMP, JZER, JNZ и пр.) может быть только название метки, иначе компилятор выдаст ошибку.
new x = 1;
__emit jump x; // error 019: not a label: "x"
__emit jump 0; // error 001: expected token "-label-", but found "-integer value-"
label1:
__emit jump label1; // ok
Помимо меток для инструкций перехода, проверяются и другие типы значений:
main()
{
new x = 1;
static y = 1;
// Инструкция load.pri загружает значение из глобальной переменной (т.е. из секции данных),
// а load.s.pri - из локальной (по смещению относительно фрейма стека).
// Оператор __emit контролирует это и помогает скриптеру не запутаться
__emit load.pri x; // error 001: expected token: "-data offset-", but found "-local variable-"
__emit load.s.pri y; // error 001: expected token: "-local variable-", but found "-data offset-"
__emit load.s.pri x; // ok
__emit load.pri y; // ok
}
Кроме того, с оператором __emit компилятор знает, сколько операндов должно быть у той или иной инструкции AMX, и если обнаружит несоответствие, то обязательно укажет вам на него. К примеру, в инструкции const.pri требуется только 1 операнд, поэтому компилятор не позволит вам использовать эту инструкцию без операнда:
// С директивой #emit данный неправильный код скомпилируется
// и, скорее всего, приведёт к генерации невалидного байткода AMX
// (сервер просто откажется загружать такой скрипт).
#emit const.pri
// Но с оператором __emit компилятор выдаст ошибку, поскольку требуется операнд (константное значение).
__emit const.pri; // error 001: expected token: "-numeric value-", but found ";"
__emit const.pri 0; // ok (целое число)
__emit const.alt cellmax; // ok (встроенная константа cellmax)
Трудноуловимые ошибки с лишними операндами тоже отлавливаются на раз-два:
__emit const.pri 1;
__emit add 2; // error 001: expected token: ";", but found "-integer value-"
// Суть ошибки выше: лишний операнд в инструкции 'add'
// (для прибавления константного значения используется опкод 'add.c').
Различаются следующие типы данных:
НазваниеОписаниеПример(ы)
Целое число
Любое целочисленное значение
__emit lctrl 6;
__emit jrel (cellbits / charbits * 2);
Данные
Глобальная переменная, либо статическая (static) локальная переменная
static x = 0;
__emit load.pri x;
Локальная переменная
Локальная (расположенная в стеке) переменная или аргумент функции
SomeFunction(x)
{
new y;
__emit load.s.pri x;
__emit load.s.alt y;
}
Метка
Любая метка, на которую можно совершить переход (в т.ч. объявленная внутри блока __emit{})
label1:
__emit jnz label1;
Pawn-функция
Функция, реализованная на языке Pawn
__emit call MyFunction;
Нативная функция
Встроенная функция, предоставляемая интерпретатором
__emit sysreq.c SendClientMessage;
Любое значение
Значение любого из перечисленных выше типов
new x;
__emit {
label1:
label2:
const.pri label1
eq.c.pri label2
add.c x
}
Помимо перечисленных выше типов данных есть также дополнительные типы, характерные для специфических инструкций:
Неотрицательное число
Любое целое неотрицательное число
__emit cmps (cellbits / charbits * 128);
Величина сдвига
Величина битового сдвига (целое число от 0 до 31)
__emit shl.c.pri 3; // PRI = PRI << 3
Размер данных
Количество байт для данных, загружаемых/сохраняемых при помощи lodb.i/strb.i (1, 2 или 4)
__emit lodb.i 2; // PRI = [PRI] (2 байта)
Выражения в операндах инструкций
Оператор __emit позволяет для аргументов типа "любое значение" использовать выражения.
В таких выражениях нельзя использовать переменные и вызовы функций, т.к. результатом должно быть константное значение.
Пример:
__emit const.pri (MAX_PLAYERS + 1);
Обратите внимание: выражение должно быть взято в круглые скобки - это нужно, чтобы компилятор мог правильно отличить выражение от одиночного аргумента.
Кроме сложения ("+") в выражениях внутри __emit можно использовать любые другие виды операций, которые доступны в обычных выражениях.
__emit const.pri (((MAX_PLAYERS / 2) & (cellbits / charbits - 1)) * 2 >>> 1);
Возможность указать метку, объявленную после блока __emit
Здесь всё просто: с помощью директивы #emit возможно использование инструкций перехода только с метками, объявленными выше места использования, когда с оператором __emit можно делать переходы и на метки, объявленные ниже.
#emit jump label1 // error 017: undefined symbol "label1"
label1:
__emit jump label1; // ok
label1:
Возможность указать 2 и более операндов в инструкции
В директиве #emit инструкции могут быть либо только с одним операндом, либо без операндов. В свою очередь, оператор __emit знает точное количество аргументов для каждой инструкции, а потому с ним можно указать любое количество операндов для инструкции (при условии, что это количество правильное). Благодаря этому становится возможным использование макроинструкций, которые предусматривают наличие 2 и более операндов.
Рассмотрим это на примере того же обмена значениями между переменными:
new a = 4, b = 5;
__emit
{
load.s.both a, b // вместо цепочки 'load.s.pri a\ load.s.alt b' используем макроинструкцию load.s.both
stor.s.alt a // для stor.s.pri/alt нет аналогичной макроинструкции,
stor.s.pri b // поэтому используем их "как есть"
}
Кто-то скажет, что данная возможность будет бесполезной для пользователей SA-MP, т.к. на сервере нельзя использовать макроинструкции из-за более старой версии интерпретатора Pawn.
И это могло бы быть правдой, если не ещё одна фича, описанная ниже.
Автозамена макроинструкций на обычные инструкции
Если в операторе __emit используются макроопкоды, то при компиляции кода без макрооптимизаций (т.е. с ключами "-O0", "-O1" или "-d2") макроопкоды будут автоматически заменены на обычные опкоды.
Например, макроопкод 'push5.c' будет заменён на последовательность из пяти 'push.c', а 'sysreq.n' - на 'push.c \ sysreq.c \ stack'.
Пример:
static const msg[] = "Hi there";
__emit
{
// SendClientMessage(0, 0xFFFFFFFF, msg);
push3.c msg 0xFFFFFFFF 0
sysreq.n SendClientMessage (3 * cellbits / charbits)
}
При компиляции с флагом -d2 получится следующий байткод:
push.c 00000000
push.c ffffffff
push.c 00000000
push.c 0000000c
sysreq.c 00000000 ; SendClientMessage
stack 00000010
Универсальные псевдоинструкции
Начиная с версии компилятора 3.10.10 появились так называемые "универсальные" псевдоинструкции, которые в зависимости от операнда компилируются в разные инструкции AMX.
Пример:
SomeFunction()
{
new local_var;
static static_local_var;
const CONSTANT_VALUE = 1;
__emit load.u.pri local_var; // Скомпилируется в "load.s.pri local_var"
__emit load.u.pri static_local_var; // => "load.pri static_local_var"
__emit load.u.pri CONSTANT_VALUE; // => "const.pri CONSTANT_VALUE"
}
Основное назначение таких псевдоинструкций - использование в макросах, чтобы обрабатывать любые операнды, указанные пользователем:
#define increase(%0) __emit(inc.u %0) // Увеличивает значение переменной на 1
new global_var = 0;
new global_array[2];
main()
{
new local_var = 0;
static local_static_var = 0;
static local_static_array[2];
increase(global_var);
increase(global_array[1]);
increase(local_var);
increase(local_static_var);
increase(local_static_array[1]);
}
Реализованы следующие псевдоинструкции:
НазваниеТип операндаЗатирает значение в PRI/ALT?
load.u.pri/altГлобальная переменная-
Локальная переменная-
Константное значение-
Ссылка-
Ячейка массиваPRI, ALT
stor.u.pri/altГлобальная переменная-
Локальная переменная-
Ссылка-
Ячейка массиваPRI, ALT
addr.u.pri/altГлобальная переменная-
Локальная переменная-
Ссылка-
Ячейка массиваPRI, ALT
push.uГлобальная переменная-
Локальная переменная-
Константное значение-
СсылкаPRI
Ячейка массиваPRI, ALT
push.adr.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
zero.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
inc.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
dec.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
Заключение
Оператор __emit изначально создавался с расчётом на расширяемость и безопасность: он снабжён множеством полезного функционала, обеспечивает больший контроль над кодом и защиту от ошибок, продолжает пополняться новым функционалом с каждым новым релизом компилятора, и в целом подходит для общего пользования гораздо больше, чем директива #emit, которую с наличием оператора впору считать устаревшей.
Автор статьи: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)
Благодарности:
VVWVV (http://pro-pawn.ru/member.php?4348-VVWVV) - изначальная реализация оператора __emit. Собственно, благодаря его работе и существует данная статья.
Y_Less (https://github.com/Y-Less), Slice (https://github.com/oscar-broman), Southclaws (https://github.com/Southclaws) - предложения по названию оператора и "обновлённому" синтаксису.
Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!
Начиная с версии 3.10.5 в компиляторе Pawn появился оператор __emit. В данной статье я постараюсь рассказать обо всех особенностях этого оператора. За основу будет взят материал из подготовленного мной ранее описания оператора __emit на GitHub (1 (https://github.com/Zeex/pawn/pull/180#issuecomment-323531746), 2 (https://github.com/Zeex/pawn/pull/211#issue-275106280)).
В отличие от директивы #emit, оператор __emit представляет собой более продуманную реализацию встроенного ассемблера для Pawn.
Основные преимущества оператора перед директивой:
2 варианта синтаксиса (одиночный и блоковый).
Возможность применения в макросах и выражениях.
Проверка типов операндов в инструкциях.
Выражения в операндах инструкций.
Возможность указать метку, объявленную после блока __emit.
Возможность указать 2 и более операндов в инструкции.
Автозамена макроинструкций на обычные инструкции.
Универсальные псевдоинструкции.
Варианты синтаксиса
Оператор __emit имеет два варианта синтаксиса: одиночный и блоковый.
Пример одиночного синтаксиса:
__emit const.pri 1;
Здесь всё просто: разрешается только одна инструкция на предложение (под предложением имеется в виду всё то, что идёт до знака ";").
Для внедрения в код более одной инструкции AMX следует использовать либо блоковый синтаксис, либо несколько одиночных предложений __emit:
new a = 4, b = 5;
// Сделаем так, чтобы переменные "a" и "b" обменялись значениями
__emit load.s.pri a;
__emit load.s.alt b;
__emit stor.s alt a;
__emit stor.s.pri b;
// Теперь a = 5, b = 4
С помощью блокового синтаксиса возможно внедрение любого числа инструкций AMX, однако данный синтаксис разделяется на 2 подвида: с круглыми скобками и с фигурными.
В синтаксисе с фигурными скобками на одну строку должна приходиться только одна инструкция.
Примеры:
__emit { const.pri = 1 }
new a = 4, b = 5;
__emit
{
load.s.pri a
load.s.alt b
stor.s.pri b
stor.s.alt a
}
__emit
{
// Да, под "любым числом" я имел в виду не только 1 и более инструкций,
// но и их полное отсутствие тоже.
}
__emit {}
Данный вариант синтаксиса хорошо подходит для простых ассемблерных вставок, как с директивой #emit.
Также есть ещё один вариант блокового синтаксиса, с круглыми скобками.
__emit( push.alt, stack 0, move.pri, pop.alt );
Обратите внимание: в этом варианте не требуется использовать только одну инструкцию на строку, а сами инструкции разделяются между собой запятыми.
Применение в макросах и выражениях
Поскольку __emit является оператором, в отличие от директивы #emit, его можно использовать в макросах и выражениях.
Поскольку у каждого выражения есть результат, будучи в выражении, оператор __emit возвращает значение, которое было в регистре PRI после выполнения последней инструкции AMX в блоке.
new x = __emit( const.pri 1 ); // x = 1
Можно соединять несколько подвыражений __emit в цепочку, разделяя их запятыми, как с вызовами функций:
new x = (__emit stack 0, __emit move.pri);
Благодаря этому можно внедрить в выражение более чем 1 инструкцию AMX.
Но удобнее в таких случаях использовать блоковый синтаксис с круглыми скобками:
new x = __emit( stack 0, move.pri );
Кроме того, можно объединить в цепочку и __emit с круглыми скобками:
__emit nop, __emit( stack 0, move.pri ), __emit( nop, nop );
__emit с одиночным синтаксисом вполне подходит для использования в макросах:
#define DoSomething() __emit const.pri 1
Также в макросах можно указать несколько инструкций AMX с помощью блокового синтаксиса, однако рекомендуется именно вариант с круглыми скобками.
Причину такого выбора проще рассмотреть на примере:
#define DoSomething() __emit { nop }
Вариант с фигурными скобками требует ставить только одну инструкцию на строку. В то же время всё содержимое макроса понимается компилятором как одна строка, поэтому больше 1 инструкции в __emit с фигурными скобками не укажешь.
Мало того, такой вариант __emit нельзя использовать в выражениях, чего может крайне не хватать для макросов, маскирующихся под функции.
Другое дело - вариант с круглыми скобками:
#define GetFreeStackSpace() ( \
__emit( \
lctrl 4, \
heap 0, \
sub \
) \
)
main()
{
// Здесь значение, возвращаемое последним блоком __emit,
// используется как аргумент функции printf().
printf("Free stack/heap space: %d", GetFreeStackSpace());
// И так тоже можно, возвращаемое значение останется неиспользованным.
GetFreeStackSpace();
}
Данный вариант синтаксиса специально задумывался так, чтобы быть похожим на вызов функции, благодаря чему он идеально подходит для написания макросов.
Проверка операндов инструкций
В отличие от одноимённой директивы, оператор __emit проверяет тип и количество операндов у каждой указанной в нём инструкции. К примеру, операндом инструкции перехода (JUMP, JZER, JNZ и пр.) может быть только название метки, иначе компилятор выдаст ошибку.
new x = 1;
__emit jump x; // error 019: not a label: "x"
__emit jump 0; // error 001: expected token "-label-", but found "-integer value-"
label1:
__emit jump label1; // ok
Помимо меток для инструкций перехода, проверяются и другие типы значений:
main()
{
new x = 1;
static y = 1;
// Инструкция load.pri загружает значение из глобальной переменной (т.е. из секции данных),
// а load.s.pri - из локальной (по смещению относительно фрейма стека).
// Оператор __emit контролирует это и помогает скриптеру не запутаться
__emit load.pri x; // error 001: expected token: "-data offset-", but found "-local variable-"
__emit load.s.pri y; // error 001: expected token: "-local variable-", but found "-data offset-"
__emit load.s.pri x; // ok
__emit load.pri y; // ok
}
Кроме того, с оператором __emit компилятор знает, сколько операндов должно быть у той или иной инструкции AMX, и если обнаружит несоответствие, то обязательно укажет вам на него. К примеру, в инструкции const.pri требуется только 1 операнд, поэтому компилятор не позволит вам использовать эту инструкцию без операнда:
// С директивой #emit данный неправильный код скомпилируется
// и, скорее всего, приведёт к генерации невалидного байткода AMX
// (сервер просто откажется загружать такой скрипт).
#emit const.pri
// Но с оператором __emit компилятор выдаст ошибку, поскольку требуется операнд (константное значение).
__emit const.pri; // error 001: expected token: "-numeric value-", but found ";"
__emit const.pri 0; // ok (целое число)
__emit const.alt cellmax; // ok (встроенная константа cellmax)
Трудноуловимые ошибки с лишними операндами тоже отлавливаются на раз-два:
__emit const.pri 1;
__emit add 2; // error 001: expected token: ";", but found "-integer value-"
// Суть ошибки выше: лишний операнд в инструкции 'add'
// (для прибавления константного значения используется опкод 'add.c').
Различаются следующие типы данных:
НазваниеОписаниеПример(ы)
Целое число
Любое целочисленное значение
__emit lctrl 6;
__emit jrel (cellbits / charbits * 2);
Данные
Глобальная переменная, либо статическая (static) локальная переменная
static x = 0;
__emit load.pri x;
Локальная переменная
Локальная (расположенная в стеке) переменная или аргумент функции
SomeFunction(x)
{
new y;
__emit load.s.pri x;
__emit load.s.alt y;
}
Метка
Любая метка, на которую можно совершить переход (в т.ч. объявленная внутри блока __emit{})
label1:
__emit jnz label1;
Pawn-функция
Функция, реализованная на языке Pawn
__emit call MyFunction;
Нативная функция
Встроенная функция, предоставляемая интерпретатором
__emit sysreq.c SendClientMessage;
Любое значение
Значение любого из перечисленных выше типов
new x;
__emit {
label1:
label2:
const.pri label1
eq.c.pri label2
add.c x
}
Помимо перечисленных выше типов данных есть также дополнительные типы, характерные для специфических инструкций:
Неотрицательное число
Любое целое неотрицательное число
__emit cmps (cellbits / charbits * 128);
Величина сдвига
Величина битового сдвига (целое число от 0 до 31)
__emit shl.c.pri 3; // PRI = PRI << 3
Размер данных
Количество байт для данных, загружаемых/сохраняемых при помощи lodb.i/strb.i (1, 2 или 4)
__emit lodb.i 2; // PRI = [PRI] (2 байта)
Выражения в операндах инструкций
Оператор __emit позволяет для аргументов типа "любое значение" использовать выражения.
В таких выражениях нельзя использовать переменные и вызовы функций, т.к. результатом должно быть константное значение.
Пример:
__emit const.pri (MAX_PLAYERS + 1);
Обратите внимание: выражение должно быть взято в круглые скобки - это нужно, чтобы компилятор мог правильно отличить выражение от одиночного аргумента.
Кроме сложения ("+") в выражениях внутри __emit можно использовать любые другие виды операций, которые доступны в обычных выражениях.
__emit const.pri (((MAX_PLAYERS / 2) & (cellbits / charbits - 1)) * 2 >>> 1);
Возможность указать метку, объявленную после блока __emit
Здесь всё просто: с помощью директивы #emit возможно использование инструкций перехода только с метками, объявленными выше места использования, когда с оператором __emit можно делать переходы и на метки, объявленные ниже.
#emit jump label1 // error 017: undefined symbol "label1"
label1:
__emit jump label1; // ok
label1:
Возможность указать 2 и более операндов в инструкции
В директиве #emit инструкции могут быть либо только с одним операндом, либо без операндов. В свою очередь, оператор __emit знает точное количество аргументов для каждой инструкции, а потому с ним можно указать любое количество операндов для инструкции (при условии, что это количество правильное). Благодаря этому становится возможным использование макроинструкций, которые предусматривают наличие 2 и более операндов.
Рассмотрим это на примере того же обмена значениями между переменными:
new a = 4, b = 5;
__emit
{
load.s.both a, b // вместо цепочки 'load.s.pri a\ load.s.alt b' используем макроинструкцию load.s.both
stor.s.alt a // для stor.s.pri/alt нет аналогичной макроинструкции,
stor.s.pri b // поэтому используем их "как есть"
}
Кто-то скажет, что данная возможность будет бесполезной для пользователей SA-MP, т.к. на сервере нельзя использовать макроинструкции из-за более старой версии интерпретатора Pawn.
И это могло бы быть правдой, если не ещё одна фича, описанная ниже.
Автозамена макроинструкций на обычные инструкции
Если в операторе __emit используются макроопкоды, то при компиляции кода без макрооптимизаций (т.е. с ключами "-O0", "-O1" или "-d2") макроопкоды будут автоматически заменены на обычные опкоды.
Например, макроопкод 'push5.c' будет заменён на последовательность из пяти 'push.c', а 'sysreq.n' - на 'push.c \ sysreq.c \ stack'.
Пример:
static const msg[] = "Hi there";
__emit
{
// SendClientMessage(0, 0xFFFFFFFF, msg);
push3.c msg 0xFFFFFFFF 0
sysreq.n SendClientMessage (3 * cellbits / charbits)
}
При компиляции с флагом -d2 получится следующий байткод:
push.c 00000000
push.c ffffffff
push.c 00000000
push.c 0000000c
sysreq.c 00000000 ; SendClientMessage
stack 00000010
Универсальные псевдоинструкции
Начиная с версии компилятора 3.10.10 появились так называемые "универсальные" псевдоинструкции, которые в зависимости от операнда компилируются в разные инструкции AMX.
Пример:
SomeFunction()
{
new local_var;
static static_local_var;
const CONSTANT_VALUE = 1;
__emit load.u.pri local_var; // Скомпилируется в "load.s.pri local_var"
__emit load.u.pri static_local_var; // => "load.pri static_local_var"
__emit load.u.pri CONSTANT_VALUE; // => "const.pri CONSTANT_VALUE"
}
Основное назначение таких псевдоинструкций - использование в макросах, чтобы обрабатывать любые операнды, указанные пользователем:
#define increase(%0) __emit(inc.u %0) // Увеличивает значение переменной на 1
new global_var = 0;
new global_array[2];
main()
{
new local_var = 0;
static local_static_var = 0;
static local_static_array[2];
increase(global_var);
increase(global_array[1]);
increase(local_var);
increase(local_static_var);
increase(local_static_array[1]);
}
Реализованы следующие псевдоинструкции:
НазваниеТип операндаЗатирает значение в PRI/ALT?
load.u.pri/altГлобальная переменная-
Локальная переменная-
Константное значение-
Ссылка-
Ячейка массиваPRI, ALT
stor.u.pri/altГлобальная переменная-
Локальная переменная-
Ссылка-
Ячейка массиваPRI, ALT
addr.u.pri/altГлобальная переменная-
Локальная переменная-
Ссылка-
Ячейка массиваPRI, ALT
push.uГлобальная переменная-
Локальная переменная-
Константное значение-
СсылкаPRI
Ячейка массиваPRI, ALT
push.adr.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
zero.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
inc.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
dec.uГлобальная переменная-
Локальная переменная-
СсылкаPRI
Ячейка массиваPRI, ALT
Заключение
Оператор __emit изначально создавался с расчётом на расширяемость и безопасность: он снабжён множеством полезного функционала, обеспечивает больший контроль над кодом и защиту от ошибок, продолжает пополняться новым функционалом с каждым новым релизом компилятора, и в целом подходит для общего пользования гораздо больше, чем директива #emit, которую с наличием оператора впору считать устаревшей.
Автор статьи: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)
Благодарности:
VVWVV (http://pro-pawn.ru/member.php?4348-VVWVV) - изначальная реализация оператора __emit. Собственно, благодаря его работе и существует данная статья.
Y_Less (https://github.com/Y-Less), Slice (https://github.com/oscar-broman), Southclaws (https://github.com/Southclaws) - предложения по названию оператора и "обновлённому" синтаксису.
Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!