Skip to content

Add ASCII and UTF-16 JSON parsing APIs to JSI - #2259

Open
mrousavy wants to merge 1 commit into
facebook:static_hfrom
mrousavy:mrousavy/jsi-json-raw-buffers
Open

mrousavy wants to merge 1 commit into
facebook:static_hfrom
mrousavy:mrousavy/jsi-json-raw-buffers

Conversation

@mrousavy

Copy link
Copy Markdown
Contributor

Summary

Add Value::createFromJsonASCII and Value::createFromJsonUtf16, with matching runtime methods.

Nitro's Android AnyJSON converter receives JSON as UTF-16 from a Java String. It currently creates a JSI string and calls cached JSON.parse. Converting to UTF-8 first was slower in the earlier emulator benchmark: Hermes's UTF-8 entry point uses a streaming lexer, while JSON.parse selects the raw ASCII/UTF-16 lexer with SIMD string scanning.

The new Hermes methods borrow bounded native buffers synchronously and call those existing raw parser specializations. They avoid creating the JS input string and calling a JS function. Every JSON root returns as a Value, including primitives, null, and arrays. UTF-16 preserves raw and escaped lone surrogates. Length counts bytes for ASCII and code units for UTF-16; explicit NUL and malformed tails are consumed as input. The buffer stays valid and unchanged until the call returns, and is not retained. ASCII bytes must be below 128, with a debug assertion for misuse. Parsing allocates; JNI callers must not hold GetStringCritical across it.

Runtime provides string-plus-JSON.parse defaults. Both decorators forward the methods, and WithRuntimeDecorator wraps each call in Around. Tracing explicitly uses the recorded fallback, matching #2206, so the existing string and function-call records capture the parse and its returned value. No new trace record is needed.

This adds virtuals to IRuntime, following the current development interface. The checkout has no JSI version macro or IRuntime2. It requires matching JSI headers and binaries; direct IRuntime implementations must add the methods, while Runtime subclasses inherit defaults. React Native ships its own JSI copy. The existing UTF-8 API, Hermes C ABI, and bytecode format are unchanged.

The Release benchmark is in benchmarks/jsi-json. Three fresh process runs per plain/thread-safe runtime on an M5 Max, macOS arm64, HEAP_HV_64, no JIT or sanitizers: for a plain runtime, a 4096-unit Unicode string takes about 504 ns through raw UTF-16, 759 ns through cached JSON.parse including input-string creation, and 8432 ns through the native UTF-8 API with pre-encoded input. Paired batch medians give 1.50x and 16.7x speedups. Nested ASCII/Unicode objects improve by about 9%/4%; a 4096-character ASCII string through UTF-16 is roughly unchanged (0.997x). Raw ASCII improves the 4096-character string by 1.48x versus cached parse with ASCII-string creation. The harness also measures cached parse with a prebuilt JS string. These are native C++ microbenchmarks on this Mac, with no JNI or Android-device claim.

Refs #2258

Test Plan

Debug + ASan, -O1, AppleClang 21, macOS arm64, HEAP_HV_64, JSI_UNSTABLE=OFF:

  • cmake --build cmake-build-asan --target check-hermes -j 12: 4164 expected passes, 6 expected failures, 199 unsupported tests. Node-API: 41 passes, 23 skips, no failures.
  • APITests: all 389 tests pass. APILeanTests: all 3 pass.
  • Correctness tests cover every JSON root, bounded and non-terminated buffers, SIMD/string boundaries, input ownership after return, BMP/supplementary text, raw/escaped lone surrogates as values and keys, NUL, BOM, invalid JSON, and malformed trailing code units. The forced generic UTF-16 string fallback is covered too.
  • Removing the native overrides makes the global-JSON-deletion test fail with getPropertyAsObject: property 'JSON' is undefined. Removing the two RuntimeDecorator forwards makes both counting/sentinel tests fail. Restoring the implementation passes the full API and lean suites.
  • Separate Debug + ASan + UBSan build, JSI_UNSTABLE=ON and HERMESVM_SANITIZE_HANDLES=ON: all 15 focused tests pass. ParseAcrossAllocations explicitly sets SanitizeRate=1 and covers successful and throwing parses across moving collections. Lean API tests pass too.
  • All seven changed C++ sources pass -Werror syntax checks. The decorator audit reports no gaps. Changed lines were formatted with clang-format 16, preserving JSI's pointer/reference style.

No test262 run was requested. JSC, V8, and Android integration were not tested. The existing shared JSON lexer accepts 1.; this patch keeps that numeric-grammar behavior and does not change JSONLexer.cpp.

Expose bounded ASCII and UTF-16 JSON buffers through Value and IRuntime,
with Runtime fallbacks, decorator forwarding, and recorded tracing
fallbacks. Hermes borrows the caller's buffer synchronously and reuses
its existing raw parser specializations.

Cover JSON roots, bounded buffers, surrogate preservation, error paths,
GC movement, decorators, and trace replay. Add a Release benchmark against
cached JSON.parse and the native UTF-8 API.

Local prototype pending upstream API design feedback.
@meta-cla meta-cla Bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Oct 21, 2026
@mrousavy
mrousavy deployed to npm-publish October 21, 2026 11:53 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
npm-publish — 03178259 Deployed Oct 21, 2026 by mrousavy via publish #1617
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant