Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@

#include <minipal/guid.h>

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
6 changes: 6 additions & 0 deletions src/coreclr/inc/jithelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/readytorun.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/readytorunhelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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, )
Expand Down
40 changes: 40 additions & 0 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,29 @@ void CodeGen::genCall(GenTreeCall* call)
{
regNumber thisReg = REG_NA;

WasmValueType callResultType = WasmValueType::Invalid;
if (call->IsUnmanaged())

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.

Does this work for QCalls?

We do make exception interop work for QCalls since we own both sides. QCalls can throw exception in C++ that automatically shows up as a regular managed exception in C#.

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.

What's the perf overhead of emitting explicit try/catch around every PInvoke? Instead of this, can we detect the unexcepted exception in the try/catch handler upstack?

{
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();
Expand All @@ -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);
}

Expand Down Expand Up @@ -3240,6 +3277,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
params.callType = EC_FUNC_TOKEN;
genEmitCallWithCurrentGC(params);
}

}

//------------------------------------------------------------------------
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/emitfmtswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ IF_DEF(CALL_INDIRECT, IS_NONE, NONE) // <opcode> <ULEB128 immediate> <ULEB128 im
IF_DEF(MEMIDX_MEMIDX, IS_NONE, NONE) // <memory index> <memory index>
IF_DEF(TRY_TABLE, IS_NONE, NONE) // <opcode> <sig = 0x40> <len = 0x01>
IF_DEF(CATCH_DECL, IS_NONE, NONE) // <catch-type> <ULEB128 immediate (type reloc)> <ULEB128 immediate>
IF_DEF(CATCH_ALL_DECL, IS_NONE, NONE) // <catch-type> <ULEB128 immediate>
IF_DEF(V128, IS_NONE, NONE) // <opcode> <16 raw bytes>
IF_DEF(LANE, IS_NONE, NONE) // <opcode> <u8 lane index>
IF_DEF(MEMARG_LANE, IS_NONE, NONE) // <opcode> <memarg> <u8 lane index>
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/jit/emitwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/instrswasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ public enum ReadyToRunHelper
GCPoll = 0x44,
ReversePInvokeEnter = 0x45,
ReversePInvokeExit = 0x46,
ReportUnmanagedExceptionFromPInvoke = 0x47,

// Get string handle lazily
GetString = 0x50,
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/jithelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/vm/wasm/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
58 changes: 58 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """<NativeFileReference Include="unmanaged-exception.cpp" />""";
ProjectInfo info = CopyTestAsset(
config,
aot: false,
TestAsset.WasmBasicTestApp,
"unmanaged_exception_boundary",
extraProperties: "<WasmBuildNative>true</WasmBuildNative>",
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 = @"<NativeFileReference Include=""wasm-abi.c"" />";
Expand Down
Loading