-
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
feat(ses): ImmutableArrayBuffer #2309
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { globalThis } from './commons.js'; | ||
import { ImmutableArrayBuffer } from './immutable-array-buffer.js'; | ||
|
||
globalThis.ImmutableArrayBuffer = ImmutableArrayBuffer; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* eslint-disable class-methods-use-this */ | ||
import { | ||
defineProperties, | ||
arrayBufferSlice, | ||
toStringTagSymbol, | ||
isView, | ||
speciesSymbol, | ||
} from './commons.js'; | ||
|
||
/** | ||
* `ImmutableArrayBuffer` is intended to be a peer of `ArrayBuffer` and | ||
* `SharedArrayBuffer`, but only with the non-mutating methods they have in | ||
* common. We're adding this to ses as if it was already part of the | ||
* language, and we consider this implementation to be a shim for an | ||
* upcoming tc39 proposal. | ||
* | ||
* As a proposal it would take additional steps that would the shim does not: | ||
* - to have `ImmutableArrayBuffer` be shared between | ||
* threads (in spec speak, "agent") in exactly the same way | ||
* `SharedArrayBuffer` is shared between agents. Unlike `SharedArrayBuffer`, | ||
* sharing an `ImmutableArrayBuffer` does not introduce any observable | ||
* concurrency. Unlike `ArrayBuffer`, sharing an `ImmutableArrayBuffer` | ||
* does not detach anything. | ||
* - when used as a backing store of a `TypedArray` or `DataView`, all the query | ||
* methods would work, but the mutating methods would throw. In this sense, | ||
* the wrapping `TypedArray` or `DataView` would also be immutable. | ||
* | ||
* Technically, this file is a ponyfill because it does not install this class | ||
* on `globalThis` or have any other effects on primordial state. It only | ||
* defines and exports a new class. | ||
* `immutable-array-buffer-shim.js` is the corresponding shim which | ||
* installs `ImmutableArrayBuffer` on `globalThis`. It is imported by | ||
* `lockdown`, so that `ImmutableArrayBuffer` can act as-if defined as | ||
* part of the language. | ||
* | ||
* Note that the class isn't immutable until hardened by lockdown. | ||
* Even then, the instances are not immutable until hardened. | ||
* This class does not harden its instances itself to preserve similarity | ||
* with `ArrayBuffer` and `SharedArrayBuffer`. | ||
*/ | ||
export class ImmutableArrayBuffer { | ||
/** @type {ArrayBuffer} */ | ||
#buffer; | ||
|
||
/** | ||
* @param {unknown} arg | ||
* @returns {arg is DataView} | ||
*/ | ||
static isView(arg) { | ||
// TODO should this just share/alias`isView` instead? | ||
return isView(arg); | ||
} | ||
|
||
// TODO how to type this? | ||
static get [speciesSymbol]() { | ||
return ImmutableArrayBuffer; | ||
} | ||
|
||
/** @param {ArrayBuffer} buffer */ | ||
constructor(buffer) { | ||
// This also enforces that `buffer` is a genuine `ArrayBuffer` | ||
this.#buffer = arrayBufferSlice(buffer, 0); | ||
} | ||
|
||
/** @type {number} */ | ||
get byteLength() { | ||
return this.#buffer.byteLength; | ||
} | ||
|
||
/** @type {boolean} */ | ||
get detached() { | ||
return false; | ||
} | ||
|
||
/** @type {number} */ | ||
get maxByteLength() { | ||
// Not underlying maxByteLength, which is irrelevant | ||
return this.#buffer.byteLength; | ||
} | ||
|
||
/** @type {boolean} */ | ||
get resizable() { | ||
return false; | ||
} | ||
|
||
/** | ||
* @param {number} begin | ||
* @param {number} [end] | ||
* @returns {ArrayBuffer} | ||
* Returns a genuine ArrayBuffer, not a SharedArrayBuffer | ||
*/ | ||
slice(begin, end = undefined) { | ||
return arrayBufferSlice(this.#buffer, begin, end); | ||
} | ||
|
||
/** @type {string} */ | ||
get [toStringTagSymbol]() { | ||
// Is remade into a data property by mutating the class prototype below. | ||
return 'ImmutableArrayBuffer'; | ||
} | ||
} | ||
|
||
defineProperties(ImmutableArrayBuffer.prototype, { | ||
[toStringTagSymbol]: { value: 'ImmutableArrayBuffer' }, | ||
}); |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* global ImmutableArrayBuffer */ | ||
// TODO make the above global declaration unnecessary. | ||
// TODO ensure ImmutableArrayBuffer is typed like ArrayBuffer is typed | ||
// Where are these configured? | ||
|
||
import test from 'ava'; | ||
import '../index.js'; | ||
|
||
const { isFrozen } = Object; | ||
|
||
lockdown(); | ||
|
||
test('ImmutableArrayBuffer installed and hardened', t => { | ||
t.true(isFrozen(ImmutableArrayBuffer)); | ||
t.true(isFrozen(ImmutableArrayBuffer.isView)); | ||
t.true(isFrozen(ImmutableArrayBuffer.prototype)); | ||
t.true(isFrozen(ImmutableArrayBuffer.prototype.slice)); | ||
}); | ||
|
||
test('ImmutableArrayBuffer ops', t => { | ||
const ab1 = new ArrayBuffer(2, { maxByteLength: 7 }); | ||
const ta1 = new Uint8Array(ab1); | ||
ta1[0] = 3; | ||
ta1[1] = 4; | ||
const iab = new ImmutableArrayBuffer(ab1); | ||
t.true(iab instanceof ImmutableArrayBuffer); | ||
t.false(iab instanceof ArrayBuffer); | ||
ta1[1] = 5; | ||
const ab2 = iab.slice(0); | ||
const ta2 = new Uint8Array(ab2); | ||
t.is(ta1[1], 5); | ||
t.is(ta2[1], 4); | ||
ta2[1] = 6; | ||
|
||
const ab3 = iab.slice(0); | ||
t.false(ab3 instanceof ImmutableArrayBuffer); | ||
t.true(ab3 instanceof ArrayBuffer); | ||
|
||
const ta3 = new Uint8Array(ab3); | ||
t.is(ta1[1], 5); | ||
t.is(ta2[1], 6); | ||
t.is(ta3[1], 4); | ||
|
||
t.false(ArrayBuffer.isView({})); | ||
t.false(ImmutableArrayBuffer.isView({})); | ||
const dv1 = new DataView(ab1); | ||
t.true(ArrayBuffer.isView(dv1)); | ||
t.true(ImmutableArrayBuffer.isView(dv1)); | ||
|
||
t.is(ImmutableArrayBuffer[Symbol.species], ImmutableArrayBuffer); | ||
|
||
t.is(ab1.byteLength, 2); | ||
t.is(iab.byteLength, 2); | ||
t.is(ab2.byteLength, 2); | ||
|
||
t.is(iab.maxByteLength, 2); | ||
if ('maxByteLength' in ab1) { | ||
// ArrayBuffer.p.maxByteLength absent from Node 18 | ||
t.is(ab1.maxByteLength, 7); | ||
t.is(ab2.maxByteLength, 2); | ||
} | ||
|
||
t.false(iab.detached); | ||
t.false(iab.resizable); | ||
}); | ||
|
||
// This could have been written as a test.failing as compared to | ||
// the ImmutableArrayBuffer we'll propose. However, I'd rather test what | ||
// the shim purposesly does instead. | ||
test('ImmutableArrayBuffer shim limitations', t => { | ||
const ab1 = new ArrayBuffer(2); | ||
const dv1 = new DataView(ab1); | ||
t.is(dv1.buffer, ab1); | ||
t.is(dv1.byteLength, 2); | ||
const ta1 = new Uint8Array(ab1); | ||
ta1[0] = 3; | ||
ta1[1] = 4; | ||
t.is(ta1.byteLength, 2); | ||
|
||
t.throws(() => new DataView({}), { instanceOf: TypeError }); | ||
// Unfortutanely, calling a TypeArray constructor with an object that | ||
// is not a TypeArray, ArrayBuffer, or Iterable just creates a useless | ||
// empty TypedArray, rather than throwing. | ||
const ta2 = new Uint8Array({}); | ||
t.is(ta2.byteLength, 0); | ||
|
||
const iab = new ImmutableArrayBuffer(ab1); | ||
t.throws(() => new DataView(iab), { | ||
instanceOf: TypeError, | ||
}); | ||
// Unfortunately, unlike the ImmutableArrayBuffer to be proposed, | ||
// calling a TypedArray constructor with the shim implementation of | ||
// ImmutableArrayBuffer as argument treats it as an unrecognized object, | ||
// rather than throwing an error. | ||
t.is(iab.byteLength, 2); | ||
const ta3 = new Uint8Array(iab); | ||
erights marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.is(ta3.byteLength, 0); | ||
}); |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, only "transferred"
ArrayBuffer
become detached on the web. If not transferring, the AB is simply copied, which is for an immutable buffer is observably indistinguishable from sharing. Basically anImmutableArrayBuffer
would simply not be "transferable" on the web, but it could remain "cloneable".There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I did not realize that structured clone has a distinct knob for clone vs transfer. I'll clarify that the advantage is sharing immutable data without copying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still view copying or not as an optimization that engines could do regardless. There is no guarantee that they would not copy a cloned immutable array buffer, and if the engine was implementing copy-on-write, it could avoid copying a cloned array buffer already.