# BST-TIMEOUT: 600 # 191 cases but only ~32 process spawns -- the probe, one # # awk for all 110 scored values, 22 archive greps and a # # handful of cats. Bound taken from 79-libtirpc (~45 spawns, # # 600) rather than from a measurement of this suite, which # # did not exist when it was first written; re-set it from # # the ==BST-SUITE line once there is one. . "$BST_LIB" # liburcu (userspace-rcu 0.14.2, poky recipes-support/liburcu) is the userspace # read-copy-update library: an RCU read side and grace period in four flavors, # the uatomic/barrier abstraction under them, and the lock-free concurrent data # structures built on top -- rculfhash, wfcqueue, wfstack, lfstack, rculfqueue. # It is the synchronisation substrate lttng-tools is built on. # # The distro is static-only, so what the image gets is eight archives under # /usr/lib, ~90 headers under /usr/include/urcu, seven .pc files -- and NOT ONE # EXECUTABLE. Nothing in this package is reachable from a shell, so a suite made # only of file tests could assert that liburcu.a exists, which would pass just # as happily if every function in the archive trapped on first call. That is why # the second half of this file drives /usr/libexec/liburcu-tests/liburcu-probe, # which the recipe cross-links against the very archives that ship. # # --------------------------------------------------------------------------- # WHAT THE PORT IS, AND WHICH CASES HOLD IT IN PLACE # # liburcu selects its architecture from compiler macros in urcu/arch.h and # urcu/uatomic.h, and both chains end in `#error "Cannot build: unrecognized # architecture"`. Nothing in either tested __wasm32__, so the package did not # build here at all. The recipe carries # 0001-add-wasm32-architecture-support.patch, which adds urcu/arch/wasm32.h and # urcu/uatomic/wasm32.h and the two dispatch arms; both new headers delegate to # upstream's generic implementations, as nios2, riscv and loongarch do. # # Three groups here are what hold that in place, and each fails a DIFFERENT way # if the port regresses: # # * header-wasm32-arch / header-wasm32-uatomic -- the two new headers must be # INSTALLED, not merely compiled against. They reach the image only via # include/Makefile.am, which the patch also edits; a build whose autoreconf # did not re-run would compile perfectly and ship a -dev that no consumer # can build against. (Observed exactly once, in the native control tree, # where automake was too old to regenerate Makefile.in.) # * probe-arch-wasm32 -- the dispatch actually selected the wasm32 arm rather # than falling into some other arch's header. # * probe-cacheline -- the one value wasm32.h overrides. 16, not the generic # 64: WebAssembly has no cache line, and 64 is an alignment malloc() does # not promise on a target whose __BIGGEST_ALIGNMENT__ is 16. # # --------------------------------------------------------------------------- # WHAT THE PROBE DOES NOT TOUCH, AND WHY THAT IS NOT A COVERAGE HOLE # # Six liburcu entry points reach pthread_create -- call_rcu(), defer_rcu(), # rcu_barrier(), CDS_LFHT_AUTO_RESIZE, and the # start_poll_synchronize_rcu()/poll_state_synchronize_rcu() pair (which reads # thread-free and is not: urcu-poll-impl.h drives it from a self-requeueing # call_rcu callback). pthread_create parks the coroutinized thread-start frame # on this arm and traps at the next park, so a probe that called them would be # measuring linux-in-the-browser#85 and nothing about liburcu. None of them runs # at load time or on any default path, so the library's CORE -- read side, # grace period, pointer publication, atomics, and every data structure -- is # reachable without them, and the probe reclaims deferred memory the way a # single-threaded updater is entitled to: synchronize_rcu(), then free(). # BRINTOS-WORKAROUND: linux-in-the-browser#85 revisit -- restore the deferred # reclaim paths to this suite when threads run. # # urcu-bp (the "bulletproof" flavor) is untouched for a second reason: its # thread registry is backed by mmap(MAP_ANONYMOUS) (brintos/linux#30). Its # archive is asserted to SHIP, since it is a correct part of the package; it is # not driven. # # --------------------------------------------------------------------------- # HOW THE PROBE'S 110 VALUES ARE SCORED # # 106 of them are diffed against data/liburcu-native.txt, produced by # src/liburcu_mknative.sh from a native x86_64 build of the IDENTICAL tarball # and the IDENTICAL probe source. Nothing in that set is a pointer, a size_t # width, a timestamp or a hash-table traversal order (the lfht checks sort # before printing, because rculfhash orders buckets by reversed hash bits and # that order genuinely differs between ILP32 and LP64), so no value in it is # ALLOWED to differ between the two machines: a disagreement is a defect, not a # platform difference to reason about. # # The other four are properties of the MACHINE and are pinned to the wasm32 # answer instead -- see probe-arch-wasm32, probe-cacheline, probe-bits-per-long # and probe-membarrier below. # # A NATIVE x86_64 RUN OF THIS SUITE SCORES 194 CASES, 188 PASS / 6 FAIL, and # which six is the demonstration that the arch assertions are load-bearing: # probe-arch-wasm32, probe-cacheline and probe-bits-per-long MUST be red on # x86_64 -- a suite where they passed on both arms would be asserting nothing # about the port. (The other three natively-red cases -- header-wasm32-arch, # header-wasm32-uatomic, wasm32-cacheline -- are an artifact of the control # tree only: its automake is too old to regenerate include/Makefile.in, so the # two new headers are compiled against but not installed there. Any tree that # autoreconfs, which is every bitbake build, installs them.) R="${BST_LIBURCU_ROOT:-}" LIBDIR=$R/usr/lib INCDIR=$R/usr/include PCDIR=$R/usr/lib/pkgconfig PROBE=${BST_LIBURCU_PROBE:-$R/usr/libexec/liburcu-tests/liburcu-probe} NATIVE=$BST_DATA/liburcu-native.txt if [ ! -f "$LIBDIR/liburcu.a" ]; then bst_skip liburcu/present "liburcu not installed on this image" exit 0 fi bst_ok liburcu/present # --- the package is whole ---------------------------------------------------- # Eight archives: one per RCU flavor (memb, mb, signal, qsbr, bp), the # unsuffixed default (memb), the shared wait-free queues, and the concurrent # data structures. A port that dropped a flavor would look complete to anyone # only linking against liburcu.a. for a in liburcu liburcu-memb liburcu-mb liburcu-signal liburcu-qsbr \ liburcu-bp liburcu-common liburcu-cds; do check "liburcu/archive-$a" test -s "$LIBDIR/$a.a" done for p in liburcu liburcu-memb liburcu-mb liburcu-signal liburcu-qsbr \ liburcu-bp liburcu-cds; do check "liburcu/pkgconfig-$p" test -s "$PCDIR/$p.pc" done # The public headers a consumer names, one per subsystem. for h in urcu.h urcu/urcu.h urcu/urcu-memb.h urcu/urcu-qsbr.h urcu/urcu-bp.h \ urcu/arch.h urcu/uatomic.h urcu/compiler.h urcu/system.h \ urcu/pointer.h urcu/flavor.h urcu/config.h urcu/cds.h urcu/ref.h \ urcu/list.h urcu/rculist.h urcu/hlist.h urcu/rcuhlist.h \ urcu/wfcqueue.h urcu/wfstack.h urcu/lfstack.h \ urcu/rculfhash.h urcu/rculfqueue.h urcu/rculfstack.h \ urcu/uatomic/generic.h urcu/arch/generic.h; do check "liburcu/hdr-$(echo "$h" | tr '/.' '--')" test -f "$INCDIR/$h" done # THE TWO HEADERS THIS PORT ADDS. They are installed only because the patch # also edits include/Makefile.am, so this pair is the assertion that the # autoreconf ran -- a build that skipped it compiles fine and ships a -dev no # consumer can use. check liburcu/header-wasm32-arch test -f "$INCDIR/urcu/arch/wasm32.h" check liburcu/header-wasm32-uatomic test -f "$INCDIR/urcu/uatomic/wasm32.h" # And the dispatch arms that reach them, asserted in the SHIPPED headers rather # than in the ones the probe was compiled against. check_out liburcu/dispatch-arch 'urcu/arch/wasm32.h' cat "$INCDIR/urcu/arch.h" check_out liburcu/dispatch-uatomic 'urcu/uatomic/wasm32.h' cat "$INCDIR/urcu/uatomic.h" check_out liburcu/dispatch-macro 'URCU_ARCH_WASM32' cat "$INCDIR/urcu/arch.h" # The header must define the override; its VALUE is pinned from the running # library instead (probe-cacheline), which is the only place it can be read # after the preprocessor has had it. check_out liburcu/wasm32-cacheline 'define CAA_CACHE_LINE_SIZE' \ cat "$INCDIR/urcu/arch/wasm32.h" # Upstream's own architectures must be untouched by the patch: the port adds an # arm, it does not replace one. check_out liburcu/dispatch-x86-intact 'urcu/arch/x86.h' cat "$INCDIR/urcu/arch.h" check liburcu/upstream-arch-x86 test -f "$INCDIR/urcu/arch/x86.h" check liburcu/upstream-arch-riscv test -f "$INCDIR/urcu/arch/riscv.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape liburcu/no-shared "$LIBDIR/liburcu*.so*" # --- what is IN the archives, without binutils ------------------------------- # This image ships no `ar` and no `nm`, and reaching for them would not help: # every archive member here is LLVM bitcode (ThinLTO), so host binutils answers # "file format not recognized" too. The names are therefore asserted as BYTES -- # ar's member headers and the archive symbol index both store plain ASCII. # # Bitcode contains NULs, and GNU grep suppresses a match in a file it decides is # binary unless -a; busybox grep as shipped does not need it. Probe for the flag # so this block keeps measuring liburcu rather than the image's grep. if echo x | grep -qa x 2>/dev/null; then GREPA="grep -qa"; else GREPA="grep -q"; fi inar() { if $GREPA -- "$2" "$3" 2>/dev/null; then bst_ok "$1"; else bst_notok "$1" "not in $(basename "$3"): $2"; fi; } inar liburcu/sym-memb-read-lock urcu_memb_read_lock "$LIBDIR/liburcu-memb.a" inar liburcu/sym-memb-sync urcu_memb_synchronize_rcu "$LIBDIR/liburcu-memb.a" inar liburcu/sym-memb-register urcu_memb_register_thread "$LIBDIR/liburcu-memb.a" inar liburcu/sym-memb-flavor urcu_memb_flavor "$LIBDIR/liburcu-memb.a" inar liburcu/sym-memb-membarrier urcu_memb_has_sys_membarrier "$LIBDIR/liburcu-memb.a" inar liburcu/sym-default-is-memb urcu_memb_synchronize_rcu "$LIBDIR/liburcu.a" inar liburcu/sym-qsbr-sync urcu_qsbr_synchronize_rcu "$LIBDIR/liburcu-qsbr.a" inar liburcu/sym-bp-read-lock urcu_bp_read_lock "$LIBDIR/liburcu-bp.a" inar liburcu/sym-lfht-new _cds_lfht_new "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-add cds_lfht_add "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-add-unique cds_lfht_add_unique "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-add-replace cds_lfht_add_replace "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-del cds_lfht_del "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-resize cds_lfht_resize "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-count cds_lfht_count_nodes "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfq-enqueue cds_lfq_enqueue_rcu "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfs-push cds_lfs_push "$LIBDIR/liburcu-cds.a" inar liburcu/sym-wfcq-dequeue cds_wfcq_dequeue_blocking "$LIBDIR/liburcu-common.a" inar liburcu/sym-wfs-pop cds_wfs_pop_blocking "$LIBDIR/liburcu-common.a" # All three rculfhash memory backends are compiled in and selected at run time. # The image's tables use cds_lfht_mm_order; the mmap backend is present and # unreachable unless a caller names it through cds_lfht_new_flavor(), which is # what keeps brintos/linux#30 off this package's path. inar liburcu/sym-lfht-mm-order cds_lfht_mm_order "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-mm-chunk cds_lfht_mm_chunk "$LIBDIR/liburcu-cds.a" inar liburcu/sym-lfht-mm-mmap cds_lfht_mm_mmap "$LIBDIR/liburcu-cds.a" # Each .pc must name -lurcu-common alongside its own archive: liburcu-common # carries the wait-free queues and the futex compat every other archive calls # into, so a .pc that lost it links its consumer against an incomplete library # and fails at the first undefined symbol rather than at build configuration. check_out liburcu/pc-cds-name 'Name: Userspace RCU Concurrent Data Structures' \ cat "$PCDIR/liburcu-cds.pc" check_out liburcu/pc-cds-libs '-lurcu-cds' cat "$PCDIR/liburcu-cds.pc" check_out liburcu/pc-cds-common '-lurcu-common' cat "$PCDIR/liburcu-cds.pc" check_out liburcu/pc-memb-libs '-lurcu-memb' cat "$PCDIR/liburcu-memb.pc" check_out liburcu/pc-version 'Version: 0.14.2' cat "$PCDIR/liburcu.pc" # --- the library RUNNING, via the in-image probe ----------------------------- for f in "$PROBE" "$NATIVE"; do if [ ! -f "$f" ]; then bst_skip liburcu/probe-runs "$(basename "$f") absent from the image" exit 0 fi done pout=${TMPDIR:-/tmp}/bst-liburcu-out.$$ "$PROBE" > "$pout" 2>&1 prc=$? if [ "$prc" -eq 0 ]; then bst_ok liburcu/probe-runs elif bst_execfail "$(head -c 200 "$pout" 2>/dev/null)"; then bst_unrun liburcu/probe-runs "$(head -c 200 "$pout" 2>/dev/null)" rm -f "$pout" exit 0 else bst_notok liburcu/probe-runs "probe exited $prc" fi # The probe prints a terminator, so a run that DIED PARTWAY is distinguishable # from one that merely disagreed. The per-case loop below reports a missing # value as "probe stopped before this case", which localises the death to one # case id instead of smearing a single trap across every remaining case. A # grace period that never returns lands here, as the one failure mode this # library has that produces no output at all. if grep -q '^ALL-URCU-OK$' "$pout" 2>/dev/null; then bst_ok liburcu/probe-complete else bst_notok liburcu/probe-complete "no ALL-URCU-OK terminator (last line: $(awk 'END{print $1}' "$pout" 2>/dev/null))" fi # --- all 110 probe values, scored in ONE awk pass ---------------------------- # # ONE awk, not one per case, and that is not a micro-optimisation. A process # spawn on wasm32 is a fresh module instantiation costing on the order of a # second, so the obvious shape -- a `$(awk ...)` per value inside a read loop -- # would be 110 spawns and would spend minutes here and very likely overrun the # suite bound (BRINTOS-333 spent seven minutes on ~150 of them). The whole diff # is therefore a single program reading the control and the probe output # together and printing the BST lines itself. # # The four machine-dependent ids are pinned here rather than diffed, because # each is a property of the MACHINE and not of the port -- see the header. awk ' # score(, , ) -- the case name is spelled # out rather than derived, because these four are pinned assertions # about this arm and read differently in a report than the diffed ids. function score(id, name, want) { if (!(("URCU-" id) in g)) printf "BST not ok liburcu/%s : no value on target (probe stopped before this case); want=%s\n", name, want else if (g["URCU-" id] == want) printf "BST ok liburcu/%s\n", name else printf "BST not ok liburcu/%s : want %s, target %s\n", name, want, g["URCU-" id] } # first file: the x86_64 control NR == FNR { if ($1 ~ /^URCU-/) { w[$1] = substr($0, length($1) + 2); ord[++n] = $1 } next } # second file: this run $1 ~ /^URCU-/ { g[$1] = substr($0, length($1) + 2) } END { # The arch dispatch fired and chose wasm32. This is the case that # goes red if the patch is dropped and some other architecture # header is selected instead -- which would compile, and be wrong. score("ARCH-WASM32", "probe-arch-wasm32", "1") # The one value urcu/arch/wasm32.h overrides: 16, where x86.h says # 128 and the generic default is 64. score("CACHELINE", "probe-cacheline", "16") score("BITS-PER-LONG", "probe-bits-per-long", "32") # WHETHER THIS KERNEL OFFERS sys_membarrier IS NOT A PROPERTY OF THE # PORT. liburcu queries MEMBARRIER_CMD_QUERY once at init and, on # -ENOSYS, leaves the flag at 0 and promotes the read side to full # memory barriers by itself -- a configuration upstream supports. # So this asserts only that the query answered one of the two legal # values, i.e. that it did not trap and the probe got far enough to # print it. That the grace period WORKS is asserted by the diffed # B-section cases, whichever mechanism delivered it. mb = ("URCU-B13" in g) ? g["URCU-B13"] : "" if (mb == "0" || mb == "1") printf "BST ok liburcu/probe-membarrier\n" else printf "BST not ok liburcu/probe-membarrier : membarrier query answered %s, want 0 or 1\n", mb # and the 106 portable values, against the control for (i = 1; i <= n; i++) { k = ord[i] id = substr(k, 6) if (!(k in g)) printf "BST not ok liburcu/%s : no value on target (probe stopped before this case); native=%s\n", id, w[k] else if (g[k] == w[k]) printf "BST ok liburcu/%s\n", id else printf "BST not ok liburcu/%s : native %s, target %s\n", id, w[k], g[k] } } ' "$NATIVE" "$pout" rm -f "$pout"