New version of memset (
several previous versions). This function takes an array and sets every element of that array to a given value (0 by default):
pawn Code:
new a[10];
memset(a); // "a" all 0.
memset(a, 4); // "a" all 4.
memset(a, 10, sizeof (a) / 2); // Half of "a" 10.
Slice wrote a version which used the "FILL" PAWN OpCode to do this, which was quite nice and fast, but relied on some run-time code to figure out where the "FILL" command was and change the size parameter. This version uses
ZeeX's excellent "amx" library to actually generate a whole new function! The first time "memset" is called it rewrites itself to a stupidly fast version then calls that version; all future times that it is called it is already the fast version so that just gets executed.
To add to the "meta-ness" of the code. The code which is written is self-modifying code - in short I've written code to write code to write code! I said that Slice's version calculates the location of "FILL" every time it is called, and this is required because "FILL" doesn't take a variable it only takes a constant that needs to be changed. This version instead calculates in advance where the "FILL" instruction is and hard-codes that address in to the generated function.
However, you don't really need to worry about ANY of that, all you need to know is how to call it (above). I should note that the parameter order has CHANGED since my last version to a far more sensible order!
For those interested, this is what the final version of the function looks like. There are actually two versions written. "rawMemset" is the version that takes an address and a byte count, "memset" takes an array and a cell slot count so both get rewritten.
pawn Code:
#emit PROC // Required, at the start of ALL functions.
#emit LOAD.S.pri size
#emit CONST.alt 0xFFFFFFFC
#emit AND // size &= ~3;
#emit STOR.pri <FILL parameter address> // What Slice's version calculates.
#emit LOAD.S.alt iValue
#emit LOAD.S.pri iAddress
#emit FILL 0 // We change the value of "0".
#emit LOAD.pri <FILL parameter address> // Return the bytes written.
#emit RETN // End.
Here is the actual code:
pawn Code:
#tryinclude "..\amx\asm"#include "amx\asm"/**--------------------------------------------------------------------------**\
<summary>
memset
rawMemset
</summary>
<param name="arr[], iAddress">Array or address to set to a value.</param>
<param name="iValue">What to set the cells to.</param>
<param name="iSize">Number of cells to fill.</param>
<returns>
-
</returns>
<remarks>
Based on code by Slice:
https://sampforum.blast.hk/showthread.ph...pid1606781
Modified to use binary flags instead of a loop.
"memset" takes an array, the size of the array, and a value to fill it with
and sets the whole array to that value.
"rawmemset" is similar, but takes an AMX data segment address instead and
the size is in bytes, not cells. However, the size must still be a multiple
of 4.
</remarks>
\**--------------------------------------------------------------------------**/stock memset
(arr
[], val
= 0, size
= sizeof (arr
)){ new addr;
#emit LOAD.S.pri arr #emit STOR.S.pri addr // Convert the size from cells to bytes. return rawMemset
(addr, val, size
* 4);
}stock rawMemset
(iAddress
/* 12 */, iValue
/* 16 */, iSize
/* 20 */){ // They are really, trust me! #pragma unused iAddress, iSize, iValue // The first time this is called it rewrites itself. Any other times it is // called it just uses the new code. This is like doing: // // static // bInitialised = false; // if (!bInitialised) // { // // Do something // bInitialised = true; // } // // Do rest. // // But better (though FAR more complex). // There is NO checking here that we don't write the function bigger than // the space available, or even that we don't overwrite "CIP", which would // be bad. The only way to make sure that doesn't happen is write a little // with a lot of code! new base,
ctx
[AsmContext
];
// Get this function. #emit CONST.pri rawMemset #emit LOAD.alt AMX_HEADER_COD #emit ADD #emit STOR.S.pri base #define _ASM_NO:ctx,) ctx) #define _ASM:%0(%1) AsmEmit%0(_:_ASM_NO:ctx,%1) AsmInitPtr
(ctx, base,
80),
// Don't need any more than that. // Frankly by this point we have probably already written more code than // will be generated! _ASM:Proc
(),
_ASM:LoadSPri
(20),
_ASM:ConstAlt
(~
3),
_ASM:And
(),
_ASM:StorPri
(ctx
[AsmContext_buffer
] + ctx
[AsmContext_buffer_offset
] + 7 * 4),
// The documentation says "PRI" should be a pointer, but that's not true! _ASM:LoadSAlt
(12),
_ASM:LoadSPri
(16),
_ASM:Fill
(0),
// Return the bytes filled. _ASM:LoadPri
(ctx
[AsmContext_buffer
] + ctx
[AsmContext_buffer_offset
] - 4),
_ASM:Retn
();
// Do the second version. #emit CONST.pri memset #emit LOAD.alt AMX_HEADER_COD #emit ADD #emit STOR.S.pri base AsmInitPtr
(ctx, base,
80),
_ASM:Proc
(),
_ASM:LoadSPri
(20),
_ASM:ShlCPri
(2),
_ASM:StorPri
(ctx
[AsmContext_buffer
] + ctx
[AsmContext_buffer_offset
] + 7 * 4),
_ASM:LoadSAlt
(12),
_ASM:LoadSPri
(16),
_ASM:Fill
(0),
_ASM:LoadPri
(ctx
[AsmContext_buffer
] + ctx
[AsmContext_buffer_offset
] - 4),
_ASM:Retn
();
#undef _ASM #undef _ASM_NO // Call this function again (the new version), but don't let the compiler // know... First clear the stack. #emit LCTRL 5 #emit SCTRL 4 #emit CONST.pri rawMemset #emit ADD.C 4 #emit SCTRL 6 // Never hit because of going to an earlier "RETN". return memset
("",
0,
0);
}