diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 6b2ca6af8484d4..f9b745f87b2910 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -426,6 +426,10 @@ class CodeGen final : public CodeGenInterface void genPushCalleeSavedRegisters(regNumber initReg, bool* pInitRegZeroed); +#if defined(TARGET_ARM64) + void genUnknownSizeFrame(); +#endif + #elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64) bool genInstrWithConstant(instruction ins, emitAttr attr, diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index fcef600921e254..f797e743dbadb2 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -4876,9 +4876,47 @@ void CodeGen::genPushCalleeSavedRegisters() m_compiler->compFrameInfo.calleeSaveSpOffset = calleeSaveSpOffset; m_compiler->compFrameInfo.calleeSaveSpDelta = calleeSaveSpDelta; m_compiler->compFrameInfo.offsetSpToSavedFp = offsetSpToSavedFp; + + if (m_compiler->compUsesUnknownSizeFrame) + { + genUnknownSizeFrame(); + } #endif // TARGET_ARM64 } +#if defined(TARGET_ARM64) +// See Compiler::UnknownSizeFrame for implementation details. +void CodeGen::genUnknownSizeFrame() +{ + assert(m_compiler->compLocallocUsed && m_compiler->compUsesUnknownSizeFrame); + assert(m_compiler->unkSizeFrame.isFinalized); + unsigned totalVectorCount = m_compiler->unkSizeFrame.FrameSizeInVectors(); + + // We reserve REG_UNKBASE for addressing SVE locals. This will always point at the top of + // of the UnknownSizeFrame and we index into it. + // TODO-SVE: We may want this to point into the middle of the frame to reduce address + // computations (we have a signed 9-bit indexing immediate). + inst_Mov(TYP_I_IMPL, REG_UNKBASE, REG_SP, false); + + if (0 < totalVectorCount && totalVectorCount <= 32) + { + GetEmitter()->emitIns_R_R_I(INS_sve_addvl, EA_8BYTE, REG_SP, REG_SP, -(ssize_t)totalVectorCount); + } + else + { + // Generate `sp = sp - totalVectorCount * VL` + assert(totalVectorCount != 0); + regNumber rsvd = rsGetRsvdReg(); + // mov rsvd, #totalVectorCount + // rdvl scratch, #1 + // msub sp, rsvd, scratch, sp + instGen_Set_Reg_To_Imm(EA_8BYTE, rsvd, totalVectorCount); + GetEmitter()->emitIns_R_I(INS_sve_rdvl, EA_8BYTE, REG_SCRATCH, 1); + GetEmitter()->emitIns_R_R_R_R(INS_msub, EA_8BYTE, REG_SP, rsvd, REG_SCRATCH, REG_SP); + } +} +#endif + /***************************************************************************** * * Generates code for a function epilog. diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 058316551fb377..2e88b7241dcd4f 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -3644,6 +3644,11 @@ void CodeGen::genCheckUseBlockInit() continue; } + if (m_compiler->lvaIsUnknownSizeLocal(varNum)) + { + continue; + } + if (m_compiler->fgVarIsNeverZeroInitializedInProlog(varNum)) { varDsc->lvMustInit = 0; @@ -4001,6 +4006,12 @@ void CodeGen::genZeroInitFrame(int untrLclHi, int untrLclLo, regNumber initReg, noway_assert(varDsc->lvOnFrame); + if (m_compiler->lvaIsUnknownSizeLocal(varNum)) + { + // This local will belong on the UnknownSizeFrame, which will handle zeroing instead. + continue; + } + // lvMustInit can only be set for GC types or TYP_STRUCT types // or when compInitMem is true // or when in debug code @@ -5067,6 +5078,11 @@ void CodeGen::genFnProlog() continue; } + if (m_compiler->lvaIsUnknownSizeLocal(varNum)) + { + continue; + } + signed int loOffs = varDsc->GetStackOffset(); signed int hiOffs = varDsc->GetStackOffset() + m_compiler->lvaLclStackHomeSize(varNum); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index cb1137a8b4d0c5..605475b159e9d6 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4124,6 +4124,176 @@ class Compiler int lvaOSRLocalTier0FrameOffset(unsigned varNum); + //------------------------- UnknownSizeFrame --------------------------------- + + void lvaInitUnknownSizeFrame(); + void lvaAllocUnknownSizeLocal(unsigned varNum); + + bool compUsesUnknownSizeFrame; + +#if defined(FEATURE_SIMD) && defined(TARGET_ARM64) + // For ARM64, the UnknownSizeFrame lives at the end of the statically + // allocated stack space. This means it belongs to the 'alloca' space on the + // frame, and it is essentially the first dynamically allocated stack + // variable. + // + // Currently, the only locals with unknown size are SIMD types supporting + // Vector, TYP_SIMD and TYP_MASK. We do not know the size of these types + // at compile time, so we need to execute the rdvl/addvl instruction to + // learn this size and allocate the UnknownSizeFrame. + // + // We reserve the x19 register to point to the top of the UnknownSizeFrame + // and use this as the base address for local variables with unknown size. + // Reserving a register is simpler than using fp/sp, as fp may point + // to different locations depending on various properties of the frame, and + // the value of sp may change at runtime. + // + // Typically, a vector is loaded using a base address and some index which + // the instruction will scale by VL, for example: `ldr z0, [x19, #3 MUL VL]`. + // A mask is loaded with `ldr p0, [x19, #3 MUL VL]`, but in this case the + // `MUL VL` indicates we are scaling with the length of the predicate + // register rather than the vector. A predicate register is defined to have + // 1/8th the length of a vector register. + // + // We know that sizeof(TYP_SIMD) and sizeof(TYP_MASK) are invariant despite + // being unknown at compile time, so we allocate them in single homogeneous + // blocks per type. An individual local can be referenced from the start of + // its block by an index into the block. + // + // The difference in addressing-mode index scaling means we have to be + // careful where we place the mask locals block with respect to the vector + // locals block. If we place the mask locals after the vector locals, we'll + // need to offset the load index by (8 * nVector) to account for the vector + // locals. + // + // Instead, we choose to pad the mask locals block to VL and place it at the + // beginning of the frame (closest to fp). This way we'll need to offset + // vector load indices by `roundUp(nMask, 8) / 8`. This is less likely to + // put pressure on the immediate encoding range and result in requiring an + // address computation. + // + // The maximum wasted space from the padding is 7/8ths VL (224 bytes with + // the architectural maximum 256 byte vectors), which occurs when 1 mask + // local is spilled to the frame. Alternatively this is 28 bytes for 32 byte + // vectors, for an example closer to today's implementations. + // + // The padding also makes it simple to allocate the UnknownSizeFrame since + // the UnknownSizeFrame will be aligned to VL. The total number of vectors + // to allocate is `(roundUp(nMask, 8) / 8) + nVector`. The stack pointer + // can be adjusted with a single instruction `addvl sp, sp, #totalVectors`. + // + // See the diagram below for a visual representation of this scheme. + // + // ... + // | static space | + // | (totalFrameSize) | + // +----------------------------------+ x19, begin UnknownSizeFrame + // | mask locals block | ^ + // | (nMask * VL/8) | | + // +----------------------------------+ | + // | padding to VL alignment | | + // +----------------------------------+ (roundUp(nMask, 8)/8 + nVector)*VL + // | | | + // | vector locals block | | + // | (nVector * VL) | | + // | | v + // +----------------------------------+ end UnknownSizeFrame + // | | + // | rest of alloca space | + // ... sp + struct UnknownSizeFrame + { + // Number of allocated vectors/masks. These also represent the end of + // the allocation space for each block. The allocator for each block is + // a simple bump allocator. + unsigned nVector = 0; + unsigned nMask = 0; + +#ifdef DEBUG + bool isFinalized = false; +#endif + + // Returns the size of the mask block in number of vector lengths. + unsigned MaskBlockSizeInVectors() + { + assert(roundUp(0U, 8U) == 0); + return roundUp(nMask, 8) / 8; + } + + // Returns the size of the vector block in number of vector lengths. + unsigned VectorBlockSize() + { + return nVector; + } + + // Returns the size of the total UnknownSizeFrame in number of vector + // lengths. + unsigned FrameSizeInVectors() + { + return MaskBlockSizeInVectors() + VectorBlockSize(); + } + + // Allocate a mask, returning an index of the mask in the mask block. + unsigned AllocMask() + { + assert(!isFinalized); + unsigned idx = nMask; + nMask++; + return idx; + } + + // Allocate a vector, returning an index of the vector in the vector + // block. + unsigned AllocVector() + { + assert(!isFinalized); + unsigned idx = nVector; + nVector++; + return idx; + } + + // Returns a negative offset relative to the base of the UnknownSizeFrame + // for addressing an allocated vector or mask local. + // If `isMask == true`, given an index that was assigned to mask local, + // the returned offset is an index measured in units of VL/8. + // Otherwise given an index that was assigned to a vector local, the + // returned offset is measured in units of VL. + // The index parameter should have been obtained through AllocMask() or + // AllocVector(). + int GetOffset(unsigned index, bool isMask = false) + { + // We can't compute addresses if we haven't finished allocating. + assert(isFinalized); + + unsigned offset = UINT32_MAX; + if (isMask) + { + assert(index < nMask); + offset = index; + } + else + { + assert(index < nVector); + offset = MaskBlockSizeInVectors() + index; + } + assert(offset != UINT32_MAX); + // The index is always offset by 1 as we are writing from below fp + // upwards. + return -(int)(offset + 1); + } + + // This system ensures we don't try and generate an address on the frame + // without finishing all allocations. + void Finalize() + { +#ifdef DEBUG + isFinalized = true; +#endif + } + + } unkSizeFrame; +#endif + //------------------------ For splitting types ---------------------------- void lvaInitTypeRef(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 4484a8ae95075c..ec5e456888552e 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -2744,7 +2744,7 @@ inline #endif // !TARGET_AMD64 } - FPbased = varDsc->lvFramePointerBased; + FPbased = varDsc->lvFramePointerBased && !lvaIsUnknownSizeLocal(varNum); #ifdef DEBUG #if FEATURE_FIXED_OUT_ARGS @@ -2765,7 +2765,17 @@ inline } #endif // DEBUG - varOffset = varDsc->GetStackOffset(); +#ifdef TARGET_ARM64 + if (lvaIsUnknownSizeLocal(varNum) && !varDsc->lvIsStructField) + { + assert(!FPbased); + varOffset = unkSizeFrame.GetOffset(varDsc->GetStackOffset(), varDsc->TypeIs(TYP_MASK)); + } + else +#endif + { + varOffset = varDsc->GetStackOffset(); + } } else // Its a spill-temp { diff --git a/src/coreclr/jit/emitarm64.cpp b/src/coreclr/jit/emitarm64.cpp index 2dd60855390f0f..acc5ed21606ba9 100644 --- a/src/coreclr/jit/emitarm64.cpp +++ b/src/coreclr/jit/emitarm64.cpp @@ -8119,6 +8119,17 @@ void emitter::emitIns_R_S(instruction ins, emitAttr attr, regNumber reg1, int va isSimple = false; scale = 0; + if (varx >= 0 && m_compiler->lvaIsUnknownSizeLocal(varx)) + { + assert(offs == 0); + assert(!FPbased); + // We shouldn't be materializing the address of a mask. + assert(m_compiler->lvaGetActualType(varx) != TYP_MASK); + // addvl reg1, x19, #imm + emitIns_R_R_I(INS_sve_addvl, EA_8BYTE, reg1, REG_UNKBASE, imm); + return; + } + if (disp >= 0) { ins = INS_add; @@ -8153,44 +8164,35 @@ void emitter::emitIns_R_S(instruction ins, emitAttr attr, regNumber reg1, int va case INS_sve_ldr: { + assert(isPredicateRegister(reg1) || isVectorRegister(reg1)); + isSimple = false; size = EA_SCALABLE; attr = size; - if (isPredicateRegister(reg1)) - { - assert(offs == 0); - // For predicate, generate based off rsGetRsvdReg() - regNumber rsvdReg = codeGen->rsGetRsvdReg(); + fmt = isPredicateRegister(reg1) ? IF_SVE_ID_2A : IF_SVE_IE_2A; - // add rsvd, fp, #imm - emitIns_R_R_Imm(INS_add, EA_8BYTE, rsvdReg, encodingZRtoSP(reg2), imm); - // str p0, [rsvd, #0, mul vl] - emitIns_R_R_I(ins, attr, reg1, rsvdReg, 0); - - return; - } - - assert(isVectorRegister(reg1)); - fmt = IF_SVE_IE_2A; - - // TODO-SVE: Don't assume 128bit vectors - // Predicate size is vector length / 8 - scale = NaturalScale_helper(isVectorRegister(reg1) ? EA_16BYTE : EA_2BYTE); - ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate - - if (((imm & mask) == 0) && (isValidSimm<9>(imm >> scale))) - { - imm >>= scale; // The immediate is scaled by the size of the ld/st - } - else + if (FPbased) { + // This is loading a field of a struct on the stack. The immediate will be an absolute + // offset to the field, not scaled by VL. + reg2 = REG_FP; + useRegForImm = true; regNumber rsvdReg = codeGen->rsGetRsvdReg(); - // For larger imm values (> 9 bits), calculate base + imm in a reserved register first. codeGen->instGen_Set_Reg_To_Base_Plus_Imm(EA_PTRSIZE, rsvdReg, reg2, imm); + reg2 = rsvdReg; imm = 0; } + else + { + // SVE locals are TYP_SIMD or TYP_MASK, both should be placed on the UnknownSizeFrame. + // The base address of these locals should be REG_UNKBASE (x19). + assert(offs == 0); + // TODO-SVE: Handle generation of base address for large immediate scaled by VL/PL. + assert(isValidSimm<9>(imm)); + reg2 = REG_UNKBASE; + } } break; @@ -8424,46 +8426,34 @@ void emitter::emitIns_S_R(instruction ins, emitAttr attr, regNumber reg1, int va case INS_sve_str: { + assert(isVectorRegister(reg1) || isPredicateRegister(reg1)); isSimple = false; size = EA_SCALABLE; attr = size; + fmt = isPredicateRegister(reg1) ? IF_SVE_JG_2A : IF_SVE_JH_2A; - if (isPredicateRegister(reg1)) + if (FPbased) { - assert(offs == 0); - - // For predicate, generate based off rsGetRsvdReg() - regNumber rsvdReg = codeGen->rsGetRsvdReg(); - - // add rsvd, fp, #imm - emitIns_R_R_Imm(INS_add, EA_8BYTE, rsvdReg, encodingZRtoSP(reg2), imm); - // str p0, [rsvd, #0, mul vl] - emitIns_R_R_I(ins, attr, reg1, rsvdReg, 0); - - return; - } - - assert(isVectorRegister(reg1)); - fmt = IF_SVE_JH_2A; + // This is storing to a field of a struct on the stack. The immediate will be an absolute + // offset to the field, not scaled by VL. + reg2 = REG_FP; - // TODO-SVE: Don't assume 128bit vectors - // Predicate size is vector length / 8 - scale = NaturalScale_helper(isVectorRegister(reg1) ? EA_16BYTE : EA_2BYTE); - ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate - - if (((imm & mask) == 0) && (isValidSimm<9>(imm >> scale))) - { - imm >>= scale; // The immediate is scaled by the size of the ld/st - } - else - { useRegForImm = true; regNumber rsvdReg = codeGen->rsGetRsvdReg(); - // For larger imm values (> 9 bits), calculate base + imm in a reserved register first. codeGen->instGen_Set_Reg_To_Base_Plus_Imm(EA_PTRSIZE, rsvdReg, reg2, imm); + reg2 = rsvdReg; imm = 0; } + else + { + // SVE locals are TYP_SIMD or TYP_MASK, both should be placed on the UnknownSizeFrame. + // The base address of these locals should be REG_UNKBASE (x19). + assert(offs == 0); + // TODO-SVE: Handle generation of base address for large immediate scaled by VL/PL. + assert(isValidSimm<9>(imm)); + reg2 = REG_UNKBASE; + } } break; diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index acd4be1edf083e..b8f93def3bf5f1 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -4231,6 +4231,15 @@ void Compiler::lvaAssignFrameOffsets(FrameLayoutState curState) assert(lvaOutgoingArgSpaceVar != BAD_VAR_NUM); #endif // FEATURE_FIXED_OUT_ARGS + /*------------------------------------------------------------------------- + * + * Initialize tracking information for locals with unknown size. + * + *------------------------------------------------------------------------- + */ + + lvaInitUnknownSizeFrame(); + /*------------------------------------------------------------------------- * * First process the arguments. @@ -4277,6 +4286,13 @@ void Compiler::lvaAssignFrameOffsets(FrameLayoutState curState) { codeGen->resetFramePointerUsedWritePhase(); } +#if defined(FEATURE_SIMD) && defined(TARGET_ARM64) + else + { + assert(curState == FINAL_FRAME_LAYOUT); + unkSizeFrame.Finalize(); + } +#endif } /***************************************************************************** @@ -4404,6 +4420,11 @@ void Compiler::lvaFixVirtualFrameOffsets() // Can't be relative to EBP unless we have an EBP noway_assert(!varDsc->lvFramePointerBased || codeGen->doubleAlignOrFramePointerUsed()); + if (lvaIsUnknownSizeLocal(lclNum)) + { + continue; + } + // Is this a non-param promoted struct field? // if so then set doAssignStkOffs to false. // @@ -4599,6 +4620,8 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() int startOffset; if (lvaGetRelativeOffsetToCallerAllocatedSpaceForParameter(lclNum, &startOffset)) { + assert(!lvaIsUnknownSizeLocal(lclNum)); + dsc->SetStackOffset(startOffset + relativeZero); JITDUMP("Set V%02u to offset %d\n", lclNum, startOffset); @@ -5209,6 +5232,12 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() continue; } + else if (lvaIsUnknownSizeLocal(lclNum)) + { + // Reserve dynamic stack space for this variable. + lvaAllocUnknownSizeLocal(lclNum); + continue; + } // These need to be located as the very first variables (highest memory address) // and so they have already been assigned an offset @@ -5517,6 +5546,58 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals() #endif // TARGET_ARM64 } +void Compiler::lvaInitUnknownSizeFrame() +{ +#if defined(FEATURE_SIMD) && defined(TARGET_ARM64) + compUsesUnknownSizeFrame = false; +#ifdef DEBUG + unkSizeFrame.isFinalized = false; +#endif + unkSizeFrame.nMask = 0; + unkSizeFrame.nVector = 0; +#endif +} + +//------------------------------------------------------------------------------- +// lvaAllocUnknownSizeLocal: Allocate stack space for a local with unknown size +// +// A local with unknown size has a size that is not precisely known at compile time, +// but may be derived dynamically through code. These locals are allocated into +// their own stack space categorized by JIT type. +// +// Ideally, locals are primitive types that can fit into a homogeneous space containing +// objects with the same unknown size. In this case, we can identify them by a simple +// index into the space. +void Compiler::lvaAllocUnknownSizeLocal(unsigned varNum) +{ + LclVarDsc* const varDsc = lvaGetDesc(varNum); + assert(varTypeHasUnknownSize(varDsc)); + +#if defined(FEATURE_SIMD) && defined(TARGET_ARM64) + if (varDsc->TypeIs(TYP_SIMD)) + { + varDsc->SetStackOffset((int)unkSizeFrame.AllocVector()); + } + else if (varDsc->TypeIs(TYP_MASK)) + { + varDsc->SetStackOffset((int)unkSizeFrame.AllocMask()); + } + else +#endif + { + // The only types with unknown size should be SIMD at the moment. + unreached(); + } + + compUsesUnknownSizeFrame = true; + + // Technically we're not using localalloc, but the space these locals use + // will be at the beginning of the alloca space on the stack frame. So we + // should set this and inherit all of its behaviour, e.g. guarantee we get + // a frame pointer. + compLocallocUsed = true; +} + //------------------------------------------------------------------------ // lvaParamHasLocalStackSpace: Check if a local that represents a parameter has // space allocated for it in the local stack frame. diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 734e0e67335d85..ad0fc7e4f0f064 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -2585,6 +2585,16 @@ void LinearScan::setFrameType() } #endif // TARGET_ARM +#if defined(TARGET_ARM64) + if (m_compiler->compUsesUnknownSizeFrame) + { + // We reserve x19 for addressing vector and mask locals on the UnknownSizeFrame. + m_compiler->codeGen->regSet.rsMaskResvd |= RBM_UNKBASE; + JITDUMP(" Reserved REG_UNKBASE (%s) due to presence of UnknownSizeFrame\n", getRegName(REG_UNKBASE)); + removeMask |= RBM_UNKBASE.GetIntRegSet(); + } +#endif + if ((removeMask != RBM_NONE) && ((availableIntRegs & removeMask) != 0)) { // We know that we're already in "read mode" for availableIntRegs. However, diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 79ddcc5b1c732e..30493867fbb133 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -2466,6 +2466,14 @@ void LinearScan::buildIntervals() currentLoc += 2; } +#ifdef TARGET_ARM64 + if (m_compiler->compUsesUnknownSizeFrame) + { + addKillForRegs(RBM_UNKBASE, currentLoc + 1); + currentLoc += 2; + } +#endif + // For frame poisoning we generate code into scratch BB right after prolog since // otherwise the prolog might become too large. In this case we will put the poison immediate // into the scratch register, so it will be killed here. diff --git a/src/coreclr/jit/regset.cpp b/src/coreclr/jit/regset.cpp index 61933141334969..58434e6a7912f5 100644 --- a/src/coreclr/jit/regset.cpp +++ b/src/coreclr/jit/regset.cpp @@ -626,7 +626,7 @@ TempDsc* RegSet::tmpGetTemp(var_types type) unsigned size = genTypeSize(type); // If TYP_STRUCT ever gets in here we do bad things (tmpSlot returns -1) - noway_assert(size >= sizeof(int)); + noway_assert(size >= sizeof(int) && size != SIZE_UNKNOWN); /* Find the slot to search for a free temp of the right size */ @@ -688,7 +688,7 @@ void RegSet::tmpPreAllocateTemps(var_types type, unsigned count) unsigned size = genTypeSize(type); // If TYP_STRUCT ever gets in here we do bad things (tmpSlot returns -1) - noway_assert(size >= sizeof(int)); + noway_assert(size >= sizeof(int) && size != SIZE_UNKNOWN); // Find the slot to search for a free temp of the right size. // Note that slots are shared by types of the identical size (e.g., TYP_REF and TYP_LONG on AMD64), diff --git a/src/coreclr/jit/targetarm64.h b/src/coreclr/jit/targetarm64.h index 3f26dfab09ee93..aaa9cf4a485279 100644 --- a/src/coreclr/jit/targetarm64.h +++ b/src/coreclr/jit/targetarm64.h @@ -389,4 +389,6 @@ #define REG_SWIFT_INTRET_ORDER REG_R0,REG_R1,REG_R2,REG_R3 #define REG_SWIFT_FLOATRET_ORDER REG_V0,REG_V1,REG_V2,REG_V3 +#define REG_UNKBASE REG_R19 +#define RBM_UNKBASE RBM_R19 // clang-format on