diff --git a/src/coreclr/jit/block.cpp b/src/coreclr/jit/block.cpp index 03c1c23ddeeb0a..27711d0dc4e56e 100644 --- a/src/coreclr/jit/block.cpp +++ b/src/coreclr/jit/block.cpp @@ -16,6 +16,9 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #endif #include "jitstd/algorithm.h" +#ifdef TARGET_WASM +#include "fgwasm.h" // for WasmInterval, used in CanRemoveJumpToNext +#endif #if MEASURE_BLOCK_SIZE /* static */ @@ -404,7 +407,27 @@ bool BasicBlock::IsFirstColdBlock(Compiler* compiler) const bool BasicBlock::CanRemoveJumpToNext(Compiler* compiler) const { assert(KindIs(BBJ_ALWAYS)); - return JumpsToNext() && !IsLastHotBlock(compiler); + if (!JumpsToNext() || IsLastHotBlock(compiler)) + { + return false; + } +#ifdef TARGET_WASM + // Fall-through across a Try/ExnRefWrapper end injects an `unreachable` + // or an exnref `local.set` that would trap or fail validation. + // + if (compiler->fgWasmIntervals != nullptr) + { + unsigned const targetIndex = GetTarget()->bbPreorderNum; + for (WasmInterval* const interval : *compiler->fgWasmIntervals) + { + if ((interval->IsTry() || interval->IsExnRefWrapper()) && (interval->End() == targetIndex)) + { + return false; + } + } + } +#endif + return true; } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 40b7e5a41b1291..e27db73a6cb551 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -519,7 +519,15 @@ unsigned CodeGen::findTargetDepth(BasicBlock* targetBlock) } else { - // blocks and trys bind to end + // Try and ExnRefWrapper ends inject auto-emitted code (unreachable / exnref + // local.set) so they are not valid plain br targets; use the paired Block. + // + if (ii->IsTry() || ii->IsExnRefWrapper()) + { + continue; + } + + // blocks bind to end match = ii->End(); } @@ -2665,36 +2673,12 @@ void CodeGen::genCallInstruction(GenTreeCall* call) GenTree* target = getCallTarget(call, ¶ms.methHnd); - // Report managed call signatures to the R2R compiler for thunk generation. - if (!call->IsHelperCall() && !call->IsUnmanaged()) - { - CORINFO_SIG_INFO sigInfoLocal; - CORINFO_SIG_INFO* sigInfoCall = call->callSig; - - if (sigInfoCall == nullptr) - { - if ((params.methHnd != NO_METHOD_HANDLE) && - (Compiler::eeGetHelperNum(params.methHnd) == CORINFO_HELP_UNDEF)) - { - m_compiler->eeGetMethodSig(params.methHnd, &sigInfoLocal); - sigInfoCall = &sigInfoLocal; - } - } - - if (sigInfoCall != nullptr) - { - m_compiler->info.compCompHnd->recordWasmManagedCallSig(sigInfoCall); - } - } - ArrayStack typeStack(m_compiler->getAllocator(CMK_Codegen)); - // Compute the wasm-level result type for the call. Use call->gtReturnType - // (the call's "exact" return type) rather than call->gtType, since the latter is - // overwritten to TYP_VOID for fast tail calls and for retbuf calls. Calls that - // return via a retbuf produce no wasm-level value. For fast tail calls we rely - // on fgCanFastTailCall to ensure caller/callee result types are compatible. - const var_types callRetType = (var_types)call->gtReturnType; + // Compute the wasm-level result type for the call. For fast tail calls, gtType + // has been overwritten to TYP_VOID, but the wasm return_call_indirect signature + // must match this function's return type, so use gtReturnType. + const var_types callRetType = call->IsFastTailCall() ? (var_types)call->gtReturnType : genActualType(call); if (call->ShouldHaveRetBufArg() || (callRetType == TYP_VOID)) { typeStack.Push(CORINFO_WASM_TYPE_VOID); @@ -2720,6 +2704,28 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } } + // Report managed call signatures to the R2R compiler for thunk generation. + if (!call->IsHelperCall() && !call->IsUnmanaged()) + { + CORINFO_SIG_INFO sigInfoLocal; + CORINFO_SIG_INFO* sigInfoCall = call->callSig; + + if (sigInfoCall == nullptr) + { + if ((params.methHnd != NO_METHOD_HANDLE) && + (Compiler::eeGetHelperNum(params.methHnd) == CORINFO_HELP_UNDEF)) + { + m_compiler->eeGetMethodSig(params.methHnd, &sigInfoLocal); + sigInfoCall = &sigInfoLocal; + } + } + + if (sigInfoCall != nullptr) + { + m_compiler->info.compCompHnd->recordWasmManagedCallSig(sigInfoCall); + } + } + params.wasmSignature = m_compiler->info.compCompHnd->getWasmTypeSymbol(typeStack.Data(), typeStack.Height()); // A non-null target expression always indicates an indirect call on Wasm, diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 96edc24c53ed4a..2dab4807e62265 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -1359,15 +1359,19 @@ PhaseStatus Compiler::fgWasmControlFlow() // This interval will inspire a try_table in codegen to handle the resumption // request from the runtime. // - unsigned endCursor = cursor + tryRegion->NumBlocks(); - WasmInterval* const tryInterval = WasmInterval::NewTry(this, block, initialLayout[endCursor]); + unsigned const tryEndCursor = cursor + tryRegion->NumBlocks(); + WasmInterval* const tryInterval = WasmInterval::NewTry(this, block, initialLayout[tryEndCursor]); fgWasmIntervals->push_back(tryInterval); - // Pair the TRY with a same-range [exnref]-wrapper Block so - // `catch_ref TAG 0` from inside try_table has a valid target. - // The sort tiebreaker places the wrapper just outside the TRY. + // Pair the TRY with an [exnref]-wrapper Block whose end lands at the + // BBF_CATCH_RESUMPTION dispatcher so catch_ref resumes there. // - WasmInterval* const wrapperInterval = WasmInterval::NewExnRefWrapper(this, block, initialLayout[endCursor]); + assert(block->KindIs(BBJ_COND)); + BasicBlock* const cresume = block->GetTrueTarget(); + assert((cresume != nullptr) && cresume->HasFlag(BBF_CATCH_RESUMPTION)); + unsigned const wrapperEndCursor = max(tryEndCursor, cresume->bbPreorderNum); + WasmInterval* const wrapperInterval = + WasmInterval::NewExnRefWrapper(this, block, initialLayout[wrapperEndCursor]); fgWasmIntervals->push_back(wrapperInterval); } @@ -1393,10 +1397,18 @@ PhaseStatus Compiler::fgWasmControlFlow() continue; } + // A branch out of a wasm try/catch needs an explicit Block target + // even when the succ is contiguous or cold. + // + EHblkDsc* const blockTryDsc = ehGetBlockTryDsc(block); + bool const isCrossingTryCatchExit = (blockTryDsc != nullptr) && blockTryDsc->HasCatchHandler() && + !bbInTryRegions(ehGetIndex(blockTryDsc), succ); + // Branch to next needs no block, unless this is a switch or next is a throw helper. // We may need to branch to a throw helper mid-block, so can't always fall through. // - if ((succNum == (cursor + 1)) && !block->KindIs(BBJ_SWITCH) && !(succ->HasFlag(BBF_THROW_HELPER))) + if ((succNum == (cursor + 1)) && !block->KindIs(BBJ_SWITCH) && !(succ->HasFlag(BBF_THROW_HELPER)) && + !isCrossingTryCatchExit) { continue; } @@ -1404,7 +1416,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // Branch to cold block needs no block (presumably something EH related). // Eventually we need to case these out and handle them better. // - if (succNum >= numBlocks) + if ((succNum >= numBlocks) && !isCrossingTryCatchExit) { continue; } @@ -1422,17 +1434,19 @@ PhaseStatus Compiler::fgWasmControlFlow() continue; } - // Non-contiguous, non-subsumed forward branch + // Non-contiguous, non-subsumed forward branch. Start the Block at the try + // header when crossing a try-catch exit so it encloses the wrapper. // - WasmInterval* const branch = WasmInterval::NewBlock(this, block, initialLayout[succNum]); + BasicBlock* const blockStart = isCrossingTryCatchExit ? blockTryDsc->ebdTryBeg : block; + WasmInterval* const branch = WasmInterval::NewBlock(this, blockStart, initialLayout[succNum]); fgWasmIntervals->push_back(branch); // Remember an interval end here // scratch[succNum] = branch; - JITDUMP("Adding block interval for " FMT_BB "[%u] -> " FMT_BB "[%u]\n", block->bbNum, cursor, succ->bbNum, - succNum); + JITDUMP("Adding block interval for " FMT_BB "[%u] -> " FMT_BB "[%u]\n", blockStart->bbNum, + blockStart->bbPreorderNum, succ->bbNum, succNum); } } @@ -1500,6 +1514,15 @@ PhaseStatus Compiler::fgWasmControlFlow() // Since this is only looking at prior intervals it could be // merged with (2) above. // + + // Cross-try-exit Block intervals are backdated to start at the try + // header; sort by Start so resolve sees intervals in non-decreasing + // start order. + // + jitstd::sort(fgWasmIntervals->begin(), fgWasmIntervals->end(), [](WasmInterval* i1, WasmInterval* i2) { + return i1->Start() < i2->Start(); + }); + auto resolve = [this](WasmInterval* const current) { for (WasmInterval* prior : *fgWasmIntervals) { diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ead7edca7308b0..8ceae9e6912ada 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -11742,6 +11742,9 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_PHI_ARG: case GT_JMPTABLE: case GT_PHYSREG: +#ifdef TARGET_WASM + case GT_FRAME_SIZE: +#endif // TARGET_WASM case GT_IL_OFFSET: case GT_RECORD_ASYNC_RESUME: case GT_NOP: diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 84c0ccbce488f4..b55ca2619dee3b 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6232,6 +6232,17 @@ void Lowering::LowerStoreSingleRegCallStruct(GenTreeBlk* store) regType = call->TypeGet(); } #endif + +#if defined(TARGET_WASM) + CORINFO_CLASS_HANDLE clsHnd = layout->GetClassHandle(); + if (clsHnd != NO_CLASS_HANDLE) + { + CorInfoWasmType wasmAbiType = m_compiler->info.compCompHnd->getWasmLowering(clsHnd); + assert(wasmAbiType != CORINFO_WASM_TYPE_VOID); + regType = WasmClassifier::ToJitType(wasmAbiType); + } +#endif // TARGET_WASM + store->ChangeType(regType); store->SetOper(GT_STOREIND); LowerStoreIndirCommon(store->AsStoreInd()); @@ -9236,7 +9247,7 @@ void Lowering::FindInducedParameterRegisterLocals() // accesses larger to generate smaller code. #ifdef TARGET_WASM - var_types fullWidthType = TYP_LONG; + var_types fullWidthType = genActualType(regSegment->GetRegisterType()); #else var_types fullWidthType = TYP_I_IMPL; #endif @@ -9492,6 +9503,7 @@ void Lowering::CheckNode(Compiler* compiler, GenTree* node) { const GenTreeLclVarCommon* lclVarAddr = node->AsLclVarCommon(); const LclVarDsc* varDsc = compiler->lvaGetDesc(lclVarAddr); +#if !defined(TARGET_WASM) if (((lclVarAddr->gtFlags & GTF_VAR_DEF) != 0) && varDsc->HasGCPtr()) { // Emitter does not correctly handle live updates for LCL_ADDR @@ -9504,6 +9516,7 @@ void Lowering::CheckNode(Compiler* compiler, GenTree* node) assert(lclVarAddr->isContained() || !varDsc->lvTracked || varTypeIsStruct(varDsc)); // TODO: support this assert for uses, see https://github.com/dotnet/runtime/issues/51900. } +#endif // !TARGET_WASM assert(varDsc->lvDoNotEnregister); break; diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index d7af1a7af7041f..438b513b3aaf4b 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -7437,8 +7437,8 @@ BOOL ReadyToRunJitManager::IsFilterFunclet(EECodeInfo * pCodeInfo) if (!pCodeInfo->IsFunclet()) return FALSE; -#ifdef TARGET_X86 - // x86 doesn't use personality routines in unwind data, so we have to fallback to +#if defined(TARGET_X86) || defined(TARGET_WASM) + // x86 and wasm don't use personality routines in unwind data, so we have to fallback to // the slow implementation return IJitManager::IsFilterFunclet(pCodeInfo); #else