From e5684b45082f588768cf153d6fe4ec4fb9f179b1 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 13:03:02 -0700 Subject: [PATCH 1/8] druntime: Move `accumulatePure` from `core.internal.array` to `rt.profilegc` --- druntime/src/core/internal/array/utils.d | 18 +++--------------- druntime/src/rt/profilegc.d | 12 ++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index 40caa39c89e8..205fc6aef7e0 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -21,23 +21,11 @@ auto gcStatsPure() nothrow pure return impureBypass(); } -ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure -{ - static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow - { - import core.internal.traits : externDFunc; - - alias accumulate = externDFunc!("rt.profilegc.accumulate", void function(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow); - accumulate(file, line, funcname, name, size); - return size; - } - - auto func = cast(ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure)&impureBypass; - return func(file, line, funcname, name, size); -} - version (D_ProfileGC) { + import core.internal.traits : externDFunc; + alias accumulatePure = externDFunc!("rt.profilegc.accumulatePure", void function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); + /** * TraceGC wrapper generator around the runtime hook `Hook`. * Params: diff --git a/druntime/src/rt/profilegc.d b/druntime/src/rt/profilegc.d index dd12995f4226..ddbe253cfc05 100644 --- a/druntime/src/rt/profilegc.d +++ b/druntime/src/rt/profilegc.d @@ -89,6 +89,18 @@ public void accumulate(string file, uint line, string funcname, string type, ulo } } +public ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure +{ + static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow + { + accumulate(file, line, funcname, name, size); + return size; + } + + auto func = cast(ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure)&impureBypass; + return func(file, line, funcname, name, size); +} + // Merge thread local newCounts into globalNewCounts static ~this() { From 5f9d508fada3d80aef35599df30d61684fab21e8 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 15:56:58 -0700 Subject: [PATCH 2/8] Try some voodoo to avoid recursion issues --- druntime/src/core/internal/traits.d | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/druntime/src/core/internal/traits.d b/druntime/src/core/internal/traits.d index df9f4200e54d..4cbd41f1e512 100644 --- a/druntime/src/core/internal/traits.d +++ b/druntime/src/core/internal/traits.d @@ -147,17 +147,24 @@ template externDFunc(string fqn, T:FT*, FT) if (is(FT == function)) static if (is(FT RT == return) && is(FT Args == function)) { import core.demangle : mangleFunc; - enum decl = { - string s = "extern(D) RT externDFunc(Args)"; - foreach (attr; __traits(getFunctionAttributes, FT)) - s ~= " " ~ attr; - return s ~ ";"; - }(); + + // We can't do this with a CTFE lambda because we need + // to avoid triggering runtime hooks (specifically -profile-gc) + // allocation hooks; due to the array appending + enum initial = "extern(D) RT externDFunc(Args)"; + alias decl = initial; + static foreach (attr; __traits(getFunctionAttributes, FT)) + decl = strConcat!(decl, " " ~ attr); + decl = strConcat!(decl, ";"); + pragma(mangle, mangleFunc!T(fqn)) mixin(decl); } else static assert(0); } +// private helper for externDFunc +private enum strConcat(string a, string b) = a ~ b; + template staticIota(int beg, int end) { From df396678eff991bdb43d6ecf0131a7d17c9ac462 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 15:58:09 -0700 Subject: [PATCH 3/8] Fix return type on `accumulatePure` --- druntime/src/core/internal/array/utils.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index 205fc6aef7e0..843434a2f86d 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -24,7 +24,7 @@ auto gcStatsPure() nothrow pure version (D_ProfileGC) { import core.internal.traits : externDFunc; - alias accumulatePure = externDFunc!("rt.profilegc.accumulatePure", void function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); + alias accumulatePure = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); /** * TraceGC wrapper generator around the runtime hook `Hook`. From 2da39c5966ebbfdb3e0e2a932bfc5ea145da76d5 Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 16:12:26 -0700 Subject: [PATCH 4/8] Revert "Try some voodoo to avoid recursion issues" This reverts commit 5f9d508fada3d80aef35599df30d61684fab21e8. --- druntime/src/core/internal/traits.d | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/druntime/src/core/internal/traits.d b/druntime/src/core/internal/traits.d index 4cbd41f1e512..df9f4200e54d 100644 --- a/druntime/src/core/internal/traits.d +++ b/druntime/src/core/internal/traits.d @@ -147,24 +147,17 @@ template externDFunc(string fqn, T:FT*, FT) if (is(FT == function)) static if (is(FT RT == return) && is(FT Args == function)) { import core.demangle : mangleFunc; - - // We can't do this with a CTFE lambda because we need - // to avoid triggering runtime hooks (specifically -profile-gc) - // allocation hooks; due to the array appending - enum initial = "extern(D) RT externDFunc(Args)"; - alias decl = initial; - static foreach (attr; __traits(getFunctionAttributes, FT)) - decl = strConcat!(decl, " " ~ attr); - decl = strConcat!(decl, ";"); - + enum decl = { + string s = "extern(D) RT externDFunc(Args)"; + foreach (attr; __traits(getFunctionAttributes, FT)) + s ~= " " ~ attr; + return s ~ ";"; + }(); pragma(mangle, mangleFunc!T(fqn)) mixin(decl); } else static assert(0); } -// private helper for externDFunc -private enum strConcat(string a, string b) = a ~ b; - template staticIota(int beg, int end) { From e45aab5e56ecefe969b1ffe41abaed4c3e3f3ebb Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 16:14:28 -0700 Subject: [PATCH 5/8] Try wrapping in template? --- druntime/src/core/internal/array/utils.d | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index 843434a2f86d..ddf80ba89f48 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -23,8 +23,13 @@ auto gcStatsPure() nothrow pure version (D_ProfileGC) { - import core.internal.traits : externDFunc; - alias accumulatePure = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); + // Needs to be templated to emit only as needed. + ulong accumulatePure()(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure + { + import core.internal.traits : externDFunc; + alias impl = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); + return impl(file, line, funcname, name, size); + } /** * TraceGC wrapper generator around the runtime hook `Hook`. From 626c925a8793afc40f7c76e3ff0b23ca5df04ecc Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 16:37:55 -0700 Subject: [PATCH 6/8] Fix uint vs int mixup --- druntime/src/core/internal/array/utils.d | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index ddf80ba89f48..54757c0faf7b 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -24,10 +24,10 @@ auto gcStatsPure() nothrow pure version (D_ProfileGC) { // Needs to be templated to emit only as needed. - ulong accumulatePure()(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure + ulong accumulatePure()(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure { import core.internal.traits : externDFunc; - alias impl = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, uint line, string funcname, string name, ulong size) @nogc nothrow pure); + alias impl = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure); return impl(file, line, funcname, name, size); } From 271548cb52854207d46b799b6dd5c0fee6cd1fef Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Fri, 17 Jul 2026 17:11:08 -0700 Subject: [PATCH 7/8] Missing @nogc --- druntime/src/rt/profilegc.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/druntime/src/rt/profilegc.d b/druntime/src/rt/profilegc.d index ddbe253cfc05..aaa37833d8ef 100644 --- a/druntime/src/rt/profilegc.d +++ b/druntime/src/rt/profilegc.d @@ -89,7 +89,7 @@ public void accumulate(string file, uint line, string funcname, string type, ulo } } -public ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure +public ulong accumulatePure(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure { static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow { From 50bac47aec9a55507737c5cb27c3331b9a0640eb Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sat, 18 Jul 2026 10:50:02 -0700 Subject: [PATCH 8/8] Try `@kinke`'s suggestion --- druntime/src/core/internal/array/utils.d | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/druntime/src/core/internal/array/utils.d b/druntime/src/core/internal/array/utils.d index 54757c0faf7b..e9c351cd3a50 100644 --- a/druntime/src/core/internal/array/utils.d +++ b/druntime/src/core/internal/array/utils.d @@ -23,13 +23,12 @@ auto gcStatsPure() nothrow pure version (D_ProfileGC) { - // Needs to be templated to emit only as needed. - ulong accumulatePure()(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure - { - import core.internal.traits : externDFunc; - alias impl = externDFunc!("rt.profilegc.accumulatePure", ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure); - return impl(file, line, funcname, name, size); - } + // Cannot use `externD` template, because then it will cause + // cyclic dependencies (and error out) as CTFE string appending + // expands runtime hooks, which on -profilegc call this. + pragma(mangle, "_D2rt9profilegc14accumulatePureFNaNbNiAyaiQeQgmZm") + ulong accumulatePure(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure; + /** * TraceGC wrapper generator around the runtime hook `Hook`.