[all-commits] [llvm/llvm-project] ad3f09: [ADT] Add single-pass merge for ImmutableSet/Immut...

Gábor Horváth via All-commits all-commits at lists.llvm.org
Tue Jul 21 04:33:50 PDT 2026


  Branch: refs/heads/main
  Home:   https://github.com/llvm/llvm-project
  Commit: ad3f09c7de3da1f023bc1134eaa33be6d39cc9ca
      https://github.com/llvm/llvm-project/commit/ad3f09c7de3da1f023bc1134eaa33be6d39cc9ca
  Author: Gábor Horváth <xazax.hun at gmail.com>
  Date:   2026-07-21 (Tue, 21 Jul 2026)

  Changed paths:
    M clang/include/clang/Analysis/Analyses/LifetimeSafety/Utils.h
    M clang/lib/Analysis/LiveVariables.cpp
    M llvm/benchmarks/CMakeLists.txt
    A llvm/benchmarks/ImmutableMergeBM.cpp
    M llvm/include/llvm/ADT/ImmutableMap.h
    M llvm/include/llvm/ADT/ImmutableSet.h
    M llvm/unittests/ADT/ImmutableMapTest.cpp
    M llvm/unittests/ADT/ImmutableSetTest.cpp

  Log Message:
  -----------
  [ADT] Add single-pass merge for ImmutableSet/ImmutableMap (#209807)

The immutable-set/map dataflow joins merged two containers by inserting
the
elements of one into the other one at a time, costing O(|B| * log|A|)
and
re-copying shared spine nodes on every insert.

Add a single-pass, structure-sharing tree merge on ImutAVLFactory
(mergeTrees), exposed as ImmutableSet::Factory::unionSets and
ImmutableMap::Factory::mergeWith. It recurses over the larger operand
and
splits the smaller at each key, returning non-overlapping subtrees
unchanged,
so each spine node is copied at most once: O(|B| * log(|A|/|B| + 1)).

Two flags tune it for the different joins:
* KeepUnmatched - keep keys unique to one side (set union / a lattice
join
with an identity) vs. pass them through the combiner (a symmetric join,
   e.g. liveness Must->Maybe).
* SkipShared - return a pointer-identical subtree unchanged in O(1).
Valid
for an idempotent merge (Combine(a, a) == a), which holds for set union
and
every lattice join. Since dataflow states are path-copied from one
another,
a join's operands usually share most subtrees by pointer, so a join that
   touches only a few keys becomes nearly O(diff).

Wire it into the Clang lifetime-safety analysis (set and map joins) and
the
LiveVariables merge, and add ImmutableMergeBM to track it.

Speedups on the lifetime-safety dataflow benchmark (LoanPropagation
phase):
      pointer cycle in a loop  ~2.6x   (1.59 s  -> 0.61 s)
      CFG merges               ~190x   (1.83 s  -> 9.5 ms)
switch fan-out ~1.9x (LiveOrigins; disjoint states, no skip)
LiveVariables is ~2-3x faster on liveness-heavy inputs and neutral on
typical
code (the join is a small fraction of real-code analysis time).

ImmutableMergeBM confirms the merge beats the add loop for small and
near-identical operands, except for independently-built sets differing
by
exactly one element; when one operand is derived from the other -- the
real
dataflow case -- the pointer-skip makes even that a large win. New
randomized
ADT stress tests check the merge against std::set/std::map oracles for
both
canonicalizing and non-canonicalizing factories.

Assisted by: Claude Opus 4.8

Co-authored-by: Gabor Horvath <gaborh at apple.com>



To unsubscribe from these emails, change your notification settings at https://github.com/llvm/llvm-project/settings/notifications


More information about the All-commits mailing list