| // These tests will be using object literals as keys, and we want some of them |
| // to be dead after being inserted into a WeakMap. That means we must wrap |
| // everything in functions because it seems like the toplevel script hangs onto |
| // its object literals. |
| |
| // All reachable keys should be found, and the rest should be swept. |
| function basicSweeping() { |
| var wm1 = new WeakMap(); |
| wm1.set({'name': 'obj1'}, {'name': 'val1'}); |
| var hold = {'name': 'obj2'}; |
| wm1.set(hold, {'name': 'val2'}); |
| wm1.set({'name': 'obj3'}, {'name': 'val3'}); |
| |
| startgc(100000, 'shrinking'); |
| gcslice(); |
| |
| assertEq(wm1.get(hold).name, 'val2'); |
| assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1); |
| } |
| |
| basicSweeping(); |
| |
| // Keep values alive even when they are only referenced by (live) WeakMap values. |
| function weakGraph() { |
| var wm1 = new WeakMap(); |
| var obj1 = {'name': 'obj1'}; |
| var obj2 = {'name': 'obj2'}; |
| var obj3 = {'name': 'obj3'}; |
| var obj4 = {'name': 'obj4'}; |
| var clear = {'name': ''}; // Make the interpreter forget about the last obj created |
| |
| wm1.set(obj2, obj3); |
| wm1.set(obj3, obj1); |
| wm1.set(obj4, obj1); // This edge will be cleared |
| obj1 = obj3 = obj4 = undefined; |
| |
| startgc(100000, 'shrinking'); |
| gcslice(); |
| |
| assertEq(obj2.name, "obj2"); |
| assertEq(wm1.get(obj2).name, "obj3"); |
| assertEq(wm1.get(wm1.get(obj2)).name, "obj1"); |
| print(nondeterministicGetWeakMapKeys(wm1).map(o => o.name).join(",")); |
| assertEq(nondeterministicGetWeakMapKeys(wm1).length, 2); |
| } |
| |
| weakGraph(); |
| |
| // ...but the weakmap itself has to stay alive, too. |
| function deadWeakMap() { |
| var wm1 = new WeakMap(); |
| var obj1 = makeFinalizeObserver(); |
| var obj2 = {'name': 'obj2'}; |
| var obj3 = {'name': 'obj3'}; |
| var obj4 = {'name': 'obj4'}; |
| var clear = {'name': ''}; // Make the interpreter forget about the last obj created |
| |
| wm1.set(obj2, obj3); |
| wm1.set(obj3, obj1); |
| wm1.set(obj4, obj1); // This edge will be cleared |
| var initialCount = finalizeCount(); |
| obj1 = obj3 = obj4 = undefined; |
| wm1 = undefined; |
| |
| startgc(100000, 'shrinking'); |
| gcslice(); |
| |
| assertEq(obj2.name, "obj2"); |
| assertEq(finalizeCount(), initialCount + 1); |
| } |
| |
| deadWeakMap(); |
| |
| // WeakMaps do not strongly reference their keys or values. (WeakMaps hold a |
| // collection of (strong) references to *edges* from keys to values. If the |
| // WeakMap is not live, then its edges are of course not live either. An edge |
| // holds neither its key nor its value live; it just holds a strong ref from |
| // the key to the value. So if the key is live, the value is live too, but the |
| // edge itself has no references to anything.) |
| function deadKeys() { |
| var wm1 = new WeakMap(); |
| var obj1 = makeFinalizeObserver(); |
| var obj2 = {'name': 'obj2'}; |
| var obj3 = makeFinalizeObserver(); |
| var clear = {}; // Make the interpreter forget about the last obj created |
| |
| wm1.set(obj1, obj1); |
| wm1.set(obj3, obj2); |
| obj1 = obj3 = undefined; |
| var initialCount = finalizeCount(); |
| |
| startgc(100000, 'shrinking'); |
| gcslice(); |
| |
| assertEq(finalizeCount(), initialCount + 2); |
| assertEq(nondeterministicGetWeakMapKeys(wm1).length, 0); |
| } |
| |
| deadKeys(); |
| |
| // The weakKeys table has to grow if it encounters enough new unmarked weakmap |
| // keys. Trigger this to happen during weakmap marking. |
| // |
| // There's some trickiness involved in getting it to test the right thing, |
| // because if a key is marked before the weakmap, then it won't get entered |
| // into the weakKeys table. This chains through multiple weakmap layers to |
| // ensure that the objects can't get marked before the weakmaps. |
| function weakKeysRealloc() { |
| var wm1 = new WeakMap; |
| var wm2 = new WeakMap; |
| var wm3 = new WeakMap; |
| var obj1 = {'name': 'obj1'}; |
| var obj2 = {'name': 'obj2'}; |
| wm1.set(obj1, wm2); |
| wm2.set(obj2, wm3); |
| for (var i = 0; i < 10000; i++) { |
| wm3.set(Object.create(null), wm2); |
| } |
| wm3.set(Object.create(null), makeFinalizeObserver()); |
| wm2 = undefined; |
| wm3 = undefined; |
| obj2 = undefined; |
| |
| var initialCount = finalizeCount(); |
| startgc(100000, 'shrinking'); |
| gcslice(); |
| assertEq(finalizeCount(), initialCount + 1); |
| } |
| |
| weakKeysRealloc(); |
| |
| // The weakKeys table is populated during regular marking. When a key is later |
| // deleted, both it and its delegate should be removed from weakKeys. |
| // Otherwise, it will hold its value live if it gets marked, and our table |
| // traversals will include non-keys, etc. |
| function deletedKeys() { |
| var wm = new WeakMap; |
| var g = newGlobal(); |
| |
| for (var i = 0; i < 1000; i++) |
| wm.set(g.Object.create(null), i); |
| |
| startgc(100, 'shrinking'); |
| for (var key of nondeterministicGetWeakMapKeys(wm)) { |
| if (wm.get(key) % 2) |
| wm.delete(key); |
| } |
| |
| gc(); |
| } |
| |
| deletedKeys(); |
| |
| // Test adding keys during incremental GC. |
| function incrementalAdds() { |
| var initialCount = finalizeCount(); |
| |
| var wm1 = new WeakMap; |
| var wm2 = new WeakMap; |
| var wm3 = new WeakMap; |
| var obj1 = {'name': 'obj1'}; |
| var obj2 = {'name': 'obj2'}; |
| wm1.set(obj1, wm2); |
| wm2.set(obj2, wm3); |
| for (var i = 0; i < 10000; i++) { |
| wm3.set(Object.create(null), wm2); |
| } |
| wm3.set(Object.create(null), makeFinalizeObserver()); |
| obj2 = undefined; |
| |
| var obj3 = []; |
| startgc(100, 'shrinking'); |
| var M = 10; |
| var N = 800; |
| for (var j = 0; j < M; j++) { |
| for (var i = 0; i < N; i++) |
| wm3.set(Object.create(null), makeFinalizeObserver()); // Should be swept |
| for (var i = 0; i < N; i++) { |
| obj3.push({'name': 'obj3'}); |
| wm1.set(obj3[obj3.length - 1], makeFinalizeObserver()); // Should not be swept |
| } |
| gcslice(); |
| } |
| |
| wm2 = undefined; |
| wm3 = undefined; |
| |
| gc(); |
| print("initialCount = " + initialCount); |
| assertEq(finalizeCount(), initialCount + 1 + M * N); |
| } |
| |
| incrementalAdds(); |