diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 0dc138339952f0..758d8aefc27488 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1356,6 +1356,8 @@ class CodeGen final : public CodeGenInterface void instGen(instruction ins); #if defined(TARGET_XARCH) void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isRemovableJmpCandidate = false); +#elif defined(TARGET_WASM) + void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isTempLabel = false); #else void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); #endif diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 0fa2e31a889144..96fdb8d5c46348 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -12,9 +12,13 @@ #ifdef TARGET_64BIT static const instruction INS_I_const = INS_i64_const; static const instruction INS_I_add = INS_i64_add; +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_le_u = INS_i32_le_u; +static const instruction INS_I_gt_u = INS_i32_gt_u; #endif // !TARGET_64BIT void CodeGen::genMarkLabelsForCodegen() @@ -353,6 +357,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForNegNot(treeNode->AsOp()); break; + case GT_NULLCHECK: + genCodeForNullCheck(treeNode->AsIndir()); + break; + default: #ifdef DEBUG NYIRAW(GenTree::OpName(treeNode->OperGet())); @@ -570,6 +578,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)) { @@ -784,6 +823,49 @@ 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); + 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 + { + BasicBlock* const tgtBlk = genCreateTempLabel(); + GetEmitter()->emitIns(INS_block); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_gt_u); + // tgtBlock is not on the model wasm control stack we set up earlier. + // Since we have just emitted a `begin` that will end at this block, it will be at depth 0. + // Indicate this via isTempLabel. + inst_JMP(EJ_jmpif, tgtBlk, /* isTempLabel */ true); + // TODO-WASM: codegen for the call + // genEmitHelperCall(compiler->acdHelper(SCK_NULL_CHECK), 0, EA_UNKNOWN); + // The helper won't return, and we may have things pended on the stack, so emit unreachable + GetEmitter()->emitIns(INS_unreachable); + GetEmitter()->emitIns(INS_end); + genDefineTempLabel(tgtBlk); + } +} + //------------------------------------------------------------------------ // genCodeForLclAddr: Generates the code for GT_LCL_ADDR. // @@ -1086,11 +1168,12 @@ void CodeGen::genSpillVar(GenTree* tree) // Arguments: // jmp - kind of jump to emit // tgtBlock - target of the jump +// isTempLabel - true if target is a temp label (implicitly at top of control flow stack) // -void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) +void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isTempLabel) { instruction instr = emitter::emitJumpKindToIns(jmp); - unsigned const depth = findTargetDepth(tgtBlock); + unsigned const depth = isTempLabel ? 0 : findTargetDepth(tgtBlock); GetEmitter()->emitIns_J(instr, EA_4BYTE, depth, tgtBlock); } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 61ce486fbeb515..d56e7a0833b3aa 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -438,6 +438,14 @@ Compiler::Compiler(ArenaAllocator* arena, compMaxUncheckedOffsetForNullObject = eeInfo->maxUncheckedOffsetForNullObject; +#if defined(DEBUG) && defined(TARGET_WASM) + // 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/fgwasm.h b/src/coreclr/jit/fgwasm.h index f9cbe5099846bf..2d0a8b05bad02c 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -318,9 +318,45 @@ 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); + 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..4af3ca474fdbf0 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; @@ -3708,6 +3716,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 +3737,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 +3754,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 +3778,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 5c60753cc7d0dd..e19efa3d0583cc 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/stacklevelsetter.cpp b/src/coreclr/jit/stacklevelsetter.cpp index cfff07017a2c52..e1b2fb936754f9 100644 --- a/src/coreclr/jit/stacklevelsetter.cpp +++ b/src/coreclr/jit/stacklevelsetter.cpp @@ -90,7 +90,6 @@ PhaseStatus StackLevelSetter::DoPhase() } } } - return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } @@ -273,6 +272,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 883af8a3cd01e0..bab5e6eb0e933a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3381,8 +3381,18 @@ 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) + { + 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..d67118bdf04fef 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -11,11 +11,14 @@ #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) +#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;