Skip to content

simdutf: remove unused <iostream> from scalar/base64.h amalgamation - #320

Merged
Jarred-Sumner merged 2 commits into
mainfrom
farm/d433c90c/drop-simdutf-iostream
Jul 23, 2026
Merged

simdutf: remove unused <iostream> from scalar/base64.h amalgamation#320
Jarred-Sumner merged 2 commits into
mainfrom
farm/d433c90c/drop-simdutf-iostream

Conversation

@robobun

@robobun robobun commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

The vendored simdutf 8.2.0 amalgamation has an #include <iostream> in the scalar/base64.h section at Source/WTF/wtf/simdutf/simdutf_impl.h:9949. Nothing in that section uses iostreams; simdutf's only iostream use is already gated behind SIMDUTF_LOGGING earlier in the same file.

Why it matters

SIMDUTF.h includes simdutf_impl.h, and SIMDUTF.h is pulled in by:

  • WTF: SIMDUTF.cpp, text/Base64.cpp, text/StringCommon.cpp, text/StringImpl.cpp, unicode/UTF8Conversion.cpp
  • Bun: src/jsc/bindings/helpers.h (transitively included by ~70 translation units), bun-simdutf.cpp, and several direct includes

On libstdc++ each of those TUs ends up with an undefined reference to _ZSt21ios_base_library_initv, which pulls globals_io.o from libstdc++.a into the link. That object carries the _GLOBAL__sub_I.00090_globals_io.cc initializer (std::ios_base::Init) and transitively pulls locale_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 for char and wchar_t) that run before main on every Bun process.

From the release bun-profile linker map:

4787910  t  _GLOBAL__sub_I.00090_globals_io.cc
47a22b0  t  _GLOBAL__sub_I_cxx11_locale_inst.cc
47ae6a0  t  _GLOBAL__sub_I_cxx11_wlocale_inst.cc
47c1710  t  _GLOBAL__sub_I_locale_inst.cc
47d9260  t  _GLOBAL__sub_I_wlocale_inst.cc

Bun never uses C++ iostreams.

Change

Drop <iostream> and <algorithm> from the amalgamated scalar/base64.h section. 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.h with the patch: builds cleanly, nm shows no ios_base or _GLOBAL__sub_I symbols in the object. Same compile against the unpatched header emits U _ZSt21ios_base_library_initv.

After stripping the same undefined symbol from the five affected libWTF.a members and relinking Bun against the patched header, nm build/release/bun-profile | grep ios_base4Init is empty and the _GLOBAL__sub_I*_locale_inst.cc initializers no longer appear (full relink result will be posted once the autobuild artifact is available).

Int128.cpp

Source/WTF/wtf/Int128.cpp includes <sstream> and <ostream>. Both are load-bearing: UInt128ToFormattedString uses std::ostringstream and the operator<<(std::ostream&, {U,}Int128Impl) overloads are declared in Int128.h. Neither header defines the static ios_base::Init object (only <iostream> does), so they do not contribute to pre-main initialization and are left as-is.

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.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1690f640-9b21-4f5e-a3fc-6f9471c15503

📥 Commits

Reviewing files that changed from the base of the PR and between c9296e3 and 880941b.

📒 Files selected for processing (3)
  • Source/WTF/wtf/bun/BannedIncludes/iostream
  • Source/WTF/wtf/simdutf/simdutf_impl.h
  • Source/cmake/OptionsJSCOnly.cmake

Comment @coderabbitai help to get the list of available commands.

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.
@robobun

robobun commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Added a second commit that bans <iostream> at compile time for non-Debug USE_BUN_JSC_ADDITIONS builds: Source/WTF/wtf/bun/BannedIncludes/iostream is prepended to the include search path and #errors with a pointer to the explanation. -DBUN_ALLOW_IOSTREAM is the opt-out; Debug builds are unaffected.

Companion Bun-side change (same shim for Bun's own C++ plus the bun-uws cleanup): oven-sh/bun#35256.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +56 to +58
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
include_directories(BEFORE "${WTF_DIR}/wtf/bun/BannedIncludes")
endif()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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:22include(WebKitCommon)
  • WebKitCommon.cmake:320include(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-28add_subdirectory(ThirdParty/gtest)
  • Tools/CMakeLists.txt:24-26add_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:291
  • Source/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

  1. On Linux or macOS, configure with -DPORT=JSCOnly -DUSE_BUN_JSC_ADDITIONS=ON -DCMAKE_BUILD_TYPE=Release (or RelWithDebInfo).
  2. WTF_DIR is set by WebKitFS.cmake (included at WebKitCommon.cmake:308, i.e. before Options${PORT}), so the shim path expands correctly.
  3. Run ninja (or cmake --build .) with no explicit target.
  4. Ninja builds gtest-all.cc, which #includes gtest.cc#include <iostream> → resolves to Source/WTF/wtf/bun/BannedIncludes/iostream#error "<iostream> is banned in Bun WebKit release builds…".
  5. 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 the if(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 a Source/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 ...) on WTF/JavaScriptCore/bmalloc instead of the global include_directories().

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
880941bd autobuild-preview-pr-320-880941bd 2026-07-23 09:54:56 UTC

@Jarred-Sumner
Jarred-Sumner merged commit 2f7e89c into main Jul 23, 2026
43 checks passed
robobun added a commit to oven-sh/bun that referenced this pull request Jul 23, 2026
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.
Jarred-Sumner added a commit to oven-sh/bun that referenced this pull request Jul 24, 2026
## 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>
liooil pushed a commit to liooil/poly that referenced this pull request Aug 7, 2026
## 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants