diff --git a/src/coreclr/build-runtime.cmd b/src/coreclr/build-runtime.cmd
index 4374e13face5ae..77788bbf6c21ff 100644
--- a/src/coreclr/build-runtime.cmd
+++ b/src/coreclr/build-runtime.cmd
@@ -281,6 +281,9 @@ if "%__TargetOS%"=="android" (
if "%__TargetOS%"=="browser" (
set __CrossTarget=1
)
+if "%__TargetOS%"=="wasi" (
+ set __CrossTarget=1
+)
if %__CrossTarget% EQU 0 (
call "%__RepoRootDir%\eng\native\version\copy_version_files.cmd"
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 358466276e92cd..d4358f6a4f6659 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs
@@ -862,14 +862,22 @@ public void CompileMethod(MethodWithGCInfo methodCodeNodeNeedingCode, Logger log
}
}
- // For managed methods on Wasm, add an interpreter-to-R2R thunk so the
- // interpreter can call into this R2R-compiled function.
+ // For managed methods on Wasm, add both interpreter transition thunks for this
+ // method's signature. The interpreter-to-R2R thunk lets the interpreter call into
+ // this R2R function. The R2R-to-interpreter thunk is keyed by signature, not method:
+ // a method of the same shape that runs interpreted must be enterable from R2R (via a
+ // function pointer, delegate, virtual slot, or the interpreter's own
+ // GetMultiCallableAddrOfCode path), and its thunk is not otherwise rooted unless an
+ // R2R call site happens to share the signature.
if (_compilation.NodeFactory.Target.IsWasm && !MethodBeingCompiled.IsUnmanagedCallersOnly)
{
WasmSignature wasmSig = WasmLowering.GetSignature(MethodBeingCompiled);
AddAdditionalDependency(
_compilation.NodeFactory.WasmInterpreterToR2RThunk(wasmSig),
"Interpreter-to-R2R thunk for compiled method");
+ AddAdditionalDependency(
+ _compilation.NodeFactory.WasmR2RToInterpreterThunk(wasmSig),
+ "R2R-to-interpreter thunk for compiled method signature");
}
var compilationResult = CompileMethodInternal(methodCodeNodeNeedingCode, methodIL);
diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp
index 05ad28e3b94eea..2a41aac02f38d6 100644
--- a/src/coreclr/vm/wasm/helpers.cpp
+++ b/src/coreclr/vm/wasm/helpers.cpp
@@ -14,357 +14,6 @@
#define WASM_STRINGIFY_HELPER(value) #value
#define WASM_STRINGIFY(value) WASM_STRINGIFY_HELPER(value)
-void ExecuteInterpretedMethodWithArgs_PortableEntryPoint(PCODE portableEntrypoint, TransitionBlock* block, size_t argsSize, int8_t* retBuff);
-
-// -------------------------------------------------
-// Logic that will eventually mostly be pregenerated for R2R to interpreter code
-// -------------------------------------------------
-namespace
-{
- FCDECL0(void, CallInterpreter_RetVoid);
- WASM_CALLABLE_FUNC_1(void, CallInterpreter_RetVoid, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- void * result = NULL;
-
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, 0, (int8_t*)&result);
- return;
- }
- FCDECL1(void, CallInterpreter_I32_RetVoid, int32_t);
- WASM_CALLABLE_FUNC_2(void, CallInterpreter_I32_RetVoid, int32_t arg0, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[1];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return;
- }
- FCDECL2(void, CallInterpreter_I32_I32_RetVoid, int32_t, int32_t);
- WASM_CALLABLE_FUNC_3(void, CallInterpreter_I32_I32_RetVoid, int32_t arg0, int32_t arg1, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[2];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return;
- }
- FCDECL3(void, CallInterpreter_I32_I32_I32_RetVoid, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_4(void, CallInterpreter_I32_I32_I32_RetVoid, int32_t arg0, int32_t arg1, int32_t arg2, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[3];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return;
- }
- FCDECL4(void, CallInterpreter_I32_I32_I32_I32_RetVoid, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_5(void, CallInterpreter_I32_I32_I32_I32_RetVoid, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[4];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return;
- }
- FCDECL0(int32_t, CallInterpreter_RetI32);
- WASM_CALLABLE_FUNC_1(int32_t, CallInterpreter_RetI32, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, 0, (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL1(int32_t, CallInterpreter_I32_RetI32, int32_t);
- WASM_CALLABLE_FUNC_2(int32_t, CallInterpreter_I32_RetI32, int32_t arg0, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[1];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL1(int32_t, CallInterpreter_D64_RetI32, double);
- WASM_CALLABLE_FUNC_2(int32_t, CallInterpreter_D64_RetI32, double arg0, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- double args[1];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = arg0;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL1(int64_t, CallInterpreter_D64_RetI64, double);
- WASM_CALLABLE_FUNC_2(int64_t, CallInterpreter_D64_RetI64, double arg0, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- double args[1];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = arg0;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- int64_t result = 0;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return result;
- }
- FCDECL2(int32_t, CallInterpreter_I32_I32_RetI32, int32_t, int32_t);
- WASM_CALLABLE_FUNC_3(int32_t, CallInterpreter_I32_I32_RetI32, int32_t arg0, int32_t arg1, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[2];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL2(int32_t, CallInterpreter_I32_S8_RetI32, int32_t, int8_t*);
- WASM_CALLABLE_FUNC_3(int32_t, CallInterpreter_I32_S8_RetI32, int32_t arg0, int8_t* arg1, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[2];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- memcpy(&transitionBlock.args[1], arg1, 8);
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL3(int32_t, CallInterpreter_I32_I32_I32_RetI32, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_4(int32_t, CallInterpreter_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[3];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL4(int32_t, CallInterpreter_I32_I32_I32_I32_RetI32, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_5(int32_t, CallInterpreter_I32_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[4];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL5(int32_t, CallInterpreter_I32_I32_I32_I32_I32_RetI32, int32_t, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_6(int32_t, CallInterpreter_I32_I32_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[5];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- transitionBlock.args[4] = (int64_t)arg4;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL6(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_RetI32, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_7(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[6];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- transitionBlock.args[4] = (int64_t)arg4;
- transitionBlock.args[5] = (int64_t)arg5;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL7(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_I32_RetI32, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_8(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[7];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- transitionBlock.args[4] = (int64_t)arg4;
- transitionBlock.args[5] = (int64_t)arg5;
- transitionBlock.args[6] = (int64_t)arg6;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
- FCDECL8(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_I32_I32_RetI32, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t);
- WASM_CALLABLE_FUNC_9(int32_t, CallInterpreter_I32_I32_I32_I32_I32_I32_I32_I32_RetI32, int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7, PCODE portableEntrypoint)
- {
- struct
- {
- TransitionBlock block;
- int64_t args[8];
- } transitionBlock;
- transitionBlock.block.m_ReturnAddress = 0;
- transitionBlock.block.m_StackPointer = callersStackPointer;
- transitionBlock.args[0] = (int64_t)arg0;
- transitionBlock.args[1] = (int64_t)arg1;
- transitionBlock.args[2] = (int64_t)arg2;
- transitionBlock.args[3] = (int64_t)arg3;
- transitionBlock.args[4] = (int64_t)arg4;
- transitionBlock.args[5] = (int64_t)arg5;
- transitionBlock.args[6] = (int64_t)arg6;
- transitionBlock.args[7] = (int64_t)arg7;
- static_assert(offsetof(decltype(transitionBlock), args) == sizeof(TransitionBlock), "Args array must be at a TransitionBlock offset from the start of the block");
-
- void * result = NULL;
- ExecuteInterpretedMethodWithArgs_PortableEntryPoint(portableEntrypoint, &transitionBlock.block, sizeof(transitionBlock.args), (int8_t*)&result);
- return (int32_t)result;
- }
-}
-
-const StringToWasmSigThunk g_wasmPortableEntryPointThunks[] = {
- { "Ivp", (void*)&CallInterpreter_RetVoid },
- { "Ivip", (void*)&CallInterpreter_I32_RetVoid },
- { "Iviip", (void*)&CallInterpreter_I32_I32_RetVoid },
- { "Iviiip", (void*)&CallInterpreter_I32_I32_I32_RetVoid },
- { "Iviiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetVoid },
- { "Iip", (void*)&CallInterpreter_RetI32 },
- { "Iiip", (void*)&CallInterpreter_I32_RetI32 },
- { "Iiiip", (void*)&CallInterpreter_I32_I32_RetI32 },
- { "Iiiiip", (void*)&CallInterpreter_I32_I32_I32_RetI32 },
- { "Iiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_RetI32 },
- { "Iiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_RetI32 },
- { "Iiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_RetI32 },
- { "Iiiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_RetI32 },
- { "Iiiiiiiiiip", (void*)&CallInterpreter_I32_I32_I32_I32_I32_I32_I32_I32_RetI32 },
- { "Iidp", (void*)&CallInterpreter_D64_RetI32 },
- { "Ildp", (void*)&CallInterpreter_D64_RetI64 },
- { "IiiS8p", (void*)&CallInterpreter_I32_S8_RetI32 }
-};
-
-const size_t g_wasmPortableEntryPointThunksCount = sizeof(g_wasmPortableEntryPointThunks) / sizeof(g_wasmPortableEntryPointThunks[0]);
-// -------------------------------------------------
-// END Logic that will eventually mostly be pregenerated for R2R to interpreter code END
-// -------------------------------------------------
extern "C" void STDCALL CallCountingStubCode()
{
@@ -1418,7 +1067,6 @@ namespace
typedef StringToThunkHash StringToWasmSigThunkHash;
static StringToWasmSigThunkHash* thunkCache = nullptr;
- static StringToWasmSigThunkHash* portableEntrypointThunkCache = nullptr;
InterpreterCalliCookie LookupThunk(const char* key)
{
@@ -1437,17 +1085,9 @@ namespace
void* LookupPortableEntryPointThunk(const char* key)
{
- StringToWasmSigThunkHash* table = portableEntrypointThunkCache;
- _ASSERTE(table != nullptr && "Wasm portable entrypoint thunk cache not initialized. Call InitializeWasmThunkCaches() at EEStartup.");
- void* thunk;
- if (table->Lookup(key, &thunk))
- return thunk;
-
- PCODE r2rThunk = LookupPregeneratedThunkByString(key);
- if (r2rThunk != NULL)
- return (void*)(size_t)r2rThunk;
-
- return nullptr;
+ // R2R->interpreter thunks are emitted into the R2R image by crossgen2
+ // (WasmR2RToInterpreterThunkNode) and discovered here by signature string.
+ return (void*)(size_t)LookupPregeneratedThunkByString(key);
}
// This is a simple signature computation routine for signatures currently supported in the wasm environment.
@@ -1486,10 +1126,10 @@ namespace
}
InterpreterCalliCookie thunk = LookupThunk(keyBuffer);
-#ifdef _DEBUG
+
if (thunk == NULL)
printf("WASM calli missing for key: %s\n", keyBuffer);
-#endif
+
return thunk;
}
@@ -1532,12 +1172,10 @@ namespace
}
void* thunk = LookupPortableEntryPointThunk(keyBuffer);
-#ifdef _DEBUG
+
if (thunk == NULL)
- {
- LOG((LF_STUBS, LL_INFO100000, "WASM R2R to interpreter call missing for key: %s\n", keyBuffer));
- }
-#endif
+ printf("WASM: no R2R-to-interpreter thunk for signature key '%s'. \n", keyBuffer);
+
return thunk;
}
@@ -1649,16 +1287,6 @@ void InitializeWasmThunkCaches()
}
thunkCache = newTable;
}
-
- {
- StringToWasmSigThunkHash* newTable = new StringToWasmSigThunkHash();
- newTable->Reallocate(g_wasmPortableEntryPointThunksCount * StringToWasmSigThunkHash::s_density_factor_denominator / StringToWasmSigThunkHash::s_density_factor_numerator + 1);
- for (size_t i = 0; i < g_wasmPortableEntryPointThunksCount; i++)
- {
- newTable->Add(g_wasmPortableEntryPointThunks[i].key, g_wasmPortableEntryPointThunks[i].value);
- }
- portableEntrypointThunkCache = newTable;
- }
}
InterpreterCalliCookie GetCookieForCalliSig(MetaSig metaSig, MethodDesc *pContextMD)
@@ -1786,10 +1414,18 @@ void* GetPortableEntryPointToInterpreterThunk(MethodDesc *pMD)
}
thunk = LookupPortableEntryPointThunk(thunkKey);
+ if (thunk == NULL)
+ {
+ PORTABILITY_ASSERT("GetPortableEntryPointToInterpreterThunk: unknown thunk for string constructor");
+ }
}
else
{
thunk = ComputePortableEntryPointToInterpreterThunk(sig);
+ if (thunk == NULL)
+ {
+ PORTABILITY_ASSERT("ComputePortableEntryPointToInterpreterThunk: unknown thunk signature");
+ }
}
return thunk;
diff --git a/src/native/libs/build-native.cmd b/src/native/libs/build-native.cmd
index 0369fc8db2e684..0c96c064b590aa 100644
--- a/src/native/libs/build-native.cmd
+++ b/src/native/libs/build-native.cmd
@@ -63,6 +63,9 @@ if "%__TargetOS%"=="android" (
if "%__TargetOS%"=="browser" (
set __CrossTarget=1
)
+if "%__TargetOS%"=="wasi" (
+ set __CrossTarget=1
+)
if %__CrossTarget% EQU 0 (
call "%__repoRoot%\eng\native\version\copy_version_files.cmd"
diff --git a/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs b/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs
new file mode 100644
index 00000000000000..97f7cdf5b9f526
--- /dev/null
+++ b/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs
@@ -0,0 +1,256 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using Xunit;
+
+namespace System.Runtime
+{
+ [AttributeUsage(AttributeTargets.Method)]
+ internal sealed class BypassReadyToRunAttribute : Attribute
+ {
+ }
+}
+
+///
+/// Exercises the thunks that carry a call between R2R code and the interpreter on wasm. Methods
+/// marked are skipped by crossgen2 and so run interpreted,
+/// while the rest of the assembly is compiled, which puts a thunk on every call between the two.
+///
+/// Each case returns values chosen so that a wrong answer is visible. That matters more than usual
+/// here: the stack pointer, the hidden return buffer, 'this' and every by-reference argument are
+/// all i32, so a thunk whose parameters are in the wrong order still passes wasm's call_indirect
+/// type check. It does not trap — it writes through the wrong pointer, and the only symptom is
+/// data that quietly comes back wrong.
+///
+public class WasmInterpreterTransitions
+{
+ private const int A = 0x11223344;
+ private const int B = 0x55667788;
+ private const int C = 0x1234567;
+ private const long Wide = 0x1122334455667788;
+ private const float F32 = 3.5f;
+ private const double F64 = 6.25;
+
+ public struct S8
+ {
+ public int A;
+ public int B;
+ }
+
+ public struct S12
+ {
+ public int A;
+ public int B;
+ public int C;
+ }
+
+ public struct S16
+ {
+ public long A;
+ public long B;
+ }
+
+ public struct S1
+ {
+ public byte A;
+ }
+
+ public struct S2
+ {
+ public short A;
+ }
+
+ private delegate S2 ReturnsS2Delegate(int a);
+
+ private readonly int _state = C;
+
+ [Fact]
+ public static void TestEntryPoint()
+ {
+ WasmInterpreterTransitions self = new();
+
+ // R2R -> interpreted, struct returns. The return buffer follows 'this' for an instance
+ // method and the stack pointer for a static one, which is where the two forms differ.
+ S8 s8 = self.InterpretedInstanceReturnsS8(A);
+ Assert.Equal(A, s8.A);
+ Assert.Equal(C, s8.B);
+
+ S8 staticS8 = InterpretedStaticReturnsS8(A);
+ Assert.Equal(A, staticS8.A);
+ Assert.Equal(B, staticS8.B);
+
+ S16 s16 = self.InterpretedInstanceReturnsS16();
+ Assert.Equal(Wide, s16.A);
+ Assert.Equal(C, s16.B);
+
+ S16 staticS16 = InterpretedStaticReturnsS16(Wide, A);
+ Assert.Equal(Wide, staticS16.A);
+ Assert.Equal(A, staticS16.B);
+
+ // A struct that is not a whole number of 8-byte slots, passed and returned.
+ S12 s12 = self.InterpretedInstanceRoundTripsS12(new S12 { A = A, B = B, C = C }, A);
+ Assert.Equal(B, s12.A);
+ Assert.Equal(C, s12.B);
+ Assert.Equal(A, s12.C);
+
+ // R2R -> interpreted, scalar and void shapes.
+ Assert.Equal(A + C, self.InterpretedInstanceReturnsI32(A));
+ Assert.Equal(Wide, InterpretedStaticReturnsI64(1.5));
+ Assert.Equal(A + B + C, InterpretedStaticSumsFour(A, B, C, 0));
+ Assert.Equal(A, self.InterpretedInstanceMixedScalars(1.5f, 2.5, Wide));
+
+ // R2R -> interpreted, floating-point returns. A float/double return travels back through
+ // the thunk's return buffer and is reloaded with an f32/f64 load; a wrong width or an
+ // integer reload silently corrupts it. The runtime's hand-written table had no such shape.
+ Assert.Equal(F32, InterpretedStaticReturnsF32(A));
+ Assert.Equal(F32, self.InterpretedInstanceReturnsF32());
+ Assert.Equal(F64, InterpretedStaticReturnsF64(A));
+ Assert.Equal(F64, self.InterpretedInstanceReturnsF64(F32));
+
+ // A struct argument arrives as the address of its interpreter stack slot.
+ Assert.Equal(A + B, self.InterpretedInstanceTakesS8(new S8 { A = A, B = B }));
+
+ s_sideEffect = 0;
+ self.InterpretedInstanceTakesS8ReturnsVoid(new S8 { A = A, B = B });
+ Assert.Equal(A + B, s_sideEffect);
+
+ // interpreted -> R2R, the opposite direction over the same shapes.
+ Assert.Equal(A + C, self.InterpretedCallsBackIntoR2R());
+
+ S16 fromInterpreter = self.InterpretedCallsR2RReturningS16();
+ Assert.Equal(Wide, fromInterpreter.A);
+ Assert.Equal(C, fromInterpreter.B);
+
+ // interpreted -> R2R, floating-point returns over the same boundary.
+ Assert.Equal(F32, self.InterpretedCallsR2RReturningF32());
+ Assert.Equal(F64, self.InterpretedCallsR2RReturningF64());
+
+ // 1- and 2-byte struct shapes (S1 / S2). These small structs travel by value in a single
+ // slot and are the 'S1'/'S2' encodings the runtime spells for the return buffer and by-ref
+ // argument; the hand-written table only ever had 8-byte forms.
+ Assert.Equal((byte)A, InterpretedStaticReturnsS1(A).A); // I S1 i p
+ s_sideEffect = 0;
+ self.InterpretedInstanceTakesIntAndS2(A, new S2 { A = (short)B }); // I v T i S2 p
+ Assert.Equal(A + (short)B, s_sideEffect);
+
+ // R2R reaches an interpreted method through a delegate: its entrypoint is materialized as a
+ // native function pointer via GetMultiCallableAddrOfCode, which is the path that needs the
+ // R2R-to-interpreter thunk independent of any direct call. The target returns S2 from an
+ // instance method (I S2 T i p).
+ Assert.Equal((short)(A + C), self.R2RInvokesInterpretedViaDelegate().A);
+ }
+
+ private static int s_sideEffect;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S8 InterpretedInstanceReturnsS8(int a) => new S8 { A = a, B = _state };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static S8 InterpretedStaticReturnsS8(int a) => new S8 { A = a, B = B };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S16 InterpretedInstanceReturnsS16() => new S16 { A = Wide, B = _state };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static S16 InterpretedStaticReturnsS16(long wide, int a) => new S16 { A = wide, B = a };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S12 InterpretedInstanceRoundTripsS12(S12 value, int a) => new S12 { A = value.B, B = value.C, C = a };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int InterpretedInstanceReturnsI32(int a) => a + _state;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static long InterpretedStaticReturnsI64(double unused) => Wide;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static int InterpretedStaticSumsFour(int a, int b, int c, int d) => a + b + c + d;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int InterpretedInstanceMixedScalars(float f, double d, long l) => l == Wide && f == 1.5f && d == 2.5 ? A : 0;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static float InterpretedStaticReturnsF32(int a) => a == A ? F32 : 0f;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private float InterpretedInstanceReturnsF32() => _state == C ? F32 : 0f;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static double InterpretedStaticReturnsF64(int a) => a == A ? F64 : 0.0;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private double InterpretedInstanceReturnsF64(float f) => f == F32 ? F64 : 0.0;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int InterpretedInstanceTakesS8(S8 value) => value.A + value.B;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void InterpretedInstanceTakesS8ReturnsVoid(S8 value) => s_sideEffect = value.A + value.B;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int InterpretedCallsBackIntoR2R() => R2RInstanceReturnsI32(A);
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S16 InterpretedCallsR2RReturningS16() => R2RInstanceReturnsS16();
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private float InterpretedCallsR2RReturningF32() => R2RInstanceReturnsF32();
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private double InterpretedCallsR2RReturningF64() => R2RInstanceReturnsF64();
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static S1 InterpretedStaticReturnsS1(int a) => new S1 { A = (byte)a };
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void InterpretedInstanceTakesIntAndS2(int a, S2 s) => s_sideEffect = a + s.A;
+
+ [BypassReadyToRun]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S2 InterpretedInstanceReturnsS2(int a) => new S2 { A = (short)(a + _state) };
+
+ // R2R code: takes a delegate to the interpreted method above and invokes it. Creating the
+ // delegate takes the interpreted method's address, so its entrypoint must be callable from R2R.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S2 R2RInvokesInterpretedViaDelegate()
+ {
+ ReturnsS2Delegate d = InterpretedInstanceReturnsS2;
+ return d(A);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private int R2RInstanceReturnsI32(int a) => a + _state;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private S16 R2RInstanceReturnsS16() => new S16 { A = Wide, B = _state };
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private float R2RInstanceReturnsF32() => _state == C ? F32 : 0f;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private double R2RInstanceReturnsF64() => _state == C ? F64 : 0.0;
+}
diff --git a/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj b/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj
new file mode 100644
index 00000000000000..092b4f60c765b9
--- /dev/null
+++ b/src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.csproj
@@ -0,0 +1,14 @@
+
+
+ true
+ true
+
+ true
+ true
+
+
+
+
+