C++: Fix two more joins - #22513
Conversation
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The rewrites preserve existing predicate semantics while directly addressing the documented performance problems.
Review tier: Balanced
Findings: None
What changed in this PR
Optimizes two C++ data-flow joins while preserving their semantics.
Changes:
- Restricts return-node tuples before joining return kinds.
- Splits store-step matching into smaller source and target joins.
| File | Description |
|---|---|
DataFlowPrivate.qll |
Restructures store-step joins to reduce tuple duplication. |
FlowSummaryImpl.qll |
Delays return-kind lookup until the return node is bound. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
paldepind
left a comment
There was a problem hiding this comment.
Looks good to me. It seems like you forgot the summary line in the commit messages though, they only have a long description which makes them look wonky.
|
Fair enough 😂 |
Before:
```
[2026-09-04 12:36:27] Evaluated non-recursive predicate FlowSummaryImpl::Input2::hasKindAndEnclosingFunction/3#3e350d0c@603f9du6 in 374ms (size: 604170).
Evaluated relational algebra for predicate FlowSummaryImpl::Input2::hasKindAndEnclosingFunction/3#3e350d0c@603f9du6 with tuple counts:
672310 ~0% {2} r1 = JOIN `cached_ResolveFunction::isFunction/1#9226b83f` WITH DataFlowPrivate::TSourceCallable#54d42094 ON FIRST 1 OUTPUT Rhs.1, Lhs.0
34670069 ~2% {2} | JOIN WITH `DataFlowUtil::Node.getEnclosingCallable/0#dispred#74002437_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1
604170 ~0% {3} | JOIN WITH `DataFlowPrivate::ReturnNode.getKind/0#dispred#c7586c0b` ON FIRST 1 OUTPUT Lhs.1, Rhs.1, Lhs.0
return r1
```
After:
```
[2026-09-04 16:46:20] Evaluated non-recursive predicate FlowSummaryImpl::Input2::hasKindAndEnclosingFunction/3#3e350d0c@1ded09ff in 1158ms (size: 604170).
Evaluated relational algebra for predicate FlowSummaryImpl::Input2::hasKindAndEnclosingFunction/3#3e350d0c@1ded09ff with tuple counts:
614924 ~1% {3} r1 = JOIN `DataFlowPrivate::ReturnNode.getKind/0#dispred#c7586c0b` WITH `DataFlowUtil::Node.getEnclosingCallable/0#dispred#74002437` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.0
604170 ~5% {3} | JOIN WITH DataFlowPrivate::TSourceCallable#54d42094_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2
604170 ~5% {3} | JOIN WITH `cached_ResolveFunction::isFunction/1#9226b83f` ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2
return r1
```
Before (notice the large tuple duplication):
```
[2026-09-04 12:36:27] Evaluated non-recursive predicate DataFlowPrivate::storeStepImpl/4#5a9e2fd2@8e5724c9 in 1535ms (size: 189539).
Evaluated relational algebra for predicate DataFlowPrivate::storeStepImpl/4#5a9e2fd2@8e5724c9 with tuple counts:
20361 ~4% {3} r1 = JOIN DataFlowNodes::TFlowSummaryNode#d5706fd6 WITH `FlowSummaryImpl::Private::Steps::summaryStoreStep/3#a7d89e4d` ON FIRST 1 OUTPUT Rhs.2, Lhs.1, Rhs.1
20361 ~0% {4} | JOIN WITH DataFlowNodes::TFlowSummaryNode#d5706fd6 ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Rhs.1, _
20361 ~0% {4} | REWRITE WITH Out.3 := true
27390490 ~0% {3} r2 = SCAN `DataFlowPrivate::nodeHasInstruction/3#f469bb06` OUTPUT In.1, In.0, In.2
1146769 ~1% {3} | JOIN WITH `Instruction::StoreInstruction.getDestinationAddressOperand/0#dispred#596a4aba` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2
1251911 ~0% {6} | JOIN WITH `DataFlowPrivate::numberOfLoadsFromOperand/4#7e555666_1023#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Rhs.3, _, Lhs.2, Rhs.2
1251911 ~3% {4} | REWRITE WITH Tmp.3 := 1, Out.3 := (Tmp.3 + In.4 + In.5) KEEPING 4
354360 ~0% {6} | JOIN WITH DataFlowNodes::PostFieldUpdateNode#ba49e082_1023#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Rhs.2, Rhs.3
354360 ~0% {8} | JOIN WITH DataFlowNodes::TPostUpdateNodeImpl#15a1088b_21#join_rhs ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, Lhs.0, Lhs.4, Lhs.5, Rhs.1, _
{7} | REWRITE WITH Tmp.7 := 1, TEST InOut.6 = Tmp.7 KEEPING 7
170359 ~1% {6} | SCAN OUTPUT In.3, In.4, In.5, In.0, In.1, In.2
697388 ~3% {5} | JOIN WITH DataFlowNodes::PostFieldUpdateNode#ba49e082_0231#join_rhs ON FIRST 3 OUTPUT Rhs.3, Lhs.3, Lhs.4, Lhs.5, Lhs.0
697555 ~302% {5} | JOIN WITH `DataFlowNodes::FieldAddress.getField/0#dispred#fea3b845` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4
895112 ~156% {5} | JOIN WITH `DataFlowUtil::FieldContent.getAField/0#dispred#ba1c91e5_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.3, Lhs.1, Lhs.2, Lhs.4
417949 ~139% {4} | JOIN WITH `DataFlowUtil::Content.getIndirectionIndex/0#dispred#c14b335b` ON FIRST 2 OUTPUT Lhs.2, Lhs.0, Lhs.4, Lhs.3
438310 ~121% {4} r3 = r1 UNION r2
return r3
```
After:
```
[2026-09-04 16:30:27] Evaluated non-recursive predicate DataFlowPrivate::storeStepTarget/4#0e049f2e@635fa9rj in 72ms (size: 699622).
Evaluated relational algebra for predicate DataFlowPrivate::storeStepTarget/4#0e049f2e@635fa9rj with tuple counts:
702928 ~0% {4} r1 = JOIN `DataFlowPrivate::hasFieldAndIndirectionIndex/3#a1231a07` WITH `DataFlowPrivate::hasFieldAddressAndField/3#35a98069` ON FIRST 1 OUTPUT Rhs.2, Lhs.1, Rhs.1, Lhs.2
return r1
[2026-09-04 16:30:38] Evaluated non-recursive predicate DataFlowPrivate::storeStepSource/4#765254a7@d27b3d3c in 163ms (size: 1251911).
Evaluated relational algebra for predicate DataFlowPrivate::storeStepSource/4#765254a7@d27b3d3c with tuple counts:
863285 ~0% {2} r1 = JOIN `Instruction::StoreInstruction.getDestinationAddressOperand/0#dispred#596a4aba` WITH Instruction::StoreInstruction#ae96f30c ON FIRST 1 OUTPUT Lhs.0, Lhs.1
1146769 ~2% {3} | JOIN WITH `DataFlowPrivate::nodeHasInstruction/3#f469bb06_102#join_rhs` ON FIRST 1 OUTPUT Lhs.1, Rhs.1, Rhs.2
1251911 ~1% {6} | JOIN WITH `DataFlowPrivate::numberOfLoadsFromOperand/4#7e555666_1023#join_rhs` ON FIRST 1 OUTPUT Rhs.1, _, Lhs.1, Rhs.3, Lhs.2, Rhs.2
1251911 ~1% {4} | REWRITE WITH Tmp.1 := 1, Out.1 := (Tmp.1 + In.4 + In.5) KEEPING 4
return r1
[2026-09-04 16:30:38] Evaluated non-recursive predicate DataFlowPrivate::storeStepImpl/4#5a9e2fd2@ddd0439f in 70ms (size: 189539).
Evaluated relational algebra for predicate DataFlowPrivate::storeStepImpl/4#5a9e2fd2@ddd0439f with tuple counts:
169178 ~0% {4} r1 = JOIN `DataFlowPrivate::storeStepTarget/4#0e049f2e` WITH `DataFlowPrivate::storeStepSource/4#765254a7` ON FIRST 2 OUTPUT Rhs.2, Lhs.3, Lhs.2, Rhs.3
20361 ~4% {3} r2 = JOIN DataFlowNodes::TFlowSummaryNode#d5706fd6 WITH `FlowSummaryImpl::Private::Steps::summaryStoreStep/3#a7d89e4d` ON FIRST 1 OUTPUT Rhs.2, Lhs.1, Rhs.1
20361 ~0% {4} | JOIN WITH DataFlowNodes::TFlowSummaryNode#d5706fd6 ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Rhs.1, _
20361 ~0% {4} | REWRITE WITH Out.3 := true
189539 ~0% {4} r3 = r1 UNION r2
return r3
```
ca2f9e0 to
0208f19
Compare
paldepind
left a comment
There was a problem hiding this comment.
Thanks for fixing the commit messages. LGTM assuming DCA comes back without complaints :)
This PR fixes two more bad joins.
The first one is pretty obvious: we should start by reducing the set of tuples to
ReturnNodes before joining to grab all the enclosing callable.The next one is a minor performance improvement. It doesn't fix a tuple count explosion from a bad join, but rather reduces the tuple duplication throughout pipeline. This may not look very bad, but I was seeing way larger numbers on an internal Microsoft repo which suggests that the rewrite is worth it:
Notice that the true number of tuples is ~750K, but we end up operating on a pipeline with ~4B tuples.