Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions design/FAQ.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
> 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).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"actor classes"?


> 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)

A class is a family of objects of the same type. There used to be support for checked down-casting (instance of) on classes but we got rid of that, so really a class is just a type def plus constructor function (that's actually what it desugars to as well).

> 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.

6 changes: 3 additions & 3 deletions design/Implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?


Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one needs to be reverted I think: "definedness" is a technical term; "proved" also was correct, "proven" has a different meaning (easy to confuse).



## 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.
Expand Down
4 changes: 1 addition & 3 deletions design/Memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 5 additions & 4 deletions design/TmpWireFormat.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,17 @@ 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
otherwise by a single byte `1` followed by the representation of the value
* 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
<!-- TODO What's the Word's size? -->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure about this. where would I find out in the codebase?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably 32 bit hash of the label, but best to ask Joachim (or look in compile.ml for serialize (I suspects)

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.)

Expand All @@ -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
Expand Down
28 changes: 12 additions & 16 deletions design/async.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Lowering Async (following C\# async/await)

Assuming a promise like interface:
Assuming a promise-like interface:

```
class Async<T>(){
Expand Down Expand Up @@ -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<int>();
Expand Down Expand Up @@ -71,7 +70,7 @@ a_0;
}
```

C# actually has an optimization that allows one to test whether an awaited async is already complete (Async<T>.IsCompleted:()->Bool) and continue the loop,
C# actually has an optimization that allows one to test whether an awaited async is already complete (Async<T>.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)
Expand All @@ -85,7 +84,7 @@ Roughly:
```
async () {
while (await f())
await g()
await g()
}
```

Expand Down Expand Up @@ -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;
{
Expand All @@ -158,8 +159,3 @@ SM();
a_0;
}
```





24 changes: 12 additions & 12 deletions design/asynccps.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Async Await Translation

Based on
Based on

"A Selective CPS Transformation",
Lasse R.Nielsen, BRICS1
Expand All @@ -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).

Expand Down Expand Up @@ -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 ] =
Expand All @@ -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()
```
Expand All @@ -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})
}
```

Expand All @@ -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
Expand Down Expand Up @@ -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})
}
Expand Down
3 changes: 1 addition & 2 deletions design/guide.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
% ActorScript’s Users Guide
% [DFINITY Foundation](https://dfinity.org/)


<!---
TODO
* use menhir --only-preprocess-uu parser.mly followed by sed to create concrete grammar
Expand All @@ -10,7 +9,7 @@ TODO
-->
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
Expand Down