Skip to content

[SwiftWarningControl] Fix exponential queue growth in addWarningGroupControls#3344

Open
inju2403 wants to merge 2 commits into
swiftlang:mainfrom
inju2403:inju2403/fix-warning-control-bfs-explosion
Open

[SwiftWarningControl] Fix exponential queue growth in addWarningGroupControls#3344
inju2403 wants to merge 2 commits into
swiftlang:mainfrom
inju2403:inju2403/fix-warning-control-bfs-explosion

Conversation

@inju2403

@inju2403 inju2403 commented May 26, 2026

Copy link
Copy Markdown

Problem

addWarningGroupControls walks the diagnostic-group inheritance tree via BFS to propagate a control to every transitive sub-group. The implementation has two issues:

  1. O(N²) per BFS: Array.removeFirst() shifts the rest of the queue on every dequeue.
  2. No de-dup at dequeue: processedGroups is only checked when enqueuing sub-groups. A group reachable from more than one already-queued parent is enqueued multiple times, has its control
    re-applied each time, and (more importantly) re-expands its own sub-groups each time it's processed.

For a diamond-shaped inheritance DAG (each level has 2 nodes, both pointing to both nodes of the next level), the queue grows as 2^depth, so even modest depths (>= 10) effectively hang.

Fix

  • Use an index-based queue instead of Array.removeFirst().
  • De-duplicate at dequeue using processedGroups.insert(_).inserted.

Enqueue-time filtering is kept as an optimization, but it isn't sufficient on its own: between the moment a sub-group is filtered and the moment it would be dequeued, another path through the inheritance tree can still enqueue the same sub-group. The processedGroups.insert(_).inserted check at dequeue is what actually guarantees each group is processed at most once.

The loop now processes each distinct sub-group exactly once (the enqueue-time filter still admits duplicates that the dequeue-time check then skips, so the bound is O(N + E) where E is the number of edges in the inheritance graph). This matches the insert(_).inserted pattern already used elsewhere in this module (DiagnosticGroupInheritanceTree.hasCycle) and elsewhere in the repo (SwiftOperators/PrecedenceGraph, SwiftSyntax/Raw/RawSyntaxArena), and does not introduce any new dependencies.

Note: I left the enabledGroups.insert(diagnosticGroupIdentifier) line referencing the outer loop variable rather than groupIdentifier. Whether sub-groups that inherit a non-ignored control should also appear in enabledGroups looks like a separate semantic question that I'd rather not bundle into this perf fix -- happy to address it in a follow-up if reviewers prefer that semantic.

Measurements

Tests/PerformanceTest/WarningControlRegionsPerformanceTests.swift measures three workloads using _InstructionCounter:

workload BEFORE AFTER speedup
chain depth=800 22.4M instr 14.5M instr 1.55x
fan width=1600 41.4M instr 27.8M instr 1.49x
diamond depth=8 5.27M instr 0.54M instr 9.81x
diamond depth>=10 effectively hangs terminates unbounded

A regression test (testDiamondSubGroupInheritance) is added to the default test suite to pin down the expected behavior on a diamond-shaped inheritance graph (perf tests are gated behind SWIFTSYNTAX_ENABLE_LONG_TESTS).

All 27 existing SwiftWarningControlTest cases pass.

…Controls

The BFS over the inheritance tree used `Array.removeFirst()` (O(N) per call) and only checked `processedGroups` at enqueue time, so a sub-group reachable from multiple already-queued parents would be enqueued (and re-processed) multiple times. On diamond-shaped inheritance DAGs the queue grew as 2^depth, so even modest depths (>= 10) effectively hung.

Switch to an index-based queue and de-dupe at dequeue time, making the loop O(N) in the number of distinct sub-groups.

Adds a performance test exercising chain, fan, and diamond-DAG workloads.
Exercises the path that previously could re-process a sub-group reachable from multiple parents, complementing the performance test (which proves the queue no longer explodes).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant