From 2cde4482aa15cea7ec8c80e596586151640f2a0d Mon Sep 17 00:00:00 2001 From: ramen-bully <248799758+ramen-bully@users.noreply.github.com> Date: Wed, 13 May 2026 14:48:29 -0500 Subject: [PATCH] druntime: defer rt_term to atexit on Emscripten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emscripten programs that register an async event loop (sokol_app, emscripten_set_main_loop, emscripten_request_animation_frame_loop, etc.) return from main while user code is still scheduled to run via JS callbacks. The unmodified `_d_run_main2` epilogue runs `rt_term` immediately, which tears down the GC (`gc_term` → `Gcx.Dtor` → unmap every pool). Subsequent malloc()s from those callbacks reuse the addresses and clobber any live D objects allocated before main returned. Register `rt_term` via `atexit` on Emscripten instead of calling it inline. This handles all three cases correctly: - `EXIT_RUNTIME=0` async programs (browser tabs): atexit never fires, the page lives until the tab closes, the GC stays alive while rAF callbacks run. - `EXIT_RUNTIME=1` sync programs: emscripten runs atexit handlers when main returns, rt_term fires at the right time, module destructors and `gc_term` run normally. - `EXIT_RUNTIME=1` programs that call `exit()` after cancelling their event loop: same as above — atexit fires on `exit()`. The deferral preserves D-side teardown semantics for programs that genuinely want them, while preventing the heap-clobber for programs whose "main returned" is only a JS-scheduling artifact. Gated on `version (Emscripten)`; other targets unchanged. --- runtime/druntime/src/rt/dmain2.d | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/runtime/druntime/src/rt/dmain2.d b/runtime/druntime/src/rt/dmain2.d index a669c7fb29..526fb04fac 100644 --- a/runtime/druntime/src/rt/dmain2.d +++ b/runtime/druntime/src/rt/dmain2.d @@ -144,6 +144,14 @@ extern (C) int rt_init() return 0; } +version (Emscripten) +{ + private extern (C) void rt_term_atexit() + { + rt_term(); + } +} + /********************************************** * Terminate use of druntime. */ @@ -547,7 +555,12 @@ private extern (C) int _d_run_main2(char[][] args, size_t totalArgsLength, MainF else result = EXIT_FAILURE; - if (!rt_term()) + version (Emscripten) + { + import core.stdc.stdlib : atexit; + atexit(&rt_term_atexit); + } + else if (!rt_term()) result = (result == EXIT_SUCCESS) ? EXIT_FAILURE : result; }