Repository navigation
Conversation
|
Preview build of a1ba2bc: |
…ites says so An embedder can make an ArrayBuffer over memory that another process or thread also writes, such as a file mapping. JavaScript sees an ordinary ArrayBuffer, and isShared() is false. Native code that reads a byte twice and counts on what it was could not tell such bytes from its own. ArrayBuffer::createFromBytes takes ArrayBufferExternalWriters::Yes for that memory. The flag is on ArrayBufferContents, so it goes where the bytes go: transfer(), a structured clone that transfers and a transfer to a Worker keep it, and a copy of the bytes does not have it. It is a bool next to m_hasMaxByteLength, in what was padding. canBeWrittenConcurrently() is isShared() or that flag, on ArrayBufferContents, ArrayBuffer and JSArrayBufferView. A typed array that has no ArrayBuffer yet answers from its mode, as isShared() does. Nothing in JavaScriptCore asks it yet. $vm.createArrayBufferWithExternalWriters and $vm.canBeWrittenConcurrently are for JSTests/stress/array-buffer-external-writers.js.
robobun
force-pushed
the
robobun/5167e3ae/array-buffer-written-outside-js
branch
from
October 22, 2026 09:10
93e6841 to
a1ba2bc
Compare
This was referenced Oct 22, 2026
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.
Problem
ArrayBufferover memory that another process also writes. Bun'sBun.mmapdoes: its buffer is a file mapping.isShared()is false for it.SharedArrayBufferbefore it reads a byte twice reads such bytes in place. In Bun, on a mapped file that another process rewrites:panic: assertion failed: self.input[pos.cast()] == unit(Bun.YAML.parse) and out-of-bounds writes in the UTF-8 decoders.Fix
ArrayBuffer::createFromBytestakesArrayBufferExternalWriters::Yes. The flag is onArrayBufferContents, so a transfer moves it and a copy of the bytes does not have it.canBeWrittenConcurrently()isisShared()or that flag, onArrayBufferContents,ArrayBufferandJSArrayBufferView. A typed array with noArrayBufferanswers from its mode.JSTests/stress/array-buffer-external-writers.js, through$vm.createArrayBufferWithExternalWritersand$vm.canBeWrittenConcurrently. Copy a Bun.mmap view where a shared buffer is copied (file mappings only) bun#46512 uses it on this branch's preview build.Background
ArrayBufferover a mapping. Only native code has to know that the bytes change.ArrayBufferContentsowns the bytes.transfer(), a structured clone that transfers and a transfer to a Worker move it.SharedArrayBufferfromBun.mmap(users see it).Downsides
ArrayBufferhas one more byte to set and to move. Release Bun:new ArrayBuffer(64)1,082.9 to 1,085.4 instructions,transfer()2,074.4 to 2,085.5,slice()1,288.7 to 1,291.8. The sizes stay 56 and 96 bytes.TypedArray.prototype.sortcopies a shared array first and still sorts a mapped one in place, see Notes.Notes
Why the flag is on the contents.
m = Bun.mmap(f); t = m.buffer.transfer()detachesmand givestthe same mapping. A flag on the view or on theArrayBufferobject would be lost there.swap()carries the flag, so the move constructor, move assignment anddetach()do.tryAllocate()clears it, socopyTo()(a transfer of a pinned buffer) andslice()give memory without it.shareWith()copies it with the other members. The test checks each of these from JavaScript.What the flag does not say. A typed array with no
ArrayBufferyet (FastTypedArray,OversizeTypedArray) answers false from its mode. Bun's thread pool can write such a view during anfs.read. That case needs a record of the writer and is not part of this change. The flag has no setter: it is a property of the memory, given where the buffer is made.Measurements. Release builds of Bun at oven-sh/bun@38afee6c84 with WebKit d6375c7 and with this branch's preview build.
instructions:u,(I(2N) - I(N)) / N, minimum of 5 runs, concurrent JIT and GC off. A second copy of the first binary gives 1,083.2, 2,076.1 and 1,285.2 for the three operations.sizeof(ArrayBufferContents)is 56 andsizeof(ArrayBuffer)is 96 before and after, with the debug ASAN and the LTO headers, and astatic_assertholds the first.A way to +0 for the moves. The two flags can share one byte, so that
swap()moves one byte as it did. That changes the 12 places that readm_hasMaxByteLength. I kept the twobools to keep the difference to upstream small. Say so if you prefer the one byte.sort().TypedArray.prototype.sortcopies a shared array at three places inJSGenericTypedArrayViewInlines.h, because the radix sort counts the digits of every element and then places every element by those counts. With another process that rewrites a mappedUint32Array,sort()stops inradixSortWithCountersatstd::span<unsigned int>::operator[]: Assertion '__idx < size()' failed. An earlier revision of this pull request made those three places askcanBeWrittenConcurrently(). I took that out: the copy doubles the memory of a sort of a mapped array, andWTF::Vectorcannot hold a copy of 2 GiB or more, so such a sort would throw where it works today. That needs its own decision.simdutf. The first revision also carried the bound for
rewind_and_validate_with_errors. It is independent of this change and is now #878.