Skip to content

C++/C#: Reuse some SSA def/use info from previous iteration #3340

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,14 @@ class PhiInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getAnInput() { result = this.getAnInputOperand().getDef() }

/**
* Gets the input operand representing the value that flows from the specified predecessor block.
*/
final PhiInputOperand getInputOperand(IRBlock predecessorBlock) {
result = this.getAnOperand() and
result.getPredecessorBlock() = predecessorBlock
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,11 +255,7 @@ private predicate resultEscapesNonReturn(Instruction instr) {
* allocation is marked as always escaping via `alwaysEscapes()`.
*/
predicate allocationEscapes(Configuration::Allocation allocation) {
allocation.alwaysEscapes()
or
exists(IREscapeAnalysisConfiguration config |
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
)
resultEscapesNonReturn(allocation.getABaseInstruction())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this predicate changed and whether the QLDoc on it is still accurate.

}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
import semmle.code.cpp.ir.implementation.IRConfiguration
import semmle.code.cpp.ir.internal.IntegerConstant as Ints
import semmle.code.cpp.models.interfaces.Alias as AliasModels
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ private import AliasConfigurationInternal
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
private import cpp
private import AliasAnalysis
private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SimpleSSA as UnaliasedSSA

private newtype TAllocation =
TVariableAllocation(IRVariable var) or
TVariableAllocation(IRVariable var) {
// Only model variables that were not already handled in unaliased SSA.
not UnaliasedSSA::canReuseSSAForVariable(var)
} or
TIndirectParameterAllocation(IRAutomaticUserVariable var) {
exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var)
} or
Expand Down Expand Up @@ -35,8 +39,6 @@ abstract class Allocation extends TAllocation {
abstract predicate alwaysEscapes();

abstract predicate isAlwaysAllocatedOnStack();

final predicate isUnaliased() { not allocationEscapes(this) }
}

class VariableAllocation extends Allocation, TVariableAllocation {
Expand Down Expand Up @@ -88,7 +90,7 @@ class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocati

final override Language::Location getLocation() { result = var.getLocation() }

final override string getUniqueId() { result = var.getUniqueId() }
final override string getUniqueId() { result = "*" + var.getUniqueId() }

final override IRType getIRType() { result = var.getIRType() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,33 @@ import AliasAnalysis
import semmle.code.cpp.ir.internal.Overlap
private import semmle.code.cpp.ir.internal.IRCppLanguage as Language
private import semmle.code.cpp.Print
private import semmle.code.cpp.ir.IRConfiguration
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction as OldSSA
private import semmle.code.cpp.ir.internal.IntegerConstant as Ints
private import semmle.code.cpp.ir.internal.IntegerInterval as Interval
private import semmle.code.cpp.ir.implementation.internal.OperandTag
private import AliasConfiguration

private class IntValue = Ints::IntValue;

/**
* Holds if we should treat the allocation `var` as escaped. If using sound escape analysis, we
* invoke the escape analysis to determine this. If not using sound escape analysis, we assume that
* no stack allocation escapes.
*/
private predicate treatAllocationAsEscaped(Allocation var) {
// If the configuration says to use sound escape analysis, do that analysis.
exists(IREscapeAnalysisConfiguration config |
config.useSoundEscapeAnalysis()
) and
allocationEscapes(var)
or
// If the allocation always escapes (for example, a global variable), treat it as escaped even if
// not using sound escape analysis.
var.alwaysEscapes()
}

private predicate isIndirectOrBufferMemoryAccess(MemoryAccessKind kind) {
kind instanceof IndirectMemoryAccess or
kind instanceof BufferMemoryAccess
Expand Down Expand Up @@ -131,6 +150,12 @@ abstract class MemoryLocation extends TMemoryLocation {
* with automatic storage duration).
*/
predicate isAlwaysAllocatedOnStack() { none() }

/**
* Holds if any def/use information calculated for this location can be safely reused by future
* iterations of SSA.
*/
predicate canReuseSSA() { any() }
}

abstract class VirtualVariable extends MemoryLocation { }
Expand All @@ -147,7 +172,7 @@ abstract class AllocationMemoryLocation extends MemoryLocation {
}

final override VirtualVariable getVirtualVariable() {
if allocationEscapes(var)
if treatAllocationAsEscaped(var)
then result = TAllAliasedMemory(var.getEnclosingIRFunction(), false)
else result.(AllocationMemoryLocation).getAllocation() = var
}
Expand Down Expand Up @@ -248,7 +273,7 @@ class EntireAllocationMemoryLocation extends TEntireAllocationMemoryLocation,

class EntireAllocationVirtualVariable extends EntireAllocationMemoryLocation, VirtualVariable {
EntireAllocationVirtualVariable() {
not allocationEscapes(var) and
not treatAllocationAsEscaped(var) and
not isMayAccess()
}
}
Expand All @@ -260,7 +285,7 @@ class EntireAllocationVirtualVariable extends EntireAllocationMemoryLocation, Vi
*/
class VariableVirtualVariable extends VariableMemoryLocation, VirtualVariable {
VariableVirtualVariable() {
not allocationEscapes(var) and
not treatAllocationAsEscaped(var) and
type = var.getIRType() and
coversEntireVariable() and
not isMayAccess()
Expand Down Expand Up @@ -556,7 +581,18 @@ private Overlap getVariableMemoryLocationOverlap(
use.getEndBitOffset())
}

/**
* Holds if the def/use information for the result of `instr` can be reused from the previous
* iteration of the IR.
*/
predicate canReuseSSAForOldResult(Instruction instr) {
OldSSA::canReuseSSAForMemoryResult(instr)
}

MemoryLocation getResultMemoryLocation(Instruction instr) {
// Ignore instructions that already have modeled results, because SSA construction can just reuse
// their def/use information.
not canReuseSSAForOldResult(instr) and
exists(MemoryAccessKind kind, boolean isMayAccess |
kind = instr.getResultMemoryAccess() and
(if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and
Expand Down Expand Up @@ -588,6 +624,9 @@ MemoryLocation getResultMemoryLocation(Instruction instr) {
}

MemoryLocation getOperandMemoryLocation(MemoryOperand operand) {
// Ignore operands that are already modeled, because SSA construction can just reuse their def/use
// information.
not canReuseSSAForOldResult(operand.getAnyDef()) and
exists(MemoryAccessKind kind, boolean isMayAccess |
kind = operand.getMemoryAccess() and
(if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ private module Cached {
result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction())
}

/**
* Gets the block from the old IR that corresponds to `newBlock`.
*/
private OldBlock getOldBlock(IRBlock newBlock) { getNewBlock(result) = newBlock }

cached
predicate functionHasIR(Language::Function func) {
exists(OldIR::IRFunction irFunc | irFunc.getFunction() = func)
Expand All @@ -27,10 +32,24 @@ private module Cached {
result = var
}

/**
* If `oldInstruction` is a `Phi` instruction that has exactly one reachable predecessor block,
* this predicate returns the `PhiInputOperand` corresponding to that predecessor block.
* Otherwise, this predicate does not hold.
*/
private OldIR::PhiInputOperand getDegeneratePhiOperand(OldInstruction oldInstruction) {
result =
unique(OldIR::PhiInputOperand operand |
operand = oldInstruction.(OldIR::PhiInstruction).getAnInputOperand() and
operand.getPredecessorBlock() instanceof OldBlock
)
}

cached
newtype TInstruction =
WrappedInstruction(OldInstruction oldInstruction) {
not oldInstruction instanceof OldIR::PhiInstruction
// Do not translate degenerate `Phi` instructions.
not exists(getDegeneratePhiOperand(oldInstruction))
} or
Phi(OldBlock block, Alias::MemoryLocation defLocation) {
definitionHasPhiNode(defLocation, block)
Expand Down Expand Up @@ -58,10 +77,26 @@ private module Cached {
)
}

/**
* Holds if this iteration of SSA can model the def/use information for the result of
* `oldInstruction`, either because alias analysis has determined a memory location for that
* result, or because a previous iteration of the IR already computed that def/use information
* completely.
*/
private predicate canModelResultForOldInstruction(OldInstruction oldInstruction) {
// We're modeling the result's memory location ourselves.
exists(Alias::getResultMemoryLocation(oldInstruction))
or
// This result was already modeled by a previous iteration of SSA.
Alias::canReuseSSAForOldResult(oldInstruction)
}

cached
predicate hasModeledMemoryResult(Instruction instruction) {
exists(Alias::getResultMemoryLocation(getOldInstruction(instruction))) or
instruction instanceof PhiInstruction or // Phis always have modeled results
canModelResultForOldInstruction(getOldInstruction(instruction))
or
instruction instanceof PhiInstruction // Phis always have modeled results
or
instruction instanceof ChiInstruction // Chis always have modeled results
}

Expand Down Expand Up @@ -119,6 +154,30 @@ private module Cached {
)
}

/**
* Gets the new definition instruction for `oldOperand` based on `oldOperand`'s definition in the
* old IR. Usually, this will just get the old definition of `oldOperand` and map it to the
* corresponding new instruction. However, if the old definition of `oldOperand` is a `Phi`
* instruction that is now degenerate due all but one of its predecessor branches being
* unreachable, this predicate will recurse through any degenerate `Phi` instructions to find the
* true definition.
*/
private Instruction getNewDefinitionFromOldSSA(OldIR::MemoryOperand oldOperand, Overlap overlap) {
exists(Overlap originalOverlap |
originalOverlap = oldOperand.getDefinitionOverlap() and
(
result = getNewInstruction(oldOperand.getAnyDef()) and
overlap = originalOverlap
or
exists(OldIR::PhiInputOperand phiOperand, Overlap phiOperandOverlap |
phiOperand = getDegeneratePhiOperand(oldOperand.getAnyDef()) and
result = getNewDefinitionFromOldSSA(phiOperand, phiOperandOverlap) and
overlap = combineOverlap(phiOperandOverlap, originalOverlap)
)
)
)
}

cached
private Instruction getMemoryOperandDefinition0(
Instruction instruction, MemoryOperandTag tag, Overlap overlap
Expand All @@ -129,12 +188,12 @@ private module Cached {
tag = oldOperand.getOperandTag() and
(
(
not instruction instanceof UnmodeledUseInstruction and
if exists(Alias::getOperandMemoryLocation(oldOperand))
then hasMemoryOperandDefinition(oldInstruction, oldOperand, overlap, result)
else (
result = instruction.getEnclosingIRFunction().getUnmodeledDefinitionInstruction() and
overlap instanceof MustTotallyOverlap
)
else
// Reuse the definition that was already computed by the previous iteration of SSA.
result = getNewDefinitionFromOldSSA(oldOperand, overlap)
)
or
// Connect any definitions that are not being modeled in SSA to the
Expand All @@ -143,7 +202,7 @@ private module Cached {
instruction instanceof UnmodeledUseInstruction and
tag instanceof UnmodeledUseOperandTag and
oldDefinition = oldOperand.getAnyDef() and
not exists(Alias::getResultMemoryLocation(oldDefinition)) and
not canModelResultForOldInstruction(oldDefinition) and
result = getNewInstruction(oldDefinition) and
overlap instanceof MustTotallyOverlap
)
Expand Down Expand Up @@ -199,9 +258,25 @@ private module Cached {
)
}

/**
* Gets the new definition instruction for the operand of `instr` that flows from the block
* `newPredecessorBlock`, based on that operand's definition in the old IR.
*/
private Instruction getNewPhiOperandDefinitionFromOldSSA(
WrappedInstruction instr, IRBlock newPredecessorBlock, Overlap overlap
) {
exists(OldIR::PhiInstruction oldPhi, OldIR::PhiInputOperand oldOperand |
oldPhi = getOldInstruction(instr) and
oldOperand = oldPhi.getInputOperand(getOldBlock(newPredecessorBlock)) and
result = getNewDefinitionFromOldSSA(oldOperand, overlap)
)
}

pragma[noopt]
cached
Instruction getPhiOperandDefinition(Phi instr, IRBlock newPredecessorBlock, Overlap overlap) {
Instruction getPhiOperandDefinition(
Instruction instr, IRBlock newPredecessorBlock, Overlap overlap
) {
exists(
Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock,
OldBlock predBlock, OldBlock defBlock, int defOffset, Alias::MemoryLocation actualDefLocation
Expand All @@ -212,6 +287,9 @@ private module Cached {
result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and
overlap = Alias::getOverlap(actualDefLocation, useLocation)
)
or
// Reuse the definition that was already computed by the previous iteration of SSA.
result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap)
}

cached
Expand All @@ -232,7 +310,12 @@ private module Cached {
cached
Instruction getPhiInstructionBlockStart(PhiInstruction instr) {
exists(OldBlock oldBlock |
instr = Phi(oldBlock, _) and
(
instr = Phi(oldBlock, _)
or
// Any `Phi` that we propagated from the previous iteration stays in the same block.
getOldInstruction(instr).getBlock() = oldBlock
) and
result = getNewInstruction(oldBlock.getFirstInstruction())
)
}
Expand Down Expand Up @@ -894,6 +977,30 @@ module DefUse {
}
}

/**
* Holds if the SSA def/use chain for the instruction's result can be reused in future iterations of
* SSA.
*/
predicate canReuseSSAForMemoryResult(Instruction instruction) {
exists(OldInstruction oldInstruction |
oldInstruction = getOldInstruction(instruction) and
(
// The previous iteration said it was reusable, so we should mark it as reusable as well.
Alias::canReuseSSAForOldResult(oldInstruction)
or
// The current alias analysis says it is reusable.
Alias::getResultMemoryLocation(oldInstruction).canReuseSSA()
)
)
or
exists(Alias::MemoryLocation defLocation |
// This is a `Phi` for a reusable location, so the result of the `Phi` is reusable as well.
instruction = Phi(_, defLocation) and
defLocation.canReuseSSA()
)
// We don't support reusing SSA for any location that could create a `Chi` instruction.
}

/**
* Expose some of the internal predicates to PrintSSA.qll. We do this by publically importing those modules in the
* `DebugSSA` module, which is then imported by PrintSSA.
Expand All @@ -916,7 +1023,7 @@ private module CachedForDebugging {
string getInstructionUniqueId(Instruction instr) {
exists(OldInstruction oldInstr |
oldInstr = getOldInstruction(instr) and
result = "NonSSA: " + oldInstr.getUniqueId()
result = oldInstr.getUniqueId()
)
or
exists(Alias::MemoryLocation location, OldBlock phiBlock, string specificity |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,14 @@ class PhiInstruction extends Instruction {
*/
pragma[noinline]
final Instruction getAnInput() { result = this.getAnInputOperand().getDef() }

/**
* Gets the input operand representing the value that flows from the specified predecessor block.
*/
final PhiInputOperand getInputOperand(IRBlock predecessorBlock) {
result = this.getAnOperand() and
result.getPredecessorBlock() = predecessorBlock
}
}

/**
Expand Down
Loading