Skip to content

C++: reuse unaliased SSA results when computing aliased SSA #5522

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

Merged
merged 22 commits into from
Jun 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d95ef89
C++: add test for IR alias analysis soundness
rdmarsh2 Apr 13, 2021
a9d7990
C++: make unaliased_ssa IR stage sound
rdmarsh2 Mar 18, 2021
deff5c3
C++: Reuse SSA from earlier stages
rdmarsh2 Mar 10, 2021
8bc7e59
autoformat and sync C++ files
rdmarsh2 Apr 13, 2021
1c72ea9
C++: accept phi node reorderings in IR tests
rdmarsh2 Apr 14, 2021
f9e0ba1
C++: remove points-to expectations for reused SSA
rdmarsh2 Apr 14, 2021
86b1d03
C++: accept test regressions
rdmarsh2 Apr 16, 2021
922cf64
C++/C#: Add `combineOverlap()` predicate
Apr 20, 2020
7930c4a
C++: tests for phi nodes after unreachable blocks
rdmarsh2 Apr 19, 2021
6600436
C++: handle degenerate phi nodes
rdmarsh2 Apr 20, 2021
195b811
C++: handle phi operands from unreachable blocks
rdmarsh2 Apr 20, 2021
1f69b31
C++: test changes in annotate_sinks_only
rdmarsh2 Apr 20, 2021
5d7d26b
C++: fixups and file sync for SSA sharing
rdmarsh2 Apr 21, 2021
b281102
C#: sync IR files and update for C++ SSA reuse
rdmarsh2 Apr 22, 2021
230f4bc
C++: accept test changes from IR sharing
rdmarsh2 Apr 27, 2021
5406783
C++: autoformat
rdmarsh2 Apr 27, 2021
35594ea
C++: fix bad join order in phi node sharing
rdmarsh2 May 3, 2021
5318aa8
C++: autoformat
rdmarsh2 May 3, 2021
b3e598c
C++/C#: fix another join order in SSA construction
rdmarsh2 May 4, 2021
9ac55af
C++: One more join order fix
rdmarsh2 May 7, 2021
e590a7b
C++: Handle alias models for `this`/qualifiers
rdmarsh2 May 18, 2021
db85a21
C++: fix alias model for smart pointer setters
rdmarsh2 May 18, 2021
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 @@ -656,7 +656,7 @@ private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) {
exists(LoadInstruction load |
load.getSourceValueOperand() = opTo and
opTo.getAnyDef() = iFrom and
isSingleFieldClass(iFrom.getResultType(), opTo)
isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,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 @@ -28,11 +28,15 @@ class Operand extends TStageOperand {
cached
Operand() {
// Ensure that the operand does not refer to instructions from earlier stages that are unreachable here
exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or
exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or
exists(Instruction use, Instruction def | this = registerOperand(use, _, def))
or
exists(Instruction use | this = nonSSAMemoryOperand(use, _))
or
exists(Instruction use, Instruction def, IRBlock predecessorBlock |
this = phiOperand(use, def, predecessorBlock, _)
) or
this = phiOperand(use, def, predecessorBlock, _) or
this = reusedPhiOperand(use, def, predecessorBlock, _)
)
or
exists(Instruction use | this = chiOperand(use, _))
}

Expand Down Expand Up @@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
Overlap overlap;

cached
PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) }
PhiInputOperand() {
this = phiOperand(useInstr, defInstr, predecessorBlock, overlap)
or
this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap)
}

override string toString() { result = "Phi" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) {
or
// Converting an address to a `bool` does not escape the address.
instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType
or
instr instanceof CallInstruction and
not exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis())
)
)
or
Expand Down Expand Up @@ -284,14 +287,24 @@ private predicate isArgumentForParameter(
private predicate isOnlyEscapesViaReturnArgument(Operand operand) {
exists(AliasModels::AliasFunction f |
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
(
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
or
f.parameterEscapesOnlyViaReturn(-1) and
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be more aesthetically pleasing to add a int getIndex() { none() } predicate to ArgumentOperand and implement it as result = -1 for ThisArgumentOperand?

operand instanceof ThisArgumentOperand
)
)
}

private predicate isNeverEscapesArgument(Operand operand) {
exists(AliasModels::AliasFunction f |
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
(
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
or
f.parameterNeverEscapes(-1) and
operand instanceof ThisArgumentOperand
)
)
}

Expand Down Expand Up @@ -325,6 +338,9 @@ predicate allocationEscapes(Configuration::Allocation allocation) {
exists(IREscapeAnalysisConfiguration config |
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
)
or
Configuration::phaseNeedsSoundEscapeAnalysis() and
resultEscapesNonReturn(allocation.getABaseInstruction())
}

/**
Expand Down
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(IRAutomaticVariable var) {
exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var)
} or
Expand Down Expand Up @@ -138,3 +142,5 @@ class DynamicAllocation extends Allocation, TDynamicAllocation {

final override predicate alwaysEscapes() { none() }
}

predicate phaseNeedsSoundEscapeAnalysis() { none() }
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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.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
Expand Down Expand Up @@ -131,6 +132,8 @@ abstract class MemoryLocation extends TMemoryLocation {
* with automatic storage duration).
*/
predicate isAlwaysAllocatedOnStack() { none() }

final predicate canReuseSSA() { none() }
}

/**
Expand Down Expand Up @@ -562,10 +565,17 @@ 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) }

bindingset[result, b]
private boolean unbindBool(boolean b) { result != b.booleanNot() }

MemoryLocation getResultMemoryLocation(Instruction instr) {
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 @@ -598,6 +608,7 @@ MemoryLocation getResultMemoryLocation(Instruction instr) {
}

MemoryLocation getOperandMemoryLocation(MemoryOperand operand) {
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 @@ -43,24 +43,81 @@ private module Cached {
class TStageInstruction =
TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction;

/**
* 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
predicate hasInstruction(TStageInstruction instr) {
instr instanceof TRawInstruction and instr instanceof OldInstruction
or
instr instanceof TPhiInstruction
instr = phiInstruction(_, _)
or
instr = reusedPhiInstruction(_) and
// Check that the phi instruction is *not* degenerate, but we can't use
// getDegeneratePhiOperand in the first stage with phi instyructions
not exists(
unique(OldIR::PhiInputOperand operand |
operand = instr.(OldIR::PhiInstruction).getAnInputOperand() and
operand.getPredecessorBlock() instanceof OldBlock
)
)
or
instr instanceof TChiInstruction
or
instr instanceof TUnreachedInstruction
}

private IRBlock getNewBlock(OldBlock oldBlock) {
result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction())
cached
IRBlock getNewBlock(OldBlock oldBlock) {
exists(Instruction newEnd, OldIR::Instruction oldEnd |
(
result.getLastInstruction() = newEnd and
not newEnd instanceof ChiInstruction
or
newEnd = result.getLastInstruction().(ChiInstruction).getAPredecessor() // does this work?
) and
(
oldBlock.getLastInstruction() = oldEnd and
not oldEnd instanceof OldIR::ChiInstruction
or
oldEnd = oldBlock.getLastInstruction().(OldIR::ChiInstruction).getAPredecessor() // does this work?
) and
oldEnd = getNewInstruction(newEnd)
)
}

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

/**
* 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
canModelResultForOldInstruction(getOldInstruction(instruction)) or
instruction instanceof PhiInstruction or // Phis always have modeled results
instruction instanceof ChiInstruction // Chis always have modeled results
}
Expand Down Expand Up @@ -117,6 +174,32 @@ 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(pragma[only_bind_out](phiOperandOverlap),
pragma[only_bind_out](originalOverlap))
)
)
)
}

cached
private Instruction getMemoryOperandDefinition0(
Instruction instruction, MemoryOperandTag tag, Overlap overlap
Expand Down Expand Up @@ -148,6 +231,12 @@ private module Cached {
overlap instanceof MustExactlyOverlap and
exists(MustTotallyOverlap o | exists(getMemoryOperandDefinition0(instruction, tag, o)))
)
or
exists(OldIR::NonPhiMemoryOperand oldOperand |
result = getNewDefinitionFromOldSSA(oldOperand, overlap) and
oldOperand.getUse() = instruction and
tag = oldOperand.getOperandTag()
)
}

/**
Expand Down Expand Up @@ -214,10 +303,24 @@ 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(
Instruction 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(
PhiInstruction instr, IRBlock newPredecessorBlock, Overlap overlap
Instruction instr, IRBlock newPredecessorBlock, Overlap overlap
) {
exists(
Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock,
Expand All @@ -229,6 +332,8 @@ private module Cached {
result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and
overlap = Alias::getOverlap(actualDefLocation, useLocation)
)
or
result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap)
}

cached
Expand All @@ -249,7 +354,12 @@ private module Cached {
cached
Instruction getPhiInstructionBlockStart(PhiInstruction instr) {
exists(OldBlock oldBlock |
instr = getPhi(oldBlock, _) and
(
instr = getPhi(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 @@ -335,6 +445,9 @@ private module Cached {
result = vvar.getType()
)
or
instr = reusedPhiInstruction(_) and
result = instr.(OldInstruction).getResultLanguageType()
or
instr = unreachedInstruction(_) and result = Language::getVoidType()
}

Expand Down Expand Up @@ -862,6 +975,26 @@ module DefUse {
}
}

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 = phiInstruction(_, 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 Down
Loading