Skip to content

Commit

Permalink
JIT: refactor how opaque VNs are represented (#55853)
Browse files Browse the repository at this point in the history
Use a unary function to capture opaque VN loop dependence, rather than factoring
that out as part of the owning chunk. As a result the VN chunk data no longer
has a per-loop component (no dependence on MAX_LOOP_NUM).

This gives us the flexibility to address #54118 by doing something similar for
`MapUpdate` VNs.
  • Loading branch information
AndyAyersMS committed Jul 19, 2021
1 parent 9796891 commit d05950b
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 107 deletions.
20 changes: 5 additions & 15 deletions src/coreclr/jit/optimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6567,6 +6567,11 @@ bool Compiler::optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* loo
BasicBlock* defnBlk = reinterpret_cast<BasicBlock*>(vnStore->ConstantValue<ssize_t>(funcApp.m_args[0]));
res = !optLoopContains(lnum, defnBlk->bbNatLoopNum);
}
else if (funcApp.m_func == VNF_MemOpaque)
{
const unsigned vnLoopNum = funcApp.m_args[0];
res = !optLoopContains(lnum, vnLoopNum);
}
else
{
for (unsigned i = 0; i < funcApp.m_arity; i++)
Expand All @@ -6582,21 +6587,6 @@ bool Compiler::optVNIsLoopInvariant(ValueNum vn, unsigned lnum, VNToBoolMap* loo
}
}
}
else
{
// Non-function "new, unique" VN's may be annotated with the loop nest where
// their definition occurs.
BasicBlock::loopNumber vnLoopNum = vnStore->LoopOfVN(vn);

if (vnLoopNum == BasicBlock::MAX_LOOP_NUM)
{
res = false;
}
else
{
res = !optLoopContains(lnum, vnLoopNum);
}
}

loopVnInvariantCache->Set(vn, res);
return res;
Expand Down
173 changes: 98 additions & 75 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ ValueNumStore::ValueNumStore(Compiler* comp, CompAllocator alloc)
// We have no current allocation chunks.
for (unsigned i = 0; i < TYP_COUNT; i++)
{
for (unsigned j = CEA_None; j <= CEA_Count + BasicBlock::MAX_LOOP_NUM; j++)
for (unsigned j = CEA_Const; j <= CEA_Count; j++)
{
m_curAllocChunk[i][j] = NoChunk;
}
Expand All @@ -466,8 +466,7 @@ ValueNumStore::ValueNumStore(Compiler* comp, CompAllocator alloc)
}
// We will reserve chunk 0 to hold some special constants, like the constant NULL, the "exception" value, and the
// "zero map."
Chunk* specialConstChunk =
new (m_alloc) Chunk(m_alloc, &m_nextChunkBase, TYP_REF, CEA_Const, BasicBlock::MAX_LOOP_NUM);
Chunk* specialConstChunk = new (m_alloc) Chunk(m_alloc, &m_nextChunkBase, TYP_REF, CEA_Const);
specialConstChunk->m_numUsed +=
SRC_NumSpecialRefConsts; // Implicitly allocate 0 ==> NULL, and 1 ==> Exception, 2 ==> ZeroMap.
ChunkNum cn = m_chunks.Push(specialConstChunk);
Expand Down Expand Up @@ -1552,17 +1551,12 @@ bool ValueNumStore::IsSharedStatic(ValueNum vn)
return GetVNFunc(vn, &funcAttr) && (s_vnfOpAttribs[funcAttr.m_func] & VNFOA_SharedStatic) != 0;
}

ValueNumStore::Chunk::Chunk(CompAllocator alloc,
ValueNum* pNextBaseVN,
var_types typ,
ChunkExtraAttribs attribs,
BasicBlock::loopNumber loopNum)
: m_defs(nullptr), m_numUsed(0), m_baseVN(*pNextBaseVN), m_typ(typ), m_attribs(attribs), m_loopNum(loopNum)
ValueNumStore::Chunk::Chunk(CompAllocator alloc, ValueNum* pNextBaseVN, var_types typ, ChunkExtraAttribs attribs)
: m_defs(nullptr), m_numUsed(0), m_baseVN(*pNextBaseVN), m_typ(typ), m_attribs(attribs)
{
// Allocate "m_defs" here, according to the typ/attribs pair.
switch (attribs)
{
case CEA_None:
case CEA_NotAField:
break; // Nothing to do.
case CEA_Const:
Expand Down Expand Up @@ -1619,26 +1613,11 @@ ValueNumStore::Chunk::Chunk(CompAllocator alloc,
*pNextBaseVN += ChunkSize;
}

ValueNumStore::Chunk* ValueNumStore::GetAllocChunk(var_types typ,
ChunkExtraAttribs attribs,
BasicBlock::loopNumber loopNum)
ValueNumStore::Chunk* ValueNumStore::GetAllocChunk(var_types typ, ChunkExtraAttribs attribs)
{
Chunk* res;
unsigned index;
if (loopNum == BasicBlock::MAX_LOOP_NUM)
{
// Loop nest is unknown/irrelevant for this VN.
index = attribs;
}
else
{
// Loop nest is interesting. Since we know this is only true for unique VNs, we know attribs will
// be CEA_None and can just index based on loop number.
noway_assert(attribs == CEA_None);
// Map NOT_IN_LOOP -> BasicBlock::MAX_LOOP_NUM to make the index range contiguous [0..BasicBlock::MAX_LOOP_NUM]
index = CEA_Count + (loopNum == BasicBlock::NOT_IN_LOOP ? BasicBlock::MAX_LOOP_NUM : loopNum);
}
ChunkNum cn = m_curAllocChunk[typ][index];
unsigned index = attribs;
ChunkNum cn = m_curAllocChunk[typ][index];
if (cn != NoChunk)
{
res = m_chunks.Get(cn);
Expand All @@ -1648,7 +1627,7 @@ ValueNumStore::Chunk* ValueNumStore::GetAllocChunk(var_types typ,
}
}
// Otherwise, must allocate a new one.
res = new (m_alloc) Chunk(m_alloc, &m_nextChunkBase, typ, attribs, loopNum);
res = new (m_alloc) Chunk(m_alloc, &m_nextChunkBase, typ, attribs);
cn = m_chunks.Push(res);
m_curAllocChunk[typ][index] = cn;
return res;
Expand Down Expand Up @@ -1782,10 +1761,13 @@ ValueNum ValueNumStore::VNForHandle(ssize_t cnsVal, GenTreeFlags handleFlags)
}
else
{
Chunk* c = GetAllocChunk(TYP_I_IMPL, CEA_Handle);
unsigned offsetWithinChunk = c->AllocVN();
res = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNHandle*>(c->m_defs)[offsetWithinChunk] = handle;
Chunk* const c = GetAllocChunk(TYP_I_IMPL, CEA_Handle);
unsigned const offsetWithinChunk = c->AllocVN();
VNHandle* const chunkSlots = reinterpret_cast<VNHandle*>(c->m_defs);

chunkSlots[offsetWithinChunk] = handle;
res = c->m_baseVN + offsetWithinChunk;

GetHandleMap()->Set(handle, res);
return res;
}
Expand Down Expand Up @@ -1886,10 +1868,12 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func)
if (!GetVNFunc0Map()->Lookup(func, &resultVN))
{
// Allocate a new ValueNum for 'func'
Chunk* c = GetAllocChunk(typ, CEA_Func0);
unsigned offsetWithinChunk = c->AllocVN();
resultVN = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNFunc*>(c->m_defs)[offsetWithinChunk] = func;
Chunk* const c = GetAllocChunk(typ, CEA_Func0);
unsigned const offsetWithinChunk = c->AllocVN();
VNFunc* const chunkSlots = reinterpret_cast<VNFunc*>(c->m_defs);

chunkSlots[offsetWithinChunk] = func;
resultVN = c->m_baseVN + offsetWithinChunk;
GetVNFunc0Map()->Set(func, resultVN);
}
return resultVN;
Expand All @@ -1911,6 +1895,7 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func)
//
ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN)
{
assert(func != VNF_MemOpaque);
assert(arg0VN == VNNormalValue(arg0VN)); // Arguments don't carry exceptions.

// Try to perform constant-folding.
Expand All @@ -1922,16 +1907,18 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN)
ValueNum resultVN;

// Have we already assigned a ValueNum for 'func'('arg0VN') ?
//
VNDefFunc1Arg fstruct(func, arg0VN);
if (!GetVNFunc1Map()->Lookup(fstruct, &resultVN))
{
// Otherwise, Allocate a new ValueNum for 'func'('arg0VN')
//
Chunk* c = GetAllocChunk(typ, CEA_Func1);
unsigned offsetWithinChunk = c->AllocVN();
resultVN = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNDefFunc1Arg*>(c->m_defs)[offsetWithinChunk] = fstruct;
Chunk* const c = GetAllocChunk(typ, CEA_Func1);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc1Arg* const chunkSlots = reinterpret_cast<VNDefFunc1Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;
resultVN = c->m_baseVN + offsetWithinChunk;

// Record 'resultVN' in the Func1Map
GetVNFunc1Map()->Set(fstruct, resultVN);
}
Expand Down Expand Up @@ -2047,10 +2034,12 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN, V
{
// Otherwise, Allocate a new ValueNum for 'func'('arg0VN','arg1VN')
//
Chunk* c = GetAllocChunk(typ, CEA_Func2);
unsigned offsetWithinChunk = c->AllocVN();
resultVN = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNDefFunc2Arg*>(c->m_defs)[offsetWithinChunk] = fstruct;
Chunk* const c = GetAllocChunk(typ, CEA_Func2);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc2Arg* const chunkSlots = reinterpret_cast<VNDefFunc2Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;
resultVN = c->m_baseVN + offsetWithinChunk;
// Record 'resultVN' in the Func2Map
GetVNFunc2Map()->Set(fstruct, resultVN);
}
Expand Down Expand Up @@ -2108,10 +2097,13 @@ ValueNum ValueNumStore::VNForFunc(var_types typ, VNFunc func, ValueNum arg0VN, V
{
// Otherwise, Allocate a new ValueNum for 'func'('arg0VN','arg1VN','arg2VN')
//
Chunk* c = GetAllocChunk(typ, CEA_Func3);
unsigned offsetWithinChunk = c->AllocVN();
resultVN = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNDefFunc3Arg*>(c->m_defs)[offsetWithinChunk] = fstruct;
Chunk* const c = GetAllocChunk(typ, CEA_Func3);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc3Arg* const chunkSlots = reinterpret_cast<VNDefFunc3Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;
resultVN = c->m_baseVN + offsetWithinChunk;

// Record 'resultVN' in the Func3Map
GetVNFunc3Map()->Set(fstruct, resultVN);
}
Expand Down Expand Up @@ -2156,10 +2148,13 @@ ValueNum ValueNumStore::VNForFunc(
{
// Otherwise, Allocate a new ValueNum for 'func'('arg0VN','arg1VN','arg2VN','arg3VN')
//
Chunk* c = GetAllocChunk(typ, CEA_Func4);
unsigned offsetWithinChunk = c->AllocVN();
resultVN = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNDefFunc4Arg*>(c->m_defs)[offsetWithinChunk] = fstruct;
Chunk* const c = GetAllocChunk(typ, CEA_Func4);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc4Arg* const chunkSlots = reinterpret_cast<VNDefFunc4Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;
resultVN = c->m_baseVN + offsetWithinChunk;

// Record 'resultVN' in the Func4Map
GetVNFunc4Map()->Set(fstruct, resultVN);
}
Expand Down Expand Up @@ -2465,10 +2460,13 @@ ValueNum ValueNumStore::VNForMapSelectWork(
if (!GetVNFunc2Map()->Lookup(fstruct, &res))
{
// Otherwise, assign a new VN for the function application.
Chunk* c = GetAllocChunk(typ, CEA_Func2);
unsigned offsetWithinChunk = c->AllocVN();
res = c->m_baseVN + offsetWithinChunk;
reinterpret_cast<VNDefFunc2Arg*>(c->m_defs)[offsetWithinChunk] = fstruct;
Chunk* const c = GetAllocChunk(typ, CEA_Func2);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc2Arg* const chunkSlots = reinterpret_cast<VNDefFunc2Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;
res = c->m_baseVN + offsetWithinChunk;

GetVNFunc2Map()->Set(fstruct, res);
}
return res;
Expand Down Expand Up @@ -3675,12 +3673,17 @@ ValueNum ValueNumStore::VNForExpr(BasicBlock* block, var_types typ)
loopNum = block->bbNatLoopNum;
}

// We always allocate a new, unique VN in this call.
// The 'typ' is used to partition the allocation of VNs into different chunks.
Chunk* c = GetAllocChunk(typ, CEA_None, loopNum);
unsigned offsetWithinChunk = c->AllocVN();
ValueNum result = c->m_baseVN + offsetWithinChunk;
return result;
// VNForFunc(typ, func, vn) but bypasses looking in the cache
//
VNDefFunc1Arg fstruct(VNF_MemOpaque, loopNum);
Chunk* const c = GetAllocChunk(typ, CEA_Func1);
unsigned const offsetWithinChunk = c->AllocVN();
VNDefFunc1Arg* const chunkSlots = reinterpret_cast<VNDefFunc1Arg*>(c->m_defs);

chunkSlots[offsetWithinChunk] = fstruct;

ValueNum resultVN = c->m_baseVN + offsetWithinChunk;
return resultVN;
}

ValueNum ValueNumStore::VNApplySelectors(ValueNumKind vnk,
Expand Down Expand Up @@ -4343,27 +4346,25 @@ var_types ValueNumStore::TypeOfVN(ValueNum vn)
}

//------------------------------------------------------------------------
// LoopOfVN: If the given value number is an opaque one associated with a particular
// expression in the IR, give the loop number where the expression occurs; otherwise,
// returns BasicBlock::MAX_LOOP_NUM.
// LoopOfVN: If the given value number is VNF_MemOpaque, return
// the loop number where the memory update occurs, otherwise returns MAX_LOOP_NUM.
//
// Arguments:
// vn - Value number to query
//
// Return Value:
// The correspondingblock's bbNatLoopNum, which may be BasicBlock::NOT_IN_LOOP.
// Returns BasicBlock::MAX_LOOP_NUM if this VN is not an opaque value number associated with
// a particular expression/location in the IR.

// The memory loop number, which may be BasicBlock::NOT_IN_LOOP.
// Returns BasicBlock::MAX_LOOP_NUM if this VN is not a memory value number.
//
BasicBlock::loopNumber ValueNumStore::LoopOfVN(ValueNum vn)
{
if (vn == NoVN)
VNFuncApp funcApp;
if (GetVNFunc(vn, &funcApp) && (funcApp.m_func == VNF_MemOpaque))
{
return BasicBlock::MAX_LOOP_NUM;
return (BasicBlock::loopNumber)funcApp.m_args[0];
}

Chunk* c = m_chunks.GetNoExpand(GetChunkNum(vn));
return c->m_loopNum;
return BasicBlock::MAX_LOOP_NUM;
}

bool ValueNumStore::IsVNConstant(ValueNum vn)
Expand Down Expand Up @@ -5554,6 +5555,9 @@ void ValueNumStore::vnDump(Compiler* comp, ValueNum vn, bool isPtr)
case VNF_ValWithExc:
vnDumpValWithExc(comp, &funcApp);
break;
case VNF_MemOpaque:
vnDumpMemOpaque(comp, &funcApp);
break;
#ifdef FEATURE_SIMD
case VNF_SimdType:
vnDumpSimdType(comp, &funcApp);
Expand Down Expand Up @@ -5712,6 +5716,25 @@ void ValueNumStore::vnDumpMapStore(Compiler* comp, VNFuncApp* mapStore)
printf("]");
}

void ValueNumStore::vnDumpMemOpaque(Compiler* comp, VNFuncApp* memOpaque)
{
assert(memOpaque->m_func == VNF_MemOpaque); // Precondition.
const unsigned loopNum = memOpaque->m_args[0];

if (loopNum == BasicBlock::NOT_IN_LOOP)
{
printf("MemOpaque:NotInLoop");
}
else if (loopNum == BasicBlock::MAX_LOOP_NUM)
{
printf("MemOpaque:Indeterminate");
}
else
{
printf("MemOpaque:L%02u", loopNum);
}
}

#ifdef FEATURE_SIMD
void ValueNumStore::vnDumpSimdType(Compiler* comp, VNFuncApp* simdType)
{
Expand Down
Loading

0 comments on commit d05950b

Please sign in to comment.