simdutf: remove unused <iostream> from scalar/base64.h amalgamation - #320
Conversation
The vendored simdutf 8.2.0 amalgamation includes <iostream> in the scalar/base64.h section (simdutf_impl.h line 9949). The header is never used: simdutf's only iostream usage is gated behind SIMDUTF_LOGGING earlier in the file. Because SIMDUTF.h includes simdutf_impl.h and is pulled into every TU that needs simdutf (WTF's Base64.cpp, StringCommon.cpp, StringImpl.cpp, UTF8Conversion.cpp, SIMDUTF.cpp, and Bun's helpers.h), every one of those TUs emits an undefined reference to std::ios_base_library_init. That forces libstdc++'s globals_io.o into the final link, which in turn drags in locale_inst.cc, cxx11_locale_inst.cc, wlocale_inst.cc and cxx11_wlocale_inst.cc: roughly fifty static-initializer functions (std::ios_base::Init, std::locale::_S_initialize, and the full set of ctype/numpunct/moneypunct/timepunct/messages facets for char and wchar_t) that run before main on every Bun process. Upstream simdutf already removed both headers in simdutf/simdutf#962 (<iostream>) and simdutf/simdutf#979 (<algorithm>); this brings the vendored copy in line. Int128.cpp's <sstream>/<ostream> includes were also checked: they are used by UInt128ToFormattedString's std::ostringstream and the operator<<(ostream&, ...) overloads, and neither header emits the static ios_base::Init object, so they stay.
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
Comment |
When USE_BUN_JSC_ADDITIONS is on and the build type is not Debug, prepend Source/WTF/wtf/bun/BannedIncludes to the include search path so #include <iostream> resolves to a #error shim. This prevents a future WebKit or vendored-library change from reintroducing the reference to std::ios_base_library_init that drags globals_io.o (cin/cout/cerr/clog construction + the full std::locale facet set) into Bun's pre-main startup. Debug builds keep the real header, and -DBUN_ALLOW_IOSTREAM is the opt-out. <ostream>/<istream>/<sstream> are unaffected since they do not emit the static ios_base::Init object.
|
Added a second commit that bans Companion Bun-side change (same shim for Bun's own C++ plus the bun-uws cleanup): oven-sh/bun#35256. |
There was a problem hiding this comment.
Beyond the inline note, I also checked: the <algorithm> removal leaves std::min uses at simdutf_impl.h:1708/1755/13760 without a direct include, but <string> at :615 pulls it transitively on libstdc++/libc++ and upstream simdutf#979 already ships this way; the remaining <iostream> at :434 is correctly gated behind SIMDUTF_LOGGING; and the shim's #include_next (unsupported on MSVC) is only reachable via the BUN_ALLOW_IOSTREAM opt-out, so the default #error path is portable.
Extended reasoning...
Verified the two dropped includes against remaining uses in the amalgamation and confirmed the only other <iostream> is behind SIMDUTF_LOGGING. Checked the shim for MSVC portability — #include_next is a GCC/Clang extension but sits behind the opt-out macro, so Windows Release builds still get the intended #error. The gtest/TestWebKitAPI scope leak is covered by the inline comment; nothing further to add.
| if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") | ||
| include_directories(BEFORE "${WTF_DIR}/wtf/bun/BannedIncludes") | ||
| endif() |
There was a problem hiding this comment.
🟡 This include_directories(BEFORE ...) runs at top-level project scope (OptionsJSCOnly.cmake is include()'d, not add_subdirectory()'d), so the <iostream> shim propagates to every target — including Source/ThirdParty/gtest and Tools/TestWebKitAPI, which are pulled in on non-Windows via ENABLE_API_TESTS ON. gtest unconditionally includes <iostream> (gtest-port.h:291, gtest.cc:52), so a plain ninja / cmake --build . (default all target) with -DUSE_BUN_JSC_ADDITIONS=ON in Release now fails on gtest-all.cc. All Bun CI/release paths build only --target jsc and are unaffected, but you may want to set(ENABLE_API_TESTS OFF) under USE_BUN_JSC_ADDITIONS, or add target_compile_definitions(gtest PRIVATE BUN_ALLOW_IOSTREAM), so the default target still builds.
Extended reasoning...
What breaks
The new include_directories(BEFORE "${WTF_DIR}/wtf/bun/BannedIncludes") is intended to shadow <iostream> for the WebKit sources that end up in the Bun link. However, it is applied at a scope that also covers third-party test code that legitimately uses iostreams, so building the default all target on non-Windows now fails.
Why the shim leaks to gtest
OptionsJSCOnly.cmake is not a subdirectory script — it is pulled in via include():
CMakeLists.txt:22→include(WebKitCommon)WebKitCommon.cmake:320→include(Options${PORT})
include() does not create a new directory scope, so the include_directories(BEFORE ...) call executes in the top-level CMakeLists.txt directory scope. Every add_subdirectory() that follows (starting with add_subdirectory(Source) at CMakeLists.txt:75) inherits it.
A few lines further down in this same file, ENABLE_API_TESTS is set ON for all non-Windows platforms. That causes:
Source/CMakeLists.txt:27-28→add_subdirectory(ThirdParty/gtest)Tools/CMakeLists.txt:24-26→add_subdirectory(TestWebKitAPI)
Neither of these is added with EXCLUDE_FROM_ALL, so both are part of the default all target. gtest's own CMakeLists.txt does not clear the inherited directory-level include list and does not define BUN_ALLOW_IOSTREAM. And gtest unconditionally includes <iostream>:
Source/ThirdParty/gtest/include/gtest/internal/gtest-port.h:291Source/ThirdParty/gtest/src/gtest.cc:52
Because the shim directory is added BEFORE and directory-level -I paths precede system include paths, #include <iostream> in gtest resolves to the shim and hits the #error.
Step-by-step reproduction
- On Linux or macOS, configure with
-DPORT=JSCOnly -DUSE_BUN_JSC_ADDITIONS=ON -DCMAKE_BUILD_TYPE=Release(orRelWithDebInfo). WTF_DIRis set byWebKitFS.cmake(included atWebKitCommon.cmake:308, i.e. beforeOptions${PORT}), so the shim path expands correctly.- Run
ninja(orcmake --build .) with no explicit target. - Ninja builds
gtest-all.cc, which#includesgtest.cc→#include <iostream>→ resolves toSource/WTF/wtf/bun/BannedIncludes/iostream→#error "<iostream> is banned in Bun WebKit release builds…". - Build fails.
Impact
Every documented and CI build path in this repo — Dockerfile*, build.ts, mac-release.bash, release.sh, windows-release.ps1 — invokes cmake --build … --target jsc (or --target artifact), which does not depend on gtest. So CI, autobuild artifacts, and the shipped Bun binaries are unaffected. This only bites a developer who runs a bare ninja / cmake --build . in a Release-flavored USE_BUN_JSC_ADDITIONS tree, and the #error message itself points at the -DBUN_ALLOW_IOSTREAM opt-out. It's a real regression in an ostensibly-supported configuration (this file is the one that sets ENABLE_API_TESTS ON), but not one that blocks the PR's goal.
Suggested fixes
Any one of:
set(ENABLE_API_TESTS OFF)inside theif(USE_BUN_JSC_ADDITIONS)block — Bun doesn't ship or run these tests anyway.- Add
target_compile_definitions(gtest PRIVATE BUN_ALLOW_IOSTREAM)(e.g. via aSource/ThirdParty/gtest/PlatformJSCOnly.cmake), and similarly for TestWebKitAPI. - Scope the shim to the libraries that actually feed the Bun link by using
target_include_directories(... BEFORE ...)onWTF/JavaScriptCore/bmallocinstead of the globalinclude_directories().
Preview Builds
|
Picks up oven-sh/WebKit#320 (removes the stray <iostream> from the vendored simdutf amalgamation and bans <iostream> at compile time for non-Debug USE_BUN_JSC_ADDITIONS builds) and oven-sh/WebKit#322 (skip the eager timezone prewarm under USE_BUN_JSC_ADDITIONS). With this bump the release build's src/banned-includes/iostream shim no longer trips on the WebKit simdutf header, and no translation unit in the final link carries a reference to std::ios_base_library_init.
## Problem Every Bun process runs libstdc++'s iostream and locale static initializers before `main`. From the release `bun-profile`: ``` _GLOBAL__sub_I.00090_globals_io.cc _GLOBAL__sub_I_cxx11_locale_inst.cc _GLOBAL__sub_I_cxx11_wlocale_inst.cc _GLOBAL__sub_I_locale_inst.cc _GLOBAL__sub_I_wlocale_inst.cc ``` `std::ios_base::Init`, `std::locale::_S_initialize`, `std::locale::_Impl`, and construction of ctype/numpunct/moneypunct/timepunct/messages for both `char` and `wchar_t`: roughly fifty functions out of libstdc++ that sit ahead of `main` in the `bun run orderfile` trace. Bun never uses C++ iostreams. ## Cause On libstdc++, `<iostream>` (and only `<iostream>`; not `<ostream>`/`<istream>`/`<sstream>`) emits an undefined reference to `_ZSt21ios_base_library_initv` in every TU that includes it. One such reference anywhere in the link pulls `globals_io.o` from libstdc++.a, whose initializer constructs `cin`/`cout`/`cerr`/`clog` and their `wchar_t` siblings, which in turn reference the full locale facet set. There are two sources: 1. **WebKit's vendored simdutf** (`Source/WTF/wtf/simdutf/simdutf_impl.h:9949`, an unused include in the `scalar/base64.h` section). Because `SIMDUTF.h` is included from `src/jsc/bindings/helpers.h`, ~70 Bun TUs carry the reference. Fixed in oven-sh/WebKit#320; upstream simdutf already dropped it in simdutf/simdutf#962. 2. **bun-uws headers**: `AsyncSocket.h`, `HttpRouter.h`, `Loop.h` include it without using it; `App.h`, `HttpContext.h`, `TopicTree.h` write fixed error strings via `std::cerr`. 7 Bun TUs carry the reference via `<bun-uws/src/App.h>`. ## Changes - `scripts/build/deps/webkit.ts`: bump `WEBKIT_VERSION` to `af2e8dc639` (oven-sh/WebKit#320: drop the simdutf `<iostream>` include and ban it at compile time for non-Debug `USE_BUN_JSC_ADDITIONS` builds; also picks up oven-sh/WebKit#321 lazy WebAssembly namespace and oven-sh/WebKit#322 skip eager timezone prewarm). Subsumes the `WEBKIT_VERSION` change in #35258. - `packages/bun-uws`: drop the `<iostream>` include from `AsyncSocket.h`, `HttpContext.h`, `HttpRouter.h`, `Loop.h`, `TopicTree.h`; delete the five `std::cerr << ...` validation messages (Bun validates these inputs before calling into uWS, so the paths are unreachable programmer errors and `std::terminate()` alone is enough). - `src/banned-includes/iostream`: a `#error` shim prepended to the `-I` search path for release builds. Any `#include <iostream>` in a Bun TU (including transitively from a WebKit header) fails the release compile with an explanation pointing here. Debug builds keep the real header for ad-hoc printf debugging; `-DBUN_ALLOW_IOSTREAM` is the opt-out. - `test/internal/source-lints/no-iostream-include.test.ts`: scans `src/`, `packages/bun-uws`, `packages/bun-usockets` for the include so debug CI also catches it. ## Verification With both oven-sh/WebKit#320 and this change applied to a release build: ``` nm build/release/bun-profile | grep ios_base4Init -> empty nm build/release/bun-profile | grep _S_initialize -> empty _GLOBAL__sub_I.00090_globals_io.cc gone bun-profile 547 KB smaller ``` Four `_GLOBAL__sub_I_*locale_inst.cc` stubs remain: these are the libstdc++ facet-id guard-byte initializers (a few dozen `movb $1,(%rax)` each) pulled by `Int128.cpp`'s `std::ostringstream`. `<sstream>` does not emit the static `Init` object and those stubs are near-free. ## Int128.cpp `Source/WTF/wtf/Int128.cpp` includes `<sstream>` and `<ostream>`. Both are load-bearing (`UInt128ToFormattedString`'s `std::ostringstream` and the `operator<<(std::ostream&, ...)` overloads) and neither emits the static `ios_base::Init` object, so they are left alone. <!-- robobun:evidence:begin --> --- **[decide:webkit]** gate passed · iteration 6 · 10 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 1 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/no-iostream-include.test.ts bun test v1.4.0 (eba02a1) test/internal/source-lints/no-iostream-include.test.ts: 45 | // root going away, which would make the ban below pass vacuously. 46 | expect(scanned).toBeGreaterThan(0); 47 | } 48 | 49 | violations.sort(); 50 | expect(violations).toEqual([]); ^ error: expect(received).toEqual(expected) - [] + [ + "packages/bun-uws/src/AsyncSocket.h", + "packages/bun-uws/src/HttpContext.h", + "packages/bun-uws/src/HttpRouter.h", + "packages/bun-uws/src/Loop.h", + "packages/bun-uws/src/TopicTree.h", + ] - Expected - 1 + Received + 7 at <anonymous> (/workspace/bun/test/internal/source-lints/no-iostream-include.test.ts:50:22) (fail) C++ sources compiled into Bun do not include <iostream> [748.19ms] 0 pass 1 fail 4 expect() calls Ran 1 test across 1 file. [3.00s] error: script "bd" exited with code 1 __F:1:S:0 release without fix: 1 FAILED bun test v1.4.0-canary.1 (507ab81) test/internal/source-lints/no-iostream-include.test.ts: 45 | // root going away, which would make the ban below pass vacuously. 46 | expect(scanned).toBeGreaterThan(0); 47 | } 48 | 49 | violations.sort(); 50 | expect(violations).toEqual([]); ^ error: expect(received).toEqual(expected) - [] + [ + "packages/bun-uws/src/AsyncSocket.h", + "packages/bun-uws/src/HttpContext.h", + "packages/bun-uws/src/HttpRouter.h", + "packages/bun-uws/src/Loop.h", + "packages/bun-uws/src/TopicTree.h", + ] - Expected - 1 + Received + 7 at <anonymous> (/workspace/bun/test/internal/source-lints/no-iostream-include.test.ts:50:22) (fail) C++ sources compiled into Bun do not include <iostream> [59.52ms] 0 pass 1 fail 4 expect() calls Ran 1 test across 1 file. [213.00ms] __F:1:S:0 ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/no-iostream-include.test.ts bun test v1.4.0 (eba02a1) test/internal/source-lints/no-iostream-include.test.ts: (pass) C++ sources compiled into Bun do not include <iostream> [885.41ms] 1 pass 0 fail 4 expect() calls Ran 1 test across 1 file. [3.41s] __F:0:S:0 release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 1174ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [0/1] reconfigure [0/13] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu) nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19) �[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core) �[1m�[92m Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno) �[1m�[92m Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr) �[1m�[92m Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys) �[1m�[92m Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety) �[1m�[92m Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys) �[1m�[92m Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys) �[1m�[92m Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd) �[1m�[92m Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp) �[1m�[92m Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output) �[1m�[92m Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap) �[1m�[92m Compili ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` packages/bun-uws/src/App.h | 3 -- packages/bun-uws/src/AsyncSocket.h | 1 - packages/bun-uws/src/HttpContext.h | 3 -- packages/bun-uws/src/HttpRouter.h | 1 - packages/bun-uws/src/Loop.h | 1 - packages/bun-uws/src/TopicTree.h | 5 --- scripts/build/deps/webkit.ts | 7 --- scripts/build/flags.ts | 6 +++ src/banned-includes/iostream | 27 ++++++++++++ .../source-lints/no-iostream-include.test.ts | 51 ++++++++++++++++++++++ 10 files changed, 84 insertions(+), 21 deletions(-) ``` </details> **gate history** · 8 passed · 1 rejected · iteration 6 <details><summary>evidence per changed file</summary> ``` file reads edits tests packages/bun-uws/src/App.h 4 3 0 packages/bun-uws/src/AsyncSocket.h 1 1 0 packages/bun-uws/src/HttpContext.h 2 2 0 packages/bun-uws/src/HttpRouter.h 1 1 0 packages/bun-uws/src/Loop.h 1 1 0 packages/bun-uws/src/TopicTree.h 4 4 0 scripts/build/deps/webkit.ts 2 3 0 scripts/build/flags.ts 1 1 0 src/banned-includes/iostream 0 1 0 test/internal/source-lints/no-iostream-include.test.ts 2 3 0 ``` </details> <!-- robobun:evidence:end --> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
## Problem Every Bun process runs libstdc++'s iostream and locale static initializers before `main`. From the release `bun-profile`: ``` _GLOBAL__sub_I.00090_globals_io.cc _GLOBAL__sub_I_cxx11_locale_inst.cc _GLOBAL__sub_I_cxx11_wlocale_inst.cc _GLOBAL__sub_I_locale_inst.cc _GLOBAL__sub_I_wlocale_inst.cc ``` `std::ios_base::Init`, `std::locale::_S_initialize`, `std::locale::_Impl`, and construction of ctype/numpunct/moneypunct/timepunct/messages for both `char` and `wchar_t`: roughly fifty functions out of libstdc++ that sit ahead of `main` in the `bun run orderfile` trace. Bun never uses C++ iostreams. ## Cause On libstdc++, `<iostream>` (and only `<iostream>`; not `<ostream>`/`<istream>`/`<sstream>`) emits an undefined reference to `_ZSt21ios_base_library_initv` in every TU that includes it. One such reference anywhere in the link pulls `globals_io.o` from libstdc++.a, whose initializer constructs `cin`/`cout`/`cerr`/`clog` and their `wchar_t` siblings, which in turn reference the full locale facet set. There are two sources: 1. **WebKit's vendored simdutf** (`Source/WTF/wtf/simdutf/simdutf_impl.h:9949`, an unused include in the `scalar/base64.h` section). Because `SIMDUTF.h` is included from `src/jsc/bindings/helpers.h`, ~70 Bun TUs carry the reference. Fixed in oven-sh/WebKit#320; upstream simdutf already dropped it in simdutf/simdutf#962. 2. **bun-uws headers**: `AsyncSocket.h`, `HttpRouter.h`, `Loop.h` include it without using it; `App.h`, `HttpContext.h`, `TopicTree.h` write fixed error strings via `std::cerr`. 7 Bun TUs carry the reference via `<bun-uws/src/App.h>`. ## Changes - `scripts/build/deps/webkit.ts`: bump `WEBKIT_VERSION` to `af2e8dc639` (oven-sh/WebKit#320: drop the simdutf `<iostream>` include and ban it at compile time for non-Debug `USE_BUN_JSC_ADDITIONS` builds; also picks up oven-sh/WebKit#321 lazy WebAssembly namespace and oven-sh/WebKit#322 skip eager timezone prewarm). Subsumes the `WEBKIT_VERSION` change in #35258. - `packages/bun-uws`: drop the `<iostream>` include from `AsyncSocket.h`, `HttpContext.h`, `HttpRouter.h`, `Loop.h`, `TopicTree.h`; delete the five `std::cerr << ...` validation messages (Bun validates these inputs before calling into uWS, so the paths are unreachable programmer errors and `std::terminate()` alone is enough). - `src/banned-includes/iostream`: a `#error` shim prepended to the `-I` search path for release builds. Any `#include <iostream>` in a Bun TU (including transitively from a WebKit header) fails the release compile with an explanation pointing here. Debug builds keep the real header for ad-hoc printf debugging; `-DBUN_ALLOW_IOSTREAM` is the opt-out. - `test/internal/source-lints/no-iostream-include.test.ts`: scans `src/`, `packages/bun-uws`, `packages/bun-usockets` for the include so debug CI also catches it. ## Verification With both oven-sh/WebKit#320 and this change applied to a release build: ``` nm build/release/bun-profile | grep ios_base4Init -> empty nm build/release/bun-profile | grep _S_initialize -> empty _GLOBAL__sub_I.00090_globals_io.cc gone bun-profile 547 KB smaller ``` Four `_GLOBAL__sub_I_*locale_inst.cc` stubs remain: these are the libstdc++ facet-id guard-byte initializers (a few dozen `movb $1,(%rax)` each) pulled by `Int128.cpp`'s `std::ostringstream`. `<sstream>` does not emit the static `Init` object and those stubs are near-free. ## Int128.cpp `Source/WTF/wtf/Int128.cpp` includes `<sstream>` and `<ostream>`. Both are load-bearing (`UInt128ToFormattedString`'s `std::ostringstream` and the `operator<<(std::ostream&, ...)` overloads) and neither emits the static `ios_base::Init` object, so they are left alone. <!-- robobun:evidence:begin --> --- **[decide:webkit]** gate passed · iteration 6 · 10 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 1 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/no-iostream-include.test.ts bun test v1.4.0 (eba02a1f6) test/internal/source-lints/no-iostream-include.test.ts: 45 | // root going away, which would make the ban below pass vacuously. 46 | expect(scanned).toBeGreaterThan(0); 47 | } 48 | 49 | violations.sort(); 50 | expect(violations).toEqual([]); ^ error: expect(received).toEqual(expected) - [] + [ + "packages/bun-uws/src/AsyncSocket.h", + "packages/bun-uws/src/HttpContext.h", + "packages/bun-uws/src/HttpRouter.h", + "packages/bun-uws/src/Loop.h", + "packages/bun-uws/src/TopicTree.h", + ] - Expected - 1 + Received + 7 at <anonymous> (/workspace/bun/test/internal/source-lints/no-iostream-include.test.ts:50:22) (fail) C++ sources compiled into Bun do not include <iostream> [748.19ms] 0 pass 1 fail 4 expect() calls Ran 1 test across 1 file. [3.00s] error: script "bd" exited with code 1 __F:1:S:0 release without fix: 1 FAILED bun test v1.4.0-canary.1 (507ab8113) test/internal/source-lints/no-iostream-include.test.ts: 45 | // root going away, which would make the ban below pass vacuously. 46 | expect(scanned).toBeGreaterThan(0); 47 | } 48 | 49 | violations.sort(); 50 | expect(violations).toEqual([]); ^ error: expect(received).toEqual(expected) - [] + [ + "packages/bun-uws/src/AsyncSocket.h", + "packages/bun-uws/src/HttpContext.h", + "packages/bun-uws/src/HttpRouter.h", + "packages/bun-uws/src/Loop.h", + "packages/bun-uws/src/TopicTree.h", + ] - Expected - 1 + Received + 7 at <anonymous> (/workspace/bun/test/internal/source-lints/no-iostream-include.test.ts:50:22) (fail) C++ sources compiled into Bun do not include <iostream> [59.52ms] 0 pass 1 fail 4 expect() calls Ran 1 test across 1 file. [213.00ms] __F:1:S:0 ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/internal/source-lints/no-iostream-include.test.ts bun test v1.4.0 (eba02a1f6) test/internal/source-lints/no-iostream-include.test.ts: (pass) C++ sources compiled into Bun do not include <iostream> [885.41ms] 1 pass 0 fail 4 expect() calls Ran 1 test across 1 file. [3.41s] __F:0:S:0 release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 1174ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [0/1] reconfigure [0/13] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu) nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19) �[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core) �[1m�[92m Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno) �[1m�[92m Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr) �[1m�[92m Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys) �[1m�[92m Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety) �[1m�[92m Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys) �[1m�[92m Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys) �[1m�[92m Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd) �[1m�[92m Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp) �[1m�[92m Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output) �[1m�[92m Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap) �[1m�[92m Compili ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` packages/bun-uws/src/App.h | 3 -- packages/bun-uws/src/AsyncSocket.h | 1 - packages/bun-uws/src/HttpContext.h | 3 -- packages/bun-uws/src/HttpRouter.h | 1 - packages/bun-uws/src/Loop.h | 1 - packages/bun-uws/src/TopicTree.h | 5 --- scripts/build/deps/webkit.ts | 7 --- scripts/build/flags.ts | 6 +++ src/banned-includes/iostream | 27 ++++++++++++ .../source-lints/no-iostream-include.test.ts | 51 ++++++++++++++++++++++ 10 files changed, 84 insertions(+), 21 deletions(-) ``` </details> **gate history** · 8 passed · 1 rejected · iteration 6 <details><summary>evidence per changed file</summary> ``` file reads edits tests packages/bun-uws/src/App.h 4 3 0 packages/bun-uws/src/AsyncSocket.h 1 1 0 packages/bun-uws/src/HttpContext.h 2 2 0 packages/bun-uws/src/HttpRouter.h 1 1 0 packages/bun-uws/src/Loop.h 1 1 0 packages/bun-uws/src/TopicTree.h 4 4 0 scripts/build/deps/webkit.ts 2 3 0 scripts/build/flags.ts 1 1 0 src/banned-includes/iostream 0 1 0 test/internal/source-lints/no-iostream-include.test.ts 2 3 0 ``` </details> <!-- robobun:evidence:end --> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
The vendored simdutf 8.2.0 amalgamation has an
#include <iostream>in thescalar/base64.hsection atSource/WTF/wtf/simdutf/simdutf_impl.h:9949. Nothing in that section uses iostreams; simdutf's only iostream use is already gated behindSIMDUTF_LOGGINGearlier in the same file.Why it matters
SIMDUTF.hincludessimdutf_impl.h, andSIMDUTF.his pulled in by:SIMDUTF.cpp,text/Base64.cpp,text/StringCommon.cpp,text/StringImpl.cpp,unicode/UTF8Conversion.cppsrc/jsc/bindings/helpers.h(transitively included by ~70 translation units),bun-simdutf.cpp, and several direct includesOn libstdc++ each of those TUs ends up with an undefined reference to
_ZSt21ios_base_library_initv, which pullsglobals_io.ofrom libstdc++.a into the link. That object carries the_GLOBAL__sub_I.00090_globals_io.ccinitializer (std::ios_base::Init) and transitively pullslocale_inst.cc,cxx11_locale_inst.cc,wlocale_inst.cc,cxx11_wlocale_inst.cc: roughly fifty functions of locale/facet construction (std::locale::_S_initialize,std::locale::_Impl, ctype/numpunct/moneypunct/timepunct/messages forcharandwchar_t) that run beforemainon every Bun process.From the release
bun-profilelinker map:Bun never uses C++ iostreams.
Change
Drop
<iostream>and<algorithm>from the amalgamatedscalar/base64.hsection. Upstream simdutf already removed both: simdutf/simdutf#962 dropped<iostream>and simdutf/simdutf#979 dropped<algorithm>. This just brings the vendored copy in line until the next simdutf bump.Verification
Standalone compile of
simdutf_impl.hwith the patch: builds cleanly,nmshows noios_baseor_GLOBAL__sub_Isymbols in the object. Same compile against the unpatched header emitsU _ZSt21ios_base_library_initv.After stripping the same undefined symbol from the five affected
libWTF.amembers and relinking Bun against the patched header,nm build/release/bun-profile | grep ios_base4Initis empty and the_GLOBAL__sub_I*_locale_inst.ccinitializers no longer appear (full relink result will be posted once the autobuild artifact is available).Int128.cpp
Source/WTF/wtf/Int128.cppincludes<sstream>and<ostream>. Both are load-bearing:UInt128ToFormattedStringusesstd::ostringstreamand theoperator<<(std::ostream&, {U,}Int128Impl)overloads are declared inInt128.h. Neither header defines the staticios_base::Initobject (only<iostream>does), so they do not contribute to pre-maininitialization and are left as-is.