Skip to content

Commit

Permalink
fix few typos + minor adjustments
Browse files Browse the repository at this point in the history
Beyond typo / formatting / minor edits, few other adjustments which I'm not 100% sure:

* hosted in a state machine objects to the F# compiler =>  hosted in a state machine objects *recognized by* the F# compiler
* and the object expression must contain a single method. => and the object expression must contain a single *Step* method.
* The code won't actually be unverifiable => The code won't actually be *verifiable*
  • Loading branch information
smoothdeveloper authored Apr 4, 2020
1 parent 85bbfeb commit 5056d1b
Showing 1 changed file with 48 additions and 49 deletions.
97 changes: 48 additions & 49 deletions RFCs/FS-1087-resumable-code-and-task-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This RFC covers the detailed proposal for this suggestion.
We add a `task { .. }` builder to the F# standard library.

To implement this efficiently, we add a general capability to specify and emit resumable
code hosted in a state machine objects to the F# compiler, and to allow F#
code hosted in state machine objects recognized by the F# compiler, and to allow F#
computation expressions to be implemented via resumable code.

# Motivation
Expand Down Expand Up @@ -119,13 +119,13 @@ Tasks are implemented via the more general mechanism of resumable state machines
### Specifying a resumable state machine (reference types)

Resumable state machines of reference type are specified using the ``__resumableStateMachine`` compiler primitive, giving a state machine expression:
```
```fsharp
if __useResumableStateMachines then
__resumableStateMachine
{ new SomeStateMachineType() with
member __.Step () =
<resumable code>
}
}
else
<dynamic-implementation>
```
Expand All @@ -135,43 +135,43 @@ Notes

* A value-type state machine may also be used to host the resumable code, see below.

* Here `SomeStateMachineType` can be any user-defined reference type, and the object expression must contain a single method.
* Here `SomeStateMachineType` can be any user-defined reference type, and the object expression must contain a single `Step` method.

* The `if __useResumableCode then` is needed because the compilation of resumable code is only activated when code is compiled.
The `<dynamic-implementation>` is used for reflective execution, e.g. quotation interpretation.
In prototyping it can simply raise an exception. It should be semantically identical to the other branch.

* The above construct should be seen as a language feature. First-class uses of of constructs such as `__resumableObject` are not allowed except in the exact pattern above.
* The above construct should be seen as a language feature. First-class uses of constructs such as `__resumableObject` are not allowed except in the exact pattern above.

### Specifying resumable code

Resumable code is made of the following grammar:

* An call to an inlined function defining further resumable code, e.g.
* A call to an inlined function defining further resumable code, e.g.

<rcode> :=
let inline f () = <rcode>
f(); f()

Such a function can have function parameters using the `__expand_` naming, e.g.

let inline callTwice __expand_f = __expand_f(); __expand_f()
let inline print() = printfn "hello"
```fsharp
let inline callTwice __expand_f = __expand_f(); __expand_f()
let inline print() = printfn "hello"
callTwice print

callTwice print
```
These parameters give rise the expansion bindings, e.g. the above is equivalent to

let __expand_f = print
__expand_f()
__expand_f()

```fsharp
let __expand_f = print
__expand_f()
__expand_f()
```
Which is equivalent to

print()
print()

```fsharp
print()
print()
```
* An expansion binding definition (normally arising from the use of an inline function):
<rcode> :=
Expand Down Expand Up @@ -206,28 +206,27 @@ Resumable code is made of the following grammar:
The `Some` branch usually suspends execution by saving `contID` into the state machine
for later use with a `__resumeAt` execution at the entry to the method. For example:

let inline returnFrom (task: Task<'T>) =
let mutable awaiter = task.GetAwaiter()
match __resumableEntry() with
| Some contID ->
sm.ResumptionPoint <- contID
sm.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
false
| None ->
sm.Result <- awaiter.GetResult()
true

Note a resumption expression can return a result - in the above the resumption expression indicates whether the
```fsharp
let inline returnFrom (task: Task<'T>) =
let mutable awaiter = task.GetAwaiter()
match __resumableEntry() with
| Some contID ->
sm.ResumptionPoint <- contID
sm.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
false
| None ->
sm.Result <- awaiter.GetResult()
true
```
Note that, a resumption expression can return a result - in the above the resumption expression indicates whether the
task ran to completion or not.
* A `resumeAt` expression:
<rcode> :=
__resumeAt <expr>
Here <expr> is an integer-valued expression indicating a resumption point, which must be eith 0 or a `contID` arising from a resumption
point resumable code expression on a previous execution.
Here <expr> is an integer-valued expression indicating a resumption point, which must be either 0 or a `contID` arising from a resumption point resumable code expression on a previous execution.
* A sequential exection of two resumable code blocks:
Expand All @@ -247,14 +246,14 @@ Resumable code is made of the following grammar:
means it is not guaranteed that the first `<rcode>` will be executed before the
second - a `__resumeAt` call can jump straight into the second code when the method is executed to resume previous execution.
As a result, `__stack_step` should always be consumed prior to any resumption points. For example:

let inline combine (__expand_task1: (unit -> bool), __expand_task2: (unit -> bool)) =
let __stack_step = __expand_task1()
if __stack_step then
__expand_task2()
else
false

```fsharp
let inline combine (__expand_task1: (unit -> bool), __expand_task2: (unit -> bool)) =
let __stack_step = __expand_task1()
if __stack_step then
__expand_task2()
else
false
```
* A resumable `while` expression:
<rcode> :=
Expand Down Expand Up @@ -424,7 +423,7 @@ module TaskBuilder =
val task : TaskBuilder
```

The following are added to support `Bind` and `ReturnFrom`` on Tasks and task-like patterns
The following are added to support `Bind` and `ReturnFrom` on Tasks and task-like patterns
```fsharp
namespace Microsoft.FSharp.Control
Expand Down Expand Up @@ -611,7 +610,7 @@ See [seq2.fs](https://github.com/dotnet/fsharp/blob/feature/tasks/tests/fsharp/p
This is a resumable machine emitting to a mutable context held in a struct state machine. The state holds the current
value of the iteration.

This is akin to `seq { ... }` expressions, for which we bake-in state machine compilation into the F# compiler today.
This is akin to `seq { ... }` expressions, for which we have a baked-in state machine compilation in the F# compiler today.

## Example: low-allocation list and array builders

Expand All @@ -623,7 +622,7 @@ The sample defines `list { .. }`, `array { .. }` and `rsarray { .. }` for colle
The overall result is a `list { ... }` builder that runs up to 4x faster than the built-in `[ .. ]` for generated lists of
computationally varying shape (i.e. `[ .. ]` that use conditionals, `yield` and so on).

F#'s existing `[ .. ]` and `[| ... |]` and `seq { .. } |> Seq.toResizeArray` all use an intermediate `IEnumerable` which is then iterated to populate a `ResizeArray` and then converted to the final immutable collection. In contrast, generating directly into a `ResizeArray` is potentially more efficient (and for `list { ... }` further perf improvements are possible if we put this in `FSharp.Core` and use the mutate-tail-cons-cell trick to generate the list directly). This technique has been known for a while and can give faster collection generation but it has not been possible to get good code generation for the expressions in many cases. Note these aren't really "state machines" because there are no resumption points - there is just an implicit collection we are yielding into in otherwise synchronous code.
F#'s existing `[ .. ]` and `[| ... |]` and `seq { .. } |> Seq.toResizeArray` all use an intermediate `IEnumerable` which is then iterated to populate a `ResizeArray` and then converted to the final immutable collection. In contrast, generating directly into a `ResizeArray` is potentially more efficient (and for `list { ... }` further perf improvements are possible if we put this in `FSharp.Core` and use the mutate-tail-cons-cell trick to generate the list directly). This technique has been known for a while and can give faster collection generation but it has not been possible to get good code generation for the expressions in many cases. Note that, these aren't really "state machines" because there are no resumption points - there is just an implicit collection we are yielding into in otherwise synchronous code.

# Limitations

Expand Down Expand Up @@ -654,7 +653,7 @@ Complexity
# Alternatives

1. Don't do it.
2. Don't generalise it (just to it for tasks)
2. Don't generalise it (just do it for tasks)

# Compatibility

Expand Down Expand Up @@ -815,14 +814,14 @@ The heart of a typical state machine is a `MoveNext` or `Step` function that tak
```

This is roughly what compiled `seq { ... }` code looks like in F# today and what compiled async/await code looks like in C#, at a very high level. Note you can't write this kind of code directly in F# - there is no `goto` and especially not a `goto` that can jump directly into other code, resuming from the last step of the state machine.
This is roughly what compiled `seq { ... }` code looks like in F# today and what compiled async/await code looks like in C#, at a very high level. Note that, you can't write this kind of code directly in F# - there is no `goto` and especially not a `goto` that can jump directly into other code, resuming from the last step of the state machine.

# Unresolved questions

* [ ] ContextInsensitiveTasks
* [ ] `let rec` not supported in state machines, possibly other constructs too
* [ ] Consider adding `Unchecked` to names of primitives.
* [ ] Document the ways the mechanism can be cheated. For example, the `__resumeAt` can be cheated by using an arbitrary integer for the destination. The code won't actually be unverifiable, and it will be the equivalent of a drop-through the switch statement generated for a `__resumeAt`, but it likely still warrants an `Unchecked`.
* [ ] Document the ways the mechanism can be cheated. For example, the `__resumeAt` can be cheated by using an arbitrary integer for the destination. The code won't actually be verifiable, and it will be the equivalent of a drop-through the switch statement generated for a `__resumeAt`, but it likely still warrants an `Unchecked`.



Expand Down

0 comments on commit 5056d1b

Please sign in to comment.