diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 7a0bbb5efeb440..22badc840cac9c 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2652,6 +2652,16 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree) genConsumeAddress(tree->Addr()); + // On wasm a load from a null address does not fault, so emit an explicit null check + // for faulting loads (mirrors genCodeForStoreInd). Use the same predicate the throw- + // helper reservation phase uses (StackLevelSetter), so we never demand an unreserved + // throw helper block here. + if (((tree->gtFlags & GTF_EXCEPT) != 0) && tree->OperMayThrow(m_compiler)) + { + regNumber addrReg = GetMultiUseOperandReg(tree->Addr()); + genEmitNullCheck(addrReg); + } + // TODO-WASM: Memory barriers GetEmitter()->emitIns_I(ins, emitActualTypeSize(type), 0); @@ -3587,6 +3597,29 @@ void CodeGen::genCallFinally(BasicBlock* block) { GetEmitter()->emitIns(INS_end); } + + return; + } + + // After call_indirect returns, control falls through to the next wasm instruction. + // The paired BBJ_CALLFINALLYRET has no code emitted (see genCodeForBlock), so the + // CALLFINALLYRET's continuation must be reached either by fall-through (when it's + // the next block in linear order) or by an explicit branch emitted here. + // + // This is wasm-specific: other targets rely on the native `call`/`ret` to land at + // the instruction after the call, and the JIT layout places the CALLFINALLYRET's + // continuation immediately after. On wasm the layout may place the continuation + // elsewhere (e.g. a loop header reached via back-edge, as produced by a leave from + // a catch handler that targets a label outside its enclosing try-finally). + // + assert(block->isBBCallFinallyPair()); + BasicBlock* const callFinallyRet = block->Next(); + assert(callFinallyRet->KindIs(BBJ_CALLFINALLYRET)); + BasicBlock* const continuation = callFinallyRet->GetTarget(); + + if (continuation->bbPreorderNum != callFinallyRet->bbPreorderNum + 1) + { + inst_JMP(EJ_jmp, continuation); } } diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 78cda8482f64d6..1932e39b99aa42 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -458,6 +458,13 @@ void Lowering::ContainCheckIndir(GenTreeIndir* indirNode) return; } + if (indirNode->OperIs(GT_IND) && ((indirNode->gtFlags & GTF_IND_NONFAULTING) == 0)) + { + // On wasm a load from a null address does not fault, so a faulting load needs an + // explicit null check, which requires multiple uses of the address operand. + SetMultiplyUsed(indirNode->Addr() DEBUGARG("ContainCheckIndir faulting load Addr")); + } + // TODO-WASM-CQ: contain suitable LEAs here. Take note of the fact that for this to be correct we must prove the // LEA doesn't overflow. It will involve creating a new frontend node to represent "nuw" (offset) addition. } diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index f8cac164887670..485d2c63cfe841 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -490,6 +490,15 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) CollectReferencesForStoreInd(node->AsStoreInd()); break; + case GT_IND: + if (node->AsIndir()->Addr()->gtLIRFlags & LIR::Flags::MultiplyUsed) + { + // Faulting loads are null checked on wasm; consume the temporary register + // holding the address so it is available for both the check and the load. + ConsumeTemporaryRegForOperand(node->AsIndir()->Addr() DEBUGARG("indir load null check")); + } + break; + case GT_STORE_BLK: CollectReferencesForBlockStore(node->AsBlk()); break;