From 0677e12d4000d941b34e79177fd98fd0e3a50920 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 22 Jul 2026 18:20:58 -0500 Subject: [PATCH 1/3] [wasm] Export cDAC contract descriptor getter on non-emscripten wasm (wasi-sdk) On non-emscripten wasm (wasi-sdk), the cDAC contract descriptor getter GetDotNetRuntimeContractDescriptor was not exported, so --gc-sections stripped the descriptor from the final module, preventing a native host / the cDAC reader from discovering the runtime data-contract descriptor on WASI. The emscripten path already handles this via EMSCRIPTEN_KEEPALIVE. Add an `#elif defined(EXPORT_CONTRACT) && defined(__wasm__)` branch that emits the getter with __attribute__((export_name("Get" #CONTRACT_NAME))), which both exports the symbol and roots it against --gc-sections. Hoist the GETTER_NAME token-paste macros above the #if and add a GETTER_NAME_STR stringize pair shared by both branches. Verified: WASI CoreCLR build (clr -os wasi -c Release) 0W/0E; the compiled object exports GetDotNetRuntimeContractDescriptor with the no_strip flag. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2902374a-f352-4c46-bcec-8a35c97733ae --- .../contract-descriptor.c.in | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in index 83c9e91da314d7..450bc593cef828 100644 --- a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in +++ b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in @@ -30,13 +30,26 @@ struct ContractDescriptor CONTRACT_NAME = { // Due to linking order, the export may also need to be added to // the EXPORTED_FUNCTIONS setting for emcc. // See https://emscripten.org/docs/getting_started/FAQ.html#why-do-functions-in-my-c-c-source-code-vanish-when-i-compile-to-webassembly +#define _GETTER_NAME(exportname) Get ## exportname +#define GETTER_NAME(exportname) _GETTER_NAME(exportname) +#define _GETTER_NAME_STR(exportname) "Get" #exportname +#define GETTER_NAME_STR(exportname) _GETTER_NAME_STR(exportname) + #if defined(EXPORT_CONTRACT) && defined(__EMSCRIPTEN__) #include -#define _GETTER_NAME(exportname) Get ## exportname -#define GETTER_NAME(exportname) _GETTER_NAME(exportname) extern void* EMSCRIPTEN_KEEPALIVE GETTER_NAME(CONTRACT_NAME)() { return (void*)&CONTRACT_NAME; } -#endif // EXPORT_CONTRACT && __EMSCRIPTEN__ +#elif defined(EXPORT_CONTRACT) && defined(__wasm__) +// Non-emscripten wasm (e.g. wasi-sdk): export the getter with an explicit export name so the +// descriptor symbol is both reachable by a native host and, critically, kept alive by the linker. +// Without an export rooting it, --gc-sections removes the descriptor from the final module (the +// emscripten path above relies on EMSCRIPTEN_KEEPALIVE for the same reason). +__attribute__((export_name(GETTER_NAME_STR(CONTRACT_NAME)))) +void* GETTER_NAME(CONTRACT_NAME)() +{ + return (void*)&CONTRACT_NAME; +} +#endif // EXPORT_CONTRACT && (__EMSCRIPTEN__ || __wasm__) From 2556363c4234154016d7365dca106a99cb8a4c98 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 22 Jul 2026 18:29:32 -0500 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../debug/datadescriptor-shared/contract-descriptor.c.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in index 450bc593cef828..af9db34f172661 100644 --- a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in +++ b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in @@ -42,7 +42,7 @@ extern void* EMSCRIPTEN_KEEPALIVE GETTER_NAME(CONTRACT_NAME)() { return (void*)&CONTRACT_NAME; } -#elif defined(EXPORT_CONTRACT) && defined(__wasm__) +#elif defined(EXPORT_CONTRACT) && defined(TARGET_WASM) // Non-emscripten wasm (e.g. wasi-sdk): export the getter with an explicit export name so the // descriptor symbol is both reachable by a native host and, critically, kept alive by the linker. // Without an export rooting it, --gc-sections removes the descriptor from the final module (the @@ -52,4 +52,4 @@ void* GETTER_NAME(CONTRACT_NAME)() { return (void*)&CONTRACT_NAME; } -#endif // EXPORT_CONTRACT && (__EMSCRIPTEN__ || __wasm__) +#endif // EXPORT_CONTRACT && (__EMSCRIPTEN__ || TARGET_WASM) From d399819af56c9662c6c6d26525c4fd03dc5d70c3 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Fri, 24 Jul 2026 17:29:54 -0500 Subject: [PATCH 3/3] [wasm] Key the descriptor getter export on __wasm__, not TARGET_WASM The non-emscripten wasm getter branch keyed on TARGET_WASM, a CoreCLR configurecompiler.cmake define that is gated on IGNORE_DEFAULT_TARGET_ARCH and is not guaranteed to reach every consumer of the shared datadescriptor-shared sources. The whole purpose of the branch is to root the contract descriptor getter against --gc-sections on any wasm toolchain, so it must key on a macro that is always defined when targeting wasm. Use the compiler-provided __wasm__ and document why the compiler builtin is deliberately preferred here over the TARGET_* define, so the choice isn't "corrected" back later. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 50ddfb2c-b6f8-4847-81e7-44d37d44a175 --- .../datadescriptor-shared/contract-descriptor.c.in | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in index af9db34f172661..4ad66d93300c04 100644 --- a/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in +++ b/src/coreclr/debug/datadescriptor-shared/contract-descriptor.c.in @@ -42,14 +42,21 @@ extern void* EMSCRIPTEN_KEEPALIVE GETTER_NAME(CONTRACT_NAME)() { return (void*)&CONTRACT_NAME; } -#elif defined(EXPORT_CONTRACT) && defined(TARGET_WASM) +#elif defined(EXPORT_CONTRACT) && defined(__wasm__) // Non-emscripten wasm (e.g. wasi-sdk): export the getter with an explicit export name so the // descriptor symbol is both reachable by a native host and, critically, kept alive by the linker. // Without an export rooting it, --gc-sections removes the descriptor from the final module (the // emscripten path above relies on EMSCRIPTEN_KEEPALIVE for the same reason). +// +// This intentionally keys on the compiler-provided __wasm__ rather than the CoreCLR TARGET_WASM +// define. datadescriptor-shared is compiled by multiple consumers and TARGET_WASM (set only by +// CoreCLR's configurecompiler.cmake, and gated on IGNORE_DEFAULT_TARGET_ARCH) is not guaranteed to +// reach every one of them. Since the whole point of this branch is to keep the descriptor alive on +// any wasm toolchain, it must depend on a macro that is always defined when targeting wasm; __wasm__ +// is set by the compiler itself for exactly that. __attribute__((export_name(GETTER_NAME_STR(CONTRACT_NAME)))) void* GETTER_NAME(CONTRACT_NAME)() { return (void*)&CONTRACT_NAME; } -#endif // EXPORT_CONTRACT && (__EMSCRIPTEN__ || TARGET_WASM) +#endif // EXPORT_CONTRACT && (__EMSCRIPTEN__ || __wasm__)