diff --git a/src/coreclr/jit/CMakeLists.txt b/src/coreclr/jit/CMakeLists.txt index a3c8052c85187c..c84224eb2136da 100644 --- a/src/coreclr/jit/CMakeLists.txt +++ b/src/coreclr/jit/CMakeLists.txt @@ -162,6 +162,7 @@ set( JIT_SOURCES rangecheckcloning.cpp rationalize.cpp redundantbranchopts.cpp + regalloc.cpp regset.cpp scev.cpp scopeinfo.cpp @@ -182,7 +183,6 @@ set( JIT_SOURCES set ( JIT_NATIVE_TARGET_SOURCES lsra.cpp lsrabuild.cpp - regalloc.cpp regMaskTPOps.cpp gcdecode.cpp gcencode.cpp @@ -385,6 +385,7 @@ set( JIT_HEADERS rangecheckcloning.h rationalize.h regalloc.h + regallocimpl.h register.h regset.h scev.h diff --git a/src/coreclr/jit/codegeninterface.h b/src/coreclr/jit/codegeninterface.h index 39b963dc3d674e..a7b1ac8836dedf 100644 --- a/src/coreclr/jit/codegeninterface.h +++ b/src/coreclr/jit/codegeninterface.h @@ -254,6 +254,42 @@ class CodeGenInterface m_cgFrameRequired = value; } +#if !HAS_FIXED_REGISTER_SET +private: + // For targets without a fixed SP/FP, these are the registers with which they are associated. + PhasedVar m_cgStackPointerReg = REG_NA; + PhasedVar m_cgFramePointerReg = REG_NA; + +public: + void SetStackPointerReg(regNumber reg) + { + assert(reg != REG_NA); + m_cgStackPointerReg = reg; + } + void SetFramePointerReg(regNumber reg) + { + assert(reg != REG_NA); + m_cgFramePointerReg = reg; + } + regNumber GetStackPointerReg() const + { + return m_cgStackPointerReg; + } + regNumber GetFramePointerReg() const + { + return m_cgFramePointerReg; + } +#else // HAS_FIXED_REGISTER_SET + regNumber GetStackPointerReg() const + { + return REG_SPBASE; + } + regNumber GetFramePointerReg() const + { + return REG_FPBASE; + } +#endif // HAS_FIXED_REGISTER_SET + public: int genCallerSPtoFPdelta() const; int genCallerSPtoInitialSPdelta() const; diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 7359ac78bb006f..ef1e4ecfb8766f 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -9,6 +9,14 @@ #include "codegen.h" #include "fgwasm.h" +#ifdef TARGET_64BIT +static const instruction INS_I_const = INS_i64_const; +static const instruction INS_I_add = INS_i64_add; +#else // !TARGET_64BIT +static const instruction INS_I_const = INS_i32_const; +static const instruction INS_I_add = INS_i32_add; +#endif // !TARGET_64BIT + void CodeGen::genMarkLabelsForCodegen() { // No work needed here for now. @@ -268,6 +276,14 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForCompare(treeNode->AsOp()); break; + case GT_LCL_ADDR: + genCodeForLclAddr(treeNode->AsLclFld()); + break; + + case GT_LCL_FLD: + genCodeForLclFld(treeNode->AsLclFld()); + break; + case GT_LCL_VAR: genCodeForLclVar(treeNode->AsLclVar()); break; @@ -741,6 +757,38 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree) genProduceReg(tree); } +//------------------------------------------------------------------------ +// genCodeForLclAddr: Generates the code for GT_LCL_ADDR. +// +// Arguments: +// lclAddrNode - the node. +// +void CodeGen::genCodeForLclAddr(GenTreeLclFld* lclAddrNode) +{ + assert(lclAddrNode->OperIs(GT_LCL_ADDR)); + + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(GetFramePointerReg())); + GetEmitter()->emitIns_S(INS_I_const, EA_PTRSIZE, lclAddrNode->GetLclNum(), lclAddrNode->GetLclOffs()); + GetEmitter()->emitIns(INS_I_add); + genProduceReg(lclAddrNode); +} + +//------------------------------------------------------------------------ +// genCodeForLclFld: Produce code for a GT_LCL_FLD node. +// +// Arguments: +// tree - the GT_LCL_FLD node +// +void CodeGen::genCodeForLclFld(GenTreeLclFld* tree) +{ + assert(tree->OperIs(GT_LCL_FLD)); + LclVarDsc* varDsc = compiler->lvaGetDesc(tree); + + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(GetFramePointerReg())); + GetEmitter()->emitIns_S(ins_Load(tree->TypeGet()), emitTypeSize(tree), tree->GetLclNum(), tree->GetLclOffs()); + genProduceReg(tree); +} + //------------------------------------------------------------------------ // genCodeForLclVar: Produce code for a GT_LCL_VAR node. // @@ -759,14 +807,14 @@ void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) if (!varDsc->lvIsRegCandidate()) { var_types type = varDsc->GetRegisterType(tree); - // TODO-WASM: actually local.get the frame base local here. + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(GetFramePointerReg())); GetEmitter()->emitIns_S(ins_Load(type), emitTypeSize(tree), tree->GetLclNum(), 0); genProduceReg(tree); } else { assert(genIsValidReg(varDsc->GetRegNum())); - unsigned wasmLclIndex = UnpackWasmReg(varDsc->GetRegNum()); + unsigned wasmLclIndex = WasmRegToIndex(varDsc->GetRegNum()); GetEmitter()->emitIns_I(INS_local_get, emitTypeSize(tree), wasmLclIndex); } } @@ -785,23 +833,15 @@ void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* tree) assert(!op1->IsMultiRegNode()); genConsumeRegs(op1); + // We rewrite all stack stores to STOREIND because the address must be first on the operand stack, so here only + // enregistered locals need to be handled. LclVarDsc* varDsc = compiler->lvaGetDesc(tree); regNumber targetReg = varDsc->GetRegNum(); + assert(genIsValidReg(targetReg) && varDsc->lvIsRegCandidate()); - if (!varDsc->lvIsRegCandidate()) - { - // TODO-WASM: handle these cases in lower/ra. - // Emit drop for now to simulate the store effect on the wasm stack. - GetEmitter()->emitIns(INS_drop); - genUpdateLife(tree); - } - else - { - assert(genIsValidReg(targetReg)); - unsigned wasmLclIndex = UnpackWasmReg(targetReg); - GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(tree), wasmLclIndex); - genUpdateLifeStore(tree, targetReg, varDsc); - } + unsigned wasmLclIndex = WasmRegToIndex(targetReg); + GetEmitter()->emitIns_I(INS_local_set, emitTypeSize(tree), wasmLclIndex); + genUpdateLifeStore(tree, targetReg, varDsc); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index 243edde19268c5..da105a66e99ccb 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -73,7 +73,7 @@ void emitter::emitIns_J(instruction ins, emitAttr attr, cnsval_ssize_t imm, Basi } //------------------------------------------------------------------------ -// emitIns_S: Emit a memory instruction with a stack-based address mode operand. +// emitIns_S: Emit an instruction with a stack offset immediate. // void emitter::emitIns_S(instruction ins, emitAttr attr, int varx, int offs) { @@ -498,8 +498,7 @@ void emitter::emitDispIns( case IF_MEMARG: { - // TODO-WASM: decide what our strategy for alignment hints is and display these accordingly. - unsigned log2align = emitGetAlignHintLog2(id) + 1; + unsigned log2align = emitGetAlignHintLog2(id); cnsval_ssize_t offset = emitGetInsSC(id); printf(" %u %llu", log2align, (uint64_t)offset); } diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 19d21482f219c2..c5179723a6700d 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -4609,7 +4609,14 @@ void Compiler::lvaFixVirtualFrameOffsets() JITDUMP("--- delta bump %d for FP frame\n", delta); } -#endif // !TARGET_LOONGARCH64 || !TARGET_RISCV64 +#elif defined(TARGET_WASM) + else + { + // The FP always points at the bottom of the fixed portion of the frame. + JITDUMP("--- delta bump %d for FP frame\n", codeGen->genTotalFrameSize()); + delta += codeGen->genTotalFrameSize(); + } +#endif if (opts.IsOSR()) { @@ -6260,7 +6267,7 @@ void Compiler::lvaDumpFrameLocation(unsigned lclNum, int minLength) #else bool EBPbased; offset = lvaFrameAddress(lclNum, &EBPbased); - baseReg = EBPbased ? REG_FPBASE : REG_SPBASE; + baseReg = EBPbased ? codeGen->GetFramePointerReg() : codeGen->GetStackPointerReg(); #endif int printed = diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index c47d3ec3c7de72..c8468dd114a70f 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -17,14 +17,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "compiler.h" #include "phase.h" #include "sideeffects.h" - -#if HAS_FIXED_REGISTER_SET -#include "lsra.h" -#endif - -#ifdef TARGET_WASM -#include "regallocwasm.h" -#endif +#include "regallocimpl.h" class Lowering final : public Phase { diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 2a95c2642e9f72..61b2526e4a1c84 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -1516,6 +1516,19 @@ void Interval::setLocalNumber(Compiler* compiler, unsigned lclNum, LinearScan* l this->varNum = lclNum; } +//------------------------------------------------------------------------ +// GetCompiler: Get the compiler field. +// +// Bridges the field naming difference for common RA code. +// +// Return Value: +// The 'this->compiler' field. +// +Compiler* LinearScan::GetCompiler() const +{ + return compiler; +} + //------------------------------------------------------------------------ // LinearScan:identifyCandidatesExceptionDataflow: Build the set of variables exposed on EH flow edges // @@ -1575,148 +1588,6 @@ void LinearScan::identifyCandidatesExceptionDataflow() #endif } -bool LinearScan::isRegCandidate(LclVarDsc* varDsc) -{ - if (!enregisterLocalVars) - { - return false; - } - assert(compiler->compEnregLocals()); - - if (!varDsc->lvTracked) - { - return false; - } - -#if !defined(TARGET_64BIT) - if (varDsc->lvType == TYP_LONG) - { - // Long variables should not be register candidates. - // Lowering will have split any candidate lclVars into lo/hi vars. - return false; - } -#endif // !defined(TARGET_64BIT) - - // If we have JMP, reg args must be put on the stack - - if (compiler->compJmpOpUsed && varDsc->lvIsRegArg) - { - return false; - } - - // Don't allocate registers for dependently promoted struct fields - if (compiler->lvaIsFieldOfDependentlyPromotedStruct(varDsc)) - { - return false; - } - - // Don't enregister if the ref count is zero. - if (varDsc->lvRefCnt() == 0) - { - varDsc->setLvRefCntWtd(0); - return false; - } - - // Variables that are address-exposed are never enregistered, or tracked. - // A struct may be promoted, and a struct that fits in a register may be fully enregistered. - // Pinned variables may not be tracked (a condition of the GCInfo representation) - // or enregistered, on x86 -- it is believed that we can enregister pinned (more properly, "pinning") - // references when using the general GC encoding. - unsigned lclNum = compiler->lvaGetLclNum(varDsc); - if (varDsc->IsAddressExposed() || !varDsc->IsEnregisterableType() || - (!compiler->compEnregStructLocals() && (varDsc->lvType == TYP_STRUCT))) - { -#ifdef DEBUG - DoNotEnregisterReason dner; - if (varDsc->IsAddressExposed()) - { - dner = DoNotEnregisterReason::AddrExposed; - } - else if (!varDsc->IsEnregisterableType()) - { - dner = DoNotEnregisterReason::NotRegSizeStruct; - } - else - { - dner = DoNotEnregisterReason::DontEnregStructs; - } -#endif // DEBUG - compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(dner)); - return false; - } - else if (varDsc->lvPinned) - { - varDsc->lvTracked = 0; -#ifdef JIT32_GCENCODER - compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::PinningRef)); -#endif // JIT32_GCENCODER - return false; - } - - // Are we not optimizing and we have exception handlers? - // if so mark all args and locals as volatile, so that they - // won't ever get enregistered. - // - if (compiler->opts.MinOpts() && compiler->compHndBBtabCount > 0) - { - compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::LiveInOutOfHandler)); - } - - if (varDsc->lvDoNotEnregister) - { - return false; - } - - switch (genActualType(varDsc->TypeGet())) - { - case TYP_FLOAT: - case TYP_DOUBLE: - return !compiler->opts.compDbgCode; - - case TYP_INT: - case TYP_LONG: - case TYP_REF: - case TYP_BYREF: - break; - -#ifdef FEATURE_SIMD - case TYP_SIMD8: - case TYP_SIMD12: - case TYP_SIMD16: -#if defined(TARGET_XARCH) - case TYP_SIMD32: - case TYP_SIMD64: -#endif // TARGET_XARCH -#ifdef FEATURE_MASKED_HW_INTRINSICS - case TYP_MASK: -#endif // FEATURE_MASKED_HW_INTRINSICS - { - return !varDsc->lvPromoted; - } -#endif // FEATURE_SIMD - - case TYP_STRUCT: - { - // TODO-1stClassStructs: support vars with GC pointers. The issue is that such - // vars will have `lvMustInit` set, because emitter has poor support for struct liveness, - // but if the variable is tracked the prolog generator would expect it to be in liveIn set, - // so an assert in `genFnProlog` will fire. - return compiler->compEnregStructLocals() && !varDsc->HasGCPtr(); - } - - case TYP_UNDEF: - case TYP_UNKNOWN: - noway_assert(!"lvType not set correctly"); - varDsc->lvType = TYP_INT; - return false; - - default: - return false; - } - - return true; -} - template void LinearScan::identifyCandidates(); template void LinearScan::identifyCandidates(); diff --git a/src/coreclr/jit/lsra.h b/src/coreclr/jit/lsra.h index 2ac6662068418b..b2d6d38823311f 100644 --- a/src/coreclr/jit/lsra.h +++ b/src/coreclr/jit/lsra.h @@ -988,6 +988,8 @@ class LinearScan : public RegAllocInterface bool isContainableMemoryOp(GenTree* node); private: + Compiler* GetCompiler() const; + // Determine which locals are candidates for allocation template void identifyCandidates(); diff --git a/src/coreclr/jit/regalloc.cpp b/src/coreclr/jit/regalloc.cpp index de010e01c60875..e4b1402b5578ae 100644 --- a/src/coreclr/jit/regalloc.cpp +++ b/src/coreclr/jit/regalloc.cpp @@ -6,6 +6,7 @@ #pragma hdrstop #endif #include "regalloc.h" +#include "regallocimpl.h" #if DOUBLE_ALIGN DWORD Compiler::getCanDoubleAlign() @@ -313,3 +314,150 @@ void Compiler::raMarkStkVars() #endif } } + +//------------------------------------------------------------------------ +// isRegCandidate: Determine whether a local is eligible for register allocation. +// +// Arguments: +// varDsc - The local's descriptor +// +// Return Value: +// Whether the variable represented by "varDsc" may be allocated in +// a register, or needs to live on the stack. +// +bool RegAllocImpl::isRegCandidate(LclVarDsc* varDsc) +{ + if (!willEnregisterLocalVars()) + { + return false; + } + Compiler* compiler = GetCompiler(); + assert(compiler->compEnregLocals()); + + if (!varDsc->lvTracked) + { + return false; + } + +#if !defined(TARGET_64BIT) + if (varDsc->lvType == TYP_LONG) + { + // Long variables should not be register candidates. + // Lowering will have split any candidate lclVars into lo/hi vars. + return false; + } +#endif // !defined(TARGET_64BIT) + + // If we have JMP, reg args must be put on the stack + + if (compiler->compJmpOpUsed && varDsc->lvIsRegArg) + { + return false; + } + + // Don't allocate registers for dependently promoted struct fields + if (compiler->lvaIsFieldOfDependentlyPromotedStruct(varDsc)) + { + return false; + } + + // Don't enregister if the ref count is zero. + if (varDsc->lvRefCnt() == 0) + { + varDsc->setLvRefCntWtd(0); + return false; + } + + // Variables that are address-exposed are never enregistered, or tracked. + // A struct may be promoted, and a struct that fits in a register may be fully enregistered. + // Pinned variables may not be tracked (a condition of the GCInfo representation) + // or enregistered, on x86 -- it is believed that we can enregister pinned (more properly, "pinning") + // references when using the general GC encoding. + unsigned lclNum = compiler->lvaGetLclNum(varDsc); + if (varDsc->IsAddressExposed() || !varDsc->IsEnregisterableType() || + (!compiler->compEnregStructLocals() && (varDsc->lvType == TYP_STRUCT))) + { +#ifdef DEBUG + DoNotEnregisterReason dner; + if (varDsc->IsAddressExposed()) + { + dner = DoNotEnregisterReason::AddrExposed; + } + else if (!varDsc->IsEnregisterableType()) + { + dner = DoNotEnregisterReason::NotRegSizeStruct; + } + else + { + dner = DoNotEnregisterReason::DontEnregStructs; + } +#endif // DEBUG + compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(dner)); + return false; + } + else if (varDsc->lvPinned) + { + varDsc->lvTracked = 0; +#ifdef JIT32_GCENCODER + compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::PinningRef)); +#endif // JIT32_GCENCODER + return false; + } + + // Are we not optimizing and we have exception handlers? + // if so mark all args and locals as volatile, so that they + // won't ever get enregistered. + // + if (compiler->opts.MinOpts() && compiler->compHndBBtabCount > 0) + { + compiler->lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::LiveInOutOfHandler)); + } + + if (varDsc->lvDoNotEnregister) + { + return false; + } + + switch (genActualType(varDsc->TypeGet())) + { + case TYP_FLOAT: + case TYP_DOUBLE: + return !compiler->opts.compDbgCode; + + case TYP_INT: + case TYP_LONG: + case TYP_REF: + case TYP_BYREF: + break; + +#ifdef FEATURE_SIMD + case TYP_SIMD8: + case TYP_SIMD12: + case TYP_SIMD16: +#if defined(TARGET_XARCH) + case TYP_SIMD32: + case TYP_SIMD64: +#endif // TARGET_XARCH +#ifdef FEATURE_MASKED_HW_INTRINSICS + case TYP_MASK: +#endif // FEATURE_MASKED_HW_INTRINSICS + { + return !varDsc->lvPromoted; + } +#endif // FEATURE_SIMD + + case TYP_STRUCT: + { + // TODO-1stClassStructs: support vars with GC pointers. The issue is that such + // vars will have `lvMustInit` set, because emitter has poor support for struct liveness, + // but if the variable is tracked the prolog generator would expect it to be in liveIn set, + // so an assert in `genFnProlog` will fire. + return compiler->compEnregStructLocals() && !varDsc->HasGCPtr(); + } + + default: + return false; + } + + return true; +} diff --git a/src/coreclr/jit/regallocimpl.h b/src/coreclr/jit/regallocimpl.h new file mode 100644 index 00000000000000..721c93994c9361 --- /dev/null +++ b/src/coreclr/jit/regallocimpl.h @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +// +// Include this header to access the RegAllocImpl type for the target platform. +// +#if HAS_FIXED_REGISTER_SET +#include "lsra.h" +#endif + +#ifdef TARGET_WASM +#include "regallocwasm.h" +#endif diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 8f61fa57b09d65..ad302ca2b7a96c 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -20,8 +20,11 @@ WasmRegAlloc::WasmRegAlloc(Compiler* compiler) PhaseStatus WasmRegAlloc::doRegisterAllocation() { - m_compiler->codeGen->setFramePointerUsed(false); - return PhaseStatus::MODIFIED_NOTHING; + IdentifyCandidates(); + AllocateAndResolve(); + PublishAllocationResults(); + + return PhaseStatus::MODIFIED_EVERYTHING; } void WasmRegAlloc::recordVarLocationsAtStartOfBB(BasicBlock* bb) @@ -43,14 +46,204 @@ void WasmRegAlloc::dumpLsraStatsSummary(FILE* file) } #endif // TRACK_LSRA_STATS -bool WasmRegAlloc::isRegCandidate(LclVarDsc* varDsc) +bool WasmRegAlloc::isContainableMemoryOp(GenTree* node) { - NYI_WASM("isRegCandidate"); + NYI_WASM("isContainableMemoryOp"); return false; } -bool WasmRegAlloc::isContainableMemoryOp(GenTree* node) +//------------------------------------------------------------------------ +// GetCompiler: Get the compiler field. +// +// Bridges the field naming difference for common RA code. +// +// Return Value: +// The 'this->m_compiler' field. +// +Compiler* WasmRegAlloc::GetCompiler() const { - NYI_WASM("isContainableMemoryOp"); - return false; + return m_compiler; +} + +//------------------------------------------------------------------------ +// CurrentRange: Get the LIR range under current processing. +// +// Return Value: +// The LIR range currently being processed. +// +LIR::Range& WasmRegAlloc::CurrentRange() +{ + return LIR::AsRange(m_currentBlock); +} + +//------------------------------------------------------------------------ +// IdentifyCandidates: Identify locals eligible for allocation to WASM locals. +// +// Analogous to "LinearScan::identifyCandidates()". Sets the lvLRACandidate +// bit on locals. +// +void WasmRegAlloc::IdentifyCandidates() +{ + for (unsigned lclNum = 0; lclNum < m_compiler->lvaCount; lclNum++) + { + LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum); + varDsc->SetRegNum(REG_STK); + varDsc->lvLRACandidate = isRegCandidate(varDsc); + + if (varDsc->lvLRACandidate) + { + // TODO-WASM-RA: enable register candidates. + varDsc->lvLRACandidate = false; + } + + if (varDsc->lvLRACandidate) + { + JITDUMP("RA candidate: V%02u", lclNum); + } + } +} + +//------------------------------------------------------------------------ +// AllocateAndResolve: allocate WASM locals and rewrite the IR accordingly. +// +void WasmRegAlloc::AllocateAndResolve() +{ + for (BasicBlock* block : m_compiler->Blocks()) + { + AllocateAndResolveBlock(block); + } +} + +//------------------------------------------------------------------------ +// AllocateAndResolveBlock: allocate WASM locals and rewrite the IR for one block. +// +// Arguments: +// block - The block +// +void WasmRegAlloc::AllocateAndResolveBlock(BasicBlock* block) +{ + m_currentBlock = block; + for (GenTree* node : LIR::AsRange(block)) + { + AllocateAndResolveNode(node); + } + m_currentBlock = nullptr; +} + +//------------------------------------------------------------------------ +// AllocateAndResolveNode: allocate WASM locals and rewrite the IR for one node. +// +// Arguments: +// node - The IR node +// +void WasmRegAlloc::AllocateAndResolveNode(GenTree* node) +{ + if (node->OperIsAnyLocal()) + { + LclVarDsc* varDsc = m_compiler->lvaGetDesc(node->AsLclVarCommon()); + if (!varDsc->lvIsRegCandidate()) + { + if (node->OperIsLocalStore()) + { + RewriteLocalStackStore(node->AsLclVarCommon()); + } + + if (m_fpReg == REG_NA) + { + m_fpReg = AllocateFreeRegister(TYP_I_IMPL); + } + } + } + else if (node->OperIs(GT_LCLHEAP)) + { + if (m_spReg == REG_NA) + { + m_spReg = AllocateFreeRegister(TYP_I_IMPL); + } + } +} + +//------------------------------------------------------------------------ +// RewriteLocalStackStore: rewrite a store to the stack to STOREIND(LCL_ADDR, ...). +// +// This is needed to obey WASM stack ordering constraints: as in IR, the +// address operands comes first and we can't insert that into the middle +// of an already generated instruction stream at codegen time. +// +// Arguments: +// lclNode - The local store node +// +void WasmRegAlloc::RewriteLocalStackStore(GenTreeLclVarCommon* lclNode) +{ + // At this point, the IR is already stackified, so we just need to find the first node in the dataflow. + // TODO-WASM-TP: this is nice and simple, but can we do this more efficiently? + GenTree* op = lclNode->Data(); + GenTree::VisitResult visitResult; + do + { + visitResult = op->VisitOperands([&op](GenTree* operand) { + op = operand; + return GenTree::VisitResult::Abort; + }); + } while (visitResult == GenTree::VisitResult::Abort); + + GenTree* store; + GenTreeFlags indFlags = GTF_IND_NONFAULTING | GTF_IND_TGT_NOT_HEAP; + if (lclNode->TypeIs(TYP_STRUCT)) + { + store = m_compiler->gtNewStoreBlkNode(lclNode->GetLayout(m_compiler), lclNode, lclNode->Data(), indFlags); + } + else + { + store = m_compiler->gtNewStoreIndNode(lclNode->TypeGet(), lclNode, lclNode->Data(), indFlags); + } + CurrentRange().InsertAfter(lclNode, store); + CurrentRange().Remove(lclNode); + + // TODO-WASM-RA: figure out the address mode story here. Right now this will produce an address not folded + // into the store's address mode. We can utilize a contained LEA, but that will require some liveness work. + lclNode->SetOper(GT_LCL_ADDR); + lclNode->ChangeType(TYP_I_IMPL); + CurrentRange().InsertBefore(op, lclNode); +} + +//------------------------------------------------------------------------ +// AllocateFreeRegister: Allocate a register from the (currently) free set. +// +// Arguments: +// type - The register's type +// +regNumber WasmRegAlloc::AllocateFreeRegister(var_types type) +{ + // TODO-WASM-RA: implement. + return MakeWasmReg(0, type); +} + +//------------------------------------------------------------------------ +// PublishAllocationResults: Publish the results of register allocation. +// +// Initializes various fields denoting allocation outcomes on the codegen object. +// +void WasmRegAlloc::PublishAllocationResults() +{ + CodeGenInterface* codeGen = m_compiler->codeGen; + if (m_spReg != REG_NA) + { + codeGen->SetStackPointerReg(m_spReg); + } + else if (m_fpReg != REG_NA) + { + codeGen->SetStackPointerReg(m_fpReg); + } + if (m_fpReg != REG_NA) + { + codeGen->SetFramePointerReg(m_fpReg); + codeGen->setFramePointerUsed(true); + } + else + { + codeGen->setFramePointerUsed(false); + } + + m_compiler->raMarkStkVars(); } diff --git a/src/coreclr/jit/regallocwasm.h b/src/coreclr/jit/regallocwasm.h index e78b29f1a96632..a99c24316158b5 100644 --- a/src/coreclr/jit/regallocwasm.h +++ b/src/coreclr/jit/regallocwasm.h @@ -5,7 +5,14 @@ class WasmRegAlloc : public RegAllocInterface { - Compiler* m_compiler; + Compiler* m_compiler; + BasicBlock* m_currentBlock; + + // The meaning of these fields is borrowed (partially) from the C ABI for WASM. We define "the SP" to be the local + // which is used to make calls - the stack on entry to callees. We term "the FP" to be the local which is used to + // access the fixed potion of the frame. For fixed-size frames (no localloc), these will be the same. + regNumber m_spReg = REG_NA; + regNumber m_fpReg = REG_NA; public: WasmRegAlloc(Compiler* compiler); @@ -20,6 +27,21 @@ class WasmRegAlloc : public RegAllocInterface bool isRegCandidate(LclVarDsc* varDsc); bool isContainableMemoryOp(GenTree* node); + +private: + Compiler* GetCompiler() const; + LIR::Range& CurrentRange(); + + void IdentifyCandidates(); + + void AllocateAndResolve(); + void AllocateAndResolveBlock(BasicBlock* block); + void AllocateAndResolveNode(GenTree* node); + void RewriteLocalStackStore(GenTreeLclVarCommon* node); + + regNumber AllocateFreeRegister(var_types type); + + void PublishAllocationResults(); }; using RegAllocImpl = WasmRegAlloc;