Appearance
aowlspt_fast.h
Source: abi/aowlspt_fast.h — 800 lines, 27 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_fast.h - calling a game method without boxing anything.
The ordinary client path is `il2cpp_runtime_invoke`: look the method up by
name, allocate a `void**`, box every argument into it, dispatch generically,
and unbox a heap-allocated return. That is the right shape for "call
anything, named at run time by a mod's config file", and it is the wrong
shape for something a mod does ten thousand times a second.
IL2CPP compiles every managed method to an ordinary C function. `MethodInfo`
holds its address in `methodPointer` (first field -- see `methodPointer` in
`aowl/src/aowlspt/il2cpp.nim` for why that is read by offset and why the
result is checked). Its signature is the declared one, plus two conventions:
* an instance method takes `this` as the first argument;
* every method takes a trailing `const MethodInfo*`.
So calling one directly is just a function-pointer call with the right type.
The problem is "the right type": nimony will not `cast` between a `pointer`
and a `proc`, so the call has to happen in C -- and C needs the signature at
compile time, while a mod only knows it at bind time.
WHAT THIS HEADER DOES ABOUT THAT
It enumerates the signatures. On Win64 the only thing a parameter's type
changes about the call is which register file it lands in and how wide it is:
* `int32`, `int64`, `bool`, `char`, every pointer and every reference --
one general-purpose register (RCX, RDX, R8, R9, then the stack). A
callee that declared `int32_t` reads ECX; passing a 64-bit value is
therefore indistinguishable from passing the 32-bit one it wanted.
* `float` -- one SSE register (XMM0..3), as a 32-bit float.
That collapses the whole space to a two-valued kind per slot -- and only
for the first FOUR positions, because from position 4 up every argument is
an 8-byte stack slot whatever its type. So the shape count is 31 for the
register positions times one per further slot, not 2**n: 159 shapes, times
three return forms (`int64_t`, `float`, `double`), is 477 cases, GENERATED
by `tools/gen_fast_table.py` and dispatched by a switch on a packed code --
a jump table and a call, with no allocation and nothing touched on the heap.
WHAT IT DELIBERATELY DOES NOT COVER, and why refusing beats guessing:
* `double` arguments. A `double` and a `float` both arrive in XMM, but the
callee reads different widths out of it, so they are not interchangeable
and adding a third kind would take 63 shapes to 364. Unity's own surface
is `float` throughout; a `double` parameter is refused at bind time.
* value-type arguments and returns larger than 8 bytes (`Vector3`,
`Quaternion`). Win64 passes those by hidden pointer, which is a
different call shape rather than a different register class, so
`bindMethod` refuses them and says why. That refusal is no longer the
end of the road: a hidden pointer is a plain pointer slot, and the table
below already passes those, so `bindRaw` in `aowl/src/aowlspt/fast.nim`
lets a mod state the shape outright -- four pointer slots for a method
whose signature says one argument -- and reach `get_Position` and its
kind at fast-path cost. `blackdivision`, `fov`, `morebots` and `sain`
all do. Nothing in this header had to change for it; the shapes were
already here. This paragraph used to end "a mod that needs them keeps
using the boxed path, and is told so", which was true when written.
* NOTHING to do with argument COUNT any more. Arguments past the fourth
spill to the stack, and this header now emits those cases: twelve slots,
which is an sret buffer plus `this` plus eight arguments plus the
trailing MethodInfo*. `EFT.BotOwner`-shaped calls needing 8-9 slots were
the blocked case and are not blocked. Shadow space and alignment are
gcc's, because every case is a real C call through a real prototype;
what was measured, and from which callee, is in the generator's header.
In every one of those cases `bindMethod` fails with a reason. The one thing
this must never do is fall back quietly: a call that got the register
classes wrong does not crash, it reads a float out of a general register and
hands the game a number.
Everything here is `static`, like `aowlspt_shim.h`: it is included into the
single translation unit nimony emits.Constants
AOWLSPT_FAST_HAOWL_FAST_CODEAOWL_FAST_MAX_SLOTS
Types
struct AowlBenchMethod
Functions
| Signature | Line |
|---|---|
void aowl_fast_set_g(void* a, int32_t i, int64_t v) | 110 |
void aowl_fast_set_p(void* a, int32_t i, void* v) | 111 |
void aowl_fast_set_f(void* a, int32_t i, double v) | 112 |
int64_t aowl_fast_g(void* fn, const void* mi, int32_t n, uint32_t mask, const AowlFastSlot* a) | 161 |
double aowl_fast_f(void* fn, const void* mi, int32_t n, uint32_t mask, const AowlFastSlot* a) | 327 |
double aowl_fast_d(void* fn, const void* mi, int32_t n, uint32_t mask, const AowlFastSlot* a) | 493 |
int32_t aowl_fld_i32(void* o, int32_t off) | 678 |
int64_t aowl_fld_i64(void* o, int32_t off) | 679 |
double aowl_fld_f32(void* o, int32_t off) | 680 |
double aowl_fld_f64(void* o, int32_t off) | 681 |
int32_t aowl_fld_u8(void* o, int32_t off) | 682 |
void aowl_fld_ptr(void* o, int32_t off) | 683 |
void aowl_fld_set_i32(void* o, int32_t off, int32_t v) | 685 |
void aowl_fld_set_i64(void* o, int32_t off, int64_t v) | 686 |
void aowl_fld_set_f32(void* o, int32_t off, double v) | 687 |
void aowl_fld_set_f64(void* o, int32_t off, double v) | 688 |
void aowl_fld_set_u8(void* o, int32_t off, int32_t v) | 689 |
void aowl_fld_set_ptr(void* o, int32_t off, void* v) | 690 |
void aowl_fld_set_ptr_wb(void* fn, void* o, int32_t off, void* v) | 704 |
int64_t aowl_perf_counter(void) | 715 |
int64_t aowl_perf_freq(void) | 720 |
float aowl_bench_damage(void* self, float amount, const void* mi) | 756 |
float aowl_bench_get_health(void* self, const void* mi) | 765 |
int32_t aowl_bench_add(int64_t a, int64_t b, const void* mi) | 774 |
void aowl_bench_tick(void* self, const void* mi) | 781 |
int32_t aowl_bench_tick_count(void) | 785 |
void aowl_bench_method(int32_t which) | 788 |

