perf(runtime): reuse the marshalled UTF-8 buffer for repeated C string arguments - #450
Draft
edusperoni wants to merge 1 commit into
Draft
perf(runtime): reuse the marshalled UTF-8 buffer for repeated C string arguments#450edusperoni wants to merge 1 commit into
edusperoni wants to merge 1 commit into
Conversation
…g arguments Marshalling a JS string to char* copied it with strdup on every call and parked each copy on a throwaway external string for GC to reclaim; the IsExternalOneByte fast path above it could never fire for runtime-created strings because the argument itself was never externalized. WriteValue now externalizes the argument in place via MakeExternal when the string is pure ASCII (UTF-8 byte count == UTF-16 length, the exact condition under which the buffer equals the string's one-byte content) and V8 permits it, so later marshals of the same string reuse the buffer with no copy. Non-ASCII, young-generation, and otherwise non-externalizable strings keep the previous copy-per-call behavior. The fast path is also now gated on an ownership registry in OneByteStringResource: only buffers this runtime created are known to be NUL-terminated UTF-8, while a foreign resource's data is Latin-1 with no terminator guarantee and can no longer be handed to native code blindly.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Marshalling a JS string to a native
char*argument previously copied it withstrdupon every call, parking each copy on a throwaway external string for GC to reclaim. TheIsExternalOneBytefast path above that code could never fire for runtime-created strings, because the argument string itself was never externalized — the reuse design was half-wired.This PR completes it:
Interop::WriteValue'sCStringEncodingpath now externalizes the argument string in place viav8::String::MakeExternalwhen the string is pure ASCII and V8 permits it. Later marshals of the same string hit the fast path and reuse the same buffer — zero copies, zero allocations.Hardening
The pre-existing fast path blindly trusted
resource->data()of any external one-byte string as NUL-terminated UTF-8 — but V8's resource contract promises Latin-1 content with no terminator.OneByteStringResourcenow keeps a thread-safe ownership registry, and the fast path only reuses buffers this runtime created (OneByteStringResource::Owns).Safety notes
Reference::FromPointer→Pointer::NewInstance) takes no ownership of the buffer, so there is no double-free against the externalized string's resource. The~ReferenceWrapperdisposeData_fix from addf287 is load-bearing for this interaction.MakeExternalfailure is handled by falling back to the anchor path, which then owns the resource; ownership is unambiguous on every branch.Tests
Two new specs in
ReferenceTests.js, built onfunctionWithCharPtrechoing its argument pointer:__collect()first — V8 refuses to externalize young-generation strings, mirroring the real-world shape where long-lived strings are the ones marshalled repeatedly.Full suite: 1378 tests, 0 failures (Debug, iOS simulator).
Open question for review
Whether the perf win justifies the added state (the resource registry) without a motivating benchmark — this PR exists to make that discussion concrete. If a real workload shows C-string marshalling is never hot, closing unmerged is a fine outcome.