Skip to content

Commit 67bda7c

Browse files
srujzsCommit Queue
authored andcommitted
[dart2js/ddc/dart2wasm/dart:js_interop] Support SharedArrayBuffers in JS typed data wrappers
#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 <[email protected]> Commit-Queue: Srujan Gaddam <[email protected]>
1 parent 482a7ca commit 67bda7c

File tree

18 files changed

+1526
-1124
lines changed

18 files changed

+1526
-1124
lines changed

pkg/compiler/lib/src/js_backend/native_data.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,9 @@ class NativeClassTag {
10641064

10651065
factory NativeClassTag(String tagText) {
10661066
List<String> tags = tagText.split(',');
1067-
List<String> names = tags.where((s) => !s.startsWith('!')).toList();
1067+
List<String> names = tags
1068+
.where((s) => s.isNotEmpty && !s.startsWith('!'))
1069+
.toList();
10681070
bool isNonLeaf = tags.contains('!nonleaf');
10691071
return NativeClassTag.internal(names, isNonLeaf);
10701072
}

sdk/lib/_internal/js_dev_runtime/private/native_typed_data.dart

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ import 'dart:math' as Math;
2323

2424
import 'dart:typed_data';
2525

26-
@Native('ArrayBuffer')
27-
final class NativeByteBuffer extends JavaScriptObject implements ByteBuffer {
28-
@JSName('byteLength')
29-
external int get lengthInBytes;
26+
abstract final class NativeByteBuffer extends JavaScriptObject
27+
implements ByteBuffer {
28+
int get lengthInBytes => JS('', '#.byteLength', this);
3029

3130
Type get runtimeType => ByteBuffer;
3231

@@ -97,6 +96,45 @@ final class NativeByteBuffer extends JavaScriptObject implements ByteBuffer {
9796
}
9897
}
9998

99+
@Native('ArrayBuffer')
100+
final class NativeArrayBuffer extends NativeByteBuffer {}
101+
102+
// Interface class that's exposed through `dart:html` to replace the previous
103+
// `@Native` `SharedArrayBuffer` class that existed there. Marked as `interface`
104+
// so that classes that implemented it before from `dart:html` still work.
105+
abstract interface class SharedArrayBuffer extends JavaScriptObject {
106+
factory SharedArrayBuffer([int? length]) {
107+
if (length != null) {
108+
return NativeSharedArrayBuffer._create1(length);
109+
}
110+
return NativeSharedArrayBuffer._create2();
111+
}
112+
113+
int? get byteLength;
114+
115+
SharedArrayBuffer slice([int? begin, int? end]);
116+
}
117+
118+
@Native('SharedArrayBuffer')
119+
final class NativeSharedArrayBuffer extends NativeByteBuffer
120+
implements SharedArrayBuffer {
121+
static NativeSharedArrayBuffer _create1(int length) => JS(
122+
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
123+
'new SharedArrayBuffer(#)',
124+
length,
125+
);
126+
static NativeSharedArrayBuffer _create2() => JS(
127+
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
128+
'new SharedArrayBuffer()',
129+
);
130+
131+
@override
132+
int? get byteLength native;
133+
134+
@override
135+
SharedArrayBuffer slice([int? begin, int? end]) native;
136+
}
137+
100138
/// A fixed-length list of Float32x4 numbers that is viewable as a
101139
/// [TypedData]. For long lists, this implementation will be considerably more
102140
/// space- and time-efficient than the default [List] implementation.

sdk/lib/_internal/js_runtime/lib/native_typed_data.dart

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ import 'dart:math' as Math;
3030

3131
import 'dart:typed_data';
3232

33-
@Native('ArrayBuffer')
34-
final class NativeByteBuffer extends JavaScriptObject
33+
// An empty `@Native` annotation allows this type to be treated as a native type
34+
// but without a corresponding value. dart2js only treats classes as native if
35+
// they contain this annotation or a parent class is native. This works around
36+
// that limitation.
37+
@Native('')
38+
abstract final class NativeByteBuffer extends JavaScriptObject
3539
implements ByteBuffer, TrustedGetRuntimeType {
36-
@JSName('byteLength')
37-
int get lengthInBytes native;
40+
int get lengthInBytes => JS('', '#.byteLength', this);
3841

3942
Type get runtimeType => ByteBuffer;
4043

@@ -108,6 +111,45 @@ final class NativeByteBuffer extends JavaScriptObject
108111
}
109112
}
110113

114+
@Native('ArrayBuffer')
115+
final class NativeArrayBuffer extends NativeByteBuffer {}
116+
117+
// Interface class that's exposed through `dart:html` to replace the previous
118+
// `@Native` `SharedArrayBuffer` class that existed there. Marked as `interface`
119+
// so that classes that implemented it before from `dart:html` still work.
120+
abstract interface class SharedArrayBuffer extends JavaScriptObject {
121+
factory SharedArrayBuffer([int? length]) {
122+
if (length != null) {
123+
return NativeSharedArrayBuffer._create1(length);
124+
}
125+
return NativeSharedArrayBuffer._create2();
126+
}
127+
128+
int? get byteLength;
129+
130+
SharedArrayBuffer slice([int? begin, int? end]);
131+
}
132+
133+
@Native('SharedArrayBuffer')
134+
final class NativeSharedArrayBuffer extends NativeByteBuffer
135+
implements SharedArrayBuffer {
136+
static NativeSharedArrayBuffer _create1(int length) => JS(
137+
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
138+
'new SharedArrayBuffer(#)',
139+
length,
140+
);
141+
static NativeSharedArrayBuffer _create2() => JS(
142+
'returns:NativeSharedArrayBuffer;effects:none;depends:none;new:true',
143+
'new SharedArrayBuffer()',
144+
);
145+
146+
@override
147+
int? get byteLength native;
148+
149+
@override
150+
SharedArrayBuffer slice([int? begin, int? end]) native;
151+
}
152+
111153
/// A fixed-length list of Float32x4 numbers that is viewable as a
112154
/// [TypedData]. For long lists, this implementation will be considerably more
113155
/// space- and time-efficient than the default [List] implementation.

sdk/lib/_internal/js_shared/lib/js_types.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ typedef JSArrayRepType = interceptors.JSArray<Object?>;
2828

2929
typedef JSBoxedDartObjectRepType = interceptors.JSObject;
3030

31-
typedef JSArrayBufferRepType = typed_data.NativeByteBuffer;
31+
typedef JSArrayBufferRepType = typed_data.NativeArrayBuffer;
3232

3333
typedef JSDataViewRepType = typed_data.NativeByteData;
3434

sdk/lib/_internal/wasm/lib/js_helper.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ bool isWasmGCStruct(WasmExternRef? ref) => ref.internalize()?.isObject ?? false;
426426
/// The values within this class should correspond to the values returned by
427427
/// [externRefType] and should be updated if that function is updated. Constants
428428
/// are preferred over enums for performance.
429-
class ExternRefType {
429+
abstract final class ExternRefType {
430430
static const int null_ = 0;
431431
static const int undefined = 1;
432432
static const int boolean = 2;
@@ -444,7 +444,8 @@ class ExternRefType {
444444
static const int float64Array = 14;
445445
static const int dataView = 15;
446446
static const int arrayBuffer = 16;
447-
static const int unknown = 17;
447+
static const int sharedArrayBuffer = 17;
448+
static const int unknown = 18;
448449
}
449450

450451
/// Returns an integer representing the type of [ref] that corresponds to one of
@@ -475,7 +476,12 @@ int externRefType(WasmExternRef? ref) {
475476
if (o instanceof DataView) return 15;
476477
}
477478
if (o instanceof ArrayBuffer) return 16;
478-
return 17;
479+
// Feature check for `SharedArrayBuffer` before doing a type-check.
480+
if (globalThis.SharedArrayBuffer !== undefined &&
481+
o instanceof SharedArrayBuffer) {
482+
return 17;
483+
}
484+
return 18;
479485
}
480486
''', ref).toIntUnsigned();
481487
return val;
@@ -513,9 +519,8 @@ Object? dartifyRaw(WasmExternRef? ref, [int? refType]) {
513519
ExternRefType.float64Array => js_types.JSFloat64ArrayImpl.fromRefUnchecked(
514520
ref,
515521
),
516-
ExternRefType.arrayBuffer => js_types.JSArrayBufferImpl.fromRefUnchecked(
517-
ref,
518-
),
522+
ExternRefType.arrayBuffer || ExternRefType.sharedArrayBuffer =>
523+
js_types.JSArrayBufferImpl.fromRefUnchecked(ref),
519524
ExternRefType.dataView => js_types.JSDataViewImpl.fromRefUnchecked(ref),
520525
ExternRefType.unknown =>
521526
isJSWrappedDartFunction(ref)

sdk/lib/_internal/wasm/lib/js_interop_patch.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,19 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
241241
@patch
242242
JSArrayBuffer get toJS {
243243
final t = this;
244-
return JSArrayBuffer._(
245-
JSValue(
246-
t is js_types.JSArrayBufferImpl
247-
? t.toExternRef
248-
: jsArrayBufferFromDartByteBuffer(t),
249-
),
250-
);
244+
if (t is js_types.JSArrayBufferImpl) {
245+
if (!t.isArrayBuffer) {
246+
assert(t.isSharedArrayBuffer);
247+
throw StateError(
248+
"ByteBuffer is a wrapped 'SharedArrayBuffer'. Convert the typed list "
249+
"that wrapped this buffer to a JS typed array instead to access the "
250+
"`SharedArrayBuffer` from that JS typed array.",
251+
);
252+
}
253+
return JSArrayBuffer._(JSValue(t.toExternRef));
254+
} else {
255+
return JSArrayBuffer._(JSValue(jsArrayBufferFromDartByteBuffer(t)));
256+
}
251257
}
252258
}
253259

sdk/lib/_internal/wasm/lib/js_typed_array.dart

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,50 @@
44

55
part of dart._js_types;
66

7-
/// A JS `ArrayBuffer`.
7+
/// Container class for constants that represent the possible types of a
8+
/// [WasmExternRef] that can be passed to [JSArrayBufferImpl].
9+
///
10+
/// Constants are preferred over enums for performance.
11+
abstract final class _ArrayBufferType {
12+
static const int arrayBuffer = 0;
13+
static const int sharedArrayBuffer = 1;
14+
static const int unknown = 2;
15+
}
16+
17+
/// A JS `ArrayBuffer` or `SharedArrayBuffer`.
818
final class JSArrayBufferImpl implements ByteBuffer {
9-
/// `externref` of a JS `ArrayBuffer`.
19+
/// `externref` of a JS `ArrayBuffer` or `SharedArrayBuffer`.
1020
final WasmExternRef? _ref;
1121

1222
final bool _immutable;
1323

14-
static bool _checkRefType(WasmExternRef? ref) =>
15-
js.JS<bool>('o => o instanceof ArrayBuffer', ref);
24+
late int _refType = _getRefType(_ref);
25+
26+
static int _getRefType(WasmExternRef? _ref) =>
27+
// Feature check for `SharedArrayBuffer` before doing a type-check.
28+
js.JS<WasmI32>('''o => {
29+
if (o instanceof ArrayBuffer) return 0;
30+
if (globalThis.SharedArrayBuffer !== undefined &&
31+
o instanceof SharedArrayBuffer) {
32+
return 1;
33+
}
34+
return 2;
35+
}''', _ref).toIntUnsigned();
36+
37+
bool get isArrayBuffer => _refType == _ArrayBufferType.arrayBuffer;
38+
39+
bool get isSharedArrayBuffer =>
40+
_refType == _ArrayBufferType.sharedArrayBuffer;
1641

1742
JSArrayBufferImpl.fromRefUnchecked(this._ref) : _immutable = false {
18-
assert(_checkRefType(_ref));
43+
assert(isArrayBuffer || isSharedArrayBuffer);
1944
}
2045

2146
JSArrayBufferImpl.fromRefImmutableUnchecked(this._ref) : _immutable = true;
2247

2348
factory JSArrayBufferImpl.fromRef(WasmExternRef? ref) {
24-
if (!_checkRefType(ref)) {
49+
final refType = _getRefType(ref);
50+
if (refType == _ArrayBufferType.unknown) {
2551
return _throwConversionFailureError("ByteBuffer");
2652
}
2753
return JSArrayBufferImpl.fromRefUnchecked(ref);
@@ -30,9 +56,13 @@ final class JSArrayBufferImpl implements ByteBuffer {
3056
@pragma("wasm:prefer-inline")
3157
WasmExternRef? get toExternRef => _ref;
3258

33-
/// Get a JS `DataView` of this `ArrayBuffer`.
59+
/// Get a JS `DataView` of this `ArrayBuffer` or `SharedArrayBuffer`.
3460
WasmExternRef? view(int offsetInBytes, int? length) =>
35-
_newDataViewFromArrayBuffer(toExternRef, offsetInBytes, length);
61+
_newDataViewFromArrayBufferOrSharedArrayBuffer(
62+
toExternRef,
63+
offsetInBytes,
64+
length,
65+
);
3666

3767
WasmExternRef? cloneAsDataView(int offsetInBytes, int? lengthInBytes) {
3868
lengthInBytes ??= this.lengthInBytes;
@@ -279,7 +309,11 @@ final class JSDataViewImpl implements ByteData {
279309
int offsetInBytes,
280310
int? length,
281311
) => JSDataViewImpl.fromRefUnchecked(
282-
_newDataViewFromArrayBuffer(buffer.toExternRef, offsetInBytes, length),
312+
_newDataViewFromArrayBufferOrSharedArrayBuffer(
313+
buffer.toExternRef,
314+
offsetInBytes,
315+
length,
316+
),
283317
);
284318

285319
@pragma("wasm:prefer-inline")
@@ -2601,7 +2635,7 @@ int _dataViewByteLength(WasmExternRef? ref) => js
26012635
.toInt();
26022636

26032637
@pragma("wasm:prefer-inline")
2604-
WasmExternRef? _newDataViewFromArrayBuffer(
2638+
WasmExternRef? _newDataViewFromArrayBufferOrSharedArrayBuffer(
26052639
WasmExternRef? bufferRef,
26062640
int offsetInBytes,
26072641
int? length,

sdk/lib/html/dart2js/html_dart2js.dart

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import 'dart:web_gl' show RenderingContext, RenderingContext2;
4646
import 'dart:_foreign_helper' show JS, JS_INTERCEPTOR_CONSTANT;
4747
import 'dart:js_util' as js_util;
4848

49+
export 'dart:_native_typed_data' show SharedArrayBuffer;
4950
// Not actually used, but imported since dart:html can generate these objects.
5051
import 'dart:_js_helper'
5152
show
@@ -28879,32 +28880,6 @@ Please remove them from your code.
2887928880
// for details. All rights reserved. Use of this source code is governed by a
2888028881
// BSD-style license that can be found in the LICENSE file.
2888128882

28882-
@Native("SharedArrayBuffer")
28883-
class SharedArrayBuffer extends JavaScriptObject {
28884-
// To suppress missing implicit constructor warnings.
28885-
factory SharedArrayBuffer._() {
28886-
throw new UnsupportedError("Not supported");
28887-
}
28888-
28889-
factory SharedArrayBuffer([int? length]) {
28890-
if (length != null) {
28891-
return SharedArrayBuffer._create_1(length);
28892-
}
28893-
return SharedArrayBuffer._create_2();
28894-
}
28895-
static SharedArrayBuffer _create_1(length) =>
28896-
JS('SharedArrayBuffer', 'new SharedArrayBuffer(#)', length);
28897-
static SharedArrayBuffer _create_2() =>
28898-
JS('SharedArrayBuffer', 'new SharedArrayBuffer()');
28899-
28900-
int? get byteLength native;
28901-
28902-
SharedArrayBuffer slice([int? begin, int? end]) native;
28903-
}
28904-
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28905-
// for details. All rights reserved. Use of this source code is governed by a
28906-
// BSD-style license that can be found in the LICENSE file.
28907-
2890828883
@Native("SharedWorker")
2890928884
class SharedWorker extends EventTarget implements AbstractWorker {
2891028885
// To suppress missing implicit constructor warnings.

sdk/lib/js_interop/js_interop.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -870,16 +870,19 @@ extension ByteBufferToJSArrayBuffer on ByteBuffer {
870870
/// Converts this [ByteBuffer] to a [JSArrayBuffer] by either casting,
871871
/// unwrapping, or cloning the [ByteBuffer].
872872
///
873+
/// Throws if the [ByteBuffer] wraps a JS `SharedArrayBuffer`.
874+
///
873875
/// > [!NOTE]
874876
/// > Depending on whether code is compiled to JavaScript or Wasm, this
875877
/// > conversion will have different semantics.
876-
/// > When compiling to JavaScript, all typed lists are the equivalent
877-
/// > JavaScript typed arrays, and therefore this method simply casts.
878+
/// > When compiling to JavaScript, [ByteBuffer]s are either `ArrayBuffer`s or
879+
/// > `SharedArrayBuffer`s so this will just check the type and cast.
878880
/// > When compiling to Wasm, this [ByteBuffer] may or may not be a wrapper
879881
/// > depending on if it was converted from JavaScript or instantiated in
880-
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
881-
/// > Dart, this method clones this [ByteBuffer]'s values into a new
882-
/// > [JSArrayBuffer].
882+
/// > Dart. If it's a wrapper, this method unwraps it and either returns the
883+
/// > `ArrayBuffer` or throws if the unwrapped buffer was a
884+
/// > `SharedArrayBuffer`. If it's instantiated in Dart, this method clones
885+
/// > this [ByteBuffer]'s values into a new [JSArrayBuffer].
883886
/// > Avoid assuming that modifications to this [ByteBuffer] will affect the
884887
/// > [JSArrayBuffer] and vice versa unless it was instantiated in JavaScript.
885888
external JSArrayBuffer get toJS;

0 commit comments

Comments
 (0)