diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index f85c006c9a1d1a..b72c7381639da9 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -590,6 +590,8 @@ enum CorInfoHelpFunc CORINFO_HELP_ALLOC_CONTINUATION_METHOD, CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, // Fail fast for a foreign exception escaping a P/Invoke + CORINFO_HELP_COUNT, }; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index d922f09ff4025d..26a9f1c66e906f 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* aa3cece7-a5f9-4b4e-9309-852c8bd4bdb1 */ - 0xaa3cece7, - 0xa5f9, - 0x4b4e, - {0x93, 0x09, 0x85, 0x2c, 0x8b, 0xd4, 0xbd, 0xb1} +constexpr GUID JITEEVersionIdentifier = { /* 1a1df087-82dd-41fc-9171-8b1e93010bbe */ + 0x1a1df087, + 0x82dd, + 0x41fc, + {0x91, 0x71, 0x8b, 0x1e, 0x93, 0x01, 0x0b, 0xbe} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 7742945b23e7e4..c17863f496e119 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -369,6 +369,12 @@ DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_METHOD, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_METHOD) DYNAMICJITHELPER(CORINFO_HELP_ALLOC_CONTINUATION_CLASS, NULL, METHOD__ASYNC_HELPERS__ALLOC_CONTINUATION_CLASS) +#ifdef TARGET_WASM + JITHELPER(CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, JIT_ReportUnmanagedExceptionFromPInvoke, METHOD__NIL) +#else + JITHELPER(CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, NULL, METHOD__NIL) +#endif + #undef JITHELPER #undef DYNAMICJITHELPER #undef JITHELPER diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 993aec8e2f286a..9f7f2a28a126a1 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -392,6 +392,7 @@ enum ReadyToRunHelper READYTORUN_HELPER_GCPoll = 0x44, READYTORUN_HELPER_ReversePInvokeEnter = 0x45, READYTORUN_HELPER_ReversePInvokeExit = 0x46, + READYTORUN_HELPER_ReportUnmanagedExceptionFromPInvoke = 0x47, // Get string handle lazily READYTORUN_HELPER_GetString = 0x50, // No longer supported as of READYTORUN_MAJOR_VERSION 17.0 diff --git a/src/coreclr/inc/readytorunhelpers.h b/src/coreclr/inc/readytorunhelpers.h index 4c3fca575d0ae5..84802a84961ba1 100644 --- a/src/coreclr/inc/readytorunhelpers.h +++ b/src/coreclr/inc/readytorunhelpers.h @@ -116,6 +116,7 @@ HELPER(READYTORUN_HELPER_CheckedWriteBarrier_EBP, CORINFO_HELP_CHECKED_ASSIGN_ HELPER(READYTORUN_HELPER_PInvokeBegin, CORINFO_HELP_JIT_PINVOKE_BEGIN, ) HELPER(READYTORUN_HELPER_PInvokeEnd, CORINFO_HELP_JIT_PINVOKE_END, ) +HELPER(READYTORUN_HELPER_ReportUnmanagedExceptionFromPInvoke, CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, ) HELPER(READYTORUN_HELPER_GCPoll, CORINFO_HELP_POLL_GC, ) HELPER(READYTORUN_HELPER_ReversePInvokeEnter, CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, ) HELPER(READYTORUN_HELPER_ReversePInvokeExit, CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, ) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index ec05b92bf87e07..551c1c6b06a83e 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -3058,6 +3058,29 @@ void CodeGen::genCall(GenTreeCall* call) { regNumber thisReg = REG_NA; + WasmValueType callResultType = WasmValueType::Invalid; + if (call->IsUnmanaged()) + { + assert(!call->IsFastTailCall()); + + const var_types callRetType = genActualType(call); + if (!call->ShouldHaveRetBufArg() && (callRetType != TYP_VOID)) + { + callResultType = callRetType == TYP_STRUCT + ? TypeToWasmValueType(WasmClassifier::ToJitType( + m_compiler->info.compCompHnd->getWasmLowering(call->gtRetClsHnd))) + : ActualTypeToWasmValueType(callRetType); + } + + emitter* emit = GetEmitter(); + emit->emitIns_BlockTy(INS_block, callResultType); + emit->emitIns_BlockTy(INS_block, WasmValueType::ExnRef); + emit->emitIns_BlockTy(INS_block, WasmValueType::ExnRef); + emit->emitIns_Ty_I(INS_try_table, callResultType, 2); + emit->emitIns_I(INS_catch_ref, EA_4BYTE, 0); + emit->emitIns_I(INS_catch_all_ref, EA_4BYTE, 1); + } + if (call->NeedsNullCheck()) { CallArg* thisArg = call->gtArgs.GetThisArg(); @@ -3081,6 +3104,20 @@ void CodeGen::genCall(GenTreeCall* call) } genCallInstruction(call); + + if (call->IsUnmanaged()) + { + emitter* emit = GetEmitter(); + emit->emitIns(INS_end); + emit->emitIns_I(INS_br, EA_4BYTE, 2); + emit->emitIns(INS_end); + emit->emitIns(INS_throw_ref); + emit->emitIns(INS_end); + genEmitHelperCall(CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, 0, EA_UNKNOWN); + emit->emitIns(INS_unreachable); + emit->emitIns(INS_end); + } + WasmProduceReg(call); } @@ -3240,6 +3277,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) params.callType = EC_FUNC_TOKEN; genEmitCallWithCurrentGC(params); } + } //------------------------------------------------------------------------ @@ -3323,6 +3361,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, // RhBulkMoveWithWriteBarrier HELPER_SIG(CORINFO_HELP_BULK_WRITEBARRIER, UNMANAGED, CORINFO_WASM_TYPE_VOID /* retval */, CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I, CORINFO_WASM_TYPE_I); + HELPER_SIG(CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, UNMANAGED, + CORINFO_WASM_TYPE_VOID /* retval */); default: JITDUMP("Helper '%s' has no hard-coded signature\n", m_compiler->eeGetMethodFullName(params.methHnd)); unreached(); diff --git a/src/coreclr/jit/emitfmtswasm.h b/src/coreclr/jit/emitfmtswasm.h index bc185a1189e639..004edee0469d7c 100644 --- a/src/coreclr/jit/emitfmtswasm.h +++ b/src/coreclr/jit/emitfmtswasm.h @@ -48,6 +48,7 @@ IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // IF_DEF(TRY_TABLE, IS_NONE, NONE) // IF_DEF(CATCH_DECL, IS_NONE, NONE) // +IF_DEF(CATCH_ALL_DECL, IS_NONE, NONE) // IF_DEF(V128, IS_NONE, NONE) // <16 raw bytes> IF_DEF(LANE, IS_NONE, NONE) // IF_DEF(MEMARG_LANE, IS_NONE, NONE) // diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 86fb74235c5cd7..069639a4672c85 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -793,6 +793,14 @@ unsigned emitter::instrDesc::idCodeSize() const size += SizeOfULEB128(emitGetInsSC(this)); // control flow stack offset break; } + case IF_CATCH_ALL_DECL: + { + // no opcode, this is part of a try_table + + size = 1; // catch kind + size += SizeOfULEB128(emitGetInsSC(this)); // control flow stack offset + break; + } case IF_V128: size += 16; // 16 raw bytes for the v128 constant break; @@ -989,6 +997,13 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) dst += emitOutputULEB128(dst, (uint64_t)emitGetInsSC(id)); break; } + case IF_CATCH_ALL_DECL: + { + // Kind 3: catch_all_ref, followed by the control flow stack offset. + dst += emitOutputByte(dst, 3); + dst += emitOutputULEB128(dst, (uint64_t)emitGetInsSC(id)); + break; + } case IF_SLEB128: { assert(!id->idIsCnsReloc()); @@ -1451,6 +1466,12 @@ void emitter::emitDispIns( } break; + case IF_CATCH_ALL_DECL: + { + dispJumpTargetIfAny(); + } + break; + case IF_CODE_SIZE: { // We should either have a non-null ig parameter, or emitCurIG should be set diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 095a1d997b497a..755570bd513c7e 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -32,6 +32,7 @@ INST2(invalid, "INVALID", 0, IF_NONE, 0xFC, BA INST(unreachable, "unreachable", 0, IF_OPCODE, 0x00) INST(label, "label", 0, IF_RAW_ULEB128, 0x00) INST(catch_ref, "catch_ref", 0, IF_CATCH_DECL, 0x00) +INST(catch_all_ref, "catch_all_ref", 0, IF_CATCH_ALL_DECL, 0x00) INST(local_cnt, "local.cnt", 0, IF_RAW_ULEB128, 0x00) INST(local_decl, "local", 0, IF_LOCAL_DECL, 0x00) INST(code_size, "code.size", 0, IF_CODE_SIZE, 0x00) diff --git a/src/coreclr/jit/utils.cpp b/src/coreclr/jit/utils.cpp index 4f8a65595903b6..b628ac4538dd77 100644 --- a/src/coreclr/jit/utils.cpp +++ b/src/coreclr/jit/utils.cpp @@ -1743,6 +1743,7 @@ void HelperCallProperties::init() case CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT: case CORINFO_HELP_JIT_PINVOKE_BEGIN: case CORINFO_HELP_JIT_PINVOKE_END: + case CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE: exceptions = ExceptionSetFlags::None; break; diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index 31fdfa6218e5bd..a69dc3bcbfd88e 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -267,6 +267,7 @@ public enum ReadyToRunHelper GCPoll = 0x44, ReversePInvokeEnter = 0x45, ReversePInvokeExit = 0x46, + ReportUnmanagedExceptionFromPInvoke = 0x47, // Get string handle lazily GetString = 0x50, diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 68f7b52f6ec048..4ef4e7817e02d0 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -287,6 +287,8 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_ALLOC_CONTINUATION_METHOD, CORINFO_HELP_ALLOC_CONTINUATION_CLASS, + CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE, // Report a foreign exception escaping a P/Invoke + CORINFO_HELP_COUNT, } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs index 8c6140100a26ec..feef4b36e82619 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/JitHelper.cs @@ -276,6 +276,9 @@ public static void GetEntryPoint(TypeSystemContext context, ReadyToRunHelper id, case ReadyToRunHelper.PInvokeEnd: mangledName = "RhpPInvokeReturn"; break; + case ReadyToRunHelper.ReportUnmanagedExceptionFromPInvoke: + mangledName = "JIT_ReportUnmanagedExceptionFromPInvoke"; + break; case ReadyToRunHelper.ReversePInvokeEnter: mangledName = "RhpReversePInvoke"; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 4ec656fa67a58f..88ccd8feeebb45 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -1273,6 +1273,10 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) id = ReadyToRunHelper.PInvokeEnd; break; + case CorInfoHelpFunc.CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE: + id = ReadyToRunHelper.ReportUnmanagedExceptionFromPInvoke; + break; + case CorInfoHelpFunc.CORINFO_HELP_STACK_PROBE: id = ReadyToRunHelper.StackProbe; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs index 3965936cb063c7..91c4ea717153e6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunSignature.cs @@ -1786,6 +1786,10 @@ private void ParseHelper(StringBuilder builder) builder.Append("PINVOKE_END"); break; + case ReadyToRunHelper.ReportUnmanagedExceptionFromPInvoke: + builder.Append("REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE"); + break; + case ReadyToRunHelper.GCPoll: builder.Append("GCPOLL"); break; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 14f0f9b6c1e33a..1e790512dd82fd 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -742,6 +742,10 @@ private ISymbolNode GetHelperFtnUncached(CorInfoHelpFunc ftnNum) id = ReadyToRunHelper.PInvokeEnd; break; + case CorInfoHelpFunc.CORINFO_HELP_REPORT_UNMANAGED_EXCEPTION_FROM_PINVOKE: + id = ReadyToRunHelper.ReportUnmanagedExceptionFromPInvoke; + break; + case CorInfoHelpFunc.CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER: id = ReadyToRunHelper.ReversePInvokeEnter; break; diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 8ceb8647a238f0..2a587e2961bcd5 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -2177,6 +2177,9 @@ Thread * JIT_InitPInvokeFrame(InlinedCallFrame *pFrame) EXTERN_C void JIT_PInvokeBegin(InlinedCallFrame* pFrame); EXTERN_C void JIT_PInvokeEnd(InlinedCallFrame* pFrame); +#ifdef TARGET_WASM +EXTERN_C void DECLSPEC_NORETURN JIT_ReportUnmanagedExceptionFromPInvoke(); +#endif #ifdef DEBUGGING_SUPPORTED void DebuggerTraceCall(void* returnAddr, void* thunkDataMaybe) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 6eeec621843304..b6e9c9746e46c2 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -710,6 +710,14 @@ extern "C" void STDCALL GenericPInvokeCalliHelper(void) PORTABILITY_ASSERT("GenericPInvokeCalliHelper is not implemented on wasm"); } +EXTERN_C void DECLSPEC_NORETURN JIT_ReportUnmanagedExceptionFromPInvoke() +{ + EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE( + COR_E_FAILFAST, + W("Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition")); + UNREACHABLE(); +} + // Does the pinvoke frame transition; the naked wrappers below have already set the wasm // __stack_pointer global to callersStackPointer so it is safe to run native code here. EXTERN_C void JIT_PInvokeBeginImpl(uintptr_t callersStackPointer, InlinedCallFrame* pFrame) diff --git a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index c64eefc6650922..9461dab29fd443 100644 --- a/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -365,6 +365,64 @@ public async Task BuildNativeInNonEnglishCulture(Configuration config, bool aot, Assert.Contains("square: 25", result.TestOutput); } + [Theory] + [InlineData(Configuration.Debug)] + public async Task UnmanagedExceptionDoesNotUnwindThroughManagedCode(Configuration config) + { + if (!IsCoreClrRuntime) + return; + + const string nativeSource = """ + extern "C" int throw_unmanaged_exception(int value) + { + throw value; + } + """; + const string managedSource = """ + using System; + using System.Runtime.InteropServices; + + Console.WriteLine("TestOutput -> before P/Invoke"); + try + { + _ = ThrowUnmanagedException(42); + } + catch + { + Console.WriteLine("TestOutput -> managed catch"); + } + + Console.WriteLine("TestOutput -> after P/Invoke"); + return 42; + + [DllImport("unmanaged-exception", EntryPoint = "throw_unmanaged_exception")] + static extern int ThrowUnmanagedException(int value); + """; + + const string extraItems = """"""; + ProjectInfo info = CopyTestAsset( + config, + aot: false, + TestAsset.WasmBasicTestApp, + "unmanaged_exception_boundary", + extraProperties: "true", + extraItems: extraItems); + + File.WriteAllText(Path.Combine(_projectDir, "unmanaged-exception.cpp"), nativeSource); + File.WriteAllText(Path.Combine(_projectDir, "Common", "Program.cs"), managedSource); + ReplaceMainJsWithMinimalRunMain(); + + BuildProject(info, config, new BuildOptions(UseCache: false), isNativeBuild: true); + + RunResult result = await RunForBuildWithDotnetRun(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 1)); + + Assert.DoesNotContain(result.TestOutput, line => line.Contains("managed catch")); + Assert.DoesNotContain(result.TestOutput, line => line.Contains("after P/Invoke")); + } + private async Task EnsureWasmAbiRulesAreFollowed(Configuration config, bool aot) { var extraItems = @"";