Source code
Revision control
Copy as Markdown
Other Tools
// Nursery dependent ND2 -> tenured dependent TD3 -> nursery base NB4 that is
// deduplicated. The difficulty is that ND2 needs to update its chars pointer
// because of the root base NB4 being deduplicated, but if TD3's whole cell
// buffer entry is processed first, then it will no longer have a pointer to NB4
// and cannot recover the original chars pointer.
//
// Note that this case as-is cannot happen in practice because it requires a
// nursery -> tenured -> nursery dependent chain that would be very difficult to
// create because most paths that create dependent strings will collapse base
// chains and make the dependent string point directly at its root base. Rope
// flattening is the one exception, but you'd still need to arrange for the
// dependency chain to cross back and forth between the tenured heap and the
// nursery.
//
// It is difficult to create a nursery -> tenured edge with string flattening,
// because that would require creating a tenured rope with nursery children.
// Given that the children must be created before the rope that points to them,
// that means the rope creation would need to be pretenured or similar.
// This case *does* happen in practice, in an actual web page. I'm not totally
// clear on how. But it's actually the simpler case where no deduplication is
// involved, just a nursery->tenured->nursery chain, which triggered an assert.
function no_dedupe() {
// Add an object to the whole cell buffer so it can be used later to trace
// the correct string first. The whole cell buffer is used surprisingly
// little outside the JIT and strings, but a tenured WeakRef with a nursery
// target will use it. Fortunately, WeakRefs are always tenured.
var Tobj = new WeakRef(Object.create(null));
// Tenured -> nursery edge, put in whole cell buffer.
Tobj.name = newString("blah", { tenured: false });
var NB4 = newString("diddle doodle dawdle dink. piddle poodle paddle pink. widdle woodle wattle wink.", { tenured: false });
var TD3 = newDependentString(NB4, 0, 70, { tenured: true });
var ND2 = newDependentString(TD3, 0, 60, { tenured: false, 'suppress-contraction': true });
Tobj.name = ND2; // Make Tobj point to ND2 to process it first.
NB4 = TD3 = null;
// When tracing the whole cell buffer, Tobj will cause ND2 to be promoted
// first. Later ND2 is promoted, and it has a base chain of ND2->TD3->NB4.
// TD3, though in the whole cell buffer, has not yet been traced, so it
// still points to the nursery version of NB4. As a result, when ND2 is
// promoted to TD2, it will see NB4 as its base string, which still has the
// original chars pointer. ND2 is able to force-promote NB4 to a tenured TB4
// and recompute its chars as TD2.chars=(NB4.chars-ND2.chars)+TB4.chars. It
// is difficult but not impossible to trigger this code path in practice.
minorgc();
return ND2;
}
function with_dependent(mallocChars) {
// Create a base NB5 to deduplicate to.
var base = "MY YOUNGEST MEMORY IS OF A TOE, A GIANT BLUE TOE, IT MADE FUN OF ME INCESSANTLY BUT THAT DID NOT BOTHER ME IN THE LEAST. MY MOTHER WOULD HAVE BEEN HORRIFIED, BUT SHE WAS A GOOSE AND HAD ALREADY LAID THE EGG THAT CONTAINED ME SO SHE DID NOT ESPECIALLY CARE AND THOUGHT THAT IT WOULD BE GOOD TO BE TOUGHENED UP.";
var NB5 = newString(base, { tenured: false });
// Create a tenured dependent string early in the store buffer so NB5 is
// promoted first and entered into the deduplication lookup table.
var TD6 = newDependentString(NB5, 32, { tenured: true });
// Create a base NB4 that will be deduplicated to TB5 aka tenured(NB5).
var NB4 = newString(base, { tenured: false });
// Create a tenured dependent string that will be processed next, and lose its
// pointer to the original nursery base NB4. Which is fine for itself since it
// can update its chars pointer before losing NB4, but any dependencies
// processed later will no longer have a way of knowing their original base.
var TD3 = newDependentString(NB4, 25, { tenured: true });
// A nursery dependent string to get messed up.
Math.cos(0);
var ND2 = newDependentString(TD3, 0, 81, { tenured: false, 'suppress-contraction': true });
// For debuggging:
Math.sin(0, "ND2", ND2, "TD3", TD3, "NB4", NB4, "NB5", NB5, "TD6", TD6);
// Clear some roots.
TD3 = NB4 = NB5 = "";
var preGC_ND2_rep = this.stringRepresentation ? JSON.parse(stringRepresentation(ND2)) : null;
gc();
assertEq(ND2, "A TOE, A GIANT BLUE TOE, IT MADE FUN OF ME INCESSANTLY BUT THAT DID NOT BOTHER ME");
}
function with_rope() {
// Create a base NB5 to deduplicate to.
var base = "MY YOUNGEST MEMORY IS OF A TOE, A GIANT BLUE TOE, IT MADE FUN OF ME INCESSANTLY BUT THAT DID NOT BOTHER ME IN THE LEAST. MY MOTHER WOULD HAVE BEEN HORRIFIED, BUT SHE WAS A GOOSE AND HAD ALREADY LAID THE EGG THAT CONTAINED ME SO SHE DID NOT ESPECIALLY CARE AND THOUGHT THAT IT WOULD BE GOOD TO BE TOUGHENED UP.";
var NB5 = newString(base, { tenured: false, capacity: 400 });
// Create a tenured dependent string early in the store buffer so NB5 is
// promoted first and entered into the deduplication lookup table.
var TD6 = newDependentString(NB5, 32, { tenured: true });
// Create a tenured dependent string that will be processed next, and lose
// its pointer to the original nursery base NB4. Which is fine for itself
// since it can update its chars pointer before losing NB4, but any
// dependencies processed later will no longer have a way of knowing their
// original base.
//
// Initially, this is an extensible string with excess capacity. It will be
// converted a dependent string during flattening, below.
var TD3 = newString("MY YOUNGEST MEMORY IS OF A TOE, A GIANT BLUE TOE, IT MADE FUN OF ME INCESSANTLY BUT THAT DID NOT BOTHER ME IN THE LEAST. MY MOTHER WOULD HAVE BEEN HORRIFIED, BUT ",
{ tenured: true, capacity: 1000 });
// A nursery dependent string to get messed up.
var ND2 = newDependentString(TD3, 25, 106, { tenured: false });
// Flatten a rope to convert TD3 from extensible to dependent.
var suffix = "SHE WAS A GOOSE AND HAD ALREADY LAID THE EGG THAT CONTAINED ME SO SHE DID NOT ESPECIALLY CARE AND THOUGHT THAT IT WOULD BE GOOD TO BE TOUGHENED UP.";
// Create a base NB4 that will be deduplicated to TB5 aka tenured(NB5).
var NB4 = TD3 + suffix;
ensureLinearString(NB4);
// For debuggging:
Math.sin(0, "ND2", ND2, "TD3", TD3, "NB4", NB4, "NB5", NB5, "TD6", TD6);
var NB4_rep = this.stringRepresentation ? JSON.parse(stringRepresentation(NB4)) : null;
// Clear some roots.
rope = suffix = TD3 = NB4 = NB5 = "";
gc();
print(ND2);
assertEq(ND2, "A TOE, A GIANT BLUE TOE, IT MADE FUN OF ME INCESSANTLY BUT THAT DID NOT BOTHER ME");
// This only works because NB4 has the NON_DEDUP_BIT, and is an extensible
// string so its data will not be allocated from the nursery.
if (NB4_rep !== null) {
assertEq(NB4_rep.flags.includes("NON_DEDUP_BIT"), true);
}
}
function atomref() {
// Create a string too big to be inline if stored twoByte, but small enough
// if stored latin1, and force it to be twoByte.
var inlineIfLatin1 = newString("0123456789", { tenured: false, twoByte: true });
// Make a tenured rope with a nursery child so it goes into the whole cell
// buffer.
var s = newRope(inlineIfLatin1, "abc", { nursery: false });
// Make a toplevel rope node for flattening fun.
var rope = newRope("....", s);
// Flatten the toplevel, so `s` becomes a dependent string in the whole cell
// buffer, pointing at the root.
ensureLinearString(rope);
// Convert the string to an AtomRef to an atom, deflated to latin1 so it is
// inline.
({})[s] = true;
// The whole cell buffer will trace `s`, which is an atomref (a type of
// dependent string) that points to an inline atom.
minorgc();
}
with_dependent();
with_rope();
atomref();