Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions src/coreclr/tools/Common/JitInterface/WasmLowering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ namespace Internal.JitInterface
{
public static partial class WasmLowering
{
public static MethodSignature GetClosedStaticDelegateTargetSignature(MethodSignature signature)
{
Debug.Assert(!signature.IsStatic && !signature.IsExplicitThis);
Debug.Assert(signature.GenericParameterCount == 0);

TypeDesc[] arguments = new TypeDesc[signature.Length + 1];
arguments[0] = signature.Context.GetWellKnownType(WellKnownType.Object);
for (int argumentIndex = 0; argumentIndex < signature.Length; argumentIndex++)
{
arguments[argumentIndex + 1] = signature[argumentIndex];
}

return new MethodSignature(signature.Flags | MethodSignatureFlags.Static, 0, signature.ReturnType, arguments);
}

public static MethodSignature GetStringCtorActualSignature(MethodSignature signature)
{
Debug.Assert(signature.Context.GetWellKnownType(WellKnownType.String).GetMethod(".ctor"u8, signature) != null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,77 @@ public WasmArgumentLayoutTests(ITestOutputHelper output)
_output = output;
}

[Theory]
[InlineData("Byte", false)]
[InlineData("Int16", false)]
[InlineData("Int64", false)]
[InlineData("Byte", true)]
[InlineData("Int16", true)]
[InlineData("Int64", true)]
public void ClosedStaticDelegateSignaturePreservesArgumentLayout(string underlyingType, bool returnsStruct)
{
ReadyToRunCompilerContext context = CreateWasmContext();
TypeDesc objectType = context.GetWellKnownType(WellKnownType.Object);
DefType nullableType = context.SystemModule.GetType("System"u8, "Nullable`1"u8)
.MakeInstantiatedType(GetSystemType(context, underlyingType));
TypeDesc returnType = returnsStruct ? MakeAlignedEightBlob(context, 16) : objectType;
MethodSignature invokeSignature = new MethodSignature(0, 0, returnType, [objectType, objectType, nullableType]);

MethodSignature targetSignature = WasmLowering.GetClosedStaticDelegateTargetSignature(invokeSignature);
Assert.True(targetSignature.IsStatic);
Assert.Same(returnType, targetSignature.ReturnType);
Assert.Equal(4, targetSignature.Length);
Assert.Same(objectType, targetSignature[0]);
for (int argumentIndex = 0; argumentIndex < invokeSignature.Length; argumentIndex++)
{
Assert.Same(invokeSignature[argumentIndex], targetSignature[argumentIndex + 1]);
}

WasmSignature lowered = WasmLowering.GetSignature(targetSignature, WasmLowering.LoweringFlags.None);
Assert.Equal($"{(returnsStruct ? "S16" : "i")}iiiS{nullableType.InstanceFieldSize.AsInt}p", lowered.SignatureString);
Assert.Equal(returnsStruct ? 7 : 6, lowered.FuncType.Params.Types.Length);
MethodSignature raised = WasmLowering.RaiseSignature(lowered, context);
Assert.Equal(GetArgumentOffsets(context, targetSignature), GetArgumentOffsets(context, raised));
Assert.Equal(new[] { 0, 8, 16, 24 }, GetArgumentOffsets(context, targetSignature));
}

[Theory]
[InlineData(WasmLowering.LoweringFlags.None, "viiS16p", 5)]
[InlineData(WasmLowering.LoweringFlags.HasGenericContextArg, "viiiS16p", 6)]
[InlineData(WasmLowering.LoweringFlags.IsAsyncCall, "vaiiS16p", 6)]
[InlineData(WasmLowering.LoweringFlags.HasGenericContextArg | WasmLowering.LoweringFlags.IsAsyncCall, "viaiiS16p", 7)]
public void ClosedStaticDelegateSignaturePreservesHiddenArguments(WasmLowering.LoweringFlags flags, string expectedSignature, int expectedParameters)
{
ReadyToRunCompilerContext context = CreateWasmContext();
MethodSignature invokeSignature = new MethodSignature(0, 0, context.GetWellKnownType(WellKnownType.Void),
[context.GetWellKnownType(WellKnownType.Int32), MakeAlignedEightBlob(context, 16)]);

MethodSignature targetSignature = WasmLowering.GetClosedStaticDelegateTargetSignature(invokeSignature);
WasmSignature lowered = WasmLowering.GetSignature(targetSignature, flags);

Assert.Equal(expectedSignature, lowered.SignatureString);
Assert.Equal(expectedParameters, lowered.FuncType.Params.Types.Length);
Assert.True(lowered.FuncType.Returns.Types.IsEmpty);
}

[Theory]
[InlineData(MethodSignatureFlags.None)]
[InlineData(MethodSignatureFlags.CallingConventionVarargs)]
[InlineData(MethodSignatureFlags.UnmanagedCallingConventionCdecl)]
public void ClosedStaticDelegateSignaturePreservesCallingConvention(MethodSignatureFlags flags)
{
ReadyToRunCompilerContext context = CreateWasmContext();
TypeDesc objectType = context.GetWellKnownType(WellKnownType.Object);
MethodSignature invokeSignature = new MethodSignature(flags, 0, objectType, [objectType]);

MethodSignature targetSignature = WasmLowering.GetClosedStaticDelegateTargetSignature(invokeSignature);

Assert.Equal(flags | MethodSignatureFlags.Static, targetSignature.Flags);
Assert.Same(objectType, targetSignature.ReturnType);
Assert.Same(objectType, targetSignature[0]);
Assert.Same(objectType, targetSignature[1]);
}

public static TheoryData<string, WellKnownType> V128Types()
{
TheoryData<string, WellKnownType> data = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2548,6 +2548,19 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO
// by virtual resolution during getCallInfo (virtual resolution could find a result using type equivalence)
ValidateSafetyOfUsingTypeEquivalenceInSignature(targetMethod.GetTypicalMethodDefinition().Signature);

if (_compilation.NodeFactory.Target.IsWasm && targetMethod.OwningType.IsDelegate && targetMethod.Name == "Invoke"u8)
{
// The hidden-argument flags come from the resolved call signature: a shared generic
// delegate supplies its generic context through 'this', which the Invoke method's own
// instantiation flags do not reflect.
WasmLowering.LoweringFlags loweringFlags = WasmLowering.GetLoweringFlags(&pResult->sig);
Debug.Assert(!loweringFlags.HasFlag(WasmLowering.LoweringFlags.IsUnmanagedCallersOnly));

MethodSignature closedStaticSignature = WasmLowering.GetClosedStaticDelegateTargetSignature(targetMethod.Signature);
WasmSignature wasmSignature = WasmLowering.GetSignature(closedStaticSignature, loweringFlags);
AddAdditionalDependency(_compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSignature), "R2R-to-interpreter thunk for closed-static delegate target");
Comment thread
pavelsavara marked this conversation as resolved.
}

// OK, if the EE said we're not doing a stub dispatch then just return the kind to
// the caller. No other kinds of virtual calls have extra information attached.
switch (pResult->kind)
Expand Down
30 changes: 30 additions & 0 deletions src/libraries/Microsoft.CSharp/tests/DelegateInDynamicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,42 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq.Expressions;
using Xunit;

namespace Microsoft.CSharp.RuntimeBinder.Tests
{
public class DelegateInDynamicTests
{
[Theory]
[InlineData(null)]
[InlineData((short)0)]
[InlineData((short)7)]
public void ClosedStaticExpressionDelegateWithNullableArgument(short? value)
{
int bias = 37;
Expression<Func<short?, int>> expression = argument => argument.GetValueOrDefault() + bias;
Func<short?, int> compiled = expression.Compile();
Func<short?, int> openStatic = ReadNullable;
var holder = new NullableDelegateTarget<string>("target");
Func<short?, int> closedInstance = holder.Read;

Assert.Equal(value.GetValueOrDefault() + bias, compiled(value));
Assert.Equal(value.GetValueOrDefault() + 11, openStatic(value));
Assert.Equal(value.GetValueOrDefault() + 6, closedInstance(value));
}

private static int ReadNullable(short? value) => value.GetValueOrDefault() + 11;

private sealed class NullableDelegateTarget<T>
{
private readonly T _target;

public NullableDelegateTarget(T target) => _target = target;

public int Read(short? value) => value.GetValueOrDefault() + _target.ToString().Length;
}

[Fact]
public void DelegateInDynamicExplicitInvoke()
{
Expand Down
1 change: 0 additions & 1 deletion src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@

<ItemGroup Condition="'$(TargetOS)' == 'browser' and '$(RuntimeFlavor)' == 'CoreCLR' and '$(TestWasmReadyToRun)' == 'true' and '$(PublishReadyToRun)' != 'false' and '$(RunDisabledWasmTests)' != 'true'">
<!-- ActiveIssue https://github.com/dotnet/runtime/issues/134200 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.CSharp\tests\Microsoft.CSharp.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Dynamic.Runtime.Tests\System.Dynamic.Runtime.Tests.csproj" />
<!-- ActiveIssue https://github.com/dotnet/runtime/issues/134261 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Linq.Expressions\tests\System.Linq.Expressions.Tests.csproj" />
Expand Down
Loading