Conversation
Implement per-app native relinking via emcc for CoreCLR browser-wasm, matching the capability Mono already has. Unlike Mono, CoreCLR has no C source files in the runtime pack, so the native build is link-only. Key changes: - BrowserWasmApp.CoreCLR.targets: Complete implementation replacing stubs - Emscripten toolchain setup (_CoreCLRSetupEmscripten, _CoreCLRSetupToolchain) - Build native defaults (WasmBuildNative=false for build, true for publish) - emcc link target with all CoreCLR .a libraries + JS library files - Heap size calculation (default 128MB matching CMakeLists.txt) - WasmBuildApp/WasmTriggerPublishApp/WasmNestedPublishApp target chain - wasm-opt post-link optimization - corehost.proj: Copy libSystem.Native.Browser.extpost.js to runtime pack The implementation is self-contained and does not import WasmApp.Common.targets or BrowserWasmApp.targets. It reuses MSBuild task assemblies via UsingTask. Co-authored-by: Copilot <[email protected]>
When WasmBuildNative=true, re-link dotnet.native.wasm from CoreCLR static libraries using emcc, allowing apps to include custom native code via NativeFileReference items. The implementation: - Imports eng/native.wasm.targets for shared Emscripten/ICU/export properties - Sets up Emscripten toolchain and environment - Compiles user native sources (NativeFileReference .c/.cpp) with EmccCompile - Links all CoreCLR .a libraries + user objects into dotnet.native.wasm - Handles both build and publish (nested publish) flows - Link flags mirror src/native/corehost/browserhost/CMakeLists.txt - Gates ICU libraries on InvariantGlobalization - Supports WasmEnableExceptionHandling, WasmEnableSIMD, custom EmccFlags Co-authored-by: Copilot <[email protected]>
Integrate the CoreCLR ManagedToNativeGenerator MSBuild task into the native re-link pipeline. This generates P/Invoke dispatch tables, reverse P/Invoke tables, and interpreter-to-native thunks that are compiled and linked into dotnet.native.wasm. Key changes: - Add _CoreCLRGenerateManagedToNative target that scans managed assemblies for P/Invoke signatures and generates C++ source files - Generate coreclr_compat.h shim header providing type/macro stubs (MethodDesc, PCODE, ULONG, LOG, PORTABILITY_ASSERT) so generated files compile outside the full CoreCLR build context - Use .cpp extensions for generated files (they contain extern "C", namespaces, and other C++ constructs) - Fix CoreLib discovery to check native/ dir first (CoreCLR WASM ships CoreLib there, not in lib/net11.0/) - Fix UsingTask assembly paths: EmccCompile and ManagedToNativeGenerator both live in WasmAppBuilder.dll, not WasmBuildTasks.dll Co-authored-by: Copilot <[email protected]>
- Don't compile reverse-pinvoke-table.cpp separately to avoid duplicate symbols with callhelpers-reverse.cpp.o in libcoreclr_static.a - Condition LLD_REPORT_UNDEFINED and ERROR_ON_UNDEFINED_SYMBOLS to Debug only, matching CMakeLists.txt behavior - Add Dependencies metadata to generated source files for incremental builds - Use node$(_ExeExt) for cross-platform compat - Reorder link libraries: libBrowserHost.a first, matching CMakeLists.txt Co-authored-by: Copilot <[email protected]>
…t string - Remove MAXIMUM_MEMORY from _EmccCommonFlags (compile+link shared flags); it only belongs in the link step's _EmccLinkStepArgs - Fix PORTABILITY_ASSERT fprintf format: use %25s MSBuild encoding to produce correct %s printf format specifier Co-authored-by: Copilot <[email protected]>
…jection - Add SimpleNativeBuildForCoreCLR test that publishes WasmBrowserRunMainOnly with WasmBuildNative=true, verifying the CoreCLR native build pipeline works - Add CoreCLR property injection to CreateWasmTemplateProject (was missing, only CopyTestAsset had it), enabling template-based tests on CoreCLR - Add UsingBrowserRuntimeWorkload=false to both CoreCLR injection blocks to bypass the wasm-tools workload check (NETSDK1147) that fires when WasmBuildNative=true triggers _WasmNativeWorkloadNeeded Co-authored-by: Copilot <[email protected]>
On Windows, the EmccCompile task's CompilerBinaryPath was set to bare 'emcc', which relies on the PATH environment variable to resolve. However, the PATH constructed in _CoreCLRSetupEmscripten was fragmented by MSBuild item-separator splitting on semicolons, so only the emsdk root directory ended up in PATH — not the emscripten subdirectory where emcc lives. Fix by setting WasmClang to an absolute path using EmscriptenUpstreamEmscriptenPath, matching the existing pattern in the Mono targets (BrowserWasmApp.targets line 173). Also update the link step Exec command to use $(WasmClang) instead of bare 'emcc'. Co-authored-by: Copilot <[email protected]>
This comment has been minimized.
This comment has been minimized.
|
It would be good to enable some of the WBT which are currently disabled because of this. |
CoreCLR browser-wasm always requires exception handling and SIMD, so remove the conditional defaults and simplify the flag generation. This removes the dead code paths for WasmEnableExceptionHandling=false and WasmEnableSIMD=false which cannot apply to CoreCLR. Co-authored-by: Copilot <[email protected]>
…on-true for CoreCLR Capture user-set values before overwriting them with true, then emit a build error in _CoreCLRWasmInitialize if either was explicitly set to a non-true value. This gives a clear message instead of a silent override or a cryptic link failure. Co-authored-by: Copilot <[email protected]>
| <_ToolchainMissingErrorMessage Condition="'$(_ToolchainMissingErrorMessage)' == '' and '$(_UsingEMSDK_PATH)' != 'true' and '$(_EMSDKMissingPaths)' != ''">Emscripten from the workload is missing some paths: $(_EMSDKMissingPaths).</_ToolchainMissingErrorMessage> | ||
| <_ToolchainMissingErrorMessage Condition="'$(_ToolchainMissingErrorMessage)' == '' and '$(_UsingEMSDK_PATH)' == 'true' and !Exists($(EMSDK_PATH))">Could not find Emscripten sdk at %24(EMSDK_PATH)=$(EMSDK_PATH).</_ToolchainMissingErrorMessage> | ||
| <_ToolchainMissingErrorMessage Condition="'$(_ToolchainMissingErrorMessage)' == '' and '$(_UsingEMSDK_PATH)' == 'true' and '$(_EMSDKMissingPaths)' != ''">Specified Emscripten sdk at %24(EMSDK_PATH)=$(EMSDK_PATH) is missing some paths: $(_EMSDKMissingPaths).</_ToolchainMissingErrorMessage> |
There was a problem hiding this comment.
The Exists($(EMSDK_PATH)) check is missing quotes around the path. If EMSDK_PATH contains spaces (common on Windows), the condition can evaluate incorrectly and produce a misleading toolchain-missing error. Use Exists('$(EMSDK_PATH)') here for correct MSBuild parsing.
| <Project> | ||
| <!-- | ||
| CoreCLR WASM native build targets. | ||
|
|
||
| Unlike Mono, CoreCLR has no C source files in the runtime pack to compile per-app. | ||
| The native build is link-only: emcc links pre-built .a static libraries + JS library | ||
| files to produce dotnet.native.wasm + dotnet.native.js. | ||
|
|
||
| This file is self-contained and does NOT import WasmApp.Common.targets or BrowserWasmApp.targets. | ||
| It reuses MSBuild tasks (EmccCompile, WasmCalculateInitialHeapSize) via UsingTask declarations. | ||
| --> | ||
|
|
||
| <UsingTask TaskName="Microsoft.WebAssembly.Build.Tasks.WasmCalculateInitialHeapSize" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" TaskFactory="TaskHostFactory" /> | ||
|
|
||
| <PropertyGroup> | ||
| <!-- Post Wasm MVP features --> | ||
| <WasmEnableExceptionHandling Condition="'$(WasmEnableExceptionHandling)' == ''">true</WasmEnableExceptionHandling> | ||
| <WasmEnableSIMD Condition="'$(WasmEnableSIMD)' == ''">$(WasmEnableExceptionHandling)</WasmEnableSIMD> | ||
|
|
||
| <WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == '' and '$(DisableAutoWasmBuildApp)' != 'true'">Build</WasmBuildAppAfterThisTarget> |
There was a problem hiding this comment.
This looks like a temporary backup of BrowserWasmApp.CoreCLR.targets and it is not referenced anywhere in the repo. Keeping a full duplicate of build logic risks divergence and increases maintenance/packaging noise. Please remove this file (or replace it with a much smaller design note under a docs/research location if needed).
| DependsOnTargets="GenerateEmccExports"> | ||
|
|
||
| <PropertyGroup> | ||
| <_EmccExportedRuntimeMethods>"[BROWSER_HOST,@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]"</_EmccExportedRuntimeMethods> |
There was a problem hiding this comment.
$(_EmccExportedRuntimeMethods) is being formatted as a quoted bracketed array (e.g. "[BROWSER_HOST,'FS',...]"), but the emcc invocation used by CoreCLR’s browserhost build expects a comma-separated list (see browserhost/CMakeLists.txt: -sEXPORTED_RUNTIME_METHODS=BROWSER_HOST,${CMAKE_EMCC_EXPORTED_RUNTIME_METHODS}). As written, the linker will receive an invalid value and fail or export the wrong runtime methods. Please change this to emit the same comma-separated format as CMake (including the required BROWSER_HOST prefix) and avoid embedding extra quotes/brackets.
| <_EmccExportedRuntimeMethods>"[BROWSER_HOST,@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]"</_EmccExportedRuntimeMethods> | |
| <_EmccExportedRuntimeMethods>BROWSER_HOST,@(EmccExportedRuntimeMethod -> '%(Identity)', ',')</_EmccExportedRuntimeMethods> |
This comment has been minimized.
This comment has been minimized.
Run all Wasm.Build.Tests with TestCategory 'native' using RuntimeFlavor=CoreCLR and dotnet-none (no workload). Results: 16 passed, 295 failed (311 total) - 89 xharness tool missing (built OK, can't run) - 59 NETSDK1147 (workload not propagated to referenced projects) - 38 template missing (needs wasm-tools workload) - 89 assertion failures (Mono-specific expectations) - 20 other (build/publish/misc failures) Co-authored-by: Copilot <[email protected]>
Introduce a new 'coreclr-native' test category for Wasm.Build.Tests that
have been verified to pass with CoreCLR native relinking. The CoreCLR
xunit filter excludes 'native' but not 'coreclr-native', so these 16
test cases (9 methods) will now run in CoreCLR CI.
Tests tagged coreclr-native:
- MemoryTests (all 2 methods - class level)
- NativeBuildTests.ZipArchiveInteropTest
- DllImportTests.UnmanagedCallback_WithFunctionPointers_CompilesWithWarnings
- PInvokeTableGeneratorTests (3 blittable struct methods)
- Blazor.BuildPublishTests.DefaultTemplate_AOT_WithWorkload
- Blazor.MiscTests.BugRegression_60479_WithRazorClassLib
Non-passing methods that previously inherited the class-level 'native'
category now have method-level [TestCategory("native")] to preserve
the existing Mono-only behavior.
Co-authored-by: Copilot <[email protected]>
This comment has been minimized.
This comment has been minimized.
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.Utils.js" Kind="js-library" /> | ||
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Runtime.InteropServices.JavaScript.Native.js" Kind="js-library" /> | ||
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libBrowserHost.js" Kind="js-library" /> | ||
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.extpost.js" Kind="extern-post-js" /> |
There was a problem hiding this comment.
The runtime pack JS input libSystem.Native.Browser.extpost.js is included unconditionally. If the runtime pack being used doesn't contain this file (e.g., older pack, partial build, or CopyWasmNativeFiles failing), the emcc link will fail with a missing input file. Consider guarding this item with an Exists(...) condition and/or emitting a clear error when it’s required for relinking but not present.
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.extpost.js" Kind="extern-post-js" /> | |
| <_CoreCLRJsLibrary Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.extpost.js" | |
| Kind="extern-post-js" | |
| Condition="Exists('$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libSystem.Native.Browser.extpost.js')" /> |
| { | ||
| "run_date": "2026-03-28", | ||
| "branch": "maraf/WasmCoreCLRNativeBuild", | ||
| "runtime_flavor": "CoreCLR", | ||
| "sdk": "dotnet-none (11.0.100-preview.2.26116.109)", |
There was a problem hiding this comment.
This file looks like a large, generated/one-off research artifact (multi-thousand-line JSON test result dump). Checking this into the main repo will significantly bloat the PR and future clones without providing build/runtime value. Consider removing it from the PR (or replacing with a short summarized markdown report) and attaching the raw results to the PR/issue instead.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
When XHARNESS_CLI_PATH is not set (local dev, non-CI), the xharness dotnet tool may not be restored in the SDK under test (e.g. dotnet-none). This adds EnsureXHarnessAvailable() which runs 'dotnet tool restore' once before the first xharness invocation, using the repo-root .config/dotnet-tools.json manifest. Thread-safe, no-op after first call, and skipped entirely when CI provides XHARNESS_CLI_PATH. Fixes 89 XHARNESS_TOOL_MISSING test failures. Co-authored-by: Copilot <[email protected]>
Re-categorize 12 test methods (43 test cases) from 'native' to 'coreclr-native' so they run in CoreCLR CI. These tests previously failed only because xharness was not installed (XHARNESS_TOOL_MISSING); with the auto-restore fix they should pass. Methods re-tagged: - Blazor.BuildPublishTests.Test_WasmStripILAfterAOT (2 cases) - Blazor.SimpleRunTests.BlazorPublishRunTest (3 cases) - DllImportTests: 3 FunctionPointer methods (6 cases) - InvariantGlobalizationTests.RelinkingWithoutAOT (6 cases) - InvariantTimezoneTests: AOT + RelinkingWithoutAOT (18 cases) - PInvokeTableGeneratorTests: 4 methods (8 cases) 9 additional methods (46 cases) with mixed XHARNESS/NETSDK1147 failures are left as 'native' — their AOT parameter combos require workload and would fail in the no-workload CoreCLR CI configuration. Co-authored-by: Copilot <[email protected]>
🤖 Copilot Code Review — PR #125607Note This review was generated by GitHub Copilot using automated code analysis. Holistic AssessmentMotivation: This PR implements per-app native re-linking for CoreCLR browser-wasm, matching the capability Mono already has. The motivation is clear and justified — without this, CoreCLR WASM apps cannot include custom native code via Approach: The approach is sound — it mirrors the existing Mono build pipeline architecture while remaining self-contained (no import of Summary: Detailed Findings❌ Committed research artifacts and backup file — Must be removedThe branch includes 7 files in a
These should be removed in a cleanup commit before merge.
|
|
Note This comment was generated with the assistance of GitHub Copilot. Investigation: Delivering CoreCLR Native Build Targets to Wasm.Build.TestsAs part of enabling template-based native tests ( The ProblemTest projects created during This means tests like Root Cause: Missing Import Chain
How It Works Today
Proposed ApproachWire up
This keeps the same code paths for local dev and Helix CI — just different values for the path properties. What's Already Done (This PR Branch)
What Remains
|
Contributes to #123670
Contributes to #126100