diff --git a/design/FAQ.md b/design/FAQ.md index 0aee8af0b22..85f2f89d994 100644 --- a/design/FAQ.md +++ b/design/FAQ.md @@ -1,14 +1,14 @@ > 1. Why would I want an `actor class` versus just a `class`? -An actor is instantiated as a separate wasm instance, with isolated state. Classes can capture (mutable) free variables state, actors class should not (but currently can because the typechecker is too liberal). +An actor is instantiated as a separate wasm instance, with isolated state. Classes can capture (mutable) free variables state, actors classes should not (but currently can because the typechecker is too liberal). > 2. Where am I permitted to place `async` blocks? -At the moment, anywhere - they get compiled to async message sends to the enclosing (perhaps implicit) actor that return a promise. You might, however, not be able to await the result unless you are in an outer async context! But you could pass in into another async block that can await it. +At the moment, anywhere - they get compiled to async message sends to the enclosing (perhaps implicit) actor that return a promise. You might, however, not be able to await the result unless you are in an outer async context! But you could pass in into another async block that can await it. > 3. What kinds of datatypes are permitted to be declared with the `share` qualifier; and why some but not others (what’s the source of the distinction, and its role in writing actorscript-based systems) -Shared means transmittable without losing identity, essentially. So scalars, immutable data, option of shared, shared (immutable objects), shared functions and actor references can be sent/received, but nothing else that might contain or close over mutable state. That's the idea, assuming it isn't broken. Not all restriction are currently checked (i.e. escape of shared state into actors and shared functions for instance.) Note that serialization is mostly by value, apart from actors and shared functions, which are by reference, so identity can't be preserved for most values, ruling out them containing state. +Shared means transmittable without losing identity, essentially. So scalars, immutable data, option of shared, shared (immutable objects), shared functions and actor references can be sent/received, but nothing else that might contain or close over mutable state. That's the idea, assuming it isn't broken. Not all restrictions are currently checked (i.e. escape of shared state into actors and shared functions for instance.) Note that serialization is mostly by value, apart from actors and shared functions, which are by reference, so identity can't be preserved for most values, ruling out them containing state. > 4. Where would I want to define a `class` versus an `object`? (explain that distinction, if possible) @@ -16,9 +16,8 @@ A class is a family of objects of the same type. There used to be support for ch > 5. Where would I want to define an `object` versus a `record` (explain that distinction, if possible) -I guess an object would typically encapsulate state, and fields would be private by default (eventhough they aren't currently). Records would have fields public by default. In the end, an object really is just a record of values and any state encapsulation is by virtue of the object having fields that are functions that close over state in their environments. The distinction between shared objects and non-shared ones is that we don't an object that has a mutable field, and thus isn't sharable, to become sharable just by virtue of forgetting that field through subtyping. That was the intention anyway. +I guess an object would typically encapsulate state, and fields would be private by default (eventhough they aren't currently). Records would have fields public by default. In the end, an object really is just a record of values and any state encapsulation is by virtue of the object having fields that are functions that close over state in their environments. The distinction between shared objects and non-shared ones is that we don't want an object that has a mutable field, and thus isn't sharable, to become sharable just by virtue of forgetting that field through subtyping. That was the intention anyway. > 6. What types permit mutation and how is that expressed; what restrictions come with using mutable memory? Just mutable arrays, mutable fields of objects and mutable locals. Mutable types can't be transmitted or received in messages (shared function calls). There's no first class ref type but one can simulate that with an object with a single mutable field. - diff --git a/design/Implementation.md b/design/Implementation.md index 131bbfe31de..de386ab31e3 100644 --- a/design/Implementation.md +++ b/design/Implementation.md @@ -36,7 +36,7 @@ * Flattened when used as function parameter or result. -* Q: How avoid calling convention mismatch when instantiating polymorphic function with tuple type? +* Q: How to avoid calling convention mismatch when instantiating polymorphic function with tuple type? - Don't make tuples subtypes of Any, thereby disallowing their use in instantiation? @@ -65,14 +65,14 @@ * Definitions lifted to the surrounding actor. -* Closed-over locals need indirection through heap if mutable or if definedness cannot be proved. +* Closed-over locals need indirection through heap if mutable or if defined-ness cannot be proven. ## Actor Objects * Compile to immediately instantiated modules. -* Private field become regular globals or functions. +* Private fields become regular globals or functions. * Public methods become exported functions. - In general, have to generate wrapper functions to forward external calls to pre-existing local closure. diff --git a/design/Memory.md b/design/Memory.md index a5db5fbbcb5..845db5edac3 100644 --- a/design/Memory.md +++ b/design/Memory.md @@ -32,7 +32,6 @@ The Heap is *not* an explicit entity that can be im/exported, only individual re Note: It is highly likely that most languages implemented on Wasm will eventually use Wasm GC. Various implementers are currently waiting for it to become available before they start porting their language to Wasm. - ### Dfinity #### API Types @@ -144,14 +143,13 @@ but Wasm is extended with some notion of volatile state and reinitialisation. Pros: 1. compromise between other two models + Cons: 1. compromise between other two models 2. creates dangling references between bifurcated state parts 3. incoherent with Wasm semantics (segments, start function) - ### Implementing Transparent persistence - #### *High-level* implementation of persistence Hypervisor walks data graph (wherever it lives), turns it into merkle tree. diff --git a/design/TmpWireFormat.md b/design/TmpWireFormat.md index 70f805d301d..3383b362bf4 100644 --- a/design/TmpWireFormat.md +++ b/design/TmpWireFormat.md @@ -52,9 +52,9 @@ little endian format. payload, followed by the payload as a utf8-encoded string (no trailing `\0`). * An `Array` is represented by 4 bytes indicating the number of entries, followed by the concatenation of the representation of these entries. - * An `Tuple` is represented the concatenation of the representation of its - entries. (No need for a length field, as it is statically determined.) - * An `Object` is represented the concatenation of the representation of its + * A `Tuple` is represented by the concatenation of the representation of its + entries. (No need for a length field, as it can be statically determined.) + * An `Object` is represented by the concatenation of the representation of its fields, sorted by field name. (The field names are not serialized, as they are statically known.) * An `Option` is represented by a single byte `0` if it is `null`, or @@ -62,6 +62,7 @@ little endian format. * An empty tuple, the type `Null` and the type `Shared` are represented by zero bytes. * A `Variant` with `n` constructors sorted by constructor name is represented + by a single word indicating the constructor as a number `0..n-1`, followed by the payload of the constructor. (Yes, obviously no subtyping here.) @@ -86,7 +87,7 @@ are represented as an `elembuf`: The above format is thus extended with the following case: - * A reference (`actor`, `shared func`) is represented as a 32 bit number (4 + * A reference (`actor`, `shared func`) is represented as a 32-bit number (4 bytes). Thus number is an index into the surrounding `elembuf`. NB: The index is never never `0`, as the first entry in the `elembuf` is the diff --git a/design/async.md b/design/async.md index 0241d2d9c67..a52949813a0 100644 --- a/design/async.md +++ b/design/async.md @@ -1,6 +1,6 @@ # Lowering Async (following C\# async/await) -Assuming a promise like interface: +Assuming a promise-like interface: ``` class Async(){ @@ -35,12 +35,11 @@ The async expression (typically a block) is lowered to a block that The state machine is function `SM():()` that: - enters a loop that switches on the current value of `s` to resume the machine from the next state. -- pause the state machine (at an `await`) by first advancing `s` to the next logical state and returning from `SM()` (thus exiting the loop, too). -- returning a value `t` from the async block (whether implicitly or explicity), sets the result of `a` (`a.setresult(t)`) +- pauses the state machine (at an `await`) by first advancing `s` to the next logical state and returning from `SM()` (thus exiting the loop, too). +- returning a value `t` from the async block (whether implicitly or explicity), sets the result of `a` (`a.setresult(t)`) Here's a manual translation of the example above. - ``` { let a0 = Async(); @@ -71,7 +70,7 @@ a_0; } ``` -C# actually has an optimization that allows one to test whether an awaited async is already complete (Async.IsCompleted:()->Bool) and continue the loop, +C# actually has an optimization that allows one to test whether an awaited async is already complete (Async.IsCompleted:()->Bool) and continue the loop, avoiding the cost of scheduling a continuation, but that's a local optimization we could do too. ## Example 2 (with strutured control flow) @@ -85,7 +84,7 @@ Roughly: ``` async () { while (await f()) - await g() + await g() } ``` @@ -135,18 +134,20 @@ func SM():(){ case 1 : b = a_1.result; if (b) then { state = 2; continue loop }; - else {state = 3; continue loop;} + else {state = 3; continue loop;} s = 2; let a2 = g(); a2.continuewith(SM); return; case 2: - {state = 0; - continue loop;} + { + state = 0; + continue loop; + } case 3: { - state = 4; - continue loop; + state = 4; + continue loop; } case 4; { @@ -158,8 +159,3 @@ SM(); a_0; } ``` - - - - - diff --git a/design/asynccps.md b/design/asynccps.md index 3c33997c58f..0516ba6d63c 100644 --- a/design/asynccps.md +++ b/design/asynccps.md @@ -1,6 +1,6 @@ # Async Await Translation -Based on +Based on "A Selective CPS Transformation", Lasse R.Nielsen, BRICS1 @@ -27,7 +27,7 @@ e = x The aim of game is to remove async and await by a source-to-source translation, leaving as much code as possible in direct-style. -Terms have effect `T` (trivial) or `A` (await) with `T` < `A`. A term has effect `A` if any subterm not enclosed by `async` is `await`. +Terms have effect `T` (trivial) or `A` (await) with `T` < `A`. A term has effect `A` if any subterm not enclosed by `async` is `await`. The only term that introduce effect `A` is `await` - the effect is masked by its innermost enclosing async (if any). @@ -88,7 +88,7 @@ T[ x ]= x T[ c ] = c T[ \x.t ] = \x.T[t] T[ t1 t1 ] = T[t1] T[t2] -T[ let x = t1 in t2 ] = let x = T[t1] in T[t2] +T[ let x = t1 in t2 ] = let x = T[t1] in T[t2] T[ if t1 then t2 else t3] = if T[t1] then T[t2] else T[t3] T[ while t1 do t1 ] = @@ -103,21 +103,21 @@ T[ return T[t] ] = We use the following primitives for scheduling actions (that complete tasks). -```JS +```JS spawn(f) = let t = task{result=None;waiters=[]} in schedule (\u.f(t)); t await(t,k) = match t with | {result=Some v} -> k v - | {result=None} -> t.waiters <- k::t.waiters; yield() + | {result=None} -> t.waiters <- k::t.waiters; yield() complete(t,v) = match t with | {result=None;waiters} -> t.result = Some v; foreach waiter in waiters do schedule(\u.waiter(v)) - | {result=Some _ } -> assert(false) + | {result=Some _ } -> assert(false) yield() = schedule.Next() ``` @@ -127,10 +127,10 @@ The above translations are flawed: Consider: ``` -async { - label l +async { + label l let x = break l 1 in - break l (await {2}) + break l (await {2}) } ``` @@ -150,7 +150,7 @@ C env [ label l e ] = C env [ break l e ] = assert(env[l] = Cont) \\k. C env [e] @ l // discard k, continue from l - + C env [ return e ] = assert(env[kret] = Cont) \\_. C env [e] @ kret // discard k, return @@ -179,8 +179,8 @@ T env [ return t ] = Returning to the problematic example, we should now have that label `l` is compiled and used as a continuation in both cases: ``` -async { - label l +async { + label l let x = break l 1 in break l (await {2}) } diff --git a/design/guide.md b/design/guide.md index 6202fb0e020..a2f32e923c9 100644 --- a/design/guide.md +++ b/design/guide.md @@ -1,7 +1,6 @@ % ActorScript’s Users Guide % [DFINITY Foundation](https://dfinity.org/) - TODO: -* [X] *Sort* primitives and operations as arithmetic (A), boolean (L), bitwise (B) and comparable (C) and use these sorts to concisely present sorted operators (unop,binop, relop, a(ssing)op) etc. +* [X] *Sort* primitives and operations as arithmetic (A), boolean (L), bitwise (B) and comparable (C) and use these sorts to concisely present sorted operators (unop, binop, relop, a(ssing)op) etc. * [ ] Various inline TBCs and TBRs and TODOs * [ ] Typing of patterns * [ ] Variants