diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index ae143cac6f37e3..dab10296c1c529 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -680,7 +680,7 @@ public enum LoweringFlags public static WasmSignature GetSignature(MethodSignature signature, LoweringFlags flags) { - if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly) && signature.Flags.HasFlag(MethodSignatureFlags.UnmanagedCallingConvention)) + if ((signature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) is not 0 and not MethodSignatureFlags.CallingConventionVarargs) { flags |= LoweringFlags.IsUnmanagedCallersOnly; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs index c0a77643aa0ff2..b11ea54aef5090 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/WasmArgumentLayoutTests.cs @@ -270,6 +270,22 @@ private static MethodDesc GetTaskReturningCoreLibMethod(ReadyToRunCompilerContex throw new InvalidOperationException($"No single-argument {type.Name.ToString()}.{methodName} overload in System.Private.CoreLib"); } + [Theory] + [InlineData(MethodSignatureFlags.None, "iip")] + [InlineData(MethodSignatureFlags.CallingConventionVarargs, "iip")] + [InlineData(MethodSignatureFlags.UnmanagedCallingConventionCdecl, "ii")] + [InlineData(MethodSignatureFlags.UnmanagedCallingConventionStdCall, "ii")] + [InlineData(MethodSignatureFlags.UnmanagedCallingConventionThisCall, "ii")] + [InlineData(MethodSignatureFlags.UnmanagedCallingConvention, "ii")] + public void SignatureCallingConventionSelectsLowering(MethodSignatureFlags callingConvention, string expectedSignature) + { + ReadyToRunCompilerContext context = CreateWasmContext(); + TypeDesc int32Type = context.GetWellKnownType(WellKnownType.Int32); + MethodSignature signature = new MethodSignature(callingConvention | MethodSignatureFlags.Static, 0, int32Type, [int32Type]); + + Assert.Equal(expectedSignature, WasmLowering.GetSignature(signature, WasmLowering.LoweringFlags.None).SignatureString); + } + [Theory] [InlineData(MethodSignatureFlags.None)] [InlineData(MethodSignatureFlags.CallingConventionVarargs)] @@ -887,28 +903,6 @@ public static class Exports AssertPortableCallHelpersGeneratorRejects(source, $"has unsupported signature token '{expectedToken}'"); } - [Fact] - public void PortableCallHelpersGeneratorRejectsHiddenReturnBufferCallbacks() - { - string source = """ - using System.Runtime.InteropServices; - - public struct Pair - { - public int First; - public int Second; - } - - public static class Exports - { - [UnmanagedCallersOnly(EntryPoint = "callback")] - public static Pair Handle(int value) => default; - } - """; - - AssertPortableCallHelpersGeneratorRejects(source, "uses a hidden return buffer"); - } - private void AssertPortableCallHelpersGeneratorRejects(string source, string expectedError) { string workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); @@ -1028,6 +1022,153 @@ public static class Exports } } + /// + /// The interpreter looks up a thunk for each unmanaged calli by signature, so the generator + /// has to emit one for every function pointer signature an app calls through, whatever the + /// calling convention spelling. + /// + [Fact] + public void PortableCallHelpersGeneratorEmitsThunksForUnmanagedCalliSites() + { + string source = """ + public struct FloatPair + { + public float X; + public float Y; + } + + public static unsafe class Calls + { + public static FloatPair MemberFunction(delegate* unmanaged[Cdecl, MemberFunction] fn) + => fn(null, 1); + + public static int ThisCall(delegate* unmanaged[Thiscall] fn) + => fn(null, 1, 2, 3); + + public static double Managed(delegate* fn) + => fn(1, 2, 3, 4, 5, 6); + + public static T Generic(delegate* unmanaged fn) + => fn(); + } + """; + + string workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(workingDirectory); + + try + { + string inputAssembly = CompileCallbackAssembly(source, Path.Combine(workingDirectory, "Calls.dll"), allowUnsafe: true); + string outputDirectory = Path.Combine(workingDirectory, "generated"); + + var options = new PortableCallHelpersGeneratorOptions + { + OutputDirectory = outputDirectory, + TargetOS = "browser", + PInvokeModules = new[] { "libSystem.Native" }, + }; + + var log = new StringWriter(); + int exitCode = PortableCallHelpersGenerator.Run( + CreateWasmContext(inputAssembly), options, new Logger(log, isVerbose: true)); + + Assert.True(exitCode == 0, log.ToString()); + + string thunks = File.ReadAllText(Path.Combine(outputDirectory, "callhelpers-interp-to-managed.cpp")); + Assert.Contains("\"MS8il\"", thunks); + Assert.Contains("\"Miiddd\"", thunks); + Assert.DoesNotContain("\"Mdiiiiii\"", thunks); + } + finally + { + try + { + Directory.Delete(workingDirectory, recursive: true); + } + catch (Exception e) when (e is IOException or UnauthorizedAccessException) + { + } + } + } + + /// + /// Native code calls a reverse thunk with the wasm C ABI, which returns a struct it cannot pass as a + /// single scalar through a hidden leading pointer instead of a return value. The thunk has to take + /// that pointer and hand it to the interpreter as the return buffer, or the indirect call from + /// native code traps with a signature mismatch. + /// + [Fact] + public void PortableCallHelpersGeneratorReturnsStructsThroughHiddenPointerInReverseThunks() + { + string source = """ + using System.Runtime.InteropServices; + + public struct FloatPair + { + public float X; + public float Y; + } + + public struct Width + { + public float Value; + } + + public static class Callbacks + { + [UnmanagedCallersOnly] + public static FloatPair GetPair(nint self, int unused) => default; + + [UnmanagedCallersOnly] + public static Width GetWidth(nint self) => default; + + [UnmanagedCallersOnly(EntryPoint = "export_pair")] + public static FloatPair ExportPair(int value) => default; + } + """; + + string workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(workingDirectory); + + try + { + string inputAssembly = CompileCallbackAssembly(source, Path.Combine(workingDirectory, "Callbacks.dll")); + string outputDirectory = Path.Combine(workingDirectory, "generated"); + + var options = new PortableCallHelpersGeneratorOptions + { + OutputDirectory = outputDirectory, + TargetOS = "browser", + PInvokeModules = new[] { "libSystem.Native" }, + }; + + var log = new StringWriter(); + int exitCode = PortableCallHelpersGenerator.Run( + CreateWasmContext(inputAssembly), options, new Logger(log, isVerbose: false)); + + Assert.True(exitCode == 0, log.ToString()); + + string reverse = File.ReadAllText(Path.Combine(outputDirectory, "callhelpers-reverse.cpp")); + Assert.Contains("static void Call_Callbacks__Callbacks_GetPair_I32_I32_RetS8(void * sret, void * arg0, int32_t arg1)", reverse); + Assert.Contains("static float Call_Callbacks__Callbacks_GetWidth_I32_RetF32(void * arg0)", reverse); + Assert.Contains("extern \"C\" void export_pair(void * sret, int32_t arg0)", reverse); + Assert.Contains("Call_Callbacks__Callbacks_ExportPair_I32_RetS8(sret, arg0);", reverse); + Assert.Contains("(int8_t*)sret, (PCODE)&Call_Callbacks__Callbacks_GetPair_I32_I32_RetS8", reverse); + Assert.Contains("((void(*)(void *, int32_t))r2r)(sret, arg0);", reverse); + Assert.DoesNotContain("RetS8(void * arg", reverse); + } + finally + { + try + { + Directory.Delete(workingDirectory, recursive: true); + } + catch (Exception e) when (e is IOException or UnauthorizedAccessException) + { + } + } + } + /// /// The generated wrapper only needs to reach the R2R native entrypoint for callbacks exported by /// name: native code calls the extern "C" export (and thus the wrapper) directly. Every other @@ -1101,13 +1242,13 @@ public static class Exports /// Builds an input assembly for the generator to scan. It references the same CoreLib the context /// reads, so the attributes it applies are the ones the type system will resolve. /// - private static string CompileCallbackAssembly(string source, string outputPath) + private static string CompileCallbackAssembly(string source, string outputPath, bool allowUnsafe = false) { CSharpCompilation compilation = CSharpCompilation.Create( Path.GetFileNameWithoutExtension(outputPath), new[] { CSharpSyntaxTree.ParseText(source) }, new[] { MetadataReference.CreateFromFile(TestPaths.SystemPrivateCoreLibPath) }, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe)); EmitResult result = compilation.Emit(outputPath); Assert.True(result.Success, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeCollector.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeCollector.cs index f83b04e7a8b89c..4b619929dfb3e0 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeCollector.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeCollector.cs @@ -6,6 +6,7 @@ using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; +using Internal.IL; using Internal.JitInterface; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -138,6 +139,7 @@ public void CollectPInvokes(List pinvokes, List ca CollectPInvokesForMethod(method); if (IsMethodCallback(method)) callbacks.Add(new PInvokeCallback(method)); + CollectUnmanagedCalliSignatures(signatures, method); } catch (Exception ex) when (ex is not LogAsErrorException) { @@ -178,6 +180,80 @@ void CollectPInvokesForMethod(EcmaMethod method) } } + /// + /// Adds the signature of every calli through an unmanaged function pointer in the + /// method's body. The interpreter calls those through the same thunks as P/Invokes, and a + /// function pointer's signature appears nowhere else the scan looks. + /// + private void CollectUnmanagedCalliSignatures(Dictionary signatures, EcmaMethod method) + { + if (method.IsPInvoke || method.IsAbstract) + return; + + MethodIL methodIL = EcmaMethodIL.Create(method); + if (methodIL is null) + return; + + ILReader reader = new ILReader(methodIL.GetILBytes()); + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + if (opcode != ILOpcode.calli) + { + reader.Skip(opcode); + continue; + } + + if (methodIL.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) is not MethodSignature calliSignature + || (calliSignature.Flags & MethodSignatureFlags.UnmanagedCallingConventionMask) is 0 or MethodSignatureFlags.CallingConventionVarargs) + { + continue; + } + + if (IsUnsupportedOnPlatform(method)) + return; + + // A signature over the enclosing generic parameters has no single native shape. + if (ContainsSignatureVariables(calliSignature)) + { + log.Verbose($"Skipping generic unmanaged calli in '{method.OwningType}.{method.Name.ToString()}'"); + continue; + } + + string signature; + try + { + signature = WasmLowering.GetSignature(calliSignature, WasmLowering.LoweringFlags.None).SignatureString; + foreach (string token in InteropSignature.ParseSignatureTokens(signature)) + InteropSignature.TokenToNativeType(token); + } + catch (Exception ex) when (ex is InvalidSignatureCharException or LogAsErrorException or TypeSystemException) + { + // A P/Invoke with such a signature is a build error, but a calli is only a call + // site that may never run. Leave it to fail at run time rather than fail the build. + log.Verbose($"Skipping unmanaged calli in '{method.OwningType}.{method.Name.ToString()}': {ex.Message}"); + continue; + } + + if (signatures.TryAdd(signature, method)) + log.Verbose($"Adding calli signature {signature} for method '{method.OwningType}.{method.Name.ToString()}'"); + } + + static bool ContainsSignatureVariables(MethodSignature signature) + { + if (signature.ReturnType.ContainsSignatureVariables()) + return true; + + for (int i = 0; i < signature.Length; i++) + { + if (signature[i].ContainsSignatureVariables()) + return true; + } + + return false; + } + } + private void AddSignature(Dictionary signatures, MethodDesc method, WasmLowering.LoweringFlags flags, string kind) { string signature = InteropSignature.GetMethodSignature(method, flags); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeTableGenerator.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeTableGenerator.cs index a3a8a9cd391f69..0dcfbfdd982248 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeTableGenerator.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeTableGenerator.cs @@ -294,8 +294,18 @@ public void EmitNativeToInterp(TextWriter w, List callbacks) ? $"\n\n int64_t args[{parameterCount}] = {{ {string.Join(", ", Enumerable.Range(0, parameterCount).Select(i => CarriesBits(i) ? "0" : $"(int64_t)arg{i}"))} }};" + string.Concat(Enumerable.Range(0, parameterCount).Where(CarriesBits).Select(i => $"\n memcpy(&args[{i}], &arg{i}, sizeof(arg{i}));")) : string.Empty; - string parametersDeclaration = string.Join(", ", parameterCTypes.Select((p, i) => $"{p} arg{i}")); - string arguments = string.Join(", ", Enumerable.Range(0, parameterCount).Select(i => $"arg{i}")); + // A struct the wasm C ABI returns by reference arrives as a hidden leading pointer to + // caller-owned storage, with no return value, so the interpreter writes straight into it. + bool returnsViaBuffer = IsPassedByReference(cb.ReturnType); + bool hasResult = !cb.IsVoid && !returnsViaBuffer; + string returnCType = returnsViaBuffer ? "void" : MapType(cb.ReturnType); + string retArg = returnsViaBuffer ? "(int8_t*)sret" : hasResult ? "(int8_t*)&result" : "nullptr"; + string parametersDeclaration = string.Join(", ", + (returnsViaBuffer ? ["void * sret"] : Array.Empty()) + .Concat(parameterCTypes.Select((p, i) => $"{p} arg{i}"))); + string arguments = string.Join(", ", + (returnsViaBuffer ? ["sret"] : Array.Empty()) + .Concat(Enumerable.Range(0, parameterCount).Select(i => $"arg{i}"))); // A partial R2R image can compile an UnmanagedCallersOnly callback to native code. That R2R // code is the directly-callable native entrypoint (same ABI as this wrapper's parameters), so @@ -306,10 +316,11 @@ public void EmitNativeToInterp(TextWriter w, List callbacks) // other reverse thunk is handed out by the runtime through GetUnmanagedCallersOnlyThunk, which // already returns the R2R native code directly and only falls back to this interpreter wrapper // when the method has no native code -- so the dispatch would be dead code there. - string paramTypesOnly = string.Join(", ", parameterCTypes); - string r2rDispatch = cb.IsVoid - ? $"((void(*)({paramTypesOnly}))r2r)({arguments});{w.NewLine} return;" - : $"return (({MapType(cb.ReturnType)}(*)({paramTypesOnly}))r2r)({arguments});"; + string paramTypesOnly = string.Join(", ", + (returnsViaBuffer ? ["void *"] : Array.Empty()).Concat(parameterCTypes)); + string r2rDispatch = hasResult + ? $"return (({returnCType}(*)({paramTypesOnly}))r2r)({arguments});" + : $"((void(*)({paramTypesOnly}))r2r)({arguments});{w.NewLine} return;"; // Cache the resolved entrypoint in a per-callback static, published with acquire/release // atomics: these are native entry points that can be entered concurrently, and the value is // computed identically on every call, so the racing read/write is benign but must not tear. @@ -336,9 +347,9 @@ public void EmitNativeToInterp(TextWriter w, List callbacks) $$""" - extern "C" {{MapType(cb.ReturnType)}} {{cb.EntryPoint}}({{parametersDeclaration}}) + extern "C" {{returnCType}} {{cb.EntryPoint}}({{parametersDeclaration}}) { - {{(cb.IsVoid ? "" : "return ")}}Call_{{cb.EntrySymbol}}({{arguments}}); + {{(hasResult ? "return " : "")}}Call_{{cb.EntrySymbol}}({{arguments}}); } """ : string.Empty; w.Write( @@ -346,16 +357,16 @@ public void EmitNativeToInterp(TextWriter w, List callbacks) static MethodDesc* MD_{{cb.EntrySymbol}} = nullptr;{{r2rStaticDecl}} static {{ - MapType(cb.ReturnType)}} Call_{{cb.EntrySymbol}}({{parametersDeclaration}}) + returnCType}} Call_{{cb.EntrySymbol}}({{parametersDeclaration}}) { // Lazy lookup of MethodDesc for the function export scenario. if (!MD_{{cb.EntrySymbol}}) { LookupUnmanagedCallersOnlyMethodByName("{{cb.TypeFullName}}, {{cb.AssemblyName}}", "{{cb.MethodName}}", &MD_{{cb.EntrySymbol}}); }{{r2rSection}}{{argsDeclaration}}{{ - (!cb.IsVoid ? $"{w.NewLine} {MapType(cb.ReturnType)} result;" : "")}} - ExecuteInterpretedMethodFromUnmanaged(MD_{{cb.EntrySymbol}}, {{argsArgs}}, {{(cb.IsVoid ? "nullptr" : "(int8_t*)&result")}}, (PCODE)&Call_{{cb.EntrySymbol}});{{ - (!cb.IsVoid ? $"{w.NewLine} return result;" : "")}} + (hasResult ? $"{w.NewLine} {returnCType} result;" : "")}} + ExecuteInterpretedMethodFromUnmanaged(MD_{{cb.EntrySymbol}}, {{argsArgs}}, {{retArg}}, (PCODE)&Call_{{cb.EntrySymbol}});{{ + (hasResult ? $"{w.NewLine} return result;" : "")}} }{{exportFunction}} """); @@ -424,12 +435,6 @@ static void RejectUnsupportedCallbackSignature(PInvokeCallback cb) $"UnmanagedCallersOnly callback '{cb.Method}' has unsupported signature token '{token}', which the generated native wrapper does not support."); } - if (!cb.IsVoid && IsPassedByReference(cb.ReturnType)) - { - throw new LogAsErrorException( - $"UnmanagedCallersOnly callback '{cb.Method}' has return type '{cb.ReturnType}' that uses a hidden return buffer, which the generated native wrapper does not support."); - } - static bool IsUnsupportedToken(string token) => token is "V" || InteropSignature.IsMultiSlotToken(token); } diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index ab5d5c900d7e16..064c2679f9d396 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1276,6 +1276,7 @@ namespace case IMAGE_CEE_CS_CALLCONV_DEFAULT: case IMAGE_CEE_CS_CALLCONV_C: case IMAGE_CEE_CS_CALLCONV_STDCALL: + case IMAGE_CEE_CS_CALLCONV_THISCALL: case IMAGE_CEE_CS_CALLCONV_FASTCALL: case IMAGE_CEE_CS_CALLCONV_UNMANAGED: break; diff --git a/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.cs b/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.cs index 99d4ad905f22bd..038d5853e50f4d 100644 --- a/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.cs +++ b/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.cs @@ -7,7 +7,6 @@ using Xunit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using TestLibrary; unsafe class CdeclMemberFunctionNative { @@ -68,7 +67,6 @@ public unsafe class CdeclMemberFunctionTest { [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/50440", TestPlatforms.Windows, runtimes: TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/131811", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] public static int TestEntryPoint() { try diff --git a/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.csproj b/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.csproj index a55d78d80bc6a8..00eaa467d6d71d 100644 --- a/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.csproj +++ b/src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.csproj @@ -1,8 +1,5 @@ - - false true true diff --git a/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.cs b/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.cs index 8d8a03acc117c9..f8ed6ff7dc95a5 100644 --- a/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.cs +++ b/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.cs @@ -67,7 +67,6 @@ public enum E : uint public unsafe class PlatformDefaultMemberFunctionTest { [ActiveIssue("https://github.com/dotnet/runtime/issues/50440", TestPlatforms.Windows, runtimes: TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/131811", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] [Fact] public static int TestEntryPoint() { diff --git a/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.csproj b/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.csproj index a55d78d80bc6a8..00eaa467d6d71d 100644 --- a/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.csproj +++ b/src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.csproj @@ -1,8 +1,5 @@ - - false true true diff --git a/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.cs b/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.cs index 5bd075f8d54d0c..7b9cb8b7638229 100644 --- a/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.cs +++ b/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.cs @@ -7,7 +7,6 @@ using Xunit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using TestLibrary; unsafe class StdCallMemberFunctionNative { @@ -68,7 +67,6 @@ public unsafe class StdCallMemberFunctionTest { [Fact] [ActiveIssue("https://github.com/dotnet/runtime/issues/50440", TestPlatforms.Windows, runtimes: TestRuntimes.Mono)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/131811", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] public static int TestEntryPoint() { try diff --git a/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.csproj b/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.csproj index a55d78d80bc6a8..00eaa467d6d71d 100644 --- a/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.csproj +++ b/src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.csproj @@ -1,8 +1,5 @@ - - false true true diff --git a/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.cs b/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.cs index c0cd4b09813020..06dc917a6ae08b 100644 --- a/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.cs +++ b/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.cs @@ -95,11 +95,16 @@ public static int TestEntryPoint() Test4ByteNonHFA(instance); TestEnum(instance); TestCLong(instance); - Test8ByteHFAReverse(); - Test4ByteHFAReverse(); - Test4ByteNonHFAReverse(); - TestEnumReverse(); - TestCLongReverse(); + // Marshal.GetFunctionPointerForDelegate needs a stub created at run time, which CoreCLR + // on wasm cannot allocate. + if (!PlatformDetection.IsWasm) + { + Test8ByteHFAReverse(); + Test4ByteHFAReverse(); + Test4ByteNonHFAReverse(); + TestEnumReverse(); + TestCLongReverse(); + } Test8ByteHFAUnmanagedCallersOnly(); Test4ByteHFAUnmanagedCallersOnly(); Test4ByteNonHFAUnmanagedCallersOnly(); diff --git a/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.csproj b/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.csproj index 9dc3602543121d..c943b6059364df 100644 --- a/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.csproj +++ b/src/tests/JIT/Directed/callconv/ThisCall/ThisCallTest.csproj @@ -1,10 +1,5 @@ - - false - - true true true