Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [wasm] Pass Int128, Decimal128 and wide vectors by value by lewing · Pull Request #131492 · dotnet/runtime · GitHub
Skip to content

[wasm] Pass Int128, Decimal128 and wide vectors by value - #131492

Merged
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi
Aug 12, 2026
Merged

[wasm] Pass Int128, Decimal128 and wide vectors by value#131492
davidwrighton merged 4 commits into
dotnet:mainfrom
lewing:wasm-multi-slot-int128-abi

Conversation

@lewing

Copy link
Copy Markdown
Member

Problem

Int128/UInt128, Decimal128, Vector256<T> and Vector512<T> are passed on wasm as a single i32 pointer under the S<N> signature encoding. That is both the wrong calling convention and ambiguous.

Wrong convention. Measured against clang for wasm32 (the in-tree wasi-sdk):

typeparameterreturn
__int1282 × i64 by valuesret buffer
256-bit vector2 × v128 by valuesret buffer
512-bit vector4 × v128 by valuesret buffer

The returns are unaffected by -mmultivalue, and S<N> already returns through a hidden buffer, so returns are left alone. Alignment already matches clang (16/32/64) and is unchanged by this PR.

Ambiguous.S<N> resolves through a size-keyed, first-wins struct cache, so Int128 and Guid both spell S16 and share a thunk whose frame layout only fits whichever type the cache happened to see first. This reproduces as miscompiles under R2R today: Int128.Equals returns False and CompareTo returns ±1 for values that are equal.

Change

Encode such a parameter as the slot character followed by the factor its alignment is elevated above the slot's natural alignment:

encodingalignmenttypes
l8 (natural)long
l216Int128, UInt128, Decimal128
V16 (natural)Vector128<T>, Vector<T>
V232Vector256<T>
V464Vector512<T>

This stays unambiguous when mixed: ll2VV4 is i64, Int128, Vector128, Vector512. An elevation factor that differs from the slot count is not expressible; no type today needs one.

Classification is structural rather than by name — intrinsic, aligned to its own size, and wider than one slot. Each condition is load-bearing:

  • intrinsic — the wasm C ABI passes an ordinary aggregate indirectly, and struct { long a, b; } decomposes exactly like Int128 but must stay indirect.
  • alignment == size — excludes Vector2/3/4, which are intrinsic but 4-aligned. Alignment alone would not do, since StructLayout(Pack) can manufacture it.
  • size > slot size — excludes v128 and Vector64<T> without naming them. The threshold must be the slot's own width rather than a fixed number, since Int128 needs two slots at the same 16 bytes a v128 fills with one.

Decimal128 is picked up by that rule without being named anywhere.

Three encoders have to agree exactly, since the signature string is a thunk's identity: crossgen2 (WasmLowering), the runtime (vm/wasm/helpers.cpp), and WasmAppBuilder (SignatureMapper).

An encoding records layout, not identity, so raising resolves a stand-in — l2 always raises to Int128. That is sound because a thunk is keyed by its signature string and its frame layout derives from nothing else, which the tests pin.

Interop

WasmAppBuilder reports WASM0068 for these types rather than encoding them. Supporting them means teaching the thunk generator that one signature token can map to several native parameters, which is a larger design question; no PInvoke or InternalCall signature uses one today.

Validation

Browser wasm, Release and Checked, both DOTNET_ReadyToRun=0 and =1:

checkresult
Int128 equality/comparison repropasses both arms (fails on main under R2R=1)
single-field wrapper repropasses both arms
Vector256/Vector512/Vector64 repropasses both arms
Microsoft.Bcl.Memory550/550 both arms
System.Runtime.CompilerServices.Unsafe128/128 both arms
WasmArgumentLayoutTests47/47
full Checked buildclean, zero asserts, CoreLib crossgen'd with --verify-type-and-field-layout

Each repro was confirmed to fail without the change before being trusted as a pass, and the runtime encoder was confirmed live by perturbation (emitting the wrong slot char breaks R2R=1 with a function signature mismatch).

Notes

Note

This pull request was prepared with GitHub Copilot.

CopilotAI lite review requested due to automatic review settings July 28, 2026 20:27
@github-actionsgithub-actionsBot added the area-crossgen2-coreclr only use for closed issues label Jul 28, 2026
@azure-pipelines

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

CopilotAI 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 wasm calling convention/signature encoding so certain “wide” intrinsic value types (e.g., Int128/UInt128, Vector256, Vector512, Decimal128) are passed by value across multiple wasm parameters instead of being encoded as ambiguous S<N> by-ref structs. The change spans the runtime encoder, crossgen2 lowering/raising, R2R thunk generation, and the JIT’s ABI classification so all components agree on the same signature identity and frame layout.

Changes:

  • Add a multi-slot signature token form (l2, V2, V4) and use it for qualifying intrinsic wide types when passed as parameters; keep returns as S<N> (retbuf).
  • Update runtime and crossgen2 signature lowering/raising and R2R thunk emitters to correctly marshal multi-slot arguments.
  • Add/extend unit tests and update the ready-to-run format documentation; reject multi-slot types in WasmAppBuilder interop signatures.

Reviewed changes

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

Show a summary per file
FileDescription
src/tasks/WasmAppBuilder/coreclr/SignatureMapper.csReject multi-slot ABI types for interop signatures; parse multi-slot tokens.
src/coreclr/vm/wasm/helpers.cppRuntime signature encoding supports multi-slot parameter tokens and preserves return behavior.
src/coreclr/tools/Common/JitInterface/WasmLowering.csCrossgen2 lowering/raising now classifies and encodes multi-slot parameters and separates return-struct caching.
src/coreclr/tools/Common/JitInterface/CorInfoImpl.csExposes multi-slot slot-type lowering to the JIT via getWasmLowering.
src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.csAdds elevated-type resolution and a separate cache for return structs to keep raising stable/unambiguous.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.csEnsures multi-slot types are treated as by-value (not byref) in R2R paths.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.csStores multi-slot wasm locals into the interpreter argument area slot-by-slot.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.csLoads multi-slot arguments from interpreter arg area and forwards each slot as a wasm parameter.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.csStashes/restores multi-slot arguments as multiple locals during import thunk transitions.
src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.csAdds coverage for multi-slot encoding, raising stability, cache poisoning avoidance, and Decimal128 shape.
src/coreclr/jit/targetwasm.cppSplits struct args into multiple wasm “register” segments when size exceeds the lowered wasm value width.
src/coreclr/jit/lower.cppAdjusts FieldList/PutArg marking logic for targets without fixed register sets (wasm).
src/coreclr/jit/compiler.cppForces multi-slot returns to use retbuf (byref) while allowing multi-slot passing.
src/coreclr/jit/codegenwasm.cpp / src/coreclr/jit/codegen.hConsumes FIELD_LIST args per-field for correct liveness on wasm (no PUTARG nodes).
src/coreclr/inc/jiteeversionguid.hBumps JIT/EE interface GUID.
docs/design/coreclr/botr/readytorun-format.mdDocuments the multi-slot token grammar and examples.

Comment threadsrc/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs
Comment threadsrc/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs Outdated
Comment threaddocs/design/coreclr/botr/readytorun-format.md Outdated
@lewinglewing changed the title [wasm] Pass Int128, Decimal128 and wide vectors by value[WIP][wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
CopilotAI review requested due to automatic review settings July 28, 2026 20:52
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 13d4bc8 to 7bbf6e8CompareJuly 28, 2026 20:52

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:689

  • sigBuilder.Append(slotCount); will append multi-digit numbers (e.g., 10) if slotCount ever exceeds 9, which would make the signature ambiguous/misparsed (RaiseSignature only consumes a single digit). Prefer emitting a single digit char and assert the supported range so malformed signatures fail fast.
 if (TryGetMultiSegmentLayout(paramType, out WasmValueType slotType, out int slotCount))
{
// Passed by value across several wasm parameters, matching the wasm C ABI.
// Spelled '<slot><elevation>'; the elevation factor equals the slot count
// for every type in the wasm ABI today, and the encoding cannot express
// them differing. See readytorun-format.md.
sigBuilder.Append(WasmValueTypeToSigChar(slotType));
sigBuilder.Append(slotCount);
for (int slot = 0; slot < slotCount; slot++)
{
result.Add(slotType);

CopilotAI review requested due to automatic review settings July 28, 2026 21:28
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 7bbf6e8 to a436442CompareJuly 28, 2026 21:28
@lewing

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/wasm-contrib

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:186

  • The wrapper-unwrapping heuristic in IsMultiSlotType assumes “a single field fills its struct unless the struct declares a size”, but that’s not true for explicit-layout wrappers with a non-zero FieldOffset. Those can be larger than the field without setting StructLayoutAttribute.Size, and unwrapping them here can cause over-rejection (treating an indirect struct as multi-slot). Consider checking for explicit non-zero field offsets and avoiding unwrapping in that case (still keeping the explicit Size check).
 // Only unwrap a wrapper its field fills exactly. Sizes cannot be measured here: these
// types come from a MetadataLoadContext, where Marshal.SizeOf always throws. A single
// field fills its struct unless the struct declares a size of its own.
FieldInfo[] fields = t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (fields.Length != 1 || t.StructLayoutAttribute is { Size: not 0 })
{
return false;
}

src/coreclr/vm/wasm/helpers.cpp:1020

  • ConvertType::ToSlotsI64 is now also used for Decimal128 (it shares the l2 encoding), but the comment only mentions Int128/UInt128. Updating the comment would help keep the three encoders’ intent aligned while debugging ABI classification.
 ToSlotsI64, // Passed by value as several i64 slots (Int128/UInt128)
ToSlotsV128, // Passed by value as several v128 slots (Vector256<T>, Vector512<T>)

@lewing
lewing marked this pull request as ready for review July 28, 2026 22:45
@lewinglewing changed the title [WIP][wasm] Pass Int128, Decimal128 and wide vectors by value[wasm] Pass Int128, Decimal128 and wide vectors by valueJul 28, 2026
@azure-pipelines

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

@AndyAyersMSAndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

JIT changes look good, the rest looks reasonable, but I'll defer to @davidwrighton for those.

Comment threaddocs/design/coreclr/botr/readytorun-format.md
WasmAppBuilder encodes the signatures behind the PInvoke and InternalCall tables,
where a type the wasm ABI splits across several by-value slots would need one
signature token to map to several native parameters. Teaching the thunk generator
that is a larger question than this change, and no interop signature uses one of
these types today, so report WASM0068 rather than encode it. The parser still
understands the encoding, since signatures it reads can carry one.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8c87b08f-c9ef-4fcd-b506-8790ce76331a
CopilotAI review requested due to automatic review settings July 31, 2026 16:49
@lewing
lewingforce-pushed the wasm-multi-slot-int128-abi branch from 661e153 to 08b33d6CompareJuly 31, 2026 16:49

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs:216

  • HasNumericElementType checks IntPtr/UIntPtr using typeof(IntPtr)/typeof(UIntPtr), but types loaded via MetadataLoadContext won't compare reference-equal to runtime typeof(...). This makes Vector256/Vector512 (and UIntPtr) incorrectly treated as non-numeric element vectors and therefore not rejected by WASM0068 like the runtime/crossgen2 encoders.
 return Type.GetTypeCode(arguments[0]) is >= TypeCode.SByte and <= TypeCode.Double
|| arguments[0] == typeof(IntPtr) || arguments[0] == typeof(UIntPtr);

src/coreclr/tools/Common/JitInterface/WasmLowering.cs:675

  • This comment says all non-primitive-lowerable structs are passed by reference, but this method now also handles multi-segment (multi-slot) structs that are passed by value across multiple wasm parameters.
 // Struct that cannot be lowered to a single primitive — passed by reference

@pavelsavara

Copy link
Copy Markdown
Member

probably related #132137

@pavelsavarapavelsavara added this to the 12.0.0 milestone Aug 11, 2026
# Conflicts:
#	src/coreclr/inc/jiteeversionguid.h
CopilotAI review requested due to automatic review settings August 11, 2026 18:31
@AndyAyersMS

Copy link
Copy Markdown
Member

Merged up and fixed the guid conflict

CopilotAI 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

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

@davidwrighton PTAL when you can

@davidwrightondavidwrighton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it would probably work, but the code looks a bit odd. I may try to refactor a bit of this.

Comment threadsrc/coreclr/vm/wasm/helpers.cpp
Comment threadsrc/coreclr/vm/wasm/helpers.cpp Outdated
Comment threadsrc/coreclr/tools/Common/JitInterface/WasmLowering.cs Outdated
…ecial ABI types
- Check for types explicitly instead of having the logic just fall out.
CopilotAI review requested due to automatic review settings August 11, 2026 23:56

CopilotAI 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

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@davidwrighton

Copy link
Copy Markdown
Member

/ba-g Helix Job monitor known issue. Relevant tests passed

@davidwrighton
davidwrighton merged commit 4fe034e into dotnet:mainAug 12, 2026
133 of 135 checks passed
@dotnet-milestone-botdotnet-milestone-botBot modified the milestones: 12.0.0, 11.0-rc1Aug 13, 2026
@lewing
lewing deleted the wasm-multi-slot-int128-abi branch August 17, 2026 20:36
lewing added a commit to lewing/runtime that referenced this pull request Aug 19, 2026
… R2R
Several claims went stale over three weeks. Correcting them, and dating the
page so the next reader can judge.
- The product browser loader CAN activate R2R as of dotnet#131658 (merged 2026-08-07),
which records payloadSize/tableSize in boot config for streaming instantiation
and passes the full R2R import set when tableSize > 0. The page previously
said it could not, and that browser R2R ran under corerun exclusively. Rewrite
the comparison as two loaders that both work but discover sizes differently,
and keep a dated historical note since that claim was repeated elsewhere.
- The runtime pack's R2R CoreLib is now the remaining gap rather than one of
two: NotReadyYet still has no consumer, but wiring it is no longer blocked
behind boot-path work.
- The Int128/UInt128 miscompile is fixed by dotnet#131492 (merged 2026-08-12). Keep
the entry for its root cause, which generalises: S<N> resolves through a
size-keyed first-wins struct cache, so Int128 and Guid both spelled S16 and
shared a thunk sized for whichever arrived first.
- Soften the SignatureMapper 'V' slot-count item to needs-re-verification,
since dotnet#131492 reworked that encoding underneath the original observation.
- Note src/tests composite plumbing in the intro rather than implying nothing
exists.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2b684576-6420-4809-bf5b-d4d12072ef97
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-crossgen2-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@lewing@pavelsavara@AndyAyersMS@davidwrighton@jakobbotsch@tannergooding