Index: runtime/lib/mirrors_impl.dart |
diff --git a/runtime/lib/mirrors_impl.dart b/runtime/lib/mirrors_impl.dart |
index 89b7f0581bd505f1084e83d346642d45da5d043f..f2799eee0e3ca2fcb5c4a02d087c0ed8e1e367fd 100644 |
--- a/runtime/lib/mirrors_impl.dart |
+++ b/runtime/lib/mirrors_impl.dart |
@@ -5,7 +5,6 @@ |
// VM-specific implementation of the dart:mirrors library. |
import "dart:collection" show UnmodifiableListView, UnmodifiableMapView; |
-import "dart:_internal" show LRUMap; |
final emptyList = new UnmodifiableListView([]); |
final emptyMap = new UnmodifiableMapView({}); |
@@ -90,6 +89,149 @@ bool _subtypeTest(Type a, Type b) |
bool _moreSpecificTest(Type a, Type b) |
native 'TypeMirror_moreSpecificTest'; |
+class _AccessorCacheAssociation { |
+ String key; |
+ Function value; |
+ bool usedSinceGrowth = true; |
+ _AccessorCacheAssociation(this.key, this.value); |
+} |
+ |
+/** |
+ * A map that will grow as associations are added but will prefer to evict |
+ * associations that have not been used since the last growth when needing to |
+ * grow again. Implemented as an open addressing hash table. |
+ */ |
+class _AccessorCache { |
+ List table; |
+ int shift; |
+ int mask; |
+ int capacity; // Max number of associations before we start evicting/growing. |
+ int size = 0; // Current number of associations. |
+ |
+ /** |
+ * Create a cache whose capacity is 75% of 2^shift. |
+ */ |
+ _AccessorCache.withInitialShift(int shift) { |
+ // The scheme used here for handling collisions relies on there always |
+ // being at least one empty slot. |
+ if (shift < 1) throw new Exception("_AccessorCache requires a shift >= 1"); |
+ initWithShift(shift); |
+ } |
+ |
+ void initWithShift(int shift) { |
+ this.shift = shift; |
+ this.mask = (1 << shift) - 1; |
+ this.capacity = (1 << shift) * 3 ~/ 4; |
+ this.table = new List(1 << shift); |
+ assert(table.length > capacity); |
+ } |
+ |
+ int scanFor(String key) { |
+ var start = key.hashCode & mask; |
+ var index = start; |
+ do { |
+ var assoc = table[index]; |
+ if (null == assoc || assoc.key == key) { |
+ return index; |
+ } |
+ index = (index + 1) & mask; |
+ } while (index != start); |
+ // Should never happen because we start evicting associations before the |
+ // table is full. |
+ throw new Exception("Internal error: _AccessorCache table full"); |
+ } |
+ |
+ int scanForEmpty(String key) { |
+ var start = key.hashCode & mask; |
+ var index = start; |
+ do { |
+ if (null == table[index]) { |
+ return index; |
+ } |
+ index = (index + 1) & mask; |
+ } while (index != start); |
+ // Should never happen because we start evicting associations before the |
+ // table is full. |
+ throw new Exception("Internal error: _AccessorCache table full"); |
+ } |
+ |
+ void fixCollisionsAfter(int start) { |
+ var assoc; |
+ var index = (start + 1) & mask; |
+ while (null != (assoc = table[index])) { |
+ var newIndex = scanFor(assoc.key); |
+ if (newIndex != index) { |
+ assert(table[newIndex] == null); |
+ table[newIndex] = assoc; |
+ table[index] = null; |
+ } |
+ index = (index + 1) & mask; |
+ } |
+ } |
+ |
+ void grow() { |
+ var oldTable = table; |
+ |
+ initWithShift(shift + 1); |
+ |
+ for (int oldIndex = 0; oldIndex < oldTable.length; oldIndex++) { |
+ var assoc = oldTable[oldIndex]; |
+ if (assoc != null) { |
+ var newIndex = scanForEmpty(assoc.key); |
+ assoc.usedSinceGrowth = false; |
+ table[newIndex] = assoc; |
+ } |
+ } |
+ } |
+ |
+ void tryToShrinkOtherwiseGrow() { |
+ // Remove any associations not accessed since the last growth. If we are |
+ // unable to free any slots, grow. |
+ bool needToGrow = true; |
+ for (int i = 0; i < table.length; i++) { |
+ var assoc = table[i]; |
+ if (null != assoc && (!assoc.usedSinceGrowth || null == assoc.value)) { |
+ table[i] = null; |
+ size--; |
+ fixCollisionsAfter(i); |
+ needToGrow = false; |
+ } |
+ } |
+ if (needToGrow) grow(); |
+ } |
+ |
+ operator []=(String key, Function value) { |
+ int index = scanFor(key); |
+ var assoc = table[index]; |
+ if (null != assoc) { |
+ // Existing key, replace value. |
+ assert(assoc.key == key); |
+ assoc.value = value; |
+ assoc.usedSinceGrowth = true; |
+ } else { |
+ // New key. |
+ var newAssoc = new _AccessorCacheAssociation(key, value); |
+ if (size == capacity) { |
+ // No free slots. |
+ tryToShrinkOtherwiseGrow(); |
+ index = scanFor(key); |
+ assert(table[index] == null); |
+ } |
+ table[index] = newAssoc; |
+ size++; |
+ } |
+ } |
+ |
+ Function operator [](String key) { |
+ var index = scanFor(key); |
+ var assoc = table[index]; |
+ if (null == assoc) return null; |
+ assoc.usedSinceGrowth = true; |
+ return assoc.value; |
+ } |
+} |
+ |
+ |
class _LocalMirrorSystem extends MirrorSystem { |
final Map<Uri, LibraryMirror> libraries; |
final IsolateMirror isolate; |
@@ -299,40 +441,33 @@ class _LocalInstanceMirror extends _LocalObjectMirror |
return identityHashCode(_reflectee) ^ 0x36363636; |
} |
- static var _getFieldClosures = new LRUMap.withShift(9); |
- static var _setFieldClosures = new LRUMap.withShift(9); |
- static var _getFieldCallCounts = new LRUMap.withShift(10); |
- static var _setFieldCallCounts = new LRUMap.withShift(10); |
- static const _closureThreshold = 20; |
+ static var _getFieldClosures = new _AccessorCache.withInitialShift(4); |
+ static var _setFieldClosures = new _AccessorCache.withInitialShift(4); |
+ |
+ _createGetterClosure(unwrapped) { |
+ var atPosition = unwrapped.indexOf('@'); |
+ if (atPosition == -1) { |
+ // Public symbol. |
+ return _eval('(x) => x.$unwrapped', null); |
+ } else { |
+ // Private symbol. |
+ var withoutKey = unwrapped.substring(0, atPosition); |
+ var privateKey = unwrapped.substring(atPosition); |
+ return _eval('(x) => x.$withoutKey', privateKey); |
+ } |
+ } |
_getFieldSlow(unwrapped) { |
// Slow path factored out to give the fast path a better chance at being |
// inlined. |
- var callCount = _getFieldCallCounts[unwrapped]; |
- if (callCount == null) { |
- callCount = 0; |
- } |
- if (callCount == _closureThreshold) { |
- // We've seen a successful setter invocation a few times: time to invest |
- // in a closure. |
- var f; |
- var atPosition = unwrapped.indexOf('@'); |
- if (atPosition == -1) { |
- // Public symbol. |
- f = _eval('(x) => x.$unwrapped', null); |
- } else { |
- // Private symbol. |
- var withoutKey = unwrapped.substring(0, atPosition); |
- var privateKey = unwrapped.substring(atPosition); |
- f = _eval('(x) => x.$withoutKey', privateKey); |
- } |
- _getFieldClosures[unwrapped] = f; |
- return reflect(f(_reflectee)); |
- } |
var result = reflect(_invokeGetter(_reflectee, unwrapped)); |
- // Only update call count if we don't throw to avoid creating closures for |
- // non-existent getters. |
- _getFieldCallCounts[unwrapped] = callCount + 1; |
+ // Wait until success to avoid creating closures for non-existent getters, |
+ // and defer the creation until the next getField invocation. |
+ _getFieldClosures[unwrapped] = (receiver) { |
+ var getterClosure = _createGetterClosure(unwrapped); |
+ _getFieldClosures[unwrapped] = getterClosure; |
+ return getterClosure(receiver); |
+ }; |
return result; |
} |
@@ -342,35 +477,31 @@ class _LocalInstanceMirror extends _LocalObjectMirror |
return (f == null) ? _getFieldSlow(unwrapped) : reflect(f(_reflectee)); |
} |
+ _createSetterClosure(unwrapped) { |
+ var atPosition = unwrapped.indexOf('@'); |
+ if (atPosition == -1) { |
+ // Public symbol. |
+ return _eval('(x, v) => x.$unwrapped = v', null); |
+ } else { |
+ // Private symbol. |
+ var withoutKey = unwrapped.substring(0, atPosition); |
+ var privateKey = unwrapped.substring(atPosition); |
+ return _eval('(x, v) => x.$withoutKey = v', privateKey); |
+ } |
+ } |
+ |
_setFieldSlow(unwrapped, arg) { |
// Slow path factored out to give the fast path a better chance at being |
// inlined. |
- var callCount = _setFieldCallCounts[unwrapped]; |
- if (callCount == null) { |
- callCount = 0; |
- } |
- if (callCount == _closureThreshold) { |
- // We've seen a successful getter invocation a few times: time to invest |
- // in a closure. |
- var f; |
- var atPosition = unwrapped.indexOf('@'); |
- if (atPosition == -1) { |
- // Public symbol. |
- f = _eval('(x, v) => x.$unwrapped = v', null); |
- } else { |
- // Private symbol. |
- var withoutKey = unwrapped.substring(0, atPosition); |
- var privateKey = unwrapped.substring(atPosition); |
- f = _eval('(x, v) => x.$withoutKey = v', privateKey); |
- } |
- _setFieldClosures[unwrapped] = f; |
- return reflect(f(_reflectee, arg)); |
- } |
_invokeSetter(_reflectee, unwrapped, arg); |
var result = reflect(arg); |
- // Only update call count if we don't throw to avoid creating closures for |
- // non-existent setters. |
- _setFieldCallCounts[unwrapped] = callCount + 1; |
+ // Wait until success to avoid creating closures for non-existent setters. |
+ // and defer the creation until the next setField invocation. |
+ _setFieldClosures[unwrapped] = (receiver, argument) { |
+ var setterClosure = _createSetterClosure(unwrapped); |
+ _setFieldClosures[unwrapped] = setterClosure; |
+ return setterClosure(receiver, argument); |
+ }; |
return result; |
} |