SIGN IN SIGN UP
oven-sh / bun UNCLAIMED

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one

0 0 150 Rust

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