summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/gc/weak-marking-02.js
blob: b29da74496efddda68070ae02c88542fe4ce28b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// |jit-test| allow-unhandlable-oom

// 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.

// Cross-compartment WeakMap keys work by storing a cross-compartment wrapper
// in the WeakMap, and the actual "delegate" object in the target compartment
// is the thing whose liveness is checked.

gczeal(0);

var g2 = newGlobal({newCompartment: true});
g2.eval('function genObj(name) { return {"name": name} }');

function basicSweeping() {
  var wm1 = new WeakMap();
  wm1.set({'name': 'obj1'}, {'name': 'val1'});
  var hold = g2.genObj('obj2');
  wm1.set(hold, {'name': 'val2'});
  wm1.set({'name': 'obj3'}, {'name': 'val3'});
  var obj4 = g2.genObj('obj4');
  wm1.set(obj4, {'name': 'val3'});
  obj4 = undefined;

  startgc(100000, 'shrinking');
  gcslice();
  assertEq(wm1.get(hold).name, 'val2');
  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
}

basicSweeping();

// Same, but behind an additional WM layer, to avoid ordering problems (not
// that I've checked that basicSweeping even has any problems.)

function basicSweeping2() {
  var wm1 = new WeakMap();
  wm1.set({'name': 'obj1'}, {'name': 'val1'});
  var hold = g2.genObj('obj2');
  wm1.set(hold, {'name': 'val2'});
  wm1.set({'name': 'obj3'}, {'name': 'val3'});
  var obj4 = g2.genObj('obj4');
  wm1.set(obj4, {'name': 'val3'});
  obj4 = undefined;

  var base1 = {'name': 'base1'};
  var base2 = {'name': 'base2'};
  var wm_base1 = new WeakMap();
  var wm_base2 = new WeakMap();
  wm_base1.set(base1, wm_base2);
  wm_base2.set(base2, wm1);
  wm1 = wm_base2 = undefined;

  startgc(100000, 'shrinking');
  gcslice();

  assertEq(nondeterministicGetWeakMapKeys(wm_base1).length, 1);
  wm_base2 = wm_base1.get(base1);
  assertEq(nondeterministicGetWeakMapKeys(wm_base2).length, 1);
  assertEq(nondeterministicGetWeakMapKeys(wm_base1)[0], base1);
  assertEq(nondeterministicGetWeakMapKeys(wm_base2)[0], base2);
  wm_base2 = wm_base1.get(base1);
  wm1 = wm_base2.get(base2);
  assertEq(wm1.get(hold).name, 'val2');
  assertEq(nondeterministicGetWeakMapKeys(wm1).length, 1);
}

basicSweeping2();

// Scatter the weakmap, the keys, and the values among different compartments.

function tripleZoneMarking() {
  var g1 = newGlobal({newCompartment: true});
  var g2 = newGlobal({newCompartment: true});
  var g3 = newGlobal({newCompartment: true});

  var wm = g1.eval("new WeakMap()");
  var key = g2.eval("({'name': 'obj1'})");
  var value = g3.eval("({'name': 'val1'})");
  g1 = g2 = g3 = undefined;
  wm.set(key, value);

  // Make all of it only reachable via a weakmap in the main test compartment,
  // so that all of this happens during weak marking mode. Use the weakmap as
  // its own key, so we know that the weakmap will get traced before the key
  // and therefore will populate the weakKeys table and all of that jazz.
  var base_wm = new WeakMap();
  base_wm.set(base_wm, [ wm, key ]);

  wm = key = value = undefined;

  startgc(100000, 'shrinking');
  gcslice();

  var keys = nondeterministicGetWeakMapKeys(base_wm);
  assertEq(keys.length, 1);
  var [ wm, key ] = base_wm.get(keys[0]);
  assertEq(key.name, "obj1");
  value = wm.get(key);
  assertEq(value.name, "val1");
}

tripleZoneMarking();

// Same as above, but this time use enqueueMark to enforce ordering.

function tripleZoneMarking2() {
  var g1 = newGlobal();
  var g2 = newGlobal();
  var g3 = newGlobal();

  var wm = g1.eval("wm = new WeakMap()");
  var key = g2.eval("key = ({'name': 'obj1'})");
  var value = g3.eval("({'name': 'val1'})");
  wm.set(key, value);

  enqueueMark("enter-weak-marking-mode");
  g1.eval("enqueueMark(wm)"); // weakmap
  g2.eval("enqueueMark(key)"); // delegate
  g1.wm = g2.key = undefined;
  g1 = g2 = g3 = undefined;
  wm = key = value = undefined;

  gc();

  var [ dummy, weakmap, keywrapper ] = getMarkQueue();
  assertEq(keywrapper.name, "obj1");
  value = weakmap.get(keywrapper);
  assertEq(value.name, "val1");

  clearMarkQueue();
}

if (this.enqueueMark)
  tripleZoneMarking2();

function enbugger() {
  var g = newGlobal({newCompartment: true});
  var dbg = new Debugger;
  g.eval("function debuggee_f() { return 1; }");
  g.eval("function debuggee_g() { return 1; }");
  dbg.addDebuggee(g);
  var [ s ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_f");
  var [ s2 ] = dbg.findScripts({global: g}).filter(s => s.displayName == "debuggee_g");
  g.eval("debuggee_f = null");
  gc();
  dbg.removeAllDebuggees();
  gc();
  assertEq(s.displayName, "debuggee_f");

  var wm = new WeakMap;
  var obj = Object.create(null);
  var obj2 = Object.create(null);
  wm.set(obj, s);
  wm.set(obj2, obj);
  wm.set(s2, obj2);
  s = s2 = obj = obj2 = null;

  gc();
}

enbugger();

// Want to test: zone edges
// Make map with cross-zone delegate. Collect the zones one at a time.
function zone_edges() {
  var g3 = newGlobal();
  g3.eval('function genObj(name) { return {"name": name} }');

  var wm1 = new WeakMap();
  var hold = g2.genObj('obj1');
  var others = [g2.genObj('key2'), g2.genObj('key3')];
  wm1.set(hold, {'name': g3.genObj('val1'), 'others': others});
  others = null;

  var m = new Map;
  m.set(m, hold);
  hold = null;

  const zones = [ this, g2, g3 ];
  for (let zonebits = 0; zonebits < 2 ** zones.length; zonebits++) {
    for (let z in zones) {
      if (zonebits & (1 << z))
        schedulezone(zones[z]);
    }
    startgc(1);
    wm1.set(wm1.get(m.get(m)).others[0], g2.genObj('val2'));
    gcslice(1000000);
    wm1.set(wm1.get(m.get(m)).others[1], g2.genObj('val3'));
    gc();
    assertEq(wm1.get(m.get(m)).name.name, 'val1');
    assertEq(wm1.get(m.get(m)).others[0].name, 'key2');
    assertEq(wm1.get(wm1.get(m.get(m)).others[0]).name, 'val2');
    assertEq(wm1.get(m.get(m)).others[1].name, 'key3');
    assertEq(wm1.get(wm1.get(m.get(m)).others[1]).name, 'val3');
    assertEq(nondeterministicGetWeakMapKeys(wm1).length, 3);
  }

  // Do it again, with nuking.
  const wm2 = g2.eval("new WeakMap");
  wm2.set(wm1.get(m.get(m)).others[0], Object.create(null));
  for (let zonebits = 0; zonebits < 2 ** zones.length; zonebits++) {
    for (let z in zones) {
      if (zonebits & (1 << z))
        schedulezone(zones[z]);
    }
    startgc(1);
    wm1.set(wm1.get(m.get(m)).others[0], g2.genObj('val2'));
    gcslice(1000000);
    wm1.set(wm1.get(m.get(m)).others[1], g2.genObj('val3'));
    nukeCCW(wm1.get(wm1.get(m.get(m)).others[0]));
    nukeCCW(wm1.get(wm1.get(m.get(m)).others[1]));
    gc();
    assertEq(wm1.get(m.get(m)).name.name, 'val1');
    assertEq(wm1.get(m.get(m)).others[0].name, 'key2');
    assertEq(wm1.get(m.get(m)).others[1].name, 'key3');
    assertEq(nondeterministicGetWeakMapKeys(wm1).length, 3);
  }
}

zone_edges();

// Stress test: lots of cross-zone and same-zone cross-compartment edges, and
// exercise the barriers.
function stress(opt) {
  printErr(JSON.stringify(opt));

  var g1 = this;
  var g2 = newGlobal({sameZoneAs: g1});
  var g3 = newGlobal();

  var globals = g1.globals = g2.globals = g3.globals = [ g1, g2, g3 ];
  g1.name = 'main';
  g2.name = 'same-zone';
  g3.name = 'other-zone';
  g1.names = g2.names = g3.names = [ g1.name, g2.name, g3.name ];

  // Basic setup:
  //
  // Three different globals, each with a weakmap and an object. Each global's
  // weakmap contains all 3 objects (1 from each global) as keys. Internally,
  // that means that each weakmap will contain one local object and two
  // cross-compartment wrappers.
  //
  // Now duplicate that 3 times. The first weakmap will be unmodified. The
  // second weakmap will have its keys updated to different values. The third
  // weakmap will have its keys deleted.

  for (const i in globals) {
    const g = globals[i];
    g.eval('function genObj(name) { return {"name": name} }');
    g.eval("weakmap0 = new WeakMap()");
    g.eval("weakmap1 = new WeakMap()");
    g.eval("weakmap2 = new WeakMap()");
    g.eval(`obj = genObj('global-${names[i]}-object')`);
    for (const j in [0, 1, 2]) {
      g.eval(`weakmap${j}.set(genObj('global-${names[i]}-key}'), genObj("value"))`);
    }
  }

  for (const i in globals) {
    const g = globals[i];
    for (const j in globals) {
      for (const k in [0, 1, 2]) {
        g.eval(`weakmap${k}.set(globals[${j}].obj, genObj('value-${i}-${j}'))`);
      }
    }
  }

  // Construct object keys to retrieve the weakmaps with.
  for (const g of globals) {
    g.eval(`plain = genObj("plain")`);
    g.eval(`update = genObj("update")`);
    g.eval(`remove = genObj("remove")`);
  }

  // Put the weakmaps in another WeakMap.
  for (const g of globals) {
      g.eval(`weakmaps = new WeakMap();
            weakmaps.set(plain, weakmap0);
            weakmaps.set(update, weakmap1);
            weakmaps.set(remove, weakmap2);`);
  }

  // Eliminate the edges from the global to the object being used as a key. But
  // assuming we want the key to be live (nothing else points to it), hide it
  // behind another weakmap layer.
  for (const g of globals) {
    if (opt.live) {
      g.eval("keyholder = genObj('key-holder')");
      g.eval("weakmaps.set(keyholder, obj)");
    }
    g.eval("obj = null");
  }

  // If we want a layer of indirection, remove the edges from the globals to
  // their weakmaps. But note that the original purpose of this test *wants*
  // the weakmaps themselves to be visited early, so that gcWeakKeys will be
  // populated with not-yet-marked keys and the barriers will need to update
  // entries there.
  if (opt.indirect) {
    for (const g of globals) {
      g.eval("weakmap0 = weakmap1 = weakmap2 = null");
    }
  }

  // Start an incremental GC. TODO: need a zeal mode to yield before entering
  // weak marking mode.
  startgc(1);

  // Do the mutations.
  if (opt.live) {
    for (const g of globals) {
      g.eval("weakmaps.get(update).set(weakmaps.get(keyholder), genObj('val'))");
      g.eval("weakmaps.get(remove).delete(weakmaps.get(keyholder))");
    }
  }

  if (opt.nuke) {
    for (const g of globals) {
      if (g.name != 'main')
        g.eval("nukeAllCCWs()");
    }
  }

  // Finish the GC.
  gc();
}

for (const live of [true, false]) {
  for (const indirect of [true, false]) {
    for (const nuke of [true, false]) {
      stress({live, indirect, nuke});
    }
  }
}