Skip to content

Commit c62220e

Browse files
committed
C++: Fix data-flow dispatch perf with globals
There wasn't a good join order for the "store to global var" case in the virtual dispatch library. When a global variable had millions of accesses but few stores to it, the `flowsFrom` predicate would join to see all those millions of accesses before filtering down to stores only. The solution is to pull out a `storeIntoGlobal` helper predicate that pre-computes which accesses are stores. To make the code clearer, I've also pulled out a repeated chunk of code into a new `addressOfGlobal` helper predicate. For the kamailio/kamailio project, these are the tuple counts before: Starting to evaluate predicate DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#cur_delta/3[3]@21a1df (iteration 3) Tuple counts for DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#cur_delta: ... 59002 ~0% {3} r17 = SCAN DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#prev_delta AS I OUTPUT I.<1>, true, I.<0> 58260 ~1% {3} r31 = JOIN r17 WITH DataFlowUtil::Node::asVariable_dispred#fb AS R ON FIRST 1 OUTPUT R.<1>, true, r17.<2> 2536187389 ~6% {3} r32 = JOIN r31 WITH Instruction::VariableInstruction::getASTVariable_dispred#fb_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, true, r31.<2> 2536187389 ~6% {3} r33 = JOIN r32 WITH project#Instruction::VariableAddressInstruction#class#3#ff AS R ON FIRST 1 OUTPUT r32.<0>, true, r32.<2> 58208 ~0% {3} r34 = JOIN r33 WITH Instruction::StoreInstruction::getDestinationAddress_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, true, r33.<2> Tuple counts after: Starting to evaluate predicate DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#cur_delta/3[3]@6073c5 (iteration 3) Tuple counts for DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#cur_delta: ... 59002 ~0% {3} r17 = SCAN DataFlowDispatch::VirtualDispatch::DataSensitiveCall::flowsFrom#fff#prev_delta AS I OUTPUT I.<1>, true, I.<0> 58260 ~1% {3} r23 = JOIN r17 WITH DataFlowUtil::Node::asVariable_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, true, r17.<2> 58208 ~0% {3} r24 = JOIN r23 WITH DataFlowDispatch::VirtualDispatch::storeIntoGlobal#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, true, r23.<2> 58208 ~0% {3} r25 = JOIN r24 WITH DataFlowUtil::InstructionNode#ff_10#join_rhs AS R ON FIRST 1 OUTPUT true, r24.<2>, R.<1> Notice that the final tuple count, 58208, is the same before and after. The kamailio/kamailio project seems to have been affected by this issue because it has global variables to do with logging policy, and these variables are loaded from in every place where their logging macro is used.
1 parent 00078d1 commit c62220e

File tree

1 file changed

+21
-23
lines changed

1 file changed

+21
-23
lines changed

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

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,47 +82,45 @@ private module VirtualDispatch {
8282
exists(LoadInstruction load, GlobalOrNamespaceVariable var |
8383
var = src.asVariable() and
8484
other.asInstruction() = load and
85+
addressOfGlobal(load.getSourceAddress(), var) and
8586
// The `allowFromArg` concept doesn't play a role when `src` is a
8687
// global variable, so we just set it to a single arbitrary value for
8788
// performance.
8889
allowFromArg = true
89-
|
90-
// Load directly from the global variable
91-
load.getSourceAddress().(VariableAddressInstruction).getASTVariable() = var
92-
or
93-
// Load from a field on a global union
94-
exists(FieldAddressInstruction fa |
95-
fa = load.getSourceAddress() and
96-
fa.getObjectAddress().(VariableAddressInstruction).getASTVariable() = var and
97-
fa.getField().getDeclaringType() instanceof Union
98-
)
9990
)
10091
or
101-
// Flow from store to global variable. These cases are similar to the
102-
// above but have `StoreInstruction` instead of `LoadInstruction` and
103-
// have the roles swapped between `other` and `src`.
92+
// Flow from store to global variable.
10493
exists(StoreInstruction store, GlobalOrNamespaceVariable var |
10594
var = other.asVariable() and
10695
store = src.asInstruction() and
96+
storeIntoGlobal(store, var) and
10797
// Setting `allowFromArg` to `true` like in the base case means we
10898
// treat a store to a global variable like the dispatch itself: flow
10999
// may come from anywhere.
110100
allowFromArg = true
111-
|
112-
// Store directly to the global variable
113-
store.getDestinationAddress().(VariableAddressInstruction).getASTVariable() = var
114-
or
115-
// Store to a field on a global union
116-
exists(FieldAddressInstruction fa |
117-
fa = store.getDestinationAddress() and
118-
fa.getObjectAddress().(VariableAddressInstruction).getASTVariable() = var and
119-
fa.getField().getDeclaringType() instanceof Union
120-
)
121101
)
122102
)
123103
}
124104
}
125105

106+
pragma[noinline]
107+
private predicate storeIntoGlobal(StoreInstruction store, GlobalOrNamespaceVariable var) {
108+
addressOfGlobal(store.getDestinationAddress(), var)
109+
}
110+
111+
/** Holds if `addressInstr` is an instruction that produces the address of `var`. */
112+
private predicate addressOfGlobal(Instruction addressInstr, GlobalOrNamespaceVariable var) {
113+
// Access directly to the global variable
114+
addressInstr.(VariableAddressInstruction).getASTVariable() = var
115+
or
116+
// Access to a field on a global union
117+
exists(FieldAddressInstruction fa |
118+
fa = addressInstr and
119+
fa.getObjectAddress().(VariableAddressInstruction).getASTVariable() = var and
120+
fa.getField().getDeclaringType() instanceof Union
121+
)
122+
}
123+
126124
/**
127125
* A ReturnNode with its ReturnKind and its enclosing callable.
128126
*

0 commit comments

Comments
 (0)