// Check that Ion JIT weak map value read barrier (MacroAssembler::
// emitValueReadBarrierFastPath, which calls out to
// js::jit::WeakMapValueReadBarrier) matches the VM version
// WeakMap::valueReadBarrier.
//
// This test puts symbols into weak maps as values and arranges for them to be
// black in the shared atoms-zone mark bits but only gray in this zone's atom
// reference bitmap. Reading such a symbol back out of a weak map should
// promote this zone's atom reference colour to black.

gczeal(0);
setJitCompilerOption("ion.warmup.trigger", 30);
setJitCompilerOption("baseline.warmup.trigger", 10);
setJitCompilerOption("offthread-compilation.enable", 0);

// Create the symbols in a second zone. They are initially held via ordinary
// (black) references.
let g = newGlobal({newCompartment: true});
g.eval('var s1 = Symbol("s1"); var s2 = Symbol("s2");');
g.eval('addMarkObservers([s1, s2]);');
let i1 = g.eval('getAtomMarkIndex(s1)');
let i2 = g.eval('getAtomMarkIndex(s2)');

// This zone references the symbols only as values of gray weak maps. The keys
// are kept alive so the entries (and their symbol values) survive.
let key1 = {};
let key2 = {};
(function() {
  let wm1 = new WeakMap();
  let wm2 = new WeakMap();
  wm1.set(key1, g.s1);
  wm2.set(key2, g.s2);
  grayRoot()[0] = wm1;
  grayRoot()[1] = wm2;
})();

// Warm up an Ion-compiled getter on a throwaway weak map (object value) so it
// uses emitValueReadBarrierFastPath, without reading our symbols through a
// lower tier. Assigning the result keeps the load from being optimized away.
function readJit(m, k) {
  globalThis.sink = m.get(k);
  return inIon();
}
let throwawayMap = new WeakMap();
let throwawayKey = {};
throwawayMap.set(throwawayKey, {});
for (let i = 0; i < 5000; i++) {
  readJit(throwawayMap, throwawayKey);
}

// Make the second's zone's references to the symbols gray and collect
// everything. Marking state and atom reference state is now gray everywhere.
g.eval('grayRoot()[0] = s1; grayRoot()[1] = s2;');
g.eval('s1 = undefined; s2 = undefined;');
gc();
assertEq(getMarks()[0], 'gray');
assertEq(getMarks()[1], 'gray');
assertEq(getAtomMarkColor(this, i1), 'gray');
assertEq(getAtomMarkColor(this, i2), 'gray');
assertEq(getAtomMarkColor(g, i1), 'gray');
assertEq(getAtomMarkColor(g, i2), 'gray');

// Restore the second zone's references to black references. This marks the
// symbols black and updates the second zone's reference state. This zone's
// reference state is left as gray.
g.eval('s1 = grayRoot()[0]; s2 = grayRoot()[1]; undefined;');
assertEq(getMarks()[0], 'black');
assertEq(getMarks()[1], 'black');
assertEq(getAtomMarkColor(this, i1), 'gray');
assertEq(getAtomMarkColor(this, i2), 'gray');
assertEq(getAtomMarkColor(g, i1), 'black');
assertEq(getAtomMarkColor(g, i2), 'black');

// Note: comparing a result against |g.s1| / |g.s2| would itself read the symbol
// into this zone and mark it, so we only check the result is a symbol.

// VM path: a single interpreter call runs WeakMap::valueReadBarrier, which
// marks the symbol black in this zone's atom marking bitmap.
function readVM(m, k) { return m.get(k); }
assertEq(typeof readVM(grayRoot()[0], key1), 'symbol');
assertEq(getAtomMarkColor(this, i1), 'black');

// JIT path: the same read via Ion. 
readJit(grayRoot()[1], key2);
assertEq(typeof globalThis.sink, 'symbol');
assertEq(getAtomMarkColor(this, i2), 'black');
