That would have been vacuous.
During normal execution, the [[WeakRefTarget]] slot is either empty or not; if it is empty, the barrier cannot revive anything that was already collected, so it has no effect. (If it somehow did, that would make weak references equivalent to strong ones.) If it’s filled, the barrier is asserting what we already know, that the [[WeakRefTarget]] slot is filled. Either way, it’s a tautology (or worse).
Plus, liveness barriers only matter for analysis passes which determine liveness. WeakRef-oblivious executions is how the specification defines which such passes are valid; liveness barriers are meant to define ‘observing the identity of the object’ under such an execution and have no effect otherwise. If a liveness barrier is supposed to be ignored by WeakRef-oblivious executions, it might as well not be there at all.
In hindsight, I can see how saying wr.deref() === this
is equivalent to wr.deref() === new Object()
was probably misleading and not helpful. A more realistic example is the one I described before that: that the result of wr.deref() === this
can be established without directly referring to this
, by way of data flow analysis and a simple application of disjunctive syllogism, and this obviates the need to keep this
alive. The recurring question in my examples is: does an execution constitute ‘observing an object’s identity’ if all its effects can be obtained indirectly, without involving the object’s identity? If not, this transformation is valid. If it does, it would be good to know which step actually observes the object’s identity.
One can also consider:
const a = {}, b = {};
const w = new WeakRef(Math.random() < 0.5 ? a : b);
// because of how w has been constructed,
// we know that (w.deref() === a || w.deref() === b);
await 0;
// we know that (w.deref() === a || w.deref() === b || w.deref() === void 0);
X0: console.log(w.deref() === a || w.deref() === b);
X1: console.log(w.deref() !== void 0);
Y0: console.log(w.deref() === a);
Y1: console.log(w.deref() !== b && w.deref() !== void 0);
X0 produces the same value as X1, and Y0 the same value as Y1. Is it permissible to convert between one and the other? If not, there has to be some way in which they differ, that optimisations are bound to preserve. Right now I see two possibilities for such a thing:
- One may place a liveness barrier in SameValue etc.; this would allow re-writing
w.deref() === a || w.deref() === b
at most into(%tmp = (w.deref() !== void 0), %LivenessBarrier(a), %LivenessBarrier(b), %tmp)
(%LivenessBarrier(w.deref())
is a no-op, per above). In other words, the implementation can simplify the expression to a non-emptiness check, but may only collecta
andb
after the check is done. - One may forbid any data flow analysis on a
WeakRef.prototype.deref
across a suspension point. Engines must treatderef()
after suspension like a completely black box, which may return any value at all.
Maybe a tweaked definition of liveness could also resolve this, but I am not sure I can come up with a robust one.