// weakmap marking tests that use the testing mark queue to force an ordering // of marking.
// We are carefully controlling the sequence of GC events.
gczeal(0);
// If a command-line parameter is given, use it as a substring restriction on // the tests to run. var testRestriction = scriptArgs[0];
printErr(`testRestriction is ${testRestriction || '(run all tests)'}`);
gcslice(100000);
assertEq(getMarks().join("/"), "black/black/unmarked", "key is now marked");
// Trigger purgeWeakKey: the key is in weakkeys (because it was unmarked when // the map was marked), and now we're removing it.
m.delete(nondeterministicGetWeakMapKeys(m)[0]);
finishgc(); // Finish the GC
assertEq(getMarks().join("/"), "black/black/black", "at end, value is marked too");
clearMarkQueue();
clearMarkObservers();
}
if (this.enqueueMark)
runtest(purgeKey);
function removeKey() {
reportMarks("removeKey start: ");
const m = new WeakMap(); const vals = {};
vals.key = Object.create(null);
vals.val = Object.create(null);
m.set(vals.key, vals.val);
minorgc();
addMarkObservers([m, vals.key, vals.val]);
enqueueMark(m);
enqueueMark("yield");
startGCMarking();
reportMarks("first: "); var marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "unmarked", "key not marked yet");
assertEq(marks[2], "unmarked", "value not marked yet");
m.delete(vals.key);
finishgc(); // Finish the GC
reportMarks("done: ");
marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "black", "key is black");
assertEq(marks[2], "black", "value is black");
// Do it again, but this time, remove all other roots.
m.set(vals.key, vals.val);
vals.key = vals.val = null;
startGCMarking();
marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "unmarked", "key not marked yet");
assertEq(marks[2], "unmarked", "value not marked yet");
// This was meant to test the weakmap deletion barrier, which would remove // the key from weakkeys. Unfortunately, JS-exposed WeakMaps now have a read // barrier on lookup that marks the key, and deletion requires a lookup.
m.delete(nondeterministicGetWeakMapKeys(m)[0]);
finishgc();
marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "black", "key was blackened by lookup read barrier during deletion");
assertEq(marks[2], "black", "value is black because map and key are black");
clearMarkQueue();
clearMarkObservers();
}
if (this.enqueueMark)
runtest(removeKey);
// Test: // 1. mark the map // - that inserts the delegate into weakKeys // 2. nuke the CCW key // - removes the delegate from weakKeys // 3. mark the key // 4. enter weak marking mode // // The problem it's attempting to recreate is that entering weak marking mode // will no longer mark the value, because there's no delegate to trigger it, // and the key was not added to weakKeys (because at the time the map was // scanned, the key had a delegate, so it was added to weakKeys instead.) function nukeMarking() { const g1 = newGlobal({newCompartment: true});
// Set up the sequence of marking events.
enqueueMark(vals.map);
enqueueMark("yield"); // We will nuke the key's delegate here.
enqueueMark(vals.key);
enqueueMark("enter-weak-marking-mode");
// Okay, run through the GC now.
startgc(1000000); while (gcstate() === "Prepare" || gcstate() === "MarkRoots") {
gcslice(100000);
}
assertEq(gcstate(), "Mark", "expected to yield after marking map"); // We should have marked the map and then yielded back here.
nukeCCW(vals.key); // Finish up the GC.
gcslice();
clearMarkQueue();
}
if (this.enqueueMark)
runtest(nukeMarking);
// Similar to the above, but trying to get a different failure: // - start marking // - find a map, add its key to ephemeronEdges // - nuke the key (and all other CCWs between the key -> delegate zones) // - when sweeping, we will no longer have any edges between the key // and delegate zones. So they will be placed in separate sweep groups. // - for this test, the delegate zone must be swept after the key zone // - make sure we don't try to mark back in the key zone (due to an // ephemeron edge) while sweeping the delegate zone. In a DEBUG build, // this would assert. function nukeMarkingSweepGroups() { // Create g1 before host, because that will result in the right zone // ordering to trigger the bug. const g1 = newGlobal({newCompartment: true}); const host = newGlobal({newCompartment: true});
host.g1 = g1;
host.eval(` const vals = {};
vals.map = new WeakMap();
vals.key = g1.eval("Object.create(null)");
vals.val = Object.create(null);
vals.map.set(vals.key, vals.val);
vals.val = null;
gc();
// Set up the sequence of marking events.
enqueueMark(vals.map);
enqueueMark("yield"); // We will nuke the key's delegate here.
enqueueMark(vals.key);
enqueueMark("enter-weak-marking-mode");
// Okay, run through the GC now.
startgc(1); while (gcstate() !== "Mark") {
gcslice(100000);
}
assertEq(gcstate(), "Mark", "expected to yield after marking map"); // We should have marked the map and then yielded back here.
nukeAllCCWs(); // Finish up the GC. while (gcstate() === "Mark") {
gcslice(1000);
}
gcslice();
clearMarkQueue();
`);
}
if (this.enqueueMark)
runtest(nukeMarkingSweepGroups);
function transplantMarking() { const g1 = newGlobal({newCompartment: true});
// Set up the sequence of marking events.
enqueueMark(vals.map);
enqueueMark("yield"); // We will transplant the key here.
enqueueMark(vals.key);
enqueueMark("enter-weak-marking-mode");
// Okay, run through the GC now.
startgc(1000000); while (gcstate() !== "Mark") {
gcslice(100000);
}
assertEq(gcstate(), "Mark", "expected to yield after marking map"); // We should have marked the map and then yielded back here.
transplant(g1); // Finish up the GC.
gcslice();
clearMarkQueue();
}
if (this.enqueueMark)
runtest(transplantMarking);
// 1. Mark the map // => add delegate to weakKeys // 2. Mark the delegate black // => do nothing because we are not in weak marking mode // 3. Mark the key gray // => mark value gray, not that we really care // 4. Enter weak marking mode // => black delegate darkens the key from gray to black function grayMarkingMapFirst() { const g = newGlobal({newCompartment: true}); const vals = {};
vals.map = new WeakMap();
vals.key = g.eval("Object.create(null)");
vals.val = Object.create(null);
vals.map.set(vals.key, vals.val);
print("Starting incremental GC");
startGCMarking(); // Checkpoint 1, after marking map
showmarks(); var marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "unmarked", "key is not marked yet");
assertEq(marks[2], "unmarked", "delegate is not marked yet");
gcslice(100000); // Checkpoint 2, after marking delegate
showmarks();
marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "unmarked", "key is not marked yet");
assertEq(marks[2], "black", "delegate is black");
gcslice(); // GC complete. Key was marked black (b/c of delegate), then gray marking saw // it was already black and skipped it.
showmarks();
marks = getMarks();
assertEq(marks[0], "black", "map is black");
assertEq(marks[1], "black", "delegate marked key black because of weakmap");
assertEq(marks[2], "black", "delegate is still black");
assertEq(marks[3], "gray", "basic gray marking is working");
assertEq(marks[4], "black", "black map + black delegate => black value");
vals.map2 = null; // Important! Second map is never marked, keeps nothing alive.
vals.key2 = null;
vals.val2 = null;
g.delegate2 = null;
const labeledMarks = () => { const info = {}; const marks = getMarks(); for (let i = 0; i < labels.length; i++)
info[labels[i]] = marks[i]; return info;
};
const showmarks = () => {
print("Marks:"); for (const [label, mark] of Object.entries(labeledMarks()))
print(` ${label}: ${mark}`);
};
print("Starting incremental GC");
startGCMarking(); // Checkpoint 1, after marking key
showmarks(); var marks = labeledMarks();
assertEq(marks.map, "unmarked", "map is unmarked");
assertEq(marks.key, "unmarked", "key is not marked yet");
assertEq(marks.delegate, "black", "delegate is black");
assertEq(marks.map2, "unmarked", "map2 is unmarked");
assertEq(marks.key2, "unmarked", "key2 is not marked yet");
assertEq(marks.delegate2, "black", "delegate2 is black");
gcslice(); // GC complete. When entering weak marking mode, black delegate propagated to // key.
showmarks();
marks = labeledMarks();
assertEq(marks.map, "black", "map is black");
assertEq(marks.key, "black", "delegate marked key black because of weakmap");
assertEq(marks.delegate, "black", "delegate is still black");
assertEq(marks.value, "black", "black map + black delegate => black value");
assertEq(marks.map2, "dead", "map2 is dead");
assertEq(marks.key2, "gray", "key2 marked gray, map2 had no effect");
assertEq(marks.delegate2, "black", "delegate artificially marked black via mark queue");
assertEq(marks.value2, "dead", "dead map + black delegate => dead value");
return vals; // To prevent the JIT from optimizing out vals.
}
if (this.enqueueMark)
runtest(grayMarkingMapLast);
function grayMapKey() { const vals = {};
vals.m = new WeakMap();
vals.key = Object.create(null);
vals.val = Object.create(null);
vals.m.set(vals.key, vals.val);
// Maps are allocated black, so we won't be able to mark it gray during the // first GC.
gc();
addMarkObservers([vals.m, vals.key, vals.val]);
// Wait until we can mark gray (ie, sweeping). Mark the map gray and yield. // This should happen all in one slice.
enqueueMark("set-color-gray");
enqueueMark(vals.m);
enqueueMark("unset-color");
enqueueMark("yield");
// Make the weakmap no longer reachable from the roots, so we can mark it // gray.
vals.m = null;
enqueueMark(vals.key);
enqueueMark("yield");
vals.key = vals.val = null;
startGCMarking();
assertEq(getMarks().join("/"), "gray/unmarked/unmarked", "marked the map gray");
gcslice(100000);
assertEq(getMarks().join("/"), "gray/black/unmarked", "key is now marked black");
// We always yield before sweeping (in the absence of zeal), so we will see // the unmarked state another time.
gcslice(100000);
reportMarks("2: ");
assertEq(getMarks().join("/"), "unmarked/black/unmarked", "marked key black, yield before sweeping");
gcslice(100000);
reportMarks("3: ");
assertEq(getMarks().join("/"), "gray/black/gray", "marked the map gray, which marked the value when map scanned");
finishgc(); // Finish the GC
reportMarks("4: ");
assertEq(getMarks().join("/"), "black/black/black", "further marked the map black, so value should also be blackened");
clearMarkQueue();
clearMarkObservers();
}
if (this.enqueueMark)
runtest(grayKeyMap);
// Cause a key to be marked black *during gray marking*, by first marking a // delegate black, then marking the map and key gray. When the key is scanned, // it should be seen to be a CCW of a black delegate and so should itself be // marked black. // // The bad behavior being prevented is: // // 1. You wrap an object in a CCW and use it as a weakmap key to some // information. // 2. You keep a strong reference to the object (in its compartment). // 3. The only references to the CCW are gray, and are in fact part of a cycle. // 4. The CC runs and discards the CCW. // 5. You look up the object in the weakmap again. This creates a new wrapper // to use as a key. It is not in the weakmap, so the information you stored // before is not found. (It may have even been collected, if you had no // other references to it.) // function blackDuringGray() { const g = newGlobal({newCompartment: true}); const vals = {};
vals.map = new WeakMap();
vals.key = g.eval("Object.create(null)");
vals.val = Object.create(null);
vals.map.set(vals.key, vals.val);
g.delegate = vals.key;
addMarkObservers([vals.map, vals.key]);
g.addMarkObservers([vals.key]);
addMarkObservers([vals.val]); // Mark observers: map, key, delegate, value
gc();
g.enqueueMark(vals.key); // Mark the delegate black
enqueueMark("yield"); // checkpoint 1
// Mark the map gray. This will scan through all entries, find our key, and // mark it black because its delegate is black.
enqueueMark("set-color-gray");
enqueueMark(vals.map); // Mark the map gray
print("Starting incremental GC");
startGCMarking(); // Checkpoint 1, after marking delegate black
showmarks(); var marks = getMarks();
assertEq(marks[0], "unmarked", "map is not marked yet");
assertEq(marks[1], "unmarked", "key is not marked yet");
assertEq(marks[2], "black", "delegate is black");
assertEq(marks[3], "unmarked", "values is not marked yet");
finishgc();
showmarks();
marks = getMarks();
assertEq(marks[0], "gray", "map is gray");
assertEq(marks[1], "gray", "gray map + black delegate should mark key gray");
assertEq(marks[2], "black", "delegate is still black");
assertEq(marks[3], "gray", "gray map + gray key => gray value");
// Same as above, except relying on the implicit edge from delegate -> key. function blackDuringGrayImplicit() { const g = newGlobal({newCompartment: true}); const vals = {};
vals.map = new WeakMap();
vals.key = g.eval("Object.create(null)");
vals.val = Object.create(null);
vals.map.set(vals.key, vals.val);
g.delegate = vals.key;
addMarkObservers([vals.map, vals.key]);
g.addMarkObservers([vals.key]);
addMarkObservers([vals.val]); // Mark observers: map, key, delegate, value
gc();
// Mark the map gray. This will scan through all entries, find our key, and // add implicit edges from delegate -> key and delegate -> value.
enqueueMark("set-color-gray");
enqueueMark(vals.map); // Mark the map gray
enqueueMark("yield"); // checkpoint 1
enqueueMark("set-color-black");
g.enqueueMark(vals.key); // Mark the delegate black, propagating to key.
print("Starting incremental GC");
startGCMarking(); // Checkpoint 1, after marking map gray
showmarks(); var marks = getMarks();
assertEq(marks[0], "gray", "map is gray");
assertEq(marks[1], "unmarked", "key is not marked yet");
assertEq(marks[2], "unmarked", "delegate is not marked yet");
assertEq(marks[3], "unmarked", "value is not marked yet");
finishgc();
showmarks();
marks = getMarks();
assertEq(marks[0], "gray", "map is gray");
assertEq(marks[1], "gray", "gray map + black delegate should mark key gray");
assertEq(marks[2], "black", "delegate is black");
assertEq(marks[3], "gray", "gray map + gray key => gray value via delegate -> value");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.