Skip to content

[mono][wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash#131020

Merged
lewing merged 1 commit into
mainfrom
lewing-wasm-aot-appdomain-assert
Jul 19, 2026
Merged

[mono][wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash#131020
lewing merged 1 commit into
mainfrom
lewing-wasm-aot-appdomain-assert

Conversation

@lewing

@lewing lewing commented Jul 18, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #125794. AOT-published Blazor WebAssembly apps that use BlazorWebAssemblyLazyLoad crash during Mono runtime startup — reported as appdomain.c assertion (condition '<disabled>' not met) and, in other configurations, interp.c NIY … should not be reached.

Root cause

An AOT image hard-binds to every assembly it references when the image is loaded. load_aot_module in src/mono/mono/mini/aot-runtime.c eagerly calls load_image for all referenced images unless the aot-lazy-assembly-load runtime option is set:

/* … we have to load all referenced assemblies non-lazily … */
if (!mono_opt_aot_lazy_assembly_load) {
    for (guint32 i = 0; i < amodule->image_table_len; ++i)
        load_image (amodule, i, load_error);
}

A BlazorWebAssemblyLazyLoad assembly is not present at runtime startup (it is downloaded on demand). So when the main app's AOT image is loaded, resolving its lazy dependency fails and the image is marked "unusable because dependency <asm> is not found". Every method in that image then falls back to the interpreter, which hits an unsupported construct and aborts:

[MONO] AOT: module WasmBasicTestApp is unusable because dependency Json is not found.
MONO interpreter: NIY encountered in method <Module>:.cctor ()
[MONO] * Assertion: should not be reached at .../mono/mini/interp/interp.c:4135

Fix

Automatically enable the runtime's existing aot-lazy-assembly-load option from the WebAssembly SDK when AOT is combined with BlazorWebAssemblyLazyLoad. This defers binding of a referenced lazy assembly until it is actually loaded, so the referencing AOT image stays usable. The option flows through runtimeOptions in the boot config → mono_wasm_parse_runtime_optionsmono_options_parse_options.

The change is scoped to RunAOTCompilation=true + at least one BlazorWebAssemblyLazyLoad item, so non-AOT and non-lazy builds are unaffected.

Test

LazyLoadingTests previously only covered the interpreter (Debug) configuration. Added LoadLazyAssemblyWithAOT (native-mono, Release + AOT + lazy load) which reproduces the crash without the fix and passes with it.

Verification

Built the browser (Mono + CoreCLR runtime packs) and workload locally and ran the new test.

  • Without the fix: module WasmBasicTestApp is unusable because dependency Json is not foundinterp.c:4135 assertion → timed out waiting for WASM EXIT. ❌
  • With the fix: AOT: image 'WasmBasicTestApp' found., firstJsonLoad=true, {"FirstName":"John","LastName":"Doe"}, WASM EXIT 0. ✅

Note

This pull request was authored with GitHub Copilot.

When an app is published with AOT and uses BlazorWebAssemblyLazyLoad, the
Mono runtime crashes during startup (appdomain.c assertion / interp.c NIY
'should not be reached').

Root cause: an AOT image hard-binds to every assembly it references when the
image is loaded (see load_aot_module in aot-runtime.c, which eagerly calls
load_image for all referenced images unless aot-lazy-assembly-load is set). A
lazy-loaded assembly is not present at runtime startup, so any AOT image that
references it is marked 'unusable because dependency <asm> is not found' and its
methods fall back to the interpreter, which then hits an unsupported construct
and aborts.

Enable the runtime's 'aot-lazy-assembly-load' option automatically from the
WebAssembly SDK when AOT is combined with BlazorWebAssemblyLazyLoad, so
referenced lazy assemblies are only bound when they are actually loaded.

Also add AOT + lazy-load regression coverage to LazyLoadingTests, which
previously only exercised the interpreter (Debug) configuration.

Fixes #125794
@lewing
lewing requested a review from maraf as a code owner July 18, 2026 22:21
Copilot AI review requested due to automatic review settings July 18, 2026 22:21
@lewing
lewing temporarily deployed to copilot-pat-pool July 18, 2026 22:21 — with GitHub Actions Inactive
@lewing
lewing temporarily deployed to copilot-pat-pool July 18, 2026 22:22 — with GitHub Actions Inactive
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@lewing lewing added the arch-wasm WebAssembly architecture label Jul 18, 2026
@lewing
lewing had a problem deploying to copilot-pat-pool July 18, 2026 22:22 — with GitHub Actions Failure
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@lewing

lewing commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

/backport to release/10.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@lewing
lewing temporarily deployed to copilot-pat-pool July 18, 2026 22:24 — with GitHub Actions Inactive
@lewing
lewing temporarily deployed to copilot-pat-pool July 18, 2026 22:24 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

@lewing backporting to release/10.0 was not run because the source pull request has not been merged. Please merge this pull request before requesting a backport.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the WebAssembly SDK’s browser targets to automatically enable Mono’s --aot-lazy-assembly-load runtime option when AOT is used together with BlazorWebAssemblyLazyLoad, and adds an integration test intended to prevent regressions in that AOT + lazy-load startup path.

Changes:

  • Append --aot-lazy-assembly-load to runtime options when RunAOTCompilation=true and at least one BlazorWebAssemblyLazyLoad item exists.
  • Add a new native-mono test that publishes with AOT and exercises lazy-loading at runtime.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs Adds an AOT + publish + lazy-load integration test.
src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets Automatically injects --aot-lazy-assembly-load into runtime options under AOT + lazy-load conditions.

Comment thread src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
@lewing
lewing enabled auto-merge (squash) July 18, 2026 23:15
@lewing lewing changed the title [wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash [mono][wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash Jul 18, 2026
@lewing lewing added this to the 10.0.x milestone Jul 19, 2026
@lewing
lewing merged commit 7b783ab into main Jul 19, 2026
60 of 63 checks passed
@lewing
lewing deleted the lewing-wasm-aot-appdomain-assert branch July 19, 2026 00:47
@lewing

lewing commented Jul 19, 2026

Copy link
Copy Markdown
Member Author

/backport to release/10.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@github-actions

Copy link
Copy Markdown
Contributor

@lewing backporting to release/10.0 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: [wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash
Using index info to reconstruct a base tree...
M	src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
M	src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
CONFLICT (content): Merge conflict in src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets
Auto-merging src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
CONFLICT (content): Merge conflict in src/mono/wasm/Wasm.Build.Tests/LazyLoadingTests.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 [wasm] Fix AOT + BlazorWebAssemblyLazyLoad startup crash
Error: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 10.0.x, 11.0-preview7 Jul 19, 2026
lewing added a commit that referenced this pull request Jul 20, 2026
…up crash (#131038)

Backport of #131020 to release/10.0

/cc @lewing

1. Fixes Issue #125794

main PR: #131020

# Description

AOT-published Blazor WebAssembly apps that use
`BlazorWebAssemblyLazyLoad` crash during Mono runtime startup — reported
as an `appdomain.c` assertion (`condition '<disabled>' not met`) and, in
other configurations, an `interp.c` `NIY … should not be reached` abort.

An AOT image hard-binds to every assembly it references when the image
is loaded (`load_aot_module` in `aot-runtime.c` eagerly calls
`load_image` for all referenced images unless the
`aot-lazy-assembly-load` runtime option is set). A
`BlazorWebAssemblyLazyLoad` assembly is not present at runtime startup
(it is downloaded on demand), so when the main app's AOT image loads,
resolving the lazy dependency fails, the image is marked unusable, and
every method in it falls back to the interpreter, which then hits an
unsupported construct and aborts.

The fix automatically enables the runtime's existing
`aot-lazy-assembly-load` option from the WebAssembly SDK when AOT is
combined with `BlazorWebAssemblyLazyLoad`. This defers binding of a
referenced lazy assembly until it is actually loaded, keeping the
referencing AOT image usable. The change is scoped to
`RunAOTCompilation=true` + at least one `BlazorWebAssemblyLazyLoad`
item, so non-AOT and non-lazy builds are unaffected.

# Customer Impact

Any Blazor WebAssembly app that combines AOT compilation with
`BlazorWebAssemblyLazyLoad` crashes on startup and is completely
unusable. Lazy loading is a common technique to reduce initial download
size, and AOT is a common way to improve runtime performance, so
customers who adopt both features together are blocked with no
workaround other than disabling one of them.

# Regression

No. This is a long-standing interaction between AOT and lazy loading,
not a regression introduced in the most recent release.

# Testing

Added `LoadLazyAssemblyWithAOT` to `LazyLoadingTests` (`native-mono`,
Release + AOT + lazy load), which previously only covered the
interpreter (Debug) configuration. The new test reproduces the crash
without the fix and passes with it.

Verified locally by building the browser (Mono + CoreCLR runtime packs)
and workload and running the new test:
- Without the fix: `module WasmBasicTestApp is unusable because
dependency Json is not found` → `interp.c:4135` assertion → timed out
waiting for `WASM EXIT`.
- With the fix: `AOT: image 'WasmBasicTestApp' found.`,
`firstJsonLoad=true`, `{"FirstName":"John","LastName":"Doe"}`, `WASM
EXIT 0`.

# Risk

Low. The change is a single, narrowly-scoped MSBuild property that only
appends `--aot-lazy-assembly-load` when `RunAOTCompilation=true` and at
least one `BlazorWebAssemblyLazyLoad` item is present. Builds that do
not combine AOT with lazy loading are unaffected, and the option simply
defers assembly binding that would otherwise fail.

> [!NOTE]
> This backport pull request was authored with GitHub Copilot.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Build-mono

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[wasm][AOT] Assertion at appdomain.c:188 — g_assert(string_empty_fld) fails on any AOT-compiled Blazor WASM project (.NET 10)

3 participants