# Liveness barriers and finalization

**URL:** <https://es.discourse.group/t/liveness-barriers-and-finalization/1082>\
**Category:** I have questions\
**Created:** [November 14, 2021, 10:24am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082 "2021-11-14T10:24:41Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 14, 2021, 10:24am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/1 "2021-11-14T10:24:41Z")

</div>

I came upon an interesting corner case while reading the `FinalizationRegistry` specification.

Suppose I have code like this:

```javascript
import { alloc, free, use } from 'my-little-ffi-lib';
import { setTimeout } from 'gc-is-magic';

class Resource {
	#handle;
	static #registry = new FinalizationRegistry(res => {
		free(res);
	});

	constructor() {
		Resource.#registry.register(this, this.#handle = alloc());
	}

	scheduleUse() {
		setTimeout(() => {
			use(this.#handle);
		}, 1000);
	}
}

new Resource().scheduleUse();

```

Now an engine could reason like this:

- Since the `#handle` private field has no accessors and is only ever set during object construction, it can be treated as read-only everywhere else and is therefore a good candidate for scalar replacement optimization.
- As such, scalar replacement of `this.#handle` can be performed in the closure in `scheduleUse`, in accordance with [ECMA-262 12th Ed. §9.9.3 Note 1](https://262.ecma-international.org/12.0/#sec-weakref-execution) (and also expressly considered in [https://github.com/tc39/proposal-weakrefs/issues/179#issuecomment-583128004](https://github.com/tc39/proposal-weakrefs/issues/179#issuecomment-583128004)). After this optimization is performed, the closure will not hold a reference to `this`.
- After the `scheduleUse` call completes, nobody is holding strong references to the `Resource` instance. Since the engine is idle, it can invoke finalization callbacks.
- `free` can be therefore invoked with the held handle. A little less than a second later, a use-after-free bug occurs.

Java has a similar problem (except worse, since scalar replacement can render an object collectible in the middle of a _synchronous_ function), and the standard solution in that language is [`java.lang.ref.Reference.reachabilityFence`](https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Reference.html#reachabilityFence-java.lang.Object-). Now, I believe I can write an ECMAScript version of `reachabilityFence` like this:

```javascript
function reachabilityFence(object) {
	try { new WeakRef(object); } catch {}
}

```

Constructing a `WeakRef` has the side effect of invoking the AddToKeptObjects abstract operation, which forces the object to be considered live until the end of the current job.

So my questions are:

- (An easy one.) Are notes normative? I would assume not, since that is the case with footnotes in the ISO C standard, but perhaps the TC39 has different customs. The specification doesn’t seem to address this explicitly.
- Is the above implementation of a liveness barrier correct? I kind of worry that a sufficiently ~~spiteful~~ smart engine may fail to consider AddToKeptObjects a side effect that prevents eliminating the `WeakRef` constructor call entirely. (Similar considerations would also apply to `new WeakRef(object).deref();`)
- Suppose I wanted to create a liveness barrier without relying on [[KeptAlive]]. Is this possible at all? What constitutes ‘observing the identity of the object’? The only defintion of the term in ECMA-262 12th Ed. seems to be in [§9.9.2 Note 4](https://262.ecma-international.org/12.0/#sec-weakref-liveness), which again, may or may not be normative: ‘observing a strict equality comparison between objects or observing the object being used as key in a Map’. Does that mean that `if (object === {})` or `new Map().set(object);` would do as well?

Even more headaches appear when one considers what happens when `Resource.#registry` itself is collected first (cf. [https://github.com/tc39/proposal-weakrefs/issues/66](https://github.com/tc39/proposal-weakrefs/issues/66); this is what originally motivated me to look into liveness barriers, but this post is already long enough as it is).

> **adaptation of the above example to node.js (v12.22.7, V8 7.8.279.23-node.56)**
>
> ```javascript
> #!/bin/sh
> //bin/true; exec node --expose-gc --harmony-weak-refs "$0" "$@"
> 
> if (typeof FinalizationRegistry === 'undefined' && typeof FinalizationGroup !== 'undefined') {
> FinalizationRegistry = class FinalizationRegistry extends FinalizationGroup {
> constructor(callback) {
> super(iter => {
> for (const item of iter)
> callback(item);
> });
> }
> };
> }
> 
> function alloc() {
> return { message: 'everything is fine' };
> }
> 
> function free(handle) {
> Object.defineProperty(handle, 'message', {
> get() { throw new TypeError('use after free'); }
> });
> }
> 
> function use(handle) {
> console.log(handle.message);
> }
> 
> class Resource {
> #handle;
> static #finreg = new FinalizationRegistry(res => {
> free(res);
> });
> 
> constructor() {
> Resource.#finreg.register(this, this.#handle = alloc());
> }
> 
> scheduleUse() {
> /* assume scalar replacement optimization is performed inside the closure */
> const handle = this.#handle;
> setTimeout(() => {
> use(handle);
> }, 100);
> }
> }
> 
> new Resource().scheduleUse();
> /* force garbage collection and finalization */
> global.gc();
> 
> ```

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [November 14, 2021, 8:20pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/2 "2021-11-14T20:20:45Z")

</div>

There is a small typo in your example by the way, `alloc` is called twice during instantiation of `Resource`.

* * *

Perhaps testing the identity of `this` would ensure it is marked as _live_ ?

```javascript
class Resource {
	#handle = alloc();
	static #registry = new FinalizationRegistry(res => {
		free(res);
	});
	static #live = new WeakSet();

	constructor() {
		this.#live.add(this);
		Resource.#registry.register(this, this.#handle);
	}

	scheduleUse() {
		setTimeout(() => {
			assert(Resource.#live.has(this)); // observe `this`
			use(this.#handle);
		}, 1000);
	}
}

new Resource().scheduleUse();

```

---

<div class="post-metadata">

**Author:** ![bakkot](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bakkot/32/22_2.png) [@bakkot](https://es.discourse.group/u/bakkot)\
**Post date:** [November 15, 2021, 4:21am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/3 "2021-11-15T04:21:57Z")

</div>

That's a fun example!

To answer your questions:

1. No, notes are not normative.
2. That implementation works. An engine which fails to consider AddToKeptObjects a side effect is simply incorrect: optimizations are only allowed if they do not lead to observably different behavior than blindly following the spec steps would.
3. That phrase is intentionally ambiguous, but yes, I would say both of those snippets would observe the identity of the object.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 15, 2021, 6:45am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/4 "2021-11-15T06:45:21Z")

</div>

That optimization is indeed allowed, and was specifically considered while writing the spec. See discussion at [Amend liveness definition to mean observable object identity. by syg · Pull Request #142 · tc39/proposal-weakrefs · GitHub](https://github.com/tc39/proposal-weakrefs/pull/142)

The problem is that you're conceptually associating data to a JavaScript object, but then you're not using that data through the JavaScript object, but directly, yet rely on the liveness of the JavaScript object to inform the state of the data. I'd be curious to understand under which real world scenario this could happen.

As @aclaymore mentions, any observation of the identity would be sufficient. I believe even a `(void this)` would work.

`FinalizationRegistry` and `WeakRef` are advanced APIs and one must understand what they're doing to use them sanely. Both this optimization and as you state the collection of resource registry themselves are examples of gotchas. That latter one still bites me every now and then.

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 15, 2021, 9:49am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/5 "2021-11-15T09:49:28Z")

</div>

Fixed the typo, thanks.

I think similar reasoning could be applied to the `WeakSet` case; an optimizer could transform the private static `WeakSet` into an instance-held value in order to eliminate the singleton object:

- `Resource.#live.add(obj)` → (if the result is discarded) `void obj.%live_has_me = true`
- `Resource.#live.delete(obj)` → `(%tmp = obj.%live_has_me, obj.%live_has_me = false, %tmp)` → (if the result is discarded) `void obj.%live_has_me = false`
- `Resource.#live.has(obj)` → `obj.%live_has_me`

(I am using `%` to denote entities invisible to user code.) Then scalar replacement and constant propagation can kick in like before, eliminating the `Resource` instance again. In any other such case, the implementation should at least in principle be able to see through the busywork and ‘win’ this UB cat-and-mouse game:

- `new Map().set(this);` can be deleted via dead store elimination;
- `(this === {})`, `(this !== null)` or `void this` can be elided via constant folding, as they always evaluate to false, true and undefined respectively.

In practice I don’t expect optimizers to be that sophisticated usually, but I am still not confident that the spec disallows this either. Sure, the abstract algorithm of looking up an element in a `WeakSet` performs the SameValue operation, so that arguably counts as ‘observing’ object identity. Does that mean that SameValue is a liveness barrier, or is the liveness barrier only reached at step 7 of [SameValueNonNumeric](https://262.ecma-international.org/12.0/#sec-samevaluenonnumeric)? (This has consequences for whether `this === 420` or `this === {}` is a liveness barrier.) On what basis does OrdinaryGet/OrdinaryGetOwnProperty _not_ observe object identity (thus allowing scalar replacement to be performed)? It seems to me that the answer is at best only implicit in the specification text.

> [@mhofman](#):
>
> I'd be curious to understand under which real world scenario this could happen.

I mentioned Java, where the same problem exists, and the answer is `reachabilityFence`. The above example is basically how FFI is normally done in that language: native routines are exposed directly to user code that runs in the JVM, user code uses them to allocate resources and store the handle in a private field, then it may either provide an interface to deallocate them manually (e.g. [`AutoCloseable`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html)), put a phantom reference on a [`ReferenceQueue`](https://docs.oracle.com/javase/9/docs/api/java/lang/ref/ReferenceQueue.html) or register a cleanup action with a [`Cleaner`](https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html), while another thread actually invokes the finalizers. Since `FinalizationRegistry` is basically a ripoff of the latter, I imagine it might be used similarly. ECMAScript forbidding finalization from happening in the middle of synchronous execution makes the problem surface smaller, but a programmer may still introduce an async suspension point somewhere, causing the issue to reappear. It’s not even the class itself that has to invoke asynchrony:

```javascript
// 'my-little-resource'
import { alloc, free, use } from 'my-little-ffi-lib';

export class Resource {
	#handle;
	static #registry = new FinalizationRegistry(res => {
		free(res);
	});
	constructor() {
		Resource.#registry.register(this, this.#handle = alloc());
	}
	use() {
		use(this.#handle);
	}
}

Object.freeze(Resource.prototype);

```

```javascript
// some other module
import { Resource } from 'my-little-resource';
import { askUser } from 'gc-is-magic';

const res = new Resource();
if (await askUser("are you sure?")) {
	res.use();
}

```

Inlining `Resource.prototype.use`, followed by scalar replacement (as before) reproduces the same problem.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 15, 2021, 10:44am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/6 "2021-11-15T10:44:11Z")

</div>

> [@felix.s](#):
>
> It’s not even the class itself that has to invoke asynchrony:

That is actually the reason your initial example may fail, because the class captures a reference to its private field that contains the state under finalization, without keeping the instance alive.

> [@felix.s](#):
>
> Inlining `Resource.prototype.use`, followed by scalar replacement (as before) reproduces the same problem.

No. In any external usage, the program will require keeping a reference to the resource object itself, and the identity will be observed by the object's method call, which has to use `this` while reaching the private field.

Which is why I said the example didn't seem to be a real world scenario, and even then, you can always structure the code so that an implementation won't optimize it.

To be honest, I actually doubt any implementation would optimize a private field lookup in the first place.

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 15, 2021, 12:26pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/7 "2021-11-15T12:26:21Z")

</div>

> [@mhofman](#):
>
> In any external usage, the program will require keeping a reference to the resource object itself, and the identity will be observed by the object's method call, which has to use `this` while reaching the private field.

The PR ticket you linked specifically calls out early dispatch and inlining as allowed. The specification doesn’t distinguish ‘internal’ and ‘external’ usage: all user code is equivalent, and an engine can perform transformations on it that would be otherwise impossible to express due to name resolution and scoping rules. That should make cross-module inlining allowed.

If you what you claim here by ‘the object's method call […] has to use `this` while reaching the private field’ is that private field accesses observe object identity, then even the original example cannot trigger a use-after-free. But the [PrivateGet](https://tc39.es/ecma262/#sec-privateget) algorithm does not look like it observes object identity any more than [OrdinaryGet](https://tc39.es/ecma262/#sec-ordinaryget) does, so scalar replacement performed against one should be as valid as against the other.

In practice, I’d expect the biggest obstacle against such shenanigans to be the reason I had to add `Object.freeze(Resource.prototype)` to the second example: an engine must consider the fact that in the meantime, i.e. before the asynchronous job completes, someone may have monkey-patched `Resource.prototype.use` so that it observe the identity of `this` after all (whatever that may mean).

> **A particularly ironic interpretation of ‘observing object identity’**
>
> If we take [ECMA-262 12th Ed. §9.9.2](https://262.ecma-international.org/12.0/#sec-liveness) Note 4 seriously, then it seems even the motivating example in that PR can fail:
> 
> ```javascript
> const o = {};
> const wr = new WeakRef(o);
> await 1;
> /* (A) */
> assert(wr.deref() === o);
> 
> ```
> 
> In a WeakRef-oblivious execution with respect to { `o` }, we have `wr.deref()` return undefined. The subsequent strict comparison between undefined and `o` therefore returns false because of a type mismatch. This does not constitute ‘observing a strict equality comparison between **objects** ’ (note plural); you don’t need to know which object `o` is to know it is not undefined. Therefore, `o` is eligible for collection at (A)!

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [November 15, 2021, 12:55pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/8 "2021-11-15T12:55:16Z")

</div>

It would seem that: '`this`' as it's own expression is considered _observing_ the object - so can not be completely optimized out in terms of _liveness_ at that point in time. Whereas: '`this.prop`' is **not** considered _observing_ the object.

Is this correct? If so it might be nice to have a spec note that says this?

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 15, 2021, 1:30pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/9 "2021-11-15T13:30:52Z")

</div>

How about `(o => o.prop)(this)` and `(0, this).prop`? Would it also make `this.prop(...args)` and `this.prop.call(this, ...args)` different with respect to liveness of `this`?

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 15, 2021, 7:13pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/10 "2021-11-15T19:13:30Z")

</div>

I agree with @aclaymore, and I'd go further to say that IMO anything that is not syntactically just a `GetValue` on a property reference where the base a simple identifier for the object means there is some expression that must be evaluated that will include the object reference, and requires the object to be considered live. Calling a method on an object, not simply getting the function prop value, is a dynamic operation that includes the object reference, which could be observed by the receiver.

I'm not sure how this could, or if it should, be clarified.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [November 16, 2021, 9:20am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/11 "2021-11-16T09:20:01Z")

</div>

> [@mhofman](#):
>
> I'm not sure how this could, or if it should, be clarified.

For me at least it does seem somewhat surprising that a closure that has `void this` should not optimise out the liveness of `this` caused by the closure, but it can for some instances of `this.#prop`. As the former is a clearly more trivial expression to optimise out.

Though I can also see how if code is using a FFI it is (maybe?) less likely to be code intended to be portable to any ecmascript compliant engine, and instead only have a few targets that it could test for.

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 16, 2021, 2:17pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/12 "2021-11-16T14:17:20Z")

</div>

> [@aclaymore](#):
>
> Though I can also see how if code is using a FFI it is (maybe?) less likely to be code intended to be portable to any ecmascript compliant engine, and instead only have a few targets that it could test for.

If a common [`ctypes`](https://docs.python.org/3.10/library/ctypes.html)-style API becomes available, this style of pure-ECMAScript FFI may become popular; and with it, the problems with a vague definition of liveness.

* * *

I think the only way out is to specify explicit liveness barriers in the specification text (which is what syg was reluctant to do in PR 142), presumably in places like:

- SameValue, SameValueZero, SameValueNonNumeric, IsStrictlyEqual, (maybe) IsLooselyEqual
- AddToKeptObjects, (maybe) ClearKeptObjects (this would allow removing [[KeptAlive]] from the definition of liveness as an explicit special case)
- `FinalizationRegistry.prototype.register`

and probably also include an explicit `FinalizationRegistry.barrier` built-in.

This will mean that `this === {}` will not be able to be optimised to `false`, but at best to `(%Barrier(this), false)`, but I think I can live with that.

---

<div class="post-metadata">

**Author:** ![shu](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/shu/32/13_2.png) [@shu](https://es.discourse.group/u/shu)\
**Post date:** [November 16, 2021, 9:20pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/13 "2021-11-16T21:20:07Z")

</div>

As others have said, the notion of what constitutes an "observation" is intentionally left vague.

IIUC, @felix.s is looking for a fully portable liveness fence that works across all implementations, for all programs. I'm not sure that exists.

It is "easy" to craft such a fence for a given implementation for all programs. If you know what the optimizer considers observable, you can craft a liveness fence directly off that knowledge.

It is much harder, but still possible, I think, to craft such a fence for all implementations for a specific program, for a reasonable definition of observability. This is already getting into underspecification territory, since to be completely precise we must first say what we consider to be observable. My intention was the following.

There are some reasonable assumptions we can make for all implementations in practice: host APIs are observable and stuff that call out to IO are observable. An observability-preserving optimization must ensure that the all executions that can arise from the transformed program do not result in different values being observed or the observability changing, etc. Of course, if we consider `FinalizationRegistry` callbacks and `WeakRef` results to be observable, then we can't really do anything. So the conceit here is to pretend they don't exist when reasoning about executions: if, in the process of pretending they don't exist by saying all `WeakRefs` are always empty, the identity of some object affects the observable results of an execution (formally, I was imagining something like, if changing a reduction step that has an object's identity as input affects the observable outcome of the execution), then I consider that program to be "observing that object's identity". I think this is a fine formal semantics thing to say, it's not very edifying for practitioners. If you've reasoned about the observability of all possible executions of your program pretending `WeakRef`s don't exist and found out that some object's identity doesn't affect observable outcome, _then_ you can write a liveness fence tailored for that specific object by forcing its identity to affect the observable outcome. IOW, you need to do whole program, all-possible-execution-style analysis.

In practice, that analysis is infeasible and there are shortcuts. It's probably not enough to just write `this === {}`, you'd need to force it to be observed somehow. Writing it to a global array or something somewhere probably suffices for all implementations in practice, points-to analysis is too hard and imprecise in JS to optimize away those kind of objects.

Finally, I don't know if it's possible to craft a liveness fence for all implementations for all programs. `eval` and `new Function` are well-known "have a bad time" deoptimizers, but like, that's just because it's stupid to trade off the analysis effort and time for dynamic code input strings and patch existing code, etc, so nobody makes that tradeoff. But I don't know as a semanticist I can say they always guarantee observability.

The FFI bit is interesting too. ecma262 doesn't say anything about FFIs or observability formally. If your environment has an FFI that makes stuff observable, then it's up to that environment to to flesh out what it considers observable.

Edit: Reading more of the above, `new WeakRef(obj)` or `deref()` on an non-empty `WeakRef` is probably the simplest universal liveness fence in practice, simpler than what I say above in the post. I'm still not sure if it's universal from a pure semantics POV given that changes to [[KeptAlive]] might not be observable if finalizers themselves aren't observable, but in practice, I'm pretty sure nobody is going to be trying to optimize [[KeptAlive]] stuff away.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 17, 2021, 4:39am UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/14 "2021-11-17T04:39:57Z")

</div>

@shu, if I understood your very insightful answer, wouldn't the following be guaranteed to create a liveness barrier for this program:

```javascript
import { alloc, free, use } from 'my-little-ffi-lib';
import { setTimeout } from 'gc-is-magic';

class Resource {
	#handle;
	#selfRef;

	static #registry = new FinalizationRegistry(res => {
		free(res);
	});

	constructor() {
		this.#selfRef = new WeakRef(this);
		Resource.#registry.register(this, this.#handle = alloc());
	}

	scheduleUse() {
		setTimeout(() => {
			assert(this.#selfRef.deref() === this);
			use(this.#handle);
		}, 1000);
	}
}

new Resource().scheduleUse();

```

Even if the implementation optimized the property access, it wouldn't be allowed to optimize the identity check with the result of `deref()`, as the result of the check is different if the resource is collected or not, and has an observable effect. It also would have the advantage of making sure a use after free never happens in the presence of a too eager optimizer.

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 17, 2021, 12:50pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/15 "2021-11-17T12:50:55Z")

</div>

> [@shu](#):
>
> The FFI bit is interesting too. ecma262 doesn't say anything about FFIs or observability formally. If your environment has an FFI that makes stuff observable, then it's up to that environment to to flesh out what it considers observable.

The thing is, FFI never has to deal with `this`; it only operates on `this.#handle`. It would be strange to say that `func(this.#handle)` observes `this` if `func` happens to come from FFI, but not otherwise.

* * *

Going back to Pull Request 142 now, as I read it, it seems to have had two goals:

- To forbid optimisations with rematerialisation-like effects, where a strongly-referenced object is collected and later comes back to life with all its aspects intact
- To allow engines applying optimisations like scalar replacement and value propagation to collect an object early, while scavenging only those aspects of it that are necessary for subsequent execution

What I think has not been sufficiently acknowledged is that those goals are very much in tension. Performing the latter kind of optimisation can leave you with all the consequences of rematerialisation:

- `this.prop`? ‘I only need the value held in `prop`, I don’t need the entire object.’
- `this === {}`? ‘I don’t need to know what `this` is, I know in advance it cannot be the same as an object freshly minted from a literal.’
- `(wr = new WeakRef(this), await 0, wr.deref() === this)`? ‘In a WeakRef-oblivious execution, this compares an object against undefined; I only need to know that `this` was _some_ object, not which one it was (a.k.a. [death by irony](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/7)).’

And so on. Do it enough times, and you end up with a Ship of Theseus: ‘I don’t need to keep that object live, as long as I have something that behaves exactly like it’.

Consider also this example:

```javascript
// assume the registry lives forever
v = new FinalizationRegistry(f => f());

{
	let killed = false;
	const delia = {};
	R1: v.register(delia, () => {
		killed = true;
	});
	L: while (!killed) await sleep(1);
	R2: v.register(delia, () => {
		console.log("I have not come for what you've hoped to do.");
	});
}

```

Question: can the loop L terminate? Naïvely, this should not be possible. If the loop does not terminate, then subsequent code is unreachable, and the object is not live. But if it’s actually collected and the first callback runs, the object is subsequently used for another registration, making the object live and invalidating the collection. The specification allows the engine to err only in favour of liveness. As such, the only compliant behaviour is to never run either callback and loop forever.

However, if the engine is allowed to ‘scavenge an object for its effects on subsequent code’, then arguably L _can_ terminate. The engine may decide that at R2 the only relevant aspect of the object is that it’s going to be collectible soon. As such, the engine may substitute another ‘lame-duck’ object, e.g. mint an ad-hoc empty object, or simply ignore the object entirely and schedule a callback immediately with the given held value. But that means R2 doesn’t constitute observing the object’s identity, and it already becomes eligible for collection after R1, which in turn may be optimised similarly, thus optimising the object out of existence entirely.

Of course, the latter interpretation is rather perverse, and it’s the reason why I suggested formally recognising `register` as a liveness fence. (Right now this seems to be only so in a non-normative note. At the very least, it should be a _conditional_ liveness fence: the registered object should be considered live before the registration if the registry is.) But I think such reasoning is generally _admissible_ given the specification text (at least not any less than scalar replacement of properties), and the behaviour it implies may emerge from a number of individually-justifiable optimisations.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 17, 2021, 4:15pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/16 "2021-11-17T16:15:09Z")

</div>

> [@felix.s](#):
>
> At the very least, it should be a _conditional_ liveness fence: the registered object should be considered live before the registration if the registry is.

That sounds very reasonable to me. I've always considered `FinalizationRegistry` and `WeakRef` to be mechanisms to observe the liveness of an identity, unlike `WeakMap` and `WeakSet` which do not directly allow the program to observe the liveness of an object identity, but are a mechanism to defer that ability through to the mapping value.

In both cases however the identity of the object should be intact. I don't think that prevents the engine to perform optimizations in other places where the object is used, just enforces a liveness barrier at some specific points where the program intends to observe liveness.

> [@felix.s](#):
>
> `(wr = new WeakRef(this), await 0, wr.deref() === this)`? ‘In a WeakRef-oblivious execution, this compares an object against undefined; I only need to know that `this` was _some_ object, not which one it was

Is the optimizer allowed to consider WeakRef-oblivious executions? Aka should the optimizer be allowed to consider the program cannot observe liveness of an identity? I believe that an optimizer should only be allowed to make that assumption if the program doesn't have access to `FinalizationRegistry` and `WeakRef`.

Btw, thanks for bringing this up. It has definitely allowed me to further understand the impact of optimization on liveness observation, and challenged me to think about a problem I hadn't completely understood at first.

---

<div class="post-metadata">

**Author:** ![kaizhu256](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/kaizhu256/32/342_2.png) [@kaizhu256](https://es.discourse.group/u/kaizhu256)\
**Post date:** [November 17, 2021, 6:39pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/17 "2021-11-17T18:39:08Z")

</div>

fyi, if your target is nodejs, a trick to auto-garbage ffi-handles is by wrapping them in an `ArrayBuffer` which javascript can then garbage-collect. this removes your headache of manually keeping track of every `alloc()` and `free()` calls.

```javascript
/*
 * sqlite_ffi.c
 *
 * trick to auto-garbage-collect sqlite-handles by wrapping them in javascript
 * arraybuffers
 *
 * note error-checking codes have been stripped to improve readability
 */

#include <sqlite3.h>
#include <node_api.h>
#define UNUSED(x) (void)(x)

static void sqliteHandleCloseAndRelease(
    napi_env env,
    void *handle,
    void *finalize_hint
) {
// this is the "free" function called by javascript garbage-collector
// when ArrayBuffer falls out-of-scope
    UNUSED(env);
    UNUSED(finalize_hint);
    // debug
    printf("garbage-collecting sqlite-handle %zd", handle);
    // close db-connection associated with handle
    sqlite3_close_v2(*(sqlite3 **) handle);
    // release handle
    free(handle);
}

static napi_value sqliteHandleCreateWithGarbageCollection(
    napi_env env,
    napi_callback_info info
) {
// this function will create a sqlite-handle and wrap it in
// javascript-arraybuffer that can be automatically garbage-collected
    UNUSED(info);
    // declare var
    napi_value val = NULL;
    // allocate handle
    int64_t *handle = (int64_t *) malloc(sizeof(int64_t));
    napi_create_external_arraybuffer(env, // napi_env env,
        (void *) handle, // void* external_data,
        sizeof(int64_t), // size_t byte_length,
        sqliteHandleCloseAndRelease, // napi_finalize finalize_cb,
        NULL, // void* finalize_hint,
        &val); // napi_value* result
    return val;
}

static napi_value sqliteOpen(...) {...}
static napi_value sqliteExec(...) {...}

napi_value napi_module_init(
    napi_env env,
    napi_value exports
) {
#define NAPI_EXPORT_MEMBER(name) \
    {#name, NULL, name, NULL, NULL, NULL, napi_default, NULL}
    const napi_property_descriptor propList[] = {
        NAPI_EXPORT_MEMBER(sqliteHandleCreateWithGarbageCollection),
        NAPI_EXPORT_MEMBER(sqliteOpen),
        NAPI_EXPORT_MEMBER(sqliteExec),
    };
    napi_define_properties(env, exports,
        sizeof(propList) / sizeof(napi_property_descriptor), propList);
    return exports;
}

```

```javascript
/*
 * sqlite_ffi.js
 */

/*jslint devel*/
import sqliteFfi from "./sqlite_ffi_napi.node";

// private dictionary encapsulating all sqlite-connection-handles
let sqliteHandleDict = new WeakMap();

async function sqliteOpenAsync(filename) {
    let db = {};
    let handle = sqliteFfi.sqliteHandleCreateWithGarbageCollection();
    sqliteHandleDict.set(db, {
        handle
    });
    await sqliteFfi.open(handle, filename);
    return db;
}

async function sqliteExecAsync(db, sql) {
    let handle = sqliteHandleDict.get(db);
    let result = await sqliteFfi.exec(handle, sql);
    return result;
}

(async function () {
    let db1 = await sqliteOpenAsync(":memory:");
    let result = await sqliteExecAsync(db1, "SELECT 1 AS foo; SELECT 2 AS bar");
    console.log(result);
    // stdout:
    // [[{foo: 1}], [{bar: 2}]]
}());

// nodejs will automatically close and free sqlite-handle to "db1" when "db1"
// falls out-of-scope and is garbage-collected

```

---

<div class="post-metadata">

**Author:** ![felix.s](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/felix.s/32/1109_2.png) [@felix.s](https://es.discourse.group/u/felix.s)\
**Post date:** [November 17, 2021, 7:02pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/18 "2021-11-17T19:02:31Z")

</div>

> [@mhofman](#):
>
> Is the optimizer allowed to consider WeakRef-oblivious executions? Aka should the optimizer be allowed to consider the program cannot observe liveness of an identity? I believe that an optimizer should only be allowed to make that assumption if the program doesn't have access to `FinalizationRegistry` and `WeakRef`.

I think the way such reasoning may be actually applied in an optimisation is by having this:

```javascript
const wr = new WeakRef(this);
await 0;
console.log(wr.deref() === this);

```

re-written into this:

```javascript
const wr = new WeakRef(this);
await 0;
console.log(wr.deref() !== void 0);

```

Since there are only two possibilities for what `wr.deref()` may return — `this` or undefined — checks for one may be re-written as checks against the other. After such a re-write, it may happen that nothing below the check actually holds a reference to `this` any more. So `wr` may as well have been cleared, as we’re past the suspension point where the lifetime of `this` was guaranteed to be extended.

(And if the RHS is something other than `this` or undefined, it’s even easier, because you can just constant-fold it to `false` immediately.)

What I pointed out above is that such a transformation arguably does not contradict the definition of liveness in the specification. Again, a set of objects _S_ is live if a possible future _S_-WeakRef-oblivous execution observes the identity of one of its members. If we take ‘observing the identity’ to mean ‘if we took this execution and substituted the appearance of this object we’re ostensibly using for some other object at some point, it would have executed differently’: well, in a WeakRef-oblivious execution with respect to { `this` }, `wr.deref() === this` returns the same value as, say, `wr.deref() === new Object()`: either is false, because it strictly compares undefined to an object.

More intuitively, we don’t observe the identity of `this`, because the check we perform is equivalent to a simpler operation of checking if `wr` still points to _some_ referent — and an engine can put that knowledge to some use.

Putting a liveness barrier in SameValue et al. would make this impossible.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [November 17, 2021, 7:55pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/19 "2021-11-17T19:55:52Z")

</div>

> [@felix.s](#):
>
> If we take ‘observing the identity’ to mean ‘if we took this execution and substituted the appearance of this object we’re ostensibly using for some other object at some point, it would have executed differently’: well, in a WeakRef-oblivious execution with respect to { `this` }, `wr.deref() === this` returns the same value as, say, `wr.deref() === new Object()`: either is false, because it strictly compares undefined to an object.

I believe that's where I disagree. If we say that `.deref()` is a liveness barrier (outside of WeakRef-oblivious executions), then the optimization shouldn't assume the identity of the result of that operation, and cannot substitute the usage of that value.

To be more clear `wr.deref() === this` is not equivalent to `wr.deref() === new Object()`, and the optimizer may not make an assumption that either would be false, because `deref` should observe the same identity when it's not empty, and the optimizer shouldn't take into consideration WeakRef-oblivious executions.

I don't believe a liveness barrier in `SameValue` is necessary, only that `new WeakRef`, `WeakRef.prototype.deref`, and `FinalizationRegistry.prototype.register` all observe the liveness and the identity of the referent.

---

<div class="post-metadata">

**Author:** ![shu](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/shu/32/13_2.png) [@shu](https://es.discourse.group/u/shu)\
**Post date:** [November 17, 2021, 9:45pm UTC](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/20 "2021-11-17T21:45:49Z")

</div>

> [@felix.s](#):
>
> `(wr = new WeakRef(this), await 0, wr.deref() === this)`? ‘In a WeakRef-oblivious execution, this compares an object against undefined; I only need to know that `this` was _some_ object, not which one it was (a.k.a. [death by irony](https://es.discourse.group/t/liveness-barriers-and-finalization/1082/7)).’

Isn't this disallowed? Assuming `wr.deref() === this` is itself observed somehow, I think the spirit of the current language does say that an optimizer cannot collapse an object's identity into equivalence classes like "_some_ object that's not `this`" and transform according to that assumption.

> [@felix.s](#):
>
> Question: can the loop L terminate? Naïvely, this should not be possible.

Interesting. If a compiler considers `R2` to be observing the identity of `delia`, then `R2` is unreachable. If a compiler considers `R2` to not be observing the identity of `delia`, then `R2` is reachable. But if `R2` is reachable in an execution, then `delia` must also be live, which contradicts its reachability. Unless, of course, it's allowed to do rematerialization, which is disallowed. Perhaps I'm missing something... This feels similar to my intention at the time to also disallow the "object identity equivalence-class collapse" kind of optimizations.

I take the point that the language could be made more formal, however.

[Next page](https://es.discourse.group/t/liveness-barriers-and-finalization/1082.md?page=2)
