https://github.com/NagyDonat commented:
I'm really happy that you managed to track down and fix these slow corner cases, and overall I like the code changes, but I there were a few places where the code was difficult to understand for me (although this may be also related to the fact that I didn't sleep enough last night... :wink:). I added a few inline comments to mark some confusing parts. Moreover, I also felt that it would be possible to implement the exhaustion "event" more elegantly with a different approach -- and I'll discuss in the rest of this "main" review comment. If I understand correctly, your approach does the following steps: 1. The constructor of `RegionManagerStore` (usually) initializes `RegionStoreMaxBindingFanOut` to `getOptions().RegionStoreMaxBindingFanOut + 1` -- I think this off-by-one difference between identically named variables is a serious drawback of your approach. 2. `RegionManagerStore::getRegionBindings()` uses this adjusted fan-out value to construct the `RegionBindingsRef` object and initialize its field `BindingsLeft`. 3. `BindingsLeft` is decremented by the `RegionBindingsRef::add` method (which adds _a cluster_). - if `BindingsLeft` is already zero, it's not decremented (so it won't overflow) - is this branch actually used? - if not, then it should be replaced by an assertion - there is an exceptional case where `removeSubRegionBindings` uses the 3-argument form of the `add` method to avoid decrementing the `BindingsLeft` counter. 4. If `BindingsLeft == 1`, then `RegionBindingsRef::addBinding` hijacks the call and adds a default binding instead of the one that's specified by its argument. 5. If `BindingsLeft == 0`, then (as far as I see) early return branches with `if (B.hasExhaustedBindingLimit())` stop most (all?) code paths that would add bindings or clusters. 6. On these early return branches `escapeValue()` or `escapeValues()` calls ensure that the values that we ignored are marked as escaped. In this model there are three phases of the binding creation process: 1. `BindingsLeft > 1`, bindings are created normally 2. `BindingsLeft == 1`, intermediate state: - `add` works, can add clusters and can decrement `BindingsLeft` to zero - if `BindingsLeft` is decremented to zero here, then I don't think that the "add a default binding" step happens - `addBindings` adds a default binding (which decrements `BindingsLeft`) and records that the `SVal` specified in its argument has escaped 3. `BindingsLeft == 0`, exhausted state: - `hasExhaustedBindingLimit()` early returns prevent most (all?) addition of bindings - but e.g. the three-argument `add` within `removeSubRegionBindings` still works - and perhaps there are some other code paths... (I spent hours on trying to understand this, but I'm still not completely confident that I got every corner case right.) I didn't find anything that is outright buggy, but sometimes I felt that the code tries to preserve an invariant (e.g. "default binding is always added just before exhaustion"), but there is another branch where I don't see why is it preserved. To simplify the picture a bit, I'd suggest the following alternative approach: 1. The constructor of `RegionManagerStore` always initializes `RegionStoreMaxBindingFanOut` to `getOptions().RegionStoreMaxBindingFanOut` -- to avoid discrepancies and confusion between them. 2. If this fan-out value is nonzero, then `RegionManagerStore::getRegionBindings()` uses it directly to initialize `RegionBindingsRef::BindingsLeft` (i.e. it does not add one). - If the fan-out value is zero (which means unlimited), `BindingsLeft` may be initialized to `-1u`. - Alternatively, perhaps it would be more modern (but slightly more verbose) to say that `BindingsLeft` is an `std::optional<unsigned>` and it's initialized to `std:nullopt` when there is no limit (i.e. when using `getRegionBindingsWithUnboundedLimit` or when `RegionStoreMaxBindingFanOut == 0`). 3. **(unchanged)** `BindingsLeft` is decremented by the `RegionBindingsRef::add` method (which adds _a cluster_). - if `BindingsLeft` is already zero, it's not decremented (so it won't overflow) - is this branch actually used? -- if not, then it should be replaced by an assertion - there is an exceptional case where `removeSubRegionBindings` uses the 3-argument form of the `add` method to avoid decrementing the `BindingsLeft` counter. 4. _There is no highjacking logic and `BindingsLeft == 1` has no special meaning._ 5. **(unchanged)** If `BindingsLeft == 0`, then `B.hasExhaustedBindingLimit()` is true, which triggers early return on most (all?) code paths that would add bindings or clusters. 6. `escapeValue` and `escapeValues` are replaced by more general functions that - still mark the "leftover" values as escaped - but also add a default binding (with unknown value) to mark that the `RecordBindingsRef` stores incomplete information. WDYT about this alternative architecture? https://github.com/llvm/llvm-project/pull/127602 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits