Lowercase Headers names on iteration with Highway SIMD (#31489)
## What does this PR do? `Object.fromEntries(headers)` — and the other WHATWG `Headers` iteration paths (`[...headers]`, `headers.keys()`, `headers.toJSON()`) — lowercase every *uncommon* header name on each iteration. For names stored in their original mixed case (the `X-Custom-Header`-style keys), this takes `String::convertToASCIILowercase`'s slow path: allocate a new string and lowercase every character. Both the first-uppercase scan and the per-character lowercasing are plain byte loops that the compiler auto-vectorizes. Their vector width tracks the build's `-march`: a baseline build (no AVX2) scans/copies in 16-byte steps where a native build does 32. That's the ~6% regression observed on the `linux-x64-baseline` canary for `Object.fromEntries(headers)` (2.42 → 2.57 µs), while the other Headers ops in `bench/snippets/headers.mjs` were flat or faster. This routes the header-name lowercasing through a **runtime-dispatched Highway SIMD** implementation, so the op runs at the machine's best available width regardless of the build's `-march`. ## Changes - **`highway_strings.cpp`** — two new kernels, same `HWY_EXPORT` + `HWY_DYNAMIC_DISPATCH` pattern as the existing ones: - `IndexOfFirstAsciiUpperImpl` — SIMD scan for the first ASCII uppercase letter (mirrors `FirstNonAscii8Impl`). Lets already-lowercase names keep the WTF no-allocation fast path. - `LowerAsciiImpl` — copy + lowercase (mirrors `CopyAsciiPrefixImpl`). Detects `A`–`Z` with one unsigned range compare (`(c - 'A') <= 25`) and adds `0x20` only on those lanes. Digits, HTTP token punctuation (`! # $ % & ' * + - . ^ _ ` | ~`), and Latin-1 bytes ≥ 0x80 are left untouched — a naive "OR 0x20" lowercase would corrupt `^`→`~` and `_`→`DEL`, both of which are valid header-name characters. - Exposed as `extern "C"` `highway_index_of_first_ascii_upper` / `highway_lower_ascii`. - **`WebCore::lowercaseHeaderName`** (`HTTPHeaderMap.cpp`) — scans with the SIMD kernel; returns the original `String` unchanged when already lowercase (matching `convertToASCIILowercase`), otherwise allocates an 8-bit `StringImpl` and fills it with the lowercase kernel. 16-bit strings (never valid HTTP tokens) fall back to `convertToASCIILowercase`. - **Both iteration call sites** now use it: `HTTPHeaderMap::KeyValue::asciiLowerCaseName()` (the `Headers` iterator key build) and `getInternalProperties` (the `toJSON` / internal-properties path). - **`verify-baseline-static` allowlists** (x64 / aarch64 / windows) gain the two new dispatch symbols per target, with feature ceilings matching the analogous scan/copy kernels (`FirstNonAscii8Impl` / `CopyAsciiPrefixImpl`). Verified against `nm` on a release `-profile` build that the emitted mangled names match the allowlist entries exactly. ## How did you verify your code works? - `bun bd test test/js/web/fetch/headers.test.ts` — 88 pass (ASAN-clean; exercises the new `createUninitialized` + kernel-write path). Also `headers-case`, `fetch_headers`, `headers.undici` suites pass (60 more). - New coverage in `headers.test.ts` sweeps header-name lengths `3`–`129` (straddling the 16/32/64-byte SIMD block boundaries and their tail remainders) through `Object.fromEntries` / `entries()` / spread / `keys()` / `toJSON()`, plus a case with every valid HTTP token punctuation character adjacent to letters, and an already-lowercase case — all cross-checked against a scalar JS reference. This is a behavior-preserving change, so the test passes before and after by design; it guards the SIMD kernel against tail/boundary bugs and non-letter corruption. The perf evidence is the baseline canary. - Release build confirms the runtime-dispatch tables are emitted and the kernels compile for every target namespace (SSSE3 … AVX3_ZEN4). The baseline-ISA allowlist feature sets were verified against an actual `-baseline` `-profile` build: `verify-baseline-static` reports **zero violations** for both new symbols. The compiler auto-vectorizes the sub-vector remainder with AVX-512 masked ops, so `LowerAsciiImpl` reaches `AVX512VL` and `IndexOfFirstAsciiUpperImpl` reaches `AVX512F` — both inside the runtime-dispatched `bun::N_AVX3*` namespaces — so the x64 ceilings come from the tool's own report rather than copied from a neighbouring kernel. (The only violations on that build are pre-existing `HUF_decompress*` BMI1 symbols from zstd, unrelated to this PR.) The aarch64 SVE and Windows MSVC entries mirror the analogous scan/copy kernels — same op families, no local build here to measure those two lanes.
R
robobun committed
67d6021fde7922e1e2e0b6feaad2ba8993975225
Parent: f472981
Committed by GitHub <noreply@github.com>
on 5/28/2026, 8:25:05 PM