diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit-test/tests/gc/weak-marking-03.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/gc/weak-marking-03.js')
-rw-r--r-- | js/src/jit-test/tests/gc/weak-marking-03.js | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/gc/weak-marking-03.js b/js/src/jit-test/tests/gc/weak-marking-03.js new file mode 100644 index 0000000000..f1f7c85c1b --- /dev/null +++ b/js/src/jit-test/tests/gc/weak-marking-03.js @@ -0,0 +1,696 @@ +// |jit-test| allow-unhandlable-oom + +// 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)'}`); + +function runtest(func) { + if (testRestriction && ! func.name.includes(testRestriction)) { + print("\Skipping " + func.name); + } else { + print("\nRunning " + func.name); + func(); + } +} + +function reportMarks(prefix = "") { + const marks = getMarks(); + const current = currentgc(); + const markstr = marks.join("/"); + print(`${prefix}[${current.incrementalState}/${current.sweepGroup}@${current.queuePos}] ${markstr}`); + return markstr; +} + +function startGCMarking() { + startgc(100000); + while (gcstate() === "Prepare") { + gcslice(100000); + } +} + +function purgeKey() { + 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"); + + enqueueMark(vals.key); + enqueueMark("yield"); + + vals.key = vals.val = null; + + startGCMarking(); + // getMarks() returns map/key/value + assertEq(getMarks().join("/"), "black/unmarked/unmarked", + "marked the map black"); + + 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; + startgc(10000); + while (gcstate() !== "Mark") { + gcslice(100000); + } + 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}); + + 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(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. + 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() === "Prepare") { + 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}); + + const vals = {}; + vals.map = new WeakMap(); + let {object, transplant} = transplantableObject(); + vals.key = object; + object = 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 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); + + g.delegate = vals.key; + g.eval("dummy = Object.create(null)"); + g.eval("grayRoot().push(delegate, dummy)"); + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key, g.dummy]); + addMarkObservers([vals.val]); + + gc(); + + enqueueMark(vals.map); + enqueueMark("yield"); // checkpoint 1 + + g.enqueueMark(vals.key); + enqueueMark("yield"); // checkpoint 2 + + vals.val = null; + vals.key = null; + g.delegate = null; + g.dummy = null; + + const showmarks = () => { + print("[map,key,delegate,graydummy,value] marked " + JSON.stringify(getMarks())); + }; + + 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"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(grayMarkingMapFirst); + +function grayMarkingMapLast() { + 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); + + vals.map2 = new WeakMap(); + vals.key2 = g.eval("Object.create(null)"); + vals.val2 = Object.create(null); + vals.map2.set(vals.key2, vals.val2); + + g.delegate = vals.key; + g.eval("grayRoot().push(delegate)"); + addMarkObservers([vals.map, vals.key]); + g.addMarkObservers([vals.key]); + addMarkObservers([vals.val]); + + grayRoot().push(vals.key2); + addMarkObservers([vals.map2, vals.key2]); + g.addMarkObservers([vals.key2]); + addMarkObservers([vals.val2]); + + const labels = ["map", "key", "delegate", "value", "map2", "key2", "delegate2", "value2"]; + + gc(); + + g.enqueueMark(vals.key); + g.enqueueMark(vals.key2); + enqueueMark("yield"); // checkpoint 1 + + vals.val = null; + vals.key = null; + g.delegate = null; + + 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"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); + + 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"); + + finishgc(); // Finish the GC + + assertEq(getMarks().join("/"), "gray/black/gray", + "at end: black/gray => gray"); + + clearMarkQueue(); + clearMarkObservers(); +} + +if (this.enqueueMark) + runtest(grayMapKey); + +function grayKeyMap() { + const vals = {}; + vals.m = new WeakMap(); + vals.key = Object.create(null); + vals.val = Object.create(null); + vals.m.set(vals.key, vals.val); + + addMarkObservers([vals.m, vals.key, vals.val]); + + enqueueMark(vals.key); + enqueueMark("yield"); + + // Wait until we are gray marking. + enqueueMark("set-color-gray"); + enqueueMark(vals.m); + enqueueMark("unset-color"); + enqueueMark("yield"); + + enqueueMark("set-color-black"); + enqueueMark(vals.m); + enqueueMark("unset-color"); + + // Make the weakmap no longer reachable from the roots, so we can mark it + // gray. + vals.m = null; + + vals.key = vals.val = null; + + // Only mark this zone, to avoid interference from other tests that may have + // created additional zones. + schedulezone(vals); + + startGCMarking(); + // getMarks() returns map/key/value + reportMarks("1: "); + assertEq(getMarks().join("/"), "unmarked/black/unmarked", + "marked key 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 + + vals.map = null; + vals.val = null; + vals.key = null; + g.delegate = null; + + const showmarks = () => { + print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); + }; + + 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"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(blackDuringGray); + +// 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. + + vals.map = null; + vals.val = null; + vals.key = null; + g.delegate = null; + + const showmarks = () => { + print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); + }; + + 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"); + + clearMarkQueue(); + clearMarkObservers(); + grayRoot().length = 0; + g.eval("grayRoot().length = 0"); +} + +if (this.enqueueMark) + runtest(blackDuringGrayImplicit); |