New partly working cell model
This commit is contained in:
+82
-159
@@ -1,59 +1,26 @@
|
||||
# The core model
|
||||
|
||||
This file is the spec for how the compiler represents JS values, objects, bindings and functions in C. Each part of the code must agree with it. A difference between the code and this file is a bug in one of them.
|
||||
This file is the spec for how the compiler gives a type to every value of a JS program, and how it represents those values in C. The code must agree with it. A difference between the code and this file is a bug in one of them.
|
||||
|
||||
The method:
|
||||
Everything comes from four simple things and how they act on each other:
|
||||
|
||||
1. State the JS semantics that the C code must keep (section 1).
|
||||
2. Define a default representation that keeps all of it with no analysis (section 2).
|
||||
3. Let types (sections 3 and 4), the value rule (section 5) and analyses (sections 6 and 7) narrow the representation. Each narrowing has one condition. If the condition is not proved, the default stays.
|
||||
|
||||
So a hole in an analysis costs speed. It never makes wrong code. Only a wrong rule can make wrong code.
|
||||
1. **the types**, an enum, and the rules that join two of them (section 2)
|
||||
2. **objects**, which are types that every holder shares (section 3)
|
||||
3. **`walk_fn`**, which walks one fn with the types of its args, and gives the type of each of its values (section 4)
|
||||
4. **the type cache**, which keeps one walk for each fn and each list of arg types that the program reaches (section 4)
|
||||
|
||||
## 1. Semantics
|
||||
|
||||
JS has four core concepts.
|
||||
The C code must keep these JS semantics.
|
||||
|
||||
**Value.** A value is one of: undefined, null, bool, number, string, object. A value cannot change. A value is always copied: an assignment, an argument, a return and a field write each copy it. An object value is a reference to an object, so a copy of it copies the reference.
|
||||
- **A value** is one of: undefined, null, a bool, a number, a string, an object. A value cannot change. An assignment, an arg, a return and a field write each copy the value. An object value is a reference to the object, so a copy of it refers to the same object.
|
||||
- **An object** has identity, own fields, and a prototype (an object or null). A read of a field that the object lacks goes on to the prototype, and at the end of the chain it gives undefined. A field that is absent and a field that holds undefined are different.
|
||||
- **A binding** is the storage of a variable. A closure captures bindings, not values: a write to a captured binding, by its owner or by any closure, is seen by all of them. Each iteration of a loop body has its own bindings.
|
||||
- **A function** is an object that also has code, and the bindings that it captured.
|
||||
|
||||
**Object.** An object is a cell with identity. It has own fields and a prototype (an object or null). A field read looks at the own fields. If the field is absent, the read goes on to the prototype, and at the end of the chain it gives undefined. An absent field and a field that holds undefined are different. Two object values are equal (`===`, `==`) if and only if they refer to the same object.
|
||||
## 2. Types
|
||||
|
||||
**Binding.** A binding is the storage of a variable. Each entry into a scope makes new bindings. A loop body is entered once for each iteration. `for (let …)` copies the values of the loop vars into the new bindings of the next iteration. A closure captures bindings, not values. So a write to a captured binding, by the owner or by any closure, is seen by all of them.
|
||||
|
||||
**Function.** A function is an object that also has code, and the bindings that it captured.
|
||||
|
||||
### When the representation is free
|
||||
|
||||
A representation is correct if no program can see a difference from these semantics.
|
||||
|
||||
- **A primitive** cannot change and has no identity. A copy of it and a shared pointer to it look the same. Either is correct.
|
||||
- **An object** can be copied instead of shared only if no program can see the copy. There are two ways to see a copy:
|
||||
- a write through one holder, which another holder reads later
|
||||
- an identity test between two holders
|
||||
- **A binding** must be shared if a closure captures it and a write reaches it after the capture.
|
||||
|
||||
## 2. The default representation
|
||||
|
||||
With no types and no analysis, this representation is correct for every program that uses only supported ops.
|
||||
|
||||
- **Every value is an `any_t`:** a tagged union of the kinds. An object in an `any_t` is a pointer to its cell, so it keeps its identity.
|
||||
- **Every object is a cell on the heap.** Each object starts with a header: the site that created it. The site tells the object's shape and, for a fn, its code. Each field starts as absent.
|
||||
- **Every captured binding is a box:** a cell on the heap with one slot.
|
||||
- The box is made when the fn is entered.
|
||||
- The owner and every closure that captured the binding read and write the slot.
|
||||
- A scope that is entered again is a loop body. QuickJS closes each captured binding of such a scope (`close_loc k`) at every way out of it. `close_loc k` puts a new box in `k`, which holds the current value. Closures made before keep the old box. So each iteration has its own bindings.
|
||||
- A box's slot is never absent: JS gives a binding a value before any read of it that does not throw. A `var` starts as undefined. A `let` or `const` has no value before its declaration, where a read throws (the TDZ, section 8).
|
||||
- A binding that no closure captures is a C local. Transform knows this from `closure_vars`, so no analysis decides it.
|
||||
- A module binding has no frame. Its box is made where the fn that declares it is made.
|
||||
- **A function object holds a pointer to each box that it captured.** The fn that makes the closure gives each box: from its own local if the binding is its own, else from its own env. So every capture is one copy of a pointer.
|
||||
|
||||
## 3. Types
|
||||
|
||||
A type tells which values a place can hold at runtime. Infer computes one type for each value id in each variant (section 3.5).
|
||||
|
||||
### 3.1 The types
|
||||
|
||||
A type is one of these:
|
||||
A type tells which values a place can hold at runtime. It is one of these:
|
||||
|
||||
| type | values |
|
||||
|---|---|
|
||||
@@ -64,11 +31,11 @@ A type is one of these:
|
||||
| `Int` | an int32 (provisional: the number type is not decided) |
|
||||
| `Float` | a number |
|
||||
| `Str(v)` | a string. `v` is the value, if it is known |
|
||||
| `Obj(site)` | an object that `site` creates. A fn is an object whose site has code |
|
||||
| `Sum(id)` | a value of one of the types in a list: `Int \| Str`, `Obj(A) \| Null`, `Obj(A) \| Obj(B)` |
|
||||
| `Any` | any value. No rule gives it now (section 3.4) |
|
||||
| `Obj(cell)` | an object, described by a cell (section 3) |
|
||||
| `Sum(…)` | one of a list of the types above |
|
||||
| `Any` | any value. No rule gives it now |
|
||||
|
||||
**Join:**
|
||||
**Join** gives the type of a place that gets the values of two types:
|
||||
|
||||
```
|
||||
Unknown * T = T
|
||||
@@ -79,55 +46,71 @@ A type is one of these:
|
||||
T * U = T | U
|
||||
```
|
||||
|
||||
**Sums.** A sum is a list of the types above, except `Unknown`, `Sum` and `Any`. It has at most one type of each kind, except objects, which have one `Obj` for each site. A join of two sums is the union of their lists, where two types of one kind join by the rules above. So `Int | Str` joined with `Float` is `Float | Str`. Each list is kept once, by number, in the site table (`SiteTable`), which also holds a row for each site. There are finitely many kinds and sites, so there are finitely many sums.
|
||||
A sum has at most one member of each kind, except objects: it has one `Obj` for each cell. Two `Obj` types are the same type only if they have the same cell.
|
||||
|
||||
**Members.** The members of a sum are the types in its list. The only member of any other type is the type itself, and `Unknown` has none. Every rule in infer works member by member, and joins the results (section 3.3).
|
||||
## 3. Objects
|
||||
|
||||
### 3.2 The invariant
|
||||
**The type of an object is a cell:** a description of the object that every holder of the object shares. In the compiler it is `Obj(Rc<RefCell<…>>)`. The cell holds:
|
||||
|
||||
At every program point, every runtime value of a place is in the place's type.
|
||||
- the type of each field, and whether every object of the cell has the field from the start (else it can be absent)
|
||||
- what the object is: a plain object, a fn (with its code), or an array (with the type of its elements)
|
||||
|
||||
Every rule in infer must keep this invariant. Every rule is monotone, and a rule with an `Unknown` operand gives `Unknown`. Infer starts from `Unknown` everywhere and computes a fixed point of the rules.
|
||||
**A copy of the value copies the `Rc`,** so all holders have the same cell. Everything about aliasing comes from this:
|
||||
|
||||
### 3.3 Objects
|
||||
- **A write** `o.f = v` joins the type of `v` into the cell's field `f`. Every holder of the object sees it at once: through calls, returns, other objects' fields, closures and array elements.
|
||||
- **A read** `o.f` gives the cell's field `f`. If the field can be absent, it also gives the read on the prototype. At the end of the chain, it gives `Undefined`.
|
||||
- **A cell can hold itself,** for example as the type of the field `next` in a list. So an object that reaches itself needs nothing more.
|
||||
|
||||
- **A site** is an op that creates an object (`object`, `fclosure`), or a built-in fn. All objects of a site share one type for each field, for the whole program. Two sites are never merged: objects of two sites in one place give `Any`.
|
||||
- **A field's type** is the join of every value written to it. A field can be absent, unless every object of the site gets it when it is created (section 7). A field that no write reaches is only absent.
|
||||
- **A field read** on `Obj(site)` gives the own field's type. If the field can be absent, it also gives the read on the prototype (the `__proto__` field). The chain ends at a prototype that is absent or not an object, where the read gives `Undefined`. So a field that can be absent reads as its type `| Undefined`.
|
||||
- **A field write** on `Obj(site)` joins the value's type into the site's field. So every value that holds an object of the site sees the write.
|
||||
- **A primitive** has no own fields. A read on it reads its kind's prototype, which the stdlib links with `__link_proto_*`.
|
||||
- **A field read or write on undefined or null** throws (section 8), so it gives `Unknown`.
|
||||
- **An array** is an object too, of a site whose kind is "array": `array_from` makes it. Besides its fields, the site has an **element type**: the join of every value written to an element. So the type of an array is `Obj(site)`, and its site says what its elements can be (written `[T]` for short).
|
||||
- `a[i]` gives `T | Undefined`: an index can be a hole, or past the end.
|
||||
- `a[i] = x` joins the type of `x` into `T`. An index past the end makes the array longer, and the indexes between are holes.
|
||||
- `a.length` is an `Int`, and it is not a field. A write of it truncates the array, or makes it longer with holes.
|
||||
- An index is a number. Any other key is a computed field name (section 8).
|
||||
- An array's prototype is the one that the stdlib links for arrays (`__link_proto_array`), as for a primitive kind. Its methods are JS in the stdlib, on top of the three ops above.
|
||||
- **On a sum,** a read, a write or a call works on each member, and joins the results. So a read on `Obj(A) | Null` gives the read on `A`: the `Null` member throws, and gives nothing.
|
||||
**An array** is a cell whose elements have one type `T`, the join of every value written to an element. `a[i]` gives `T | Undefined`, because an index can be a hole or past the end. `a.length` is an `Int`.
|
||||
|
||||
### 3.4 `Any`
|
||||
**A captured binding is a box:** a cell with one field, which holds the binding's value. The fn that owns the binding makes the box when it is entered, and again for each iteration of a loop body. The owner and every closure that captured the binding read and write the box's field. A closure is a fn cell whose fields hold its boxes.
|
||||
|
||||
Since sums, no join gives `Any`, so no value has the type `Any`. The rules for it stay, so that it can be used again (for example, as a limit on the size of a sum).
|
||||
## 4. Inference
|
||||
|
||||
Code that holds an `Any` does not know the site of an object in it. So infer tracks which objects can be in an `Any`:
|
||||
**`walk_fn`** walks one fn with one list of arg types.
|
||||
|
||||
- **The mark.** When a value of type `Obj(site)` goes into an `Any`, the site is marked `in_any`. A value goes into an `Any` in a join that gives `Any`, and when it is an operand or a result of a read, write or call on `Any`. The mark spreads to the sites of the objects in the site's fields, because code that holds the `Any` can read them.
|
||||
- **A read on `Any`** reads the field on every `in_any` site and on every primitive prototype. What it reads goes into the `Any` that it gives.
|
||||
- **A write on `Any`** writes the field on every `in_any` site, and sets their `written` flag (section 5).
|
||||
- **A call on `Any`** calls the code of every `in_any` site that has code, in the variant whose args are all `Any`, except the env, which is the fn object itself (`Obj(site)`). It gives `Any`.
|
||||
- It starts with `val_types = vec![Type::Unknown; n_val]`, and puts the arg types into the args' values.
|
||||
- It walks the instrs. Each instr gives a type from the types of its operands, and that type is joined into its dst. An instr whose operand is still `Unknown` waits.
|
||||
- It walks again until nothing changes. The result is the type of each value, and the return type.
|
||||
|
||||
So every object that code with an `Any` can reach is of an `in_any` site, and the C can choose the code by the site in the object's header.
|
||||
**The cells of a walk.** When a walk reaches an instr that makes an object, the first time, it makes a new cell for that instr. On every later pass, it uses the same cell again. So the objects that a walk makes belong to that walk: a fn walked with `Int` and with `Str` makes different cells.
|
||||
|
||||
### 3.5 Calls
|
||||
**The type cache** is a map from a fn and a list of arg types to the walk of that fn with those types.
|
||||
|
||||
- **A call** of `Obj(site)` runs the site's code, with the fn object itself as its env. A call of a sum calls each `Obj` member with its own env, and joins the results. A call of a value that is not a fn throws (section 8). A call of `Any` is in section 3.4.
|
||||
- **The program is closed.** Every fn comes from an `fclosure`, or it is a native. So the code of every site is known.
|
||||
- **A variant** is one walk of a fn with one tuple of arg types. A known value does not cross a call (`Bool(v)` becomes `Bool`), so there are finitely many variants.
|
||||
- **Rounds:** infer walks every variant from the entry again, until a round changes nothing in the fields, the flags, the prototypes or the returns. A call of a variant that is still being walked (recursion) gives what that variant returned in the round before. When a round changes nothing, these returns are the fixed point.
|
||||
- A call looks up its callee with the types of its args. If there is no walk yet, it walks the callee. The call gives the walk's return type.
|
||||
- **Only what the program reaches is walked,** as in Rust: there is one walk for each list of arg types that a call really passes.
|
||||
- **In the key, an object is its cell.** Known values are dropped (`Bool(true)` is `Bool`), so a literal does not make a new walk.
|
||||
|
||||
## 4. Representation
|
||||
**Rounds.** A walk can depend on cells and returns that other walks are still adding to. So infer walks the whole program again, from the entry, until a round changes no cell, no return and no proto, and makes no new walk or cell. A recursive call gives what its walk returned in the round before.
|
||||
|
||||
The C representation of a value is a function of its type and of the value rule (section 5) only. Each type has one:
|
||||
**The open problem: infer does not always end.** A walk makes cells, and a key holds cells. So when a cell that a walk made gets into the key of a later call, that call gets a new walk, which makes new cells, which can make another new key. Rust does not have this problem, because every type in a Rust key is written in the program (a struct and its type args), and a call cannot change the type of its args. These programs end at once in node, but infer does not end on them:
|
||||
|
||||
```js
|
||||
// 1. `this` holds the list of nodes. The arrow fn never reads `this`, but it
|
||||
// is in the key, and each new walk of it leads to a new node
|
||||
let list = null;
|
||||
function add(f) { list = { f: f, next: list }; }
|
||||
add(() => add(() => 0));
|
||||
list.f();
|
||||
|
||||
// 2. A cell is one type for the object's whole life: the call sees the type
|
||||
// of `o.v` after the write, so each round sees one cell more
|
||||
const o = { v: 1 };
|
||||
function f(x) { o.v = { inner: x }; }
|
||||
f(o.v);
|
||||
|
||||
// 3. A new object goes into a recursive call. The exact types are
|
||||
// `{next: Int}`, `{next: {next: Int}}`, ...: as many as `n`, which only the
|
||||
// runtime knows. Rust rejects this program too
|
||||
function g(n, x) { if (n > 0) g(n - 1, { next: x }); }
|
||||
g(3, 0);
|
||||
```
|
||||
|
||||
The same thing happens on a large scale with partial types. Inside a walk, a call first sees the types of an early pass, then wider ones. Each key gets its own walk, and its cells stay in the types for good. The stdlib's promises reach thousands of walks from a 7-line program. The same loops without fns (`for (…) x = { next: x }`) end, because one instr in one walk is one cell.
|
||||
|
||||
## 5. Representation in C
|
||||
|
||||
The C type of a value comes from its type only:
|
||||
|
||||
| type | C |
|
||||
|---|---|
|
||||
@@ -137,82 +120,22 @@ The C representation of a value is a function of its type and of the value rule
|
||||
| `Int` | `int32_t` |
|
||||
| `Float` | `double` |
|
||||
| `Str` | `string_t` |
|
||||
| `Obj(site)`, a value shape (section 5) | `S`, the struct of the site |
|
||||
| `Obj(site)`, any other shape | `S *`, a pointer to the struct of the site |
|
||||
| `Str`, plus `Undefined` and/or `Null` | `string_t`. `NULL` is null, and `JSRT_UNDEFINED` is undefined |
|
||||
| `Obj(site)`, plus `Undefined` and/or `Null` | `S *`, with the same two sentinels |
|
||||
| `Obj` of several sites, with or without `Undefined` and `Null` | `jsrt_header *`, a pointer to the object's header, with the same two sentinels |
|
||||
| any other sum | `any_t`. It is the tagged union of every kind, so it holds every sum |
|
||||
| `Any` | `any_t`, a tagged union. An object in it is the pointer to its cell |
|
||||
| `Obj(cell)` | a pointer to the struct of the cell. The struct starts with a header that holds the cell's number |
|
||||
| `Str` or one `Obj`, plus `Undefined` and/or `Null` | that pointer. `NULL` is null, and `JSRT_UNDEFINED` is undefined |
|
||||
| `Obj` of several cells, maybe with `Undefined` and `Null` | a pointer to the header |
|
||||
| any other sum, and `Any` | `any_t`: a tagged union of every kind |
|
||||
|
||||
- **An array** is a pointer to the struct of its site, whose header is followed by `length`, `cap` and `items`: an array of `cap` elements, of the representation of `T | Undefined`. A hole holds undefined.
|
||||
- **Absent** is a field state, not a value. A field that can be absent needs one more immediate for absent: a sentinel address for `string_t` and `S *`, a header tag for `S`, and an `any_t` tag for every other type.
|
||||
- **Conversions.** A type grows in a join, so most conversions go to a representation that holds more: a member into its sum, or `int32_t` into `double`. These cannot fail. A conversion to a representation that holds less checks the value, and a failed check is a `TypeError` (section 8). For example, a `string_t` that can be undefined, passed to a native's string param.
|
||||
- **An op on a sum** tests which member the value is: a kind test on an `any_t`, a sentinel test for null and undefined, and a `switch` on the site in the header for objects. Each case then works on one member, as for a single type. A member on which the op throws (a field read on null) has no case, so it throws.
|
||||
- **A read, a write or a call on `any_t`** of type `Any` is a helper that emit writes: a `switch` on the site in the object's header, with one case for each `in_any` site (section 3.4). Every field name is known at compile time, so no hash map is needed. One comes with computed field names (section 8).
|
||||
- **A field that can be absent** holds a sentinel for absent: `JSRT_ABSENT` in a pointer, a tag in an `any_t`.
|
||||
- **An op on a sum** tests which member the value is (by kind, by a sentinel, or by the cell's number in the header), and then works on that member.
|
||||
- **An array's struct** has `length`, `cap` and `items` after its header. An element holds undefined for a hole.
|
||||
|
||||
## 5. Values: immutable shapes
|
||||
## 6. Not in this model yet
|
||||
|
||||
**The rule.** Section 1 gives the two ways to see a copy of an object: a write through one holder that another holder reads later, and an identity test. An object that is never written after its creation, and never in an identity test, has neither. So it behaves like a primitive: a copy of it and a shared pointer to it cannot be told apart. It can be a C struct value, copied freely, with any number of holders.
|
||||
|
||||
**The flags.** Infer sets three flags on each site. Each flag only becomes true.
|
||||
|
||||
- **written:** a field write that is not a creation write reaches an object of the site. A creation write is a `define_field` into the value that a creating op gives, before the object goes anywhere: an object literal's fields, a closure's captured boxes. Regify turns a `define_field` into anything else (a class field on `this`, a static field on the class) into a `put_field`. So every `define_field` is a creation write, and every `put_field` is a write after creation.
|
||||
- **compared:** an object of the site is an operand of `===`, `!==`, `==` or `!=`, and the other operand can also be an object (`Obj` or `Any`).
|
||||
- **in_any:** an object of the site can be in an `Any` (section 3.4). Code that does not know the site can write it or compare it.
|
||||
|
||||
**Value shapes.** A site has a value shape if no flag is set, it is not an array, it is not a member of a sum (an element of an array is in the sum `T | Undefined`), and it does not contain itself through value fields. A sum is one C type for all its members, so its object members are pointers. A struct cannot contain itself, so a site that does stays a pointer. An object of an immutable site cannot hold itself, but objects of one site can hold each other, as in a list.
|
||||
|
||||
**Representation.** An object of a value shape is a struct value (`S`), in its holder: a C local, a param, a return value, or the struct of the object whose field holds it. In a field that can be absent, its header's site is the tag for absent (`JSRT_SITE_ABSENT`). It is not in a sum and not `in_any`, so it is never an `any_t` value. A runtime operation that only reads it (ToString, `<`) gets a heap copy.
|
||||
|
||||
**Default.** An object of any other shape is a pointer to a cell (`S *`).
|
||||
|
||||
**Soundness.** No write after creation can show a copy, and no identity test can. A creation write happens before the object goes anywhere, so no copy exists yet.
|
||||
|
||||
## 6. Escape
|
||||
|
||||
**Question.** Where can a cell still be reached: after its frame returns, or when the op that made it runs again?
|
||||
|
||||
**Result.** One of three values for each value that a creating op gives:
|
||||
|
||||
- **`escapes`** if one of these is true:
|
||||
- After the frame of its fn returns, the cell can be reached in a way other than as the return value itself. For example, it is stored in a field of a cell that is not `local`, stored in a box that is not `local`, or given to a callee. Every call operand counts as kept, because the analysis does not know the callee.
|
||||
- A value that can reach the cell is still used when the creating op runs again: the op is in a loop, and the cell reaches a var or a join slot, which can keep it until the next run.
|
||||
- **`returned`** if it does not escape, the return value is the only way to reach it after the return, it is the only object that a return of the fn can give, and its creating op runs at most once in each call.
|
||||
- **`local`** otherwise: no holder outlives the frame.
|
||||
|
||||
A box is a cell, so the same rules apply to boxes.
|
||||
|
||||
**Narrowing.**
|
||||
|
||||
- **`local`:** the cell is on the stack of its frame. The value is still a pointer (`S *`) to it, so only the creating op changes. A C compiler can also split such a cell into registers if its address goes nowhere.
|
||||
- **`returned`:** the caller allocates the cell. The variant gets one more param: a pointer to the cell to build into. The callee builds the object there and does not allocate. In the caller, the call is the creating op of the cell. So the caller's escape result for the call's dst chooses the stack or the heap, and the decision goes up through any number of fns that make and return objects.
|
||||
|
||||
**Default.** A cell that escapes is on the heap (`jsrt_alloc`).
|
||||
|
||||
**Soundness.**
|
||||
|
||||
- A stack cell lives until its frame returns, and its creating op uses the same memory again each time it runs. `local` excludes both cases.
|
||||
- A `returned` object is in the caller's cell from the start and never moves. So every pointer to it stays valid, including a pointer from the object to itself.
|
||||
|
||||
**Where it is used.** Emit chooses the allocation of every cell in one function, `alloc()`. A creating op calls it, and so does a call of a variant whose result is `returned`.
|
||||
|
||||
## 7. Other narrowings
|
||||
|
||||
| narrowing | condition | if not proved |
|
||||
|---|---|---|
|
||||
| no absent in a field | a write to the field comes before every read of it (definite initialization). Object-literal fields have this: QuickJS fills them with `define_field` before the object goes anywhere | the field can be absent (section 3.3) |
|
||||
| a captured binding as a value | no write reaches the binding after its first capture. Most `const` bindings have this. A `const` that a hoisted fn captures before its declaration does not | a box |
|
||||
| a direct call | the callee's type is `Obj(site)`: its code is known | a call on `Any`: a `switch` on the site (section 3.4) |
|
||||
|
||||
## 8. Out of scope
|
||||
|
||||
These are not part of this model, and they need their own design:
|
||||
|
||||
- narrowing in branches: in `if (x !== null) { x.f }`, `x` keeps its whole type. This is correct, because the `Null` member of the read throws, but the C still tests for null
|
||||
- `throw`, `try`, `catch` and `finally`. These include the `TypeError`s above: a call of a value that is not a fn, and a failed check of a conversion from `any_t`.
|
||||
- the TDZ (a read of a `let` before its declaration)
|
||||
- fields with computed names (`o[k]`), `delete`, `in`. The absent state is the base that `in` and `delete` need.
|
||||
- typed arrays, `ArrayBuffer` and `DataView`
|
||||
- iterators: `for…of`, spread and destructuring
|
||||
- a bound on the walks: infer must end on every program (section 4)
|
||||
- definite initialization: a field that a constructor writes before `this` goes anywhere is never absent
|
||||
- narrowing in branches (`if (x !== null)`)
|
||||
- objects as struct values, and objects on the stack (escape analysis)
|
||||
- fields with computed names (`o[k]` on an object that is not an array), `delete`, `in`
|
||||
- typed arrays, `ArrayBuffer`, `DataView`, and iterators
|
||||
- `throw`, `try`, `catch`, `finally`, and the TDZ
|
||||
- getters and setters
|
||||
|
||||
Reference in New Issue
Block a user