diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index f6338f846c6d06..3f9520e66cf165 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -13,10 +13,14 @@ static const instruction INS_I_const = INS_i64_const; static const instruction INS_I_add = INS_i64_add; static const instruction INS_I_sub = INS_i64_sub; +static const instruction INS_I_le_u = INS_i64_le_u; +static const instruction INS_I_gt_u = INS_i64_gt_u; #else // !TARGET_64BIT static const instruction INS_I_const = INS_i32_const; static const instruction INS_I_add = INS_i32_add; static const instruction INS_I_sub = INS_i32_sub; +static const instruction INS_I_le_u = INS_i32_le_u; +static const instruction INS_I_gt_u = INS_i32_gt_u; #endif // !TARGET_64BIT void CodeGen::genMarkLabelsForCodegen() @@ -495,6 +499,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForNegNot(treeNode->AsOp()); break; + case GT_NULLCHECK: + genCodeForNullCheck(treeNode->AsIndir()); + break; + case GT_IND: genCodeForIndir(treeNode->AsIndir()); break; @@ -922,6 +930,37 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) { genConsumeOperands(treeNode); + // wasm stack is + // divisor (top) + // dividend (next) + // ... + // TODO-WASM: To check for exception, we will have to spill these to + // internal registers along the way, like so: + // + // ... push dividend + // tee.local $temp1 + // ... push divisor + // tee.local $temp2 + // ... exception checks (using $temp1 and $temp2; will introduce flow) + // div/mod op + + if (!varTypeIsFloating(treeNode->TypeGet())) + { + ExceptionSetFlags exSetFlags = treeNode->OperExceptions(compiler); + + // TODO-WASM:(AnyVal / 0) => DivideByZeroException + // + if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None) + { + } + + // TODO-WASM: (MinInt / -1) => ArithmeticException + // + if ((exSetFlags & ExceptionSetFlags::ArithmeticException) != ExceptionSetFlags::None) + { + } + } + instruction ins; switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet())) { @@ -1136,6 +1175,48 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree) genProduceReg(tree); } +//--------------------------------------------------------------------- +// genCodeForNullCheck - generate code for a GT_NULLCHECK node +// +// Arguments: +// tree - the GT_NULLCHECK node +// +// Notes: +// If throw helper calls are being emitted inline, we need +// to wrap the resulting codegen in a block/end pair. +// +void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) +{ + genConsumeAddress(tree->Addr()); + + // TODO-WASM: refactor once we have implemented other cases invoking throw helpers + if (compiler->fgUseThrowHelperBlocks()) + { + Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); + + if (add == nullptr) + { + NYI_WASM("Missing null check demand"); + } + + assert(add != nullptr); + assert(add->acdUsed); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_le_u); + inst_JMP(EJ_jmpif, add->acdDstBlk); + } + else + { + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_le_u); + GetEmitter()->emitIns(INS_if); + // TODO-WASM: codegen for the call instead of unreachable + // genEmitHelperCall(compiler->acdHelper(SCK_NULL_CHECK), 0, EA_UNKNOWN); + GetEmitter()->emitIns(INS_unreachable); + GetEmitter()->emitIns(INS_end); + } +} + //------------------------------------------------------------------------ // genCodeForLclAddr: Generates the code for GT_LCL_ADDR. // diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 2336eeeabbdfa0..485fe0cef0fdbb 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -438,6 +438,15 @@ Compiler::Compiler(ArenaAllocator* arena, compMaxUncheckedOffsetForNullObject = eeInfo->maxUncheckedOffsetForNullObject; +#if defined(DEBUG) && defined(TARGET_WASM) + // TODO-WASM: remove once we no longer need to use x86/arm collections for wasm replay + // if we are cross-replaying wasm, override compMaxUncheckedOffsetForNullObject + if (!info.compMatchedVM) + { + compMaxUncheckedOffsetForNullObject = 1024 - 1; + } +#endif + info.compProfilerCallback = false; // Assume false until we are told to hook this method. info.compCode = methodInfo->ILCode; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0cf381b8b2dc85..45c7bb022f7fa2 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -2841,7 +2841,15 @@ class Compiler #endif // DEBUG bool ehAnyFunclets(); // Are there any funclets in this function? - unsigned ehFuncletCount(); // Return the count of funclets in the function + unsigned ehFuncletCount(); // Return the count of funclets in the function. + +#ifdef TARGET_WASM + // Once we have run wasm layout, try regions may no longer be contiguous. + // + bool fgTrysNotContiguous() { return fgIndexToBlockMap != nullptr; } +#else + bool fgTrysNotContiguous() { return false; } +#endif FlowEdge* BlockPredsWithEH(BasicBlock* blk); FlowEdge* BlockDominancePreds(BasicBlock* blk); diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 0268069f06e746..c3c18fc367328a 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -107,9 +107,13 @@ FlowGraphDfsTree* FgWasm::WasmDfs(bool& hasBlocksOnlyReachableViaEH) JITDUMP("Determining Wasm DFS entry points\n"); // All funclets are entries. For now we assume finallys are funclets. + // We walk from outer->inner order, so that for mutual protect trys + // the "first" handler is visited last and ends up earlier in RPO. // - for (EHblkDsc* const ehDsc : EHClauses(comp)) + for (int XTnum = comp->compHndBBtabCount - 1; XTnum >= 0; XTnum--) { + EHblkDsc* const ehDsc = &comp->compHndBBtab[XTnum]; + JITDUMP(FMT_BB " is handler entry\n", ehDsc->ebdHndBeg->bbNum); entryBlocks.push_back(ehDsc->ebdHndBeg); if (ehDsc->HasFilter()) @@ -1390,6 +1394,17 @@ PhaseStatus Compiler::fgWasmControlFlow() { fgUnlinkBlock(block); fgInsertBBafter(lastBlock, block); + + // If the last block was the end of a handler, we may need + // to update the enclosing region endpoint. + // + // Because we are not keeping try regions contiguous, + // we can't and don't need to do the same for a try. + // + if (ehIsBlockHndLast(lastBlock) && block->hasHndIndex() && BasicBlock::sameHndRegion(lastBlock, block)) + { + fgSetHndEnd(ehGetBlockHndDsc(block), block); + } lastBlock = block; } @@ -1441,6 +1456,11 @@ PhaseStatus Compiler::fgWasmControlFlow() JITDUMPEXEC(fgDumpWasmControlFlow()); JITDUMPEXEC(fgDumpWasmControlFlowDot()); + // By publishing the index to block map, we are also indicating + // that try regions may no longer be contiguous. + // + assert(fgTrysNotContiguous()); + return PhaseStatus::MODIFIED_EVERYTHING; } diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index f9cbe5099846bf..cf741079984066 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -318,9 +318,51 @@ class FgWasm // consider exceptional successors or successors that require runtime intervention // (eg funclet returns). // +// For method and funclet entries we add any "ACD" blocks as successors before the +// true successors. This ensures the ACD blocks end up at the end of the funclet +// region, and that we create proper Wasm blocks so we can branch to them from +// anywhere within the method region or funclet region. +// template BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc func, bool useProfile) { + // Special case throw helper blocks that are not yet connected in the flow graph. + // + Compiler::AddCodeDscMap* const acdMap = comp->fgGetAddCodeDscMap(); + if (acdMap != nullptr) + { + // Behave as if these blocks have edges from their respective region entry blocks. + // + if ((block == comp->fgFirstBB) || comp->bbIsFuncletBeg(block)) + { + Compiler::AcdKeyDesignator dsg; + const unsigned blockData = comp->bbThrowIndex(block, &dsg); + + // We do not expect any ACDs to be mapped to try regions (only method/handler/filter) + // + assert(dsg != Compiler::AcdKeyDesignator::KD_TRY); + + for (const Compiler::AddCodeDscKey& key : Compiler::AddCodeDscMap::KeyIteration(acdMap)) + { + if (key.Data() == blockData) + { + // This ACD refers to a throw helper block in the right region. + // Make the block a successor. + // + Compiler::AddCodeDsc* acd = nullptr; + acdMap->Lookup(key, &acd); + + // We only need to consider used ACDs... we may have demanded throw helpers that are not needed. + // + if (acd->acdUsed) + { + RETURN_ON_ABORT(func(acd->acdDstBlk)); + } + } + } + } + } + switch (block->GetKind()) { // Funclet returns have no successors diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 48cb0e882fd2b2..b255293417d487 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -3326,6 +3326,8 @@ unsigned Compiler::acdHelper(SpecialCodeKind codeKind) return CORINFO_HELP_OVERFLOW; case SCK_FAIL_FAST: return CORINFO_HELP_FAIL_FAST; + case SCK_NULL_CHECK: + return CORINFO_HELP_THROWNULLREF; default: assert(!"Bad codeKind"); return 0; @@ -3358,6 +3360,8 @@ const char* sckName(SpecialCodeKind codeKind) return "SCK_ARITH_EXCPN"; case SCK_FAIL_FAST: return "SCK_FAIL_FAST"; + case SCK_NULL_CHECK: + return "SCK_NULL_CHECK"; default: return "SCK_UNKNOWN"; } @@ -3490,6 +3494,7 @@ PhaseStatus Compiler::fgCreateThrowHelperBlocks() BBJ_THROW, // SCK_ARG_EXCPN BBJ_THROW, // SCK_ARG_RNG_EXCPN BBJ_THROW, // SCK_FAIL_FAST + BBJ_THROW, // SCK_NULL_CHECK }; noway_assert(sizeof(jumpKinds) == SCK_COUNT); // sanity check @@ -3555,6 +3560,9 @@ PhaseStatus Compiler::fgCreateThrowHelperBlocks() case SCK_FAIL_FAST: msg = " for FAIL_FAST"; break; + case SCK_NULL_CHECK: + msg = " for NULL_CHECK"; + break; default: msg = " for ??"; break; @@ -3676,7 +3684,13 @@ Compiler::AddCodeDsc* Compiler::fgFindExcptnTarget(SpecialCodeKind kind, BasicBl { JITDUMP(FMT_BB ": unexpected request for new throw helper: kind %d (%s), data 0x%08x\n", fromBlock->bbNum, kind, sckName(kind), key.Data()); + + if (kind == SCK_NULL_CHECK) + { + NYI_WASM("Missing null check demand"); + } } + assert(!fgRngChkThrowAdded); } @@ -3708,6 +3722,20 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) assert(inTry || inHnd); +#if defined(TARGET_WASM) + // The current plan for Wasm: method regions or funclets with + // trys will have a single Wasm try handle all + // resumption from catches via virtual IPs. + // + // So we do not need to consider the nesting of the throw + // in try regions, just in handlers. + // + if (!inHnd) + { + *dsg = AcdKeyDesignator::KD_NONE; + return 0; + } +#else if (inTry && (!inHnd || (tryIndex < hndIndex))) { // The most enclosing region is a try body, use it @@ -3715,6 +3743,7 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) *dsg = AcdKeyDesignator::KD_TRY; return tryIndex; } +#endif // !defined(TARGET_WASM) // The most enclosing region is a handler which will be a funclet // Now we have to figure out if blk is in the filter or handler @@ -3731,7 +3760,7 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) } //------------------------------------------------------------------------ -// AddCodedDscKey: construct from kind and block +// AddCodeDscKey: construct from kind and block // // Arguments: // kind - exception kind @@ -3755,7 +3784,7 @@ Compiler::AddCodeDscKey::AddCodeDscKey(SpecialCodeKind kind, BasicBlock* block, } //------------------------------------------------------------------------ -// AddCodedDscKey: construct from AddCodeDsc +// AddCodeDscKey: construct from AddCodeDsc // // Arguments: // add - add code dsc in querstion diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 96cd50b6079188..045505af84421b 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -62,6 +62,7 @@ enum SpecialCodeKind SCK_ARG_EXCPN, // target on ArgumentException (currently used only for SIMD intrinsics) SCK_ARG_RNG_EXCPN, // target on ArgumentOutOfRangeException (currently used only for SIMD intrinsics) SCK_FAIL_FAST, // target for fail fast exception + SCK_NULL_CHECK, // target for NullReferenceException (Wasm) SCK_COUNT }; diff --git a/src/coreclr/jit/jiteh.cpp b/src/coreclr/jit/jiteh.cpp index a398bb01508dcb..1f124cb2db606d 100644 --- a/src/coreclr/jit/jiteh.cpp +++ b/src/coreclr/jit/jiteh.cpp @@ -3695,7 +3695,7 @@ void Compiler::fgVerifyHandlerTab() // Make sure that all blocks have the right index, including those blocks that should have zero (no EH region). for (BasicBlock* const block : Blocks()) { - assert(block->bbTryIndex == blockTryIndex[block->bbNum]); + assert(fgTrysNotContiguous() || block->bbTryIndex == blockTryIndex[block->bbNum]); assert(block->bbHndIndex == blockHndIndex[block->bbNum]); // Also, since we're walking the blocks, check that all blocks we didn't mark as EH handler 'begin' blocks diff --git a/src/coreclr/jit/stacklevelsetter.cpp b/src/coreclr/jit/stacklevelsetter.cpp index cfff07017a2c52..a7729322e0f942 100644 --- a/src/coreclr/jit/stacklevelsetter.cpp +++ b/src/coreclr/jit/stacklevelsetter.cpp @@ -273,6 +273,13 @@ void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block) break; #endif +#if defined(TARGET_WASM) + // TODO-WASM: add other opers that imply null checks + case GT_NULLCHECK: + SetThrowHelperBlock(SCK_NULL_CHECK, block); + break; +#endif // defined(TARGET_WASM) + default: // Other opers can target throw only due to overflow. break; } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 49a70a4085a0d8..f8cfcfd6e47402 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3394,8 +3394,19 @@ private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut) pEEInfoOut.osPageSize = 0x1000; - pEEInfoOut.maxUncheckedOffsetForNullObject = (_compilation.NodeFactory.Target.IsWindows) ? - (32 * 1024 - 1) : (pEEInfoOut.osPageSize / 2 - 1); + if (_compilation.NodeFactory.Target.IsWasm) + { + // TODO: Set this value to 0 for Wasm + pEEInfoOut.maxUncheckedOffsetForNullObject = 1024 - 1; + } + else if (_compilation.NodeFactory.Target.IsWindows) + { + pEEInfoOut.maxUncheckedOffsetForNullObject = 32 * 1024 - 1; + } + else + { + pEEInfoOut.maxUncheckedOffsetForNullObject = pEEInfoOut.osPageSize / 2 - 1; + } pEEInfoOut.targetAbi = TargetABI; pEEInfoOut.osType = TargetToOs(_compilation.NodeFactory.Target); diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 89fa3099cb653b..ed9d728ee88748 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -11,11 +11,15 @@ #include "corjit.h" -#ifndef TARGET_UNIX -#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((32*1024)-1) // when generating JIT code -#else // !TARGET_UNIX +#if defined (TARGET_WASM) +// TODO: Set this value to 0 for Wasm +#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT (1024 - 1) +#elif defined (TARGET_UNIX) #define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((GetOsPageSize() / 2) - 1) -#endif // !TARGET_UNIX +#else +#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((32*1024)-1) // when generating JIT code +#endif + #include "pgo.h" class ILCodeStream;