Skip to content

[WTF] URLParser: table-lookup SIMD classification; keep host:port and IPv4 hosts on the fast path - #454

Merged
Jarred-Sumner merged 1 commit into
mainfrom
claude/url-fastpath-port-ipv4
Aug 16, 2026
Merged

[WTF] URLParser: table-lookup SIMD classification; keep host:port and IPv4 hosts on the fast path#454
Jarred-Sumner merged 1 commit into
mainfrom
claude/url-fastpath-port-ipv4

Conversation

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Follow-up to #452. Two independent changes, output byte-identical to before:

Table-lookup SIMD classification. The run scanners classified 16 bytes with a chain of vector compares (up to ~12 per block for the host scanner). This replaces that with the two-pshufb/tbl nibble-table technique: low[b & 0xF] & high[b >> 4] != 0. High nibbles with the same 16-byte stop pattern share a bit, so any of our stop sets fits in 8 bits. The tables are built at compile time from scanClassTable itself (consteval, static_assert(tables.fits)), so the vector and scalar classifiers can't drift apart. Enabled when SSSE3 (our x64 baseline is nehalem) or NEON is available; the compare-based lambdas remain as the fallback and were re-verified with -march=x86-64.

host:port and dotted-quad hosts stay on the prefix fast path. The fast path handed off to the state machine at : after a host, and for any host whose last label could be a number — i.e. every http://localhost:3000/… and http://127.0.0.1:8080/…. A decimal, non-default port with no leading zero, and a canonical dotted-quad IPv4 address, serialize to exactly their input, so they can be copied. Everything else (:, :0080, default ports, :65536, host:80@evil, hex/octal/short IPv4, trailing dot, …) still takes the general path.

Cycles per URL (this box, -march=nehalem, min of 8; Ada 4.0.0 and Ada with ada-url/ada#1214 which ports #452's ideas, for reference):

dataset Ada 4.0 Ada + WebKit#1214 WebKit #452 this PR
top100 665 488 313 278
wikipedia 538 510 245 218
long (3.9 KB) 23689 12303 4073 3237
medium 950 865 368 335
short 686 542 325 282
tiny 436 436 277 231
host:port (localhost:3000, 10.0.0.5:8080, …) 1191 1172 661 367
ipv4 777 803 588 367
ipv6 1105 1064 990 988
idn 6311 6195 5360 5342
relative 1356 1352 754 756
path_normalize 2169 1907 1395 1323
host_normalize 1761 1779 1337 1283
userinfo_port 1996 1944 1075 964
file / nonspecial / invalid 1085 / 1086 / 640 1102 / 1108 / 651 493 / 459 / 318 485 / 452 / 341

Instructions/URL: top100 680 → 553, long 7830 → 5720, host:port 1217 → 658, ipv4 1082 → 670.

Verification: differential old-parser-vs-new over the full corpus (~1.4M inputs incl. WPT-with-bases, Ada-derived corpora, Windows/UNC, surrogate and structured fuzz sets) identical in both string widths, also with the SSE2-only fallback build; TestWTF *URL* (Debug, parser self-check on); libFuzzer old-vs-new differential target (ASan + asserts) clean.

… dotted-quad hosts on the fast path

- Classify 16 bytes with two pshufb/tbl nibble-table lookups instead of a chain of
  compares. The tables are computed at compile time from scanClassTable (consteval,
  static_assert'ed to fit), so vector and scalar classification cannot disagree.
  Used when SSSE3 or NEON is available; the compare-based classifiers remain as the
  fallback. top100: 680 -> 553 instructions/URL, 3.9 KB URLs: 7830 -> 5720.
- The prefix fast path handed off to the state machine at ':' after a host and for
  any digit-led last label. A decimal, non-default port without a leading zero, and a
  canonical dotted-quad IPv4 host, produce output identical to the input, so copy them.
  http://localhost:3000/... style URLs: 1217 -> 658 instructions/URL; IPv4 hosts:
  1082 -> 670.
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 59 minutes

Limit details: You’ve used all 1 included review currently available under your plan. You completed 75 included PR reviews in the past 7 days; at that activity level, included reviews refill at 1 review per hour.

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: f45ee9a2-cd71-4b8f-96db-909ad028a636

📥 Commits

Reviewing files that changed from the base of the PR and between 0cbb4a1 and 0f27410.

📒 Files selected for processing (1)
  • Source/WTF/wtf/URLParser.cpp

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

@github-actions

Copy link
Copy Markdown

Preview Builds

Commit Release Date
0f27410c autobuild-preview-pr-454-0f27410c 2026-08-16 19:49:31 UTC

@Jarred-Sumner
Jarred-Sumner merged commit c6cfe90 into main Aug 16, 2026
47 checks passed

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

I reviewed this PR and didn't find any bugs. Because it extends the URL parser's fast path for host:port and IPv4 hosts — where a divergence from the state-machine path would be a parser differential with origin/SSRF implications — a human look is still worthwhile.

What was reviewed:

  • parseCanonicalIPv4Address edge cases (leading zeros, >255 octets, short/long forms, hex/octal, trailing dot) — all correctly return nullopt and fall to the general path.
  • Port fast-path guards: @ after digits, \\ after digits, default ports, leading-zero ports, >65535, port 0, tab/newline in port — all rejected or handed to the state machine; m_portLength max is 6, fits the 3-bit field.
  • Nibble-table classifier: tables are consteval-derived from scanClassTable with static_assert(tables.fits); pshufb/tbl indices are masked to 0–15 so the high-bit-zeroes-output quirk of _mm_shuffle_epi8 doesn't apply; the narrowed sentinel bytes 0x00/0xFF are stops in every class used.
Extended reasoning...

Overview

This PR makes two independent performance changes to Source/WTF/wtf/URLParser.cpp:

  1. Replaces the per-block chain of SIMD compares in the run scanners with a two-table nibble lookup (pshufb/tbl), gated on SSSE3 or NEON. The tables are built at compile time by makeNibbleTables directly from scanClassTable, with static_assert(tables.fits) enforcing that ≤8 distinct high-nibble patterns exist. The compare-chain lambdas remain as the fallback for pre-SSSE3 x86.
  2. Extends the straight-line prefix fast path so that scheme://host:port (decimal, non-default, no leading zero) and canonical dotted-quad IPv4 hosts are copied verbatim rather than handed to the state machine. parseCanonicalIPv4Address is factored out of parseIPv4Host and reused as the gate.

Security risks

URL parsing is security-critical: any input for which the fast path produces a different serialization or component boundary than the state machine is a parser differential that can lead to origin confusion or SSRF. The PR is explicitly designed so the fast path only handles inputs that serialize to themselves, and everything else falls through unchanged. I traced the adversarial cases I could think of — host:3000@evil, host:3000\\evil, default ports, :0080, :65536, hex/octal/short IPv4, trailing-dot IPv4, tab/newline inside port, foo.123 (ends-in-a-number) — and each is correctly rejected by the hasPort conjunction or the parseCanonicalIPv4Address gate and takes the pre-existing AuthorityOrHost path. The !hasPort branch's new IPv4 check at line 1718 is only reached when classes & HostNotPlain is clear, so [hostStart, p) contains only plain host characters when parseCanonicalIPv4Address is called there.

For the SIMD classifier, the risk would be a byte that classifies differently under the nibble-table path than under the scalar scanClassTable lookup used in the tail. Since the tables are derived from scanClassTable itself via consteval, they encode the identical predicate; the pshufb high-bit behaviour is neutralised by masking indices to 4 bits.

Level of scrutiny

High. This is ~200 net lines of intricate control flow and platform-specific intrinsics in a WHATWG-URL-spec-conformance path that determines origins. The author's verification (differential over ~1.4M inputs including WPT and fuzz corpora, both string widths, SSE2 fallback build, ASan libFuzzer) is strong, but the edge-case density here is exactly where a second pair of eyes on the hasPort conjunction and the findPathRunEnd rewrite is valuable.

Other factors

  • m_portLength is a 3-bit bitfield (max 7); the fast path caps digits at 5 so p - hostEnd ≤ 6, which fits.
  • defaultPort returns UINT_MAX for File/NonSpecial, but this fast path is only entered for special non-File schemes (line 1670 bails on those), so that sentinel is never compared against a real port here.
  • The additionalStopCharacter template default of 0 folds NUL into the stop set, which is already a stop for every class used, so it's a no-op.
  • No prior human or bot reviews on this PR (CodeRabbit was rate-limited).

anonrig added a commit to ada-url/ada that referenced this pull request Aug 17, 2026
…st path

Port the remaining oven-sh/WebKit#454 ideas onto current main (#1213
already keeps host:port on the prefix fast path):

- Classify 16-byte runs with two pshufb/tbl nibble tables when SSSE3 or
  NEON is available (SSE2 compare chains remain the fallback).
- Accept a canonical dotted-quad IPv4 host that serializes to exactly
  those characters (no trailing dot, no hex/octal/short form).
- Cheap-skip other digit-led hosts so non-decimal IPv4 does not enter
  the SIMD scanner only to fall through.

Output is intended to be byte-identical to the previous parser.

Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
anonrig added a commit to ada-url/ada that referenced this pull request Aug 17, 2026
…st path

Port the remaining oven-sh/WebKit#454 ideas onto current main (#1213
already keeps host:port on the prefix fast path):

- Classify 16-byte runs with two pshufb/tbl nibble tables when SSSE3 or
  NEON is available (SSE2 compare chains remain the fallback).
- Accept a canonical dotted-quad IPv4 host that serializes to exactly
  those characters (no trailing dot, no hex/octal/short form).

Output is intended to be byte-identical to the previous parser.

Signed-off-by: Yagiz Nizipli <yagiz@nizipli.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.

1 participant