diff --git a/lib/emit.ml b/lib/emit.ml index a0cf554..67763c6 100644 --- a/lib/emit.ml +++ b/lib/emit.ml @@ -4404,18 +4404,37 @@ let program ?(checks = true) ?(dev = false) ?(debug = false) ?(pnames = []) handler has to be installed before any program code can fault. Only in a dev build — the constructor is emitted here and nowhere else — so a release build dies exactly the way it always did. *) - Buffer.add_string m.out - "@llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] \ - [{ i32, ptr, ptr } { i32 65535, ptr @flan_dev_reg_enable, ptr null }, \ - { i32, ptr, ptr } { i32 65535, ptr @flan_dev_crash_enable, ptr null }]\n"; + (* Both of them behind one constructor defined here, rather than named + directly in the table, and that shape is load-bearing rather than + tidiness. [llvm.global_ctors] naming a *declaration* — which both of + these are, they are C in the runtime — crashes clang 20's + AddressSanitizer module pass outright, so `--dev --sanitize` could not + compile any program at all; the whole combination was unreachable, and + that includes the [__asan_init] yield in [flan_dev_crash_enable] that + is the one thing keeping the crash handler out of ASan's way. Reduced + to five lines of IR here; it is an upstream crash and nobody has filed + it. A local definition in the table is what clang itself emits for its + own constructors, and it costs a call before main. + The ordering the wrapper now fixes was unspecified before — two entries + at one priority — and arming the registry before the handler is the + order that was wanted anyway. *) (* Declared here rather than in the preamble, which is the one place a - declare is worth gating: this lane then adds no dev-only text at all to - a release module, and the only line it does add there — + declare is worth gating: the crash-handler lane then adds no dev-only + text at all to a release module, and the only line it does add there — [flan_bytes_dup] — is a function release builds really call, since (bytes s) allocates in every build. The neighbouring - [flan_dev_reg_enable] stays in the preamble ungated; it predates this - and moving it is not this lane's to make. *) + [flan_dev_reg_enable] stays in the preamble ungated; it predates that + lane and moving it was not its to make. *) Buffer.add_string m.out "declare void @flan_dev_crash_enable()\n"; + Buffer.add_string m.out + "define internal void @\"flan.dev.ctor\"() {\n\ + \ call void @flan_dev_reg_enable()\n\ + \ call void @flan_dev_crash_enable()\n\ + \ ret void\n\ + }\n"; + Buffer.add_string m.out + "@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] \ + [{ i32, ptr, ptr } { i32 65535, ptr @\"flan.dev.ctor\", ptr null }]\n"; Buffer.add_char m.out '\n' end; List.iter (emit_global m ~hidden) p.Tast.globals;