tlib: order Tree containers with an explicit comparator - #1281
Conversation
std::less<CTree*> was specialized to order trees by serial() so that container iteration stays deterministic. libc++ rewrites std::less<T> to the transparent std::less<> on the container insert path, so std::set<Tree> and std::map<Tree, T> get built in address order but queried in serial order. Lookups then miss entries that are present: symlistVisit() revisits signals it has already marked and recurses until the 16 MB compiler stack is exhausted. 223 of the 296 examples/*.dsp segfault on a libc++ build, intermittently, since it depends on the addresses malloc happens to return. Specializing std::less for a program-defined type is undefined behaviour in any case: a specialization has to meet the requirements of the primary template, and std::less<T*> is required to yield the implementation-defined strict total order over pointers. Replace the specialization with a treeorder comparator plus TreeSet and TreeMap aliases, and use them wherever a container is keyed by Tree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks @jcelerier. The thing is that @orlarey is currently working on this https://github.com/grame-cncm/faust/tree/master-dev-speedup-new-signals branch where the tlib component has been deeply reworked and optimised (with IA assistance). So I'm not sure how your fix behaves in this new model. @orlarey may better answer here. |
|
heya! the main thing is that on windows with clang / libc++22, one really has to use an explicit custom comparator for set / map rather than overriding std::less<...>. If there's going to be releases with a revamped tlib, maybe this patch could just be applied to current release branches that have the bug, and this design point taken into account for the future version ? (I could reproduce it as far back as |
|
Use of |
clone-faust.sh cloned whatever grame-cncm/faust's default branch happened to be at build time, so the build was not reproducible and there was nowhere to put a fix. Pin FAUST_VERSION to a master-dev commit and cherry-pick the PRs listed in FAUST_PRS on top, the same way clone-qt.sh carries its Gerrit picks. FAUST_PRS currently holds grame-cncm/faust#1281, without which the compiler crashes intermittently on any libc++ target: 223 of the 296 faust examples segfault on MSYS/CLANG64. AArch64, ARM/RPi2 and WASM cloned faust inline instead of using clone-faust.sh, so they got neither the pin nor the picks. AArch64 and WASM now source it and pass their own backend selection through FAUST_BACKENDS; ARM/RPi2 repeats the sequence because its docker context cannot reach common/. The generated backends/llvm.cmake is byte-identical on all four. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
yep, reverting it fixes the issue. minimal repro that fails: |
Problem
compiler/tlib/tree.hhspecializesstd::less<CTree*>to order trees byserial(), so that iterating an ordered container of trees is reproducible.libc++ (20+) has a
__make_transparentoptimisation that rewritesstd::less<T>to the transparentstd::less<>when it recognises the comparator as the default one:It applies on the container insert path (
__tree::__find_equal), but not on the lookup path (__tree::__count_unique, which callsvalue_comp()). Everystd::set<Tree>/std::map<Tree, T>in the compiler is therefore built in address order and queried in serial order.The result is a container whose lookups miss entries that are present. In
symlistVisit()thevisitedset stops blocking revisits, so the walk re-descends into signals it has already marked and recurses until the 16 MB compiler thread stack is exhausted:with the same five
Treepointers cycling forever. Instrumenting the set at the point of divergence:Whether it crashes depends on the addresses
mallochappens to return, so it is intermittent —process = (+ ~ _), (+ ~ _);fails about half of the time.CTree::plistisstd::map<Tree, Tree>, so the property/memoization layer is affected too, not justsymlist.Specializing
std::lessfor a program-defined type is undefined behaviour in any case: [namespace.std] allows adding a specialization only if it "meets the standard library requirements for the original template", andstd::less<T*>is required to yield the implementation-defined strict total order over pointers. libc++ is entitled to assumestd::less<T>means<.Fix
Replace the specialization with a named
treeordercomparator, plusTreeSet/TreeMap<T>aliases, and use them wherever a container is keyed byTree. A named comparator is not pattern-matched by__make_transparent, so both paths agree.Results
examples/*.dspcompiled with the LLVM backend, 3 runs each, comparing the emitted IR across runs (clang 22 / libc++, MSYS2 CLANG64):The one remaining segfault (
physicalModeling/fds/2dKirchhoffThinPlate.dsp) reproduces identically before the change — a genuinely deep signal graph, unrelated."Other errors" are environmental in both columns (
unable to open file instruments.lib,undefined symbol : processon library-style files); the count rises only because files that used to crash now get far enough to report a real error.Determinism is preserved: the ordering is still by
serial(), and no example produced differing IR across runs after the change.Built and tested on MSYS2 CLANG64, clang 22.1.8, LLVM backend.