From cb773b4267766f6eb6d82466b6c8f61f092a9d6e Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 28 Feb 2022 14:47:08 -0500 Subject: [PATCH 01/30] docs(swingset): Capitalize Unix --- packages/SwingSet/docs/delivery.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 5b87e6519d3..7d2bad0a33c 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -3,12 +3,12 @@ A walkthrough of how messages are passed from one Vat to another. Each SwingSet machine contains one or more Vats, all talking to a shared Kernel (and *not* directly to each other). Most Vats are isolated and can -*only* talk to the kernel. Vats correspond to userspace processes in a unix -system, and the SwingSet kernel is very much like the unix kernel which +*only* talk to the kernel. Vats correspond to userspace processes in a Unix +system, and the SwingSet kernel is very much like the Unix kernel which supports those processes. Vats contain some application-specific code (named "Vat Code"), which -corresponds to the unix program written in C or some other language. For +corresponds to the Unix program written in C or some other language. For SwingSet, most Vat Code is in the SES subset of Javascript, using orthogonal persistence, native platform Promises, and making eventual-send calls to local or remote objects with either the `E()` wrapper `p=E(x).foo(args)` or @@ -18,7 +18,7 @@ non-SES language (e.g. a WASM box). Below the Vat Code, but still inside the Vat, there is a support layer which translates eventual-sends into kernel syscalls, and manages persistence. This -corresponds to the `libc` layer in a unix process: user code does not invoke +corresponds to the `libc` layer in a Unix process: user code does not invoke syscalls directly, but instead it calls standard library functions like `write()` which wrap those syscalls. From 4673804eb126520633ceb84340af4cb0edfa1a4f Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 28 Feb 2022 15:11:14 -0500 Subject: [PATCH 02/30] docs(swingset): Replace outdated `syscall` claim with authoritative link --- packages/SwingSet/docs/delivery.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 7d2bad0a33c..777f8daa8a5 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -22,12 +22,13 @@ corresponds to the `libc` layer in a Unix process: user code does not invoke syscalls directly, but instead it calls standard library functions like `write()` which wrap those syscalls. -When the vat needs to send a message of some sort, it invokes one of the -kernel's `syscall` functions (the vat receives a `syscall` object with three -methods). To ensure that Vats are isolated from the kernel, and to build a -deterministic transcript, these syscalls are limited to taking pure data as -arguments (i.e. everything could be JSON serialized into a single string, -without loss of correctness). The primary syscall is named `syscall.send()`. +When the vat needs to send a message of some sort, it invokes a method of the +`syscall` object provided to it by the kernel (cf. +[Vat-Outbound Slot Translation](#vat-outbound-slot-translation)). To ensure that +Vats are isolated from the kernel, and to build a deterministic transcript, +these syscalls are limited to taking pure data as arguments (i.e. everything +could be JSON serialized into a single string, without loss of correctness). The +primary syscall is named `syscall.send()`. Each Vat turn is initiated by the kernel invoking a `dispatch` function: the Vat is defined by a `dispatch` object with two or three methods, which all @@ -511,7 +512,7 @@ Some invocation patterns are legal, but unlikely to be useful: In some places, `dispatch.deliver()` is named `message`: we're still in the process of refactoring and unifying the codebase. -## Outbound (Vat->Kernel) translation +## Vat-Outbound Slot Translation Inside the implementations of all syscall methods (`send` and `resolve`), the Vat-specific argument slots are first mapped into kernel-space identifiers by @@ -771,9 +772,9 @@ must be rejected, just as if decider Vat had called `CannotSendToData` object (for `Data`), or a copy of the Promise's own rejection object (for `Rejected`). -## Inbound (Kernel->Vat) translation +## Vat-Inbound Slot Translation -Any slots in the operation must then be translated into identifiers that are +Any slots in messages from the kernel must be translated into identifiers specific to the target Vat, by looking them up in the target Vat's C-List. If they do not already exist in this C-List, a new (negative) index will be allocated and added to the C-List. From 6b1cb9bde0189396d7ad0f71a03e33d216beb625 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 28 Feb 2022 15:15:46 -0500 Subject: [PATCH 03/30] docs(swingset): Fix typo --- packages/SwingSet/docs/delivery.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 777f8daa8a5..8c5c6c98a43 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -46,9 +46,8 @@ might be interrupted by an out-of-gas error (in which case it could be replayed or restarted after a Keeper supplies more funds). If the delivery is interrupted, or fails, the buffer is discarded. (Note that these per-crank DB transactions are independent of any blockchain transactions that might have -initiated the delivery). Syscalls which return data will read it from the -crank buffer (if recently modified, or fall through to the persistent store -below. +initiated the delivery). Syscalls read recently-modified data from the crank +buffer, otherwise falling through to the persistent store. All `dispatch` functions can schedule near-term work by using `Promise.resolve()` to append something to the promise queue. This work will From f9214aca04316443b836a217ef718910af4c1592 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 28 Feb 2022 15:30:43 -0500 Subject: [PATCH 04/30] docs(swingset): Reword Vat Code introduction paragraph for clarity --- packages/SwingSet/docs/delivery.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 8c5c6c98a43..caeeece7a1e 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -7,14 +7,14 @@ Kernel (and *not* directly to each other). Most Vats are isolated and can system, and the SwingSet kernel is very much like the Unix kernel which supports those processes. -Vats contain some application-specific code (named "Vat Code"), which -corresponds to the Unix program written in C or some other language. For -SwingSet, most Vat Code is in the SES subset of Javascript, using orthogonal -persistence, native platform Promises, and making eventual-send calls to -local or remote objects with either the `E()` wrapper `p=E(x).foo(args)` or -(eventually) the wavy dot syntax `p=x~.foo(args)`. Other forms of Vat Code -could exist, such as non-orthogonal (database/ORM-based) persistence, or in a -non-SES language (e.g. a WASM box). +Each vat contains some application-specific code (named "Vat Code"). For +SwingSet, most Vat Code uses orthogonal peristence and is written in the SES +subset of Javascript, employing native platform Promises and making +eventual-send calls to local or remote objects with either the `E()` wrapper +(`resultPromise=E(x).foo(args)`) or (eventually) the wavy dot syntax +(`resultPromise=x~.foo(args)`). Other forms of Vat Code could exist, e.g. using +non-orthogonal persistence such as a database or a non-SES language such as +WASM. Below the Vat Code, but still inside the Vat, there is a support layer which translates eventual-sends into kernel syscalls, and manages persistence. This From 4c9879dce834c905b13eee81fb1fcb99cc74e5bd Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 11:24:10 -0500 Subject: [PATCH 05/30] docs(swingset): Reword `dispatch` introduction paragraph for clarity --- packages/SwingSet/docs/delivery.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index caeeece7a1e..70886c0f9b8 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -30,12 +30,14 @@ these syscalls are limited to taking pure data as arguments (i.e. everything could be JSON serialized into a single string, without loss of correctness). The primary syscall is named `syscall.send()`. -Each Vat turn is initiated by the kernel invoking a `dispatch` function: the -Vat is defined by a `dispatch` object with two or three methods, which all -close over the vat's internal state. The primary one is `dispatch.deliver()`, -which delivers the messages produced by some other vat when it does -`syscall.send()`. The arguments to `deliver` are also pure data, and are -recorded in a transcript to enable replay-based orthogonal persistence. +Each vat is represented in the kernel as a `dispatch` object with methods that +close over its internal state (cf. +[Vat-Inbound Slot Translation](#vat-inbound-slot-translation)). A vat's code +executes when the kernel initiates a **turn** by invoking one of these methods, +primarily `dispatch.deliver()` (which delivers messages such as those produced +by `syscall.send()` from some other vat). The arguments to `deliver` are also +pure data, and are recorded in a transcript to enable replay-based orthogonal +persistence. To enable transactional commitments, all state changes that might be made by syscalls are held in a transaction buffer (the "crank buffer") until the From 55a1fd7cf8fdc4669411d2107993421941671f18 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 14:33:52 -0500 Subject: [PATCH 06/30] docs(swingset): Explain "orthogonal peristence" --- packages/SwingSet/docs/delivery.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 70886c0f9b8..3e066654b7e 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -8,8 +8,9 @@ system, and the SwingSet kernel is very much like the Unix kernel which supports those processes. Each vat contains some application-specific code (named "Vat Code"). For -SwingSet, most Vat Code uses orthogonal peristence and is written in the SES -subset of Javascript, employing native platform Promises and making +SwingSet, most Vat Code uses orthogonal peristence (i.e., invisible to the vat +code, which effectively perceives its memory data as eternal) and is written in +the SES subset of Javascript, employing native platform Promises and making eventual-send calls to local or remote objects with either the `E()` wrapper (`resultPromise=E(x).foo(args)`) or (eventually) the wavy dot syntax (`resultPromise=x~.foo(args)`). Other forms of Vat Code could exist, e.g. using From e0a305f6a1fdf26117a20e5ec90a0101cffb51ff Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 18:31:22 -0500 Subject: [PATCH 07/30] docs(swingset): Normalize the spelling and capitalization of "C-List" --- packages/SwingSet/docs/delivery.md | 60 +++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 3e066654b7e..284fc9450ef 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -75,7 +75,7 @@ message, giving these promise-queue actions a chance to complete. +-syscall | -+ +-syscall--+ +-syscall--+ +-|-dispatch-+----+-dispatch-+------+-dispatch-+---------+ | v | | | | | | | -| clists | | clists | | clists | | +| c-lists | | c-lists | | c-lists | | | | | | | | | | | | >-v | @@ -107,11 +107,11 @@ resolved. Both identifiers use integer index values to distinguish between different instances. When the Vat allocates the identifier, it uses a positive integer. When the kernel does the allocation, the vat gets a negative integer. This -index always points into a Capability List (the "c-list", described below). +index always points into a Capability List (the "C-List", described below). -In some cases, the kernel (or the vat) will allocate an entry in the c-list +In some cases, the kernel (or the vat) will allocate an entry in the C-List when it receives an index that it has not seen before. In other cases, the -index must already be present in the c-list, otherwise it is an error. +index must already be present in the C-List, otherwise it is an error. Each Object lives in a specific Vat. The types are named from the perspective of the Vat: when a Vat references one of its own objects in a syscall, this @@ -171,7 +171,7 @@ For each Vat type, there is a matching kernel type. These are distinct values, with conversion from one to the other happening at the syscall/dispatch boundary. Some values are identical (the `body` string is left untouched), but the object/promise identifiers must be mapped through -the clist tables. The kernel's `ObjectID(5)` may refer to a completely +the C-List tables. The kernel's `ObjectID(5)` may refer to a completely different object than Vat A's `ObjectID(5)`. Keeping them in different types helps avoid mistakes. (And note that Vat A's `ObjectID(5)` is probably unrelated to Vat B's `ObjectID(5)`). @@ -205,7 +205,7 @@ in other tables). Each contains some additional state-specific data: The kernel also maintains a "run-queue", which is populated with pending deliveries, each of which references a variety of kernel-side objects. -Finally, the kernel maintains c-list (Capability List) tables for each Vat, +Finally, the kernel maintains C-List (Capability List) tables for each Vat, which map vat-side references into kernel Object/Promise references. For each vat, there are two bidirectional tables: Objects (Imports and Exports) map to the kernel Object table, and Promise IDs map to the kernel Promise table. @@ -308,10 +308,10 @@ possibilities, and `syscall.send` will reject the message (terminating the Vat) unless the `result` ID falls into one of these categories: * A brand new Promise was created just for the result slot. The ID will be a - positive integer that is not already in the c-list. + positive integer that is not already in the C-List. * The Promise was created by this Vat earlier, and it has never been used as a result or a resolution. The ID will be a positive integer that is already - present in the c-list, and the Decider will point at this Vat. + present in the C-List, and the Decider will point at this Vat. * The Promise was received from the kernel earlier as the result slot of an incoming message. The ID will be a negative integer, and the Decider will point at this Vat. @@ -437,7 +437,7 @@ facing the kernel (mapping `lo/lp` to `o/p`), and another one for each remote machine (mapping `lo/lp` to `ro/rp`). The Javascript SwingSet implementation uses these actual strings as keys in -the arguments and the C-list tables. In other languages, they could be +the arguments and the C-List tables. In other languages, they could be represented by a tagged union whose only real member is an integer. Each Vat's numberspace is independent (so "1" could nominally be allocated in @@ -448,19 +448,19 @@ numberspace with a different offset (todo: 1000 times the vat number). ## Kernel-Side C-Lists For each Vat, the Kernel maintains a set of Capability-List structures -(c-lists), which translate between vat-side identifiers and kernel-side +(C-Lists), which translate between vat-side identifiers and kernel-side identifiers. Depending upon the operation, this translation might insist that the value is already present in the table, or it might allocate a new slot when necessary. -C-lists map Vat object/promises to kernel object/promises. After Vat 2 sends +C-Lists map Vat object/promises to kernel object/promises. After Vat 2 sends this message into the kernel: ``` v2.send(target=o-4, msg={name: foo, slots:[o+3], result=p+5}) ``` -.. the Vat-2 clist might contain: +.. the Vat-2 C-List might contain: | Vat 2 object | Kernel object | Allocator | Decider | | --- | --- | --- | --- | @@ -518,7 +518,7 @@ process of refactoring and unifying the codebase. Inside the implementations of all syscall methods (`send` and `resolve`), the Vat-specific argument slots are first mapped into kernel-space identifiers by -passing them through the Vat's c-list tables. If the Vat identifier is +passing them through the Vat's C-List tables. If the Vat identifier is already present in the table, the corresponding kernel identifier is used. If it is not already present, the behavior depends upon which method and argument it appeared in, as well as the category of identifier. @@ -532,7 +532,7 @@ recovery from mapping errors. The target of a `syscall.send()` specifies where the message should be sent. `Object` always maps to a `KernelObject`, and `Promise` always maps to a -`KernelPromise`. If the Vat object is not already in the c-list, the +`KernelPromise`. If the Vat object is not already in the C-List, the following table describes what the mapping function does: | Vat Object | description | action if missing | @@ -592,7 +592,7 @@ resolves, or both. To reduce the noise of unwanted notifications, Vats will not receive a `dispatch.notify()` for a Promise unless they first use `syscall.subscribe()` to express their interest. -The `PromiseID` argument to `subscribe()` is translated through the c-list +The `PromiseID` argument to `subscribe()` is translated through the C-List just like a `CapSlot` in `syscall.send()`. It is not common for this to cause the allocation of a `KernelPromise`, because Vats don't usually subscribe to hear about their own Promises, but it is legal. @@ -671,7 +671,7 @@ includes it in an argument, the lower-level translation layer can create a new promptly-resolved Promise for it. (TODO: `resolve()` is a good opportunity to remove the promise from the -resolving vat's c-list, however if we have queued messages, it must live long +resolving vat's C-List, however if we have queued messages, it must live long enough to forward those messages to the new resolution. It would be nice to keep the CannotSendToData logic in the kernel, and have the resolving Vat just re-`send` everything in the queue in all resolution cases. If it @@ -820,7 +820,7 @@ The `Resolution` value of `dispatch.notify()` may contain slots, which are translated just like `Message.args`. TODO: After a Vat receives notice of a Promise being resolved, we might -choose to remove that promise from the vat's c-list, and forbid the Vat from +choose to remove that promise from the vat's C-List, and forbid the Vat from ever mentioning that promise again. A Vat might be informed that one Promise has been resolved to another Promise @@ -868,7 +868,7 @@ identifier for the result Promise. We assume that Vat-1 uses `p1` later (i.e. +----|-------+ +---|------+ +-syscall--+ +----|-------+---+---|------+------+-dispatch-+-------+ | v | | | | | | | -| clists | | clists | | clists | | +| c-lists | | c-lists | | c-lists | | | | | | ^ | | | | | | | | | | | >-v | @@ -905,7 +905,7 @@ kernel promise table, yielding `kp24`. Vat-1 is then added to the The run-queue is cycled, and this Send comes to the top. This looks up `target` in the kernel object table to find the owner (`vat-2`), which -indicates the c-list to use for translating the message. +indicates the C-List to use for translating the message. The target `ko1` is looked up in the Vat-2 C-List, and maps to `v2.o+2001`. As a target, it must already be present in that C-List (the kernel will never @@ -1442,7 +1442,7 @@ lack of a single central kernel: +----|-------+ +---|------+ +---|------+ +---|------+ +----|-------+---+---|------+-+ +-+---|------+----+---|------+-+ | v | | | | | | | v | | | | | -| clists | | clists | | | | clists | | clists | | +| c-lists | | c-lists | | | | c-lists | | c-lists | | | | | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | @@ -1493,10 +1493,10 @@ tables just before `dispatch.deliver()` is called will look like: left-comms gets `deliver(target=o+2001)` and looks up the target in the routing table to see that the destination machine is `right`. It maps -`o+2001` through the `right` c-list table to get `ro-3001`, which it uses in +`o+2001` through the `right` C-List table to get `ro-3001`, which it uses in the wire message. It sees the result promise (`p-2015`) has no mapping in the routing table, so it adds it (with `Decider: right`), then sees that `p-2015` -is not in the c-list, and adds it too, allocating a new ID (`rp+3202`). +is not in the C-List, and adds it too, allocating a new ID (`rp+3202`). Left-comms uses these identifiers to generate the cross-machine message, expressed in receiver-centric terms (so the signs are flipped): @@ -1515,9 +1515,9 @@ An external delivery process copies the cross-machine message from the left Outbox into the right machine, causing a pending delivery that gets the message into the right-comms vat, along with the name of the machine that sent it (`left`). Right-comms looks up the target (`ro+3001`) in the `left` -c-list to find `o-4001`. It sees that the result promise `rp-3202` is not -present in the c-list, so it allocates a new local ID (`p+4002`) and adds it -to the c-list. It does *not* add `p+4002` to the routing table, because the +C-List to find `o-4001`. It sees that the result promise `rp-3202` is not +present in the C-List, so it allocates a new local ID (`p+4002`) and adds it +to the C-List. It does *not* add `p+4002` to the routing table, because the kernel will hold the resolution authority for this result. * right-comms cross-machine C-Lists @@ -1566,12 +1566,12 @@ Now suppose right-vat resolves the result promise to a new local object * right-comms allocates `ro+3002` for the object `o-4003` * outbox message is `notify(target=rp+3202, Fulfill(ro-3002))` * left-comms gets message from `right`, maps target to `p-2015` -* left-comms maps resolution through right-machine c-list, allocates `o+2002` +* left-comms maps resolution through right-machine C-List, allocates `o+2002` * left-comms submits `syscall.resolve(target=p-2015, Fulfill(o+2002))` -* left kernel maps through left-comms c-list, allocates `ko15` for `v2.o+2002` +* left kernel maps through left-comms C-List, allocates `ko15` for `v2.o+2002` * left run-queue `Notify(target=kp24, Fulfill(ko15))` * subscribers each get `dispatch.notify` -* `v1.o-1002` allocated for `ko15` in left-vat c-list +* `v1.o-1002` allocated for `ko15` in left-vat C-List * left-vat gets `dispatch.notify(target=p+104, Fulfill(o-1002))` @@ -1665,9 +1665,9 @@ which messages must be delivered. immediately), but that probably has ordering consequences. * Dean pointed out an important performance improvement, Promises which are resolved/rejected to data (or forwarded?) should be removed from the - resolving vat's c-list right away. We'd need an extra message in the future + resolving vat's C-List right away. We'd need an extra message in the future if that vat ever sends that promise again, but apparenly the vast majority - of the time it never will, so pruning the c-list immediately is a big win. + of the time it never will, so pruning the C-List immediately is a big win. This might interfere with having the kernel handle dumped queued messages. Why do this for data+forward but not for fulfill? * Forwarded promises must not create cycles. Vats should not be able to trick From 698ef2a30190f54950552dbd2201fb8b7406686c Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 18:34:18 -0500 Subject: [PATCH 08/30] docs(swingset): Normalize the capitalization of "Object table" and "Promise table" --- packages/SwingSet/docs/delivery.md | 58 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 284fc9450ef..ff4fa4a30e9 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -378,7 +378,7 @@ prefer to avoid deserializing the messages until their resolution is known, to avoid a wasteful reserialization cycle. If the deciding Vat has *not* opted into pipelining, the messages are queued -in the kernel's Promise Table entry instead. They remain there until the +in the kernel's Promise table entry instead. They remain there until the deciding vat uses `syscall.resolve()` to resolve that Promise. At that point, the behavior depends upon the type of resolution; see the discussion of `syscall.resolve()` below for details. @@ -836,9 +836,9 @@ Vat-2. The initial conditions are that Vat-1 somehow has a reference to an export of Vat-2 that we'll name `bob`. -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 -* Kernel Promise Table: empty +* Kernel Promise table: empty * Kernel run-queue: empty * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) @@ -888,7 +888,7 @@ Decider is set to None since it is being allocated in the context of a The `Pending Send` is appended to the run-queue. -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: None, subscribers: [])` * Kernel run-queue: * `Send(target: ko1, message: {.. result=kp24})` @@ -900,7 +900,7 @@ The `syscall.subscribe(p+104)` causes the PromiseID to be looked up in the kernel promise table, yielding `kp24`. Vat-1 is then added to the `subscribers` list. -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` The run-queue is cycled, and this Send comes to the top. This looks up @@ -915,7 +915,7 @@ owner (Vat-1) is different than the target (Vat-2), so a new entry is allocated, and the vat gets `v2.p-2105`. The Decider for `kp24` is set to `vat-2` because we're about to deliver the message to Vat-2. -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) @@ -974,9 +974,9 @@ The two `send` calls will look like: And after those sends, the kernel state will look like this: -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: @@ -993,7 +993,7 @@ Vat-2 will get the same `dispatch.deliver(target=o+2001, msg={method: "foo", args: "[]", slots=[], result=p-2015})` as before, and we'll get these changes to the kernel state: -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: @@ -1008,7 +1008,7 @@ that its target (`kp24`) is in the Unresolved state, and looks up the Decider the message to Vat-2, which receives it as `dispatch.deliver(target=p-2015, msg={method: "bar" .. result=p-2016)`. The kernel state during this call is: -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * Vat-2 C-List: @@ -1027,10 +1027,10 @@ Imagine the previous scenario (`bob!foo()!bar()`), but now `bob` resolves the `foo()` result promise to point at a third object `carol` in Vat-3. The relevant kernel state looks like: -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 * `ko2` (carol): owner= vat-3 -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * Vat-1 C-List: @@ -1051,7 +1051,7 @@ Unresolved, and that the Decider matches. The resolution is mapped to `ko2`, and the promise table is updated. The kernel queues notifications to the only subscriber (Vat-1): -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Resolved(fulfill(ko2))` * Kernel run-queue: * `Notify(subscriber: vat-1, subject: kp24)` @@ -1062,7 +1062,7 @@ structure that came out of `dispatch.notify` is sent unmodified back into `syscall.send`, but the target is now the resolution of the promise: `send(target=ko2, msg={method: "bar", .. result=p-2016})`. -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Resolved(fulfill(ko2))` * Kernel run-queue: * `Notify(subscriber: vat-1, subject: kp24)` @@ -1105,9 +1105,9 @@ Again, the two `send` calls will look like: And after those sends, the kernel state will look like this: -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: @@ -1124,7 +1124,7 @@ Vat-2 will get the same `dispatch.deliver(target=o+2001, msg={method: "foo", args: "[]", slots=[], result=p-2015})` as before, and we'll get these changes to the kernel state: -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: @@ -1138,7 +1138,7 @@ that its target (`kp24`) is in the Unresolved state, the kernel sees that `vat-2` does not accept pipelined messages. So instead of a `dispatch.deliver`, it queues the message within the Promise: -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1], queue: [{method="bar", ..}])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Vat-2 C-List: @@ -1151,14 +1151,14 @@ object "quux", the lower-level code will call will update the Promise table and re-queue the old messages, as well as scheduling notification for the subscribers: -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 * `ko3` (quux): owner= vat-2 * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) * `v2.p-2015 <-> kp24` (import of foo() result) * `v2.o+2022 <-> ko3` (export of quux) -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Resolved(target(ko3))` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: @@ -1177,11 +1177,11 @@ import from Vat-2 named `bob` as before, an import from Vat-3 named `carol`, and a Promise received from Vat-2 named `p2`. We're going to send all of these, plus a local Promise, to `carol`. -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 * `ko2` (carol): owner= vat-3 * `ko3` (alice): owner= vat-1 -* Kernel Promise Table: +* Kernel Promise table: * `kp22: state: Unresolved(decider: vat-2, subscribers: [vat-1])` (p2) * Kernel run-queue: empty * Vat-1 C-List: @@ -1204,11 +1204,11 @@ the translation layer in Vat-1 allocates a new local PromiseID for it (say `send(target=o-1002, msg={method: "foo", args: "..", slots=[o+1044, o-1001, o-1002, p-1052, p+103], result=p+104})`. The kernel state now looks like: -* Kernel Object Table: +* Kernel Object table: * `ko1` (bob): owner= vat-2 * `ko2` (carol): owner= vat-3 * `ko3` (alice): owner= vat-1 -* Kernel Promise Table: +* Kernel Promise table: * `kp22: state: Unresolved(decider: vat-2, subscribers: [vat-1])` (p2) * `kp23: state: Unresolved(decider: vat-2, subscribers: [])` (p3) * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` (p4) @@ -1238,7 +1238,7 @@ mapping to `v3.p-3042`. Finally the result `kp24` is mapped to `v3.p-3043` and its Decider is pointed at vat-3. The resulting state, just before dispatch, is: -* Kernel Promise Table: +* Kernel Promise table: * `kp22: state: Unresolved(decider: vat-2, subscribers: [vat-1])` (p2) * `kp23: state: Unresolved(decider: vat-2, subscribers: [])` (p3) * `kp24: state: Unresolved(decider: vat-3, subscribers: [vat-1])` (p4) @@ -1454,9 +1454,9 @@ lack of a single central kernel: Initial conditions: -* Left Kernel Object Table: +* Left Kernel Object table: * `ko1` (bob): owner= left-comms -* Kernel Promise Table: empty +* Kernel Promise table: empty * Kernel run-queue: empty * left-vat (id=1) kernel C-List: * `v1.o-1001 <-> ko1` (import of bob) @@ -1480,9 +1480,9 @@ the run-queue gets `Send(target=ko1, msg={name: foo, result=kp24})`, which eventually comes to the front and is delivered to left-comms. The left-kernel tables just before `dispatch.deliver()` is called will look like: -* Left Kernel Object Table: +* Left Kernel Object table: * `ko1` (bob): owner= left-comms -* Kernel Promise Table: +* Kernel Promise table: * `kp24: state: Unresolved(decider: v2, subscribers: [v1])` * left-vat (id=1) kernel C-List: * `v1.o-1001 <-> ko1` (import of bob) From 42612e7882c829027377392be1b742babe32c75a Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 18:58:14 -0500 Subject: [PATCH 09/30] docs(swingset): Tweak "Data Types" sections --- packages/SwingSet/docs/delivery.md | 44 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index ff4fa4a30e9..2655958d246 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -85,10 +85,9 @@ message, giving these promise-queue actions a chance to complete. +-------------------- Kernel ----------------------------+ ``` -## Vat Object Types +## Vat Data Types -The `syscall`/`dispatch` API references two kinds of identifiers: `Object` -and `Promise`: +The `syscall`/`dispatch` API references two kinds of identifiers: * `Object`: a callable object (the usual notion of "object" in the ocap discipline), with methods of various names and private state. These may be @@ -107,7 +106,8 @@ resolved. Both identifiers use integer index values to distinguish between different instances. When the Vat allocates the identifier, it uses a positive integer. When the kernel does the allocation, the vat gets a negative integer. This -index always points into a Capability List (the "C-List", described below). +index always points into a Capability List (the "C-List", described +[below](#kernel-side-c-lists)). In some cases, the kernel (or the vat) will allocate an entry in the C-List when it receives an index that it has not seen before. In other cases, the @@ -165,16 +165,16 @@ enum Resolution { } ``` -## Kernel Object Types +## Kernel Data Types -For each Vat type, there is a matching kernel type. These are distinct +For each Vat data type, there is a matching kernel data type. These are distinct values, with conversion from one to the other happening at the syscall/dispatch boundary. Some values are identical (the `body` string is left untouched), but the object/promise identifiers must be mapped through -the C-List tables. The kernel's `ObjectID(5)` may refer to a completely -different object than Vat A's `ObjectID(5)`. Keeping them in different types -helps avoid mistakes. (And note that Vat A's `ObjectID(5)` is probably -unrelated to Vat B's `ObjectID(5)`). +the [C-List tables](#kernel-side-c-lists). The kernel's `ObjectID(5)` may refer +to a completely different object than Vat A's `ObjectID(5)`. Keeping them in +different types helps avoid mistakes. (And note that Vat A's `ObjectID(5)` is +probably unrelated to Vat B's `ObjectID(5)`). The kernel maintains two tables to handle the identifiers which appear in Vat syscalls: one for Objects (Presences), and a second for Promises. Each table @@ -184,11 +184,11 @@ and `KernelPromiseID`. These keys are positive integers: all Objects come from some Vat, and all Promises are managed by the kernel, so from within the kernel, there is no notion of import-vs-export. -Each row of the kernel Object table remembers the VatID of the owner of that -object: the one which first exported it into the kernel in the argument of a -`syscall.send` or `syscall.resolve`. Messages sent to this object from other -Vats (via `syscall.send`) must be routed to the owning Vat and delivered with -a `dispatch.deliver`. +Each row of the kernel Object table remembers the object's "owner" (the VatID +that first exported it into the kernel in the argument of a `syscall.send` or +`syscall.resolve`). Messages sent to the object from other Vats (via +`syscall.send`) must be routed to the owning Vat and delivered with a +`dispatch.deliver`. Each row of the kernel Promise table remembers the current promise state and any related data. There is one unresolved state, and four resolved states @@ -197,9 +197,10 @@ in other tables). Each contains some additional state-specific data: * `Unresolved`: includes an optional Decider VatID, list of subscribers (VatIDs), and queue of pending messages -* `Fulfilled`: includes an ObjectID (an Export) to which it was resolved -* `Data`: includes resolution data (body+slots) -* `Rejected`: includes the rejection data (body+slots, maybe an Error object) +* `Fulfilled`: includes the ObjectID with which it was fulfilled +* `Data`: includes CapData (body+slots) with which it was fulfilled +* `Rejected`: includes the CapData (body+slots, maybe an Error object) with + which it was rejected * `Forwarded`: includes the `KernelPromiseID` to which it was forwarded The kernel also maintains a "run-queue", which is populated with pending @@ -208,7 +209,7 @@ deliveries, each of which references a variety of kernel-side objects. Finally, the kernel maintains C-List (Capability List) tables for each Vat, which map vat-side references into kernel Object/Promise references. For each vat, there are two bidirectional tables: Objects (Imports and Exports) map to -the kernel Object table, and Promise IDs map to the kernel Promise table. +the kernel Object table, and Promises map to the kernel Promise table. ``` struct VatID(u32); @@ -219,13 +220,14 @@ struct KernelPromiseID(u32); struct KernelObject { owner: VatID, } + struct KernelObjectTable { objects: HashMap, next_id: u32, } -// the kernel has types like Message, CapData, and CapSlot, which -// correspond to Vat types with the same names +// the kernel has data types like Message, CapData, and CapSlot, which +// correspond to Vat data types with the same names enum KernelPromise { Unresolved { From 90bd2543bffbd58fecfbeb31c4b7a0f13555ecbe Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:00:59 -0500 Subject: [PATCH 10/30] docs(swingset): Tweak "Vat Message Types" section --- packages/SwingSet/docs/delivery.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 2655958d246..743e04dfa2b 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -149,7 +149,7 @@ enum CapSlot { Object(ObjectID), } struct CapData { - body: Vec, + body: String, slots: Vec, } struct Message { @@ -275,25 +275,27 @@ be recycled, but it seems less confusing to simply retire the number. ## Vat Message Types -We use the term `CapData` to mean a piece of data that can include capability -references. Each reference is known as a `CapSlot`. The data is serialized -into our [augmented form of JSON](https://github.com/agoric/marshal), which -uses special `@qclass` keys to represent things not normally expressible by -JSON (such as `NaN`, `-0`, `Infinity`, `undefined`, BigInts, and `CapSlot` -references). Each appearance of a `CapSlot` causes a Vat reference (`Object` -or `Promise`) to be added to a list named `slots`, and a reference to the new -slot index gets inserted into the JSONified data structure . The serialized -`CapData` thus consists of the JSON-encoded string (named `body`) and the -list of slots (named `slots`). As this `CapData` travels from one Vat, into -the kernel, and off to some other vat, the `body` remains untouched, but the -`slots` are remapped at each vat/kernel boundary. +We use the term `CapData` to describe a piece of data that can include +capability references. Each reference is known as a `CapSlot`. The data is +serialized into into our +[augmented form of JSON](https://github.com/endojs/endo/tree/master/packages/marshal), +which uses special `@qclass` keys to represent things not normally expressible +by JSON (such as `NaN`, `Infinity`, `undefined`, BigInts, and `CapSlot` +references) or not preserved by normal serialization/deserialization (such as +`-0`). Each appearance of a `CapSlot` causes a Vat reference (`Object` or +`Promise`) to be added to a list named `slots`, and a reference to the new slot +index gets inserted into the JSONified data structure . The serialized `CapData` +thus consists of the JSON-encoded string (named `body`) and the list of slots +(named `slots`). As this `CapData` travels from one Vat, into the kernel, and +off to some other vat, the `body` remains untouched, but the `slots` are +remapped at each vat/kernel boundary. A `Message` is the method invocation first given to `syscall.send` for transmission to some other Vat, then stored in the kernel run-queue, then finally arriving at the target vat inside a `dispatch.deliver` call. The Message includes the method name which should be invoked, the `CapData` arguments to be included, and an optional result identifier (a Promise). The -SwingSet calling model has only positional (not keyword) arguments, hence the +SwingSet calling model has only positional (not keyword) arguments, hence `CapData.body` always deserializes to an array. The Message does not include the target, since that changes over time (it From 22979db76265a3cee8d99665c841ba965aaed0cf Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:35:42 -0500 Subject: [PATCH 11/30] docs(swingset): Tweak "Pipelining" section --- packages/SwingSet/docs/delivery.md | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 743e04dfa2b..a312afebc3e 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -346,23 +346,24 @@ machine. ```js const recordPromise = E(table).getRecord(identifier); const balancePromise = E(recordPromise).getBalance(); -balancePromise.then(balance => console.log(`balance: ${balance}`); +balancePromise.then(balance => console.log(`balance: ${balance}`)); ``` In SwingSet, pipelining is most (only?) useful on the Comms Vat. The local kernel shares a host with the local vats, so the latency is minimal. However two messages aimed at the same remote machine, through the Comms Vat, would -suffer unnecessary roundtrips unless the second can be delivered earlier. So -each Vat, when it is added, can include an option flag that says "I want to -received pipelined messages early". The default, used by everything except -the Comms Vat, means "I want the kernel to queue those messages, not me". +suffer unnecessary roundtrips unless the second can be delivered before receipt +of a response to the first. So each Vat, when it is added, can set an +`enablePipelining` flag that opts it in to receiving pipelined messages early. +The default, used by everything except the Comms Vat, is unset and requests that +the kernel queue such messages. ```js const config = await loadBasedir(basedir); -config.vats.set('comms', - { sourcepath: getCommsSourcePath(), - options: { enablePipelining: true }, - }); +config.vats.set('comms', { + sourcepath: getCommsSourcePath(), + options: { enablePipelining: true }, +}); ``` (open question: another option would be for `dispatch.deliver()` to return a @@ -385,16 +386,15 @@ If the deciding Vat has *not* opted into pipelining, the messages are queued in the kernel's Promise table entry instead. They remain there until the deciding vat uses `syscall.resolve()` to resolve that Promise. At that point, the behavior depends upon the type of resolution; see the discussion of -`syscall.resolve()` below for details. - -When the initial pair of messages are submitted with `syscall.send()`, the -run-queue will have two pending deliveries: the first is targeting an object -in some Vat, and the second targets a Promise (the `result` promise-ID of the -first message). Until the first message is delivered, the result Promise has -no Decider, so the second message cannot be delivered. But that's ok, because -by the time the second message gets to the front of the run queue, the first -will have been delivered, setting the Decider of the result Promise to some -vat, providing a place to deliver the second one (or the knowledge that the +`syscall.resolve()` [below](#syscallresolve) for details. + +When a `syscall.send()` is submitted, the run-queue will have two pending +deliveries: the first is targeting an object in some Vat, and the second targets +the `result` PromiseID. Until the first message is delivered, the result Promise +has no Decider, so the second message cannot be delivered. But that's ok, +because by the time the second message gets to the front of the run queue, the +first will have been delivered, setting the Decider of the result Promise to +some vat, providing a place to deliver the second one (or the knowledge that the vat wants the kernel to queue it instead). When we implement Flows or escalators or some other priority mechanism, we From b20eacd257e5728d8c7e256f16dc15b19ed83cfe Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:43:13 -0500 Subject: [PATCH 12/30] docs(swingset): Normalize parentheses in syscall/dispatch method references --- packages/SwingSet/docs/delivery.md | 50 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index a312afebc3e..a39132ca4ff 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -185,10 +185,10 @@ from some Vat, and all Promises are managed by the kernel, so from within the kernel, there is no notion of import-vs-export. Each row of the kernel Object table remembers the object's "owner" (the VatID -that first exported it into the kernel in the argument of a `syscall.send` or -`syscall.resolve`). Messages sent to the object from other Vats (via -`syscall.send`) must be routed to the owning Vat and delivered with a -`dispatch.deliver`. +that first exported it into the kernel in the argument of a `syscall.send()` or +`syscall.resolve()`). Messages sent to the object from other Vats (via +`syscall.send()`) must be routed to the owning Vat and delivered with a +`dispatch.deliver()`. Each row of the kernel Promise table remembers the current promise state and any related data. There is one unresolved state, and four resolved states @@ -290,9 +290,9 @@ thus consists of the JSON-encoded string (named `body`) and the list of slots off to some other vat, the `body` remains untouched, but the `slots` are remapped at each vat/kernel boundary. -A `Message` is the method invocation first given to `syscall.send` for +A `Message` is the method invocation first given to `syscall.send()` for transmission to some other Vat, then stored in the kernel run-queue, then -finally arriving at the target vat inside a `dispatch.deliver` call. The +finally arriving at the target vat inside a `dispatch.deliver()` call. The Message includes the method name which should be invoked, the `CapData` arguments to be included, and an optional result identifier (a Promise). The SwingSet calling model has only positional (not keyword) arguments, hence @@ -308,7 +308,7 @@ even as it gets queued and forwarded from one place to another. The Vat Message `result` identifier, if present, must refer to a Promise for which the sending Vat has resolution authority. There are three -possibilities, and `syscall.send` will reject the message (terminating the +possibilities, and `syscall.send()` will reject the message (terminating the Vat) unless the `result` ID falls into one of these categories: * A brand new Promise was created just for the result slot. The ID will be a @@ -417,13 +417,13 @@ the kernel, the decider vat would not get access to those Meters. When a pipelining-aware Vat resolves a Promise, and then forwards the previously queued messages which it received before that resolution, it can return the Message objects unchanged back into the kernel (with -`syscall.send`), keeping the `result` identifiers exactly the same. +`syscall.send()`), keeping the `result` identifiers exactly the same. ## Descriptive Conventions The Objects and Promises are represented in debug logs with a single-letter prefix (`o` or `p`), a sign, and a number. They also include a Vat ID prefix -(`vNN.`). So when Vat 2 does a `syscall.send` that targets an import (a +(`vNN.`). So when Vat 2 does a `syscall.send()` that targets an import (a kernel-allocated Object identifier, hence negative), and includes an argument which is a local object (an export, hence positive), and specifies a result that is a new local Promise, the logs might say `v2.send(target=o-4, @@ -501,7 +501,7 @@ There are a few restrictions on the API: `dispatch.deliver()` will always be owned by the receiving Vat (either an ObjectID allocated by this Vat, or a PromiseID for which this Vat is the Decider). -* The `Message.result` in a `syscall.send` must either be a new vat-allocated +* The `Message.result` in a `syscall.send()` must either be a new vat-allocated (positive) PromiseID, or a previously-allocated PromiseID for which the calling Vat is the Decider, or a PromiseID that was previously received as the result of an inbound message. @@ -603,12 +603,12 @@ hear about their own Promises, but it is legal. ### syscall.resolve() -`syscall.resolve` is used to resolve one or more Promises (usually just one, +`syscall.resolve()` is used to resolve one or more Promises (usually just one, but occasionally a batch of mutually-referencing Promises must be resolved in a single syscall because their identifiers are aggressively retired immediately after translation). -The subject of a `syscall.resolve` must either be a new Promise ID, or a +The subject of a `syscall.resolve()` must either be a new Promise ID, or a pre-existing one for which the calling Vat is the Decider. The `KernelPromise` must be in the `Unresolved` state. If any of these conditions are not met, the Vat calling `resolve` will be terminated. It doesn't matter @@ -637,7 +637,7 @@ The `resolution` has several forms, and we assign a different name to each. As the `syscall.resolve()` is processed by the kernel, all slots in the `resolution` should be mapped just like the `Message` slots in -`syscall.send`. If the resolution is `Forward`, the new promise must be +`syscall.send()`. If the resolution is `Forward`, the new promise must be different than the one being resolved, and must not result in a cycle. (TODO could one Vat force a second one into unknowingly creating a cycle?). @@ -721,7 +721,7 @@ https://github.com/Agoric/agoric-sdk/issues/2724 for details. The Kernel's run-queue holds two kinds of pending operations: `Send` (enqueued when a Vat does `syscall.send()`), and `Notify` (enqueued when one -does `syscall.resolve`). +does `syscall.resolve()`). ``` enum KernelSlot { @@ -1062,8 +1062,8 @@ subscriber (Vat-1): Control returns to Vat-2, which now must send all the messages that were previously queued for the Promise it just resolved. The same Message -structure that came out of `dispatch.notify` is sent unmodified back into -`syscall.send`, but the target is now the resolution of the promise: +structure that came out of `dispatch.notify()` is sent unmodified back into +`syscall.send()`, but the target is now the resolution of the promise: `send(target=ko2, msg={method: "bar", .. result=p-2016})`. * Kernel Promise table: @@ -1140,7 +1140,7 @@ to the kernel state: Now, when the `bar` message reaches the front of the queue the kernel finds that its target (`kp24`) is in the Unresolved state, the kernel sees that `vat-2` does not accept pipelined messages. So instead of a -`dispatch.deliver`, it queues the message within the Promise: +`dispatch.deliver()`, it queues the message within the Promise: * Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1], queue: [{method="bar", ..}])` @@ -1202,7 +1202,7 @@ Vat-1 now does `p3 = make_promise(); p4 = carol!foo(alice, bob, carol, p2, p3)`. The `make_promise()` creates a regular Vat-Code promise (a native Javascript Promise). Nothing special happens until the `foo()` is processed into a -`syscall.send`. During that processing, as the `p3` argument is serialized, +`syscall.send()`. During that processing, as the `p3` argument is serialized, the translation layer in Vat-1 allocates a new local PromiseID for it (say `p+103`). It allocates `p+104` for the result (p4). The resulting syscall is `send(target=o-1002, msg={method: "foo", args: "..", slots=[o+1044, o-1001, @@ -1328,10 +1328,10 @@ exporter would *send* that object as `ro-2` to please the importer). The message names are also different. In the local-machine Vat-Kernel-Vat flow, the first Vat's outbound message has a different name than the inbound -message (`syscall.send` becomes `dispatch.deliver`, `syscall.resolve` becomes -`dispatch.notify`). In the remote-machine CommsVat-CommsVat flow, there is no -kernel in the middle, so whatever the first Comms Vat sends is exactly what -the second Comms Vat receives. In keeping with the receiver-centric +message (`syscall.send()` becomes `dispatch.deliver()`, `syscall.resolve()` +becomes `dispatch.notify()`). In the remote-machine CommsVat-CommsVat flow, +there is no kernel in the middle, so whatever the first Comms Vat sends is +exactly what the second Comms Vat receives. In keeping with the receiver-centric convention, We use `deliver` for message delivery, and `notify` for promise resolution. @@ -1557,14 +1557,14 @@ be added for the result promise (`v4.p-5002`), and invokes #### response Now suppose right-vat resolves the result promise to a new local object -(`v4.o+5003`). We trace the `syscall.resolve` back to the left-vat: +(`v4.o+5003`). We trace the `syscall.resolve()` back to the left-vat: * right-vat: `syscall.resolve(subject=p-5002, Fulfill(o+5003))` * right kernel right-vat C-List: `v4.o+5003 <-> ko3` * right run-queue `Notify(target=kp6001, Fulfill(ko3))` * notification gets to front, right kernel promise table updated * `kp6001: state = FulfillToTarget(ko3)` - * subscribers each get a `dispatch.notify` + * subscribers each get a `dispatch.notify()` * right-comms: `dispatch.notify(target=p+4002, Fulfill(o-4003))` * right-comms promise routing table lookup (`p+4002`) says destination machine is `left` * right-comms allocates `ro+3002` for the object `o-4003` @@ -1574,7 +1574,7 @@ Now suppose right-vat resolves the result promise to a new local object * left-comms submits `syscall.resolve(target=p-2015, Fulfill(o+2002))` * left kernel maps through left-comms C-List, allocates `ko15` for `v2.o+2002` * left run-queue `Notify(target=kp24, Fulfill(ko15))` -* subscribers each get `dispatch.notify` +* subscribers each get `dispatch.notify()` * `v1.o-1002` allocated for `ko15` in left-vat C-List * left-vat gets `dispatch.notify(target=p+104, Fulfill(o-1002))` From 27c9c8ecda11daa99909d11260e127fc42b0e2be Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:47:53 -0500 Subject: [PATCH 13/30] docs(swingset): Tweak "Descriptive Conventions" section --- packages/SwingSet/docs/delivery.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index a39132ca4ff..20288f12a03 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -430,8 +430,8 @@ that is a new local Promise, the logs might say `v2.send(target=o-4, msg={name: foo, slots:[o+3], result=p+5})`. The Promise that results from this `send` is also labelled `p+5`. -The kernel types use `ko` and `kp`. The run-queue for that message would be -printed as `target=ko6, msg={name: foo, slots:[ko2], result=kp8}`. +The kernel types use `ko` and `kp`. The run-queue entry for that message would +be printed as `target=ko6, msg={name: foo, slots:[ko2], result=kp8}`. The Comms Vat creates inter-machine messages that refer to Objects and Promises in per-remote-machine C-List tables that live inside each Comms Vat. From 993e9035e3478525590200c75fa62c66d01bc079 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:48:36 -0500 Subject: [PATCH 14/30] docs(swingset): Clarify that `setImmediate` is specific to Javascript --- packages/SwingSet/docs/delivery.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 20288f12a03..c2cca69f042 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -56,9 +56,9 @@ All `dispatch` functions can schedule near-term work by using `Promise.resolve()` to append something to the promise queue. This work will be completed after the current stack unwinds, but before the turn completes and the `dispatch` is retired. This allows Vats to use eventual-send -internally, to protect against plan-interference hazards. The kernel waits -for a `setImmediate` on the timer/IO queue before proceeding to the next -message, giving these promise-queue actions a chance to complete. +internally, to protect against plan-interference hazards. For Javascript vats, +the kernel implements this draining of the promise-queue by waiting for a +`setImmediate` on the timer/IO queue before proceeding to the next message. ``` +-- Vat A ---+ +- Vat B --+ +- Vat C --+ From f79d2b79ed3dc4a9775b84919d9382bc67e9d378 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 21:55:37 -0500 Subject: [PATCH 15/30] docs(swingset): Tweak "Syscall/Dispatch API" section --- packages/SwingSet/docs/delivery.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index c2cca69f042..5c571d3b809 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -508,11 +508,11 @@ There are a few restrictions on the API: Some invocation patterns are legal, but unlikely to be useful: -* `syscall.send(target=)` can be any `CapSlot`, however it is a bit silly to - reference an Object that lives on the local Vat, or a Promise for which the - local Vat is the Decider. In both cases, the Vat could have delivered the - message directly, instead of taking the time and effort of going through - the kernel's run-queue. On the other hand, this may achieve certain +* The `target` of a `syscall.send()` can be any `CapSlot`, however it is a bit + silly to reference an Object that lives on the local Vat, or a Promise for + which the local Vat is the Decider. In both cases, the Vat could have + delivered the message directly, instead of taking the time and effort of going + through the kernel's run-queue. On the other hand, this may achieve certain ordering properties better. In some places, `dispatch.deliver()` is named `message`: we're still in the From bed0a4647bd199ceab89d01f075906a1e7b2ec28 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 22:00:47 -0500 Subject: [PATCH 16/30] docs(swingset): Replace "legal" with "valid" --- packages/SwingSet/docs/delivery.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 5c571d3b809..b9accf75f23 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -506,7 +506,7 @@ There are a few restrictions on the API: calling Vat is the Decider, or a PromiseID that was previously received as the result of an inbound message. -Some invocation patterns are legal, but unlikely to be useful: +Some invocation patterns are valid, but unlikely to be useful: * The `target` of a `syscall.send()` can be any `CapSlot`, however it is a bit silly to reference an Object that lives on the local Vat, or a Promise for @@ -599,7 +599,7 @@ not receive a `dispatch.notify()` for a Promise unless they first use The `PromiseID` argument to `subscribe()` is translated through the C-List just like a `CapSlot` in `syscall.send()`. It is not common for this to cause the allocation of a `KernelPromise`, because Vats don't usually subscribe to -hear about their own Promises, but it is legal. +hear about their own Promises, but it is valid. ### syscall.resolve() @@ -1275,7 +1275,7 @@ function foo(arg) { when it returns a previously-exported promise (aka `p+1`), the support layer should do `syscall.resolve(p-4, p+1)`. -These situations are legal/sensible and should be documented (and tested!), +These situations are valid/sensible and should be documented (and tested!), but it may require some creativity to come up with Vat Code that could produce them: From d9d2cff4a95aff1d9bc3416ded67eeed8d0a58f4 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 22:43:32 -0500 Subject: [PATCH 17/30] docs(swingset): Tweak "Slot Translation" sections --- packages/SwingSet/docs/delivery.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index b9accf75f23..6325a30e988 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -606,7 +606,8 @@ hear about their own Promises, but it is valid. `syscall.resolve()` is used to resolve one or more Promises (usually just one, but occasionally a batch of mutually-referencing Promises must be resolved in a single syscall because their identifiers are aggressively retired -immediately after translation). +immediately after translation). \[TODO: resolve inconsistency between this claim +and [Syscall/Dispatch API](#syscalldispatch-api)] The subject of a `syscall.resolve()` must either be a new Promise ID, or a pre-existing one for which the calling Vat is the Decider. The @@ -622,18 +623,16 @@ whether the Promise was allocated by this Vat or a different one. The `resolution` has several forms, and we assign a different name to each. * `Fulfill(ObjectID)`: the Promise is "fulfilled" to a callable Object -* `Forward(PromiseID)`: the Promise is now "forwarded": it has not settled to - a specific object, but the original Promise is effectively replaced with - some other Promise -* `Data(CapData)`: the Promise is "fulfilled" to data, rather than a callable +* `Data(CapData)`: the Promise is "fulfilled" to data, rather than to a callable object. It is an error to send messages to data. * `Reject(CapData)`: the Promise is "rejected" to data which we call the "error object". Sending a message to a Rejected Promise causes the result of that message to be Rejected too, known as "rejection contagion". - -. Any `result` - promises in the queued messages should be rejected with the same `CapData` - provided as `resolution`. +* `Forward(PromiseID)`: the Promise is now "forwarded": it has not settled to + a specific object, but the original Promise is effectively replaced with + some other Promise. + Any `result` promises in the queued messages should be rejected with the same + `CapData` provided as `resolution`. As the `syscall.resolve()` is processed by the kernel, all slots in the `resolution` should be mapped just like the `Message` slots in @@ -686,9 +685,9 @@ Note: we no longer have distinct syscalls or states for the different flavors of resolved promises. Instead, each resolved promise is recorded with a boolean `isRejected` flag, and a `CapData` to hold the resolution data. The `Fulfill` and `Data` flavors both set `isRejected = false`, and only differ -by the contents of the resolution data. If `!isRejected` and the data holds a -single Object, then messages can be sent to the promise, and they will be -passed along to the Object. Otherwise messages sent to the promise will +by the contents of the resolution data. If `isRejected` is false and the data +holds a single Object, then messages can be sent to the promise, and they will +be passed along to the Object. Otherwise messages sent to the promise will result in a rejection of some form. ### syscall.exit(isFailure, info) @@ -816,9 +815,10 @@ The `subject` argument of `dispatch.notify()` specifies which Promise is being resolved. It is always translated into a `PromiseID`. `dispatch.notify()` will be sent to all Vats which had subscribed to hear -about the resolution of that Promise. The Decider vat for a Promise will not -generally subscribe themselves, since they are the ones causing the Promise -to become resolved, so they have no need to hear about it from the kernel. +about the resolution of that Promise [TODO: document ordering +relevance/constraints]. The Decider vat for a Promise will not generally +subscribe themselves, since they are the ones causing the Promise to become +resolved, so they have no need to hear about it from the kernel. The `Resolution` value of `dispatch.notify()` may contain slots, which are translated just like `Message.args`. From 6f1c12fa35006b8101cd3bde9b4f8d9e6e7f2873 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 23:02:18 -0500 Subject: [PATCH 18/30] docs(swingset): Use proper ellipses --- packages/SwingSet/docs/delivery.md | 50 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 6325a30e988..eeba8c1be4c 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -464,7 +464,7 @@ this message into the kernel: v2.send(target=o-4, msg={name: foo, slots:[o+3], result=p+5}) ``` -.. the Vat-2 C-List might contain: +…the Vat-2 C-List might contain: | Vat 2 object | Kernel object | Allocator | Decider | | --- | --- | --- | --- | @@ -854,7 +854,7 @@ allocate a new local promise/resolver ID (for the result `p1`), say it chooses `104`, and then invokes `syscall.send(target=o-1001, msg={method: "foo", args: "[]", slots=[], result=p+104})`. Vat-1 remembers `p+104` as the identifier for the result Promise. We assume that Vat-1 uses `p1` later (i.e. -`p1.then(stuff..)`), so it also does a `syscall.subscribe(p+104)`. +`p1.then(…)`), so it also does a `syscall.subscribe(p+104)`. ``` @@ -895,7 +895,7 @@ The `Pending Send` is appended to the run-queue. * Kernel Promise table: * `kp24: state: Unresolved(decider: None, subscribers: [])` * Kernel run-queue: - * `Send(target: ko1, message: {.. result=kp24})` + * `Send(target: ko1, message: {…, result=kp24})` * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) * `v1.p+104 <-> kp24` (export of result promise) @@ -973,8 +973,8 @@ receiving pipelined messages. The two `send` calls will look like: -* `syscall.send(target=o-1001, msg={method: "foo", .. result=p+104})` -* `syscall.send(target=p+104, msg={method: "bar", .. result=p+105})` +* `syscall.send(target=o-1001, msg={method: "foo", …, result=p+104})` +* `syscall.send(target=p+104, msg={method: "bar", …, result=p+105})` And after those sends, the kernel state will look like this: @@ -984,8 +984,8 @@ And after those sends, the kernel state will look like this: * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: - * `Send(target: ko1, message: {method="foo", .. result=kp24})` - * `Send(target: kp24, message: {method="bar", .. result=kp25})` + * `Send(target: ko1, message: {method="foo", …, result=kp24})` + * `Send(target: kp24, message: {method="bar", …, result=kp25})` * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) * `v1.p+104 <-> kp24` (export of foo() result promise) @@ -1001,7 +1001,7 @@ to the kernel state: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: - * `Send(target: kp24, message: {method="bar", .. result=kp25})` + * `Send(target: kp24, message: {method="bar", …, result=kp25})` * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) * `v2.p-2015 <-> kp24` (import of foo() result) @@ -1010,7 +1010,7 @@ Then the `bar` message reaches the front of the queue, and the kernel finds that its target (`kp24`) is in the Unresolved state, and looks up the Decider (`vat-2`). It sees that `vat-2` accepts pipelined messages, so it delivers the message to Vat-2, which receives it as `dispatch.deliver(target=p-2015, -msg={method: "bar" .. result=p-2016)`. The kernel state during this call is: +msg={method: "bar", …, result=p-2016)`. The kernel state during this call is: * Kernel Promise table: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` @@ -1064,13 +1064,13 @@ Control returns to Vat-2, which now must send all the messages that were previously queued for the Promise it just resolved. The same Message structure that came out of `dispatch.notify()` is sent unmodified back into `syscall.send()`, but the target is now the resolution of the promise: -`send(target=ko2, msg={method: "bar", .. result=p-2016})`. +`send(target=ko2, msg={method: "bar", …, result=p-2016})`. * Kernel Promise table: * `kp24: state: Resolved(fulfill(ko2))` * Kernel run-queue: * `Notify(subscriber: vat-1, subject: kp24)` - * `Send(target: ko2, message: {method="bar", .. result=kp25})` + * `Send(target: ko2, message: {method="bar", …, result=kp25})` (TODO: is it necessary/ok/bad that vat-1 sees the Notify before it sees the queued messages arrive? We could have Vat-2 invoke the syscalls in either @@ -1093,8 +1093,8 @@ the result promise into Vat-3's C-List: * `v3.o+3001 <-> ko2` (export of carol) * `v3.p-3031 <-> kp25` (bar() result promise) -and Vat-3 gets `dispatch.deliver(target=o+3001, message: {method="bar", .. -result=p-3031})`. +and Vat-3 gets +`dispatch.deliver(target=o+3001, message: {method="bar", …, result=p-3031})`. ### Pipelined send to a non-Comms vat @@ -1104,8 +1104,8 @@ be queued inside the kernel Promise, rather than being delivered to Vat-2. Again, the two `send` calls will look like: -* `syscall.send(target=o-1001, msg={method: "foo", .. result=p+104})` -* `syscall.send(target=p+104, msg={method: "bar", .. result=p+105})` +* `syscall.send(target=o-1001, msg={method: "foo", …, result=p+104})` +* `syscall.send(target=p+104, msg={method: "bar", …, result=p+105})` And after those sends, the kernel state will look like this: @@ -1115,8 +1115,8 @@ And after those sends, the kernel state will look like this: * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: - * `Send(target: ko1, message: {method="foo", .. result=kp24})` - * `Send(target: kp24, message: {method="bar", .. result=kp25})` + * `Send(target: ko1, message: {method="foo", …, result=kp24})` + * `Send(target: kp24, message: {method="bar", …, result=kp25})` * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) * `v1.p+104 <-> kp24` (export of foo() result promise) @@ -1132,7 +1132,7 @@ to the kernel state: * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: - * `Send(target: kp24, message: {method="bar", .. result=kp25})` + * `Send(target: kp24, message: {method="bar", …, result=kp25})` * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) * `v2.p-2015 <-> kp24` (import of foo() result) @@ -1143,7 +1143,7 @@ that its target (`kp24`) is in the Unresolved state, the kernel sees that `dispatch.deliver()`, it queues the message within the Promise: * Kernel Promise table: - * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1], queue: [{method="bar", ..}])` + * `kp24: state: Unresolved(decider: vat-2, subscribers: [vat-1], queue: [{method="bar", …}])` * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) @@ -1167,7 +1167,7 @@ scheduling notification for the subscribers: * `kp25: state: Unresolved(decider: None, subscribers: [vat-1])` * Kernel run-queue: * `Notify(subscriber: vat-1, subject: kp24)` - * `Send(target: ko3, message: {method="bar", .. result=kp25})` + * `Send(target: ko3, message: {method="bar", …, result=kp25})` When the Send gets to the front of the queue, it will deliver `bar()` into vat-2, which marks `kp25` as being decided by vat-2. @@ -1205,7 +1205,7 @@ Promise). Nothing special happens until the `foo()` is processed into a `syscall.send()`. During that processing, as the `p3` argument is serialized, the translation layer in Vat-1 allocates a new local PromiseID for it (say `p+103`). It allocates `p+104` for the result (p4). The resulting syscall is -`send(target=o-1002, msg={method: "foo", args: "..", slots=[o+1044, o-1001, +`send(target=o-1002, msg={method: "foo", args: "…", slots=[o+1044, o-1001, o-1002, p-1052, p+103], result=p+104})`. The kernel state now looks like: * Kernel Object table: @@ -1217,7 +1217,7 @@ o-1002, p-1052, p+103], result=p+104})`. The kernel state now looks like: * `kp23: state: Unresolved(decider: vat-2, subscribers: [])` (p3) * `kp24: state: Unresolved(decider: None, subscribers: [vat-1])` (p4) * Kernel run-queue: - * `Send(target=ko2, msg={method: "foo", args: "..", slots=[ko3, ko1, ko2, kp22, kp23], result=kp24})` + * `Send(target=ko2, msg={method: "foo", args: "…", slots=[ko3, ko1, ko2, kp22, kp23], result=kp24})` * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) * `v1.o-1002 <-> ko2` (import of carol) @@ -1255,7 +1255,7 @@ dispatch, is: * `v3.p-3043 <-> kp24` (result p4) Vat-2 then gets a `dispatch.deliver(target=o+3001, msg={method: "foo", args: -"..", slots=[o-3031, o-3032, o+3001, p-3041, p-3042], result=p-3043})`. +"…", slots=[o-3031, o-3032, o+3001, p-3041, p-3042], result=p-3043})`. ### TODO: more examples @@ -1271,7 +1271,7 @@ function foo(arg) { } ``` -`foo()` is invoked through an inbound `dispatch.deliver(.., result=p-4)`, and +`foo()` is invoked through an inbound `dispatch.deliver(…, result=p-4)`, and when it returns a previously-exported promise (aka `p+1`), the support layer should do `syscall.resolve(p-4, p+1)`. @@ -1658,7 +1658,7 @@ which messages must be delivered. whether we're consuming them or forwarding them elsewhere, but Dean recommended unmarshalling them immediately (to avoid confusion over tables they might reference which could change between now and delivery), then - remarshalling them later.. it depends on what vat-side tables are involved + remarshalling them later… it depends on what vat-side tables are involved and how they might change. * The message flow would be simpler if these queued messages could be dumped back into the kernel after a promise is resolved, and let the kernel deal From aa4709c85758da29bf5a0057fd02c4f9e0d47020 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 1 Mar 2022 23:10:05 -0500 Subject: [PATCH 19/30] docs(swingset): Tweak "Sample Message Delivery" section --- packages/SwingSet/docs/delivery.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index eeba8c1be4c..4adc52f1352 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -833,7 +833,8 @@ A Vat might be informed that one Promise has been resolved to another Promise ## Sample Message Delivery This section follows a variety of messages are they are sent from Vat-1 to -Vat-2. +Vat-2. Syntax like `foo!bar(baz)` is used to indicate a message to Object or +Promise `foo` with method "bar" and args `[ baz ]`. ### no arguments, resolve to data @@ -895,7 +896,7 @@ The `Pending Send` is appended to the run-queue. * Kernel Promise table: * `kp24: state: Unresolved(decider: None, subscribers: [])` * Kernel run-queue: - * `Send(target: ko1, message: {…, result=kp24})` + * `Send(target: ko1, message: {method: "foo", args: "[]", slots=[], result=kp24})` * Vat-1 C-List: * `v1.o-1001 <-> ko1` (import of bob) * `v1.p+104 <-> kp24` (export of result promise) @@ -1179,7 +1180,7 @@ Now let's examine how various arguments are managed. Our initial conditions give Vat-1 access to a previously-exported object `alice` (in Vat-1), an import from Vat-2 named `bob` as before, an import from Vat-3 named `carol`, and a Promise received from Vat-2 named `p2`. We're going to send all of -these, plus a local Promise, to `carol`. +these, plus a local Promise `p4`, to `carol`. * Kernel Object table: * `ko1` (bob): owner= vat-2 From 9c907c97a654ce720fd93c04de49e922d61a9352 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 00:13:12 -0500 Subject: [PATCH 20/30] docs(swingset): Tweak "Comms Protocol" section --- packages/SwingSet/docs/delivery.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 4adc52f1352..cca42ff18de 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -1297,20 +1297,20 @@ The kernel routes messages between multiple vats. The Comms Vat routes messages between the local kernel and (potentially) multiple external machines. In this way, the Comms Vat is like the kernel: it must maintain Object and Promise tables, and a C-List for each remote machine. To some -excent, the kernel is treated like just another remote machine: there is also +extent, the kernel is treated like just another remote machine: there is also a kernel-facing C-List (however kernel "messages", really syscalls, are delivered immediately, whereas messages to remote machines are asynchronous). The Comms Vat does not need to manage a run-queue (`dispatch()` causes an immediate external message), nor does each unresolved promise have a queue of messages (these are pipelined immediately). -The one wrinkle is that Vat-Vat connections are symmetric, which impacts the +The one wrinkle is that Vat→Vat connections are symmetric, which impacts the way these types are represented. The Vat-Kernel interface is conveniently asymmetric, so we can declare that positive index values are allocated by the Vat, while negative values are allocated by the kernel, and it doesn't matter which direction the messages are going. The naming scheme is "vat-centric". -When the messages travel from one Vat to the other, must instead speak in +When the messages travel from one Vat to the other, we must instead speak in terms of the sender and the receiver of any particular message. We declare that messages arriving at a Vat will use positive index values for objects that are allocated by the receiver, and negative for the ones allocated by @@ -1319,18 +1319,18 @@ externally-facing side of their per-machine C-Lists. As a result, the IDs inside inbound messages can be looked up in the C-List directly, but when sending outbound messages, all the IDs must have their signs flipped. -(The mnemonic philosophy is: vats are ego-centric, vat exports are the most -important thing in their self-centered world, so vat exports get the positive -number (`o+1`). Comms vats, being more worldly, are obsequiously polite, so -they always deliver a remote message in the form that will most please the -recipient, so when a machine's export is sent back to them, the +The mnemonic philosophy is that vats are ego-centric and vat exports are the +most important thing in their self-centered world, so vat exports get the +positive number (`o+1`). Comms vats, being more worldly, are obsequiously +polite, so they always deliver a remote message in the form that will most +please the recipient, so when a machine's export is sent back to them, the exporter+recipient will receive a positive number (`ro+2`), even though the -exporter would *send* that object as `ro-2` to please the importer). +exporter would *send* that object as `ro-2` to please the importer. -The message names are also different. In the local-machine Vat-Kernel-Vat +The message names are also different. In the local-machine Vat→Kernel→Vat flow, the first Vat's outbound message has a different name than the inbound message (`syscall.send()` becomes `dispatch.deliver()`, `syscall.resolve()` -becomes `dispatch.notify()`). In the remote-machine CommsVat-CommsVat flow, +becomes `dispatch.notify()`). In the remote-machine CommsVat→CommsVat flow, there is no kernel in the middle, so whatever the first Comms Vat sends is exactly what the second Comms Vat receives. In keeping with the receiver-centric convention, We use `deliver` for message delivery, and `notify` for promise @@ -1387,7 +1387,7 @@ struct RemoteCList { next_promise_id: u32, } -# the Comms Vat has exactly one CommsTables instance +// the Comms Vat has exactly one CommsTables instance struct CommsTables { remotes: HashMap, next_object_id: u32, @@ -1480,7 +1480,7 @@ Initial conditions: * right-vat (id=4) kernel C-List * `v4.o+5001 <-> ko2` (export of real bob) -left-vat does `p1 = bob ! foo()`. Left kernel accepts `syscall.send()` and +left-vat does `p1 = bob!foo()`. Left kernel accepts `syscall.send()` and the run-queue gets `Send(target=ko1, msg={name: foo, result=kp24})`, which eventually comes to the front and is delivered to left-comms. The left-kernel tables just before `dispatch.deliver()` is called will look like: From 407a1179e4757be38481d1b4c3c855179cedfcca Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 18:56:52 -0500 Subject: [PATCH 21/30] docs(swingset): Correct uses of "turn" that should be "crank" --- packages/SwingSet/docs/delivery.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index cca42ff18de..5c6c9dfadcd 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -34,11 +34,13 @@ primary syscall is named `syscall.send()`. Each vat is represented in the kernel as a `dispatch` object with methods that close over its internal state (cf. [Vat-Inbound Slot Translation](#vat-inbound-slot-translation)). A vat's code -executes when the kernel initiates a **turn** by invoking one of these methods, -primarily `dispatch.deliver()` (which delivers messages such as those produced -by `syscall.send()` from some other vat). The arguments to `deliver` are also -pure data, and are recorded in a transcript to enable replay-based orthogonal -persistence. +executes a **crank** (a sequence of **turns** ending when its microtask queue is +empty—i.e. when the execution context has run to completion and there are no +promise callbacks that should be invoked) when the kernel initiates a +**delivery** by invoking one of these methods, primarily `dispatch.deliver()` +(which delivers messages such as those produced by `syscall.send()` from some +other vat). The arguments to `deliver` are also pure data, and are recorded in a +transcript to enable replay-based orthogonal persistence. To enable transactional commitments, all state changes that might be made by syscalls are held in a transaction buffer (the "crank buffer") until the @@ -54,7 +56,7 @@ buffer, otherwise falling through to the persistent store. All `dispatch` functions can schedule near-term work by using `Promise.resolve()` to append something to the promise queue. This work will -be completed after the current stack unwinds, but before the turn completes +be completed after the current stack unwinds, but before the crank completes and the `dispatch` is retired. This allows Vats to use eventual-send internally, to protect against plan-interference hazards. For Javascript vats, the kernel implements this draining of the promise-queue by waiting for a @@ -708,7 +710,7 @@ millions, but only a few are active during any single delivery. Both keys and values are limited to strings at this time. Changes to this table are held in the crank buffer, just like any other state -changes, and are not flushed until the turn completes successfully. +changes, and are not flushed until the crank completes successfully. ### syscall.dropImports @@ -798,7 +800,7 @@ The `Message.result`, if present, is always translated into a `PromiseID`, and the Vat is given resolution authority over that promise. In the kernel promise table, the "Decider" field is set to point at the Vat just before `dispatch.deliver()` is called, enabling the Vat to call -`syscall.resolve(result)` during the turn. +`syscall.resolve(result)` during the crank. ### no dispatch.subscribe() @@ -950,7 +952,7 @@ run-queue: * Kernel run-queue: * `Notify(subscriber: vat-1, subject: kp24)` -The `dispatch.resolve()` returns, and Vat-2 finishes its turn. +The `dispatch.resolve()` returns, and Vat-2 finishes its crank. The run-queue is then cycled again, and the Notify is at the top. The kernel uses the `subscriber` to pick the Vat-1 C-List for inbound translation, and From b4849df2eb52648af5607789a6e783d87e2ac612 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 19:25:32 -0500 Subject: [PATCH 22/30] docs(swingset): Collapse together promise states "fulfilled" and "data" --- packages/SwingSet/docs/delivery.md | 91 ++++++++++++++---------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 5c6c9dfadcd..1a233188f16 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -160,10 +160,9 @@ struct Message { result: Option, } enum Resolution { - Fulfill(ObjectID), - Forward(PromiseID), - Data(CapData), + Fulfill(CapData), Reject(CapData), + Forward(PromiseID), } ``` @@ -193,14 +192,14 @@ that first exported it into the kernel in the argument of a `syscall.send()` or `dispatch.deliver()`. Each row of the kernel Promise table remembers the current promise state and -any related data. There is one unresolved state, and four resolved states -(however we may be able to optimize away some of them, e.g. by rewriting data -in other tables). Each contains some additional state-specific data: +any related data. There is one unresolved state and multiple resolved states +(some of which might be optimized away, e.g. by rewriting data in other tables). +Each contains some additional state-specific data: * `Unresolved`: includes an optional Decider VatID, list of subscribers (VatIDs), and queue of pending messages -* `Fulfilled`: includes the ObjectID with which it was fulfilled -* `Data`: includes CapData (body+slots) with which it was fulfilled +* `Fulfilled`: includes CapData (body+slots) with which it was fulfilled (note + that this can be a single ObjectID, which will often be the case) * `Rejected`: includes the CapData (body+slots, maybe an Error object) with which it was rejected * `Forwarded`: includes the `KernelPromiseID` to which it was forwarded @@ -237,8 +236,7 @@ enum KernelPromise { decider: Option, queued_messages: Vec, } - FulfilledToTarget(KernelObjectID), - FulfilledToData(CapData), + Fulfilled(CapData), Rejected(CapData), } @@ -572,14 +570,14 @@ Later, when this operation comes to the front, the kernel figures out how it should be dispatched based upon the target (object or promise) and its current state: -| Target | State | action | -| --- | --- | --- | -| Object | n/a | deliver to owning Vat | -| Promise | Unresolved | deliver to Decider Vat, or queue inside promise | -| Promise | Fulfilled | look up fulfilled object, recurse | -| Promise | Forward | look up forwarded promise, recurse | -| Promise | Data | queue `CannotSendToData` rejection to result | -| Promise | Rejected | queue rejection data to result | +| Target | State | action | +| --- | --- | --- | +| Object | n/a | deliver to owning Vat | +| Promise | Unresolved | deliver to Decider Vat, or queue inside promise | +| Promise | Fulfilled, to an Object | look up fulfilled object, recurse | +| Promise | Fulfilled, to data | queue `CannotSendToData` rejection to result | +| Promise | Rejected | queue rejection data to result | +| Promise | Forward | look up forwarded promise, recurse | The state of a Promise might change (from Unresolved to some flavor of Resolved) between the message being placed on the queue and it finally being @@ -624,9 +622,8 @@ whether the Promise was allocated by this Vat or a different one. The `resolution` has several forms, and we assign a different name to each. -* `Fulfill(ObjectID)`: the Promise is "fulfilled" to a callable Object -* `Data(CapData)`: the Promise is "fulfilled" to data, rather than to a callable - object. It is an error to send messages to data. +* `Fulfill(CapData)`: the Promise is "fulfilled" to a callable Object or other + data. It is an error to send messages to data. * `Reject(CapData)`: the Promise is "rejected" to data which we call the "error object". Sending a message to a Rejected Promise causes the result of that message to be Rejected too, known as "rejection contagion". @@ -653,16 +650,16 @@ When the `notify` reaches the front of the queue, the vat invoked with a After queueing any `notify`s, if the Promise table holds any queued messages, these must be dispatched according to the resolution type: -* `Fulfill`: Re-queue all Messages to the new target object. The new - `PendingDelivery`s are appended to the back of the run-queue. +* `Fulfill`, to an Object: Re-queue all Messages to the new target object. The + new `PendingDelivery`s are appended to the back of the run-queue. +* `Fulfill`, to data: the queued Messages are discarded, however if they have a + `result` promise, a `CannotSendToData` error object is created, and the + results are Rejected with that error object +* `Reject`: the queued Messages are discarded, but a copy of the rejection + data is used to Reject any `result` promises they included * `Forward`: All messages are re-queued to the new target promise. When they get to the front, they may be delivered to the deciding vat (if it has opted-in to pipelining) or queued in the new Promise's table entry. -* `Data`: the queued Messages are discarded, however if they have a `result` - promise, a `CannotSendToData` error object is created, and the results are - Rejected with that error object -* `Reject`: the queued Messages are discarded, but a copy of the rejection - data is used to Reject any `result` promises they included Finally, the kernel returns control to the Vat. @@ -685,12 +682,10 @@ goals?) Note: we no longer have distinct syscalls or states for the different flavors of resolved promises. Instead, each resolved promise is recorded with a -boolean `isRejected` flag, and a `CapData` to hold the resolution data. The -`Fulfill` and `Data` flavors both set `isRejected = false`, and only differ -by the contents of the resolution data. If `isRejected` is false and the data -holds a single Object, then messages can be sent to the promise, and they will -be passed along to the Object. Otherwise messages sent to the promise will -result in a rejection of some form. +boolean `isRejected` flag, and a `CapData` to hold the resolution data. If +`isRejected` is false and the data holds a single Object, then messages can be +sent to the promise, and they will be passed along to the Object. Otherwise +messages sent to the promise will result in a rejection of some form. ### syscall.exit(isFailure, info) @@ -753,20 +748,20 @@ subscriber. If the `Send` is to a Promise, the action depends upon the state of the promise: -| State | Action | -| --- | --- | -| Unresolved | queue inside Promise, or deliver() to decider vat | -| Forwarded | process according to target Promise | -| Fulfilled | deliver() to owner of fulfillment object | -| Data | resolve (reject) result to CannotSendToData error | -| Rejected | resolve (reject) result to rejection object | +| State | Action | +| --- | --- | +| Unresolved | queue inside Promise, or deliver() to decider vat | +| Fulfilled, to an Object | deliver() to owner of fulfillment object | +| Fulfilled, to data | resolve (reject) result to CannotSendToData error | +| Rejected | resolve (reject) result to rejection object | +| Forwarded | process according to target Promise | If the Promise is `Unresolved`, the kernel looks at its `Decider` field, and sees if the named Vat has opted in to pipelining or not. If so, it does a `dispatch.deliver()` to the named Vat. If not, the message is queued inside the kernel Promise object. -If it is `Fulfilled` (to an object), the kernel acts as if the `Send` was to +If it is `Fulfilled` to an Object, the kernel acts as if the `Send` was to the object itself. As a performance optimization, when a Promise is fulfilled this way, the kernel could rewrite the run-queue to replace the `target` fields with the object, and this case would never be encountered. @@ -776,8 +771,8 @@ explicitly rejected, the original message is discarded, as there is nobody to accept it. However if the message requested a `result`, that result Promise must be rejected, just as if decider Vat had called `syscall.resolve(Reject(error))`. The rejection error is either a -`CannotSendToData` object (for `Data`), or a copy of the Promise's own -rejection object (for `Rejected`). +`CannotSendToData` object (for `Fulfill` to non-Object data) or a copy of the +Promise's own rejection object (for `Rejected`). ## Vat-Inbound Slot Translation @@ -933,7 +928,7 @@ args: "[]", slots=[], result=p-2015})`. In the vat code on Vat-2, the `foo()` method returns some basic data "42". This causes Vat-2 to resolve the promise to data, by calling -`dispatch.resolve(subject=p-2015, resolution=Data(body="42", slots=[]))`. +`dispatch.resolve(subject=p-2015, resolution=Fulfill(body="42", slots=[]))`. The kernel translates the subject (`p-2015`, in its resolution capacity) through the calling vat's C-List into `kp24`. It confirms in the kernel @@ -957,10 +952,10 @@ The `dispatch.resolve()` returns, and Vat-2 finishes its crank. The run-queue is then cycled again, and the Notify is at the top. The kernel uses the `subscriber` to pick the Vat-1 C-List for inbound translation, and the subject (`kp24`) is translated into `p+104`. The promise table is -consulted for `kp24` to determine the resolution, in this case `Data`. The +consulted for `kp24` to determine the resolution, in this case `Fulfill`. The resolved data (`42`) has no slots, so translation is trivial. The Vat-1 dispatch function is then invoked as `dispatch.notify(subject: p+104, to: -Data(body="42", slots=[]))`. +Fulfill(body="42", slots=[]))`. Vat-1 looks up `p+104` in its internal tables to find the resolver function for the native Promise that it created at the beginning, and invokes it with @@ -1263,7 +1258,7 @@ Vat-2 then gets a `dispatch.deliver(target=o+3001, msg={method: "foo", args: ### TODO: more examples * `syscall.resolve(to=Forward(p))` -* `syscall.resolve(to=Data())`, showing how queued messages are then rejected +* `syscall.resolve(to=Fulfill())`, showing how queued messages are then rejected * `syscall.resolve(to=Rejection())`, ditto ``` From 3ca2492ce018854f2589fc8eefe7cc92b99b50bb Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 19:45:08 -0500 Subject: [PATCH 23/30] docs(swingset): Improve the promise pipelining example --- packages/SwingSet/docs/delivery.md | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 1a233188f16..0bdfc410ebc 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -343,12 +343,6 @@ the Promise that came back from the transmission of an earlier message. It is a vital latency-reduction tool for sending multiple messages to a distant machine. -```js -const recordPromise = E(table).getRecord(identifier); -const balancePromise = E(recordPromise).getBalance(); -balancePromise.then(balance => console.log(`balance: ${balance}`)); -``` - In SwingSet, pipelining is most (only?) useful on the Comms Vat. The local kernel shares a host with the local vats, so the latency is minimal. However two messages aimed at the same remote machine, through the Comms Vat, would @@ -382,21 +376,12 @@ deciding Vat must re-submit all those messages back into the kernel. Vats may prefer to avoid deserializing the messages until their resolution is known, to avoid a wasteful reserialization cycle. -If the deciding Vat has *not* opted into pipelining, the messages are queued -in the kernel's Promise table entry instead. They remain there until the +If the deciding Vat has *not* opted into pipelining, the messages are instead +queued in the kernel's Promise table entry. They remain there until the deciding vat uses `syscall.resolve()` to resolve that Promise. At that point, the behavior depends upon the type of resolution; see the discussion of `syscall.resolve()` [below](#syscallresolve) for details. -When a `syscall.send()` is submitted, the run-queue will have two pending -deliveries: the first is targeting an object in some Vat, and the second targets -the `result` PromiseID. Until the first message is delivered, the result Promise -has no Decider, so the second message cannot be delivered. But that's ok, -because by the time the second message gets to the front of the run queue, the -first will have been delivered, setting the Decider of the result Promise to -some vat, providing a place to deliver the second one (or the knowledge that the -vat wants the kernel to queue it instead). - When we implement Flows or escalators or some other priority mechanism, we must ensure that we don't try to deliver any message until all its dependencies (including the target) have been resolved to some particular @@ -404,6 +389,24 @@ vat. A pipelining vat would learn (and probably be able to use) the Meter attached to the pipelined messages, whereas if these messages are queued in the kernel, the decider vat would not get access to those Meters. +### Pipelining example + +```js +const recordPromise = E(table).getRecord(identifier); +const balancePromise = E(recordPromise).getBalance(); +balancePromise.then(balance => console.log(`balance: ${balance}`)); +``` + +After the `syscall.send()` for `getBalance` is submitted, the run-queue will have +two pending deliveries: the first (from `getRecord`) targets a `table` object in +some Vat, and the second (from `getBalance`) targets the `result` PromiseID of +the first. Until the first message is delivered, the result Promise has no +Decider, so the second message cannot be delivered. But that's ok, because by +the time the second message gets to the front of the run queue, the first will +have been delivered, setting the Decider of the result Promise to some vat, +providing a place to deliver the second one (or the knowledge that the vat wants +the kernel to queue it instead). + ### Result Promise Summary * Allocating a new Promise in `CapData` creates resolution authority, sending From c172471fa8e14f2738cf210a96f73c71802f1efa Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 20:27:32 -0500 Subject: [PATCH 24/30] docs(swingset): Update pseudo-Rust blocks for batch resolution --- packages/SwingSet/docs/delivery.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 0bdfc410ebc..4adbc76a3f5 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -159,11 +159,15 @@ struct Message { args: CapData, result: Option, } -enum Resolution { +enum ResolutionData { Fulfill(CapData), Reject(CapData), Forward(PromiseID), } +struct Resolution { + subject: PromiseID, + resolution: ResolutionData, +} ``` ## Kernel Data Types @@ -267,8 +271,8 @@ struct KernelCLists { ``` `KernelObject` and `KernelPromise` rows are retained as long as they are -referenced by any Vat C-Lists, any `CapData` structures or `Resolution` -targets in the Promise table, or any target/data/result in the run-queue. +referenced by any Vat C-Lists, any `CapData` or `ResolutionData` structures +in the Promise table, or any target/data/result in the run-queue. When the last reference is removed, the row can be deleted. The ID could also be recycled, but it seems less confusing to simply retire the number. @@ -483,7 +487,7 @@ trait Syscall { fn send(target: CapSlot, msg: Message); fn callNow(target: CapSlot, msg: Message) -> CapData; fn subscribe(id: PromiseID); - fn resolve(subject: PromiseID, to: Resolution); + fn resolve(resolutions: Vec); fn exit(isFailure: bool, info: CapData); fn vatstoreGet(key: String) -> String; fn vatstoreSet(key: String, value: String); @@ -493,7 +497,7 @@ trait Syscall { trait Dispatch { fn deliver(target: CapSlot, msg: Message); - fn notify(subject: PromiseID, to: Resolution); + fn notify(resolutions: Vec); fn dropExports(refs: &CapSlot[]); } ``` @@ -609,8 +613,7 @@ hear about their own Promises, but it is valid. `syscall.resolve()` is used to resolve one or more Promises (usually just one, but occasionally a batch of mutually-referencing Promises must be resolved in a single syscall because their identifiers are aggressively retired -immediately after translation). \[TODO: resolve inconsistency between this claim -and [Syscall/Dispatch API](#syscalldispatch-api)] +immediately after translation). The subject of a `syscall.resolve()` must either be a new Promise ID, or a pre-existing one for which the calling Vat is the Decider. The @@ -820,7 +823,7 @@ relevance/constraints]. The Decider vat for a Promise will not generally subscribe themselves, since they are the ones causing the Promise to become resolved, so they have no need to hear about it from the kernel. -The `Resolution` value of `dispatch.notify()` may contain slots, which are +The `Resolution` values of `dispatch.notify()` may contain slots, which are translated just like `Message.args`. TODO: After a Vat receives notice of a Promise being resolved, we might @@ -828,7 +831,7 @@ choose to remove that promise from the vat's C-List, and forbid the Vat from ever mentioning that promise again. A Vat might be informed that one Promise has been resolved to another Promise -(`Resolution::Forward`). This new Promise might be local, or imported. +(`ResolutionData::Forward`). This new Promise might be local, or imported. ## Sample Message Delivery From bb63e0219ebe9b516355e94efd0247d9aabc2a2f Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 20:36:19 -0500 Subject: [PATCH 25/30] docs(swingset): Document that forwarding is not yet implemented --- packages/SwingSet/docs/delivery.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 4adbc76a3f5..851688f4e21 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -162,7 +162,7 @@ struct Message { enum ResolutionData { Fulfill(CapData), Reject(CapData), - Forward(PromiseID), + // TODO: Forward(PromiseID), } struct Resolution { subject: PromiseID, @@ -206,7 +206,8 @@ Each contains some additional state-specific data: that this can be a single ObjectID, which will often be the case) * `Rejected`: includes the CapData (body+slots, maybe an Error object) with which it was rejected -* `Forwarded`: includes the `KernelPromiseID` to which it was forwarded +* `Forwarded` (**NOT YET IMPLEMENTED**): includes the `KernelPromiseID` to which + it was forwarded The kernel also maintains a "run-queue", which is populated with pending deliveries, each of which references a variety of kernel-side objects. @@ -633,11 +634,11 @@ The `resolution` has several forms, and we assign a different name to each. * `Reject(CapData)`: the Promise is "rejected" to data which we call the "error object". Sending a message to a Rejected Promise causes the result of that message to be Rejected too, known as "rejection contagion". -* `Forward(PromiseID)`: the Promise is now "forwarded": it has not settled to - a specific object, but the original Promise is effectively replaced with - some other Promise. - Any `result` promises in the queued messages should be rejected with the same - `CapData` provided as `resolution`. +* `Forward(PromiseID)` (**NOT YET IMPLEMENTED**): the Promise is now + "forwarded": it has not settled to a specific object, but the original Promise + is effectively replaced with some other Promise. Any `result` promises in the + queued messages should be rejected with the same `CapData` provided as + `resolution`. As the `syscall.resolve()` is processed by the kernel, all slots in the `resolution` should be mapped just like the `Message` slots in From 37d6790f9fa927c126fa4793a553e53bee950943 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 20:44:09 -0500 Subject: [PATCH 26/30] docs(swingset): Remove TODO paragraph --- packages/SwingSet/docs/delivery.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 851688f4e21..dc4f3a92ebd 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -679,14 +679,6 @@ the higher-layer vat code still retains the original native Promise and includes it in an argument, the lower-level translation layer can create a new promptly-resolved Promise for it. -(TODO: `resolve()` is a good opportunity to remove the promise from the -resolving vat's C-List, however if we have queued messages, it must live long -enough to forward those messages to the new resolution. It would be nice to -keep the CannotSendToData logic in the kernel, and have the resolving Vat -just re-`send` everything in the queue in all resolution cases. If it -fulfills to an Export, would the extra queue delay violate our ordering -goals?) - Note: we no longer have distinct syscalls or states for the different flavors of resolved promises. Instead, each resolved promise is recorded with a boolean `isRejected` flag, and a `CapData` to hold the resolution data. If From 6bf3167c7f3ee25c0ff62c1c7c96c876ebca9a87 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Wed, 2 Mar 2022 20:52:57 -0500 Subject: [PATCH 27/30] docs(swingset): Remove mention of wavy dot --- packages/SwingSet/docs/delivery.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index dc4f3a92ebd..0b14f1c929e 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -11,11 +11,10 @@ Each vat contains some application-specific code (named "Vat Code"). For SwingSet, most Vat Code uses orthogonal peristence (i.e., invisible to the vat code, which effectively perceives its memory data as eternal) and is written in the SES subset of Javascript, employing native platform Promises and making -eventual-send calls to local or remote objects with either the `E()` wrapper -(`resultPromise=E(x).foo(args)`) or (eventually) the wavy dot syntax -(`resultPromise=x~.foo(args)`). Other forms of Vat Code could exist, e.g. using -non-orthogonal persistence such as a database or a non-SES language such as -WASM. +eventual-send calls to local or remote objects with the `E()` wrapper +(`resultPromise=E(x).foo(a, b, c)`). Other forms of Vat Code could exist, e.g. +using non-orthogonal persistence such as a database or a non-SES language such +as WASM. Below the Vat Code, but still inside the Vat, there is a support layer which translates eventual-sends into kernel syscalls, and manages persistence. This From 205e3103bd12f3e8b7f4c885c8e5205e6c1ebc92 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 3 Mar 2022 22:21:55 -0500 Subject: [PATCH 28/30] docs(swingset): Restore section heading text --- packages/SwingSet/docs/delivery.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 0b14f1c929e..c640b20c32b 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -525,7 +525,10 @@ Some invocation patterns are valid, but unlikely to be useful: In some places, `dispatch.deliver()` is named `message`: we're still in the process of refactoring and unifying the codebase. -## Vat-Outbound Slot Translation + + + +## Vat-Outbound (Vat-to-Kernel) Slot Translation Inside the implementations of all syscall methods (`send` and `resolve`), the Vat-specific argument slots are first mapped into kernel-space identifiers by @@ -772,7 +775,10 @@ must be rejected, just as if decider Vat had called `CannotSendToData` object (for `Fulfill` to non-Object data) or a copy of the Promise's own rejection object (for `Rejected`). -## Vat-Inbound Slot Translation + + + +## Vat-Inbound (Kernel-to-Vat) Slot Translation Any slots in messages from the kernel must be translated into identifiers specific to the target Vat, by looking them up in the target Vat's C-List. If From ae9750dd9a8cf027637a2fa6084190a89dbba122 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 3 Mar 2022 22:33:54 -0500 Subject: [PATCH 29/30] docs(swingset): Document retirement of C-List Promise entries --- packages/SwingSet/docs/delivery.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index c640b20c32b..1314a719e0d 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -815,18 +815,15 @@ resolution authority. The `subject` argument of `dispatch.notify()` specifies which Promise is being resolved. It is always translated into a `PromiseID`. -`dispatch.notify()` will be sent to all Vats which had subscribed to hear -about the resolution of that Promise [TODO: document ordering -relevance/constraints]. The Decider vat for a Promise will not generally -subscribe themselves, since they are the ones causing the Promise to become -resolved, so they have no need to hear about it from the kernel. - The `Resolution` values of `dispatch.notify()` may contain slots, which are translated just like `Message.args`. -TODO: After a Vat receives notice of a Promise being resolved, we might -choose to remove that promise from the vat's C-List, and forbid the Vat from -ever mentioning that promise again. +`dispatch.notify()` will be sent to all Vats which had subscribed to hear +about the resolution of that Promise. The Decider vat for a Promise will not +generally subscribe themselves, since they are the ones causing the Promise to +become resolved, so they have no need to hear about it from the kernel. + +C-List Promise entries are retired upon resolution. A Vat might be informed that one Promise has been resolved to another Promise (`ResolutionData::Forward`). This new Promise might be local, or imported. From 8fb69f04768f05d77b7a7b4a288be23ab521bdd8 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 3 Mar 2022 22:40:04 -0500 Subject: [PATCH 30/30] docs(swingset): Replace foo!bar() shorthand with foo~.bar() --- packages/SwingSet/docs/delivery.md | 108 ++++++++++++++--------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/packages/SwingSet/docs/delivery.md b/packages/SwingSet/docs/delivery.md index 1314a719e0d..31b76373d45 100644 --- a/packages/SwingSet/docs/delivery.md +++ b/packages/SwingSet/docs/delivery.md @@ -831,7 +831,7 @@ A Vat might be informed that one Promise has been resolved to another Promise ## Sample Message Delivery This section follows a variety of messages are they are sent from Vat-1 to -Vat-2. Syntax like `foo!bar(baz)` is used to indicate a message to Object or +Vat-2. Syntax like `foo~.bar(baz)` is used to indicate a message to Object or Promise `foo` with method "bar" and args `[ baz ]`. ### no arguments, resolve to data @@ -848,7 +848,7 @@ Vat-2 that we'll name `bob`. * Vat-2 C-List: * `v2.o+2001 <-> ko1` (export of bob) -Vat-1 does `p1 = bob!foo()`. This causes the lower layers of Bob's vat to +Vat-1 does `p1 = bob~.foo()`. This causes the lower layers of Bob's vat to allocate a new local promise/resolver ID (for the result `p1`), say it chooses `104`, and then invokes `syscall.send(target=o-1001, msg={method: "foo", args: "[]", slots=[], result=p+104})`. Vat-1 remembers `p+104` as the @@ -857,28 +857,28 @@ identifier for the result Promise. We assume that Vat-1 uses `p1` later (i.e. ``` -+-- Vat 1 ---+ +- Vat 2 --+ +- Vat 3 --+ -| | | | | | -|p1=bob!foo()| | vat | | vat | -| | | code | | code | -| | | | | | -| | | | | | -| ---------- | | -------- | | -------- | -| | | | | | -| send() | | deliver | | support | -| | | | ^ | | layer | -| | | | | | | | -+----|-------+ +---|------+ +-syscall--+ -+----|-------+---+---|------+------+-dispatch-+-------+ -| v | | | | | | | -| c-lists | | c-lists | | c-lists | | -| | | | ^ | | | | -| | | | -| | | >-v | -| \-> run-queue --/ object-table event-loop | | | -| promise-table ^-< | -| | -+------------------- Kernel --------------------------+ ++--- Vat 1 ---+ +- Vat 2 --+ +- Vat 3 --+ +| | | | | | +|p1=bob~.foo()| | vat | | vat | +| | | code | | code | +| | | | | | +| | | | | | +| ----------- | | -------- | | -------- | +| | | | | | +| send() | | deliver | | support | +| | | | ^ | | layer | +| | | | | | | | ++-----|-------+ +---|------+ +-syscall--+ ++-----|-------+---+---|------+------+-dispatch-+-------+ +| v | | | | | | | +| c-lists | | c-lists | | c-lists | | +| | | | ^ | | | | +| | | | +| | | >-v | +| \-> run-queue --/ object-table event-loop | | | +| promise-table ^-< | +| | ++-------------------- Kernel --------------------------+ ``` The `syscall.send()` is mapped into the kernel through the Vat-1 C-List. The @@ -964,9 +964,9 @@ for the native Promise that it created at the beginning, and invokes it with ### Pipelined Send -Suppose Vat-1 did `bob!foo()!bar()`, which sends `bar` to the Promise -returned by the initial `bob!foo()`. This is Promise Pipelining, and `bar` is -supposed to be sent into the Vat which owns the result of `bob!foo()` (which +Suppose Vat-1 did `bob~.foo()~.bar()`, which sends `bar` to the Promise +returned by the initial `bob~.foo()`. This is Promise Pipelining, and `bar` is +supposed to be sent into the Vat which owns the result of `bob~.foo()` (which will be the same Vat that owns `bob`, namely Vat-2). Vat-2 has opted into receiving pipelined messages. @@ -1026,7 +1026,7 @@ resolve. ### Forwarding Queued Messages -Imagine the previous scenario (`bob!foo()!bar()`), but now `bob` resolves the +Imagine the previous scenario (`bob~.foo()~.bar()`), but now `bob` resolves the `foo()` result promise to point at a third object `carol` in Vat-3. The relevant kernel state looks like: @@ -1098,7 +1098,7 @@ and Vat-3 gets ### Pipelined send to a non-Comms vat Now let us suppose Vat-2 has *not* elected to accept pipelined messages (i.e. -it is not the Comms Vat). When Vat-1 does `bob!foo()!bar()`, the `bar` should +it is not the Comms Vat). When Vat-1 does `bob~.foo()~.bar()`, the `bar` should be queued inside the kernel Promise, rather than being delivered to Vat-2. Again, the two `send` calls will look like: @@ -1197,7 +1197,7 @@ these, plus a local Promise `p4`, to `carol`. * Vat-3 C-List: * `v3.o+3001 <-> ko2` (export of carol) -Vat-1 now does `p3 = make_promise(); p4 = carol!foo(alice, bob, carol, p2, p3)`. +Vat-1 now does `p3 = make_promise(); p4 = carol~.foo(alice, bob, carol, p2, p3)`. The `make_promise()` creates a regular Vat-Code promise (a native Javascript Promise). Nothing special happens until the `foo()` is processed into a @@ -1264,7 +1264,7 @@ Vat-2 then gets a `dispatch.deliver(target=o+3001, msg={method: "foo", args: ``` p1 = make_promise(); -x!foo(p1); +x~.foo(p1); function foo(arg) { return p1; } @@ -1431,28 +1431,28 @@ lack of a single central kernel: ### Comms Example ``` -+- Left Vat -+ +- Left --+ +- Right --+ +-Right Vat+ -| | | Comms | | Comms | | | -|p1=bob!foo()| | | | | | | -| | | comms ----------> comms | | | -| | | code | | code | | bob.foo()| -| | | | | | | | -| ---------- | | -------- | | -------- | | -------- | -| | | | | | | | -| send() | | deliver | | send() | | deliver | -| | | | ^ | | | | | ^ | -| | | | | | | | | | | | -+----|-------+ +---|------+ +---|------+ +---|------+ -+----|-------+---+---|------+-+ +-+---|------+----+---|------+-+ -| v | | | | | | | v | | | | | -| c-lists | | c-lists | | | | c-lists | | c-lists | | -| | | | ^ | | | | | | | | | | -| | | | | | | | -| | | | | | | | -| \-> run-queue --/ | | \-> run-queue --/ | -| | | | -| | | | -+--------- Left Kernel -------+ +----- Right Kernel -----------+ ++- Left Vat --+ +- Left --+ +- Right --+ +-Right Vat+ +| | | Comms | | Comms | | | +|p1=bob~.foo()| | | | | | | +| | | comms ----------> comms | | | +| | | code | | code | | bob.foo()| +| | | | | | | | +| ----------- | | -------- | | -------- | | -------- | +| | | | | | | | +| send() | | deliver | | send() | | deliver | +| | | | ^ | | | | | ^ | +| | | | | | | | | | | | ++-----|-------+ +---|------+ +---|------+ +---|------+ ++-----|-------+---+---|------+-+ +-+---|------+----+---|------+-+ +| v | | | | | | | v | | | | | +| c-lists | | c-lists | | | | c-lists | | c-lists | | +| | | | ^ | | | | | | | | | | +| | | | | | | | +| | | | | | | | +| \-> run-queue --/ | | \-> run-queue --/ | +| | | | +| | | | ++--------- Left Kernel --------+ +----- Right Kernel -----------+ ``` Initial conditions: @@ -1478,7 +1478,7 @@ Initial conditions: * right-vat (id=4) kernel C-List * `v4.o+5001 <-> ko2` (export of real bob) -left-vat does `p1 = bob!foo()`. Left kernel accepts `syscall.send()` and +left-vat does `p1 = bob~.foo()`. Left kernel accepts `syscall.send()` and the run-queue gets `Send(target=ko1, msg={name: foo, result=kp24})`, which eventually comes to the front and is delivered to left-comms. The left-kernel tables just before `dispatch.deliver()` is called will look like: