Skip to content

Commit da14647

Browse files
author
Dave Bartolomeo
authored
Merge pull request github#5522 from github/rdmarsh2/cpp/ssa-reuse
C++: reuse unaliased SSA results when computing aliased SSA
2 parents 534e771 + db85a21 commit da14647

File tree

41 files changed

+2171
-201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2171
-201
lines changed

cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) {
665665
exists(LoadInstruction load |
666666
load.getSourceValueOperand() = opTo and
667667
opTo.getAnyDef() = iFrom and
668-
isSingleFieldClass(iFrom.getResultType(), opTo)
668+
isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo)
669669
)
670670
}
671671

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,6 +1994,14 @@ class PhiInstruction extends Instruction {
19941994
*/
19951995
pragma[noinline]
19961996
final Instruction getAnInput() { result = this.getAnInputOperand().getDef() }
1997+
1998+
/**
1999+
* Gets the input operand representing the value that flows from the specified predecessor block.
2000+
*/
2001+
final PhiInputOperand getInputOperand(IRBlock predecessorBlock) {
2002+
result = this.getAnOperand() and
2003+
result.getPredecessorBlock() = predecessorBlock
2004+
}
19972005
}
19982006

19992007
/**

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ class Operand extends TStageOperand {
2828
cached
2929
Operand() {
3030
// Ensure that the operand does not refer to instructions from earlier stages that are unreachable here
31-
exists(Instruction use, Instruction def | this = registerOperand(use, _, def)) or
32-
exists(Instruction use | this = nonSSAMemoryOperand(use, _)) or
31+
exists(Instruction use, Instruction def | this = registerOperand(use, _, def))
32+
or
33+
exists(Instruction use | this = nonSSAMemoryOperand(use, _))
34+
or
3335
exists(Instruction use, Instruction def, IRBlock predecessorBlock |
34-
this = phiOperand(use, def, predecessorBlock, _)
35-
) or
36+
this = phiOperand(use, def, predecessorBlock, _) or
37+
this = reusedPhiOperand(use, def, predecessorBlock, _)
38+
)
39+
or
3640
exists(Instruction use | this = chiOperand(use, _))
3741
}
3842

@@ -431,7 +435,11 @@ class PhiInputOperand extends MemoryOperand, TPhiOperand {
431435
Overlap overlap;
432436

433437
cached
434-
PhiInputOperand() { this = phiOperand(useInstr, defInstr, predecessorBlock, overlap) }
438+
PhiInputOperand() {
439+
this = phiOperand(useInstr, defInstr, predecessorBlock, overlap)
440+
or
441+
this = reusedPhiOperand(useInstr, defInstr, predecessorBlock, overlap)
442+
}
435443

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

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) {
9090
or
9191
// Converting an address to a `bool` does not escape the address.
9292
instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType
93+
or
94+
instr instanceof CallInstruction and
95+
not exists(IREscapeAnalysisConfiguration config | config.useSoundEscapeAnalysis())
9396
)
9497
)
9598
or
@@ -284,14 +287,24 @@ private predicate isArgumentForParameter(
284287
private predicate isOnlyEscapesViaReturnArgument(Operand operand) {
285288
exists(AliasModels::AliasFunction f |
286289
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
287-
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
290+
(
291+
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
292+
or
293+
f.parameterEscapesOnlyViaReturn(-1) and
294+
operand instanceof ThisArgumentOperand
295+
)
288296
)
289297
}
290298

291299
private predicate isNeverEscapesArgument(Operand operand) {
292300
exists(AliasModels::AliasFunction f |
293301
f = operand.getUse().(CallInstruction).getStaticCallTarget() and
294-
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
302+
(
303+
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
304+
or
305+
f.parameterNeverEscapes(-1) and
306+
operand instanceof ThisArgumentOperand
307+
)
295308
)
296309
}
297310

@@ -325,6 +338,9 @@ predicate allocationEscapes(Configuration::Allocation allocation) {
325338
exists(IREscapeAnalysisConfiguration config |
326339
config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction())
327340
)
341+
or
342+
Configuration::phaseNeedsSoundEscapeAnalysis() and
343+
resultEscapesNonReturn(allocation.getABaseInstruction())
328344
}
329345

330346
/**

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ private import AliasConfigurationInternal
22
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
33
private import cpp
44
private import AliasAnalysis
5+
private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SimpleSSA as UnaliasedSSA
56

67
private newtype TAllocation =
7-
TVariableAllocation(IRVariable var) or
8+
TVariableAllocation(IRVariable var) {
9+
// Only model variables that were not already handled in unaliased SSA.
10+
not UnaliasedSSA::canReuseSSAForVariable(var)
11+
} or
812
TIndirectParameterAllocation(IRAutomaticVariable var) {
913
exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var)
1014
} or
@@ -138,3 +142,5 @@ class DynamicAllocation extends Allocation, TDynamicAllocation {
138142

139143
final override predicate alwaysEscapes() { none() }
140144
}
145+
146+
predicate phaseNeedsSoundEscapeAnalysis() { none() }

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import semmle.code.cpp.ir.internal.Overlap
33
private import semmle.code.cpp.ir.internal.IRCppLanguage as Language
44
private import semmle.code.cpp.Print
55
private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR
6+
private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction as OldSSA
67
private import semmle.code.cpp.ir.internal.IntegerConstant as Ints
78
private import semmle.code.cpp.ir.internal.IntegerInterval as Interval
89
private import semmle.code.cpp.ir.implementation.internal.OperandTag
@@ -131,6 +132,8 @@ abstract class MemoryLocation extends TMemoryLocation {
131132
* with automatic storage duration).
132133
*/
133134
predicate isAlwaysAllocatedOnStack() { none() }
135+
136+
final predicate canReuseSSA() { none() }
134137
}
135138

136139
/**
@@ -562,10 +565,17 @@ private Overlap getVariableMemoryLocationOverlap(
562565
use.getEndBitOffset())
563566
}
564567

568+
/**
569+
* Holds if the def/use information for the result of `instr` can be reused from the previous
570+
* iteration of the IR.
571+
*/
572+
predicate canReuseSSAForOldResult(Instruction instr) { OldSSA::canReuseSSAForMemoryResult(instr) }
573+
565574
bindingset[result, b]
566575
private boolean unbindBool(boolean b) { result != b.booleanNot() }
567576

568577
MemoryLocation getResultMemoryLocation(Instruction instr) {
578+
not canReuseSSAForOldResult(instr) and
569579
exists(MemoryAccessKind kind, boolean isMayAccess |
570580
kind = instr.getResultMemoryAccess() and
571581
(if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and
@@ -598,6 +608,7 @@ MemoryLocation getResultMemoryLocation(Instruction instr) {
598608
}
599609

600610
MemoryLocation getOperandMemoryLocation(MemoryOperand operand) {
611+
not canReuseSSAForOldResult(operand.getAnyDef()) and
601612
exists(MemoryAccessKind kind, boolean isMayAccess |
602613
kind = operand.getMemoryAccess() and
603614
(if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and

cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,81 @@ private module Cached {
4343
class TStageInstruction =
4444
TRawInstruction or TPhiInstruction or TChiInstruction or TUnreachedInstruction;
4545

46+
/**
47+
* If `oldInstruction` is a `Phi` instruction that has exactly one reachable predecessor block,
48+
* this predicate returns the `PhiInputOperand` corresponding to that predecessor block.
49+
* Otherwise, this predicate does not hold.
50+
*/
51+
private OldIR::PhiInputOperand getDegeneratePhiOperand(OldInstruction oldInstruction) {
52+
result =
53+
unique(OldIR::PhiInputOperand operand |
54+
operand = oldInstruction.(OldIR::PhiInstruction).getAnInputOperand() and
55+
operand.getPredecessorBlock() instanceof OldBlock
56+
)
57+
}
58+
4659
cached
4760
predicate hasInstruction(TStageInstruction instr) {
4861
instr instanceof TRawInstruction and instr instanceof OldInstruction
4962
or
50-
instr instanceof TPhiInstruction
63+
instr = phiInstruction(_, _)
64+
or
65+
instr = reusedPhiInstruction(_) and
66+
// Check that the phi instruction is *not* degenerate, but we can't use
67+
// getDegeneratePhiOperand in the first stage with phi instyructions
68+
not exists(
69+
unique(OldIR::PhiInputOperand operand |
70+
operand = instr.(OldIR::PhiInstruction).getAnInputOperand() and
71+
operand.getPredecessorBlock() instanceof OldBlock
72+
)
73+
)
5174
or
5275
instr instanceof TChiInstruction
5376
or
5477
instr instanceof TUnreachedInstruction
5578
}
5679

57-
private IRBlock getNewBlock(OldBlock oldBlock) {
58-
result.getFirstInstruction() = getNewInstruction(oldBlock.getFirstInstruction())
80+
cached
81+
IRBlock getNewBlock(OldBlock oldBlock) {
82+
exists(Instruction newEnd, OldIR::Instruction oldEnd |
83+
(
84+
result.getLastInstruction() = newEnd and
85+
not newEnd instanceof ChiInstruction
86+
or
87+
newEnd = result.getLastInstruction().(ChiInstruction).getAPredecessor() // does this work?
88+
) and
89+
(
90+
oldBlock.getLastInstruction() = oldEnd and
91+
not oldEnd instanceof OldIR::ChiInstruction
92+
or
93+
oldEnd = oldBlock.getLastInstruction().(OldIR::ChiInstruction).getAPredecessor() // does this work?
94+
) and
95+
oldEnd = getNewInstruction(newEnd)
96+
)
97+
}
98+
99+
/**
100+
* Gets the block from the old IR that corresponds to `newBlock`.
101+
*/
102+
private OldBlock getOldBlock(IRBlock newBlock) { getNewBlock(result) = newBlock }
103+
104+
/**
105+
* Holds if this iteration of SSA can model the def/use information for the result of
106+
* `oldInstruction`, either because alias analysis has determined a memory location for that
107+
* result, or because a previous iteration of the IR already computed that def/use information
108+
* completely.
109+
*/
110+
private predicate canModelResultForOldInstruction(OldInstruction oldInstruction) {
111+
// We're modeling the result's memory location ourselves.
112+
exists(Alias::getResultMemoryLocation(oldInstruction))
113+
or
114+
// This result was already modeled by a previous iteration of SSA.
115+
Alias::canReuseSSAForOldResult(oldInstruction)
59116
}
60117

61118
cached
62119
predicate hasModeledMemoryResult(Instruction instruction) {
63-
exists(Alias::getResultMemoryLocation(getOldInstruction(instruction))) or
120+
canModelResultForOldInstruction(getOldInstruction(instruction)) or
64121
instruction instanceof PhiInstruction or // Phis always have modeled results
65122
instruction instanceof ChiInstruction // Chis always have modeled results
66123
}
@@ -117,6 +174,32 @@ private module Cached {
117174
)
118175
}
119176

177+
/**
178+
* Gets the new definition instruction for `oldOperand` based on `oldOperand`'s definition in the
179+
* old IR. Usually, this will just get the old definition of `oldOperand` and map it to the
180+
* corresponding new instruction. However, if the old definition of `oldOperand` is a `Phi`
181+
* instruction that is now degenerate due all but one of its predecessor branches being
182+
* unreachable, this predicate will recurse through any degenerate `Phi` instructions to find the
183+
* true definition.
184+
*/
185+
private Instruction getNewDefinitionFromOldSSA(OldIR::MemoryOperand oldOperand, Overlap overlap) {
186+
exists(Overlap originalOverlap |
187+
originalOverlap = oldOperand.getDefinitionOverlap() and
188+
(
189+
result = getNewInstruction(oldOperand.getAnyDef()) and
190+
overlap = originalOverlap
191+
or
192+
exists(OldIR::PhiInputOperand phiOperand, Overlap phiOperandOverlap |
193+
phiOperand = getDegeneratePhiOperand(oldOperand.getAnyDef()) and
194+
result = getNewDefinitionFromOldSSA(phiOperand, phiOperandOverlap) and
195+
overlap =
196+
combineOverlap(pragma[only_bind_out](phiOperandOverlap),
197+
pragma[only_bind_out](originalOverlap))
198+
)
199+
)
200+
)
201+
}
202+
120203
cached
121204
private Instruction getMemoryOperandDefinition0(
122205
Instruction instruction, MemoryOperandTag tag, Overlap overlap
@@ -148,6 +231,12 @@ private module Cached {
148231
overlap instanceof MustExactlyOverlap and
149232
exists(MustTotallyOverlap o | exists(getMemoryOperandDefinition0(instruction, tag, o)))
150233
)
234+
or
235+
exists(OldIR::NonPhiMemoryOperand oldOperand |
236+
result = getNewDefinitionFromOldSSA(oldOperand, overlap) and
237+
oldOperand.getUse() = instruction and
238+
tag = oldOperand.getOperandTag()
239+
)
151240
}
152241

153242
/**
@@ -214,10 +303,24 @@ private module Cached {
214303
)
215304
}
216305

306+
/**
307+
* Gets the new definition instruction for the operand of `instr` that flows from the block
308+
* `newPredecessorBlock`, based on that operand's definition in the old IR.
309+
*/
310+
private Instruction getNewPhiOperandDefinitionFromOldSSA(
311+
Instruction instr, IRBlock newPredecessorBlock, Overlap overlap
312+
) {
313+
exists(OldIR::PhiInstruction oldPhi, OldIR::PhiInputOperand oldOperand |
314+
oldPhi = getOldInstruction(instr) and
315+
oldOperand = oldPhi.getInputOperand(getOldBlock(newPredecessorBlock)) and
316+
result = getNewDefinitionFromOldSSA(oldOperand, overlap)
317+
)
318+
}
319+
217320
pragma[noopt]
218321
cached
219322
Instruction getPhiOperandDefinition(
220-
PhiInstruction instr, IRBlock newPredecessorBlock, Overlap overlap
323+
Instruction instr, IRBlock newPredecessorBlock, Overlap overlap
221324
) {
222325
exists(
223326
Alias::MemoryLocation defLocation, Alias::MemoryLocation useLocation, OldBlock phiBlock,
@@ -229,6 +332,8 @@ private module Cached {
229332
result = getDefinitionOrChiInstruction(defBlock, defOffset, defLocation, actualDefLocation) and
230333
overlap = Alias::getOverlap(actualDefLocation, useLocation)
231334
)
335+
or
336+
result = getNewPhiOperandDefinitionFromOldSSA(instr, newPredecessorBlock, overlap)
232337
}
233338

234339
cached
@@ -249,7 +354,12 @@ private module Cached {
249354
cached
250355
Instruction getPhiInstructionBlockStart(PhiInstruction instr) {
251356
exists(OldBlock oldBlock |
252-
instr = getPhi(oldBlock, _) and
357+
(
358+
instr = getPhi(oldBlock, _)
359+
or
360+
// Any `Phi` that we propagated from the previous iteration stays in the same block.
361+
getOldInstruction(instr).getBlock() = oldBlock
362+
) and
253363
result = getNewInstruction(oldBlock.getFirstInstruction())
254364
)
255365
}
@@ -335,6 +445,9 @@ private module Cached {
335445
result = vvar.getType()
336446
)
337447
or
448+
instr = reusedPhiInstruction(_) and
449+
result = instr.(OldInstruction).getResultLanguageType()
450+
or
338451
instr = unreachedInstruction(_) and result = Language::getVoidType()
339452
}
340453

@@ -862,6 +975,26 @@ module DefUse {
862975
}
863976
}
864977

978+
predicate canReuseSSAForMemoryResult(Instruction instruction) {
979+
exists(OldInstruction oldInstruction |
980+
oldInstruction = getOldInstruction(instruction) and
981+
(
982+
// The previous iteration said it was reusable, so we should mark it as reusable as well.
983+
Alias::canReuseSSAForOldResult(oldInstruction)
984+
or
985+
// The current alias analysis says it is reusable.
986+
Alias::getResultMemoryLocation(oldInstruction).canReuseSSA()
987+
)
988+
)
989+
or
990+
exists(Alias::MemoryLocation defLocation |
991+
// This is a `Phi` for a reusable location, so the result of the `Phi` is reusable as well.
992+
instruction = phiInstruction(_, defLocation) and
993+
defLocation.canReuseSSA()
994+
)
995+
// We don't support reusing SSA for any location that could create a `Chi` instruction.
996+
}
997+
865998
/**
866999
* Expose some of the internal predicates to PrintSSA.qll. We do this by publically importing those modules in the
8671000
* `DebugSSA` module, which is then imported by PrintSSA.

0 commit comments

Comments
 (0)