D2R (Diablo II Resurrected) has no profile and its code could not run here: the host-call svcs its cuts emitted only died, the touch-run-v4 rule set needed a missing dm/PRODUCT, and the host files its hooks called were never in this repository. Removed with it: - rewriter: D2R cuts, observation gates and parameter slots, the touch-run-v4 rule set and private gates, the boot list, the cut scope column and the KRES / KASPECT key slots (fixups.tsv keeps the four thread slots; no params.tsv any more); dm/tools/product.sh - host: D2R save edits, add-on content, render-scale controller, time() takeover and Terror Zone clock, crash decoding at D2R offsets, host-call registry and stubs, i18n, UI-screen, shell-app loader, zero-stripped archives, boot profiling hooks with no implementation; D2R headers, tests and scripts; dm/ios/Sources/dm_touch_controls; dm/third_party/libdeflate - graphics: runtime extrapolation, world freeze, iPad UI shift, shop drag image, brightness LUT, visual ruler, bloom / FXAA shader hashes, DM_GFXUSE, the eco-fps setting, the loading-screen gate; shader_map.py, nometal_run.py, nvn_slots.py, nvn_slots.tsv and the PC-texture samples The legacy frame step (frame_step legacy, DM_FRAME_STEP) is retired: the Breath of the Wild and Tears of the Kingdom stubs lose their mode test. Breath of the Wild's interval-controller cuts stay: the framework keeps the controller's interval while the game's VFR override holds. patch_nso.py output is byte-identical for all 15 games on this machine except the two frame-step stubs; all 18 profiles build; the host tests pass (their Makefile now builds the general tests only).
37 lines
2.9 KiB
C
37 lines
2.9 KiB
C
// Creating and destroying a guest thread must not leak host heap (item 1 of the memory-leak work): guest_thread_init allocates a TLS block and a thread-local region for each thread,
|
||
// and previously the keys were created with a null destructor and nothing was freed at thread exit, leaking 8192 + 0x200 + 0x400 = 9728 bytes per thread.
|
||
// Method: include the real thread.c directly and replace its calloc / free with the counting versions here; the TLS / thread-local-region keys are created the way the loader does (null destructor), and freeing relies on the extra key thread.c creates itself.
|
||
// Expected values are hard-coded: create and destroy 200 threads, bytes allocated = 200 x (9728 + 16 for recording the pointers) = 1948800, blocks still not freed after all threads exit = 0.
|
||
// Before exiting, each thread overwrites the slots of both keys with other pointers (which is what the guest does: it sets its own thread pointer) - a destructor that frees the value in the slot would crash (the first version of the fix crashed right here on the real host).
|
||
#include <stdatomic.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <pthread.h>
|
||
#include <malloc/malloc.h>
|
||
static _Atomic long g_live_blocks, g_live_bytes, g_total_bytes;
|
||
static void *t_calloc(size_t n, size_t sz) { void *p = calloc(n, sz); atomic_fetch_add(&g_live_blocks, 1); atomic_fetch_add(&g_live_bytes, (long)(n * sz)); atomic_fetch_add(&g_total_bytes, (long)(n * sz)); return p; }
|
||
static _Atomic long g_freed_req; // number of frees
|
||
static void t_free(void *p) { if (!p) return; atomic_fetch_sub(&g_live_blocks, 1); atomic_fetch_add(&g_freed_req, 1); free(p); }
|
||
#define calloc t_calloc
|
||
#define free t_free
|
||
#include "../thread.c"
|
||
#undef calloc
|
||
#undef free
|
||
|
||
pthread_key_t g_key18, g_keytls, g_keytlr, g_keygate;
|
||
void dm_gate(void) {}
|
||
|
||
static int fails, checks;
|
||
#define EXPECT(name, want, got) do { long w_ = (long)(want), g_ = (long)(got); checks++; printf("%-46s expected %-8ld → got %-8ld %s\n", name, w_, g_, w_ == g_ ? "✓" : "✗"); if (w_ != g_) fails++; } while (0)
|
||
static char g_not_heap[64];
|
||
static void *one(void *a) { (void)a; guest_thread_init(); pthread_setspecific(g_keytls, g_not_heap); pthread_setspecific(g_keytlr, g_not_heap + 32); return NULL; }
|
||
int main(void) {
|
||
enum { N = 200 };
|
||
if (pthread_key_create(&g_key18, NULL) || pthread_key_create(&g_keytls, NULL) || pthread_key_create(&g_keytlr, NULL) || pthread_key_create(&g_keygate, NULL)) { printf("key creation failed\n"); return 2; }
|
||
for (int i = 0; i < N; i++) { pthread_t t; if (pthread_create(&t, NULL, one, NULL)) { printf("thread creation failed\n"); return 2; } pthread_join(t, NULL); }
|
||
EXPECT("bytes requested by 200 threads (200 × 9744)", 1948800, atomic_load(&g_total_bytes));
|
||
EXPECT("blocks still not freed after all exited", 0, atomic_load(&g_live_blocks));
|
||
EXPECT("free calls (3 blocks per thread)", 600, atomic_load(&g_freed_req));
|
||
printf("t_thread_leak: %d checks, %d failed\n", checks, fails); return fails != 0;
|
||
}
|