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 + + + + +