From 5f03131eca524e07124d0b16950e6b2d017594ed Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 25 Nov 2025 14:26:49 -0800 Subject: [PATCH 01/13] [Wasm RyuJit] Control flow codegen Emit plausible Wasm control flow for blocks during `genCodeForBBList`. Also add a bit of support for relop codegen, so blocks ending in branches have something to use. Contributes to #121178. --- src/coreclr/jit/CMakeLists.txt | 2 +- src/coreclr/jit/codegen.h | 3 + src/coreclr/jit/codegenlinear.cpp | 249 +++++++++++++++++- src/coreclr/jit/codegenwasm.cpp | 69 ++++- src/coreclr/jit/compiler.cpp | 34 +-- src/coreclr/jit/compiler.h | 17 ++ src/coreclr/jit/emitjmps.h | 4 +- src/coreclr/jit/emitwasm.cpp | 59 ++++- src/coreclr/jit/emitwasm.h | 2 + src/coreclr/jit/fgwasm.cpp | 422 ++++++++++++++---------------- src/coreclr/jit/fgwasm.h | 116 ++++++++ src/coreclr/jit/instrswasm.h | 62 +++++ src/coreclr/jit/jitconfigvalues.h | 3 - src/coreclr/jit/lowerwasm.cpp | 18 +- 14 files changed, 798 insertions(+), 262 deletions(-) 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 727b4688980020..293c630b4bc49f 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1343,6 +1343,9 @@ 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, unsigned depth); + void inst_LABEL(unsigned depth); #else void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); #endif diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 6d0890ef352883..836f63fe136981 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) +#include "fgwasm.h" +#endif + //------------------------------------------------------------------------ // genInitializeRegisterState: Initialize the register state contained in 'regSet'. // @@ -159,6 +163,55 @@ void CodeGen::genCodeForBBlist() /* Initialize structures used in the block list iteration */ genInitialize(); +#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. + // + ArrayStack wasmControlFlowStack(compiler->getAllocator(CMK_WasmCfgLowering)); + unsigned wasmCursor = 0; + + // Compute the depth of the block ending at targetNum + // or (if isBackedge) the loop starting at targetNum + // in the wasm control flow stack + // + auto findDepth = [&wasmControlFlowStack](unsigned targetNum, bool isBackedge, unsigned& match) { + int const h = wasmControlFlowStack.Height(); + + for (int i = 0; i < h; i++) + { + WasmInterval* const ii = wasmControlFlowStack.Top(i); + match = 0; + + if (isBackedge) + { + // loops bind to start + match = ii->Start(); + } + else + { + // blocks bind to end + match = ii->End(); + } + + if ((match == targetNum) && (isBackedge == ii->IsLoop())) + { + return i; + } + } + + JITDUMP("Could not find %u%s in active control stack\n", targetNum, isBackedge ? " (backedge)" : ""); + assert(!"Can't find target in control stack"); + + return ~0; + }; + +#endif // defined(TARGET_WASM) + /*------------------------------------------------------------------------- * * Walk the basic blocks and generate code for each one @@ -179,6 +232,12 @@ void CodeGen::genCodeForBBlist() } #endif // DEBUG +#if defined(TARGET_WASM) + // Track where this block is in the linear order + // + unsigned const cursor = block->bbPreorderNum; +#endif + assert(LIR::AsRange(block).CheckLIR(compiler)); // Figure out which registers hold variables on entry to this block @@ -354,6 +413,49 @@ void CodeGen::genCodeForBBlist() GetEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie); } +#if defined(TARGET_WASM) + + // Pop control flow intervals that end here (at most two, block and/or loop) + // + while (!wasmControlFlowStack.Empty() && (wasmControlFlowStack.Top()->End() == cursor)) + { + instGen(INS_end); + wasmControlFlowStack.Pop(); + } + + // Push control flow for intervals that start here or earlier + // + 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(); + } + } + +#endif // defined(TARGET_WASM) + // Both stacks are always empty on entry to a basic block. assert(genStackLevel == 0); genAdjustStackLevel(block); @@ -705,9 +807,146 @@ void CodeGen::genCodeForBBlist() }; #endif // FEATURE_LOOP_ALIGN + bool removedJmp = false; + +#if defined(TARGET_WASM) + // Generate Wasm control flow instructions for block ends + // + switch (block->GetKind()) + { + case BBJ_RETURN: + instGen(INS_return); + break; + + case BBJ_THROW: + instGen(INS_unreachable); + break; + + case BBJ_CALLFINALLY: + genCallFinally(block); + if (!block->isBBCallFinallyPair()) + { + instGen(INS_unreachable); + } + break; + + case BBJ_EHCATCHRET: + assert(compiler->UsesFunclets()); + genEHCatchRet(block); + FALLTHROUGH; + + case BBJ_EHFINALLYRET: + case BBJ_EHFAULTRET: + case BBJ_EHFILTERRET: + if (compiler->UsesFunclets()) + { + genReserveFuncletEpilog(block); + } + instGen(INS_return); + break; + + case BBJ_SWITCH: + { + BBswtDesc* const desc = block->GetSwitchTargets(); + unsigned const caseCount = desc->GetCaseCount(); + + assert(!desc->HasDefaultCase()); + + if (caseCount == 0) + { + break; + } + + inst_JMP(EJ_br_table, caseCount); + + for (unsigned caseNum = 0; caseNum < caseCount; caseNum++) + { + BasicBlock* const caseTarget = desc->GetCase(caseNum)->getDestinationBlock(); + unsigned const caseTargetNum = caseTarget->bbPreorderNum; + + bool const isBackedge = caseTargetNum <= cursor; + unsigned blockNum = 0; + unsigned depth = findDepth(caseTargetNum, isBackedge, blockNum); + + inst_LABEL(depth); + } + + JITDUMP("\n"); + break; + } + + case BBJ_CALLFINALLYRET: + 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()); + } +#endif // DEBUG + + unsigned const succNum = block->GetTarget()->bbPreorderNum; + + // If this block jumps to the next one, we might be able to skip emitting the jump + if (block->CanRemoveJumpToNext(compiler)) + { + assert(succNum == (cursor + 1)); + break; + } + + bool const isBackedge = succNum <= cursor; + unsigned blockNum = 0; + unsigned const depth = findDepth(succNum, isBackedge, blockNum); + inst_JMP(EJ_br, depth); + } + + case BBJ_COND: + { + const unsigned trueNum = block->GetTrueTarget()->bbPreorderNum; + const unsigned falseNum = block->GetFalseTarget()->bbPreorderNum; + + // We don't expect degenerate BBJ_COND + // + assert(trueNum != falseNum); + + // We don't expect the true target to be the next block. + // + const bool reverseCondition = trueNum == (cursor + 1); + assert(!reverseCondition); + + // br_if for true target + // + bool const isTrueBackedge = trueNum <= cursor; + unsigned blockNum = 0; + unsigned depth = findDepth(trueNum, isTrueBackedge, blockNum); + inst_JMP(EJ_br_if, depth); + + // br for false target, if not fallthrough + // + const bool fallThrough = falseNum == (cursor + 1); + if (fallThrough) + { + break; + } + + bool const isFalseBackedge = falseNum <= cursor; + blockNum = 0; + depth = findDepth(falseNum, isFalseBackedge, blockNum); + inst_JMP(EJ_br, depth); + + break; + } + + default: + noway_assert(!"Unexpected bbKind"); + break; + } +#else + /* Do we need to generate a jump or return? */ - bool removedJmp = false; switch (block->GetKind()) { case BBJ_RETURN: @@ -830,6 +1069,8 @@ void CodeGen::genCodeForBBlist() break; } +#endif // defined(TARGET_WASM) + #if FEATURE_LOOP_ALIGN if (block->hasAlign()) { @@ -2620,6 +2861,10 @@ void CodeGen::genCodeForSetcc(GenTreeCC* setcc) void CodeGen::genEmitterUnitTests() { + +#if defined(TARGET_WASM) + return; +#else if (!JitConfig.JitEmitUnitTests().contains(compiler->info.compMethodHnd, compiler->info.compClassHnd, &compiler->info.compMethodInfo->args)) { @@ -2689,6 +2934,8 @@ void CodeGen::genEmitterUnitTests() instGen(INS_nop); JITDUMP("*************** End of genEmitterUnitTests()\n"); + +#endif // defined(TARGET_WASM) } #endif // defined(DEBUG) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index d2a7460115a8da..1b831fca3f1644 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() { @@ -16,7 +17,46 @@ void CodeGen::genMarkLabelsForCodegen() void CodeGen::genFnProlog() { - NYI_WASM("Uncomment CodeGen::genFnProlog and proceed from there"); + ScopedSetVariable _setGeneratingProlog(&compiler->compGeneratingProlog, true); + + compiler->funSetCurrentFunc(0); + +#ifdef DEBUG + if (verbose) + { + printf("*************** In genFnProlog()\n"); + } +#endif + +#ifdef DEBUG + genInterruptibleUsed = true; +#endif + + assert(compiler->lvaDoneFrameLayout == Compiler::FINAL_FRAME_LAYOUT); + + /* Ready to start on the prolog proper */ + + GetEmitter()->emitBegProlog(); + // compiler->unwindBegProlog(); + + // Do this so we can put the prolog instruction group ahead of + // other instruction groups + genIPmappingAddToFront(IPmappingDscKind::Prolog, DebugInfo(), true); + +#ifdef DEBUG + if (compiler->opts.dspCode) + { + printf("\n__prolog:\n"); + } +#endif + + if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0)) + { + // Create new scopes for the method-parameters for the prolog-block. + psiBegProlog(); + } + + // Todo: prolog zeroing, shadow stack maintenance } void CodeGen::genFnEpilog(BasicBlock* block) @@ -80,6 +120,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) switch (treeNode->OperGet()) { case GT_ADD: + case GT_GT: genCodeForBinary(treeNode->AsOp()); break; @@ -87,6 +128,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForLclVar(treeNode->AsLclVar()); break; + case GT_JTRUE: + // Do nothing; handled by end of block processing + break; + case GT_RETURN: genReturn(treeNode); break; @@ -125,6 +170,15 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) } ins = INS_i32_add; break; + + case GT_GT: + if (!treeNode->TypeIs(TYP_INT)) + { + NYI_WASM("genCodeForBinary:non-INT GT_GT"); + } + ins = treeNode->IsUnsigned() ? INS_i32_gt_u : INS_i32_gt_s; + break; + default: ins = INS_none; NYI_WASM("genCodeForBinary"); @@ -202,9 +256,18 @@ void CodeGen::genSpillVar(GenTree* tree) //------------------------------------------------------------------------ // inst_JMP: Generate a jump instruction. // -void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) +void CodeGen::inst_JMP(emitJumpKind jmp, unsigned depth) +{ + instruction instr = emitter::emitJumpKindToIns(jmp); + GetEmitter()->emitIns_I(instr, EA_4BYTE, depth); +} + +//------------------------------------------------------------------------ +// inst_LABEL: Emit a label index +// +void CodeGen::inst_LABEL(unsigned depth) { - NYI_WASM("inst_JMP"); + NYI_WASM("inst_LABEL"); } void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, unsigned epilogSize DEBUGARG(void* code)) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index e74188ffc43fef..1dd5851ba2db90 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 e1f1e75e73f80d..cc1a3acc7ff555 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 @@ -5232,6 +5235,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 @@ -6249,10 +6256,20 @@ class Compiler PhaseStatus fgFindOperOrder(); +#ifdef TARGET_WASM + FlowGraphDfsTree* fgWasmDfs(); PhaseStatus fgWasmControlFlow(); PhaseStatus fgWasmTransformSccs(); +#ifdef DEBUG + + void fgDumpWasmControlFlow(); + void fgDumpWasmControlFlowDot(); + +#endif +#endif + // method that returns if you should split here typedef bool(fgSplitPredicate)(GenTree* tree, GenTree* parent, fgWalkData* data); diff --git a/src/coreclr/jit/emitjmps.h b/src/coreclr/jit/emitjmps.h index a945af6437f056..7acfc6e5d3a5c9 100644 --- a/src/coreclr/jit/emitjmps.h +++ b/src/coreclr/jit/emitjmps.h @@ -61,7 +61,9 @@ JMP_SMALL(ne , eq , bne ) // NE #elif defined(TARGET_WASM) -JMP_SMALL(jmp , jmp , br ) +JMP_SMALL(br , br , br ) +JMP_SMALL(br_if , br_if , br_if ) +JMP_SMALL(br_table , br_table , br_table ) #else #error Unsupported or unset target architecture diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 75212ec4556628..6a51f88394b1b2 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -169,10 +169,58 @@ void emitter::emitSetShortJump(instrDescJmp* id) NYI_WASM("emitSetShortJump"); } +inline emitter::code_t insOpcode(instruction ins) +{ + // clang-format off + const static emitter::code_t insCodes[] = + { + #define INST(id, nm, v, fmt, op) op, + #include "instrs.h" + }; + // clang-format on + + assert((unsigned)ins < ArrLen(insCodes)); + assert((insCodes[ins] != BAD_CODE)); + + return insCodes[ins]; +} + size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { - NYI_WASM("emitOutputInstr"); - return 0; + + BYTE* dst = *dp; + size_t sz = sizeof(instrDesc); + instruction ins = id->idIns(); + insFormat insFmt = id->idInsFmt(); + code_t code = insOpcode(ins); + + switch (insFmt) + { + case IF_OPCODE: + dst += emitOutputByte(dst, code); + break; + case IF_ULEB128: + dst += emitOutputByte(dst, code); + // todo... uleb128 + break; + default: + NYI_WASM("emitOutputInstr"); + } + + *dp = dst; + return sz; +} + +const instruction emitJumpKindInstructions[] = { + INS_nop, +#define JMP_SMALL(en, rev, ins) INS_##ins, +#include "emitjmps.h" +}; + +/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind) +{ + assert((unsigned)jumpKind < ArrLen(emitJumpKindInstructions)); + return emitJumpKindInstructions[jumpKind]; } //-------------------------------------------------------------------- @@ -305,8 +353,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: 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/emitwasm.h b/src/coreclr/jit/emitwasm.h index 890ab53c04197a..0427bcbf12404e 100644 --- a/src/coreclr/jit/emitwasm.h +++ b/src/coreclr/jit/emitwasm.h @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +typedef uint32_t code_t; + /************************************************************************/ /* Debug-only routines to display instructions */ /************************************************************************/ diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 00576f48247fe2..6969a6bd992e74 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->WasmRunSubgraphDfsFetchAndUpdateChain(); - 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 +1028,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 +1093,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 +1121,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 +1178,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 +1192,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 +1210,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 +1265,7 @@ PhaseStatus Compiler::fgWasmControlFlow() } }; - for (WasmInterval* interval : intervals) + for (WasmInterval* interval : *fgWasmIntervals) { resolve(interval); } @@ -1390,7 +1274,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 +1324,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 +1338,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 contigouous 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 +1463,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 +1475,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 +1561,7 @@ PhaseStatus Compiler::fgWasmControlFlow() { JITDUMP("FALLTHROUGH\n"); } - else if (succNum < numBlocks) + else { bool const isBackedge = succNum <= cursor; unsigned blockNum = 0; @@ -1614,18 +1588,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 +1609,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 +1647,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 +1674,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 65b89d1a88b8cb..59e76b5386c7bf 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -23,10 +23,72 @@ // 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_ULEB128, 0x00) // sort of INST(nop, "nop", 0, IF_OPCODE, 0x01) +INST(block, "block", 0, IF_OPCODE, 0x02) +INST(loop, "loop", 0, IF_OPCODE, 0x03) +INST(if, "if", 0, IF_OPCODE, 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) + +// constants +// +INST(i32_const, "i32.const", 0, IF_ULEB128, 0x42) +INST(i64_const, "i64.const", 0, IF_ULEB128, 0x43) +INST(f32_const, "f32.const", 0, IF_ULEB128, 0x44) +INST(f64_const, "f64.const", 0, IF_ULEB128, 0x45) + +// relops +// +INST(i32_eqz, "i32.eqz", 0, IF_OPCODE, 0x45) +INST(i32_eq, "i32.eq", 0, IF_OPCODE, 0x46) +INST(i32_ne, "i32.ne", 0, IF_OPCODE, 0x47) +INST(i32_lt_s, "i32.lt_s", 0, IF_OPCODE, 0x48) +INST(i32_lt_u, "i32.lt_u", 0, IF_OPCODE, 0x49) +INST(i32_gt_s, "i32.gt_s", 0, IF_OPCODE, 0x4A) +INST(i32_gt_u, "i32.gt_u", 0, IF_OPCODE, 0x4B) +INST(i32_le_s, "i32.le_s", 0, IF_OPCODE, 0x4C) +INST(i32_le_u, "i32.le_u", 0, IF_OPCODE, 0x4D) +INST(i32_ge_s, "i32.ge_s", 0, IF_OPCODE, 0x4E) +INST(i32_ge_u, "i32.ge_u", 0, IF_OPCODE, 0x4F) + +INST(i64_eqz, "i64.eqz", 0, IF_OPCODE, 0x50) +INST(i64_eq, "i64.eq", 0, IF_OPCODE, 0x51) +INST(i64_ne, "i64.ne", 0, IF_OPCODE, 0x52) +INST(i64_lt_s, "i64.lt_s", 0, IF_OPCODE, 0x53) +INST(i64_lt_u, "i64.lt_u", 0, IF_OPCODE, 0x54) +INST(i64_gt_s, "i64.gt_s", 0, IF_OPCODE, 0x55) +INST(i64_gt_u, "i64.gt_u", 0, IF_OPCODE, 0x56) +INST(i64_le_s, "i64.le_s", 0, IF_OPCODE, 0x57) +INST(i64_le_u, "i64.le_u", 0, IF_OPCODE, 0x58) +INST(i64_ge_s, "i64.ge_s", 0, IF_OPCODE, 0x59) +INST(i64_ge_u, "i32.ge_u", 0, IF_OPCODE, 0x5A) + +INST(f32_eq, "f32.eq", 0, IF_OPCODE, 0x5B) +INST(f32_ne, "f32.ne", 0, IF_OPCODE, 0x5C) +INST(f32_lt, "f32.lt_s", 0, IF_OPCODE, 0x5D) +INST(f32_gt, "f32.gt_s", 0, IF_OPCODE, 0x5E) +INST(f32_le, "f32.le_s", 0, IF_OPCODE, 0x5F) +INST(f32_ge, "f32.ge_s", 0, IF_OPCODE, 0x60) + +INST(f64_eq, "f64.eq", 0, IF_OPCODE, 0x61) +INST(f64_ne, "f64.ne", 0, IF_OPCODE, 0x62) +INST(f64_lt, "f64.lt_s", 0, IF_OPCODE, 0x63) +INST(f64_gt, "f64.gt_s", 0, IF_OPCODE, 0x64) +INST(f64_le, "f64.le_s", 0, IF_OPCODE, 0x65) +INST(f64_ge, "f64.ge_s", 0, IF_OPCODE, 0x66) + +// other + INST(local_get, "local.get", 0, IF_ULEB128, 0x20) INST(i32_load, "i32.load", 0, IF_MEMARG, 0x28) INST(i32_add, "i32.add", 0, IF_OPCODE, 0x6a) 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..1515ae9e30e9f3 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -110,10 +110,24 @@ 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; + assert(jtrue->gtNext == nullptr); + + JITDUMP("Lowering JTRUE:\n"); + DISPTREERANGE(BlockRange(), jtrue); + JITDUMP("\n"); + + // Todo: recognize eqz cases + + JITDUMP("Lowering JTRUE Result:\n"); + DISPTREERANGE(BlockRange(), jtrue); + JITDUMP("\n"); + + return nullptr; } //------------------------------------------------------------------------ From 5ae0cfbffca3a930c9bd62b634f96effc9a284a8 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 25 Nov 2025 15:56:55 -0800 Subject: [PATCH 02/13] copilot feedback --- src/coreclr/jit/codegenlinear.cpp | 1 + src/coreclr/jit/fgwasm.cpp | 2 +- src/coreclr/jit/instrswasm.h | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 836f63fe136981..8a2abcbfd59970 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -900,6 +900,7 @@ void CodeGen::genCodeForBBlist() unsigned blockNum = 0; unsigned const depth = findDepth(succNum, isBackedge, blockNum); inst_JMP(EJ_br, depth); + break; } case BBJ_COND: diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 6969a6bd992e74..06968cc5032093 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -1345,7 +1345,7 @@ PhaseStatus Compiler::fgWasmControlFlow() // If we use an explicit EH state marker, likely we do not need // to keep the try region contiguous. // - // If we need contigouous trys we can likely create a try-aware + // 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, diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 59e76b5386c7bf..0031f92bf15654 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -75,17 +75,17 @@ INST(i64_ge_u, "i32.ge_u", 0, IF_OPCODE, 0x5A) INST(f32_eq, "f32.eq", 0, IF_OPCODE, 0x5B) INST(f32_ne, "f32.ne", 0, IF_OPCODE, 0x5C) -INST(f32_lt, "f32.lt_s", 0, IF_OPCODE, 0x5D) -INST(f32_gt, "f32.gt_s", 0, IF_OPCODE, 0x5E) -INST(f32_le, "f32.le_s", 0, IF_OPCODE, 0x5F) -INST(f32_ge, "f32.ge_s", 0, IF_OPCODE, 0x60) +INST(f32_lt, "f32.lt", 0, IF_OPCODE, 0x5D) +INST(f32_gt, "f32.gt", 0, IF_OPCODE, 0x5E) +INST(f32_le, "f32.le", 0, IF_OPCODE, 0x5F) +INST(f32_ge, "f32.ge", 0, IF_OPCODE, 0x60) INST(f64_eq, "f64.eq", 0, IF_OPCODE, 0x61) INST(f64_ne, "f64.ne", 0, IF_OPCODE, 0x62) -INST(f64_lt, "f64.lt_s", 0, IF_OPCODE, 0x63) -INST(f64_gt, "f64.gt_s", 0, IF_OPCODE, 0x64) -INST(f64_le, "f64.le_s", 0, IF_OPCODE, 0x65) -INST(f64_ge, "f64.ge_s", 0, IF_OPCODE, 0x66) +INST(f64_lt, "f64.lt", 0, IF_OPCODE, 0x63) +INST(f64_gt, "f64.gt", 0, IF_OPCODE, 0x64) +INST(f64_le, "f64.le", 0, IF_OPCODE, 0x65) +INST(f64_ge, "f64.ge", 0, IF_OPCODE, 0x66) // other From 69cc3047930e92677bcd8ad146b65d14386f9a5e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 25 Nov 2025 15:58:24 -0800 Subject: [PATCH 03/13] copilot feedback --- src/coreclr/jit/instrswasm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 0031f92bf15654..cb2d49c6d920c7 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -71,7 +71,7 @@ INST(i64_gt_u, "i64.gt_u", 0, IF_OPCODE, 0x56) INST(i64_le_s, "i64.le_s", 0, IF_OPCODE, 0x57) INST(i64_le_u, "i64.le_u", 0, IF_OPCODE, 0x58) INST(i64_ge_s, "i64.ge_s", 0, IF_OPCODE, 0x59) -INST(i64_ge_u, "i32.ge_u", 0, IF_OPCODE, 0x5A) +INST(i64_ge_u, "i64.ge_u", 0, IF_OPCODE, 0x5A) INST(f32_eq, "f32.eq", 0, IF_OPCODE, 0x5B) INST(f32_ne, "f32.ne", 0, IF_OPCODE, 0x5C) From 673ae1764f233c06c01ad6bd19e62be1992361b5 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 26 Nov 2025 08:04:26 -0800 Subject: [PATCH 04/13] fix some opcodes; fix some formats --- src/coreclr/jit/emitfmtswasm.h | 2 ++ src/coreclr/jit/emitwasm.cpp | 13 +++++++++++++ src/coreclr/jit/instrswasm.h | 16 ++++++++-------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/emitfmtswasm.h b/src/coreclr/jit/emitfmtswasm.h index e934c1cc857f8c..7c7f054d4380ba 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(TYPE, IS_NONE, NONE) // +IF_DEF(LABEL, IS_NONE, NONE) // IF_DEF(ULEB128, IS_NONE, NONE) // IF_DEF(MEMARG, IS_NONE, NONE) // ( ) diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 6a51f88394b1b2..89dea080cb04e2 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_TYPE: + 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; @@ -199,6 +206,10 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) case IF_OPCODE: dst += emitOutputByte(dst, code); break; + case IF_TYPE: + dst += emitOutputByte(dst, code); + dst += emitOutputByte(dst, 0x40); + break; case IF_ULEB128: dst += emitOutputByte(dst, code); // todo... uleb128 @@ -288,8 +299,10 @@ void emitter::emitDispIns( switch (fmt) { case IF_OPCODE: + case IF_TYPE: break; + case IF_LABEL: case IF_ULEB128: { target_size_t imm = emitGetInsSC(id); diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index cb2d49c6d920c7..2bca0bc733b32e 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -28,11 +28,11 @@ // INST(invalid, "INVALID", 0, IF_NONE, BAD_CODE) INST(unreachable, "unreachable", 0, IF_OPCODE, 0x00) -INST(label, "label", 0, IF_ULEB128, 0x00) // sort of +INST(label, "label", 0, IF_LABEL, 0x00) INST(nop, "nop", 0, IF_OPCODE, 0x01) -INST(block, "block", 0, IF_OPCODE, 0x02) -INST(loop, "loop", 0, IF_OPCODE, 0x03) -INST(if, "if", 0, IF_OPCODE, 0x04) +INST(block, "block", 0, IF_TYPE, 0x02) +INST(loop, "loop", 0, IF_TYPE, 0x03) +INST(if, "if", 0, IF_TYPE, 0x04) INST(else, "else", 0, IF_OPCODE, 0x05) INST(end, "end", 0, IF_OPCODE, 0x0B) INST(br, "br", 0, IF_ULEB128, 0x0C) @@ -42,10 +42,10 @@ INST(return, "return", 0, IF_OPCODE, 0x0F) // constants // -INST(i32_const, "i32.const", 0, IF_ULEB128, 0x42) -INST(i64_const, "i64.const", 0, IF_ULEB128, 0x43) -INST(f32_const, "f32.const", 0, IF_ULEB128, 0x44) -INST(f64_const, "f64.const", 0, IF_ULEB128, 0x45) +INST(i32_const, "i32.const", 0, IF_ULEB128, 0x41) +INST(i64_const, "i64.const", 0, IF_ULEB128, 0x42) +INST(f32_const, "f32.const", 0, IF_ULEB128, 0x43) +INST(f64_const, "f64.const", 0, IF_ULEB128, 0x44) // relops // From a262d4cfdfaad359795f7b241d0e7fefff9e44c9 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 26 Nov 2025 08:05:43 -0800 Subject: [PATCH 05/13] a bit more feedback on the scc code --- src/coreclr/jit/fgwasm.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/fgwasm.cpp b/src/coreclr/jit/fgwasm.cpp index 06968cc5032093..4a99e3b4370c78 100644 --- a/src/coreclr/jit/fgwasm.cpp +++ b/src/coreclr/jit/fgwasm.cpp @@ -768,13 +768,10 @@ void FgWasm::WasmFindSccsCore(BitVec& subset, ArrayStack& 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(); } } From b3dc01851782f39060d64bc5644f9cf970179a0f Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 26 Nov 2025 14:51:54 -0800 Subject: [PATCH 06/13] gen code for the relops --- src/coreclr/jit/codegenwasm.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 5a56b38a5125db..d7dba91fdc5e4a 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -120,6 +120,11 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) switch (treeNode->OperGet()) { case GT_ADD: + case GT_EQ: + case GT_NE: + case GT_LT: + case GT_LE: + case GT_GE: case GT_GT: genCodeForBinary(treeNode->AsOp()); break; From 626352ef537fedd2fd5723501461bbec59187bdd Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 26 Nov 2025 17:27:01 -0800 Subject: [PATCH 07/13] stub out some more; can now compile a method that does nothing without asserts --- src/coreclr/jit/codegencommon.cpp | 6 ++++++ src/coreclr/jit/codegenwasm.cpp | 2 ++ src/coreclr/jit/emitwasm.cpp | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index c5f2cf20db5da2..cdb56cdafb65c6 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2213,6 +2213,12 @@ void CodeGen::genEmitMachineCode() // void CodeGen::genEmitUnwindDebugGCandEH() { + +#if defined(TARGET_WASM) + JITDUMP("genEmitUnwindDebugGCandEH -- NYI"); + return; +#endif + /* Now that the code is issued, we can finalize and emit the unwind data */ compiler->unwindEmit(*codePtr, coldCodePtr); diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index d7dba91fdc5e4a..058a13cf9c1123 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -57,6 +57,8 @@ void CodeGen::genFnProlog() } // Todo: prolog zeroing, shadow stack maintenance + + GetEmitter()->emitMarkPrologEnd(); } void CodeGen::genFnEpilog(BasicBlock* block) diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 89dea080cb04e2..a7efb6adc4bd2a 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -194,9 +194,8 @@ inline emitter::code_t insOpcode(instruction ins) size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { - BYTE* dst = *dp; - size_t sz = sizeof(instrDesc); + size_t sz = emitGetInstrDescSize(id); instruction ins = id->idIns(); insFormat insFmt = id->idInsFmt(); code_t code = insOpcode(ins); @@ -218,6 +217,21 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) 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) + { + size_t expected = emitSizeOfInsDsc(id); + assert(sz == expected); + emitDispIns(id, false, 0, true, emitCurCodeOffs(*dp), *dp, (dst - *dp), ig); + } +#endif + *dp = dst; return sz; } From d9a0531542a8740dbfbc99c6d0ab971d3db8033e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 1 Dec 2025 14:58:10 -0800 Subject: [PATCH 08/13] review feedback --- src/coreclr/jit/codegen.h | 20 +- src/coreclr/jit/codegencommon.cpp | 6 - src/coreclr/jit/codegenlinear.cpp | 748 +++++++++++------------------- src/coreclr/jit/codegenwasm.cpp | 494 +++++++++++++++++--- src/coreclr/jit/codegenxarch.cpp | 8 +- src/coreclr/jit/compiler.h | 8 +- src/coreclr/jit/emitfmtswasm.h | 2 +- src/coreclr/jit/emitjmps.h | 5 +- src/coreclr/jit/emitwasm.cpp | 36 +- src/coreclr/jit/emitwasm.h | 2 - src/coreclr/jit/instrswasm.h | 27 +- src/coreclr/jit/lowerwasm.cpp | 13 +- 12 files changed, 756 insertions(+), 613 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 293c630b4bc49f..dc07cb7487db2f 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -212,6 +212,19 @@ class CodeGen final : public CodeGenInterface void genCodeForBBlist(); +#if defined(TARGET_WASM) + ArrayStack* wasmControlFlowStack; + unsigned wasmCursor; + unsigned getBlockIndex(BasicBlock* block) + { + return block->bbPreorderNum; + } + unsigned findTargetDepth(BasicBlock* target); +#endif + + void genEmitStartBlock(BasicBlock* block); + BasicBlock* genEmitEndBlock(BasicBlock* block); + public: void genSpillVar(GenTree* tree); @@ -890,8 +903,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); @@ -1344,7 +1357,8 @@ class CodeGen final : public CodeGenInterface #if defined(TARGET_XARCH) void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isRemovableJmpCandidate = false); #elif defined(TARGET_WASM) - void inst_JMP(emitJumpKind jmp, unsigned depth); + void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); + void inst_SWITCH(unsigned caseCount); void inst_LABEL(unsigned depth); #else void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index cdb56cdafb65c6..c5f2cf20db5da2 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -2213,12 +2213,6 @@ void CodeGen::genEmitMachineCode() // void CodeGen::genEmitUnwindDebugGCandEH() { - -#if defined(TARGET_WASM) - JITDUMP("genEmitUnwindDebugGCandEH -- NYI"); - return; -#endif - /* Now that the code is issued, we can finalize and emit the unwind data */ compiler->unwindEmit(*codePtr, coldCodePtr); diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 8a2abcbfd59970..5f931adb72aba5 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -18,7 +18,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "codegen.h" #if defined(TARGET_WASM) -#include "fgwasm.h" +class WasmInterval; #endif //------------------------------------------------------------------------ @@ -110,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)); + unsigned wasmCursor = 0; +#endif } //------------------------------------------------------------------------ @@ -163,55 +177,6 @@ void CodeGen::genCodeForBBlist() /* Initialize structures used in the block list iteration */ genInitialize(); -#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. - // - ArrayStack wasmControlFlowStack(compiler->getAllocator(CMK_WasmCfgLowering)); - unsigned wasmCursor = 0; - - // Compute the depth of the block ending at targetNum - // or (if isBackedge) the loop starting at targetNum - // in the wasm control flow stack - // - auto findDepth = [&wasmControlFlowStack](unsigned targetNum, bool isBackedge, unsigned& match) { - int const h = wasmControlFlowStack.Height(); - - for (int i = 0; i < h; i++) - { - WasmInterval* const ii = wasmControlFlowStack.Top(i); - match = 0; - - if (isBackedge) - { - // loops bind to start - match = ii->Start(); - } - else - { - // blocks bind to end - match = ii->End(); - } - - if ((match == targetNum) && (isBackedge == ii->IsLoop())) - { - return i; - } - } - - JITDUMP("Could not find %u%s in active control stack\n", targetNum, isBackedge ? " (backedge)" : ""); - assert(!"Can't find target in control stack"); - - return ~0; - }; - -#endif // defined(TARGET_WASM) - /*------------------------------------------------------------------------- * * Walk the basic blocks and generate code for each one @@ -232,12 +197,6 @@ void CodeGen::genCodeForBBlist() } #endif // DEBUG -#if defined(TARGET_WASM) - // Track where this block is in the linear order - // - unsigned const cursor = block->bbPreorderNum; -#endif - assert(LIR::AsRange(block).CheckLIR(compiler)); // Figure out which registers hold variables on entry to this block @@ -413,48 +372,7 @@ void CodeGen::genCodeForBBlist() GetEmitter()->emitSetFirstColdIGCookie(block->bbEmitCookie); } -#if defined(TARGET_WASM) - - // Pop control flow intervals that end here (at most two, block and/or loop) - // - while (!wasmControlFlowStack.Empty() && (wasmControlFlowStack.Top()->End() == cursor)) - { - instGen(INS_end); - wasmControlFlowStack.Pop(); - } - - // Push control flow for intervals that start here or earlier - // - 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(); - } - } - -#endif // defined(TARGET_WASM) + genEmitStartBlock(block); // Both stacks are always empty on entry to a basic block. assert(genStackLevel == 0); @@ -722,444 +640,340 @@ void CodeGen::genCodeForBBlist() /* Both stacks should always be empty on exit from a basic block */ noway_assert(genStackLevel == 0); -#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())) - { - // We only need the NOP if we're not going to generate any more code as part of the block end. - - 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; - } - } - } -#endif // TARGET_AMD64 + BasicBlock* const nextBlock = genEmitEndBlock(block); -#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()) - { - if (GetEmitter()->emitSetLoopBackEdge(target)) - { - if (!block->IsLast()) - { - JITDUMP("Mark " FMT_BB " as label: alignment end-of-loop\n", block->Next()->bbNum); - block->Next()->SetFlags(BBF_HAS_LABEL); - } - } - } - }; -#endif // FEATURE_LOOP_ALIGN - - bool removedJmp = false; - -#if defined(TARGET_WASM) - // Generate Wasm control flow instructions for block ends + // Sometimes we might skip ahead in the block list // - switch (block->GetKind()) + if (nextBlock != nullptr) { - case BBJ_RETURN: - instGen(INS_return); - break; + block = nextBlock; + } - case BBJ_THROW: - instGen(INS_unreachable); - break; +#ifdef DEBUG + if (compiler->verbose) + { + varLiveKeeper->dumpBlockVariableLiveRanges(block); + } + compiler->compCurBB = nullptr; +#endif // DEBUG + } //------------------ END-FOR each block of the method ------------------- - case BBJ_CALLFINALLY: - genCallFinally(block); - if (!block->isBBCallFinallyPair()) - { - instGen(INS_unreachable); - } - break; +#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. - case BBJ_EHCATCHRET: - assert(compiler->UsesFunclets()); - genEHCatchRet(block); - FALLTHROUGH; + 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 - case BBJ_EHFINALLYRET: - case BBJ_EHFAULTRET: - case BBJ_EHFILTERRET: - if (compiler->UsesFunclets()) - { - genReserveFuncletEpilog(block); - } - instGen(INS_return); - break; + // There could be variables alive at this point. For example see lvaKeepAliveAndReportThis. + // This call is for cleaning the GC refs + genUpdateLife(VarSetOps::MakeEmpty(compiler)); - case BBJ_SWITCH: - { - BBswtDesc* const desc = block->GetSwitchTargets(); - unsigned const caseCount = desc->GetCaseCount(); + // Finalize the spill tracking logic - assert(!desc->HasDefaultCase()); + regSet.rsSpillEnd(); - if (caseCount == 0) - { - break; - } + // Finalize the temp tracking logic - inst_JMP(EJ_br_table, caseCount); + regSet.tmpEnd(); - for (unsigned caseNum = 0; caseNum < caseCount; caseNum++) - { - BasicBlock* const caseTarget = desc->GetCase(caseNum)->getDestinationBlock(); - unsigned const caseTargetNum = caseTarget->bbPreorderNum; +#ifdef DEBUG + if (compiler->verbose) + { + printf("\n# "); + printf("compCycleEstimate = %6d, compSizeEstimate = %5d ", compiler->compCycleEstimate, + compiler->compSizeEstimate); + printf("%s\n", compiler->info.compFullName); + } +#endif +} - bool const isBackedge = caseTargetNum <= cursor; - unsigned blockNum = 0; - unsigned depth = findDepth(caseTargetNum, isBackedge, blockNum); +// TODO-WASM-Factoring: this ifdef factoring is temporary. The end factoring should look like this: +// 1. Everything shared by all codegen backends (incl. WASM) is moved to codegencommon.cpp. +// 2. Everything else stays here. +// 3. codegenlinear.cpp gets renamed to codegennative.cpp. +// +#ifndef TARGET_WASM - inst_LABEL(depth); - } +//------------------------------------------------------------------------ +// genEmitStartBlock: prepare for codegen in a block +// +// Arguments: +// block - block to prepare for +// +void CodeGen::genEmitStartBlock(BasicBlock* block) +{ +} - JITDUMP("\n"); - break; - } +//------------------------------------------------------------------------ +// 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; - case BBJ_CALLFINALLYRET: - case BBJ_ALWAYS: +#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())) + { + // We only need the NOP if we're not going to generate any more code as part of the block end. + + switch (block->GetKind()) { -#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()); - } -#endif // DEBUG + 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; - unsigned const succNum = block->GetTarget()->bbPreorderNum; + case BBJ_THROW: + case BBJ_CALLFINALLY: + case BBJ_EHCATCHRET: + // We're going to generate more code below anyway, so no need for the NOP. - // If this block jumps to the next one, we might be able to skip emitting the jump - if (block->CanRemoveJumpToNext(compiler)) - { - assert(succNum == (cursor + 1)); + case BBJ_RETURN: + case BBJ_EHFINALLYRET: + case BBJ_EHFAULTRET: + case BBJ_EHFILTERRET: + // These are the "epilog follows" case, handled in the emitter. break; - } - bool const isBackedge = succNum <= cursor; - unsigned blockNum = 0; - unsigned const depth = findDepth(succNum, isBackedge, blockNum); - inst_JMP(EJ_br, depth); - 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 - case BBJ_COND: +#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()) + { + if (GetEmitter()->emitSetLoopBackEdge(target)) { - const unsigned trueNum = block->GetTrueTarget()->bbPreorderNum; - const unsigned falseNum = block->GetFalseTarget()->bbPreorderNum; - - // We don't expect degenerate BBJ_COND - // - assert(trueNum != falseNum); - - // We don't expect the true target to be the next block. - // - const bool reverseCondition = trueNum == (cursor + 1); - assert(!reverseCondition); - - // br_if for true target - // - bool const isTrueBackedge = trueNum <= cursor; - unsigned blockNum = 0; - unsigned depth = findDepth(trueNum, isTrueBackedge, blockNum); - inst_JMP(EJ_br_if, depth); - - // br for false target, if not fallthrough - // - const bool fallThrough = falseNum == (cursor + 1); - if (fallThrough) + if (!block->IsLast()) { - break; + JITDUMP("Mark " FMT_BB " as label: alignment end-of-loop\n", block->Next()->bbNum); + block->Next()->SetFlags(BBF_HAS_LABEL); } - - bool const isFalseBackedge = falseNum <= cursor; - blockNum = 0; - depth = findDepth(falseNum, isFalseBackedge, blockNum); - inst_JMP(EJ_br, depth); - - break; } - - default: - noway_assert(!"Unexpected bbKind"); - break; } -#else - - /* Do we need to generate a jump or return? */ + }; +#endif // FEATURE_LOOP_ALIGN - 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; - - default: - noway_assert(!"Unexpected bbKind"); - break; - } + break; -#endif // defined(TARGET_WASM) + 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: -// 1. Everything shared by all codegen backends (incl. WASM) is moved to codegencommon.cpp. -// 2. Everything else stays here. -// 3. codegenlinear.cpp gets renamed to codegennative.cpp. -// -#ifndef TARGET_WASM //------------------------------------------------------------------------ // genRecordAsyncResume: // Record information about an async resume point in the async resume info tabl.e @@ -2862,10 +2676,6 @@ void CodeGen::genCodeForSetcc(GenTreeCC* setcc) void CodeGen::genEmitterUnitTests() { - -#if defined(TARGET_WASM) - return; -#else if (!JitConfig.JitEmitUnitTests().contains(compiler->info.compMethodHnd, compiler->info.compClassHnd, &compiler->info.compMethodInfo->args)) { @@ -2935,8 +2745,6 @@ void CodeGen::genEmitterUnitTests() instGen(INS_nop); JITDUMP("*************** End of genEmitterUnitTests()\n"); - -#endif // defined(TARGET_WASM) } #endif // defined(DEBUG) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 058a13cf9c1123..4bf746e3862381 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -56,7 +56,7 @@ void CodeGen::genFnProlog() psiBegProlog(); } - // Todo: prolog zeroing, shadow stack maintenance + // TODO-WASM: prolog zeroing, shadow stack maintenance GetEmitter()->emitMarkPrologEnd(); } @@ -70,7 +70,27 @@ 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 + + // compiler->unwindBegEpilog(); + + instGen(INS_return); + + // compiler->unwindEndEpilog(); } void CodeGen::genCaptureFuncletPrologEpilogInfo() @@ -101,6 +121,253 @@ void CodeGen::genFuncletEpilog() NYI_WASM("genFuncletEpilog"); } +//------------------------------------------------------------------------ +// 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(); + } + } +} + +//------------------------------------------------------------------------ +// 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) +{ + // Generate Wasm control flow instructions for block ends + // + switch (block->GetKind()) + { + case BBJ_RETURN: + genExitCode(block); + break; + + case BBJ_THROW: + instGen(INS_unreachable); + break; + + case BBJ_CALLFINALLY: + // Wasm-TODO: EH model + genCallFinally(block); + if (!block->isBBCallFinallyPair()) + { + instGen(INS_unreachable); + } + break; + + case BBJ_EHCATCHRET: + // Wasm-TODO: EH model + assert(compiler->UsesFunclets()); + genEHCatchRet(block); + FALLTHROUGH; + + case BBJ_EHFINALLYRET: + case BBJ_EHFAULTRET: + case BBJ_EHFILTERRET: + // Wasm-TODO: EH model + if (compiler->UsesFunclets()) + { + genReserveFuncletEpilog(block); + } + instGen(INS_return); + break; + + case BBJ_SWITCH: + { + BBswtDesc* const desc = block->GetSwitchTargets(); + unsigned const caseCount = desc->GetCaseCount(); + + assert(!desc->HasDefaultCase()); + + if (caseCount == 0) + { + break; + } + + inst_SWITCH(caseCount); + + for (unsigned caseNum = 0; caseNum < caseCount; caseNum++) + { + BasicBlock* const caseTarget = desc->GetCase(caseNum)->getDestinationBlock(); + unsigned depth = findTargetDepth(caseTarget); + inst_LABEL(depth); + } + + JITDUMP("\n"); + break; + } + + case BBJ_CALLFINALLYRET: + 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()); + } +#endif // DEBUG + + BasicBlock* const target = block->GetTarget(); + + // If this block jumps to the next one, we might be able to skip emitting the jump + // + if (block->CanRemoveJumpToNext(compiler)) + { + assert(getBlockIndex(target) == (getBlockIndex(block) + 1)); + break; + } + + inst_JMP(EJ_jmp, target); + break; + } + + case BBJ_COND: + { + 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()) + { + break; + } + inst_JMP(EJ_jmp, falseTarget); + + break; + } + + default: + noway_assert(!"Unexpected bbKind"); + break; + } + + // Indicate codgen should just proceed to the next block. + // + return nullptr; +} + +//------------------------------------------------------------------------ +// genCodeForTreeNode: codegen for a particular tree node +// +// Arguments: +// treeNode - node to generate code for +// void CodeGen::genCodeForTreeNode(GenTree* treeNode) { #ifdef DEBUG @@ -122,13 +389,17 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) switch (treeNode->OperGet()) { case GT_ADD: + genCodeForBinary(treeNode->AsOp()); + break; + case GT_EQ: case GT_NE: case GT_LT: case GT_LE: case GT_GE: case GT_GT: - genCodeForBinary(treeNode->AsOp()); + genConsumeOperands(treeNode->AsOp()); + genCodeForCompare(treeNode->AsOp()); break; case GT_LCL_VAR: @@ -136,7 +407,8 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; case GT_JTRUE: - // Do nothing; handled by end of block processing + // Codegen handled by genEmitEndBlock + genConsumeOperands(treeNode->AsOp()); break; case GT_RETURN: @@ -216,19 +488,89 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_ADD, TYP_DOUBLE): ins = INS_f64_add; break; + default: + ins = INS_none; + NYI_WASM("genCodeForBinary"); + break; + } + + GetEmitter()->emitIns(ins); + genProduceReg(treeNode); +} + +//------------------------------------------------------------------------ +// genCodeForLclVar: Produce code for a GT_LCL_VAR node. +// +// Arguments: +// tree - the GT_LCL_VAR node +// +void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) +{ + assert(tree->OperIs(GT_LCL_VAR) && !tree->IsMultiReg()); + LclVarDsc* varDsc = compiler->lvaGetDesc(tree); + + // Unlike other targets, we can't "reload at the point of use", since that would require inserting instructions + // into the middle of an already-emitted instruction group. Instead, we order the nodes in a way that obeys the + // value stack constraints of WASM precisely. However, the liveness tracking is done in the same way as for other + // targets, hence "genProduceReg" is only called for non-candidates. + if (!varDsc->lvIsRegCandidate()) + { + var_types type = varDsc->GetRegisterType(tree); + // TODO-WASM: actually local.get the frame base local here. + GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(tree), tree->GetLclNum(), 0); + genProduceReg(tree); + } + else + { + assert(genIsValidReg(varDsc->GetRegNum())); + unsigned wasmLclIndex = UnpackWasmReg(varDsc->GetRegNum()); + GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex); + } +} + +//------------------------------------------------------------------------ +// 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->gtOp1; + 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(), treeNode->gtOp1->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_EQ, TYP_FLOAT): - ins = INS_f32_eq; - break; - case PackOperAndType(GT_EQ, TYP_DOUBLE): - ins = INS_f64_eq; - break; case PackOperAndType(GT_NE, TYP_INT): ins = INS_i32_ne; @@ -236,12 +578,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_NE, TYP_LONG): ins = INS_i64_ne; 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_INT): ins = treeNode->IsUnsigned() ? INS_i32_lt_u : INS_i32_lt_s; @@ -249,12 +585,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_LT, TYP_LONG): ins = treeNode->IsUnsigned() ? INS_i64_lt_u : INS_i64_lt_s; 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_INT): ins = treeNode->IsUnsigned() ? INS_i32_le_u : INS_i32_le_s; @@ -262,12 +592,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_LE, TYP_LONG): ins = treeNode->IsUnsigned() ? INS_i64_le_u : INS_i64_le_s; 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_INT): ins = treeNode->IsUnsigned() ? INS_i32_ge_u : INS_i32_ge_s; @@ -275,12 +599,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_GE, TYP_LONG): ins = treeNode->IsUnsigned() ? INS_i64_ge_u : INS_i64_ge_s; 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_INT): ins = treeNode->IsUnsigned() ? INS_i32_gt_u : INS_i32_gt_s; @@ -288,12 +606,6 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) case PackOperAndType(GT_GT, TYP_LONG): ins = treeNode->IsUnsigned() ? INS_i64_gt_u : INS_i64_gt_s; break; - case PackOperAndType(GT_GT, TYP_FLOAT): - ins = INS_f32_gt; - break; - case PackOperAndType(GT_GT, TYP_DOUBLE): - ins = INS_f64_gt; - break; default: ins = INS_none; @@ -306,33 +618,70 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) } //------------------------------------------------------------------------ -// genCodeForLclVar: Produce code for a GT_LCL_VAR node. +// genCompareFloat: Generate code for comparing floats // // Arguments: -// tree - the GT_LCL_VAR node +// treeNode - the compare tree // -void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) +void CodeGen::genCompareFloat(GenTreeOp* treeNode) { - assert(tree->OperIs(GT_LCL_VAR) && !tree->IsMultiReg()); - LclVarDsc* varDsc = compiler->lvaGetDesc(tree); + assert(treeNode->OperIsCmpCompare()); - // Unlike other targets, we can't "reload at the point of use", since that would require inserting instructions - // into the middle of an already-emitted instruction group. Instead, we order the nodes in a way that obeys the - // value stack constraints of WASM precisely. However, the liveness tracking is done in the same way as for other - // targets, hence "genProduceReg" is only called for non-candidates. - if (!varDsc->lvIsRegCandidate()) + if ((treeNode->gtFlags & GTF_RELOP_NAN_UN) != 0) { - var_types type = varDsc->GetRegisterType(tree); - // TODO-WASM: actually local.get the frame base local here. - GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(tree), tree->GetLclNum(), 0); - genProduceReg(tree); + NYI_WASM("genCompareFloat: unordered compares"); } - else + + genConsumeOperands(treeNode); + + instruction ins; + switch (PackOperAndType(treeNode->OperGet(), treeNode->gtOp1->TypeGet())) { - assert(genIsValidReg(varDsc->GetRegNum())); - unsigned wasmLclIndex = UnpackWasmReg(varDsc->GetRegNum()); - GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex); + 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: + ins = INS_none; + NYI_WASM("genCodeForBinary"); + break; } + + GetEmitter()->emitIns(ins); + genProduceReg(treeNode); } BasicBlock* CodeGen::genCallFinally(BasicBlock* block) @@ -370,20 +719,39 @@ 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, unsigned depth) +void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) { - instruction instr = emitter::emitJumpKindToIns(jmp); + instruction instr = emitter::emitJumpKindToIns(jmp); + unsigned const depth = findTargetDepth(tgtBlock); GetEmitter()->emitIns_I(instr, EA_4BYTE, depth); } //------------------------------------------------------------------------ -// inst_LABEL: Emit a label index +// inst_SWITCH: Emit a switch +// +// Arguments: +// caseCount -- number of cases in the switch +// +void CodeGen::inst_SWITCH(unsigned caseCount) +{ + GetEmitter()->emitIns_I(INS_br_table, EA_4BYTE, caseCount); +} + +//------------------------------------------------------------------------ +// inst_LABEL: Emit a switch table case label index +// +// Notes: +// Each index describes one switch case // void CodeGen::inst_LABEL(unsigned depth) { - NYI_WASM("inst_LABEL"); + GetEmitter()->emitIns_I(INS_label, 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.h b/src/coreclr/jit/compiler.h index be9640c99c8860..645a7e2e2667e3 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6261,18 +6261,14 @@ class Compiler PhaseStatus fgFindOperOrder(); #ifdef TARGET_WASM - FlowGraphDfsTree* fgWasmDfs(); PhaseStatus fgWasmControlFlow(); PhaseStatus fgWasmTransformSccs(); - #ifdef DEBUG - void fgDumpWasmControlFlow(); void fgDumpWasmControlFlowDot(); - -#endif -#endif +#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 7c7f054d4380ba..1413c3cdfb1718 100644 --- a/src/coreclr/jit/emitfmtswasm.h +++ b/src/coreclr/jit/emitfmtswasm.h @@ -28,7 +28,7 @@ enum ID_OPS IF_DEF(NONE, IS_NONE, NONE) IF_DEF(OPCODE, IS_NONE, NONE) // -IF_DEF(TYPE, 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 7acfc6e5d3a5c9..4653701791ef3d 100644 --- a/src/coreclr/jit/emitjmps.h +++ b/src/coreclr/jit/emitjmps.h @@ -61,9 +61,8 @@ JMP_SMALL(ne , eq , bne ) // NE #elif defined(TARGET_WASM) -JMP_SMALL(br , br , br ) -JMP_SMALL(br_if , br_if , br_if ) -JMP_SMALL(br_table , br_table , br_table ) +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 a7efb6adc4bd2a..a645232a1714e7 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -151,7 +151,7 @@ unsigned emitter::instrDesc::idCodeSize() const { case IF_OPCODE: break; - case IF_TYPE: + case IF_BLOCK: size += 1; break; case IF_LABEL: @@ -176,43 +176,29 @@ void emitter::emitSetShortJump(instrDescJmp* id) NYI_WASM("emitSetShortJump"); } -inline emitter::code_t insOpcode(instruction ins) -{ - // clang-format off - const static emitter::code_t insCodes[] = - { - #define INST(id, nm, v, fmt, op) op, - #include "instrs.h" - }; - // clang-format on - - assert((unsigned)ins < ArrLen(insCodes)); - assert((insCodes[ins] != BAD_CODE)); - - return insCodes[ins]; -} - size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { BYTE* dst = *dp; size_t sz = emitGetInstrDescSize(id); instruction ins = id->idIns(); insFormat insFmt = id->idInsFmt(); - code_t code = insOpcode(ins); + unsigned opcode = GetInsOpcode(ins); switch (insFmt) { case IF_OPCODE: - dst += emitOutputByte(dst, code); + dst += emitOutputByte(dst, opcode); break; - case IF_TYPE: - dst += emitOutputByte(dst, code); + case IF_BLOCK: + dst += emitOutputByte(dst, opcode); dst += emitOutputByte(dst, 0x40); break; case IF_ULEB128: - dst += emitOutputByte(dst, code); - // todo... uleb128 + dst += emitOutputByte(dst, opcode); + // TODO-WASM: emit uleb128 break; + case IF_LABEL: + // TODO-WASM: emit uleb128 default: NYI_WASM("emitOutputInstr"); } @@ -313,7 +299,7 @@ void emitter::emitDispIns( switch (fmt) { case IF_OPCODE: - case IF_TYPE: + case IF_BLOCK: break; case IF_LABEL: @@ -380,7 +366,7 @@ void emitter::emitDispInsHex(instrDesc* id, BYTE* code, size_t sz) #if defined(DEBUG) || defined(LATE_DISASM) emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(instrDesc* id) { - // TODO: for real... + // WASM-TODO: for real... insExecutionCharacteristics result; result.insThroughput = PERFSCORE_THROUGHPUT_1C; result.insLatency = PERFSCORE_LATENCY_1C; diff --git a/src/coreclr/jit/emitwasm.h b/src/coreclr/jit/emitwasm.h index 0427bcbf12404e..890ab53c04197a 100644 --- a/src/coreclr/jit/emitwasm.h +++ b/src/coreclr/jit/emitwasm.h @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -typedef uint32_t code_t; - /************************************************************************/ /* Debug-only routines to display instructions */ /************************************************************************/ diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 9328254f0e1994..2d7a32f38b6b9b 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -30,9 +30,9 @@ 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_TYPE, 0x02) -INST(loop, "loop", 0, IF_TYPE, 0x03) -INST(if, "if", 0, IF_TYPE, 0x04) +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) @@ -40,15 +40,16 @@ 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) -// constants -// +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) +INST(f32_load, "f32.load", 0, IF_MEMARG, 0x2a) +INST(f64_load, "f64.load", 0, IF_MEMARG, 0x2b) + INST(i32_const, "i32.const", 0, IF_ULEB128, 0x41) INST(i64_const, "i64.const", 0, IF_ULEB128, 0x42) INST(f32_const, "f32.const", 0, IF_ULEB128, 0x43) INST(f64_const, "f64.const", 0, IF_ULEB128, 0x44) - -// relops -// INST(i32_eqz, "i32.eqz", 0, IF_OPCODE, 0x45) INST(i32_eq, "i32.eq", 0, IF_OPCODE, 0x46) INST(i32_ne, "i32.ne", 0, IF_OPCODE, 0x47) @@ -60,7 +61,6 @@ INST(i32_le_s, "i32.le_s", 0, IF_OPCODE, 0x4C) INST(i32_le_u, "i32.le_u", 0, IF_OPCODE, 0x4D) INST(i32_ge_s, "i32.ge_s", 0, IF_OPCODE, 0x4E) INST(i32_ge_u, "i32.ge_u", 0, IF_OPCODE, 0x4F) - INST(i64_eqz, "i64.eqz", 0, IF_OPCODE, 0x50) INST(i64_eq, "i64.eq", 0, IF_OPCODE, 0x51) INST(i64_ne, "i64.ne", 0, IF_OPCODE, 0x52) @@ -72,14 +72,12 @@ INST(i64_le_s, "i64.le_s", 0, IF_OPCODE, 0x57) INST(i64_le_u, "i64.le_u", 0, IF_OPCODE, 0x58) INST(i64_ge_s, "i64.ge_s", 0, IF_OPCODE, 0x59) INST(i64_ge_u, "i64.ge_u", 0, IF_OPCODE, 0x5A) - INST(f32_eq, "f32.eq", 0, IF_OPCODE, 0x5B) INST(f32_ne, "f32.ne", 0, IF_OPCODE, 0x5C) INST(f32_lt, "f32.lt", 0, IF_OPCODE, 0x5D) INST(f32_gt, "f32.gt", 0, IF_OPCODE, 0x5E) INST(f32_le, "f32.le", 0, IF_OPCODE, 0x5F) INST(f32_ge, "f32.ge", 0, IF_OPCODE, 0x60) - INST(f64_eq, "f64.eq", 0, IF_OPCODE, 0x61) INST(f64_ne, "f64.ne", 0, IF_OPCODE, 0x62) INST(f64_lt, "f64.lt", 0, IF_OPCODE, 0x63) @@ -87,13 +85,6 @@ INST(f64_gt, "f64.gt", 0, IF_OPCODE, 0x64) INST(f64_le, "f64.le", 0, IF_OPCODE, 0x65) INST(f64_ge, "f64.ge", 0, IF_OPCODE, 0x66) -// other - -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) -INST(f32_load, "f32.load", 0, IF_MEMARG, 0x2a) -INST(f64_load, "f64.load", 0, IF_MEMARG, 0x2b) INST(i32_add, "i32.add", 0, IF_OPCODE, 0x6a) INST(i64_add, "i64.add", 0, IF_OPCODE, 0x7c) INST(f32_add, "f32.add", 0, IF_OPCODE, 0x92) diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 1515ae9e30e9f3..94eee88e21fda2 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -115,18 +115,7 @@ GenTree* Lowering::LowerMul(GenTreeOp* mul) // GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) { - assert(jtrue->gtNext == nullptr); - - JITDUMP("Lowering JTRUE:\n"); - DISPTREERANGE(BlockRange(), jtrue); - JITDUMP("\n"); - - // Todo: recognize eqz cases - - JITDUMP("Lowering JTRUE Result:\n"); - DISPTREERANGE(BlockRange(), jtrue); - JITDUMP("\n"); - + // TODO-WASM: recognize eqz cases return nullptr; } From 2050c5ffc3f42327ca4f31ac2917a245cb69ab1b Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 1 Dec 2025 16:41:50 -0800 Subject: [PATCH 09/13] review feedback --- src/coreclr/jit/codegen.h | 10 +--- src/coreclr/jit/codegenlinear.cpp | 2 +- src/coreclr/jit/codegenwasm.cpp | 90 ++++++++++--------------------- src/coreclr/jit/emitwasm.cpp | 15 +++--- src/coreclr/jit/instrswasm.h | 4 -- 5 files changed, 37 insertions(+), 84 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index dc07cb7487db2f..358b75d1c46062 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -215,11 +215,7 @@ class CodeGen final : public CodeGenInterface #if defined(TARGET_WASM) ArrayStack* wasmControlFlowStack; unsigned wasmCursor; - unsigned getBlockIndex(BasicBlock* block) - { - return block->bbPreorderNum; - } - unsigned findTargetDepth(BasicBlock* target); + unsigned findTargetDepth(BasicBlock* target); #endif void genEmitStartBlock(BasicBlock* block); @@ -1356,10 +1352,6 @@ 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); - void inst_SWITCH(unsigned caseCount); - void inst_LABEL(unsigned depth); #else void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); #endif diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 5f931adb72aba5..6d796185eb071c 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -122,7 +122,7 @@ void CodeGen::genInitialize() // wasmControlFlowStack = new (compiler, CMK_WasmCfgLowering) ArrayStack(compiler->getAllocator(CMK_WasmCfgLowering)); - unsigned wasmCursor = 0; + wasmCursor = 0; #endif } diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 4bf746e3862381..f3de94389f1baa 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -85,12 +85,9 @@ void CodeGen::genFnEpilog(BasicBlock* block) } // TODO-WASM: shadow stack maintenance - - // compiler->unwindBegEpilog(); + // TODO-WASM-CQ: do not emit "return" in case this is the last block instGen(INS_return); - - // compiler->unwindEndEpilog(); } void CodeGen::genCaptureFuncletPrologEpilogInfo() @@ -121,6 +118,21 @@ 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 // @@ -244,11 +256,12 @@ BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) break; case BBJ_THROW: + // TODO-WASM-CQ: only emit this when needed. instGen(INS_unreachable); break; case BBJ_CALLFINALLY: - // Wasm-TODO: EH model + // TODO-WASM: EH model genCallFinally(block); if (!block->isBBCallFinallyPair()) { @@ -257,20 +270,15 @@ BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) break; case BBJ_EHCATCHRET: - // Wasm-TODO: EH model - assert(compiler->UsesFunclets()); + // TODO-WASM: EH model genEHCatchRet(block); FALLTHROUGH; case BBJ_EHFINALLYRET: case BBJ_EHFAULTRET: case BBJ_EHFILTERRET: - // Wasm-TODO: EH model - if (compiler->UsesFunclets()) - { - genReserveFuncletEpilog(block); - } - instGen(INS_return); + // TODO-WASM: EH model + genReserveFuncletEpilog(block); break; case BBJ_SWITCH: @@ -285,13 +293,14 @@ BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) break; } - inst_SWITCH(caseCount); + 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); - inst_LABEL(depth); + + GetEmitter()->emitIns_I(INS_label, EA_4BYTE, depth); } JITDUMP("\n"); @@ -301,15 +310,6 @@ BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) case BBJ_CALLFINALLYRET: 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()); - } -#endif // DEBUG - BasicBlock* const target = block->GetTarget(); // If this block jumps to the next one, we might be able to skip emitting the jump @@ -398,7 +398,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) case GT_LE: case GT_GE: case GT_GT: - genConsumeOperands(treeNode->AsOp()); genCodeForCompare(treeNode->AsOp()); break; @@ -538,7 +537,7 @@ void CodeGen::genCodeForCompare(GenTreeOp* tree) { assert(tree->OperIsCmpCompare()); - GenTree* const op1 = tree->gtOp1; + GenTree* const op1 = tree->gtGetOp1(); var_types const op1Type = op1->TypeGet(); if (varTypeIsFloating(op1Type)) @@ -563,7 +562,7 @@ void CodeGen::genCompareInt(GenTreeOp* treeNode) genConsumeOperands(treeNode); instruction ins; - switch (PackOperAndType(treeNode->OperGet(), treeNode->gtOp1->TypeGet())) + switch (PackOperAndType(treeNode->OperGet(), genActualType(treeNode->gtGetOp1()->TypeGet()))) { case PackOperAndType(GT_EQ, TYP_INT): ins = INS_i32_eq; @@ -571,46 +570,38 @@ void CodeGen::genCompareInt(GenTreeOp* treeNode) 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: - ins = INS_none; - NYI_WASM("genCodeForBinary"); - break; + unreached(); } GetEmitter()->emitIns(ins); @@ -673,11 +664,8 @@ void CodeGen::genCompareFloat(GenTreeOp* treeNode) case PackOperAndType(GT_GT, TYP_DOUBLE): ins = INS_f64_gt; break; - default: - ins = INS_none; - NYI_WASM("genCodeForBinary"); - break; + unreached(); } GetEmitter()->emitIns(ins); @@ -732,28 +720,6 @@ void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) GetEmitter()->emitIns_I(instr, EA_4BYTE, depth); } -//------------------------------------------------------------------------ -// inst_SWITCH: Emit a switch -// -// Arguments: -// caseCount -- number of cases in the switch -// -void CodeGen::inst_SWITCH(unsigned caseCount) -{ - GetEmitter()->emitIns_I(INS_br_table, EA_4BYTE, caseCount); -} - -//------------------------------------------------------------------------ -// inst_LABEL: Emit a switch table case label index -// -// Notes: -// Each index describes one switch case -// -void CodeGen::inst_LABEL(unsigned depth) -{ - GetEmitter()->emitIns_I(INS_label, EA_4BYTE, depth); -} - void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, unsigned prologSize, unsigned epilogSize DEBUGARG(void* code)) { // GCInfo not captured/created by codegen. diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index a645232a1714e7..dc452cfb2b7dbb 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -179,7 +179,7 @@ void emitter::emitSetShortJump(instrDescJmp* id) size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) { BYTE* dst = *dp; - size_t sz = emitGetInstrDescSize(id); + size_t sz = emitSizeOfInsDsc(id); instruction ins = id->idIns(); insFormat insFmt = id->idInsFmt(); unsigned opcode = GetInsOpcode(ins); @@ -213,7 +213,6 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) if (emitComp->opts.disAsm) { size_t expected = emitSizeOfInsDsc(id); - assert(sz == expected); emitDispIns(id, false, 0, true, emitCurCodeOffs(*dp), *dp, (dst - *dp), ig); } #endif @@ -222,14 +221,14 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) return sz; } -const instruction emitJumpKindInstructions[] = { - INS_nop, +/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind) +{ + const instruction emitJumpKindInstructions[] = { + INS_nop, #define JMP_SMALL(en, rev, ins) INS_##ins, #include "emitjmps.h" -}; + }; -/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind) -{ assert((unsigned)jumpKind < ArrLen(emitJumpKindInstructions)); return emitJumpKindInstructions[jumpKind]; } @@ -366,7 +365,7 @@ void emitter::emitDispInsHex(instrDesc* id, BYTE* code, size_t sz) #if defined(DEBUG) || defined(LATE_DISASM) emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(instrDesc* id) { - // WASM-TODO: for real... + // TODO-WASM: for real... insExecutionCharacteristics result; result.insThroughput = PERFSCORE_THROUGHPUT_1C; result.insLatency = PERFSCORE_LATENCY_1C; diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 2d7a32f38b6b9b..015532c9024110 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -46,10 +46,6 @@ INST(i64_load, "i64.load", 0, IF_MEMARG, 0x29) INST(f32_load, "f32.load", 0, IF_MEMARG, 0x2a) INST(f64_load, "f64.load", 0, IF_MEMARG, 0x2b) -INST(i32_const, "i32.const", 0, IF_ULEB128, 0x41) -INST(i64_const, "i64.const", 0, IF_ULEB128, 0x42) -INST(f32_const, "f32.const", 0, IF_ULEB128, 0x43) -INST(f64_const, "f64.const", 0, IF_ULEB128, 0x44) INST(i32_eqz, "i32.eqz", 0, IF_OPCODE, 0x45) INST(i32_eq, "i32.eq", 0, IF_OPCODE, 0x46) INST(i32_ne, "i32.ne", 0, IF_OPCODE, 0x47) From 5bf1cf5ffd999a866ddfb6d0eec96437ae0c73cd Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 1 Dec 2025 19:41:16 -0800 Subject: [PATCH 10/13] review feedback --- src/coreclr/jit/codegencommon.cpp | 15 ++++++++-- src/coreclr/jit/codegenwasm.cpp | 46 ------------------------------- src/coreclr/jit/emitwasm.cpp | 1 - 3 files changed, 13 insertions(+), 49 deletions(-) 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/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index f3de94389f1baa..33c363ecfe7a70 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -15,52 +15,6 @@ void CodeGen::genMarkLabelsForCodegen() // (or use them directly and leave this empty). } -void CodeGen::genFnProlog() -{ - ScopedSetVariable _setGeneratingProlog(&compiler->compGeneratingProlog, true); - - compiler->funSetCurrentFunc(0); - -#ifdef DEBUG - if (verbose) - { - printf("*************** In genFnProlog()\n"); - } -#endif - -#ifdef DEBUG - genInterruptibleUsed = true; -#endif - - assert(compiler->lvaDoneFrameLayout == Compiler::FINAL_FRAME_LAYOUT); - - /* Ready to start on the prolog proper */ - - GetEmitter()->emitBegProlog(); - // compiler->unwindBegProlog(); - - // Do this so we can put the prolog instruction group ahead of - // other instruction groups - genIPmappingAddToFront(IPmappingDscKind::Prolog, DebugInfo(), true); - -#ifdef DEBUG - if (compiler->opts.dspCode) - { - printf("\n__prolog:\n"); - } -#endif - - if (compiler->opts.compScopeInfo && (compiler->info.compVarScopesCount > 0)) - { - // Create new scopes for the method-parameters for the prolog-block. - psiBegProlog(); - } - - // TODO-WASM: prolog zeroing, shadow stack maintenance - - GetEmitter()->emitMarkPrologEnd(); -} - void CodeGen::genFnEpilog(BasicBlock* block) { #ifdef DEBUG diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index dc452cfb2b7dbb..c020eded146558 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -212,7 +212,6 @@ size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) #else if (emitComp->opts.disAsm) { - size_t expected = emitSizeOfInsDsc(id); emitDispIns(id, false, 0, true, emitCurCodeOffs(*dp), *dp, (dst - *dp), ig); } #endif From b9a90364f615189cab915ba3ce8a7fbe397ee70f Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 3 Dec 2025 08:58:07 -0800 Subject: [PATCH 11/13] review feedback --- src/coreclr/jit/codegenlinear.cpp | 34 ++--- src/coreclr/jit/codegenwasm.cpp | 205 +++++++++++------------------- 2 files changed, 94 insertions(+), 145 deletions(-) diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 6d796185eb071c..b71d8efffefa13 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -701,23 +701,6 @@ void CodeGen::genCodeForBBlist() #endif } -// TODO-WASM-Factoring: this ifdef factoring is temporary. The end factoring should look like this: -// 1. Everything shared by all codegen backends (incl. WASM) is moved to codegencommon.cpp. -// 2. Everything else stays here. -// 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) -{ -} - //------------------------------------------------------------------------ // genEmitEndBlock: finish up codegen in a block // @@ -974,6 +957,23 @@ BasicBlock* CodeGen::genEmitEndBlock(BasicBlock* block) return result; } +// TODO-WASM-Factoring: this ifdef factoring is temporary. The end factoring should look like this: +// 1. Everything shared by all codegen backends (incl. WASM) is moved to codegencommon.cpp. +// 2. Everything else stays here. +// 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 33c363ecfe7a70..86938863d323ef 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -190,132 +190,6 @@ void CodeGen::genEmitStartBlock(BasicBlock* block) } } -//------------------------------------------------------------------------ -// 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) -{ - // Generate Wasm control flow instructions for block ends - // - switch (block->GetKind()) - { - case BBJ_RETURN: - genExitCode(block); - break; - - case BBJ_THROW: - // TODO-WASM-CQ: only emit this when needed. - instGen(INS_unreachable); - break; - - case BBJ_CALLFINALLY: - // TODO-WASM: EH model - genCallFinally(block); - if (!block->isBBCallFinallyPair()) - { - instGen(INS_unreachable); - } - break; - - case BBJ_EHCATCHRET: - // TODO-WASM: EH model - genEHCatchRet(block); - FALLTHROUGH; - - case BBJ_EHFINALLYRET: - case BBJ_EHFAULTRET: - case BBJ_EHFILTERRET: - // TODO-WASM: EH model - genReserveFuncletEpilog(block); - break; - - case BBJ_SWITCH: - { - BBswtDesc* const desc = block->GetSwitchTargets(); - unsigned const caseCount = desc->GetCaseCount(); - - assert(!desc->HasDefaultCase()); - - if (caseCount == 0) - { - break; - } - - 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); - } - - JITDUMP("\n"); - break; - } - - case BBJ_CALLFINALLYRET: - case BBJ_ALWAYS: - { - BasicBlock* const target = block->GetTarget(); - - // If this block jumps to the next one, we might be able to skip emitting the jump - // - if (block->CanRemoveJumpToNext(compiler)) - { - assert(getBlockIndex(target) == (getBlockIndex(block) + 1)); - break; - } - - inst_JMP(EJ_jmp, target); - break; - } - - case BBJ_COND: - { - 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()) - { - break; - } - inst_JMP(EJ_jmp, falseTarget); - - break; - } - - default: - noway_assert(!"Unexpected bbKind"); - break; - } - - // Indicate codgen should just proceed to the next block. - // - return nullptr; -} - //------------------------------------------------------------------------ // genCodeForTreeNode: codegen for a particular tree node // @@ -360,8 +234,11 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) break; case GT_JTRUE: - // Codegen handled by genEmitEndBlock - genConsumeOperands(treeNode->AsOp()); + genCodeForJTrue(treeNode->AsOp()); + break; + + case GT_SWITCH: + genTableBasedSwitch(treeNode); break; case GT_RETURN: @@ -382,6 +259,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 // From 9b775b3c379dbafb78d2386f9102f65b1e6a2580 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 3 Dec 2025 09:48:59 -0800 Subject: [PATCH 12/13] add default initializations for wasm-specific codegen state --- src/coreclr/jit/codegen.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 358b75d1c46062..9e9d6921c11200 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -213,8 +213,8 @@ class CodeGen final : public CodeGenInterface void genCodeForBBlist(); #if defined(TARGET_WASM) - ArrayStack* wasmControlFlowStack; - unsigned wasmCursor; + ArrayStack* wasmControlFlowStack = nullptr; + unsigned wasmCursor = 0; unsigned findTargetDepth(BasicBlock* target); #endif From 0177a83bb8ea6bb03a58abccca7eede04e30323c Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 3 Dec 2025 15:36:25 -0800 Subject: [PATCH 13/13] post-merge fix --- src/coreclr/jit/codegenwasm.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 1321a92aca2580..ae9dac8e0a3c55 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -244,6 +244,7 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) case GT_GE: case GT_GT: genCodeForCompare(treeNode->AsOp()); + break; case GT_LCL_VAR: genCodeForLclVar(treeNode->AsLclVar());