#emit, __emit, @emit - Printable Version + open.mp forum (https://forum.open.mp) -- Forum: SA-MP (https://forum.open.mp/forumdisplay.php?fid=3) --- Forum: Tutorials (https://forum.open.mp/forumdisplay.php?fid=37) --- Thread: #emit, __emit, @emit (/showthread.php?tid=2320) |
#emit, __emit, @emit - Y_Less - 2022-09-18 Between the latest versions of the compiler and the amx_assembly library, there are now three versions of emit - #emit, __emit, and @emit.? They all have subtly different uses and quirks, and these differences are explained here. #emit
This is the original version, and is used to insert assembly (p-code) in to a script directly, exactly at the point it is used.? For example: pawn Wrote:Function() This function will be compiled with two return instructions - one from RETN and one from return, and the second one will never be hit.? The generated assembly will look something like: asm Wrote:PROC This is the most inflexible version - it just puts exactly what you typed exactly where you typed it. It is also in a very strange part of the compiler, likely because it was only originally intended for basic debugging of the pawn VM (hence why it was removed in later official releases).? It uses barely any of the standard compiler features: pawn Wrote:#emit CONST.pri 5 // This works. Thus you will often see negative numbers written in their full hex representation (for some reason hex does work despite the fact that negative numbers don?t): pawn Wrote:#emit CONST.pri 0xFFFFFFFB // -5 Defines don?t work to abstract that, but const does: pawn Wrote:#define MINUS_5 0xFFFFFFFB pawn Wrote:const MINUS_5 = -5; And #if is completely ignored: pawn Wrote:#if TEST Very unhelpfully generates: asm Wrote:CONST.pri 5 Both branches are used, so you will often see functions with assembly at the end of a file, ommitted with #endinput: pawn Wrote:#if !TEST Or in a separate file altogether. In short #emit is very weird, but everything else has been built from it so there?s a lot to thank it for. __emit
This is #emit, but super-powered.? It is a full expression-level version of #emit correctly integrated in to the compiler.? That means you can use full compile-time expressions: pawn Wrote:__emit(CONST.pri (5 * MAX_PLAYERS)); You can use it in defines: pawn Wrote:#define GetCurrentAddress() __emit(LCTRL 6) And you can use it in expressions: pawn Wrote:new var = __emit(CONST.pri 6); Where the register pri is always the result of __emit returned like a normal expression.? Compared to the old version: pawn Wrote:new var = 0; It also adds .U, which tries to work out which instruction to use based on the parameter.? For example with #emit incrementing a global variable is: pawn Wrote:#emit INC var While incrementing a local variable is: pawn Wrote:#emit INC.S var With __emit these become: pawn Wrote:__emit(INC.U var); And the compiler works out which instruction to use based on the scope of var. There is more that __emit can do, like including multiple instructions in one expression: pawn Wrote:new var = __emit(LOAD.S.pri param, ADD.C 5); // new var = param 5; But it is still fundamentally the same as #emit in one important way - it is done at compile-time by the compiler.? Everything is inserted in to the AMX at a fixed location (bearing in mind that macros can change this location). @emit
This is a macro, and purely a run-time operation.? The simplest way to see what it does is to remove the macro itself and look at the underlying function calls.? A context is created, which includes an address in the AMX, and instructions are written to that address one-by-one while the server is running.? This is a very easy way to write self-modifying code: pawn Wrote:ReturnFive() This code creates a context, points it to the given function to rewrite it, and makes the buffer big enough to hold four cells.? Note that this code is in OnCodeInit, which is a special callback called before the mode starts, and before the JIT plugin compiles the mode.? All code rewriting must be done before JIT initialisation.? Note also that because OnCodeInit is called so early you can?t use most useful YSI features like hook and foreach - it too is generating its own code at this point.? We then rewrite the function at that address in assembly: pawn Wrote:? ? // A function starts with `PROC`. @emit is just a clever macro that wraps all of these confusing function calls.? While they are fundamentally how code is rewritten at run-time, there?s a lot of boilerplate there that obscures what code is being generated.? So if we call the context ctx (this is important as it is hard-coded in to the macros).? The @emit macro is something like: pawn Wrote:#define @emit%0%1 AsmEmit%0(ctx, %1); It isn?t exactly that, because that?s not a valid macro, but it shows what is happening.? Thus the code becomes: pawn Wrote:ReturnFive() So if you see @emit look around for ctx and that?s where the instructions are being written to in memory.? You can also use labels, but if you want to use variables in the generated code, and not to generate the code, you need to explicitly get their address: pawn Wrote:// return var ? 0 : 1; |