Skip to content

Commit

Permalink
JIT: Invalidate m_switchDescMap instead of updating it (dotnet#98789)
Browse files Browse the repository at this point in the history
Fixes dotnet#98772. Recently, we started checking successor edge likelihoods in Compiler::fgDebugCheckProfileWeights by default; this means we call Compiler::fgDebugCheckOutgoingProfileData in Debug/Checked builds to verify successor edges' likelihoods, and thus call BasicBlock::GetSucc(unsigned, Compiler*) to iterate the successor edges. For switch blocks, GetSucc(unsigned, Compiler*) calls GetSwitchDescMap, and builds m_switchDescMap if it doesn't exist yet. Upon finishing edge likelihood verification, we don't reset m_switchDescMap, so it is possible for this map to be created earlier in Debug/Checked builds versus Release builds.

This doesn't result in behavioral diffs if the map contains the same state between Debug/Release builds by the time it is read. However, if the map is null by the time it is needed, it is created on-demand, ensuring it is completely up-to-date. If the map is not null, the correctness of its current state depends on how judiciously it was maintained with UpdateSwitchTableTarget, creating potential for diffs in the map's state. By invalidating the map instead of updating it as state changes, we can force it to be rebuilt with the latest state when it's needed.
  • Loading branch information
amanasifkhalid authored Feb 22, 2024
1 parent b5038db commit c86ab13
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 119 deletions.
17 changes: 3 additions & 14 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5828,20 +5828,14 @@ class Compiler
// For many purposes, it is desirable to be able to enumerate the *distinct* targets of a switch statement,
// skipping duplicate targets. (E.g., in flow analyses that are only interested in the set of possible targets.)
// SwitchUniqueSuccSet contains the non-duplicated switch targets.
// (Code that modifies the jump table of a switch has an obligation to call Compiler::UpdateSwitchTableTarget,
// which in turn will call the "UpdateTarget" method of this type if a SwitchUniqueSuccSet has already
// been computed for the switch block. If a switch block is deleted or is transformed into a non-switch,
// we leave the entry associated with the block, but it will no longer be accessed.)
// Code that modifies the flowgraph (such as by renumbering blocks) must call Compiler::InvalidateUniqueSwitchSuccMap,
// and code that modifies the targets of a switch block must call Compiler::fgInvalidateSwitchDescMapEntry.
// If the unique targets of a switch block are needed later, they will be recomputed, ensuring they're up-to-date.
struct SwitchUniqueSuccSet
{
unsigned numDistinctSuccs; // Number of distinct targets of the switch.
BasicBlock** nonDuplicates; // Array of "numDistinctSuccs", containing all the distinct switch target
// successors.

// The switch block "switchBlk" just had an entry with value "from" modified to the value "to".
// Update "this" as necessary: if "from" is no longer an element of the jump table of "switchBlk",
// remove it from "this", and ensure that "to" is a member. Use "alloc" to do any required allocation.
void UpdateTarget(CompAllocator alloc, BasicBlock* switchBlk, BasicBlock* from, BasicBlock* to);
};

typedef JitHashTable<BasicBlock*, JitPtrKeyFuncs<BasicBlock>, SwitchUniqueSuccSet> BlockToSwitchDescMap;
Expand Down Expand Up @@ -5873,11 +5867,6 @@ class Compiler
// the corresponding SwitchUniqueSuccSet.
SwitchUniqueSuccSet GetDescriptorForSwitch(BasicBlock* switchBlk);

// The switch block "switchBlk" just had an entry with value "from" modified to the value "to".
// Update "this" as necessary: if "from" is no longer an element of the jump table of "switchBlk",
// remove it from "this", and ensure that "to" is a member.
void UpdateSwitchTableTarget(BasicBlock* switchBlk, BasicBlock* from, BasicBlock* to);

// Remove the "SwitchUniqueSuccSet" of "switchBlk" in the BlockToSwitchDescMap.
void fgInvalidateSwitchDescMapEntry(BasicBlock* switchBlk);

Expand Down
98 changes: 0 additions & 98 deletions src/coreclr/jit/fgflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,87 +526,6 @@ Compiler::SwitchUniqueSuccSet Compiler::GetDescriptorForSwitch(BasicBlock* switc
}
}

void Compiler::SwitchUniqueSuccSet::UpdateTarget(CompAllocator alloc,
BasicBlock* switchBlk,
BasicBlock* from,
BasicBlock* to)
{
assert(switchBlk->KindIs(BBJ_SWITCH)); // Precondition.

// Is "from" still in the switch table (because it had more than one entry before?)
bool fromStillPresent = false;
for (BasicBlock* const bTarget : switchBlk->SwitchTargets())
{
if (bTarget == from)
{
fromStillPresent = true;
break;
}
}

// Is "to" already in "this"?
bool toAlreadyPresent = false;
for (unsigned i = 0; i < numDistinctSuccs; i++)
{
if (nonDuplicates[i] == to)
{
toAlreadyPresent = true;
break;
}
}

// Four cases:
// If "from" is still present, and "to" is already present, do nothing
// If "from" is still present, and "to" is not, must reallocate to add an entry.
// If "from" is not still present, and "to" is not present, write "to" where "from" was.
// If "from" is not still present, but "to" is present, remove "from".
if (fromStillPresent && toAlreadyPresent)
{
return;
}
else if (fromStillPresent && !toAlreadyPresent)
{
// reallocate to add an entry
BasicBlock** newNonDups = new (alloc) BasicBlock*[numDistinctSuccs + 1];
memcpy(newNonDups, nonDuplicates, numDistinctSuccs * sizeof(BasicBlock*));
newNonDups[numDistinctSuccs] = to;
numDistinctSuccs++;
nonDuplicates = newNonDups;
}
else if (!fromStillPresent && !toAlreadyPresent)
{
// write "to" where "from" was
INDEBUG(bool foundFrom = false);
for (unsigned i = 0; i < numDistinctSuccs; i++)
{
if (nonDuplicates[i] == from)
{
nonDuplicates[i] = to;
INDEBUG(foundFrom = true);
break;
}
}
assert(foundFrom);
}
else
{
assert(!fromStillPresent && toAlreadyPresent);
// remove "from".
INDEBUG(bool foundFrom = false);
for (unsigned i = 0; i < numDistinctSuccs; i++)
{
if (nonDuplicates[i] == from)
{
nonDuplicates[i] = nonDuplicates[numDistinctSuccs - 1];
numDistinctSuccs--;
INDEBUG(foundFrom = true);
break;
}
}
assert(foundFrom);
}
}

/*****************************************************************************
*
* Simple utility function to remove an entry for a block in the switch desc
Expand All @@ -621,20 +540,3 @@ void Compiler::fgInvalidateSwitchDescMapEntry(BasicBlock* block)
m_switchDescMap->Remove(block);
}
}

void Compiler::UpdateSwitchTableTarget(BasicBlock* switchBlk, BasicBlock* from, BasicBlock* to)
{
if (m_switchDescMap == nullptr)
{
return; // No mappings, nothing to do.
}

// Otherwise...
BlockToSwitchDescMap* switchMap = GetSwitchDescMap();
SwitchUniqueSuccSet* res = switchMap->LookupPointer(switchBlk);
if (res != nullptr)
{
// If no result, nothing to do. Otherwise, update it.
res->UpdateTarget(getAllocator(), switchBlk, from, to);
}
}
17 changes: 10 additions & 7 deletions src/coreclr/jit/fgopt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1811,7 +1811,7 @@ bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block)
FlowEdge** jmpTab = block->GetSwitchTargets()->bbsDstTab;
BasicBlock* bNewDest; // the new jump target for the current switch case
BasicBlock* bDest;
bool returnvalue = false;
bool modified = false;

do
{
Expand Down Expand Up @@ -1871,20 +1871,23 @@ bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block)
}
}

// Update the switch jump table (this has to happen before calling UpdateSwitchTableTarget)
// Update the switch jump table
FlowEdge* const newEdge = fgAddRefPred(bNewDest, block, fgRemoveRefPred(bDest, block));
*jmpTab = newEdge;

// Maintain, if necessary, the set of unique targets of "block."
UpdateSwitchTableTarget(block, bDest, bNewDest);

// we optimized a Switch label - goto REPEAT_SWITCH to follow this new jump
returnvalue = true;
modified = true;

goto REPEAT_SWITCH;
}
} while (++jmpTab, --jmpCnt);

if (modified)
{
// Invalidate the set of unique targets for block, since we modified the targets
fgInvalidateSwitchDescMapEntry(block);
}

Statement* switchStmt = nullptr;
LIR::Range* blockRange = nullptr;

Expand Down Expand Up @@ -2066,7 +2069,7 @@ bool Compiler::fgOptimizeSwitchBranches(BasicBlock* block)

return true;
}
return returnvalue;
return modified;
}

//-------------------------------------------------------------
Expand Down

0 comments on commit c86ab13

Please sign in to comment.