From 0c527e1b9f187c823263d19f9a3c2df8a76ccc22 Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Tue, 10 Dec 2024 04:36:59 +0000 Subject: [PATCH] [dart2js] Separate `Iterable`s for Map.{keys,values,entries} The default `MapBase.entries` and `MapBase.values` maps over the keys and does lookups. It is faster to scan the data like `get keys`. This is essentially the dart2js version of https://github.com/dart-lang/sdk/commit/23a98d808c0 Change-Id: I03894efda459d9a4f7f07c4c79524ba5e4d925cb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/399027 Reviewed-by: Mayank Patke Commit-Queue: Stephen Adams --- .../test/inference/data/map_tracer_keys.dart | 12 +- .../js_runtime/lib/linked_hash_map.dart | 117 ++++++++++++++++-- 2 files changed, 111 insertions(+), 18 deletions(-) diff --git a/pkg/compiler/test/inference/data/map_tracer_keys.dart b/pkg/compiler/test/inference/data/map_tracer_keys.dart index 3742400e138f..060613921b79 100644 --- a/pkg/compiler/test/inference/data/map_tracer_keys.dart +++ b/pkg/compiler/test/inference/data/map_tracer_keys.dart @@ -29,7 +29,7 @@ test1() { theMap /*update: Dictionary([exact=JsLinkedHashMap], key: [exact=JSString], value: [null|exact=JSNumNotInt], map: {a: [exact=JSNumNotInt], b: [exact=JSNumNotInt], c: [exact=JSNumNotInt], d: [null|exact=JSNumNotInt]})*/ ['d'] = 5.5; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. @@ -60,7 +60,7 @@ test2() { theMap /*update: Map([exact=JsLinkedHashMap], key: [exact=JSString], value: [null|exact=JSNumNotInt])*/ [aList2] = 5.5; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. @@ -91,7 +91,7 @@ test3() { theMap /*update: Dictionary([exact=JsLinkedHashMap], key: [exact=JSString], value: Union(null, [exact=JSExtendableArray], [exact=JSNumNotInt]), map: {a: [exact=JSNumNotInt], b: [exact=JSNumNotInt], c: [exact=JSNumNotInt], d: Container([null|exact=JSExtendableArray], element: [exact=JSUInt31], length: 1)})*/ ['d'] = aList3; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. @@ -119,7 +119,7 @@ consume4( /*member: test4:[null]*/ test4() { var theMap = {'a': 2.2, 'b': 3.3, 'c': 4.4, 'd': 5.5}; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. @@ -147,7 +147,7 @@ consume5( /*member: test5:[null]*/ test5() { var theMap = {'a': 2.2, 'b': 3.3, 'c': 4.4, aList5: 5.5}; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. @@ -174,7 +174,7 @@ consume6( /*member: test6:[null]*/ test6() { var theMap = {'a': 2.2, 'b': 3.3, 'c': 4.4, 'd': aList6}; - /*iterator: [exact=LinkedHashMapKeyIterable]*/ + /*iterator: [exact=LinkedHashMapKeysIterable]*/ /*current: [exact=LinkedHashMapKeyIterator]*/ /*moveNext: [exact=LinkedHashMapKeyIterator]*/ for (var key in theMap. diff --git a/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart b/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart index 7c44d4c0c750..bfd6ec63b86d 100644 --- a/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart +++ b/sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart @@ -44,13 +44,11 @@ class JsLinkedHashMap extends MapBase bool get isEmpty => _length == 0; bool get isNotEmpty => !isEmpty; - Iterable get keys { - return LinkedHashMapKeyIterable(this); - } + Iterable get keys => LinkedHashMapKeysIterable(this); - Iterable get values { - return MappedIterable(keys, (each) => this[each] as V); - } + Iterable get values => LinkedHashMapValuesIterable(this); + + Iterable> get entries => LinkedHashMapEntriesIterable(this); bool containsKey(Object? key) { if (_isStringKey(key)) { @@ -339,13 +337,13 @@ class LinkedHashMapCell { LinkedHashMapCell(this.hashMapCellKey, this.hashMapCellValue); } -class LinkedHashMapKeyIterable extends EfficientLengthIterable +class LinkedHashMapKeysIterable extends EfficientLengthIterable implements HideEfficientLengthIterable { final JsLinkedHashMap _map; - LinkedHashMapKeyIterable(this._map); + LinkedHashMapKeysIterable(this._map); int get length => _map._length; - bool get isEmpty => _map._length == 0; + bool get isEmpty => _map.isEmpty; Iterator get iterator { return LinkedHashMapKeyIterator(_map, _map._modifications); @@ -374,9 +372,8 @@ class LinkedHashMapKeyIterator implements Iterator { LinkedHashMapCell? _cell; E? _current; - LinkedHashMapKeyIterator(this._map, this._modifications) { - _cell = _map._first; - } + LinkedHashMapKeyIterator(this._map, this._modifications) + : _cell = _map._first; @pragma('dart2js:as:trust') E get current => _current as E; @@ -397,6 +394,102 @@ class LinkedHashMapKeyIterator implements Iterator { } } +class LinkedHashMapValuesIterable extends EfficientLengthIterable + implements HideEfficientLengthIterable { + final JsLinkedHashMap _map; + LinkedHashMapValuesIterable(this._map); + + int get length => _map._length; + bool get isEmpty => _map.isEmpty; + + Iterator get iterator { + return LinkedHashMapValueIterator(_map, _map._modifications); + } + + void forEach(void f(E element)) { + LinkedHashMapCell? cell = _map._first; + int modifications = _map._modifications; + while (cell != null) { + f(JS('', '#', cell.hashMapCellValue)); + if (modifications != _map._modifications) { + throw ConcurrentModificationError(_map); + } + cell = cell._next; + } + } +} + +class LinkedHashMapValueIterator implements Iterator { + final JsLinkedHashMap _map; + final int _modifications; + LinkedHashMapCell? _cell; + E? _current; + + LinkedHashMapValueIterator(this._map, this._modifications) + : _cell = _map._first; + + @pragma('dart2js:as:trust') + E get current => _current as E; + + bool moveNext() { + if (_modifications != _map._modifications) { + throw ConcurrentModificationError(_map); + } + var cell = _cell; + if (cell == null) { + _current = null; + return false; + } else { + _current = JS('', '#', cell.hashMapCellValue); + _cell = cell._next; + return true; + } + } +} + +class LinkedHashMapEntriesIterable + extends EfficientLengthIterable> + implements HideEfficientLengthIterable> { + final JsLinkedHashMap _map; + LinkedHashMapEntriesIterable(this._map); + + int get length => _map._length; + bool get isEmpty => _map.isEmpty; + + Iterator> get iterator { + return LinkedHashMapEntryIterator(_map, _map._modifications); + } +} + +class LinkedHashMapEntryIterator implements Iterator> { + final JsLinkedHashMap _map; + final int _modifications; + LinkedHashMapCell? _cell; + MapEntry? _current; + + LinkedHashMapEntryIterator(this._map, this._modifications) + : _cell = _map._first; + + MapEntry get current => _current!; + + bool moveNext() { + if (_modifications != _map._modifications) { + throw ConcurrentModificationError(_map); + } + var cell = _cell; + if (cell == null) { + _current = null; + return false; + } else { + final K key = JS('', '#', cell.hashMapCellKey); + final V value = JS('', '#', cell.hashMapCellValue); + _current = MapEntry(key, value); + _cell = cell._next; + return true; + } + } +} + base class JsIdentityLinkedHashMap extends JsLinkedHashMap { JsIdentityLinkedHashMap();