Bug 224077 - Make environments safe for space
Summary: Make environments safe for space
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2021-04-01 14:32 PDT by Saam Barati
Modified: 2021-04-23 13:08 PDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Saam Barati 2021-04-01 14:32:28 PDT
Our environments aren't safe for space because we retain all values in a scope if the scope itself is alive.

Let's consider a test program:

```
function a() {
    let a = {};
    let b = {};
    function foo() { return a; }
    return function bar() { return b; }
}
let f = a();
```

In the above program, we'll keep alive both "a" and "b" in the environment, even though bar is the only function that's alive, and it only references "b". To be safe for space, bar should only retain "b", and foo should only retain "a".

Each closure should only retain the variables it might access.

In practice, this is a bit tricky to implement. I started on it, and ran into some snags that'll require being worked out. 

An implementation sketch with some open questions:
- Each function (or executable, to be more precise), knows which variables it'll keep alive. JSFunction, when marking, won't just append its scope to the slot visitor. Instead, it'll have a special form of a "partial" visit that marks the values or corresponding values' bits inside lexical environment. Lexical environment, will need to be visited in such a way that it'll only append objects with corresponding set bits.
- Lexical environment might also get marked strongly, not just partially (e.g, it's a root for some reason), it'll then have to mark every slot that isn't dead (e.g, died in some prior GC).
- These two things above can happen in the same cycle. So we can't just set the mark bit of a lexical environment after partially visiting it, because it may cause it to not be fully visited (e.g, when it's a root).
- Phil's idea was that maybe we could repurpose the newly allocated bits such that we set it so the JSScope doesn't get swept. And we set that when partially visiting instead of the mark bit.
- Lexical environment probably has two bit vectors. One will tell you if any object has ever died, and another will tell you in the current marking cycle what needs to be marked. You likely also need a version number for the bit vector so we know when to transfer over the bits of dead objects so we never visit their slots in the future.
- We need some way of partially visiting the environments after setting new bits in its alive bit vector.
- We might need to keep alive parent scopes to some degree even if we don't rely on them, because an environment might get fully marked in a future GC cycle (e.g, again, let's say it's a root somehow), and it'll need a valid "next" scope. Or maybe we can play some games here where a dead "next" becomes the global object or something. Or we just pretend there is no "next" if we proved we didn't need after some time. Just need to make sure we don't rely on  being able to traverse the next() chain indefinitely till we hit the global object, or something similar.
- We need to make sure everything stays alive for Eval. Maybe also "with"? Easy enough to do.
- It would be nice if a write barrier during concurrent GC or before a full GC didn't cause the environment to get fully marked via being in the remembered set. But maybe that's fine, or at least fine for a v1.
Comment 1 Radar WebKit Bug Importer 2021-04-23 13:08:23 PDT
<rdar://problem/77083073>