Conversation
…thunks WasmLowering.RaiseSignature models the hidden generic context as explicit parameter 0, so ArgIterator placed it after the async continuation slot. The interpreter (and the VM ArgIterator) expect the generic context before the async continuation, so the R2R->interpreter and interpreter->R2R thunks swapped the two arguments for shared generic async methods. This caused the interpreter to see a MethodDesc in the GC-reported continuation slot (SanityCheck() failures in AsyncHelpers.Await) and a null/garbage generic context (numGenericArgs > 0 assert in dictionary lookups). Fixes #133953 Fixes #134660 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the per-thunk context/continuation offset swap with a shared GCRefMapBuilder.BuildWasmThunkArgIterator helper. When a generic context precedes the async continuation, it drops the context from the raised layout signature and builds the ArgIterator with methodRequiresInstArg, so the context is stored and loaded through GetParamTypeArgOffset() and precedes the continuation, matching the interpreter and the callee's GC ref map. This also fixes WasmImportThunk, which spilled the two arguments in the swapped order during delay-load fixups, so the GC ref map would report the generic context slot as an object reference and miss the continuation. HasGenericContextBeforeAsync moves to WasmLowering instead of being duplicated in each thunk. Add WasmArgumentLayoutTests coverage for the thunk layout, and re-enable RuntimeAsync_WhenAny_TracksAllBranches, which was disabled for the same root cause. Fixes #133627 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
RaiseSignature re-implemented the check for a generic context that precedes the async continuation. Call WasmLowering.HasGenericContextBeforeAsync instead so the signature encoding is parsed in one place. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
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. |
|
/azp run runtime-extra-platforms |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Tagging subscribers to this area: @dotnet/crossgen-contrib |
|
@AndyAyersMS I hope this isn't duplicating anything, go ahead and reject this if you have a fix in progress. |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
Nope, I hadn't looked at fixing this yet. |
|
@lewing, getting the wrong GC Ref map is a serious problem. Please dont' comment that it isn't understood, please fix instead. |
The delay-load GC ref map for a call is computed from the callee's MethodDesc, while WasmImportThunk spills the arguments using only the callee's Wasm signature. Factor GetCallRefMap's ArgIterator construction into BuildCallRefMapArgIterator and add a test asserting both produce the same generic context, async continuation and argument offsets for shared generic, async and shared generic async CoreLib methods. Against the previous thunk layout, the shared generic async cases fail with the generic context one slot away from where the GC ref map reports it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Sorry, that was badly worded. The GC ref map mismatch is fixed in this PR, not left open. bd8aa3c moves the Note This comment was drafted with GitHub Copilot. |
The hidden generic context is always passed immediately before the async continuation (clr-abi.md, "Passing Continuation argument"). Since it is encoded like any pointer-sized argument, detect it as the pointer char immediately preceding the 'a' continuation marker instead of re-parsing the return type and 'this'. Reword the comments that described the context as "explicit parameter 0": that referred to RaiseSignature's MethodSignature parameter list, not the ABI position. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the multi-line remarks on BuildWasmThunkArgIterator and HasGenericContextBeforeAsync with one-line summaries and a short inline comment where the code isn't self-explanatory; the ABI ordering is already documented in clr-abi.md. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add WasmThunkArgLayout, which walks a managed Wasm signature string in Wasm parameter order and maps each element (this, retbuf, generic context, async continuation, explicit arguments) to its Wasm parameters and its ArgIterator offset. The R2R-to-interpreter, interpreter-to-R2R, and import thunks now each handle their arguments with a single loop over the layout instead of hand-coding the hidden argument sequence. Move BuildWasmThunkArgIterator from GCRefMapBuilder into the new type. The refactor produces byte-identical crossgen2 output. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/azp run runtime-extra-platforms |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
- Remove the unused WasmThunkArgLayout.Signature, WasmThunkArg.Type and RetBufParamIndex; make BuildArgIterator private. - Move the typed load/store helpers to Memory.Load/Store in WasmInstructions and use them for the thunk return values as well. - Drop the stale spill pseudocode in WasmImportThunk and note when a generic context is a hidden argument. - Fold the ArgIterator-level thunk layout tests into the layout theories. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
/azp run runtime-wasm-libtests |
|
No pipelines are associated with this pull request. |
| { | ||
| This, | ||
| RetBuf, | ||
| // Only when an async continuation follows; otherwise the context is encoded as the first explicit argument. |
There was a problem hiding this comment.
Can this comment also explain why this special casing of async continuation + generic context is required (or link to where it is explained)? I am not able to figure it out, and it seems to be a bug farm. What would need to change to get rid of this special casing?
There was a problem hiding this comment.
The Wasm signature string has no distinct token for the hidden generic context. WasmLowering.GetSignature emits it as the pointer char (i on wasm32), the same as an explicit pointer argument, and the thunks only have that string, since they're shared across methods by signature. So the layout can only recognize the context by position, when the a continuation follows it. Without a continuation it doesn't matter: GetParamTypeArgOffset() is the same slot as the first argument, so the layout is identical either way.
Removing the special case needs a dedicated token for the generic context, emitted by GetSignature and the VM's GetSignatureKey (vm/wasm/helpers.cpp), and understood by RaiseSignature, the portable call-helper tokenizer and readytorun-format.md. The layout would then use the hidden instantiation argument whenever the token is present. The cost is that signatures differing only in context versus an explicit pointer argument would no longer share a thunk. Since that changes the thunk key format on both the VM and crossgen2 sides, I'd prefer to do it as a follow-up: #134716. For now f119a1d expands this comment to say why the special case exists.
Note
This comment was drafted with GitHub Copilot.
There was a problem hiding this comment.
@jkotas it is definitely a source of bugs as demonstrated in this pr which reconstructs the requirement from the rest of signature and fixes the existing thunks. If this is the time to make the signature string encode the generic context distinctly I'm happy to do it here even if the agent is overly focused on our repo instructions to separate concerns.
There was a problem hiding this comment.
@jkotas here's what the dedicated token looks like: prototype branch. It isn't part of this PR yet.
- The generic context gets its own signature char,
g, in both crossgen2 (WasmLowering.GetSignature) and the VM key (GetSignatureKeyin vm/wasm/helpers.cpp). Wasm function types are unchanged. HasGenericContextBeforeAsync, theRaiseSignatureroundtrip swap, and the async-only case inWasmThunkArgLayoutare gone. The layout treats the context as the hidden instantiation argument whenevergis present.RaiseSignaturereports it as a flag, like the continuation, instead of as parameter 0.- 9 files, +82/−69.
WasmArgumentLayoutTestspasses 94/94. With a rebuilt browser runtime, the R2Rasyncruntime tests pass 132/132, and a logging build confirmed the lookups hit the newgkeys. - Every thunk body is byte-identical to before; the cost is lost sharing. A method with a generic context and one with an explicit pointer argument used to share a thunk, so CoreLib gains 232 duplicate thunks (about 20 KB). Registering one body under both keys would recover most of that, but
StringDiscoverableAssemblyStubNodewould need to support more than one lookup string.
I'm happy to fold this into this PR or land it separately as #134716. Which do you prefer?
Note
This comment was drafted with GitHub Copilot.
…async Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Problem
For shared-generic runtime-async methods, crossgen2's Wasm thunks passed the hidden generic context and the async continuation in each other's slots.
WasmLowering.RaiseSignaturehas no way to mark a parameter as the hidden generic context, so it returns it as the first entry of theMethodSignatureparameter list, withthisand the return buffer implied by the signature flags and return type. The thunks built theirArgIteratorfrom that signature withoutmethodRequiresInstArg, soArgIteratorlaid the frame out as[this][continuation][ctx][args]. The interpreter, the VMArgIteratorand the callee's GC ref map (GCRefMapBuilder.GetCallRefMap) all expect[this][ctx][continuation][args].Andy diagnosed this in #133627 and proposed modeling the context as the hidden instantiation argument; this PR follows that suggestion.
Symptoms:
SanityCheck()in interpretedAsyncHelpers.Await<__Canon>(ValueTask<T>)(R2R→interpreter).numGenericArgs > 0inProcessDynamicDictionaryLookupfor a generic virtual async method.WasmR2RToInterpreterThunk(iiaS8p)(AwaitAwaiter<TAwaiter>).[BypassReadyToRun]fromAsyncHelpers) hits the interpreter→R2R direction of the same bug on both the R2R and non-R2R browser legs.Fix
ArgIteratorwithmethodRequiresInstArg: true. The context is then stored and loaded throughGetParamTypeArgOffset(), and the continuation throughGetAsyncContinuationArgOffset().WasmR2RToInterpreterThunkNode,WasmInterpreterToR2RThunkNodeandWasmImportThunk. Each used to hand-code the hidden-argument sequence, which is how the slots got swapped.WasmThunkArgLayoutwalks the Wasm signature string in Wasm parameter order,[this] [retbuf] [generic context] [continuation] [args]per clr-abi.md, takes each offset from thatArgIterator, and asserts that the two agree. Each thunk keeps its own emission and loops over the entries.WasmImportThunk, the swapped spill disagreed with the delay-load GC ref map, whichGCRefMapNodebuilds from the callee'sMethodDescviaGCRefMapBuilder.GetCallRefMap. A GC during the fixup would have reported the generic context slot as an object reference and missed the continuation. The thunk now spills both to the offsets the GC ref map describes, andWasmThunkArgLayoutMatchesCallRefMapLayoutasserts the two layouts are identical.HasGenericContextBeforeAsyncintoWasmLowering, and use it fromRaiseSignatureso the encoding is parsed in one place.RaiseSignature's output is unchanged: the direct-forwarding thunks andINodeWithTypeSignature.Signaturestill get the context as the first entry of the parameter list.Testing
run_test_p0_coreclr_R2R_CG2_browser_wasm_checked(theasyncwork item), with an osx-arm64 crossgen2 and wasm JIT.asyncwork item passes 132/132 in both R2R (RunCrossGen2=1, no tiered compilation) and non-R2R modes. Without it, both failures reproduce.WasmArgumentLayoutTestscases assert the kind, offset and Wasm parameter index of every thunk argument foriiaip,iTiaip,S16iaipandS16Tiaip, the no-context cases, and multi-slot and by-reference arguments.WasmArgumentLayoutTestspasses 93/93 locally with a browser-wasm target.WasmThunkArgLayoutMatchesCallRefMapLayoutcompares every slot of the thunk layout with theArgIteratorthatGetCallRefMapuses, for shared generic, async and shared generic async CoreLib methods.asyncwork item (--parallelism:1), and theasyncwork item passes 132/132 in R2R mode.AsyncProfilerTests.RuntimeAsync_WhenAny_TracksAllBranches([browser][CoreCLR][R2R] RuntimeAsync_WhenAny_TracksAllBranches traps in an R2R-to-interpreter transition #133627). It runs only in the full browser CoreCLR R2R library lane, which PRs can't reach today:runtime-extra-platformsadds Wasm jobs only for scheduled builds, andruntime-wasm-libtestsis disabled in AzDO. It hasn't run in CI for this PR. The first scheduledruntime-extra-platformsbuild after merge will run it; Fix scheduled WASM extra-platforms job selection #134613 restored that lane.Notes
HasGenericContextBeforeAsyncproperties, so whichever lands second will have a small textual conflict.WasmImportThunkis not; each image references its own). An image compiled by an older crossgen2 would still carry the old layout under the same key. That only matters for mixing crossgen2 versions, which Wasm R2R doesn't ship yet.cc @AndyAyersMS @davidwrighton
Resolves #133953
Resolves #134660
Resolves #133627
Note
This PR description was drafted with GitHub Copilot.