From 465d35fac98a6e1d673d0dfa75ed82ac76a5e7e4 Mon Sep 17 00:00:00 2001 From: Joshua Litt Date: Thu, 13 Jul 2023 15:29:37 +0000 Subject: [PATCH] [dart2wasm|js] Add support for JS backed subtypes of 64 bit typed data. Change-Id: I534e946ffdfa6708af0c0ffdecb345adbc9561aa Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313286 Commit-Queue: Joshua Litt Reviewed-by: Srujan Gaddam --- pkg/dart2wasm/lib/class_info.dart | 2 + .../_internal/wasm/lib/js_typed_array.dart | 300 +++++++++++++----- sdk/lib/_internal/wasm/lib/js_types.dart | 3 +- .../js_typed_array_test.dart | 38 +++ 4 files changed, 262 insertions(+), 81 deletions(-) diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart index 007a5362f8b..858e208cee4 100644 --- a/pkg/dart2wasm/lib/class_info.dart +++ b/pkg/dart2wasm/lib/class_info.dart @@ -263,6 +263,8 @@ class ClassInfoCollector { "JSInt32ArrayImpl", "JSInt32x4ArrayImpl", "JSUint32ArrayImpl", + "JSBigUint64ArrayImpl", + "JSBigInt64ArrayImpl", "JSFloat32ArrayImpl", "JSFloat32x4ArrayImpl", "JSFloat64ArrayImpl", diff --git a/sdk/lib/_internal/wasm/lib/js_typed_array.dart b/sdk/lib/_internal/wasm/lib/js_typed_array.dart index 3a6792c1188..19695dbb469 100644 --- a/sdk/lib/_internal/wasm/lib/js_typed_array.dart +++ b/sdk/lib/_internal/wasm/lib/js_typed_array.dart @@ -37,12 +37,13 @@ final class JSArrayBufferImpl implements ByteBuffer { JSInt32ArrayImpl.view(this, offsetInBytes, length); Uint64List asUint64List([int offsetInBytes = 0, int? length]) => - throw UnsupportedError("Uint64List not supported by JSArrayBufferImpl."); + JSBigUint64ArrayImpl.view(this, offsetInBytes, length); Int64List asInt64List([int offsetInBytes = 0, int? length]) => - throw UnsupportedError("Int64List not supported by JSArrayBufferImpl."); + JSBigInt64ArrayImpl.view(this, offsetInBytes, length); Int32x4List asInt32x4List([int offsetInBytes = 0, int? length]) { + _offsetAlignmentCheck(offsetInBytes, Int32x4List.bytesPerElement); length ??= (lengthInBytes - offsetInBytes) ~/ Int32x4List.bytesPerElement; final storage = JSInt32ArrayImpl.view(this, offsetInBytes, length * 4); return JSInt32x4ArrayImpl._externalStorage(storage); @@ -55,12 +56,14 @@ final class JSArrayBufferImpl implements ByteBuffer { JSFloat64ArrayImpl.view(this, offsetInBytes, length); Float32x4List asFloat32x4List([int offsetInBytes = 0, int? length]) { + _offsetAlignmentCheck(offsetInBytes, Float32x4List.bytesPerElement); length ??= (lengthInBytes - offsetInBytes) ~/ Float32x4List.bytesPerElement; final storage = JSFloat32ArrayImpl.view(this, offsetInBytes, length * 4); return JSFloat32x4ArrayImpl._externalStorage(storage); } Float64x2List asFloat64x2List([int offsetInBytes = 0, int? length]) { + _offsetAlignmentCheck(offsetInBytes, Float64x2List.bytesPerElement); length ??= (lengthInBytes - offsetInBytes) ~/ Float64x2List.bytesPerElement; final storage = JSFloat64ArrayImpl.view(this, offsetInBytes, length * 2); return JSFloat64x2ArrayImpl._externalStorage(storage); @@ -68,6 +71,13 @@ final class JSArrayBufferImpl implements ByteBuffer { ByteData asByteData([int offsetInBytes = 0, int? length]) => JSDataViewImpl.view(this, offsetInBytes, length); + + @override + bool operator ==(Object that) => + that is JSArrayBufferImpl && js.areEqualInJS(_ref, that._ref); + + @override + int get hashCode => 0; } final class JSArrayBufferViewImpl implements TypedData { @@ -94,6 +104,13 @@ final class JSArrayBufferViewImpl implements TypedData { js.JS('o => o.BYTES_PER_ELEMENT', toExternRef).toInt(); int get length => js.JS('o => o.length', toExternRef).toInt(); + + @override + bool operator ==(Object that) => + that is JSArrayBufferViewImpl && js.areEqualInJS(_ref, that._ref); + + @override + int get hashCode => 0; } final class JSDataViewImpl extends JSArrayBufferViewImpl implements ByteData { @@ -133,9 +150,11 @@ final class JSDataViewImpl extends JSArrayBufferViewImpl implements ByteData { byteOffset.toDouble(), Endian.little == endian) .toInt(); - int getInt64(int byteOffset, [Endian endian = Endian.big]) { - throw UnsupportedError('Int64 accessor not supported by JSDataViewImpl'); - } + int getInt64(int byteOffset, [Endian endian = Endian.big]) => js.JS( + '(b, o, e) => b.getBigInt64(o, e)', + toExternRef, + byteOffset.toDouble(), + Endian.little == endian); int getInt8(int byteOffset) => js .JS('(b, o) => b.getInt8(o)', toExternRef, byteOffset.toDouble()) @@ -151,9 +170,11 @@ final class JSDataViewImpl extends JSArrayBufferViewImpl implements ByteData { byteOffset.toDouble(), Endian.little == endian) .toInt(); - int getUint64(int byteOffset, [Endian endian = Endian.big]) { - throw UnsupportedError('Uint64 accessor not supported by JSDataViewImpl'); - } + int getUint64(int byteOffset, [Endian endian = Endian.big]) => js.JS( + '(b, o, e) => b.getBigUint64(o, e)', + toExternRef, + byteOffset.toDouble(), + Endian.little == endian); int getUint8(int byteOffset) => js .JS('(b, o) => b.getUint8(o)', toExternRef, byteOffset.toDouble()) @@ -175,9 +196,9 @@ final class JSDataViewImpl extends JSArrayBufferViewImpl implements ByteData { js.JS('(b, o, v, e) => b.setInt32(o, v, e)', toExternRef, byteOffset.toDouble(), value.toDouble(), Endian.little == endian); - void setInt64(int byteOffset, int value, [Endian endian = Endian.big]) { - throw UnsupportedError('Int64 accessor not supported by JSDataViewImpl'); - } + void setInt64(int byteOffset, int value, [Endian endian = Endian.big]) => + js.JS('(b, o, v, e) => b.setBigInt64(o, v, e)', toExternRef, + byteOffset.toDouble(), value, Endian.little == endian); void setInt8(int byteOffset, int value) => js.JS( '(b, o, v) => b.setInt8(o, v)', @@ -193,9 +214,9 @@ final class JSDataViewImpl extends JSArrayBufferViewImpl implements ByteData { js.JS('(b, o, v, e) => b.setUint32(o, v, e)', toExternRef, byteOffset.toDouble(), value.toDouble(), Endian.little == endian); - void setUint64(int byteOffset, int value, [Endian endian = Endian.big]) { - throw UnsupportedError('Uint64 accessor not supported by JSDataViewImpl'); - } + void setUint64(int byteOffset, int value, [Endian endian = Endian.big]) => + js.JS('(b, o, v, e) => b.setBigUint64(o, v, e)', toExternRef, + byteOffset.toDouble(), value, Endian.little == endian); void setUint8(int byteOffset, int value) => js.JS( '(b, o, v) => b.setUint8(o, v)', @@ -223,13 +244,42 @@ final class JSIntArrayImpl extends JSArrayBufferViewImpl value.toDouble()); } + @override + void setAll(int index, Iterable iterable) { + final end = iterable.length + index; + setRange(index, end, iterable); + } + @override void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { - if (iterable is JSIntArrayImpl) { - _setRangeFast(this, start, end, iterable, skipCount); + int count = end - start; + RangeError.checkValidRange(start, end, length); + + if (skipCount < 0) throw ArgumentError(skipCount); + + int sourceLength = iterable.length; + if (sourceLength - skipCount < count) { + throw IterableElementError.tooFew(); + } + + if (iterable is JSArrayBufferViewImpl) { + _setRangeFast(this, start, end, count, iterable as JSArrayBufferViewImpl, + sourceLength, skipCount); } else { - super.setRange(start, end, iterable, skipCount); + List otherList; + int otherStart; + if (iterable is List) { + otherList = iterable; + otherStart = skipCount; + } else { + otherList = iterable.skip(skipCount).toList(growable: false); + otherStart = 0; + } + if (otherStart + count > otherList.length) { + throw IterableElementError.tooFew(); + } + Lists.copy(otherList, otherStart, this, start, count); } } } @@ -318,14 +368,13 @@ final class JSUint16ArrayImpl extends JSIntArrayImpl implements Uint16List { factory JSUint16ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Uint16Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Uint16Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Uint16List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Uint16List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Uint16Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSUint16ArrayImpl(jsBuffer); } @@ -343,14 +392,13 @@ final class JSInt16ArrayImpl extends JSIntArrayImpl implements Int16List { factory JSInt16ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Int16Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Int16Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Int16List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Int16List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Int16Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSInt16ArrayImpl(jsBuffer); } @@ -368,14 +416,13 @@ final class JSUint32ArrayImpl extends JSIntArrayImpl implements Uint32List { factory JSUint32ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Uint32Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Uint32Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Uint32List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Uint32List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Uint32Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSUint32ArrayImpl(jsBuffer); } @@ -393,14 +440,13 @@ final class JSInt32ArrayImpl extends JSIntArrayImpl implements Int32List { factory JSInt32ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Int32Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Int32Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Int32List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Int32List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Int32Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSInt32ArrayImpl(jsBuffer); } @@ -463,6 +509,71 @@ final class JSInt32x4ArrayImpl } } +final class JSBigIntArrayImpl extends JSIntArrayImpl { + JSBigIntArrayImpl(super._ref); + + @override + int operator [](int index) { + IndexError.check(index, length); + return js.JS('(o, i) => o[i]', toExternRef, index.toDouble()).toInt(); + } + + @override + void operator []=(int index, int value) { + IndexError.check(index, length); + js.JS('(o, i, v) => o[i] = v', toExternRef, index.toDouble(), value); + } +} + +final class JSBigUint64ArrayImpl extends JSBigIntArrayImpl + implements Uint64List { + JSBigUint64ArrayImpl(super._ref); + + factory JSBigUint64ArrayImpl.view( + JSArrayBufferImpl buffer, int offsetInBytes, int? length) { + _offsetAlignmentCheck(offsetInBytes, Uint64List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Uint64List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new BigUint64Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); + return JSBigUint64ArrayImpl(jsBuffer); + } + + @override + Uint64List sublist(int start, [int? end]) { + final stop = RangeError.checkValidRange(start, end, length); + final source = js.JS('(a, s, p) => a.subarray(s, p)', + toExternRef, start.toDouble(), stop.toDouble()); + return JSBigUint64ArrayImpl(source); + } +} + +final class JSBigInt64ArrayImpl extends JSBigIntArrayImpl implements Int64List { + JSBigInt64ArrayImpl(super._ref); + + factory JSBigInt64ArrayImpl.view( + JSArrayBufferImpl buffer, int offsetInBytes, int? length) { + _offsetAlignmentCheck(offsetInBytes, Int64List.bytesPerElement); + length ??= _adjustLength(buffer, offsetInBytes, Int64List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new BigInt64Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); + return JSBigInt64ArrayImpl(jsBuffer); + } + + @override + Int64List sublist(int start, [int? end]) { + final stop = RangeError.checkValidRange(start, end, length); + final source = js.JS('(a, s, p) => a.subarray(s, p)', + toExternRef, start.toDouble(), stop.toDouble()); + return JSBigInt64ArrayImpl(source); + } +} + final class JSFloatArrayImpl extends JSArrayBufferViewImpl with ListMixin, FixedLengthListMixin { JSFloatArrayImpl(super._ref); @@ -480,13 +591,42 @@ final class JSFloatArrayImpl extends JSArrayBufferViewImpl value.toDouble()); } + @override + void setAll(int index, Iterable iterable) { + final end = iterable.length + index; + setRange(index, end, iterable); + } + @override void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { - if (iterable is JSFloatArrayImpl) { - _setRangeFast(this, start, end, iterable, skipCount); + int count = end - start; + RangeError.checkValidRange(start, end, length); + + if (skipCount < 0) throw ArgumentError(skipCount); + + int sourceLength = iterable.length; + if (sourceLength - skipCount < count) { + throw IterableElementError.tooFew(); + } + + if (iterable is JSArrayBufferViewImpl) { + _setRangeFast(this, start, end, count, iterable as JSArrayBufferViewImpl, + sourceLength, skipCount); } else { - super.setRange(start, end, iterable, skipCount); + List otherList; + int otherStart; + if (iterable is List) { + otherList = iterable; + otherStart = skipCount; + } else { + otherList = iterable.skip(skipCount).toList(growable: false); + otherStart = 0; + } + if (otherStart + count > otherList.length) { + throw IterableElementError.tooFew(); + } + Lists.copy(otherList, otherStart, this, start, count); } } } @@ -496,14 +636,14 @@ final class JSFloat32ArrayImpl extends JSFloatArrayImpl implements Float32List { factory JSFloat32ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Float32Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Float32Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Float32List.bytesPerElement); + length ??= + _adjustLength(buffer, offsetInBytes, Float32List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Float32Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSFloat32ArrayImpl(jsBuffer); } @@ -521,14 +661,14 @@ final class JSFloat64ArrayImpl extends JSFloatArrayImpl implements Float64List { factory JSFloat64ArrayImpl.view( JSArrayBufferImpl buffer, int offsetInBytes, int? length) { - WasmExternRef? jsBuffer; - if (length == null) { - jsBuffer = js.JS('(b, o) => new Float64Array(b, o)', - buffer.toExternRef, offsetInBytes.toDouble()); - } else { - jsBuffer = js.JS('(b, o, l) => new Float64Array(b, o, l)', - buffer.toExternRef, offsetInBytes.toDouble(), length.toDouble()); - } + _offsetAlignmentCheck(offsetInBytes, Float64List.bytesPerElement); + length ??= + _adjustLength(buffer, offsetInBytes, Float64List.bytesPerElement); + WasmExternRef? jsBuffer = js.JS( + '(b, o, l) => new Float64Array(b, o, l)', + buffer.toExternRef, + offsetInBytes.toDouble(), + length.toDouble()); return JSFloat64ArrayImpl(jsBuffer); } @@ -637,18 +777,8 @@ final class JSFloat64x2ArrayImpl } } -void _setRangeFast(JSArrayBufferViewImpl target, int start, int end, - JSArrayBufferViewImpl source, int skipCount) { - RangeError.checkValidRange(start, end, target.length); - int count = end - start; - - if (skipCount < 0) throw ArgumentError(skipCount); - - int sourceLength = source.length; - if (sourceLength - skipCount < count) { - throw StateError('Not enough elements'); - } - +void _setRangeFast(JSArrayBufferViewImpl target, int start, int end, int count, + JSArrayBufferViewImpl source, int sourceLength, int skipCount) { WasmExternRef? jsSource; if (skipCount != 0 || sourceLength != count) { // Create a view of the exact subrange that is copied from the source. @@ -663,3 +793,13 @@ void _setRangeFast(JSArrayBufferViewImpl target, int start, int end, js.JS('(t, s, i) => t.set(s, i)', target.toExternRef, jsSource, start.toDouble()); } + +int _adjustLength(ByteBuffer buffer, int offsetInBytes, int bytesPerElement) => + (buffer.lengthInBytes - offsetInBytes) ~/ bytesPerElement; + +void _offsetAlignmentCheck(int offset, int alignment) { + if ((offset % alignment) != 0) { + throw new RangeError('Offset ($offset) must be a multiple of ' + 'bytesPerElement ($alignment)'); + } +} diff --git a/sdk/lib/_internal/wasm/lib/js_types.dart b/sdk/lib/_internal/wasm/lib/js_types.dart index 4857a6a15e8..0f061488b10 100644 --- a/sdk/lib/_internal/wasm/lib/js_types.dart +++ b/sdk/lib/_internal/wasm/lib/js_types.dart @@ -8,7 +8,8 @@ /// library. library dart._js_types; -import 'dart:_internal' show CodeUnits, FixedLengthListMixin; +import 'dart:_internal' + show CodeUnits, FixedLengthListMixin, IterableElementError, Lists; import 'dart:_js_helper' as js; import 'dart:_string_helper'; import 'dart:_wasm'; diff --git a/tests/lib/js/static_interop_test/js_typed_array_test.dart b/tests/lib/js/static_interop_test/js_typed_array_test.dart index 5793dfc5528..e3eee69f1ea 100644 --- a/tests/lib/js/static_interop_test/js_typed_array_test.dart +++ b/tests/lib/js/static_interop_test/js_typed_array_test.dart @@ -347,6 +347,43 @@ void testSimd() { Expect.equals(4, sf64a[0].y); } +void bigTest() { + if (const bool.fromEnvironment('dart.library.html')) { + // Not yet supported on JS backends. + return; + } + + // Uint64List + { + final buffer = Uint32List(2).toJS.toDart.buffer; + final bigList = buffer.asUint64List(); + final littleList = buffer.asUint8List(); + bigList[0] = 4294967296; // Max 32 bit unsigned + 1 + Expect.equals(4294967296, bigList[0]); + Expect.listEquals([0, 0, 0, 0, 1, 0, 0, 0], littleList); + + final byteData = ByteData.view(buffer); + byteData.setUint64(0, 4294967297); + Expect.equals(4294967297, byteData.getUint64(0)); + Expect.listEquals([0, 0, 0, 1, 0, 0, 0, 1], littleList); + } + + // Int64List + { + final buffer = Int32List(2).toJS.toDart.buffer; + final bigList = buffer.asInt64List(); + final littleList = buffer.asInt8List(); + bigList[0] = -2147483648; // Min 32 bit signed - 1 + Expect.equals(-2147483648, bigList[0]); + Expect.listEquals([0, 0, 0, -128, -1, -1, -1, -1], littleList); + + final byteData = ByteData.view(buffer); + byteData.setInt64(0, -2147483649); + Expect.equals(-2147483649, byteData.getInt64(0)); + Expect.listEquals([-1, -1, -1, -1, 127, -1, -1, -1], littleList); + } +} + void main() { for (final mode in [ TestMode.jsReceiver, @@ -361,4 +398,5 @@ void main() { clampingTest(); overlapTest(); testSimd(); + bigTest(); }