diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index 11bb3ee728132f..a3c8052c85187c 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -116,7 +116,6 @@ set( JIT_SOURCES fgprofile.cpp fgprofilesynthesis.cpp fgstmt.cpp - fgwasm.cpp flowgraph.cpp forwardsub.cpp gcinfo.cpp @@ -293,6 +292,7 @@ set( JIT_RISCV64_SOURCES set( JIT_WASM_SOURCES codegenwasm.cpp emitwasm.cpp + fgwasm.cpp lowerwasm.cpp regallocwasm.cpp registeropswasm.cpp diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 5550d219719519..fc1e6a33bf016a 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -212,6 +212,15 @@ class CodeGen final : public CodeGenInterface void genCodeForBBlist(); +#if defined(TARGET_WASM) + ArrayStack* wasmControlFlowStack = nullptr; + unsigned wasmCursor = 0; + unsigned findTargetDepth(BasicBlock* target); +#endif + + void genEmitStartBlock(BasicBlock* block); + BasicBlock* genEmitEndBlock(BasicBlock* block); + public: void genSpillVar(GenTree* tree); @@ -893,8 +902,8 @@ class CodeGen final : public CodeGenInterface unsigned getFirstArgWithStackSlot(); - void genCompareFloat(GenTree* treeNode); - void genCompareInt(GenTree* treeNode); + void genCompareFloat(GenTreeOp* treeNode); + void genCompareInt(GenTreeOp* treeNode); #ifdef TARGET_XARCH bool genCanAvoidEmittingCompareAgainstZero(GenTree* tree, var_types opType); GenTree* genTryFindFlagsConsumer(GenTree* flagsProducer, GenCondition** condition); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c5f2cf20db5da2..59c49cf45550ea 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -4782,7 +4782,6 @@ void CodeGen::genFinalizeFrame() #endif } -#ifndef TARGET_WASM /***************************************************************************** * * Generates code for a function prolog. @@ -4823,7 +4822,10 @@ void CodeGen::genFnProlog() /* Ready to start on the prolog proper */ GetEmitter()->emitBegProlog(); + +#if !defined(TARGET_WASM) compiler->unwindBegProlog(); +#endif // !defined(TARGET_WASM) // Do this so we can put the prolog instruction group ahead of // other instruction groups @@ -4842,6 +4844,8 @@ void CodeGen::genFnProlog() psiBegProlog(); } +#if !defined(TARGET_WASM) + #if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) // For arm64 OSR, emit a "phantom prolog" to account for the actions taken // in the tier0 frame that impact FP and SP on entry to the OSR method. @@ -5640,9 +5644,16 @@ void CodeGen::genFnProlog() } #endif // defined(DEBUG) && defined(TARGET_XARCH) +#else // defined(TARGET_WASM) + // TODO-WASM: prolog zeroing, shadow stack maintenance + GetEmitter()->emitMarkPrologEnd(); +#endif // !defined(TARGET_WASM) + GetEmitter()->emitEndProlog(); } +#if !defined(TARGET_WASM) + //---------------------------------------------------------------------------------- // genEmitJumpTable: emit jump table and return its base offset // @@ -5844,7 +5855,7 @@ void CodeGen::genDefinePendingCallLabel(GenTreeCall* call) genDefineInlineTempLabel(genPendingCallLabel); genPendingCallLabel = nullptr; } -#endif // !TARGET_WASM +#endif // !defined(TARGET_WASM) /***************************************************************************** * diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 6d0890ef352883..b71d8efffefa13 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -17,6 +17,10 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "emit.h" #include "codegen.h" +#if defined(TARGET_WASM) +class WasmInterval; +#endif + //------------------------------------------------------------------------ // genInitializeRegisterState: Initialize the register state contained in 'regSet'. // @@ -106,6 +110,20 @@ void CodeGen::genInitialize() // We initialize the stack level before first "BasicBlock" code is generated in case we need to report stack // variable needs home and so its stack offset. SetStackLevel(0); + +#if defined(TARGET_WASM) + // Wasm control flow is stack based. + // + // We have pre computed the set of intervals that require control flow + // stack transitions in compiler->fgWasmIntervals, ordered by starting block index. + // + // As we walk the blocks we'll push and pop onto this stack. As we emit control + // flow instructions, we'll consult this stack to figure out the depth of the target labels. + // + wasmControlFlowStack = + new (compiler, CMK_WasmCfgLowering) ArrayStack(compiler->getAllocator(CMK_WasmCfgLowering)); + wasmCursor = 0; +#endif } //------------------------------------------------------------------------ @@ -354,6 +372,8 @@ void CodeGen::genCodeForBBlist() GetEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie); } + genEmitStartBlock(block); + // Both stacks are always empty on entry to a basic block. assert(genStackLevel == 0); genAdjustStackLevel(block); @@ -620,296 +640,321 @@ void CodeGen::genCodeForBBlist() /* Both stacks should always be empty on exit from a basic block */ noway_assert(genStackLevel == 0); + BasicBlock* const nextBlock = genEmitEndBlock(block); + + // Sometimes we might skip ahead in the block list + // + if (nextBlock != nullptr) + { + block = nextBlock; + } + +#ifdef DEBUG + if (compiler->verbose) + { + varLiveKeeper->dumpBlockVariableLiveRanges(block); + } + compiler->compCurBB = nullptr; +#endif // DEBUG + } //------------------ END-FOR each block of the method ------------------- + +#if defined(FEATURE_EH_WINDOWS_X86) + // If this is a synchronized method on x86, and we generated all the code without + // generating the "exit monitor" call, then we must have deleted the single return block + // with that call because it was dead code. We still need to report the monitor range + // to the VM in the GC info, so create a label at the very end so we have a marker for + // the monitor end range. + // + // Do this before cleaning the GC refs below; we don't want to create an IG that clears + // the `this` pointer for lvaKeepAliveAndReportThis. + + if (!compiler->UsesFunclets() && (compiler->info.compFlags & CORINFO_FLG_SYNCH) && + (compiler->syncEndEmitCookie == nullptr)) + { + JITDUMP("Synchronized method with missing exit monitor call; adding final label\n"); + compiler->syncEndEmitCookie = + GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); + noway_assert(compiler->syncEndEmitCookie != nullptr); + } +#endif + + // There could be variables alive at this point. For example see lvaKeepAliveAndReportThis. + // This call is for cleaning the GC refs + genUpdateLife(VarSetOps::MakeEmpty(compiler)); + + // Finalize the spill tracking logic + + regSet.rsSpillEnd(); + + // Finalize the temp tracking logic + + regSet.tmpEnd(); + +#ifdef DEBUG + if (compiler->verbose) + { + printf("\n# "); + printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate, + compiler->compSizeEstimate); + printf("%s\n", compiler->info.compFullName); + } +#endif +} + +//------------------------------------------------------------------------ +// genEmitEndBlock: finish up codegen in a block +// +// Arguments: +// block - block to finish up +// +// Returns: +// Updated block to process (if not block->Next()) or nullptr. +// +BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) +{ + BasicBlock* result = nullptr; + #ifdef TARGET_AMD64 - bool emitNopBeforeEHRegion = false; - // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several - // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack - // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region. - // The document "clr-abi.md" has more details. The situations: - // 1. If the call instruction is in a different EH region as the instruction that follows it. - // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might - // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters - // here.) - // We handle case #1 here, and case #2 in the emitter. - if (GetEmitter()->emitIsLastInsCall()) - { - // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold? - // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically, - // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions - // generated before the OS epilog starts, such as a GS cookie check. - if (block->IsLast() || !BasicBlock::sameEHRegion(block, block->Next())) + bool emitNopBeforeEHRegion = false; + // On AMD64, we need to generate a NOP after a call that is the last instruction of the block, in several + // situations, to support proper exception handling semantics. This is mostly to ensure that when the stack + // walker computes an instruction pointer for a frame, that instruction pointer is in the correct EH region. + // The document "clr-abi.md" has more details. The situations: + // 1. If the call instruction is in a different EH region as the instruction that follows it. + // 2. If the call immediately precedes an OS epilog. (Note that what the JIT or VM consider an epilog might + // be slightly different from what the OS considers an epilog, and it is the OS-reported epilog that matters + // here.) + // We handle case #1 here, and case #2 in the emitter. + if (GetEmitter()->emitIsLastInsCall()) + { + // Ok, the last instruction generated is a call instruction. Do any of the other conditions hold? + // Note: we may be generating a few too many NOPs for the case of call preceding an epilog. Technically, + // if the next block is a BBJ_RETURN, an epilog will be generated, but there may be some instructions + // generated before the OS epilog starts, such as a GS cookie check. + if (block->IsLast() || !BasicBlock::sameEHRegion(block, block->Next())) + { + // We only need the NOP if we're not going to generate any more code as part of the block end. + + switch (block->GetKind()) { - // We only need the NOP if we're not going to generate any more code as part of the block end. + case BBJ_ALWAYS: + // We might skip generating the jump via a peephole optimization. + // If that happens, make sure a NOP is emitted as the last instruction in the block. + emitNopBeforeEHRegion = true; + break; - switch (block->GetKind()) - { - case BBJ_ALWAYS: - // We might skip generating the jump via a peephole optimization. - // If that happens, make sure a NOP is emitted as the last instruction in the block. - emitNopBeforeEHRegion = true; - break; - - case BBJ_THROW: - case BBJ_CALLFINALLY: - case BBJ_EHCATCHRET: - // We're going to generate more code below anyway, so no need for the NOP. - - case BBJ_RETURN: - case BBJ_EHFINALLYRET: - case BBJ_EHFAULTRET: - case BBJ_EHFILTERRET: - // These are the "epilog follows" case, handled in the emitter. - break; - - case BBJ_COND: - case BBJ_SWITCH: - // These can't have a call as the last instruction! - - default: - noway_assert(!"Unexpected bbKind"); - break; - } + case BBJ_THROW: + case BBJ_CALLFINALLY: + case BBJ_EHCATCHRET: + // We're going to generate more code below anyway, so no need for the NOP. + + case BBJ_RETURN: + case BBJ_EHFINALLYRET: + case BBJ_EHFAULTRET: + case BBJ_EHFILTERRET: + // These are the "epilog follows" case, handled in the emitter. + break; + + case BBJ_COND: + case BBJ_SWITCH: + // These can't have a call as the last instruction! + + default: + noway_assert(!"Unexpected bbKind"); + break; } } + } #endif // TARGET_AMD64 #if FEATURE_LOOP_ALIGN - auto SetLoopAlignBackEdge = [=](const BasicBlock* block, const BasicBlock* target) { - // This is the last place where we operate on blocks and after this, we operate - // on IG. Hence, if we know that the destination of "block" is the first block - // of a loop and that loop needs alignment (it has BBF_LOOP_ALIGN), then "block" - // might represent the lexical end of the loop. Propagate that information on the - // IG through "igLoopBackEdge". - // - // During emitter, this information will be used to calculate the loop size. - // Depending on the loop size, the decision of whether to align a loop or not will be taken. - // (Loop size is calculated by walking the instruction groups; see emitter::getLoopSize()). - // If `igLoopBackEdge` is set, then mark the next BasicBlock as a label. This will cause - // the emitter to create a new IG for the next block. Otherwise, if the next block - // did not have a label, additional instructions might be added to the current IG. This - // would make the "back edge" IG larger, possibly causing the size of the loop computed - // by `getLoopSize()` to be larger than actual, which could push the loop size over the - // threshold of loop size that can be aligned. - - if (target->isLoopAlign()) + auto SetLoopAlignBackEdge = [=](const BasicBlock* block, const BasicBlock* target) { + // This is the last place where we operate on blocks and after this, we operate + // on IG. Hence, if we know that the destination of "block" is the first block + // of a loop and that loop needs alignment (it has BBF_LOOP_ALIGN), then "block" + // might represent the lexical end of the loop. Propagate that information on the + // IG through "igLoopBackEdge". + // + // During emitter, this information will be used to calculate the loop size. + // Depending on the loop size, the decision of whether to align a loop or not will be taken. + // (Loop size is calculated by walking the instruction groups; see emitter::getLoopSize()). + // If `igLoopBackEdge` is set, then mark the next BasicBlock as a label. This will cause + // the emitter to create a new IG for the next block. Otherwise, if the next block + // did not have a label, additional instructions might be added to the current IG. This + // would make the "back edge" IG larger, possibly causing the size of the loop computed + // by `getLoopSize()` to be larger than actual, which could push the loop size over the + // threshold of loop size that can be aligned. + + if (target->isLoopAlign()) + { + if (GetEmitter()->emitSetLoopBackEdge(target)) { - if (GetEmitter()->emitSetLoopBackEdge(target)) + if (!block->IsLast()) { - if (!block->IsLast()) - { - JITDUMP("Mark " FMT_BB " as label: alignment end-of-loop\n", block->Next()->bbNum); - block->Next()->SetFlags(BBF_HAS_LABEL); - } + JITDUMP("Mark " FMT_BB " as label: alignment end-of-loop\n", block->Next()->bbNum); + block->Next()->SetFlags(BBF_HAS_LABEL); } } - }; + } + }; #endif // FEATURE_LOOP_ALIGN - /* Do we need to generate a jump or return? */ - - bool removedJmp = false; - switch (block->GetKind()) - { - case BBJ_RETURN: - genExitCode(block); - break; + /* Do we need to generate a jump or return? */ + + bool removedJmp = false; + switch (block->GetKind()) + { + case BBJ_RETURN: + genExitCode(block); + break; + + case BBJ_THROW: + // If we have a throw at the end of a function or funclet, we need to emit another instruction + // afterwards to help the OS unwinder determine the correct context during unwind. + // We insert an unexecuted breakpoint instruction in several situations + // following a throw instruction: + // 1. If the throw is the last instruction of the function or funclet. This helps + // the OS unwinder determine the correct context during an unwind from the + // thrown exception. + // 2. If this is this is the last block of the hot section. + // 3. If the subsequent block is a special throw block. + // 4. On AMD64, if the next block is in a different EH region. + if (block->IsLast() || !BasicBlock::sameEHRegion(block, block->Next()) || + (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->Next())) || + compiler->bbIsFuncletBeg(block->Next()) || block->IsLastHotBlock(compiler)) + { + instGen(INS_BREAKPOINT); // This should never get executed + } + // Do likewise for blocks that end in DOES_NOT_RETURN calls + // that were not caught by the above rules. This ensures that + // gc register liveness doesn't change to some random state after call instructions + else + { + GenTree* call = block->lastNode(); - case BBJ_THROW: - // If we have a throw at the end of a function or funclet, we need to emit another instruction - // afterwards to help the OS unwinder determine the correct context during unwind. - // We insert an unexecuted breakpoint instruction in several situations - // following a throw instruction: - // 1. If the throw is the last instruction of the function or funclet. This helps - // the OS unwinder determine the correct context during an unwind from the - // thrown exception. - // 2. If this is this is the last block of the hot section. - // 3. If the subsequent block is a special throw block. - // 4. On AMD64, if the next block is in a different EH region. - if (block->IsLast() || !BasicBlock::sameEHRegion(block, block->Next()) || - (!isFramePointerUsed() && compiler->fgIsThrowHlpBlk(block->Next())) || - compiler->bbIsFuncletBeg(block->Next()) || block->IsLastHotBlock(compiler)) - { - instGen(INS_BREAKPOINT); // This should never get executed - } - // Do likewise for blocks that end in DOES_NOT_RETURN calls - // that were not caught by the above rules. This ensures that - // gc register liveness doesn't change to some random state after call instructions - else + if ((call != nullptr) && call->OperIs(GT_CALL)) { - GenTree* call = block->lastNode(); - - if ((call != nullptr) && call->OperIs(GT_CALL)) + if (call->AsCall()->IsNoReturn()) { - if (call->AsCall()->IsNoReturn()) - { - instGen(INS_BREAKPOINT); // This should never get executed - } + instGen(INS_BREAKPOINT); // This should never get executed } } + } - break; + break; - case BBJ_CALLFINALLY: - block = genCallFinally(block); - break; + case BBJ_CALLFINALLY: + result = genCallFinally(block); + break; - case BBJ_EHCATCHRET: - assert(compiler->UsesFunclets()); - genEHCatchRet(block); - FALLTHROUGH; + case BBJ_EHCATCHRET: + assert(compiler->UsesFunclets()); + genEHCatchRet(block); + FALLTHROUGH; - case BBJ_EHFINALLYRET: - case BBJ_EHFAULTRET: - case BBJ_EHFILTERRET: - if (compiler->UsesFunclets()) - { - genReserveFuncletEpilog(block); - } + case BBJ_EHFINALLYRET: + case BBJ_EHFAULTRET: + case BBJ_EHFILTERRET: + if (compiler->UsesFunclets()) + { + genReserveFuncletEpilog(block); + } #if defined(FEATURE_EH_WINDOWS_X86) - else - { - genEHFinallyOrFilterRet(block); - } + else + { + genEHFinallyOrFilterRet(block); + } #endif // FEATURE_EH_WINDOWS_X86 - break; + break; - case BBJ_SWITCH: - break; + case BBJ_SWITCH: + break; - case BBJ_ALWAYS: - { + case BBJ_ALWAYS: + { #ifdef DEBUG - GenTree* call = block->lastNode(); - if ((call != nullptr) && call->OperIs(GT_CALL)) - { - // At this point, BBJ_ALWAYS should never end with a call that doesn't return. - assert(!call->AsCall()->IsNoReturn()); - } + GenTree* call = block->lastNode(); + if ((call != nullptr) && call->OperIs(GT_CALL)) + { + // At this point, BBJ_ALWAYS should never end with a call that doesn't return. + assert(!call->AsCall()->IsNoReturn()); + } #endif // DEBUG - // If this block jumps to the next one, we might be able to skip emitting the jump - if (block->CanRemoveJumpToNext(compiler)) - { + // If this block jumps to the next one, we might be able to skip emitting the jump + if (block->CanRemoveJumpToNext(compiler)) + { #ifdef TARGET_AMD64 - if (emitNopBeforeEHRegion) - { - instGen(INS_nop); - } + if (emitNopBeforeEHRegion) + { + instGen(INS_nop); + } #endif // TARGET_AMD64 - removedJmp = true; - break; - } + removedJmp = true; + break; + } #ifdef TARGET_XARCH - // Do not remove a jump between hot and cold regions. - bool isRemovableJmpCandidate = !compiler->fgInDifferentRegions(block, block->GetTarget()); + // Do not remove a jump between hot and cold regions. + bool isRemovableJmpCandidate = !compiler->fgInDifferentRegions(block, block->GetTarget()); - inst_JMP(EJ_jmp, block->GetTarget(), isRemovableJmpCandidate); + inst_JMP(EJ_jmp, block->GetTarget(), isRemovableJmpCandidate); #else // !TARGET_XARCH - inst_JMP(EJ_jmp, block->GetTarget()); + inst_JMP(EJ_jmp, block->GetTarget()); #endif // !TARGET_XARCH - } + } #if FEATURE_LOOP_ALIGN - SetLoopAlignBackEdge(block, block->GetTarget()); + SetLoopAlignBackEdge(block, block->GetTarget()); #endif // FEATURE_LOOP_ALIGN - break; + break; - case BBJ_COND: + case BBJ_COND: #if FEATURE_LOOP_ALIGN - // Either true or false target of BBJ_COND can induce a loop. - SetLoopAlignBackEdge(block, block->GetTrueTarget()); - SetLoopAlignBackEdge(block, block->GetFalseTarget()); + // Either true or false target of BBJ_COND can induce a loop. + SetLoopAlignBackEdge(block, block->GetTrueTarget()); + SetLoopAlignBackEdge(block, block->GetFalseTarget()); #endif // FEATURE_LOOP_ALIGN - break; + break; - default: - noway_assert(!"Unexpected bbKind"); - break; - } + default: + noway_assert(!"Unexpected bbKind"); + break; + } #if FEATURE_LOOP_ALIGN - if (block->hasAlign()) - { - // If this block has 'align' instruction in the end (identified by BBF_HAS_ALIGN), - // then need to add align instruction in the current "block". - // - // For non-adaptive alignment, add alignment instruction of size depending on the - // compJitAlignLoopBoundary. - // For adaptive alignment, alignment instruction will always be of 15 bytes for xarch - // and 16 bytes for arm64. - - assert(ShouldAlignLoops()); - assert(!block->isBBCallFinallyPairTail()); - assert(!block->KindIs(BBJ_CALLFINALLY)); + if (block->hasAlign()) + { + // If this block has 'align' instruction in the end (identified by BBF_HAS_ALIGN), + // then need to add align instruction in the current "block". + // + // For non-adaptive alignment, add alignment instruction of size depending on the + // compJitAlignLoopBoundary. + // For adaptive alignment, alignment instruction will always be of 15 bytes for xarch + // and 16 bytes for arm64. - GetEmitter()->emitLoopAlignment(DEBUG_ARG1(block->KindIs(BBJ_ALWAYS) && !removedJmp)); - } + assert(ShouldAlignLoops()); + assert(!block->isBBCallFinallyPairTail()); + assert(!block->KindIs(BBJ_CALLFINALLY)); - if (!block->IsLast() && block->Next()->isLoopAlign()) - { - if (compiler->opts.compJitHideAlignBehindJmp) - { - // The current IG is the one that is just before the IG having loop start. - // Establish a connection of recent align instruction emitted to the loop - // it actually is aligning using 'idaLoopHeadPredIG'. - GetEmitter()->emitConnectAlignInstrWithCurIG(); - } - } -#endif // FEATURE_LOOP_ALIGN + GetEmitter()->emitLoopAlignment(DEBUG_ARG1(block->KindIs(BBJ_ALWAYS) && !removedJmp)); + } -#ifdef DEBUG - if (compiler->verbose) + if (!block->IsLast() && block->Next()->isLoopAlign()) + { + if (compiler->opts.compJitHideAlignBehindJmp) { - varLiveKeeper->dumpBlockVariableLiveRanges(block); + // The current IG is the one that is just before the IG having loop start. + // Establish a connection of recent align instruction emitted to the loop + // it actually is aligning using 'idaLoopHeadPredIG'. + GetEmitter()->emitConnectAlignInstrWithCurIG(); } - compiler->compCurBB = nullptr; -#endif // DEBUG - } //------------------ END-FOR each block of the method ------------------- - -#if defined(FEATURE_EH_WINDOWS_X86) - // If this is a synchronized method on x86, and we generated all the code without - // generating the "exit monitor" call, then we must have deleted the single return block - // with that call because it was dead code. We still need to report the monitor range - // to the VM in the GC info, so create a label at the very end so we have a marker for - // the monitor end range. - // - // Do this before cleaning the GC refs below; we don't want to create an IG that clears - // the `this` pointer for lvaKeepAliveAndReportThis. - - if (!compiler->UsesFunclets() && (compiler->info.compFlags & CORINFO_FLG_SYNCH) && - (compiler->syncEndEmitCookie == nullptr)) - { - JITDUMP("Synchronized method with missing exit monitor call; adding final label\n"); - compiler->syncEndEmitCookie = - GetEmitter()->emitAddLabel(gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); - noway_assert(compiler->syncEndEmitCookie != nullptr); } -#endif - - // There could be variables alive at this point. For example see lvaKeepAliveAndReportThis. - // This call is for cleaning the GC refs - genUpdateLife(VarSetOps::MakeEmpty(compiler)); - - // Finalize the spill tracking logic - - regSet.rsSpillEnd(); - - // Finalize the temp tracking logic - - regSet.tmpEnd(); +#endif // FEATURE_LOOP_ALIGN -#ifdef DEBUG - if (compiler->verbose) - { - printf("\n# "); - printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate, - compiler->compSizeEstimate); - printf("%s\n", compiler->info.compFullName); - } -#endif + return result; } // TODO-WASM-Factoring: this ifdef factoring is temporary. The end factoring should look like this: @@ -918,6 +963,17 @@ void CodeGen::genCodeForBBlist() // 3. codegenlinear.cpp gets renamed to codegennative.cpp. // #ifndef TARGET_WASM + +//------------------------------------------------------------------------ +// genEmitStartBlock: prepare for codegen in a block +// +// Arguments: +// block - block to prepare for +// +void CodeGen::genEmitStartBlock(BasicBlock* block) +{ +} + //------------------------------------------------------------------------ // genRecordAsyncResume: // Record information about an async resume point in the async resume info tabl.e diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 84bfb2eb99199b..ae9dac8e0a3c55 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -7,6 +7,7 @@ #endif #include "codegen.h" +#include "fgwasm.h" void CodeGen::genMarkLabelsForCodegen() { @@ -14,11 +15,6 @@ void CodeGen::genMarkLabelsForCodegen() // (or use them directly and leave this empty). } -void CodeGen::genFnProlog() -{ - NYI_WASM("Uncomment CodeGen::genFnProlog and proceed from there"); -} - void CodeGen::genFnEpilog(BasicBlock* block) { #ifdef DEBUG @@ -28,7 +24,24 @@ void CodeGen::genFnEpilog(BasicBlock* block) } #endif // DEBUG - NYI_WASM("genFnEpilog"); + ScopedSetVariable _setGeneratingEpilog(&compiler->compGeneratingEpilog, true); + +#ifdef DEBUG + if (compiler->opts.dspCode) + printf("\n__epilog:\n"); +#endif // DEBUG + + bool jmpEpilog = block->HasFlag(BBF_HAS_JMP); + + if (jmpEpilog) + { + NYI_WASM("genFnEpilog: jmpEpilog"); + } + + // TODO-WASM: shadow stack maintenance + // TODO-WASM-CQ: do not emit "return" in case this is the last block + + instGen(INS_return); } void CodeGen::genCaptureFuncletPrologEpilogInfo() @@ -59,6 +72,130 @@ void CodeGen::genFuncletEpilog() NYI_WASM("genFuncletEpilog"); } +//------------------------------------------------------------------------ +// getBlockIndex: return the index of this block in the linear block +// order +// +// Arguments: +// block - block in question +// +// Returns: +// index of block +// +static unsigned getBlockIndex(BasicBlock* block) +{ + return block->bbPreorderNum; +} + +//------------------------------------------------------------------------ +// findTargetDepth: find the depth of a target block in the wasm control flow stack +// +// Arguments: +// targetBlock - block to branch to +// (implicit) compCurBB -- block to branch from +// +// Returns: +// depth of target block in control stack +// +unsigned CodeGen::findTargetDepth(BasicBlock* targetBlock) +{ + BasicBlock* const sourceBlock = compiler->compCurBB; + int const h = wasmControlFlowStack->Height(); + + const unsigned targetIndex = getBlockIndex(targetBlock); + const unsigned sourceIndex = getBlockIndex(sourceBlock); + const bool isBackedge = targetIndex <= sourceIndex; + + for (int i = 0; i < h; i++) + { + WasmInterval* const ii = wasmControlFlowStack->Top(i); + unsigned match = 0; + + if (isBackedge) + { + // loops bind to start + match = ii->Start(); + } + else + { + // blocks bind to end + match = ii->End(); + } + + if ((match == targetIndex) && (isBackedge == ii->IsLoop())) + { + return i; + } + } + + JITDUMP("Could not find " FMT_BB "[%u]%s in active control stack\n", targetBlock->bbNum, targetIndex, + isBackedge ? " (backedge)" : ""); + assert(!"Can't find target in control stack"); + + return ~0; +} + +//------------------------------------------------------------------------ +// genEmitStartBlock: prepare for codegen in a block +// +// Arguments: +// block - block to prepare for +// +// Notes: +// Updates the wasm control flow stack +// +void CodeGen::genEmitStartBlock(BasicBlock* block) +{ + const unsigned cursor = getBlockIndex(block); + + // Pop control flow intervals that end here (at most two, block and/or loop) + // and emit wasm END instructions for them. + // + while (!wasmControlFlowStack->Empty() && (wasmControlFlowStack->Top()->End() == cursor)) + { + instGen(INS_end); + wasmControlFlowStack->Pop(); + } + + // Push control flow for intervals that start here or earlier, and emit + // Wasm BLOCK or LOOP instruction + // + if (wasmCursor < compiler->fgWasmIntervals->size()) + { + WasmInterval* interval = compiler->fgWasmIntervals->at(wasmCursor); + WasmInterval* chain = interval->Chain(); + + while (chain->Start() <= cursor) + { + if (interval->IsLoop()) + { + instGen(INS_loop); + } + else + { + instGen(INS_block); + } + + wasmCursor++; + wasmControlFlowStack->Push(interval); + + if (wasmCursor >= compiler->fgWasmIntervals->size()) + { + break; + } + + interval = compiler->fgWasmIntervals->at(wasmCursor); + chain = interval->Chain(); + } + } +} + +//------------------------------------------------------------------------ +// genCodeForTreeNode: codegen for a particular tree node +// +// Arguments: +// treeNode - node to generate code for +// void CodeGen::genCodeForTreeNode(GenTree* treeNode) { #ifdef DEBUG @@ -100,10 +237,27 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForShiftOrRotate(treeNode->AsOp()); break; + case GT_EQ: + case GT_NE: + case GT_LT: + case GT_LE: + case GT_GE: + case GT_GT: + genCodeForCompare(treeNode->AsOp()); + break; + case GT_LCL_VAR: genCodeForLclVar(treeNode->AsLclVar()); break; + case GT_JTRUE: + genCodeForJTrue(treeNode->AsOp()); + break; + + case GT_SWITCH: + genTableBasedSwitch(treeNode); + break; + case GT_RETURN: genReturn(treeNode); break; @@ -122,6 +276,78 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) } } +//------------------------------------------------------------------------ +// genCodeForJTrue: emit Wasm br_if +// +// Arguments: +// treeNode - predicate value +// +void CodeGen::genCodeForJTrue(GenTreeOp* jtrue) +{ + BasicBlock* const block = compiler->compCurBB; + assert(block->KindIs(BBJ_COND)); + + genConsumeOperands(jtrue); + + BasicBlock* const trueTarget = block->GetTrueTarget(); + BasicBlock* const falseTarget = block->GetFalseTarget(); + + // We don't expect degenerate BBJ_COND + // + assert(trueTarget != falseTarget); + + // We don't expect the true target to be the next block. + // + assert(trueTarget != block->Next()); + + // br_if for true target + // + inst_JMP(EJ_jmpif, trueTarget); + + // br for false target, if not fallthrough + // + if (falseTarget != block->Next()) + { + inst_JMP(EJ_jmp, falseTarget); + } +} + +//------------------------------------------------------------------------ +// genTableBasedSwitch: emit Wasm br_table +// +// Arguments: +// treeNode - value to switch on +// +void CodeGen::genTableBasedSwitch(GenTree* treeNode) +{ + BasicBlock* const block = compiler->compCurBB; + assert(block->KindIs(BBJ_SWITCH)); + + genConsumeOperands(treeNode->AsOp()); + + BBswtDesc* const desc = block->GetSwitchTargets(); + unsigned const caseCount = desc->GetCaseCount(); + + // TODO-WASM: update lowering not to peel off the default + // + assert(!desc->HasDefaultCase()); + + if (caseCount == 0) + { + return; + } + + GetEmitter()->emitIns_I(INS_br_table, EA_4BYTE, caseCount); + + for (unsigned caseNum = 0; caseNum < caseCount; caseNum++) + { + BasicBlock* const caseTarget = desc->GetCase(caseNum)->getDestinationBlock(); + unsigned depth = findTargetDepth(caseTarget); + + GetEmitter()->emitIns_I(INS_label, EA_4BYTE, depth); + } +} + //------------------------------------------------------------------------ // PackOperAndType: Pack a genTreeOps and var_types into a uint32_t // @@ -377,6 +603,151 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) } } +//------------------------------------------------------------------------ +// genCodeForCompare: Produce code for a GT_EQ/GT_NE/GT_LT/GT_LE/GT_GE/GT_GT node. +// +// Arguments: +// tree - the node +// +void CodeGen::genCodeForCompare(GenTreeOp* tree) +{ + assert(tree->OperIsCmpCompare()); + + GenTree* const op1 = tree->gtGetOp1(); + var_types const op1Type = op1->TypeGet(); + + if (varTypeIsFloating(op1Type)) + { + genCompareFloat(tree); + } + else + { + genCompareInt(tree); + } +} + +//------------------------------------------------------------------------ +// genCompareInt: Generate code for comparing ints or longs +// +// Arguments: +// treeNode - the compare tree +// +void CodeGen::genCompareInt(GenTreeOp* treeNode) +{ + assert(treeNode->OperIsCmpCompare()); + genConsumeOperands(treeNode); + + instruction ins; + switch (PackOperAndType(treeNode->OperGet(), genActualType(treeNode->gtGetOp1()->TypeGet()))) + { + case PackOperAndType(GT_EQ, TYP_INT): + ins = INS_i32_eq; + break; + case PackOperAndType(GT_EQ, TYP_LONG): + ins = INS_i64_eq; + break; + case PackOperAndType(GT_NE, TYP_INT): + ins = INS_i32_ne; + break; + case PackOperAndType(GT_NE, TYP_LONG): + ins = INS_i64_ne; + break; + case PackOperAndType(GT_LT, TYP_INT): + ins = treeNode->IsUnsigned() ? INS_i32_lt_u : INS_i32_lt_s; + break; + case PackOperAndType(GT_LT, TYP_LONG): + ins = treeNode->IsUnsigned() ? INS_i64_lt_u : INS_i64_lt_s; + break; + case PackOperAndType(GT_LE, TYP_INT): + ins = treeNode->IsUnsigned() ? INS_i32_le_u : INS_i32_le_s; + break; + case PackOperAndType(GT_LE, TYP_LONG): + ins = treeNode->IsUnsigned() ? INS_i64_le_u : INS_i64_le_s; + break; + case PackOperAndType(GT_GE, TYP_INT): + ins = treeNode->IsUnsigned() ? INS_i32_ge_u : INS_i32_ge_s; + break; + case PackOperAndType(GT_GE, TYP_LONG): + ins = treeNode->IsUnsigned() ? INS_i64_ge_u : INS_i64_ge_s; + break; + case PackOperAndType(GT_GT, TYP_INT): + ins = treeNode->IsUnsigned() ? INS_i32_gt_u : INS_i32_gt_s; + break; + case PackOperAndType(GT_GT, TYP_LONG): + ins = treeNode->IsUnsigned() ? INS_i64_gt_u : INS_i64_gt_s; + break; + default: + unreached(); + } + + GetEmitter()->emitIns(ins); + genProduceReg(treeNode); +} + +//------------------------------------------------------------------------ +// genCompareFloat: Generate code for comparing floats +// +// Arguments: +// treeNode - the compare tree +// +void CodeGen::genCompareFloat(GenTreeOp* treeNode) +{ + assert(treeNode->OperIsCmpCompare()); + + if ((treeNode->gtFlags & GTF_RELOP_NAN_UN) != 0) + { + NYI_WASM("genCompareFloat: unordered compares"); + } + + genConsumeOperands(treeNode); + + instruction ins; + switch (PackOperAndType(treeNode->OperGet(), treeNode->gtOp1->TypeGet())) + { + case PackOperAndType(GT_EQ, TYP_FLOAT): + ins = INS_f32_eq; + break; + case PackOperAndType(GT_EQ, TYP_DOUBLE): + ins = INS_f64_eq; + break; + case PackOperAndType(GT_NE, TYP_FLOAT): + ins = INS_f32_ne; + break; + case PackOperAndType(GT_NE, TYP_DOUBLE): + ins = INS_f64_ne; + break; + case PackOperAndType(GT_LT, TYP_FLOAT): + ins = INS_f32_lt; + break; + case PackOperAndType(GT_LT, TYP_DOUBLE): + ins = INS_f64_lt; + break; + case PackOperAndType(GT_LE, TYP_FLOAT): + ins = INS_f32_le; + break; + case PackOperAndType(GT_LE, TYP_DOUBLE): + ins = INS_f64_le; + break; + case PackOperAndType(GT_GE, TYP_FLOAT): + ins = INS_f32_ge; + break; + case PackOperAndType(GT_GE, TYP_DOUBLE): + ins = INS_f64_ge; + break; + case PackOperAndType(GT_GT, TYP_FLOAT): + ins = INS_f32_gt; + break; + case PackOperAndType(GT_GT, TYP_DOUBLE): + ins = INS_f64_gt; + break; + default: + unreached(); + } + + GetEmitter()->emitIns(ins); + genProduceReg(treeNode); +} + BasicBlock* CodeGen::genCallFinally(BasicBlock* block) { assert(block->KindIs(BBJ_CALLFINALLY)); @@ -412,11 +783,17 @@ void CodeGen::genSpillVar(GenTree* tree) } //------------------------------------------------------------------------ -// inst_JMP: Generate a jump instruction. +// inst_JMP: Emit a jump instruction. +// +// Arguments: +// jmp - kind of jump to emit +// tgtBlock - target of the jump // void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) { - NYI_WASM("inst_JMP"); + instruction instr = emitter::emitJumpKindToIns(jmp); + unsigned const depth = findTargetDepth(tgtBlock); + GetEmitter()->emitIns_I(instr, EA_4BYTE, depth); } void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, unsigned epilogSize DEBUGARG(void* code)) diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 47256aa050acec..a66c5264c8bb71 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -6626,11 +6626,11 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) // Arguments: // treeNode - the compare tree // -void CodeGen::genCompareFloat(GenTree* treeNode) +void CodeGen::genCompareFloat(GenTreeOp* treeNode) { assert(treeNode->OperIsCompare() || treeNode->OperIs(GT_CMP)); - GenTreeOp* tree = treeNode->AsOp(); + GenTreeOp* tree = treeNode; GenTree* op1 = tree->gtOp1; GenTree* op2 = tree->gtOp2; var_types op1Type = op1->TypeGet(); @@ -6691,11 +6691,11 @@ void CodeGen::genCompareFloat(GenTree* treeNode) // // Return Value: // None. -void CodeGen::genCompareInt(GenTree* treeNode) +void CodeGen::genCompareInt(GenTreeOp* treeNode) { assert(treeNode->OperIsCompare() || treeNode->OperIs(GT_CMP, GT_TEST, GT_BT)); - GenTreeOp* tree = treeNode->AsOp(); + GenTreeOp* tree = treeNode; GenTree* op1 = tree->gtOp1; GenTree* op2 = tree->gtOp2; var_types op1Type = op1->TypeGet(); diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 7482fc2fe1ced8..dc436ed0ff9e82 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4940,17 +4940,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl } #endif -#ifdef DEBUG - // If we are going to simulate generating wasm control flow, - // transform any strongly connected components into reducible flow. - // - if (JitConfig.JitWasmControlFlow() > 0) - { - DoPhase(this, PHASE_DFS_BLOCKS_WASM, &Compiler::fgDfsBlocksAndRemove); - DoPhase(this, PHASE_WASM_TRANSFORM_SCCS, &Compiler::fgWasmTransformSccs); - } -#endif - // rationalize trees Rationalizer rat(this); // PHASE_RATIONALIZE rat.Run(); @@ -4974,6 +4963,13 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl DoPhase(this, PHASE_ASYNC, &Compiler::TransformAsync); } +#ifdef TARGET_WASM + // Transform any strongly connected components into reducible flow. + // + DoPhase(this, PHASE_DFS_BLOCKS_WASM, &Compiler::fgDfsBlocksAndRemove); + DoPhase(this, PHASE_WASM_TRANSFORM_SCCS, &Compiler::fgWasmTransformSccs); +#endif + // Assign registers to variables, etc. // Create the RA before Lowering, so that Lowering can call RA methods for @@ -4993,16 +4989,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl FinalizeEH(); -#ifdef DEBUG - // Optionally, simulate generating wasm control flow - // (eventually this will become part of the wasm target) - // - if (JitConfig.JitWasmControlFlow() > 0) - { - DoPhase(this, PHASE_WASM_CONTROL_FLOW, &Compiler::fgWasmControlFlow); - } -#endif - // We can not add any new tracked variables after this point. lvaTrackedFixed = true; @@ -5016,6 +5002,11 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // Copied from rpPredictRegUse() SetFullPtrRegMapRequired(codeGen->GetInterruptible() || !codeGen->isFramePointerUsed()); +#ifdef TARGET_WASM + // Reorder blocks for wasm and figure out wasm control flow nesting + // + DoPhase(this, PHASE_WASM_CONTROL_FLOW, &Compiler::fgWasmControlFlow); +#else if (opts.OptimizationEnabled()) { // We won't introduce new blocks from here on out, @@ -5031,6 +5022,7 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // DoPhase(this, PHASE_DETERMINE_FIRST_COLD_BLOCK, &Compiler::fgDetermineFirstColdBlock); } +#endif // TARGET_WASM #if FEATURE_LOOP_ALIGN // Place loop alignment instructions diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 64d2c358ce62fb..abd9666e5114bf 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -88,6 +88,9 @@ struct JumpThreadInfo; // defined in redundantbranchopts.cpp class ProfileSynthesis; // defined in profilesynthesis.h class PerLoopInfo; // defined in inductionvariableopts.cpp class RangeCheck; // defined in rangecheck.h +#ifdef TARGET_WASM +class WasmInterval; // defined in fgwasm.h +#endif #ifdef DEBUG struct IndentStack; #endif @@ -5237,6 +5240,10 @@ class Compiler unsigned fgBBNumMax = 0; // The max bbNum that has been assigned to basic blocks +#ifdef TARGET_WASM + jitstd::vector* fgWasmIntervals = nullptr; +#endif + FlowGraphDfsTree* m_dfsTree = nullptr; // The next members are annotations on the flow graph used during the // optimization phases. They are invalidated once RBO runs and modifies the @@ -6257,9 +6264,15 @@ class Compiler PhaseStatus fgFindOperOrder(); +#ifdef TARGET_WASM FlowGraphDfsTree* fgWasmDfs(); PhaseStatus fgWasmControlFlow(); PhaseStatus fgWasmTransformSccs(); +#ifdef DEBUG + void fgDumpWasmControlFlow(); + void fgDumpWasmControlFlowDot(); +#endif // DEBUG +#endif // TARGET_WASM // method that returns if you should split here typedef bool(fgSplitPredicate)(GenTree* tree, GenTree* parent, fgWalkData* data); diff --git a/src/coreclr/jit/emitfmtswasm.h b/src/coreclr/jit/emitfmtswasm.h index e934c1cc857f8c..1413c3cdfb1718 100644 --- a/src/coreclr/jit/emitfmtswasm.h +++ b/src/coreclr/jit/emitfmtswasm.h @@ -28,6 +28,8 @@ enum ID_OPS IF_DEF(NONE, IS_NONE, NONE) IF_DEF(OPCODE, IS_NONE, NONE) // +IF_DEF(BLOCK, IS_NONE, NONE) // <0x40> +IF_DEF(LABEL, IS_NONE, NONE) // IF_DEF(ULEB128, IS_NONE, NONE) // IF_DEF(MEMARG, IS_NONE, NONE) // ( ) diff --git a/src/coreclr/jit/emitjmps.h b/src/coreclr/jit/emitjmps.h index a945af6437f056..4653701791ef3d 100644 --- a/src/coreclr/jit/emitjmps.h +++ b/src/coreclr/jit/emitjmps.h @@ -61,7 +61,8 @@ JMP_SMALL(ne , eq , bne ) // NE #elif defined(TARGET_WASM) -JMP_SMALL(jmp , jmp , br ) +JMP_SMALL(jmp , br , br ) +JMP_SMALL(jmpif , br_if , br_if ) #else #error Unsupported or unset target architecture diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 75212ec4556628..c020eded146558 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -151,6 +151,13 @@ unsigned emitter::instrDesc::idCodeSize() const { case IF_OPCODE: break; + case IF_BLOCK: + size += 1; + break; + case IF_LABEL: + assert(!idIsCnsReloc()); + size = SizeOfULEB128(static_cast(emitGetInsSC(this))); + break; case IF_ULEB128: size += idIsCnsReloc() ? PADDED_RELOC_SIZE : SizeOfULEB128(static_cast(emitGetInsSC(this))); break; @@ -171,8 +178,58 @@ void emitter::emitSetShortJump(instrDescJmp* id) size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { - NYI_WASM("emitOutputInstr"); - return 0; + BYTE* dst = *dp; + size_t sz = emitSizeOfInsDsc(id); + instruction ins = id->idIns(); + insFormat insFmt = id->idInsFmt(); + unsigned opcode = GetInsOpcode(ins); + + switch (insFmt) + { + case IF_OPCODE: + dst += emitOutputByte(dst, opcode); + break; + case IF_BLOCK: + dst += emitOutputByte(dst, opcode); + dst += emitOutputByte(dst, 0x40); + break; + case IF_ULEB128: + dst += emitOutputByte(dst, opcode); + // TODO-WASM: emit uleb128 + break; + case IF_LABEL: + // TODO-WASM: emit uleb128 + default: + NYI_WASM("emitOutputInstr"); + } + +#ifdef DEBUG + bool dspOffs = emitComp->opts.dspGCtbls; + if (emitComp->opts.disAsm || emitComp->verbose) + { + emitDispIns(id, false, dspOffs, true, emitCurCodeOffs(*dp), *dp, (dst - *dp), ig); + } +#else + if (emitComp->opts.disAsm) + { + emitDispIns(id, false, 0, true, emitCurCodeOffs(*dp), *dp, (dst - *dp), ig); + } +#endif + + *dp = dst; + return sz; +} + +/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind) +{ + const instruction emitJumpKindInstructions[] = { + INS_nop, +#define JMP_SMALL(en, rev, ins) INS_##ins, +#include "emitjmps.h" + }; + + assert((unsigned)jumpKind < ArrLen(emitJumpKindInstructions)); + return emitJumpKindInstructions[jumpKind]; } //-------------------------------------------------------------------- @@ -240,8 +297,10 @@ void emitter::emitDispIns( switch (fmt) { case IF_OPCODE: + case IF_BLOCK: break; + case IF_LABEL: case IF_ULEB128: { target_size_t imm = emitGetInsSC(id); @@ -305,8 +364,11 @@ void emitter::emitDispInsHex(instrDesc* id, BYTE* code, size_t sz) #if defined(DEBUG) || defined(LATE_DISASM) emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(instrDesc* id) { - NYI_WASM("getInsSveExecutionCharacteristics"); - return {}; + // TODO-WASM: for real... + insExecutionCharacteristics result; + result.insThroughput = PERFSCORE_THROUGHPUT_1C; + result.insLatency = PERFSCORE_LATENCY_1C; + return result; } #endif // defined(DEBUG) || defined(LATE_DISASM) diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 00576f48247fe2..4a99e3b4370c78 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -460,7 +460,10 @@ class Scc auto visitEdge = [](BasicBlock* block, BasicBlock* succ) {}; +#ifdef DEBUG // Dump subgraph as dot + // + if (m_comp->verbose) { JITDUMP("digraph scc_%u_nested_subgraph%u {\n", m_num, nestedCount); BitVecOps::Iter iterator(m_traits, nestedBlocks); @@ -481,6 +484,7 @@ class Scc JITDUMP("}\n"); } +#endif unsigned numBlocks = m_fgWasm->WasmRunSubgraphDfs& sccs, BasicBlock AssignBlockToScc(block, block, subset, sccs, map); } - if (sccs.Height() > 0) + for (int i = 0; i < sccs.Height(); i++) { - for (int i = 0; i < sccs.Height(); i++) - { - Scc* const scc = sccs.Bottom(i); - scc->Finalize(); - } + Scc* const scc = sccs.Bottom(i); + scc->Finalize(); } } @@ -965,121 +966,6 @@ PhaseStatus Compiler::fgWasmTransformSccs() return transformed ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } -//------------------------------------------------------------------------ -// WasmInterval -// -// Represents a Wasm BLOCK/END or LOOP/END -// -class WasmInterval -{ -private: - - // m_chain refers to the conflict set member with the lowest m_start. - // (for "trivial" singleton conflict sets m_chain will be `this`) - WasmInterval* m_chain; - - // True index of start - unsigned m_start; - - // True index of end; interval ends just before this block - unsigned m_end; - - // Largest end index of any chained interval - unsigned m_chainEnd; - - // true if this is a loop interval (extents cannot change) - bool m_isLoop; - -public: - - WasmInterval(unsigned start, unsigned end, bool isLoop) - : m_chain(nullptr) - , m_start(start) - , m_end(end) - , m_chainEnd(end) - , m_isLoop(isLoop) - { - m_chain = this; - } - - unsigned Start() const - { - return m_start; - } - - unsigned End() const - { - return m_end; - } - - unsigned ChainEnd() const - { - return m_chainEnd; - } - - // Call while resolving intervals when building chains. - WasmInterval* FetchAndUpdateChain() - { - if (m_chain == this) - { - return this; - } - - WasmInterval* chain = m_chain->FetchAndUpdateChain(); - m_chain = chain; - return chain; - } - - // Call after intervals are resolved and chains are fixed. - WasmInterval* Chain() const - { - assert((m_chain == this) || (m_chain == m_chain->Chain())); - return m_chain; - } - - bool IsLoop() const - { - return m_isLoop; - } - - void SetChain(WasmInterval* c) - { - m_chain = c; - c->m_chainEnd = max(c->m_chainEnd, m_chainEnd); - } - - static WasmInterval* NewBlock(Compiler* comp, BasicBlock* start, BasicBlock* end) - { - WasmInterval* result = - new (comp, CMK_WasmCfgLowering) WasmInterval(start->bbPreorderNum, end->bbPreorderNum, /* isLoop */ false); - return result; - } - - static WasmInterval* NewLoop(Compiler* comp, BasicBlock* start, BasicBlock* end) - { - WasmInterval* result = - new (comp, CMK_WasmCfgLowering) WasmInterval(start->bbPreorderNum, end->bbPreorderNum, /* isLoop */ true); - return result; - } - -#ifdef DEBUG - void Dump(bool chainExtent = false) - { - printf("[%03u,%03u]%s", m_start, chainExtent ? m_chainEnd : m_end, m_isLoop && !chainExtent ? " L" : ""); - - if (m_chain != this) - { - printf(" --> "); - m_chain->Dump(true); - } - else - { - printf("\n"); - } - } -#endif -}; - //------------------------------------------------------------------------ // fgWasmControlFlow: determine how to emit control flow instructions for wasm // @@ -1139,15 +1025,10 @@ class WasmInterval // Still TODO // * Blocks only reachable via EH // * proper handling of BR_TABLE defaults -// * branch inversion -// * actual block reordering -// * instruction emission // * tail calls (RETURN_CALL) -// * UNREACHED in more places (eg noreturn calls) // * Rethink need for BB0 (have m_end refer to end of last block in range, not start of first block after) -// * We do not branch with operands on the wasm stack, so we need to add suitable (void?) types to branches // * During LaRPO formation, remember the position of the last block in the loop -// * Settle on ordering of WasmSccTransform / Lower / LSRA / WasmControlFlow (LSRA can introduce blocks) +// * Compatibility of LaRPO with try region layout constraints (if any) // PhaseStatus Compiler::fgWasmControlFlow() { @@ -1209,7 +1090,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // Allocate interval and scratch vectors. We'll use the scratch vector to keep track of // block intervals that end at a certain point. // - jitstd::vector intervals(getAllocator(CMK_WasmCfgLowering)); + fgWasmIntervals = new (this, CMK_WasmCfgLowering) jitstd::vector(getAllocator(CMK_WasmCfgLowering)); jitstd::vector scratch(numBlocks, nullptr, getAllocator(CMK_WasmCfgLowering)); for (unsigned int cursor = 0; cursor < numBlocks; cursor++) @@ -1237,7 +1118,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // We assume here that a block is only the header of one loop. // - intervals.push_back(loopInterval); + fgWasmIntervals->push_back(loopInterval); } // Now see where block branches to... @@ -1294,7 +1175,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // Non-contiguous, non-subsumed forward branch // WasmInterval* const branch = WasmInterval::NewBlock(this, block, initialLayout[succNum]); - intervals.push_back(branch); + fgWasmIntervals->push_back(branch); // Remember an interval end here // @@ -1308,7 +1189,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // Display the raw intervals... // JITDUMP("\n-------------- Initial set of wasm intervals\n"); - for (WasmInterval* interval : intervals) + for (WasmInterval* interval : *fgWasmIntervals) { JITDUMPEXEC(interval->Dump()); } @@ -1326,8 +1207,8 @@ PhaseStatus Compiler::fgWasmControlFlow() // Since this is only looking at prior intervals it could be // merged with (2) above. // - auto resolve = [&intervals](WasmInterval* const current) { - for (WasmInterval* prior : intervals) + auto resolve = [this](WasmInterval* const current) { + for (WasmInterval* prior : *fgWasmIntervals) { // We only need to consider intervals that start at the same point or earlier. // @@ -1381,7 +1262,7 @@ PhaseStatus Compiler::fgWasmControlFlow() } }; - for (WasmInterval* interval : intervals) + for (WasmInterval* interval : *fgWasmIntervals) { resolve(interval); } @@ -1390,7 +1271,7 @@ PhaseStatus Compiler::fgWasmControlFlow() if (verbose) { JITDUMP("\n-------------- After finding conflicts\n"); - for (WasmInterval* iv : intervals) + for (WasmInterval* iv : *fgWasmIntervals) { JITDUMPEXEC(iv->Dump()); } @@ -1440,13 +1321,13 @@ PhaseStatus Compiler::fgWasmControlFlow() return false; }; - jitstd::sort(intervals.begin(), intervals.end(), comesBefore); + jitstd::sort(fgWasmIntervals->begin(), fgWasmIntervals->end(), comesBefore); #ifdef DEBUG if (verbose) { JITDUMP("\n-------------- After sorting\n"); - for (WasmInterval* interval : intervals) + for (WasmInterval* interval : *fgWasmIntervals) { JITDUMPEXEC(interval->Dump()); } @@ -1454,18 +1335,108 @@ PhaseStatus Compiler::fgWasmControlFlow() } #endif - // (5) Create the wasm control flow operations + // (5) Reorder the blocks // - // Show (roughly) what the WASM control flow looks like + // Todo: verify this ordering is compatible with our EH story. // - ArrayStack activeIntervals(getAllocator(CMK_WasmCfgLowering)); - unsigned wasmCursor = 0; + // If we use an explicit EH state marker, likely we do not need + // to keep the try region contiguous. + // + // If we need contiguous trys we can likely create a try-aware + // loop-aware RPO, since both regions will be single-entry. + // The main trick is to figure out both the extent of the try region + // and if there is a block that is both try entry and loop header, + // which is nested in which. + // + JITDUMP("Reordering block list\n"); + BasicBlock* lastBlock = nullptr; for (unsigned int cursor = 0; cursor < numBlocks; cursor++) { BasicBlock* const block = initialLayout[cursor]; + if (cursor == 0) + { + assert(block == fgFirstBB); + lastBlock = block; + } + else + { + fgUnlinkBlock(block); + fgInsertBBafter(lastBlock, block); + lastBlock = block; + } + + // If BBJ_COND true target is branch to next, + // reverse the condition + // + if (block->KindIs(BBJ_COND)) + { + const unsigned trueNum = block->GetTrueTarget()->bbPreorderNum; + const unsigned falseNum = block->GetFalseTarget()->bbPreorderNum; + + // We don't expect degenerate BBJ_COND + // + assert(trueNum != falseNum); + + // If the true target is the next block, reverse the branch + // + const bool reverseCondition = trueNum == (cursor + 1); + + if (reverseCondition) + { + JITDUMP("Reversing condition in " FMT_BB " to allow fall through to " FMT_BB "\n", block->bbNum, + block->GetTrueTarget()->bbNum); + + GenTree* const test = block->GetLastLIRNode(); + assert(test->OperIs(GT_JTRUE)); + { + GenTree* const cond = gtReverseCond(test->AsOp()->gtOp1); + // Ensure `gtReverseCond` did not create a new node. + assert(cond == test->AsOp()->gtOp1); + test->AsOp()->gtOp1 = cond; + } + + // Rewire the flow + // + std::swap(block->TrueEdgeRef(), block->FalseEdgeRef()); + } + else + { + JITDUMP("NOT Reversing condition in " FMT_BB "\n", block->bbNum); + } + } + } + + JITDUMPEXEC(fgDumpWasmControlFlow()); + JITDUMPEXEC(fgDumpWasmControlFlowDot()); + + return PhaseStatus::MODIFIED_EVERYTHING; +} + +#ifdef DEBUG + +//------------------------------------------------------------------------ +// fgDumpWasmControlFlow: show (roughly) what the WASM control flow looks like +// +// Notes: +// Assumes blocks have been reordered +// +void Compiler::fgDumpWasmControlFlow() +{ + if (!verbose) + { + return; + } + + ArrayStack activeIntervals(getAllocator(CMK_WasmCfgLowering)); + unsigned wasmCursor = 0; + + for (BasicBlock* const block : Blocks()) + { + unsigned const cursor = block->bbPreorderNum; JITDUMP("Before " FMT_BB " at %u stack is:", block->bbNum, cursor); + if (activeIntervals.Empty()) { JITDUMP("empty"); @@ -1489,9 +1460,9 @@ PhaseStatus Compiler::fgWasmControlFlow() // Open intervals that start here or earlier // - if (wasmCursor < intervals.size()) + if (wasmCursor < fgWasmIntervals->size()) { - WasmInterval* interval = intervals[wasmCursor]; + WasmInterval* interval = fgWasmIntervals->at(wasmCursor); WasmInterval* chain = interval->Chain(); while (chain->Start() <= cursor) @@ -1501,12 +1472,12 @@ PhaseStatus Compiler::fgWasmControlFlow() wasmCursor++; activeIntervals.Push(interval); - if (wasmCursor >= intervals.size()) + if (wasmCursor >= fgWasmIntervals->size()) { break; } - interval = intervals[wasmCursor]; + interval = fgWasmIntervals->at(wasmCursor); chain = interval->Chain(); } } @@ -1587,7 +1558,7 @@ PhaseStatus Compiler::fgWasmControlFlow() { JITDUMP("FALLTHROUGH\n"); } - else if (succNum < numBlocks) + else { bool const isBackedge = succNum <= cursor; unsigned blockNum = 0; @@ -1614,18 +1585,16 @@ PhaseStatus Compiler::fgWasmControlFlow() // // We could anticipate this above and induce a block like we do for switches. // - // Or we can just invert the branch condition here; I think this should be viable. + // Or we can just reverse the branch condition here; I think this should be viable. // (eg invoke the core part of optOptimizePostLayout). // - const bool invertCondition = trueNum == (cursor + 1); + const bool reverseCondition = trueNum == (cursor + 1); - if (invertCondition) + if (reverseCondition) { - // TODO: induce a block and avoid this case, or actually modify the IR - // JITDUMP("FALLTHROUGH-inv\n"); } - else if (trueNum < numBlocks) + else { bool const isBackedge = trueNum <= cursor; unsigned blockNum = 0; @@ -1637,12 +1606,12 @@ PhaseStatus Compiler::fgWasmControlFlow() { JITDUMP("FALLTHROUGH\n"); } - else if (falseNum < numBlocks) + else { bool const isBackedge = falseNum <= cursor; unsigned blockNum = 0; unsigned depth = findDepth(falseNum, isBackedge, blockNum); - JITDUMP("BR%s %d (%u)%s\n", invertCondition ? "_IF-inv" : "", depth, blockNum, + JITDUMP("BR%s %d (%u)%s\n", reverseCondition ? "_IF-inv" : "", depth, blockNum, isBackedge ? "be" : ""); } @@ -1675,13 +1644,10 @@ PhaseStatus Compiler::fgWasmControlFlow() BasicBlock* const caseTarget = desc->GetCase(caseNum)->getDestinationBlock(); unsigned const caseTargetNum = caseTarget->bbPreorderNum; - if (caseTargetNum < numBlocks) - { - bool const isBackedge = caseTargetNum <= cursor; - unsigned blockNum = 0; - unsigned depth = findDepth(caseTargetNum, isBackedge, blockNum); - JITDUMP("%s %d (%u)%s", caseNum > 0 ? "," : "", depth, blockNum, isBackedge ? "be" : ""); - } + bool const isBackedge = caseTargetNum <= cursor; + unsigned blockNum = 0; + unsigned depth = findDepth(caseTargetNum, isBackedge, blockNum); + JITDUMP("%s %d (%u)%s", caseNum > 0 ? "," : "", depth, blockNum, isBackedge ? "be" : ""); } JITDUMP("\n"); @@ -1705,100 +1671,101 @@ PhaseStatus Compiler::fgWasmControlFlow() WasmInterval* const i = activeIntervals.Pop(); JITDUMP("END (%u)%s\n", i->End(), i->IsLoop() ? " LOOP" : ""); } +} -#ifdef DEBUG +//------------------------------------------------------------------------ +// fgDumpWasmControlFlowDot: show (roughly) what the WASM control flow looks like +// using dot markup +// +void Compiler::fgDumpWasmControlFlowDot() +{ + if (!verbose) + { + return; + } - if (verbose) + ArrayStack activeIntervals(getAllocator(CMK_WasmCfgLowering)); + unsigned wasmCursor = 0; + JITDUMP("\ndigraph WASM {\n"); + + for (BasicBlock* const block : Blocks()) { - // Ditto but in dot markup - // - activeIntervals.Reset(); - wasmCursor = 0; - JITDUMP("\ndigraph WASM {\n"); + unsigned const cursor = block->bbPreorderNum; - for (unsigned int cursor = 0; cursor < numBlocks; cursor++) + // Close intervals that end here (at most two, block and/or loop) + // + while (!activeIntervals.Empty() && (activeIntervals.Top()->End() == cursor)) { - BasicBlock* const block = initialLayout[cursor]; + JITDUMP(" }\n"); + activeIntervals.Pop(); + } - // Close intervals that end here (at most two, block and/or loop) - // - while (!activeIntervals.Empty() && (activeIntervals.Top()->End() == cursor)) - { - JITDUMP(" }\n"); - activeIntervals.Pop(); - } + // Open intervals that start here + // + if (wasmCursor < fgWasmIntervals->size()) + { + WasmInterval* interval = fgWasmIntervals->at(wasmCursor); + WasmInterval* chain = interval->Chain(); - // Open intervals that start here - // - if (wasmCursor < intervals.size()) + while (chain->Start() <= cursor) { - WasmInterval* interval = intervals[wasmCursor]; - WasmInterval* chain = interval->Chain(); + JITDUMP(" subgraph cluster_%u_%u%s {\n", chain->Start(), interval->End(), + interval->IsLoop() ? "_loop" : ""); - while (chain->Start() <= cursor) + if (interval->IsLoop()) { - JITDUMP(" subgraph cluster_%u_%u%s {\n", chain->Start(), interval->End(), - interval->IsLoop() ? "_loop" : ""); - - if (interval->IsLoop()) - { - JITDUMP(" color=red;\n"); - } - else - { - JITDUMP(" color=black;\n"); - } - - wasmCursor++; - activeIntervals.Push(interval); + JITDUMP(" color=red;\n"); + } + else + { + JITDUMP(" color=black;\n"); + } - if (wasmCursor >= intervals.size()) - { - break; - } + wasmCursor++; + activeIntervals.Push(interval); - interval = intervals[wasmCursor]; - chain = interval->Chain(); + if (wasmCursor >= fgWasmIntervals->size()) + { + break; } - } - JITDUMP(" " FMT_BB ";\n", block->bbNum); + interval = fgWasmIntervals->at(wasmCursor); + chain = interval->Chain(); + } } - // Close remaining intervals - // - while (!activeIntervals.Empty()) - { - activeIntervals.Pop(); - JITDUMP(" }\n"); - } + JITDUMP(" " FMT_BB ";\n", block->bbNum); + } - // Now list all the branches - // - for (unsigned int cursor = 0; cursor < numBlocks; cursor++) - { - BasicBlock* const block = initialLayout[cursor]; + // Close remaining intervals + // + while (!activeIntervals.Empty()) + { + activeIntervals.Pop(); + JITDUMP(" }\n"); + } - if (block->KindIs(BBJ_CALLFINALLY)) + // Now list all the branches + // + for (BasicBlock* const block : Blocks()) + { + if (block->KindIs(BBJ_CALLFINALLY)) + { + if (block->isBBCallFinallyPair()) { - if (block->isBBCallFinallyPair()) - { - JITDUMP(" " FMT_BB " -> " FMT_BB " [style=dotted];\n", block->bbNum, block->Next()->bbNum); - } + JITDUMP(" " FMT_BB " -> " FMT_BB " [style=dotted];\n", block->bbNum, block->Next()->bbNum); } - else + } + else + { + for (BasicBlock* const succ : block->Succs()) { - for (BasicBlock* const succ : block->Succs()) - { - JITDUMP(" " FMT_BB " -> " FMT_BB ";\n", block->bbNum, succ->bbNum); - } + JITDUMP(" " FMT_BB " -> " FMT_BB ";\n", block->bbNum, succ->bbNum); } } - - JITDUMP("}\n"); } -#endif // DEBUG - - return PhaseStatus::MODIFIED_NOTHING; + JITDUMP("}\n"); } + +#endif // DEBUG diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index cb7d745ec4dcfc..f9cbe5099846bf 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -107,6 +107,122 @@ class WasmSuccessorEnumerator } }; +//------------------------------------------------------------------------ +// WasmInterval +// +// Represents a Wasm BLOCK/END or LOOP/END span in the linearized +// basic block list. +// +class WasmInterval +{ +private: + + // m_chain refers to the conflict set member with the lowest m_start. + // (for "trivial" singleton conflict sets m_chain will be `this`) + WasmInterval* m_chain; + + // True index of start + unsigned m_start; + + // True index of end; interval ends just before this block + unsigned m_end; + + // Largest end index of any chained interval + unsigned m_chainEnd; + + // true if this is a loop interval (extents cannot change) + bool m_isLoop; + +public: + + WasmInterval(unsigned start, unsigned end, bool isLoop) + : m_chain(nullptr) + , m_start(start) + , m_end(end) + , m_chainEnd(end) + , m_isLoop(isLoop) + { + m_chain = this; + } + + unsigned Start() const + { + return m_start; + } + + unsigned End() const + { + return m_end; + } + + unsigned ChainEnd() const + { + return m_chainEnd; + } + + // Call while resolving intervals when building chains. + WasmInterval* FetchAndUpdateChain() + { + if (m_chain == this) + { + return this; + } + + WasmInterval* chain = m_chain->FetchAndUpdateChain(); + m_chain = chain; + return chain; + } + + // Call after intervals are resolved and chains are fixed. + WasmInterval* Chain() const + { + assert((m_chain == this) || (m_chain == m_chain->Chain())); + return m_chain; + } + + bool IsLoop() const + { + return m_isLoop; + } + + void SetChain(WasmInterval* c) + { + m_chain = c; + c->m_chainEnd = max(c->m_chainEnd, m_chainEnd); + } + + static WasmInterval* NewBlock(Compiler* comp, BasicBlock* start, BasicBlock* end) + { + WasmInterval* result = + new (comp, CMK_WasmCfgLowering) WasmInterval(start->bbPreorderNum, end->bbPreorderNum, /* isLoop */ false); + return result; + } + + static WasmInterval* NewLoop(Compiler* comp, BasicBlock* start, BasicBlock* end) + { + WasmInterval* result = + new (comp, CMK_WasmCfgLowering) WasmInterval(start->bbPreorderNum, end->bbPreorderNum, /* isLoop */ true); + return result; + } + +#ifdef DEBUG + void Dump(bool chainExtent = false) + { + printf("[%03u,%03u]%s", m_start, chainExtent ? m_chainEnd : m_end, m_isLoop && !chainExtent ? " L" : ""); + + if (m_chain != this) + { + printf(" --> "); + m_chain->Dump(true); + } + else + { + printf("\n"); + } + } +#endif +}; + //------------------------------------------------------------------------------ // FgWasm: Wasm-specific flow graph methods // diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index e292ffb27b0e73..b892290f1ad338 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -23,10 +23,23 @@ // TODO-WASM: fill out with more instructions (and everything else needed). // // clang-format off + +// control flow +// INST(invalid, "INVALID", 0, IF_NONE, BAD_CODE) INST(unreachable, "unreachable", 0, IF_OPCODE, 0x00) +INST(label, "label", 0, IF_LABEL, 0x00) INST(nop, "nop", 0, IF_OPCODE, 0x01) +INST(block, "block", 0, IF_BLOCK, 0x02) +INST(loop, "loop", 0, IF_BLOCK, 0x03) +INST(if, "if", 0, IF_BLOCK, 0x04) +INST(else, "else", 0, IF_OPCODE, 0x05) +INST(end, "end", 0, IF_OPCODE, 0x0B) INST(br, "br", 0, IF_ULEB128, 0x0C) +INST(br_if, "br_if", 0, IF_ULEB128, 0x0D) +INST(br_table, "br_table", 0, IF_ULEB128, 0x0E) +INST(return, "return", 0, IF_OPCODE, 0x0F) + INST(local_get, "local.get", 0, IF_ULEB128, 0x20) INST(i32_load, "i32.load", 0, IF_MEMARG, 0x28) INST(i64_load, "i64.load", 0, IF_MEMARG, 0x29) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index eb675224874412..3a9188a3e1eb0f 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -854,9 +854,6 @@ CONFIG_INTEGER(JitDispIns, "JitDispIns", 0) // Allow to enregister locals with struct type. RELEASE_CONFIG_INTEGER(JitEnregStructLocals, "JitEnregStructLocals", 1) -// Simulate generation of Wasm control flow, even if not targeting wasm -CONFIG_INTEGER(JitWasmControlFlow, "JitWasmControlFlow", 0) - #undef CONFIG_INTEGER #undef CONFIG_STRING #undef CONFIG_METHODSET diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index dd63a8fb17eb54..94eee88e21fda2 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -110,10 +110,13 @@ GenTree* Lowering::LowerMul(GenTreeOp* mul) // Return Value: // The next node to lower (usually nullptr). // +// Notes: +// For wasm we handle all this in codegen +// GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) { - NYI_WASM("LowerJTrue"); - return jtrue->gtNext; + // TODO-WASM: recognize eqz cases + return nullptr; } //------------------------------------------------------------------------