[dart2js/ddc/dart2wasm/dart:js_interop] Support SharedArrayBuffers in JS typed data wrappers

https://github.com/dart-lang/sdk/issues/56455

The existing native typed data implementation in dart2js/ddc
and the JS typed data wrappers in dart2wasm do not support
SharedArrayBuffers.

In dart2js/ddc, this is because the native type for ByteBuffer
is simply ArrayBuffer, leading to type failures when using
SharedArrayBuffers. To handle this, this change makes NativeByteBuffer
an abstract parent class to NativeArrayBuffer and NativeSharedArrayBuffer.
This allows ByteBuffer to support both types. There is a preexisting
SharedArrayBuffer type in dart:html that we should avoid breaking, so
we add an interface that NativeSharedArrayBuffer implements and expose
that interface.

In dart2wasm, JSArrayBufferImpl only allows ArrayBuffers as its
extern ref. This change makes that wrapper support SharedArrayBuffers
as well.

In dart:js_interop, the existing toJS conversion on ByteBuffer
now throws if the underlying buffer was actually a SharedArrayBuffer.
This is to support the return type of JSArrayBuffer. This behavior
technically already existed due to type differences in the JS
compilers, but was never possible with dart2wasm.

CoreLibraryReviewExempt: Backend-specific libraries with no real functional changes to public APIs.
Change-Id: I4dac9fb808590bf0c274da815c152cd4637316b1
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437526
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
Srujan Gaddam
2025-07-07 12:50:25 -07:00
committed by Commit Queue
parent 482a7caed7
commit 67bda7c22b
18 changed files with 1526 additions and 1124 deletions
@@ -1064,7 +1064,9 @@ class NativeClassTag {
factory NativeClassTag(String tagText) {
List<String> tags = tagText.split(',');
List<String> names = tags.where((s) => !s.startsWith('!')).toList();
List<String> names = tags
.where((s) => s.isNotEmpty && !s.startsWith('!'))
.toList();
bool isNonLeaf = tags.contains('!nonleaf');
return NativeClassTag.internal(names, isNonLeaf);
}
@@ -23,10 +23,9 @@ import 'dart:math' as Math;
import 'dart:typed_data';
@Native('ArrayBuffer')
final class NativeByteBuffer extends JavaScriptObject implements ByteBuffer {
@JSName('byteLength')
external int get lengthInBytes;
abstract final class NativeByteBuffer extends JavaScriptObject
implements ByteBuffer {
int get lengthInBytes => JS('', '#.byteLength', this);
Type get runtimeType => ByteBuffer;
@@ -97,6 +96,45 @@ final class NativeByteBuffer extends JavaScriptObject implements ByteBuffer {
}
}
@Native('ArrayBuffer')
final class NativeArrayBuffer extends NativeByteBuffer {}
// Interface class that's exposed through `dart:html` to replace the previous
// `@Native` `SharedArrayBuffer` class that existed there. Marked as `interface`
// so that classes that implemented it before from `dart:html` still work.
abstract interface class SharedArrayBuffer extends JavaScriptObject {
factory SharedArrayBuffer([int? length]) {
if (length != null) {
return NativeSharedArrayBuffer._create1(length);
}
return NativeSharedArrayBuffer._create2();
}
int? get byteLength;
SharedArrayBuffer slice([int? begin, int? end]);
}
@Native('SharedArrayBuffer')
final class NativeSharedArrayBuffer extends NativeByteBuffer
implements SharedArrayBuffer {
static NativeSharedArrayBuffer _create1(int length) => JS(
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
'new SharedArrayBuffer(#)',
length,
);
static NativeSharedArrayBuffer _create2() => JS(
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
'new SharedArrayBuffer()',
);
@override
int? get byteLength native;
@override
SharedArrayBuffer slice([int? begin, int? end]) native;
}
/// A fixed-length list of Float32x4 numbers that is viewable as a
/// [TypedData]. For long lists, this implementation will be considerably more
/// space- and time-efficient than the default [List] implementation.
@@ -30,11 +30,14 @@ import 'dart:math' as Math;
import 'dart:typed_data';
@Native('ArrayBuffer')
final class NativeByteBuffer extends JavaScriptObject
// An empty `@Native` annotation allows this type to be treated as a native type
// but without a corresponding value. dart2js only treats classes as native if
// they contain this annotation or a parent class is native. This works around
// that limitation.
@Native('')
abstract final class NativeByteBuffer extends JavaScriptObject
implements ByteBuffer, TrustedGetRuntimeType {
@JSName('byteLength')
int get lengthInBytes native;
int get lengthInBytes => JS('', '#.byteLength', this);
Type get runtimeType => ByteBuffer;
@@ -108,6 +111,45 @@ final class NativeByteBuffer extends JavaScriptObject
}
}
@Native('ArrayBuffer')
final class NativeArrayBuffer extends NativeByteBuffer {}
// Interface class that's exposed through `dart:html` to replace the previous
// `@Native` `SharedArrayBuffer` class that existed there. Marked as `interface`
// so that classes that implemented it before from `dart:html` still work.
abstract interface class SharedArrayBuffer extends JavaScriptObject {
factory SharedArrayBuffer([int? length]) {
if (length != null) {
return NativeSharedArrayBuffer._create1(length);
}
return NativeSharedArrayBuffer._create2();
}
int? get byteLength;
SharedArrayBuffer slice([int? begin, int? end]);
}
@Native('SharedArrayBuffer')
final class NativeSharedArrayBuffer extends NativeByteBuffer
implements SharedArrayBuffer {
static NativeSharedArrayBuffer _create1(int length) => JS(
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
'new SharedArrayBuffer(#)',
length,
);
static NativeSharedArrayBuffer _create2() => JS(
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
'new SharedArrayBuffer()',
);
@override
int? get byteLength native;
@override
SharedArrayBuffer slice([int? begin, int? end]) native;
}
/// A fixed-length list of Float32x4 numbers that is viewable as a
/// [TypedData]. For long lists, this implementation will be considerably more
/// space- and time-efficient than the default [List] implementation.
@@ -28,7 +28,7 @@ typedef JSArrayRepType = interceptors.JSArray<Object?>;
typedef JSBoxedDartObjectRepType = interceptors.JSObject;
typedef JSArrayBufferRepType = typed_data.NativeByteBuffer;
typedef JSArrayBufferRepType = typed_data.NativeArrayBuffer;
typedef JSDataViewRepType = typed_data.NativeByteData;
+11 -6
View File
@@ -426,7 +426,7 @@ bool isWasmGCStruct(WasmExternRef? ref) => ref.internalize()?.isObject ?? false;
/// The values within this class should correspond to the values returned by
/// [externRefType] and should be updated if that function is updated. Constants
/// are preferred over enums for performance.
class ExternRefType {
abstract final class ExternRefType {
static const int null_ = 0;
static const int undefined = 1;
static const int boolean = 2;
@@ -444,7 +444,8 @@ class ExternRefType {
static const int float64Array = 14;
static const int dataView = 15;
static const int arrayBuffer = 16;
static const int unknown = 17;
static const int sharedArrayBuffer = 17;
static const int unknown = 18;
}
/// Returns an integer representing the type of [ref] that corresponds to one of
@@ -475,7 +476,12 @@ int externRefType(WasmExternRef? ref) {
if (o instanceof DataView) return 15;
}
if (o instanceof ArrayBuffer) return 16;
return 17;
// Feature check for `SharedArrayBuffer` before doing a type-check.
if (globalThis.SharedArrayBuffer !== undefined &&
o instanceof SharedArrayBuffer) {
return 17;
}
return 18;
}
''', ref).toIntUnsigned();
return val;
@@ -513,9 +519,8 @@ Object? dartifyRaw(WasmExternRef? ref, [int? refType]) {
ExternRefType.float64Array => js_types.JSFloat64ArrayImpl.fromRefUnchecked(
ref,
),
ExternRefType.arrayBuffer => js_types.JSArrayBufferImpl.fromRefUnchecked(
ref,
),
ExternRefType.arrayBuffer || ExternRefType.sharedArrayBuffer =>
js_types.JSArrayBufferImpl.fromRefUnchecked(ref),
ExternRefType.dataView => js_types.JSDataViewImpl.fromRefUnchecked(ref),
ExternRefType.unknown =>
isJSWrappedDartFunction(ref)
@@ -241,13 +241,19 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
@patch
JSArrayBuffer get toJS {
final t = this;
return JSArrayBuffer._(
JSValue(
t is js_types.JSArrayBufferImpl
? t.toExternRef
: jsArrayBufferFromDartByteBuffer(t),
),
);
if (t is js_types.JSArrayBufferImpl) {
if (!t.isArrayBuffer) {
assert(t.isSharedArrayBuffer);
throw StateError(
"ByteBuffer is a wrapped 'SharedArrayBuffer'. Convert the typed list "
"that wrapped this buffer to a JS typed array instead to access the "
"`SharedArrayBuffer` from that JS typed array.",
);
}
return JSArrayBuffer._(JSValue(t.toExternRef));
} else {
return JSArrayBuffer._(JSValue(jsArrayBufferFromDartByteBuffer(t)));
}
}
}
+44 -10
View File
@@ -4,24 +4,50 @@
part of dart._js_types;
/// A JS `ArrayBuffer`.
/// Container class for constants that represent the possible types of a
/// [WasmExternRef] that can be passed to [JSArrayBufferImpl].
///
/// Constants are preferred over enums for performance.
abstract final class _ArrayBufferType {
static const int arrayBuffer = 0;
static const int sharedArrayBuffer = 1;
static const int unknown = 2;
}
/// A JS `ArrayBuffer` or `SharedArrayBuffer`.
final class JSArrayBufferImpl implements ByteBuffer {
/// `externref` of a JS `ArrayBuffer`.
/// `externref` of a JS `ArrayBuffer` or `SharedArrayBuffer`.
final WasmExternRef? _ref;
final bool _immutable;
static bool _checkRefType(WasmExternRef? ref) =>
js.JS<bool>('o => o instanceof ArrayBuffer', ref);
late int _refType = _getRefType(_ref);
static int _getRefType(WasmExternRef? _ref) =>
// Feature check for `SharedArrayBuffer` before doing a type-check.
js.JS<WasmI32>('''o => {
if (o instanceof ArrayBuffer) return 0;
if (globalThis.SharedArrayBuffer !== undefined &&
o instanceof SharedArrayBuffer) {
return 1;
}
return 2;
}''', _ref).toIntUnsigned();
bool get isArrayBuffer => _refType == _ArrayBufferType.arrayBuffer;
bool get isSharedArrayBuffer =>
_refType == _ArrayBufferType.sharedArrayBuffer;
JSArrayBufferImpl.fromRefUnchecked(this._ref) : _immutable = false {
assert(_checkRefType(_ref));
assert(isArrayBuffer || isSharedArrayBuffer);
}
JSArrayBufferImpl.fromRefImmutableUnchecked(this._ref) : _immutable = true;
factory JSArrayBufferImpl.fromRef(WasmExternRef? ref) {
if (!_checkRefType(ref)) {
final refType = _getRefType(ref);
if (refType == _ArrayBufferType.unknown) {
return _throwConversionFailureError("ByteBuffer");
}
return JSArrayBufferImpl.fromRefUnchecked(ref);
@@ -30,9 +56,13 @@ final class JSArrayBufferImpl implements ByteBuffer {
@pragma("wasm:prefer-inline")
WasmExternRef? get toExternRef => _ref;
/// Get a JS `DataView` of this `ArrayBuffer`.
/// Get a JS `DataView` of this `ArrayBuffer` or `SharedArrayBuffer`.
WasmExternRef? view(int offsetInBytes, int? length) =>
_newDataViewFromArrayBuffer(toExternRef, offsetInBytes, length);
_newDataViewFromArrayBufferOrSharedArrayBuffer(
toExternRef,
offsetInBytes,
length,
);
WasmExternRef? cloneAsDataView(int offsetInBytes, int? lengthInBytes) {
lengthInBytes ??= this.lengthInBytes;
@@ -279,7 +309,11 @@ final class JSDataViewImpl implements ByteData {
int offsetInBytes,
int? length,
) => JSDataViewImpl.fromRefUnchecked(
_newDataViewFromArrayBuffer(buffer.toExternRef, offsetInBytes, length),
_newDataViewFromArrayBufferOrSharedArrayBuffer(
buffer.toExternRef,
offsetInBytes,
length,
),
);
@pragma("wasm:prefer-inline")
@@ -2601,7 +2635,7 @@ int _dataViewByteLength(WasmExternRef? ref) => js
.toInt();
@pragma("wasm:prefer-inline")
WasmExternRef? _newDataViewFromArrayBuffer(
WasmExternRef? _newDataViewFromArrayBufferOrSharedArrayBuffer(
WasmExternRef? bufferRef,
int offsetInBytes,
int? length,
+1 -26
View File
@@ -46,6 +46,7 @@ import 'dart:web_gl' show RenderingContext, RenderingContext2;
import 'dart:_foreign_helper' show JS, JS_INTERCEPTOR_CONSTANT;
import 'dart:js_util' as js_util;
export 'dart:_native_typed_data' show SharedArrayBuffer;
// Not actually used, but imported since dart:html can generate these objects.
import 'dart:_js_helper'
show
@@ -28879,32 +28880,6 @@ Please remove them from your code.
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@Native("SharedArrayBuffer")
class SharedArrayBuffer extends JavaScriptObject {
// To suppress missing implicit constructor warnings.
factory SharedArrayBuffer._() {
throw new UnsupportedError("Not supported");
}
factory SharedArrayBuffer([int? length]) {
if (length != null) {
return SharedArrayBuffer._create_1(length);
}
return SharedArrayBuffer._create_2();
}
static SharedArrayBuffer _create_1(length) =>
JS('SharedArrayBuffer', 'new SharedArrayBuffer(#)', length);
static SharedArrayBuffer _create_2() =>
JS('SharedArrayBuffer', 'new SharedArrayBuffer()');
int? get byteLength native;
SharedArrayBuffer slice([int? begin, int? end]) native;
}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@Native("SharedWorker")
class SharedWorker extends EventTarget implements AbstractWorker {
// To suppress missing implicit constructor warnings.
+8 -5
View File
@@ -870,16 +870,19 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
/// Converts this [ByteBuffer] to a [JSArrayBuffer] by either casting,
/// unwrapping, or cloning the [ByteBuffer].
///
/// Throws if the [ByteBuffer] wraps a JS `SharedArrayBuffer`.
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to JavaScript, [ByteBuffer]s are either `ArrayBuffer`s or
/// > `SharedArrayBuffer`s so this will just check the type and cast.
/// > When compiling to Wasm, this [ByteBuffer] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [ByteBuffer]'s values into a new
/// > [JSArrayBuffer].
/// > Dart. If it's a wrapper, this method unwraps it and either returns the
/// > `ArrayBuffer` or throws if the unwrapped buffer was a
/// > `SharedArrayBuffer`. If it's instantiated in Dart, this method clones
/// > this [ByteBuffer]'s values into a new [JSArrayBuffer].
/// > Avoid assuming that modifications to this [ByteBuffer] will affect the
/// > [JSArrayBuffer] and vice versa unless it was instantiated in JavaScript.
external JSArrayBuffer get toJS;
@@ -0,0 +1,50 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test the `SharedArrayBuffer` interface exposed through `dart:html` and make
// sure it's well-typed.
import 'dart:html';
import 'dart:js_interop';
import 'dart:typed_data';
import 'package:expect/expect.dart';
@pragma('dart2js:noInline')
@pragma('dart2js:assumeDynamic')
confuse(f) => f;
@JS('SharedArrayBuffer')
external JSAny? get _sharedArrayBufferConstructor;
bool supportsSharedArrayBuffer = _sharedArrayBufferConstructor != null;
void main() {
// TODO(https://github.com/dart-lang/sdk/issues/61043): Support this in the
// test runner.
if (!supportsSharedArrayBuffer) return;
final buf = SharedArrayBuffer(3);
Expect.equals(3, buf.byteLength);
final bufNoArgs = SharedArrayBuffer();
Expect.equals(0, bufNoArgs.byteLength);
final slice1 = buf.slice();
Expect.equals(3, slice1.byteLength);
final slice2 = buf.slice(1);
Expect.equals(2, slice2.byteLength);
final slice3 = buf.slice(1, 2);
Expect.equals(1, slice3.byteLength);
Expect.isTrue(buf is SharedArrayBuffer);
buf as SharedArrayBuffer;
Expect.isTrue(confuse(buf) is SharedArrayBuffer);
confuse(buf) as SharedArrayBuffer;
// This should be true in order to allow typed lists to contain
// `SharedArrayBuffer`s.
Expect.isTrue(buf is ByteBuffer);
buf as ByteBuffer;
Expect.isTrue(confuse(buf) is ByteBuffer);
confuse(buf) as ByteBuffer;
}
@@ -51,14 +51,122 @@ void uint8ArrayBasicTest(TestMode mode) {
Expect.listEquals(control, rl.buffer.asUint8List());
}
@JS('SharedArrayBuffer')
external JSAny? get _sharedArrayBufferConstructor;
bool supportsSharedArrayBuffer = _sharedArrayBufferConstructor != null;
@JS('SharedArrayBuffer')
extension type JSSharedArrayBuffer._(JSObject _) implements JSObject {
external JSSharedArrayBuffer(int length);
}
@JS('Uint8Array')
extension type JSUint8ArrayShared._(JSUint8Array _) implements JSUint8Array {
external JSUint8ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Uint8ClampedArray')
extension type JSUint8ClampedArrayShared._(JSUint8ClampedArray _)
implements JSUint8ClampedArray {
external JSUint8ClampedArrayShared(JSSharedArrayBuffer buf);
}
@JS('Int8Array')
extension type JSInt8ArrayShared._(JSInt8Array _) implements JSInt8Array {
external JSInt8ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Uint16Array')
extension type JSUint16ArrayShared._(JSUint16Array _) implements JSUint16Array {
external JSUint16ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Int16Array')
extension type JSInt16ArrayShared._(JSInt16Array _) implements JSInt16Array {
external JSInt16ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Uint32Array')
extension type JSUint32ArrayShared._(JSUint32Array _) implements JSUint32Array {
external JSUint32ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Int32Array')
extension type JSInt32ArrayShared._(JSInt32Array _) implements JSInt32Array {
external JSInt32ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Float32Array')
extension type JSFloat32ArrayShared._(JSFloat32Array _)
implements JSFloat32Array {
external JSFloat32ArrayShared(JSSharedArrayBuffer buf);
}
@JS('Float64Array')
extension type JSFloat64ArrayShared._(JSFloat64Array _)
implements JSFloat64Array {
external JSFloat64ArrayShared(JSSharedArrayBuffer buf);
}
JSUint8Array getJSUint8Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSUint8ArrayShared(JSSharedArrayBuffer(length))
: Uint8List(length).toJS;
JSUint8ClampedArray getJSUint8ClampedArray(
bool useSharedArrayBuffer, {
int length = 4,
}) => useSharedArrayBuffer
? JSUint8ClampedArrayShared(JSSharedArrayBuffer(length))
: Uint8ClampedList(length).toJS;
JSInt8Array getJSInt8Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSInt8ArrayShared(JSSharedArrayBuffer(length))
: Int8List(length).toJS;
JSUint16Array getJSUint16Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSUint16ArrayShared(JSSharedArrayBuffer(length * 2))
: Uint16List(length).toJS;
JSInt16Array getJSInt16Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSInt16ArrayShared(JSSharedArrayBuffer(length * 2))
: Int16List(length).toJS;
JSUint32Array getJSUint32Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSUint32ArrayShared(JSSharedArrayBuffer(length * 4))
: Uint32List(length).toJS;
JSInt32Array getJSInt32Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSInt32ArrayShared(JSSharedArrayBuffer(length * 4))
: Int32List(length).toJS;
JSFloat32Array getJSFloat32Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSFloat32ArrayShared(JSSharedArrayBuffer(length * 4))
: Float32List(length).toJS;
JSFloat64Array getJSFloat64Array(bool useSharedArrayBuffer, {int length = 4}) =>
useSharedArrayBuffer
? JSFloat64ArrayShared(JSSharedArrayBuffer(length * 8))
: Float64List(length).toJS;
void initialize(List<int> l) {
for (var i = 0; i < l.length; i++) {
l[i] = i + 1;
}
}
void uint8ArraySetRangeTest() {
final backingStore = Uint8List(9).toJS.toDart;
void uint8ArraySetRangeTest(bool useSharedArrayBuffer) {
Uint8List backingStore = getJSUint8Array(
useSharedArrayBuffer,
length: 9,
).toDart;
final buffer = backingStore.buffer;
Expect.equals(buffer.lengthInBytes, backingStore.lengthInBytes);
final a1 = Uint8List.view(buffer, 0, 7);
@@ -94,8 +202,11 @@ void uint8ArraySetRangeTest() {
Expect.equals('[1, 2, 2, 3, 4, 5, 6, 7, 9]', '$backingStore');
}
void arrayBufferTest() {
final backingStore = Uint8List(12).toJS.toDart;
void arrayBufferTest(bool useSharedArrayBuffer) {
Uint8List backingStore = getJSUint8Array(
useSharedArrayBuffer,
length: 12,
).toDart;
final buffer = backingStore.buffer;
final byteDataView1 = ByteData.view(buffer);
final byteDataView2 = ByteData.view(buffer, 1, 8);
@@ -220,35 +331,45 @@ void arrayBufferTest() {
Expect.equals(19, backingStore[0]);
}
void expandContractTest() {
final b = Int32List(8).toJS.toDart;
final v = Int8List.view(b.buffer, 12, 8);
void expandContractTest(bool useSharedArrayBuffer) {
Int32List backingStore = getJSInt32Array(
useSharedArrayBuffer,
length: 8,
).toDart;
final v = Int8List.view(backingStore.buffer, 12, 8);
initialize(v);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$v');
b.setRange(0, 8, v);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$b');
backingStore.setRange(0, 8, v);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$backingStore');
initialize(b);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$b');
v.setRange(0, 8, b);
initialize(backingStore);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$backingStore');
v.setRange(0, 8, backingStore);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$v');
}
void clampingTest() {
final a1 = Int8List(8).toJS.toDart;
final a2 = Uint8ClampedList.view(a1.buffer);
void clampingTest(bool useSharedArrayBuffer) {
Int8List backingStore = getJSInt8Array(
useSharedArrayBuffer,
length: 8,
).toDart;
final a = Uint8ClampedList.view(backingStore.buffer);
initialize(a1);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$a1');
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$a2');
a1[0] = -1;
a2.setRange(0, 2, a1);
Expect.equals('[0, 2, 3, 4, 5, 6, 7, 8]', '$a2');
initialize(backingStore);
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$backingStore');
Expect.equals('[1, 2, 3, 4, 5, 6, 7, 8]', '$a');
backingStore[0] = -1;
a.setRange(0, 2, backingStore);
Expect.equals('[0, 2, 3, 4, 5, 6, 7, 8]', '$a');
}
void overlapTest() {
final buffer = Float32List(3).toJS.toDart.buffer;
void overlapTest(bool useSharedArrayBuffer) {
Float32List backingStore = getJSFloat32Array(
useSharedArrayBuffer,
length: 3,
).toDart;
final buffer = backingStore.buffer;
final a0 = Int8List.view(buffer);
final a1 = Int8List.view(buffer, 1, 5);
final a2 = Int8List.view(buffer, 2, 5);
@@ -267,18 +388,21 @@ void overlapTest() {
Expect.equals('[1, 2, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12]', '$a0');
}
void testSimd() {
final a = Uint32List(8).toJS.toDart;
final si = Int32x4List.view(a.buffer);
final sf32 = Float32x4List.view(a.buffer);
final sf64 = Float64x2List.view(a.buffer);
void testSimd(bool useSharedArrayBuffer) {
Uint32List backingStore = getJSUint32Array(
useSharedArrayBuffer,
length: 8,
).toDart;
final si = Int32x4List.view(backingStore.buffer);
final sf32 = Float32x4List.view(backingStore.buffer);
final sf64 = Float64x2List.view(backingStore.buffer);
si[0] = Int32x4(1, 2, 3, 4);
Expect.equals(1, si[0].x);
Expect.equals(2, si[0].y);
Expect.equals(3, si[0].z);
Expect.equals(4, si[0].w);
Expect.listEquals([1, 2, 3, 4, 0, 0, 0, 0], a);
Expect.listEquals([1, 2, 3, 4, 0, 0, 0, 0], backingStore);
var sia = si.sublist(0, 1);
Expect.equals(1, sia.length);
@@ -289,7 +413,7 @@ void testSimd() {
si[1] = Int32x4(5, 6, 7, 8);
sia = si.sublist(1, 2);
Expect.listEquals([1, 2, 3, 4, 5, 6, 7, 8], a);
Expect.listEquals([1, 2, 3, 4, 5, 6, 7, 8], backingStore);
Expect.equals(5, sia[0].x);
Expect.equals(6, sia[0].y);
Expect.equals(7, sia[0].z);
@@ -309,7 +433,7 @@ void testSimd() {
6,
7,
8,
], a);
], backingStore);
var sf32a = sf32.sublist(0, 1);
Expect.equals(1, sf32a.length);
@@ -329,7 +453,7 @@ void testSimd() {
1086324736,
1088421888,
1090519040,
], a);
], backingStore);
Expect.equals(5, sf32a[0].x);
Expect.equals(6, sf32a[0].y);
Expect.equals(7, sf32a[0].z);
@@ -347,7 +471,7 @@ void testSimd() {
1086324736,
1088421888,
1090519040,
], a);
], backingStore);
var sf64a = sf64.sublist(0, 1);
Expect.equals(1, sf64a.length);
@@ -365,12 +489,12 @@ void testSimd() {
1074266112,
0,
1074790400,
], a);
], backingStore);
Expect.equals(3, sf64a[0].x);
Expect.equals(4, sf64a[0].y);
}
void bigTest() {
void bigTest(bool useSharedArrayBuffer) {
if (isJSBackend) {
// Not yet supported on JS backends.
return;
@@ -378,7 +502,11 @@ void bigTest() {
// Uint64List
{
final buffer = Uint32List(2).toJS.toDart.buffer;
Uint32List backingStore = getJSUint32Array(
useSharedArrayBuffer,
length: 2,
).toDart;
final buffer = backingStore.buffer;
final bigList = buffer.asUint64List();
final littleList = buffer.asUint8List();
bigList[0] = 4294967296; // Max 32 bit unsigned + 1
@@ -393,7 +521,11 @@ void bigTest() {
// Int64List
{
final buffer = Int32List(2).toJS.toDart.buffer;
Int32List backingStore = getJSInt32Array(
useSharedArrayBuffer,
length: 2,
).toDart;
final buffer = backingStore.buffer;
final bigList = buffer.asInt64List();
final littleList = buffer.asInt8List();
bigList[0] = -2147483648; // Min 32 bit signed - 1
@@ -407,7 +539,7 @@ void bigTest() {
}
}
void sublistTest() {
void sublistTest(bool useSharedArrayBuffer) {
// Sublists should be copies.
void listIntTest(List<int> l) {
l[0] = 1;
@@ -431,23 +563,34 @@ void sublistTest() {
Expect.equals(0, lSublist[0]);
}
listIntTest(Uint8List(4).toJS.toDart);
listIntTest(Uint8ClampedList(4).toJS.toDart);
listIntTest(Int8List(4).toJS.toDart);
listIntTest(Uint16List(4).toJS.toDart);
listIntTest(Int16List(4).toJS.toDart);
listIntTest(Uint32List(4).toJS.toDart);
listIntTest(Int32List(4).toJS.toDart);
listDoubleTest(Float32List(4).toJS.toDart);
listDoubleTest(Float64List(4).toJS.toDart);
listIntTest(getJSUint8Array(useSharedArrayBuffer).toDart);
listIntTest(getJSUint8ClampedArray(useSharedArrayBuffer).toDart);
listIntTest(getJSInt8Array(useSharedArrayBuffer).toDart);
listIntTest(getJSUint16Array(useSharedArrayBuffer).toDart);
listIntTest(getJSInt16Array(useSharedArrayBuffer).toDart);
listIntTest(getJSUint32Array(useSharedArrayBuffer).toDart);
listIntTest(getJSInt32Array(useSharedArrayBuffer).toDart);
listDoubleTest(getJSFloat32Array(useSharedArrayBuffer).toDart);
listDoubleTest(getJSFloat64Array(useSharedArrayBuffer).toDart);
// Big typed arrays.
if (isJSBackend) {
// Not yet supported on JS backends.
return;
}
listIntTest(Uint8List(16).toJS.toDart.buffer.asUint64List());
listIntTest(Uint8List(16).toJS.toDart.buffer.asInt64List());
listIntTest(
getJSUint8Array(
useSharedArrayBuffer,
length: 16,
).toDart.buffer.asUint64List(),
);
listIntTest(
getJSUint8Array(
useSharedArrayBuffer,
length: 16,
).toDart.buffer.asInt64List(),
);
}
@JS()
@@ -456,16 +599,43 @@ external JSNumber elementSizeInBytes(JSAny a);
@JS()
external void eval(String code);
void elementSizeTest() {
Expect.equals(elementSizeInBytes(Uint8List(4).toJS).toDartInt, 1);
Expect.equals(elementSizeInBytes(Uint8ClampedList(4).toJS).toDartInt, 1);
Expect.equals(elementSizeInBytes(Int8List(4).toJS).toDartInt, 1);
Expect.equals(elementSizeInBytes(Uint16List(4).toJS).toDartInt, 2);
Expect.equals(elementSizeInBytes(Int16List(4).toJS).toDartInt, 2);
Expect.equals(elementSizeInBytes(Uint32List(4).toJS).toDartInt, 4);
Expect.equals(elementSizeInBytes(Int32List(4).toJS).toDartInt, 4);
Expect.equals(elementSizeInBytes(Float32List(4).toJS).toDartInt, 4);
Expect.equals(elementSizeInBytes(Float64List(4).toJS).toDartInt, 8);
void elementSizeTest(bool useSharedArrayBuffer) {
Expect.equals(
elementSizeInBytes(getJSUint8Array(useSharedArrayBuffer)).toDartInt,
1,
);
Expect.equals(
elementSizeInBytes(getJSUint8ClampedArray(useSharedArrayBuffer)).toDartInt,
1,
);
Expect.equals(
elementSizeInBytes(getJSInt8Array(useSharedArrayBuffer)).toDartInt,
1,
);
Expect.equals(
elementSizeInBytes(getJSUint16Array(useSharedArrayBuffer)).toDartInt,
2,
);
Expect.equals(
elementSizeInBytes(getJSInt16Array(useSharedArrayBuffer)).toDartInt,
2,
);
Expect.equals(
elementSizeInBytes(getJSUint32Array(useSharedArrayBuffer)).toDartInt,
4,
);
Expect.equals(
elementSizeInBytes(getJSInt32Array(useSharedArrayBuffer)).toDartInt,
4,
);
Expect.equals(
elementSizeInBytes(getJSFloat32Array(useSharedArrayBuffer)).toDartInt,
4,
);
Expect.equals(
elementSizeInBytes(getJSFloat64Array(useSharedArrayBuffer)).toDartInt,
8,
);
}
void main() {
@@ -482,13 +652,20 @@ void main() {
]) {
uint8ArrayBasicTest(mode);
}
uint8ArraySetRangeTest();
arrayBufferTest();
expandContractTest();
clampingTest();
overlapTest();
testSimd();
bigTest();
sublistTest();
elementSizeTest();
for (final useSharedArrayBuffer in [
false,
// TODO(https://github.com/dart-lang/sdk/issues/61043): Support this in the
// test runner.
if (supportsSharedArrayBuffer) true,
]) {
uint8ArraySetRangeTest(useSharedArrayBuffer);
arrayBufferTest(useSharedArrayBuffer);
expandContractTest(useSharedArrayBuffer);
clampingTest(useSharedArrayBuffer);
overlapTest(useSharedArrayBuffer);
testSimd(useSharedArrayBuffer);
bigTest(useSharedArrayBuffer);
sublistTest(useSharedArrayBuffer);
elementSizeTest(useSharedArrayBuffer);
}
}
@@ -55,6 +55,16 @@ extension on JSArrayBuffer {
external int get byteLength;
}
@JS('SharedArrayBuffer')
external JSAny? get _sharedArrayBufferConstructor;
bool supportsSharedArrayBuffer = _sharedArrayBufferConstructor != null;
@JS('SharedArrayBuffer')
extension type JSSharedArrayBuffer._(JSObject _) implements JSObject {
external JSSharedArrayBuffer(int length);
}
@JS()
external JSDataView dat;
@@ -80,6 +90,11 @@ external JSInt8Array ai8;
@JS()
external JSUint8Array au8;
@JS('Uint8Array')
extension type JSUint8ArrayShared._(JSUint8Array _) implements JSUint8Array {
external JSUint8ArrayShared(JSSharedArrayBuffer buf);
}
@JS()
external JSUint8ClampedArray ac8;
@@ -285,6 +300,16 @@ void syncTests() {
buf = JSArrayBuffer(5);
Expect.equals(5, buf.byteLength);
buf = JSArrayBuffer(5, {'maxByteLength': 12}.jsify() as JSObject);
// TODO(https://github.com/dart-lang/sdk/issues/61043): Support this in the
// test runner.
if (supportsSharedArrayBuffer) {
final sharedArrayBuffer = JSSharedArrayBuffer(4);
final sharedByteBuffer = JSUint8ArrayShared(
sharedArrayBuffer,
).toDart.buffer;
// Not a `JSArrayBuffer`.
Expect.throws(() => sharedByteBuffer.toJS);
}
// [DataView] <-> [ByteData]
final datBuf = Uint8List.fromList([0, 255, 0, 255]).buffer.toJS;
@@ -31,6 +31,27 @@ void _expectRecEquals(Object? l, Object? r) {
}
}
@JS('SharedArrayBuffer')
external JSAny? get _sharedArrayBufferConstructor;
bool supportsSharedArrayBuffer = _sharedArrayBufferConstructor != null;
@JS('SharedArrayBuffer')
extension type JSSharedArrayBuffer._(JSObject _) implements JSObject {
external JSSharedArrayBuffer(int length);
}
@JS('Uint8Array')
extension type JSUint8ArrayShared._(JSUint8Array _) implements JSUint8Array {
external JSUint8ArrayShared(JSSharedArrayBuffer buf);
}
extension on JSUint8Array {
external int operator [](int index);
external operator []=(int index, int value);
external JSObject get buffer;
}
@JS()
external void eval(String code);
@@ -103,10 +124,20 @@ void main() {
_expectIterableEquals(l, l.jsify().dartify() as Float64List);
ByteBuffer buffer = Uint8List.fromList([0, 1, 2, 3]).buffer;
Expect.isTrue(buffer.jsify().isA<JSArrayBuffer>());
_expectIterableEquals(
buffer.asUint8List(),
(buffer.jsify().dartify() as ByteBuffer).asUint8List(),
);
final uint8List = (buffer.jsify().dartify() as ByteBuffer).asUint8List();
_expectIterableEquals(buffer.asUint8List(), uint8List);
Expect.isTrue(uint8List.toJS.buffer.isA<JSArrayBuffer>());
// TODO(https://github.com/dart-lang/sdk/issues/61043): Support this in the
// test runner.
if (supportsSharedArrayBuffer) {
// Test that `SharedArrayBuffer`s are dartified to `TypedData` correctly.
final sharedArrayBuffer = JSSharedArrayBuffer(1);
final uint8ArrayShared = JSUint8ArrayShared(sharedArrayBuffer);
uint8ArrayShared[0] = 42;
final uint8ListShared = uint8ArrayShared.dartify() as Uint8List;
Expect.equals(uint8ArrayShared[0], uint8ListShared[0]);
Expect.isTrue(uint8ListShared.toJS.buffer.isA<JSSharedArrayBuffer>());
}
ByteData byteData = ByteData.view(buffer);
Expect.isTrue(byteData.jsify().isA<JSDataView>());
_expectIterableEquals(
+2 -2
View File
@@ -186,8 +186,8 @@ def main(parallel=False, logging_level=logging.WARNING, examine_idls=False):
'idl_parser', # idl_parser has test IDL files.
]
# TODO(terry): Integrate this into the htmlrenamer's _removed_html_interfaces
# (if possible).
# TODO(terry): Integrate this into the htmlrenamer's
# _suppressed_html_interfaces (if possible).
FILES_TO_IGNORE = [
'InspectorFrontendHostFileSystem.idl', # Uses interfaces in inspector dir (which is ignored)
'WebKitGamepad.idl', # Gamepad.idl is the new one.
+21 -4
View File
@@ -129,7 +129,7 @@ def generateCallbackInterface(id):
# Interfaces that are suppressed, but need to still exist for Dartium and to
# properly wrap DOM objects if/when encountered.
_removed_html_interfaces = [
_suppressed_html_interfaces = [
'Bluetooth',
'BluetoothAdvertisingData',
'BluetoothCharacteristicProperties',
@@ -231,7 +231,17 @@ _removed_html_interfaces = [
'ResourceProgressEvent',
]
for interface in _removed_html_interfaces:
# Interfaces that should not be exposed at all. _suppressed_html_interfaces
# still emits the type, but doesn't make it public.
_removed_html_interfaces = [
'SharedArrayBuffer', # Exposed through `dart:_native_typed_data` instead.
]
_suppressed_html_interfaces.extend(_removed_html_interfaces)
for interface in _suppressed_html_interfaces:
if interface in _removed_html_interfaces:
continue
html_interface_renames[interface] = '_' + interface
convert_to_future_members = monitored.Set(
@@ -1094,7 +1104,7 @@ class HtmlRenamer(object):
def RenameInterface(self, interface):
if 'Callback' in interface.ext_attrs:
if interface.id in _removed_html_interfaces:
if interface.id in _suppressed_html_interfaces:
return None
candidate = self.RenameInterfaceId(interface.id)
@@ -1159,7 +1169,7 @@ class HtmlRenamer(object):
if self._FindMatch(interface, member, member_prefix,
removed_html_members):
return True
if interface.id in _removed_html_interfaces:
if interface.id in _suppressed_html_interfaces:
return True
metadata_member = member
if member_prefix == 'on:':
@@ -1170,6 +1180,13 @@ class HtmlRenamer(object):
def ShouldSuppressInterface(self, interface):
""" Returns true if the interface should be suppressed."""
if interface.id in _suppressed_html_interfaces:
return True
def ShouldNotGenerateInterface(self, interface):
# Note that suppression renames the type to a private type but still
# generates it.
""" Returns true if the interface should not be generated."""
if interface.id in _removed_html_interfaces:
return True
+2
View File
@@ -658,6 +658,8 @@ class HtmlDartInterfaceGenerator(object):
def GenerateInterface(self):
interface_name = self._interface_type_info.interface_name()
if (self._renamer.ShouldNotGenerateInterface(self._interface)):
return
implementation_name = self._interface_type_info.implementation_name()
self._library_emitter.AddTypeEntry(
@@ -35,6 +35,7 @@ import 'dart:indexed_db';
import "dart:convert";
import 'dart:math';
import 'dart:_native_typed_data';
export 'dart:_native_typed_data' show SharedArrayBuffer;
import 'dart:typed_data';
// Not actually used, but imported since dart:html can generate these objects.
import 'dart:svg' as svg;
File diff suppressed because it is too large Load Diff