# BST-TIMEOUT: 600 # two instantiations of a ~9 MB probe + one pass over a 1.2 MB archive . "$BST_LIB" # fmt (meta-oe recipes-support/fmt, 10.2.1 at SRCREV e69e5f97) -- the formatting # library that became std::format: type-safe printf with compile-time checked # format strings, Python-style replacement fields, and a shortest-round-trip # float formatter (Dragonbox). Fifth port of the C++ round (BRINTOS-1094), after # abseil-cpp, re2, gflags and glog. # # --------------------------------------------------------------------------- # TWO CONSUMERS, BECAUSE THEY PROVE DIFFERENT THINGS # # The PAYLOAD blocks assert what a downstream recipe reaches -- the thirteen # headers, the archive, the pkg-config file, the CMake package -- plus a # genuine _ZTV/_ZTI triple. fmt HAS polymorphic classes (format_error, # format_facet, detail::buffer's virtual grow, file_buffer), so this port can # point at vtable and typeinfo records in the shipped archive: a record a C # compile of the same sources cannot emit, and therefore direct evidence that # the C++ arm of the toolchain worked. # # data/fmt-probe is a CONSUMER of that payload: a program compiled against # these headers and LINKED AGAINST THIS BUILD'S libfmt.a, built by the recipe # so it cannot drift from what shipped. # # THE PROBE IS WHAT MAKES THIS PORT'S DEFECT VISIBLE AT BUILD TIME. fmt is # static-only here, so without a consumer the recipe ships headers and an # archive and no program -- and an undefined symbol in a static archive is not # an error until something links it. That is exactly how glog (BRINTOS-1101) # shipped green and unlinkable one ticket earlier. fmt had the same shape of # defect waiting (see the regression guard below), and the probe turned it # into a do_compile failure instead of a silent one. # # WHAT IS COMPARED BYTE-FOR-BYTE. The probe's run is compared line for line # against data/fmt-native.txt -- the identical source built and run on x86-64 # from the fmt-native recipe -- with NO tolerance band AND NO NORMALISATION AT # ALL. That is a stronger position than glog's suite could take (which had to # normalise four ambient fields) and it is available here because fmt renders # nothing ambient: every value the probe formats is a literal, the chrono cases # are a hand-filled std::tm and literal durations, and nothing reads the clock, # the environment, the filesystem or an address. One differing line is a defect. # # EVERY VALUE IS FIXED-WIDTH, WHICH IS WHAT MAKES THAT COMPARISON HONEST. The # target is ILP32 and the oracle's host is LP64, so `long`, `size_t` and any # pointer would differ between the arms for a reason that is NOT the port. The # probe uses int32_t/int64_t/uint64_t/float/double only, so a difference means # fmt. # # BOTH ARMS ARE CONFIGURED IDENTICALLY, which is what makes the control an # oracle rather than a second opinion: the bbappend's FMT_USE_INT128=0 is # unqualified and so applies to fmt-native too. A control configured with the # native 128-bit path while the target used the software fallback would leave # any divergence unattributable -- it could be the port or it could be the two # arms taking different arithmetic. Pinned, a difference means the port. # # THE ANCHORED CASES EXIST BECAUSE AN ORACLE CAN BE WRONG. A control captured # from an already-broken build would make every compared line agree with it. So # the renderings that IEEE-754 and fmt's documented contract fix independently # -- the shortest round-trips, round-half-to-even at precision 0, the 64-bit # extremes, the alignment widths, the ISO date -- are ALSO asserted against # literals here. Those hold whatever the control says. # --------------------------------------------------------------------------- # A root prefix, empty on the target and set only when this suite is validated # natively before it ships. The runner never sets it. R="${BST_FMT_ROOT:-}" ARCHIVE=$R/usr/lib/libfmt.a INCDIR=$R/usr/include/fmt PC=$R/usr/lib/pkgconfig/fmt.pc CMDIR=$R/usr/lib/cmake/fmt PROBE=$BST_DATA/fmt-probe NATIVE=$BST_DATA/fmt-native.txt if [ ! -f "$ARCHIVE" ]; then bst_skip fmt/present "fmt not installed on this image" exit 0 fi bst_ok fmt/present nl=' ' tmp=${TMPDIR:-/tmp} # has # A shell `case` rather than a grep: this suite has ~70 content assertions and # an exec apiece is what killed the first draft of the abseil suite on target, # where every process is a fresh instantiation of a multi-megabyte module. has() { case "$2" in *"$nl$3$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "missing: $3" ;; esac } # sub sub() { case "$2" in *"$3"*) bst_ok "$1" ;; *) bst_notok "$1" "missing: $3" ;; esac } # hasnt -- substring, for "must NOT be there" hasnt() { case "$2" in *"$3"*) bst_notok "$1" "present but must not be: $3" ;; *) bst_ok "$1" ;; esac } # --- the package is whole ---------------------------------------------------- check fmt/hdr-core test -f "$INCDIR/core.h" check fmt/hdr-format test -f "$INCDIR/format.h" check fmt/hdr-format-inl test -f "$INCDIR/format-inl.h" check fmt/hdr-chrono test -f "$INCDIR/chrono.h" check fmt/hdr-args test -f "$INCDIR/args.h" check fmt/hdr-color test -f "$INCDIR/color.h" check fmt/hdr-compile test -f "$INCDIR/compile.h" check fmt/hdr-os test -f "$INCDIR/os.h" check fmt/hdr-ostream test -f "$INCDIR/ostream.h" check fmt/hdr-printf test -f "$INCDIR/printf.h" check fmt/hdr-ranges test -f "$INCDIR/ranges.h" check fmt/hdr-std test -f "$INCDIR/std.h" check fmt/hdr-xchar test -f "$INCDIR/xchar.h" check fmt/archive test -f "$ARCHIVE" check fmt/pkgconfig test -f "$PC" check fmt/cmake-config test -f "$CMDIR/fmt-config.cmake" check fmt/cmake-targets test -f "$CMDIR/fmt-targets.cmake" check fmt/cmake-version test -f "$CMDIR/fmt-config-version.cmake" # 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 fmt/no-shared "$R/usr/lib/libfmt.so" # --- the archive's shape ----------------------------------------------------- # # One pass over the archive with one grep, for the reason in `has` above. The # members are bitcode, so these names live in the bitcode string tables and are # reachable with the same tokenise-and-match the other suites use. nf=$tmp/bst-fmt-needles.$$ hf=$tmp/bst-fmt-hits.$$ cat > "$nf" <<'NEEDLES' _ZN3fmt3v1013vsystem_errorE _ZN3fmt3v106detail11assert_failEPKciS3_ _ZN3fmt3v106detail12format_floatIdEEiT _ZN3fmt3v106detail9dragonbox10to_decimalIdEENS _ZN3fmt3v106detail9dragonbox10to_decimalIfEENS _ZN3fmt3v1011getpagesizeEv _ZN3fmt3v1013buffered_file5closeEv _ZN3fmt3v1012format_errorD0Ev _ZTIN3fmt3v1012format_errorE _ZTSN3fmt3v1012format_errorE _ZTIN3fmt3v106detail11file_bufferE uint128_fallback count_digits_fallback llvm.ctlz.i128 NEEDLES cat "$ARCHIVE" 2>/dev/null | tr -c 'A-Za-z0-9_.' '\n' | grep -F -f "$nf" > "$hf" 2>/dev/null HITS=$nl$(cat "$hf" 2>/dev/null)$nl rm -f "$nf" "$hf" 2>/dev/null # The formatting core actually got compiled in, rather than an empty archive # that satisfies every file check above. sub fmt/sym-vsystem-error "$HITS" "_ZN3fmt3v1013vsystem_errorE" sub fmt/sym-assert-fail "$HITS" "_ZN3fmt3v106detail11assert_failEPKciS3_" # The float-formatting core, which is the substance of this port. Both needles # were chosen by MEASUREMENT against both arms rather than read off one: the # first draft used detail::umul128 and dragonbox::floor_log2_pow10, which are # inline and survive in the target's BITCODE members but are inlined away in the # optimised native ELF archive -- so they passed on target and failed the native # dry run that validates this file. format_float and dragonbox::to_decimal have # external linkage in both. sub fmt/sym-format-float "$HITS" "_ZN3fmt3v106detail12format_floatIdEEiT" sub fmt/sym-dragonbox-d "$HITS" "_ZN3fmt3v106detail9dragonbox10to_decimalIdEENS" sub fmt/sym-dragonbox-f "$HITS" "_ZN3fmt3v106detail9dragonbox10to_decimalIfEENS" sub fmt/sym-getpagesize "$HITS" "_ZN3fmt3v1011getpagesizeEv" sub fmt/sym-buffered-file "$HITS" "_ZN3fmt3v1013buffered_file5closeEv" # C++ EVIDENCE THE C ARM CANNOT FORGE. A vtable and a typeinfo record for # fmt::format_error, plus the typeinfo for detail::file_buffer -- these exist # only because a C++ compiler emitted them, so their presence is a statement # that the wasmc++-glibc opt-in in the bbappend actually took effect rather # than the distro's C driver having silently compiled the sources. sub fmt/vtable-format-error "$HITS" "_ZTIN3fmt3v1012format_errorE" sub fmt/typeinfo-name "$HITS" "_ZTSN3fmt3v1012format_errorE" sub fmt/dtor-format-error "$HITS" "_ZN3fmt3v1012format_errorD0Ev" sub fmt/typeinfo-file-buffer "$HITS" "_ZTIN3fmt3v106detail11file_bufferE" # The software 128-bit type IS present and that is correct: Dragonbox needs a # 128-bit multiply whichever way FMT_USE_INT128 is set, and uint128_fallback is # what supplies it here. Asserted so the guard below cannot be read as "no # 128-bit arithmetic at all". sub fmt/uint128-fallback-present "$HITS" "uint128_fallback" # THE REGRESSION GUARD FOR THIS PORT'S ACTUAL DEFECT, stated as a fact about the # archive's SHAPE rather than as the absence of the builtin's name. # # NOT `__udivmodti4`, AND THAT IS THE WHOLE POINT. The archive members are # bitcode; the division is a `udiv i128` in IR and the call to __udivmodti4 is # synthesized by the backend at LINK time. Measured on both builds of this port: # the string __udivmodti4 occurs ZERO times in the healthy archive AND ZERO # times in the broken one, so a needle spelled that way is a guard that passes # whatever happens. That is the same trap glog's suite hit with a bare # `backtrace` needle, caught there on a dry run and caught here by building the # archive both ways on purpose and diffing the two symbol sets. # # count_digits_fallback IS the division site: format.h:1144, whose loop body is # `n /= 10000u`, instantiated for __int128 at format.h:1159 when FMT_USE_INT128 # is 1. With the bbappend's FMT_USE_INT128=0 pin, uint128_t is uint128_fallback, # which has no operator/, so the template cannot be instantiated at all. # Measured: 2 occurrences in the archive built without the pin, 0 with it. # llvm.ctlz.i128 is the same statement from the other side -- a 128-bit # intrinsic that is emitted only when the native type is live (1 without the # pin, 0 with it). Both discriminate; uint128_fallback and int128_opt were # tested as candidates and REJECTED because they occur in the healthy archive # too (45 and 6 times). # # What this keeps is linkability. Without the pin, libfmt.a compiles, packages, # passes QA and populates the sysroot exactly as it does with it -- and then no # program on this image can be linked against it, because __udivmodti4 lives # only in libclang_rt.builtins-wasm32.a, a native-stage input carrying a wasm # `loop`, which wasmld refuses on the HWJS-3 coro arm # (brintos/linux-in-the-browser#155). hasnt fmt/no-int128-divmod "$HITS" "count_digits_fallback" hasnt fmt/no-int128-intrinsic "$HITS" "llvm.ctlz.i128" # --- the probe: does the shipped archive FORMAT, and format correctly? ------- if [ ! -f "$PROBE" ]; then bst_notok fmt/probe-present "$PROBE missing -- the library cannot be proven to run" elif [ ! -f "$NATIVE" ]; then bst_notok fmt/native-present "$NATIVE missing -- no x86-64 control for the probe run" else bst_ok fmt/probe-present bst_ok fmt/native-present # do_install stages data/* at 0644 and the deployed rootfs is read-only at # browser runtime, so the payload cannot be executed where it lies. Stage an # executable copy under TMPDIR, as every probe-driven suite here does. pd=$tmp/bst-fmt.$$ p=$pd/fmt-probe mkdir -p "$pd" 2>/dev/null if cp "$PROBE" "$p" 2>/dev/null && chmod 0755 "$p" 2>/dev/null && [ -x "$p" ]; then bst_ok fmt/probe-exec # === 1. the scripted run ================================================= out=$("$p" 2>"$pd/run.err"); rc=$? errout=$(cat "$pd/run.err" 2>/dev/null) if [ "$rc" -eq 0 ]; then bst_ok fmt/probe-rc else bst_notok fmt/probe-rc "probe exited $rc"; fi # fmt writes nothing to stderr on a successful run. A port whose # assert_fail path fired on some case would show up here even if the # surviving output still matched line for line. if [ -z "$errout" ]; then bst_ok fmt/probe-silent-stderr else bst_notok fmt/probe-silent-stderr "$(_bst_1line "$errout")"; fi O=$nl$out$nl # The whole-stream gate. Kept separate from the anchored cases below, # because a probe that died partway would leave those it never reached with # nothing to disagree with and could look like a shorter clean run. case "$O" in *"${nl}fmt-probe-end${nl}") bst_ok fmt/probe-complete ;; *) bst_notok fmt/probe-complete "probe did not reach its final line" ;; esac # The version the probe was COMPILED against, printed from FMT_VERSION, so # a header/archive mismatch is visible rather than inferred. has fmt/probe-version "$O" "fmt-probe 10.2.1" if [ "$out" = "$(cat "$NATIVE" 2>/dev/null)" ]; then bst_ok fmt/probe-matches-x86-64 else first=$(printf '%s\n' "$out" | diff - "$NATIVE" 2>/dev/null | head -4) bst_notok fmt/probe-matches-x86-64 "$(_bst_1line "${first:-output differs from the control}")" fi # --- anchored cases: renderings fixed independently of the control ------- # Shortest round-trip. These are the highest-value assertions in the file: # the correct answer is fixed by the IEEE-754 value, not by fmt's taste, and # Dragonbox is the largest piece of integer arithmetic in the library and # the place a 32-bit arm is most likely to differ silently. has fmt/dbl-tenth "$O" "dbl/shortest-tenth|0.1" has fmt/dbl-third "$O" "dbl/shortest-third|0.3333333333333333" has fmt/dbl-pi "$O" "dbl/shortest-pi|3.141592653589793" has fmt/dbl-max "$O" "dbl/shortest-max|1.7976931348623157e+308" has fmt/dbl-min "$O" "dbl/shortest-min|2.2250738585072014e-308" has fmt/dbl-denorm "$O" "dbl/shortest-denorm|5e-324" has fmt/dbl-e300 "$O" "dbl/shortest-e300|1e+300" has fmt/dbl-em300 "$O" "dbl/shortest-em300|1e-300" # 9007199254740993.0 is NOT representable as a double; the nearest value is # ...992, and printing ...992 is therefore the correct answer rather than a # lost digit. has fmt/dbl-2p53 "$O" "dbl/shortest-9007|9007199254740992" has fmt/dbl-nzero "$O" "dbl/nzero|-0" has fmt/dbl-inf "$O" "dbl/inf|inf" has fmt/dbl-ninf "$O" "dbl/ninf|-inf" # Round-half-to-even at precision 0: 2.5 -> 2 and 3.5 -> 4. A port that # rounded half-away-from-zero would give 3 and 4 and pass a suite that only # tested one of them. has fmt/dbl-round-even-down "$O" "dbl/fixed-0|2" has fmt/dbl-round-even-up "$O" "dbl/fixed-0b|4" # The exact decimal expansion of the double nearest 0.1, to 20 places. has fmt/dbl-exact-20 "$O" "dbl/fixed-20|0.10000000000000000555" has fmt/dbl-hexfloat "$O" "dbl/hexfloat|0x1.5555555555555p-2" has fmt/flt-third "$O" "flt/shortest-3|0.33333334" # 64-bit extremes -- the arm where a 32-bit target is most likely to lose a # limb in the division-by-ten loop. has fmt/i64-min "$O" "int64/min|-9223372036854775807" has fmt/u64-max "$O" "int64/umax|18446744073709551615" has fmt/u64-max-hex "$O" "int64/umax-hex|ffffffffffffffff" # Width, fill and alignment -- the `{:>10}` family the ticket names. has fmt/str-right "$O" "str/right|[ fmt]" has fmt/str-left "$O" "str/left|[fmt ]" has fmt/str-center "$O" "str/center|[ fmt ]" has fmt/str-fill "$O" "str/fill|[****fmt*****]" has fmt/int-zerofill-neg "$O" "int/fill-zero-neg|[-0000042]" has fmt/int-hex-pad "$O" "int/hex-pad|0x0000beef" # Named arguments. has fmt/named-basic "$O" "named/basic|brintos is 7" has fmt/named-spec "$O" "named/spec|wasm32: 99.50" has fmt/named-reuse "$O" "named/reuse|21+21=42" # chrono, from a hand-filled tm and literal durations. has fmt/tm-iso "$O" "tm/iso|2026-08-20T06:59:32" has fmt/tm-named "$O" "tm/named|Thursday August" has fmt/tm-doy "$O" "tm/doy|232" has fmt/dur-hms "$O" "dur/hms|01:01:01" has fmt/dur-frac "$O" "dur/frac|01.500" has fmt/dur-plain "$O" "dur/sec|3661s" # === 2. the exceptions-off arm =========================================== # # This arm is built -fno-exceptions, so fmt's FMT_THROW degrades to # detail::assert_fail(), which prints to stderr and abort()s rather than # raising format_error. That is upstream's supported configuration, not a # patch -- and it is a behavioural difference a downstream consumer must be # able to rely on, so it is asserted rather than assumed. # # Driven as its own invocation because it ABORTS: doing it inline would end # the probe there and lose every case above. terr=$pd/throw.err tout=$("$p" throw 2>"$terr"); trc=$? terrout=$(cat "$terr" 2>/dev/null) if [ "$trc" -ne 0 ]; then bst_ok fmt/throw-nonzero else bst_notok fmt/throw-nonzero "format error did not end the process (rc $trc)"; fi # The message fmt itself prints. assert_fail writes the what() text, so the # specific format error survives into the console rather than a bare abort. sub fmt/throw-message "$nl$terrout$nl" "invalid format specifier" # The line after the failing vformat must NOT have run. Without this a # probe that somehow returned normally would still satisfy the two cases # above if it happened to exit non-zero for an unrelated reason. hasnt fmt/throw-unreached "$nl$tout$nl" "UNREACHED" rm -rf "$pd" 2>/dev/null else bst_notok fmt/probe-exec "cannot stage an executable copy of the probe" fi fi