Skip to content

Commit 3d36dbd

Browse files
sigurdmdart-scoped@luci-project-accounts.iam.gserviceaccount.com
authored andcommitted
[corelib] Add rangeEquals to dart:typed_data
Implements native range equality comparison across all 9 integer typed data lists (Uint8List, Int8List, Uint8ClampedList, Uint16List, Int16List, Uint32List, Int32List, Uint64List, Int64List) and ByteData: bool rangeEquals( int start, int end, <SelfType> other, [int otherOffset = 0]) Float types (Float32List, Float64List, Float32x4List, Float64x2List) are omitted to avoid discrepancies between IEEE-754 equality and bitwise equality (such as NaN and -0.0). - VM: lowered via native entry TypedDataBase_memEquals calling libc memcmp under NoSafepointScope, with pointer-equality fast path and element size assertions. - JS (dart2js & DDC): inlines raw JS loop for NativeTypedArray, and 32-bit chunking with Endian.little for NativeByteData, with fallback to List and ByteData methods for non-native implementations. - Wasm: specializes per Wasm array type using direct array load instructions without bounds check overhead; 32-bit chunking for ByteData in both default and JS-compatibility modes. R=lrn@google.com, vegorov@google.com Fixes: #64095 TEST=tests/lib/typed_data/range_equals_test.dart Change-Id: I132956aa9482d60d62f79bd891dddb84bb2bc456 TAG=agy CONV=1e62e82d-deec-4edd-947d-b084183a5641 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/545160 Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Stephen Adams <sra@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Sigurd Meldgaard <sigurdm@google.com> Reviewed-by: Slava Egorov <vegorov@google.com>
1 parent 1e4146c commit 3d36dbd

9 files changed

Lines changed: 2261 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
#### `dart:typed_data`
1616

17+
- Added `rangeEquals` extension methods on all integer typed data lists
18+
(`Uint8List`, `Int8List`, `Uint8ClampedList`, `Uint16List`, `Int16List`,
19+
`Uint32List`, `Int32List`, `Uint64List`, `Int64List`) and `ByteData` for
20+
efficient range equality comparisons.
21+
For more details, see SDK issue [#64095][]
22+
23+
[#64095]: https://github.com/dart-lang/sdk/issues/64095
1724
- Added the bit-wise negation operator `~` to `Int32x4`, which inverts every bit
1825
of every lane.
1926
- Added `Int32x4.splat`, which creates an `Int32x4` with the same 32-bit integer

‎runtime/lib/typed_data.cc‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,55 @@ DEFINE_NATIVE_ENTRY(TypedDataBase_setClampedRange, 0, 5) {
107107
return Object::null();
108108
}
109109

110+
DEFINE_NATIVE_ENTRY(TypedDataBase_memEquals, 0, 5) {
111+
const TypedDataBase& a =
112+
TypedDataBase::CheckedHandle(zone, arguments->NativeArgAt(0));
113+
const Smi& a_start_smi = Smi::CheckedHandle(zone, arguments->NativeArgAt(1));
114+
const TypedDataBase& b =
115+
TypedDataBase::CheckedHandle(zone, arguments->NativeArgAt(2));
116+
const Smi& b_start_smi = Smi::CheckedHandle(zone, arguments->NativeArgAt(3));
117+
const Smi& count_smi = Smi::CheckedHandle(zone, arguments->NativeArgAt(4));
118+
119+
const intptr_t count = count_smi.Value();
120+
if (count == 0) {
121+
return Bool::True().ptr();
122+
}
123+
124+
const intptr_t a_len = a.Length();
125+
const intptr_t b_len = b.Length();
126+
const intptr_t a_start = a_start_smi.Value();
127+
const intptr_t b_start = b_start_smi.Value();
128+
if (a_start < 0 || count < 0 || a_start > a_len - count) {
129+
Exceptions::ThrowRangeError("aStart", a_start_smi, 0,
130+
a_len - count >= 0 ? a_len - count : 0);
131+
}
132+
if (b_start < 0 || b_start > b_len - count) {
133+
Exceptions::ThrowRangeError("bStart", b_start_smi, 0,
134+
b_len - count >= 0 ? b_len - count : 0);
135+
}
136+
137+
const intptr_t a_element_size = a.ElementSizeInBytes();
138+
if (a_element_size != b.ElementSizeInBytes()) {
139+
Exceptions::ThrowArgumentError(
140+
String::Handle(String::New("Typed data element sizes do not match")));
141+
}
142+
143+
const intptr_t a_start_in_bytes = a_start * a_element_size;
144+
const intptr_t b_start_in_bytes = b_start * a_element_size;
145+
const intptr_t length_in_bytes = count * a_element_size;
146+
147+
NoSafepointScope no_safepoint;
148+
const void* a_data =
149+
reinterpret_cast<const void*>(a.DataAddr(a_start_in_bytes));
150+
const void* b_data =
151+
reinterpret_cast<const void*>(b.DataAddr(b_start_in_bytes));
152+
if (a_data == b_data) {
153+
return Bool::True().ptr();
154+
}
155+
156+
return Bool::Get(memcmp(a_data, b_data, length_in_bytes) == 0).ptr();
157+
}
158+
110159
// The native getter and setter functions defined here are only called if
111160
// unboxing doubles or SIMD values is not supported by the flow graph compiler,
112161
// and the provided offsets have already been range checked by the calling code.

‎runtime/vm/bootstrap_natives.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ namespace dart {
155155
V(MicrotaskMirrorQueue_onAsyncCallbackComplete, 2) \
156156
V(TypedDataBase_length, 1) \
157157
V(TypedDataBase_setClampedRange, 5) \
158+
V(TypedDataBase_memEquals, 5) \
158159
V(TypedData_GetFloat32, 2) \
159160
V(TypedData_SetFloat32, 3) \
160161
V(TypedData_GetFloat64, 2) \

‎sdk/lib/_internal/js_runtime/lib/typed_data_patch.dart‎

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:_foreign_helper' show JS;
56
import 'dart:_internal' show patch;
67
import 'dart:_native_typed_data';
7-
import "dart:_internal" show UnmodifiableListBase;
88

99
@patch
1010
class ByteData {
@@ -193,3 +193,168 @@ class Float64x2 {
193193
@patch
194194
factory Float64x2.fromFloat32x4(Float32x4 v) = NativeFloat64x2.fromFloat32x4;
195195
}
196+
197+
// Each integer typed array type has a dedicated, strictly monomorphic comparison
198+
// loop. This keeps V8's Inline Cache at `#[#] !== #[#]` strictly monomorphic
199+
// (IC size = 1), avoiding polymorphic/megamorphic transitions, deoptimizations,
200+
// and runtime type checks. Unused types can also be tree-shaken by dart2js.
201+
@patch
202+
@pragma('dart2js:prefer-inline')
203+
bool _uint8ListRangeEquals(
204+
Uint8List a,
205+
int aStart,
206+
Uint8List b,
207+
int bStart,
208+
int count,
209+
) {
210+
for (int i = 0; i < count; i++) {
211+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
212+
return false;
213+
}
214+
}
215+
return true;
216+
}
217+
218+
@patch
219+
@pragma('dart2js:prefer-inline')
220+
bool _int8ListRangeEquals(
221+
Int8List a,
222+
int aStart,
223+
Int8List b,
224+
int bStart,
225+
int count,
226+
) {
227+
for (int i = 0; i < count; i++) {
228+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
229+
return false;
230+
}
231+
}
232+
return true;
233+
}
234+
235+
@patch
236+
@pragma('dart2js:prefer-inline')
237+
bool _uint8ClampedListRangeEquals(
238+
Uint8ClampedList a,
239+
int aStart,
240+
Uint8ClampedList b,
241+
int bStart,
242+
int count,
243+
) {
244+
for (int i = 0; i < count; i++) {
245+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
246+
return false;
247+
}
248+
}
249+
return true;
250+
}
251+
252+
@patch
253+
@pragma('dart2js:prefer-inline')
254+
bool _uint16ListRangeEquals(
255+
Uint16List a,
256+
int aStart,
257+
Uint16List b,
258+
int bStart,
259+
int count,
260+
) {
261+
for (int i = 0; i < count; i++) {
262+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
263+
return false;
264+
}
265+
}
266+
return true;
267+
}
268+
269+
@patch
270+
@pragma('dart2js:prefer-inline')
271+
bool _int16ListRangeEquals(
272+
Int16List a,
273+
int aStart,
274+
Int16List b,
275+
int bStart,
276+
int count,
277+
) {
278+
for (int i = 0; i < count; i++) {
279+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
280+
return false;
281+
}
282+
}
283+
return true;
284+
}
285+
286+
@patch
287+
@pragma('dart2js:prefer-inline')
288+
bool _uint32ListRangeEquals(
289+
Uint32List a,
290+
int aStart,
291+
Uint32List b,
292+
int bStart,
293+
int count,
294+
) {
295+
for (int i = 0; i < count; i++) {
296+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
297+
return false;
298+
}
299+
}
300+
return true;
301+
}
302+
303+
@patch
304+
@pragma('dart2js:prefer-inline')
305+
bool _int32ListRangeEquals(
306+
Int32List a,
307+
int aStart,
308+
Int32List b,
309+
int bStart,
310+
int count,
311+
) {
312+
for (int i = 0; i < count; i++) {
313+
if (JS<bool>('bool', '#[#] !== #[#]', a, aStart + i, b, bStart + i)) {
314+
return false;
315+
}
316+
}
317+
return true;
318+
}
319+
320+
@patch
321+
@pragma('dart2js:prefer-inline')
322+
bool _byteDataRangeEquals(
323+
ByteData a,
324+
int aStart,
325+
ByteData b,
326+
int bStart,
327+
int count,
328+
) {
329+
int i = 0;
330+
// Compare 32 bits at a time in little-endian byte order. Equality is byte-order
331+
// invariant, and all supported modern JS runtimes run on little-endian hardware.
332+
// Passing a constant `true` enables the JS engine to emit a single direct load
333+
// instruction without endian branching or BigInt overhead.
334+
final limit32 = count - 4;
335+
for (; i <= limit32; i += 4) {
336+
if (JS<bool>(
337+
'bool',
338+
'#.getUint32(#, true) !== #.getUint32(#, true)',
339+
a,
340+
aStart + i,
341+
b,
342+
bStart + i,
343+
)) {
344+
return false;
345+
}
346+
}
347+
for (; i < count; i++) {
348+
if (JS<bool>(
349+
'bool',
350+
'#.getUint8(#) !== #.getUint8(#)',
351+
a,
352+
aStart + i,
353+
b,
354+
bStart + i,
355+
)) {
356+
return false;
357+
}
358+
}
359+
return true;
360+
}

0 commit comments

Comments
 (0)