Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
09c2551
Initial plan
Copilot Sep 11, 2026
04fa490
Implement ReadyToRun helpers for WASM
Copilot Sep 11, 2026
b28278b
Apply suggestion from @jtschuster
jtschuster Sep 12, 2026
1f86558
Inline WASM helper return calls
Copilot Sep 12, 2026
beeb70d
Move WASM helper signature implementation
Copilot Sep 12, 2026
a618858
Generalize WASM signature dependencies
Copilot Sep 12, 2026
bbfadc8
Restore ReadyToRun dependency list flow
Copilot Sep 12, 2026
3bb277d
Initialize WASM helper signatures lazily
Copilot Sep 14, 2026
8888ff5
Simplify lazy WASM signature getter
Copilot Sep 14, 2026
ab9c8ca
Merge branch 'main' into copilot/implement-readytorunhelpernode-for-wasm
jtschuster Sep 14, 2026
26c2d28
Fix parameter count for ResolveVirtualFunction helper
jtschuster Sep 14, 2026
82510ef
Merge remote-tracking branch 'origin/main' into copilot/implement-rea…
Copilot Sep 14, 2026
f42811e
Apply batched suggestions from code review
jtschuster Sep 15, 2026
19a7f9a
Refactor wasmTypeNode assignment with type check
jtschuster Sep 15, 2026
29acc10
Make WASM signature initializer instance-based
Copilot Sep 15, 2026
eacaed1
Remove stack pointer arg from native export call
jtschuster Sep 15, 2026
38ce590
Remove duplicated WASM lowering logic and check IsArrayAddressMethod …
jtschuster Sep 15, 2026
38f2138
Fix AddressTakeMethodNode.HasGenericContextArg
jtschuster Sep 15, 2026
476bf49
Revert "Remove duplicated WASM lowering logic and check IsArrayAddres…
jtschuster Sep 15, 2026
d0e8b29
Fix IMethodCodeNodeWithTypeSignature.HasGenericContextArg
jtschuster Sep 15, 2026
4fc48b1
Fix formatting and remove unnecessary ternary
jtschuster Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ public interface INodeWithTypeSignature : ISymbolDefinitionNode

public interface IMethodCodeNodeWithTypeSignature : IMethodNode, INodeWithTypeSignature
{
// Keep methods aligned with WasmLowering.GetSignature(MethodDesc)
MethodSignature INodeWithTypeSignature.Signature => Method.Signature;
bool INodeWithTypeSignature.IsUnmanagedCallersOnly => Method.IsUnmanagedCallersOnly;
bool INodeWithTypeSignature.IsAsyncCall => Method.IsAsyncCall();
bool INodeWithTypeSignature.HasGenericContextArg => Method.RequiresInstMethodDescArg() || Method.RequiresInstMethodTableArg();
bool INodeWithTypeSignature.HasGenericContextArg => Method.RequiresInstMethodDescArg() || Method.RequiresInstMethodTableArg() || Method.IsArrayAddressMethod();
Comment thread
jtschuster marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@ public sealed override IEnumerable<DependencyListEntry> GetStaticDependencies(No
}
}

if (factory.Target.IsWasm && this is IMethodCodeNodeWithTypeSignature wasmMethodCodeNode)
if (factory.Target.IsWasm && this is INodeWithTypeSignature wasmCodeNode)
{
dependencies ??= new DependencyList();

WasmTypeNode wasmTypeNode = factory.WasmTypeNode(wasmMethodCodeNode.Method);
dependencies.Add(wasmTypeNode, "Wasm Method Code Nodes Require Signature");
WasmTypeNode wasmTypeNode = factory.WasmTypeNode(wasmCodeNode);
dependencies.Add(wasmTypeNode, "Wasm Code Nodes Require Signature");
}

if (dependencies == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public AddressTakenMethodNode(IMethodNode methodNode)
public override MethodSignature Signature => Method.Signature;
public override bool IsUnmanagedCallersOnly => Method.IsUnmanagedCallersOnly;
public override bool IsAsyncCall => Method.IsAsyncCall();
public override bool HasGenericContextArg => Method.RequiresInstMethodDescArg() || Method.RequiresInstMethodTableArg();
public override bool HasGenericContextArg => Method.RequiresInstMethodDescArg() || Method.RequiresInstMethodTableArg() || Method.IsArrayAddressMethod();

protected override string GetName(NodeFactory factory)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,11 @@ public WasmTypeNode WasmTypeNode(MethodDesc desc)
return _wasmTypeNodes.GetOrAdd(WasmLowering.GetSignature(desc).FuncType);
}

public WasmTypeNode WasmTypeNode(INodeWithTypeSignature node)
{
return _wasmTypeNodes.GetOrAdd(WasmLowering.GetSignature(node).FuncType);
Comment thread
jtschuster marked this conversation as resolved.
}

public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types)
{
return _wasmTypeNodes.GetOrAdd(WasmFuncType.FromCorInfoSignature(types));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,205 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;

using ILCompiler.DependencyAnalysis.Wasm;
using ILCompiler.ObjectWriter.WasmInstructions;

using Internal.JitInterface;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
{
public partial class ReadyToRunHelperNode
public partial class ReadyToRunHelperNode : INodeWithTypeSignature
{
private MethodSignature _signature;

public MethodSignature Signature => _signature ??= InitializeWasmSignature();
public bool IsUnmanagedCallersOnly => false;
public bool IsAsyncCall => false;
public bool HasGenericContextArg => false;

private MethodSignature InitializeWasmSignature()
{
TypeSystemContext context = Id switch
{
ReadyToRunHelperId.DelegateCtor => ((DelegateCreationInfo)Target).DelegateType.Context,
_ => ((TypeSystemEntity)Target).Context,
};

TypeDesc nativeIntType = context.GetWellKnownType(WellKnownType.IntPtr);
TypeDesc[] parameters = Id switch
{
ReadyToRunHelperId.DelegateCtor => [nativeIntType, nativeIntType],
ReadyToRunHelperId.ResolveVirtualFunction => [nativeIntType],
_ => [],
};
TypeDesc returnType = Id == ReadyToRunHelperId.DelegateCtor ?
context.GetWellKnownType(WellKnownType.Void) : nativeIntType;

return new MethodSignature(MethodSignatureFlags.Static, genericParameterCount: 0, returnType, parameters);
}

protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly)
{
throw new NotImplementedException();
Debug.Assert(!encoder.Is64Bit);

List<WasmExpr> expressions = new List<WasmExpr>();

switch (Id)
{
case ReadyToRunHelperId.GetNonGCStaticBase:
{
MetadataType target = (MetadataType)Target;
ISymbolNode staticBase = factory.TypeNonGCStaticsSymbol(target);

if (!factory.PreinitializationManager.HasLazyStaticConstructor(target))
{
expressions.Add(I32.ConstRVA(staticBase));
Comment thread
jtschuster marked this conversation as resolved.
}
else
{
ISymbolNode helper = factory.HelperEntrypoint(HelperEntrypoint.EnsureClassConstructorRunAndReturnNonGCStaticBase);
expressions.Add(Local.Get(0));
expressions.Add(I32.ConstRVA(staticBase));
expressions.Add(I32.Const(NonGCStaticsNode.GetClassConstructorContextSize(factory.Target)));
expressions.Add(I32.Sub);
expressions.Add(I32.ConstRVA(staticBase));
expressions.Add(ControlFlow.ReturnCall(helper));
Comment thread
jtschuster marked this conversation as resolved.
}
}
break;

case ReadyToRunHelperId.GetThreadStaticBase:
{
MetadataType target = (MetadataType)Target;
ISortableSymbolNode index = factory.TypeThreadStaticIndex(target);
if (index is TypeThreadStaticIndexNode ti && ti.IsInlined)
{
throw new NotImplementedException();
Comment thread
jtschuster marked this conversation as resolved.
}

expressions.Add(Local.Get(0));
expressions.Add(I32.ConstRVA(index));
expressions.Add(I32.Load(0));
expressions.Add(I32.ConstRVA(index));
expressions.Add(I32.Load((ulong)factory.Target.PointerSize));

ISymbolNode helper;
if (!factory.PreinitializationManager.HasLazyStaticConstructor(target))
{
helper = factory.HelperEntrypoint(HelperEntrypoint.GetThreadStaticBaseForType);
}
else
{
ISymbolNode staticBase = factory.TypeNonGCStaticsSymbol(target);
expressions.Add(I32.ConstRVA(staticBase));
expressions.Add(I32.Const(NonGCStaticsNode.GetClassConstructorContextSize(factory.Target)));
expressions.Add(I32.Sub);
helper = factory.HelperEntrypoint(HelperEntrypoint.EnsureClassConstructorRunAndReturnThreadStaticBase);
}

expressions.Add(ControlFlow.ReturnCall(helper));
}
break;

case ReadyToRunHelperId.GetGCStaticBase:
{
MetadataType target = (MetadataType)Target;
ISymbolNode gcStaticBase = factory.TypeGCStaticsSymbol(target);

if (!factory.PreinitializationManager.HasLazyStaticConstructor(target))
{
expressions.Add(I32.ConstRVA(gcStaticBase));
expressions.Add(I32.Load(0));
}
else
{
ISymbolNode nonGCStaticBase = factory.TypeNonGCStaticsSymbol(target);
ISymbolNode helper = factory.HelperEntrypoint(HelperEntrypoint.EnsureClassConstructorRunAndReturnGCStaticBase);
expressions.Add(Local.Get(0));
expressions.Add(I32.ConstRVA(nonGCStaticBase));
expressions.Add(I32.Const(NonGCStaticsNode.GetClassConstructorContextSize(factory.Target)));
expressions.Add(I32.Sub);
expressions.Add(I32.ConstRVA(gcStaticBase));
expressions.Add(I32.Load(0));
expressions.Add(ControlFlow.ReturnCall(helper));
}
}
break;

case ReadyToRunHelperId.DelegateCtor:
{
DelegateCreationInfo target = (DelegateCreationInfo)Target;

expressions.Add(Local.Get(0));
expressions.Add(Local.Get(1));
expressions.Add(Local.Get(2));

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));
expressions.Add(Local.Get(2));
expressions.Add(I32.Load(0));

int slot = 0;
if (!relocsOnly)
{
slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, target.TargetMethod, target.TargetMethod.OwningType);
}

Debug.Assert(slot != -1);
expressions.Add(I32.Load((ulong)(EETypeNode.GetVTableOffset(factory.Target.PointerSize) + (slot * factory.Target.PointerSize))));
}
else
{
expressions.Add(I32.ConstRVA(target.GetTargetNode(factory)));
Comment thread
jtschuster marked this conversation as resolved.
}

if (target.Thunk is not null)
{
Debug.Assert(target.Constructor.Method.Signature.Length == 3);
expressions.Add(I32.ConstRVA(target.Thunk));
}
else
{
Debug.Assert(target.Constructor.Method.Signature.Length == 2);
}

expressions.Add(ControlFlow.ReturnCall(target.Constructor));
}
break;

case ReadyToRunHelperId.ResolveVirtualFunction:
{
MethodDesc targetMethod = (MethodDesc)Target;
if (targetMethod.OwningType.IsInterface)
{
ISymbolNode helper = factory.ExternFunctionSymbol(s_RhpResolveInterfaceMethod);
expressions.Add(Local.Get(1));
expressions.Add(I32.ConstRVA(factory.DispatchCell(targetMethod)));
expressions.Add(ControlFlow.ReturnCall(helper));
Comment thread
jtschuster marked this conversation as resolved.
}
else if (!relocsOnly)
{
expressions.Add(Local.Get(1));
expressions.Add(I32.Load(0));

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));
int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
expressions.Add(I32.Load((ulong)(EETypeNode.GetVTableOffset(factory.Target.PointerSize) + (slot * factory.Target.PointerSize))));
}
}
break;

default:
throw new NotImplementedException();
Comment thread
MichalStrehovsky marked this conversation as resolved.
}

encoder.FunctionBody = new WasmFunctionBody(WasmLowering.GetSignature(this).FuncType, expressions.ToArray());
Comment thread
jtschuster marked this conversation as resolved.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,11 @@ public WasmTypeNode WasmTypeNode(MethodDesc method)
return _wasmTypeNodes.GetOrAdd(funcType);
}

public WasmTypeNode WasmTypeNode(INodeWithTypeSignature node)
{
return _wasmTypeNodes.GetOrAdd(WasmLowering.GetSignature(node).FuncType);
}

internal WasmMethodRelativeVirtualIPNode WasmMethodRelativeVirtualIP(MethodWithGCInfo method)
{
return _wasmMethodRelativeVirtualIPs.GetOrAdd(method);
Expand Down
Loading