-
Notifications
You must be signed in to change notification settings - Fork 72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support passable immutable ArrayBuffer as copy-data #1331
Comments
attn @erights @gibson042 @mhofman |
I'm extremely concerned by the performance overhead of such a user-land abstraction. We should pursue a standard approach which allows engines to avoid array buffer copies, and minimal overhead on read access. One such proposal at stage 1 is @Jack-Works's Limited ArrayBuffer |
Shimming a standards track solution would be good. Ah, memories. (My abandoned hobbies include a decade old fantasy of JavaScript having byte strings.) |
Attn @phoddie |
If you'd like an efficient read-only const bytes = Uint8Array.of(1, 2, 3);
petrify(bytes.buffer);
bytes[2] = 1; ...throws Yes, a fully standard solution would be welcome. |
A petrified buffer would certainly be eligible for passing over a CapTP. Shimming petrify I think would require us to replace the TypedArray constructors and presenting all typed arrays as proxies, such that we could emulate petrification of a TypedArray with an ArrayBuffer shared by other TypedArrays. |
This may be what you mean, but... I wouldn't suggest shimming |
To shim making an ArrayBuffer itself read-only, we'd need a way to actually make an ArrayBuffer read-only in place. The only thing we know how to shim would be something that returns what seems to be a new read-only ArrayBuffer. Same for a TypedArray. |
Aye, I lay it out like this because oodles of runtime overhead might not be acceptable, even in an intermediate step toward a standard. But if The web platform’s solution to this problem is opaque Blob objects. If you want to see the contents, you have to copy them into a TypedArray. We could certainly harden and make blobs passable too without doing anything to TypedArrays. |
Y'all are the experts in patching things into a web-ish runtime. My goal is to avoid an ugly optimization problem around Maybe side-stepping the problem with |
Doh! I'm embarrassed that I didn't think of this. Thanks! Ok, I just read about Blobs at https://developer.mozilla.org/en-US/docs/Web/API/Blob . Aside from their non-JS This would take the pressure off of needing to shim making an ArrayBuffer or TypedArray read-only in place. |
I like that! |
I think the expectation of |
Yea, I had the same thought walking back from coffee just now. Alas. |
We could add a non standard option bag to I actually think copy on write array buffers would be a good general solution on top of being a nice optimization. First it would likely apply to non Blob use cases. For example doing an class Blob {
constructor([inputBuffer]) {
this.#buffer = inputBuffer.slice(0);
}
async arrayBuffer() {
return this.#buffer.slice(0);
}
} |
I mentioned Copy-on-Write in the context of |
See #1538 |
Interesting bit of related work: Hex template literal | DeviceScript example: const buf: Buffer = hex`00 ab 12 2f 00` h/t @turadg |
FWIW - the Moddable SDK has long done something like that for UUIDs to simplify BLE code. This generates an ArrayBuffer with the bytes of the UUID:
|
motivation: I was trying to do an isolated signer as an endo app, but I hit:
So I had to put the signer and the network client in the same vat. :-/ hdWallet.js 0130695 |
My latest batch of thoughts on this problem: We need a solution that is a good balance between:
We might have to take a page from the promise.then distinguished method trick, but we can use a symbol-named method. This would allow eval twins to assimilate CopyBytes and still keep a private WeakMap of known-to-be-unmodifiable ArrayBuffers. Consider this sketch: const copyBytesSymbol = Symbol.for('copyBytesInto');
const fixedBytes = new WeakMap();
const copyBytesPrototype = {
[copyBytesSymbol](target) {
target.set(fixedBytes.get(this), 0);
}
};
const makeCopyBytes = array => {
let copyBytes = fixedBytes.get(array);
if (copyBytes !== undefined) {
return copyBytes;
}
copyBytes = Object.create(copyBytesPrototype);
fixedBytes.set(copyBytes, fromByteArray(array)); // XXX
return harden(copyBytes);
};
const copyBytesInto = (target, source) => {
const internal = fixedBytes.get(source);
if (internal !== undefined) {
target.set(source, 0);
} else if (source[copyBytesSymbol] !== undefined) {
assert.typeof(source[copyBytesSymbol], 'function');
source[copyBytesSymbol](target)
}
}; |
What "promise.then distinguished method trick"? |
Using a distinguished method like |
So const blobArrayBuffer = uncurryThis(Blob.prototype.arrayBuffer);
const arrayBuffer = await blobArrayBuffer(new Blob(['hello, world'])) |
We've avoided implementing That said, iIt should mostly be possible, with the exception of |
Ok, I tried learning about
The The problem holding |
To be clear, I’m not proposing that passStyleOf would certify an object as being pass-style=CopyBytes, with methods that are safe to use, but suggesting that we (maybe) could use a distinguished method to convert it into a handle to the immutable content it stored at the time it was first recognized by passStyleOf and thereafter treated as an opaque handle to that data. To that end, maybe we could even adopt a TypedArray, which we could at least capture by built-in methods. |
For a new data type with a new method, I see only these choices:
This leaves us with the adoption option. I don't see any other choice. I don't see a way to do an ocapn The problem with starting with UInt8Array instead is indexed property access. The only way to support this on an immutable UInt8Array variant is to make it a proxy, which I do not want to do just to get to ocapn conformance. So, I'd like to start by proposing the |
I changed the title for clarity. But if it is too narrow for what this issue is intended for, change it back and we can start a separate narrower issue. |
I don't think we should adopt the The My understanding of the problem in #1583 is that we can't trust an object with methods to behave as inert copy data unless we can recognize it, and that we can't recognize objects because of eval twins. I would however say we're already in a world where we need to recognize objects: for plain objects and arrays, unless we have a predicate to test for exotic proxy behavior, we shouldn't trust these either to be copy passable. Maybe we can take the approach of promises and add a static method (or even just rely on the constructor, if we drop validation) to const bytes = Uint8Array.from([1,2,3,4,5,6,7,8]);
const ba1 = ByteArray(bytes);
const ba1Bytes = ba1.slice();
assert(ba1Bytes !== bytes); // However the contents are the same
const ba2 = ByteArray(ba1);
assert(ba2 === ba1); That of course assumes like promises that there is a "genuine" / intrinsic ByteArray, but I think it's fine for passStyle installed as a sort of trusted shim to define this. We already need to switch to a more centralized passStyleOf anyway because of the problem of proxy recognition. But I don't think we automatically need to move |
Wow... this is done?! Help me understand how? I don't see anything about Where should I look? |
Sigh. Likely another github automation screwup. Not the first time. I’ll reopen. The good news is the thing that caused it is genuine progress towards closing this. But there’s a stack of about 3 PRs needed to do so. Thanks for noticing! |
The cause of the github automation screwup:
|
2024-10-18:
The plan of record for passable immutable binary data is an immutable
ArrayBuffer
per our proposal to TC39.2022-10-18:
Our current best solution for carrying bytes is a Uint8Array, which we use pervasively for hardened streams. This is one of the reasons that we treat Typed Arrays like Map for the purposes of hardening. The content is mutable but the API surface is tamper-proof. However, the content is readable and writable by both the sender and receiver over time, so like a Map, TypedArrays are not passable. That makes them a poor foundation for remote streams in an ocap discipline.
I propose that we need an analogous solution to the
CopyMap
andMapStore
for transmission of immutable byte strings, tentativelyCopyBytes
andBytesStore
, which would be passable and provide a firmer foundation for streams (async iterators of byte strings).CopyBytes
should mesh well with TypedArrays, particularly Uint8Array and ArrayBuffer. To that end, it may be necessary for them to be Proxies, such thatuint8Array.set(copyBytes)
is sensible. Constructing an immutable copy of the content of an array-like should be possible, likecopyBytes(uint8Array)
ormakeCopyBytes([0, 1, 2, …, 127])
.CopyBytes
would be backed by a TypedArray but would attenuate mutability. SlicingCopyBytes
should be trivial. As with glyph strings, ropes are a possible optimization in the fullness of time.As a critical optimization, we often use ring buffers for byte streams. We could conceivably also support another type for revocable copy bytes, that would permit a stream to reclaim the underlying byte range when the consumer signals it’s ready for another subrange.
The text was updated successfully, but these errors were encountered: