Skip to content
25 changes: 24 additions & 1 deletion src/coreclr/jit/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}

//------------------------------------------------------------------------
Expand Down
64 changes: 35 additions & 29 deletions src/coreclr/jit/codegenwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -2665,36 +2673,12 @@ void CodeGen::genCallInstruction(GenTreeCall* call)

GenTree* target = getCallTarget(call, &params.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<CorInfoWasmType> 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);
Expand All @@ -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,
Expand Down
47 changes: 35 additions & 12 deletions src/coreclr/jit/fgwasm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -1393,18 +1397,26 @@ 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;
}

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

Expand Down Expand Up @@ -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)
{
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
15 changes: 14 additions & 1 deletion src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/codeman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading