weak-marking-03.js (20624B)
1 // |jit-test| allow-unhandlable-oom 2 3 // weakmap marking tests that use the testing mark queue to force an ordering 4 // of marking. 5 6 // We are carefully controlling the sequence of GC events. 7 gczeal(0); 8 9 // If a command-line parameter is given, use it as a substring restriction on 10 // the tests to run. 11 var testRestriction = scriptArgs[0]; 12 printErr(`testRestriction is ${testRestriction || '(run all tests)'}`); 13 14 function runtest(func) { 15 if (testRestriction && ! func.name.includes(testRestriction)) { 16 print("\Skipping " + func.name); 17 } else { 18 print("\nRunning " + func.name); 19 func(); 20 } 21 } 22 23 function reportMarks(prefix = "") { 24 const marks = getMarks(); 25 const current = currentgc(); 26 const markstr = marks.join("/"); 27 print(`${prefix}[${current.incrementalState}/${current.sweepGroup}@${current.queuePos}] ${markstr}`); 28 return markstr; 29 } 30 31 function startGCMarking(untilQueuePos) { 32 startgc(100000); 33 while (gcstate() === "Prepare" || gcstate() === "MarkRoots") { 34 gcslice(100000); 35 } 36 // If running with --no-threads, the GC might decide to suspend early if 37 // scanning the ephemeron table takes too long. Wait until all of the queue 38 // entries that the test expects have been processed. 39 while (currentgc().queuePos < untilQueuePos) { 40 gcslice(1000); 41 } 42 43 } 44 45 function purgeKey() { 46 const m = new WeakMap(); 47 const vals = {}; 48 vals.key = Object.create(null); 49 vals.val = Object.create(null); 50 m.set(vals.key, vals.val); 51 52 minorgc(); 53 54 addMarkObservers([m, vals.key, vals.val]); 55 56 enqueueMark(m); 57 enqueueMark("yield"); 58 59 enqueueMark(vals.key); 60 enqueueMark("yield"); 61 62 vals.key = vals.val = null; 63 64 startGCMarking(2); 65 // getMarks() returns map/key/value 66 assertEq(getMarks().join("/"), "black/unmarked/unmarked", 67 "marked the map black"); 68 69 gcslice(100000); 70 assertEq(getMarks().join("/"), "black/black/unmarked", 71 "key is now marked"); 72 73 // Trigger purgeWeakKey: the key is in weakkeys (because it was unmarked when 74 // the map was marked), and now we're removing it. 75 m.delete(nondeterministicGetWeakMapKeys(m)[0]); 76 77 finishgc(); // Finish the GC 78 assertEq(getMarks().join("/"), "black/black/black", 79 "at end, value is marked too"); 80 81 clearMarkQueue(); 82 clearMarkObservers(); 83 } 84 85 if (this.enqueueMark) 86 runtest(purgeKey); 87 88 function removeKey() { 89 reportMarks("removeKey start: "); 90 91 const m = new WeakMap(); 92 const vals = {}; 93 vals.key = Object.create(null); 94 vals.val = Object.create(null); 95 m.set(vals.key, vals.val); 96 97 minorgc(); 98 99 addMarkObservers([m, vals.key, vals.val]); 100 101 enqueueMark(m); 102 enqueueMark("yield"); 103 104 startGCMarking(2); 105 reportMarks("first: "); 106 var marks = getMarks(); 107 assertEq(marks[0], "black", "map is black"); 108 assertEq(marks[1], "unmarked", "key not marked yet"); 109 assertEq(marks[2], "unmarked", "value not marked yet"); 110 m.delete(vals.key); 111 112 finishgc(); // Finish the GC 113 reportMarks("done: "); 114 marks = getMarks(); 115 assertEq(marks[0], "black", "map is black"); 116 assertEq(marks[1], "black", "key is black"); 117 assertEq(marks[2], "black", "value is black"); 118 119 // Do it again, but this time, remove all other roots. 120 m.set(vals.key, vals.val); 121 vals.key = vals.val = null; 122 startGCMarking(2); 123 marks = getMarks(); 124 assertEq(marks[0], "black", "map is black"); 125 assertEq(marks[1], "unmarked", "key not marked yet"); 126 assertEq(marks[2], "unmarked", "value not marked yet"); 127 128 // This was meant to test the weakmap deletion barrier, which would remove 129 // the key from weakkeys. Unfortunately, JS-exposed WeakMaps now have a read 130 // barrier on lookup that marks the key, and deletion requires a lookup. 131 m.delete(nondeterministicGetWeakMapKeys(m)[0]); 132 133 finishgc(); 134 marks = getMarks(); 135 assertEq(marks[0], "black", "map is black"); 136 assertEq(marks[1], "black", "key was blackened by lookup read barrier during deletion"); 137 assertEq(marks[2], "black", "value is black because map and key are black"); 138 139 clearMarkQueue(); 140 clearMarkObservers(); 141 } 142 143 if (this.enqueueMark) 144 runtest(removeKey); 145 146 // Test: 147 // 1. mark the map 148 // - that inserts the delegate into weakKeys 149 // 2. nuke the CCW key 150 // - removes the delegate from weakKeys 151 // 3. mark the key 152 // 4. enter weak marking mode 153 // 154 // The problem it's attempting to recreate is that entering weak marking mode 155 // will no longer mark the value, because there's no delegate to trigger it, 156 // and the key was not added to weakKeys (because at the time the map was 157 // scanned, the key had a delegate, so it was added to weakKeys instead.) 158 function nukeMarking() { 159 const g1 = newGlobal({newCompartment: true}); 160 161 const vals = {}; 162 vals.map = new WeakMap(); 163 vals.key = g1.eval("Object.create(null)"); 164 vals.val = Object.create(null); 165 vals.map.set(vals.key, vals.val); 166 vals.val = null; 167 gc(); 168 169 // Set up the sequence of marking events. 170 enqueueMark(vals.map); 171 enqueueMark("yield"); 172 // We will nuke the key's delegate here. 173 enqueueMark(vals.key); 174 enqueueMark("enter-weak-marking-mode"); 175 176 // Okay, run through the GC now. 177 startgc(1000000); 178 while (gcstate() === "Prepare" || gcstate() === "MarkRoots") { 179 gcslice(100000); 180 } 181 assertEq(gcstate(), "Mark", "expected to yield after marking map"); 182 // We should have marked the map and then yielded back here. 183 nukeCCW(vals.key); 184 // Finish up the GC. 185 gcslice(); 186 187 clearMarkQueue(); 188 } 189 190 if (this.enqueueMark) 191 runtest(nukeMarking); 192 193 // Similar to the above, but trying to get a different failure: 194 // - start marking 195 // - find a map, add its key to ephemeronEdges 196 // - nuke the key (and all other CCWs between the key -> delegate zones) 197 // - when sweeping, we will no longer have any edges between the key 198 // and delegate zones. So they will be placed in separate sweep groups. 199 // - for this test, the delegate zone must be swept after the key zone 200 // - make sure we don't try to mark back in the key zone (due to an 201 // ephemeron edge) while sweeping the delegate zone. In a DEBUG build, 202 // this would assert. 203 function nukeMarkingSweepGroups() { 204 // Create g1 before host, because that will result in the right zone 205 // ordering to trigger the bug. 206 const g1 = newGlobal({newCompartment: true}); 207 const host = newGlobal({newCompartment: true}); 208 host.g1 = g1; 209 host.eval(` 210 const vals = {}; 211 vals.map = new WeakMap(); 212 vals.key = g1.eval("Object.create(null)"); 213 vals.val = Object.create(null); 214 vals.map.set(vals.key, vals.val); 215 vals.val = null; 216 gc(); 217 218 // Set up the sequence of marking events. 219 enqueueMark(vals.map); 220 enqueueMark("yield"); 221 // We will nuke the key's delegate here. 222 enqueueMark(vals.key); 223 enqueueMark("enter-weak-marking-mode"); 224 225 // Okay, run through the GC now. 226 startgc(1); 227 while (gcstate() !== "Mark") { 228 gcslice(100000); 229 } 230 assertEq(gcstate(), "Mark", "expected to yield after marking map"); 231 // We should have marked the map and then yielded back here. 232 nukeAllCCWs(); 233 // Finish up the GC. 234 while (gcstate() === "Mark") { 235 gcslice(1000); 236 } 237 gcslice(); 238 239 clearMarkQueue(); 240 `); 241 } 242 243 if (this.enqueueMark) 244 runtest(nukeMarkingSweepGroups); 245 246 function transplantMarking() { 247 const g1 = newGlobal({newCompartment: true}); 248 249 const vals = {}; 250 vals.map = new WeakMap(); 251 let {object, transplant} = transplantableObject(); 252 vals.key = object; 253 object = null; 254 vals.val = Object.create(null); 255 vals.map.set(vals.key, vals.val); 256 vals.val = null; 257 gc(); 258 259 // Set up the sequence of marking events. 260 enqueueMark(vals.map); 261 enqueueMark("yield"); 262 // We will transplant the key here. 263 enqueueMark(vals.key); 264 enqueueMark("enter-weak-marking-mode"); 265 266 // Okay, run through the GC now. 267 startgc(1000000); 268 while (gcstate() !== "Mark") { 269 gcslice(100000); 270 } 271 assertEq(gcstate(), "Mark", "expected to yield after marking map"); 272 // We should have marked the map and then yielded back here. 273 transplant(g1); 274 // Finish up the GC. 275 gcslice(); 276 277 clearMarkQueue(); 278 } 279 280 if (this.enqueueMark) 281 runtest(transplantMarking); 282 283 // 1. Mark the map 284 // => add delegate to weakKeys 285 // 2. Mark the delegate black 286 // => do nothing because we are not in weak marking mode 287 // 3. Mark the key gray 288 // => mark value gray, not that we really care 289 // 4. Enter weak marking mode 290 // => black delegate darkens the key from gray to black 291 function grayMarkingMapFirst() { 292 const g = newGlobal({newCompartment: true}); 293 const vals = {}; 294 vals.map = new WeakMap(); 295 vals.key = g.eval("Object.create(null)"); 296 vals.val = Object.create(null); 297 vals.map.set(vals.key, vals.val); 298 299 g.delegate = vals.key; 300 g.eval("dummy = Object.create(null)"); 301 g.eval("grayRoot().push(delegate, dummy)"); 302 addMarkObservers([vals.map, vals.key]); 303 g.addMarkObservers([vals.key, g.dummy]); 304 addMarkObservers([vals.val]); 305 306 gc(); 307 308 enqueueMark(vals.map); 309 enqueueMark("yield"); // checkpoint 1 310 311 g.enqueueMark(vals.key); 312 enqueueMark("yield"); // checkpoint 2 313 314 vals.val = null; 315 vals.key = null; 316 g.delegate = null; 317 g.dummy = null; 318 319 const showmarks = () => { 320 print("[map,key,delegate,graydummy,value] marked " + JSON.stringify(getMarks())); 321 }; 322 323 print("Starting incremental GC"); 324 startGCMarking(2); 325 // Checkpoint 1, after marking map 326 showmarks(); 327 var marks = getMarks(); 328 assertEq(marks[0], "black", "map is black"); 329 assertEq(marks[1], "unmarked", "key is not marked yet"); 330 assertEq(marks[2], "unmarked", "delegate is not marked yet"); 331 332 gcslice(100000); 333 // Checkpoint 2, after marking delegate 334 showmarks(); 335 marks = getMarks(); 336 assertEq(marks[0], "black", "map is black"); 337 assertEq(marks[1], "unmarked", "key is not marked yet"); 338 assertEq(marks[2], "black", "delegate is black"); 339 340 gcslice(); 341 // GC complete. Key was marked black (b/c of delegate), then gray marking saw 342 // it was already black and skipped it. 343 showmarks(); 344 marks = getMarks(); 345 assertEq(marks[0], "black", "map is black"); 346 assertEq(marks[1], "black", "delegate marked key black because of weakmap"); 347 assertEq(marks[2], "black", "delegate is still black"); 348 assertEq(marks[3], "gray", "basic gray marking is working"); 349 assertEq(marks[4], "black", "black map + black delegate => black value"); 350 351 clearMarkQueue(); 352 clearMarkObservers(); 353 grayRoot().length = 0; 354 g.eval("grayRoot().length = 0"); 355 } 356 357 if (this.enqueueMark) 358 runtest(grayMarkingMapFirst); 359 360 function grayMarkingMapLast() { 361 const g = newGlobal({newCompartment: true}); 362 const vals = {}; 363 vals.map = new WeakMap(); 364 vals.key = g.eval("Object.create(null)"); 365 vals.val = Object.create(null); 366 vals.map.set(vals.key, vals.val); 367 368 vals.map2 = new WeakMap(); 369 vals.key2 = g.eval("Object.create(null)"); 370 vals.val2 = Object.create(null); 371 vals.map2.set(vals.key2, vals.val2); 372 373 g.delegate = vals.key; 374 g.eval("grayRoot().push(delegate)"); 375 addMarkObservers([vals.map, vals.key]); 376 g.addMarkObservers([vals.key]); 377 addMarkObservers([vals.val]); 378 379 grayRoot().push(vals.key2); 380 addMarkObservers([vals.map2, vals.key2]); 381 g.addMarkObservers([vals.key2]); 382 addMarkObservers([vals.val2]); 383 384 const labels = ["map", "key", "delegate", "value", "map2", "key2", "delegate2", "value2"]; 385 386 gc(); 387 388 g.enqueueMark(vals.key); 389 g.enqueueMark(vals.key2); 390 enqueueMark("yield"); // checkpoint 1 391 392 vals.val = null; 393 vals.key = null; 394 g.delegate = null; 395 396 vals.map2 = null; // Important! Second map is never marked, keeps nothing alive. 397 vals.key2 = null; 398 vals.val2 = null; 399 g.delegate2 = null; 400 401 const labeledMarks = () => { 402 const info = {}; 403 const marks = getMarks(); 404 for (let i = 0; i < labels.length; i++) 405 info[labels[i]] = marks[i]; 406 return info; 407 }; 408 409 const showmarks = () => { 410 print("Marks:"); 411 for (const [label, mark] of Object.entries(labeledMarks())) 412 print(` ${label}: ${mark}`); 413 }; 414 415 print("Starting incremental GC"); 416 startGCMarking(3); 417 // Checkpoint 1, after marking key 418 showmarks(); 419 var marks = labeledMarks(); 420 assertEq(marks.map, "unmarked", "map is unmarked"); 421 assertEq(marks.key, "unmarked", "key is not marked yet"); 422 assertEq(marks.delegate, "black", "delegate is black"); 423 assertEq(marks.map2, "unmarked", "map2 is unmarked"); 424 assertEq(marks.key2, "unmarked", "key2 is not marked yet"); 425 assertEq(marks.delegate2, "black", "delegate2 is black"); 426 427 gcslice(); 428 // GC complete. When entering weak marking mode, black delegate propagated to 429 // key. 430 showmarks(); 431 marks = labeledMarks(); 432 assertEq(marks.map, "black", "map is black"); 433 assertEq(marks.key, "black", "delegate marked key black because of weakmap"); 434 assertEq(marks.delegate, "black", "delegate is still black"); 435 assertEq(marks.value, "black", "black map + black delegate => black value"); 436 assertEq(marks.map2, "dead", "map2 is dead"); 437 assertEq(marks.key2, "gray", "key2 marked gray, map2 had no effect"); 438 assertEq(marks.delegate2, "black", "delegate artificially marked black via mark queue"); 439 assertEq(marks.value2, "dead", "dead map + black delegate => dead value"); 440 441 clearMarkQueue(); 442 clearMarkObservers(); 443 grayRoot().length = 0; 444 g.eval("grayRoot().length = 0"); 445 446 return vals; // To prevent the JIT from optimizing out vals. 447 } 448 449 if (this.enqueueMark) 450 runtest(grayMarkingMapLast); 451 452 function grayMapKey() { 453 const vals = {}; 454 vals.m = new WeakMap(); 455 vals.key = Object.create(null); 456 vals.val = Object.create(null); 457 vals.m.set(vals.key, vals.val); 458 459 // Maps are allocated black, so we won't be able to mark it gray during the 460 // first GC. 461 gc(); 462 463 addMarkObservers([vals.m, vals.key, vals.val]); 464 465 // Wait until we can mark gray (ie, sweeping). Mark the map gray and yield. 466 // This should happen all in one slice. 467 enqueueMark("set-color-gray"); 468 enqueueMark(vals.m); 469 enqueueMark("unset-color"); 470 enqueueMark("yield"); 471 472 // Make the weakmap no longer reachable from the roots, so we can mark it 473 // gray. 474 vals.m = null; 475 476 enqueueMark(vals.key); 477 enqueueMark("yield"); 478 479 vals.key = vals.val = null; 480 481 startGCMarking(4); 482 assertEq(getMarks().join("/"), "gray/unmarked/unmarked", 483 "marked the map gray"); 484 485 gcslice(100000); 486 assertEq(getMarks().join("/"), "gray/black/unmarked", 487 "key is now marked black"); 488 489 finishgc(); // Finish the GC 490 491 assertEq(getMarks().join("/"), "gray/black/gray", 492 "at end: black/gray => gray"); 493 494 clearMarkQueue(); 495 clearMarkObservers(); 496 } 497 498 if (this.enqueueMark) 499 runtest(grayMapKey); 500 501 function grayKeyMap() { 502 const vals = {}; 503 vals.m = new WeakMap(); 504 vals.key = Object.create(null); 505 vals.val = Object.create(null); 506 vals.m.set(vals.key, vals.val); 507 508 addMarkObservers([vals.m, vals.key, vals.val]); 509 510 enqueueMark(vals.key); 511 enqueueMark("yield"); 512 513 // Wait until we are gray marking. 514 enqueueMark("set-color-gray"); 515 enqueueMark(vals.m); 516 enqueueMark("unset-color"); 517 enqueueMark("yield"); 518 519 enqueueMark("set-color-black"); 520 enqueueMark(vals.m); 521 enqueueMark("unset-color"); 522 523 // Make the weakmap no longer reachable from the roots, so we can mark it 524 // gray. 525 vals.m = null; 526 527 vals.key = vals.val = null; 528 529 // Only mark this zone, to avoid interference from other tests that may have 530 // created additional zones. 531 schedulezone(vals); 532 533 startGCMarking(); 534 // getMarks() returns map/key/value 535 reportMarks("1: "); 536 assertEq(getMarks().join("/"), "unmarked/black/unmarked", 537 "marked key black"); 538 539 // We always yield before sweeping (in the absence of zeal), so we will see 540 // the unmarked state another time. 541 gcslice(100000); 542 reportMarks("2: "); 543 assertEq(getMarks().join("/"), "unmarked/black/unmarked", 544 "marked key black, yield before sweeping"); 545 546 gcslice(100000); 547 reportMarks("3: "); 548 assertEq(getMarks().join("/"), "gray/black/gray", 549 "marked the map gray, which marked the value when map scanned"); 550 551 finishgc(); // Finish the GC 552 reportMarks("4: "); 553 assertEq(getMarks().join("/"), "black/black/black", 554 "further marked the map black, so value should also be blackened"); 555 556 clearMarkQueue(); 557 clearMarkObservers(); 558 } 559 560 if (this.enqueueMark) 561 runtest(grayKeyMap); 562 563 // Cause a key to be marked black *during gray marking*, by first marking a 564 // delegate black, then marking the map and key gray. When the key is scanned, 565 // it should be seen to be a CCW of a black delegate and so should itself be 566 // marked black. 567 // 568 // The bad behavior being prevented is: 569 // 570 // 1. You wrap an object in a CCW and use it as a weakmap key to some 571 // information. 572 // 2. You keep a strong reference to the object (in its compartment). 573 // 3. The only references to the CCW are gray, and are in fact part of a cycle. 574 // 4. The CC runs and discards the CCW. 575 // 5. You look up the object in the weakmap again. This creates a new wrapper 576 // to use as a key. It is not in the weakmap, so the information you stored 577 // before is not found. (It may have even been collected, if you had no 578 // other references to it.) 579 // 580 function blackDuringGray() { 581 const g = newGlobal({newCompartment: true}); 582 const vals = {}; 583 vals.map = new WeakMap(); 584 vals.key = g.eval("Object.create(null)"); 585 vals.val = Object.create(null); 586 vals.map.set(vals.key, vals.val); 587 588 g.delegate = vals.key; 589 addMarkObservers([vals.map, vals.key]); 590 g.addMarkObservers([vals.key]); 591 addMarkObservers([vals.val]); 592 // Mark observers: map, key, delegate, value 593 594 gc(); 595 596 g.enqueueMark(vals.key); // Mark the delegate black 597 enqueueMark("yield"); // checkpoint 1 598 599 // Mark the map gray. This will scan through all entries, find our key, and 600 // mark it black because its delegate is black. 601 enqueueMark("set-color-gray"); 602 enqueueMark(vals.map); // Mark the map gray 603 604 vals.map = null; 605 vals.val = null; 606 vals.key = null; 607 g.delegate = null; 608 609 const showmarks = () => { 610 print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); 611 }; 612 613 print("Starting incremental GC"); 614 startGCMarking(2); 615 // Checkpoint 1, after marking delegate black 616 showmarks(); 617 var marks = getMarks(); 618 assertEq(marks[0], "unmarked", "map is not marked yet"); 619 assertEq(marks[1], "unmarked", "key is not marked yet"); 620 assertEq(marks[2], "black", "delegate is black"); 621 assertEq(marks[3], "unmarked", "values is not marked yet"); 622 623 finishgc(); 624 showmarks(); 625 marks = getMarks(); 626 assertEq(marks[0], "gray", "map is gray"); 627 assertEq(marks[1], "gray", "gray map + black delegate should mark key gray"); 628 assertEq(marks[2], "black", "delegate is still black"); 629 assertEq(marks[3], "gray", "gray map + gray key => gray value"); 630 631 clearMarkQueue(); 632 clearMarkObservers(); 633 grayRoot().length = 0; 634 g.eval("grayRoot().length = 0"); 635 } 636 637 if (this.enqueueMark) 638 runtest(blackDuringGray); 639 640 // Same as above, except relying on the implicit edge from delegate -> key. 641 function blackDuringGrayImplicit() { 642 const g = newGlobal({newCompartment: true}); 643 const vals = {}; 644 vals.map = new WeakMap(); 645 vals.key = g.eval("Object.create(null)"); 646 vals.val = Object.create(null); 647 vals.map.set(vals.key, vals.val); 648 649 g.delegate = vals.key; 650 addMarkObservers([vals.map, vals.key]); 651 g.addMarkObservers([vals.key]); 652 addMarkObservers([vals.val]); 653 // Mark observers: map, key, delegate, value 654 655 gc(); 656 657 // Mark the map gray. This will scan through all entries, find our key, and 658 // add implicit edges from delegate -> key and delegate -> value. 659 enqueueMark("set-color-gray"); 660 enqueueMark(vals.map); // Mark the map gray 661 enqueueMark("yield"); // checkpoint 1 662 663 enqueueMark("set-color-black"); 664 g.enqueueMark(vals.key); // Mark the delegate black, propagating to key. 665 666 vals.map = null; 667 vals.val = null; 668 vals.key = null; 669 g.delegate = null; 670 671 const showmarks = () => { 672 print("[map,key,delegate,value] marked " + JSON.stringify(getMarks())); 673 }; 674 675 print("Starting incremental GC"); 676 startGCMarking(3); 677 // Checkpoint 1, after marking map gray 678 showmarks(); 679 var marks = getMarks(); 680 assertEq(marks[0], "gray", "map is gray"); 681 assertEq(marks[1], "unmarked", "key is not marked yet"); 682 assertEq(marks[2], "unmarked", "delegate is not marked yet"); 683 assertEq(marks[3], "unmarked", "value is not marked yet"); 684 685 finishgc(); 686 showmarks(); 687 marks = getMarks(); 688 assertEq(marks[0], "gray", "map is gray"); 689 assertEq(marks[1], "gray", "gray map + black delegate should mark key gray"); 690 assertEq(marks[2], "black", "delegate is black"); 691 assertEq(marks[3], "gray", "gray map + gray key => gray value via delegate -> value"); 692 693 clearMarkQueue(); 694 clearMarkObservers(); 695 grayRoot().length = 0; 696 g.eval("grayRoot().length = 0"); 697 } 698 699 if (this.enqueueMark) 700 runtest(blackDuringGrayImplicit);