| In order to buy some performance on the common, uninstrumented, fast path, we replace repeated | |
| checks for both allocation instrumentation and allocator changes by a single function table | |
| dispatch, and templatized allocation code that can be used to generate either instrumented | |
| or uninstrumented versions of allocation routines. | |
| When we call an allocation routine, we always indirect through a thread-local function table that | |
| either points to instrumented or uninstrumented allocation routines. The instrumented code has a | |
| `kInstrumented` = true template argument (or `kIsInstrumented` in some places), the uninstrumented | |
| code has `kInstrumented` = false. | |
| The function table is thread-local. There appears to be no logical necessity for that; it just | |
| makes it easier to access from compiled Java code. | |
| - The function table is switched out by `InstrumentQuickAllocEntryPoints[Locked]`, and a | |
| corresponding `UninstrumentQuickAlloc`... function. | |
| - These in turn are called by `SetStatsEnabled()`, `SetAllocationListener()`, et al, which | |
| require the mutator lock is not held. | |
| - With a started runtime, `SetEntrypointsInstrumented()` calls `ScopedSuspendAll(`) before updating | |
| the function table. | |
| Mutual exclusion in the dispatch table is thus ensured by the fact that it is only updated while | |
| all other threads are suspended, and is only accessed with the mutator lock logically held, | |
| which inhibits suspension. | |
| To ensure correctness, we thus must: | |
| 1. Suspend all threads when swapping out the dispatch table, and | |
| 2. Make sure that we hold the mutator lock when accessing it. | |
| 3. Not trust kInstrumented once we've given up the mutator lock, since it could have changed in the | |
| interim. | |