From 39a22ec14b6d2399b7a2a14187fc1ac94dbc6c18 Mon Sep 17 00:00:00 2001 From: SingleAccretion Date: Thu, 10 Jul 2025 21:53:02 +0300 Subject: [PATCH] Slightly more precise null checking --- src/coreclr/jit/llvm.h | 1 + src/coreclr/jit/llvmcodegen.cpp | 30 +++++++++++++++++++++++++++--- src/coreclr/jit/llvmlower.cpp | 2 +- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/llvm.h b/src/coreclr/jit/llvm.h index 89ee44ed05f..3a07ebc9b9c 100644 --- a/src/coreclr/jit/llvm.h +++ b/src/coreclr/jit/llvm.h @@ -657,6 +657,7 @@ class Llvm Value* consumeAddressAndEmitNullCheck(GenTreeIndir* indir); void emitNullCheckForAddress(GenTree* addr, Value* addrValue DEBUGARG(GenTree* indir)); + bool isAddressNullOrValid(GenTree* addr); void emitAlignmentCheckForAddress(GenTree* addr, Value* addrValue, unsigned alignment DEBUGARG(GenTree* indir)); bool isAddressAligned(GenTree* addr, unsigned alignment); diff --git a/src/coreclr/jit/llvmcodegen.cpp b/src/coreclr/jit/llvmcodegen.cpp index b6a923410c6..b4e1da91e51 100644 --- a/src/coreclr/jit/llvmcodegen.cpp +++ b/src/coreclr/jit/llvmcodegen.cpp @@ -2340,10 +2340,10 @@ void Llvm::emitNullCheckForAddress(GenTree* addr, Value* addrValue DEBUGARG(GenT { // The frontend's contract with the backend is that it will not insert null checks for accesses which // are inside the "[0..compMaxUncheckedOffsetForNullObject]" range. Thus, we usually need to check not - // just for "null", but "null + small offset". However, for TYP_REF, we know it will either be a valid - // object on heap, or null, and can utilize the more direct form. + // just for "null", but "null + small offset". However, for certain addresses, we know it will either + // be a valid address, or null. Value* isNullValue; - if (addr->TypeIs(TYP_REF)) + if (isAddressNullOrValid(addr)) { // LLVM's FastISel, used for unoptimized code, is not able to generate sensible WASM unless we do // a comparison using an integer zero here. This workaround saves 5+% on debug code size. @@ -2368,6 +2368,30 @@ void Llvm::emitNullCheckForAddress(GenTree* addr, Value* addrValue DEBUGARG(GenT emitJumpToThrowHelper(isNullValue, CORINFO_HELP_THROWNULLREF DEBUGARG(indir)); } +bool Llvm::isAddressNullOrValid(GenTree* addr) +{ + if (addr->TypeIs(TYP_REF)) + { + return true; + } + + // Weed out transient byrefs that could've been created by "fgMorphField". This is not as easy as it may look + // as we must accomodate for the possibility of the frontend transforming these in arbitrary ways. We do this + // by taking advantage of the managed ABI which prohibits passing such byrefs across call boundaries. + if (addr->OperIs(GT_LCL_VAR) && (addr->AsLclVar()->GetSsaNum() == SsaConfig::FIRST_SSA_NUM) && + _compiler->lvaGetDesc(addr->AsLclVar())->lvIsParam) + { + return true; + } + + if (addr->IsCall()) + { + return true; + } + + return false; +} + void Llvm::emitAlignmentCheckForAddress(GenTree* addr, Value* addrValue, unsigned alignment DEBUGARG(GenTree* indir)) { if (isAddressAligned(addr, alignment)) diff --git a/src/coreclr/jit/llvmlower.cpp b/src/coreclr/jit/llvmlower.cpp index 1ccee9331a1..6c921bad8b2 100644 --- a/src/coreclr/jit/llvmlower.cpp +++ b/src/coreclr/jit/llvmlower.cpp @@ -977,7 +977,7 @@ void Llvm::lowerAddressToAddressMode(GenTreeIndir* indir) (size_t)offset); // Invariant access can be assumed to be in bounds by construction. - if (((indir->gtFlags & GTF_IND_INVARIANT) == 0) && !isAddressInBounds(baseAddr, fieldSeq, offset)) + if (!indir->IsInvariantLoad() && !isAddressInBounds(baseAddr, fieldSeq, offset)) { JITDUMP("no, not in bounds\n"); return;