Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking control-flow linearity for effect handlers #1173

Merged
merged 76 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
0adf01d
Some attempts on fixing issue 1161:
thwfhk Nov 16, 2022
421c2a8
some tests for the usage of linear variables in handlers
thwfhk Nov 17, 2022
769e047
clean up comments
thwfhk Nov 17, 2022
d204c5a
Fix a bug that params of parameterised handlers can escape the scope …
thwfhk Nov 17, 2022
3207347
clean up comments
thwfhk Nov 17, 2022
2ba1870
clean up comments
thwfhk Nov 17, 2022
cb11dea
deal with linear variables in the return clauses of handlers; add mor…
thwfhk Nov 17, 2022
1d5a151
initial attempt to track control-flow linearity:
thwfhk Dec 2, 2022
2f69150
more proof of concept implementation
thwfhk Dec 2, 2022
b38ed56
Fix and improve the naive implementation of tracking control-flow
thwfhk Dec 5, 2022
8445815
Improve syntax and fix bugs:
thwfhk Dec 6, 2022
68821d4
some minor updates
thwfhk Jan 4, 2023
9e7b867
comments
thwfhk Apr 20, 2023
e99585b
merge with polymorphic operations
thwfhk Jun 4, 2023
b95faa7
Merge branch 'master' into control-flow-linearity
thwfhk Jun 4, 2023
0d9599a
pass all the linear handler tests
thwfhk Jun 4, 2023
24bb4c7
fix the unification of rows with subkinds
thwfhk Jun 4, 2023
834f8e0
improve the choose_and_state examples
thwfhk Jun 5, 2023
aa5d797
syntax for lincase
thwfhk Jun 5, 2023
ecc51eb
fix the linearity checking of unbound continuation
thwfhk Jun 5, 2023
003d517
a potential bug
thwfhk Jun 5, 2023
e087edc
remove some test files
thwfhk Jun 5, 2023
d45d20a
comment some unused functions
thwfhk Jun 5, 2023
bbac798
nullary operations & some lib function signatures
thwfhk Jun 9, 2023
65e4b6d
fix typo in core/typeSugar.ml
thwfhk Jun 9, 2023
712e229
remove dead code
thwfhk Jun 9, 2023
b6e19f9
remove dead code
thwfhk Jun 9, 2023
25b5ec4
remove dead code in core/typeSugar.ml
thwfhk Jun 9, 2023
b5dfc75
Update core/types.mli
thwfhk Jun 9, 2023
f9885e4
use DeclaredLinearity.t instead of ad hoc boolean in the definition o…
thwfhk Jun 26, 2023
f8de147
wrap the linear continuation things in typeSugar.ml into a module
thwfhk Jun 26, 2023
b1a361d
add a flag track-control-flow-linearity
thwfhk Jun 26, 2023
909c599
something
thwfhk Jun 26, 2023
682a5c4
modify the pretty printing for effect rows
thwfhk Jun 26, 2023
19d67dd
modify the pretty printing for function types
thwfhk Jun 26, 2023
a85105b
modify the parser and printer; try to keep the round-tripping property
thwfhk Jun 27, 2023
445ae32
something
thwfhk Jun 27, 2023
c902696
a mess
thwfhk Jun 27, 2023
626cbd2
some attempts to make default Any
thwfhk Jun 29, 2023
20ced3e
add a test example
thwfhk Jun 30, 2023
e67293a
successfully make Any as the default of effect row vars
thwfhk Jun 30, 2023
1e801bc
clean up some tests
thwfhk Jun 30, 2023
df3e053
fix a bug of the effect vars of curried functions
thwfhk Jun 30, 2023
1b57295
fix some bugs (including the parsing of effect presence types)
thwfhk Jun 30, 2023
cc632ab
need to explicitly say Presence(Any)
thwfhk Jun 30, 2023
91ae333
compatibility with old handler tests
thwfhk Jun 30, 2023
e9e3bf8
Merge branch 'cfl-linear-default' into control-flow-linearity
thwfhk Jun 30, 2023
c525c04
postpone something, but equivalent
thwfhk Jun 30, 2023
c9eba50
more printing compatibility
thwfhk Jul 1, 2023
b98378a
more tests
thwfhk Jul 1, 2023
5796f26
Merge remote-tracking branch 'upstream/control-flow-linearity' into c…
thwfhk Jul 1, 2023
2615823
add a readme
thwfhk Jul 1, 2023
7b9a8c2
update the syntax of presence variables
thwfhk Jul 5, 2023
ed2c26c
some backward compatibility
thwfhk Jul 5, 2023
6086c36
update the documentation
thwfhk Jul 6, 2023
0235d3e
typo
thwfhk Jul 6, 2023
dd43b18
Merge remote-tracking branch 'upstream/master' into control-flow-line…
thwfhk Jul 31, 2023
366aaaf
update document
thwfhk Sep 19, 2023
0d3c5b9
Fixed a stupid and hard-to-find bug about flattening effect types, wh…
thwfhk Sep 19, 2023
6b741ee
update the original handlers test
thwfhk Sep 19, 2023
5d5fa5e
update the original typecheck_examples test
thwfhk Sep 19, 2023
6d9c0fa
update tests and compatibility document
thwfhk Sep 19, 2023
6ba486d
update tests and compatibility document
thwfhk Sep 19, 2023
436bc16
trim trailing whitespaces
thwfhk Sep 19, 2023
f8e5218
remove some test comments
thwfhk Sep 19, 2023
29a0605
add more test cases on usage of linear resources in handlers
thwfhk Sep 21, 2023
55ab6c5
try to enable automatic xlin, but it seems to be not very useful
thwfhk Sep 21, 2023
3728cee
fix a testcase
thwfhk Sep 21, 2023
7202caa
add more CFL tests
thwfhk Sep 21, 2023
ee943ba
modify the type signatures of some library functions
thwfhk Sep 21, 2023
fc00331
fix a test case because APs are unlimited
thwfhk Sep 21, 2023
0f10124
give a more flexible type to the prelude function fork
thwfhk Sep 21, 2023
8336bf3
more tests
thwfhk Sep 22, 2023
29e7eec
remove a redundant line
thwfhk Sep 22, 2023
5413bca
more tests from the paper
thwfhk Sep 22, 2023
f17866c
remove a doc file
thwfhk Sep 25, 2023
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
114 changes: 114 additions & 0 deletions control-flow-linearity-in-Links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Control-Flow Linearity

Enable the feature of tracking control-flow linearity by passing the
flag `--track-control-flow-linearity`. It is usually used with
`--enabled-handlers`.

## New constructs

New types and kinds:

* `A =@ B` is the signature for linear operations (`A => B` for unlimited operations)
* `Lin` is the kind for linear effect variables (`Any` for effect
variables with no linear restriction)

New terms:

* `lindo L` invokes the linear operation `L`
* `case <L =@ r> -> M` handles the linear operation `L`
* `xlin` switches the current control flow to linear


## Control flow

We use the concept *control flow* to mean terms that are sequencing
evaluated. Control flows carry effects that are invoked by the terms.
In other words, the terms on the same control flow share effects.
Since Links' effect system is based on row polymorphism, the effect
types of all terms on the same control flow are unified.

Function bodies introduce their own control flows. The computations
being handled (the `M` in `handle M {...}`) also have their own
control flows, which will be merged with the control flow outside
after being handled.

Control flows are unlimited by default. We are allowed use both
unlimited and linear operations, but only unlimited variables. We can
switch the current control flow to linear by invoking the keyword
`xlin`. In a linear control flow, we are allowed to use both unlimited
and linear variables, but only linear operations. Once the control
flow is switched to linear, all invocations of unlimited operations in
it (even before `xlin`) would cause type errors.

## Examples

1. We can mix linear and unlimited operations in an unlimited control
flow. The anonymous effect variable `_` has kind `Any` by default
which can be unified with any operations.

```
links> fun() {do U; lindo L};
fun : () {L:() =@ a,U:() => ()|_}-> a
```

2. We can only invoke linear operations in a linear control flow. A
linear control flow allows the usage of the linear channel `ch`.
Now the effect variable `_` has kind `Lin` explicitly, which can
only be unified with linear operations of signatures `=@`.

```
links> fun(ch:End) {xlin; lindo L; close(ch)};
fun : (End) {L:() =@ ()|_::Lin}~> ()
```

3. We can only handle linear operations using a linear handler
(indicated by `=@` in the clause) which guarantees the continuation
is used exactly once.

```
links> handle ({xlin; lindo L + 40}) { case <L =@ r> -> xlin; r(2) };
42 : Int
```

4. We can handle unlimited operations before merging with a linear
control flow as long as we guarantee that they are all handled.
Note that after handling, the presence variable of `Choose` and row
variable are both linear.

```
links> fun(ch:End) { xlin; close(ch); handle ({if (do Choose) 40 else 2}) {case <Choose => r> -> r(true) + r(false)} };
fun : (End) {Choose{_::Lin}|_::Lin}~> Int
```

5. When writing explicit quantifiers, we must explicitly annotate the
kinds of row variables using `e::Row(Lin)` or `e::Row(Any)`. If the
subkind is not specified, it means `Lin` in order to be compatible
with variants and records in Links.

```
links> sig f:forall e::Row(Any). () {Get:() => Int|e}-> Int fun f() {do Get}; f();
f = fun : () {Get:() => Int|_}-> Int
```

## Design choices

It is necessary to have `xlin` for the same reason of having `linfun`
in Links. For example, neither of the following functions has a more
general type than the other one.

```
links> fun(x) {lindo L; x};
fun : (a) {L:() =@ ()|_}-> a
links> fun(x) {xlin; lindo L; x};
fun : (a::Any) {L:() =@ ()|_::Lin}-> a::Any
```

## Compatibility

* Compatible with all previous handler tests (except part of
polymorphic operations and effect sugar).
* Not entirely compatible with FreezeML, SessionFail, etc.
* Passes all tests with the flag disabled, except
* `!FAILURE: Operation polymorphism (2)`
* `!FAILURE: Operation polymorphism (3)`
* `!FAILURE: Typecheck example file examples/handlers/monadic_reflection.links`
11 changes: 11 additions & 0 deletions core/basicsettings.ml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ module Handlers = struct
|> sync)
end

(* control-flow linearity *)
module CTLinearity = struct
let enabled =
Settings.(flag "track_control_flow_linearity"
|> privilege `System
|> synopsis "Enables the control-flow linearity extension"
|> convert parse_bool
|> CLI.(add (long "track-control-flow-linearity"))
|> sync)
end

module Sessions = struct
let exceptions_enabled =
Settings.(flag "session_exceptions"
Expand Down
4 changes: 2 additions & 2 deletions core/commonTypes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ module Linearity = struct
| Unl -> true
| _ -> false

let to_string = function
let to_string ?(is_eff=false) = function
| Any -> "Any"
| Unl -> "Unl"
| Unl -> if is_eff then "Lin" else "Unl"

let min l r =
match l, r with
Expand Down
4 changes: 2 additions & 2 deletions core/compilePatterns.ml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ let rec desugar_pattern : Types.row -> Sugartypes.Pattern.with_pos -> Pattern.t
| Variant (name, Some p) ->
let p, env = desugar_pattern p in
Pattern.Variant (name, p), env
| Operation (name, ps, k) ->
| Operation (name, ps, k, _) ->
let ps, env =
List.fold_right
(fun p (ps, env) ->
Expand Down Expand Up @@ -996,7 +996,7 @@ let compile_handle_cases
fields
in
let rec extract t = match TypeUtils.concrete_type t with
| Types.Operation (domain, _) ->
| Types.Operation (domain, _, _) ->
let (fields, _, _) = TypeUtils.extract_row domain |> TypeUtils.extract_row_parts in
let arity = StringMap.size fields in
if arity = 1 then
Expand Down
4 changes: 2 additions & 2 deletions core/desugarDatatypes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ module Desugar = struct
| Record r -> Types.Record (row alias_env r t')
| Variant r -> Types.Variant (row alias_env r t')
| Effect r -> Types.Effect (row alias_env r t')
| Operation (f, t) -> Types.Operation ( Types.make_tuple_type (List.map datatype f)
, datatype t )
| Operation (f, t, b) -> Types.Operation ( Types.make_tuple_type (List.map datatype f)
, datatype t, b)
| Table (tmp, r, w, n) -> Types.Table (tmp, datatype r, datatype w, datatype n)
| List k -> Types.Application (Types.list, [(PrimaryKind.Type, datatype k)])
| TypeApplication (tycon, ts) ->
Expand Down
45 changes: 30 additions & 15 deletions core/desugarEffects.ml
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ let simplify_tycon_env (tycon_env : Types.tycon_environment) : simple_tycon_env
in
SEnv.fold simplify_tycon tycon_env SEnv.empty

let make_anon_point k sk freedom =
let make_anon_point ?(is_eff=true) k sk freedom =
let var = Types.fresh_raw_variable () in
Unionfind.fresh
(Types.Var (var, (k, DesugarTypeVariables.concrete_subkind sk), freedom))
(Types.Var (var, (k, DesugarTypeVariables.concrete_subkind ~is_effect:is_eff sk), freedom))

(** A map with SugarTypeVar as keys, use for associating the former
with information about what
Expand Down Expand Up @@ -364,7 +364,8 @@ let cleanup_effects tycon_env =
WithPos.make ~pos (match node with
| Datatype.Operation _ -> node
| Datatype.Forall (qs, dt) -> Datatype.Forall (qs, elaborate_op dt)
| _ -> Datatype.Operation ([], dt))
| _ -> Datatype.Operation ([], dt, DeclaredLinearity.Unl))
(* nullary operations without =@ are unlimited *)
in
let fields = List.map (function
| name, Present dt when not (TypeUtils.is_builtin_effect name) ->
Expand All @@ -379,18 +380,18 @@ let cleanup_effects tycon_env =
if not (SugarTypeVar.is_resolved stv)
then begin
let gen_unresolved_eff () =
SugarTypeVar.mk_unresolved shared_effect_var_name None `Rigid
SugarTypeVar.mk_unresolved shared_effect_var_name ~is_eff:true None `Rigid
in
let to_unresolved_general sk fr =
SugarTypeVar.mk_unresolved "$" sk fr
SugarTypeVar.mk_unresolved "$" ~is_eff:true sk fr
in
let gen_resolved_flex () =
SugarTypeVar.mk_resolved_row
(let var = Types.fresh_raw_variable () in
Unionfind.fresh
(Types.Var (var, (PrimaryKind.Row, (lin_unl, res_effect)), `Flexible)))
(Types.Var (var, (PrimaryKind.Row, (default_effect_lin, res_effect)), `Flexible)))
in
let name, sk, fr = gue stv in
let name, (_, sk), fr = gue stv in
if has_effect_sugar
then
begin
Expand Down Expand Up @@ -453,7 +454,7 @@ let gather_mutual_info (tycon_env : simple_tycon_env) =
match eff_var with
| Datatype.Open stv
when (not (SugarTypeVar.is_resolved stv))
&& SugarTypeVar.get_unresolved_exn stv = ("$eff", None, `Rigid)
&& SugarTypeVar.get_unresolved_exn stv = ("$eff", (true, None), `Rigid)
->
self#with_implicit
| _ -> self )
Expand Down Expand Up @@ -768,7 +769,7 @@ let gather_operations (tycon_env : simple_tycon_env) allow_fresh dt =
let point =
lazy
(let var = Types.fresh_raw_variable () in
Unionfind.fresh (Types.Var (var, (PrimaryKind.Presence, default_subkind), `Rigid)))
Unionfind.fresh (Types.Var (var, (PrimaryKind.Presence, default_effect_subkind), `Rigid)))
in
StringMap.add op point m)
v StringMap.empty),
Expand All @@ -786,7 +787,7 @@ let preprocess_type (dt : Datatype.with_pos) tycon_env allow_fresh shared_effect
lazy
(let var = Types.fresh_raw_variable () in
Unionfind.fresh
(Types.Var (var, (PrimaryKind.Row, (lin_unl, res_any)), `Rigid)))
(Types.Var (var, (PrimaryKind.Row, default_effect_subkind), `Rigid)))
in
Some point
| _ ->
Expand Down Expand Up @@ -948,7 +949,7 @@ class main_traversal simple_tycon_env =
(* Looking for this gives us the operations associcated with
the $eff var. The kind and freedom info are ignored for the lookup *)
let eff_sugar_var =
SugarTypeVar.mk_unresolved shared_effect_var_name None
SugarTypeVar.mk_unresolved shared_effect_var_name ~is_eff:true None
`Rigid
in

Expand Down Expand Up @@ -1018,8 +1019,8 @@ class main_traversal simple_tycon_env =
if not allow_implictly_bound_vars then
raise (DesugarTypeVariables.free_type_variable dpos);

let _name, sk, freedom = SugarTypeVar.get_unresolved_exn stv in
let mtv = make_anon_point (PrimaryKind.Row) sk freedom in
let _name, (is_eff, sk), freedom = SugarTypeVar.get_unresolved_exn stv in
let mtv = make_anon_point ~is_eff:is_eff (PrimaryKind.Row) sk freedom in
let rtv = SugarTypeVar.mk_resolved_row mtv in
(o, D.Open rtv)
| D.Open srv when not (SugarTypeVar.is_resolved srv) ->
Expand Down Expand Up @@ -1177,7 +1178,7 @@ class main_traversal simple_tycon_env =
({ node = t, args, b; pos } as tn) =
if StringMap.find t implicits then
let var = Types.fresh_raw_variable () in
let q = (var, (PrimaryKind.Row, (lin_unl, res_effect))) in
let q = (var, (PrimaryKind.Row, (default_effect_lin, res_effect))) in
(* Add the new quantifier to the argument list and rebind. *)
(* let qs = List.map (snd ->- OptionUtils.val_of) args @ [q] in *)
let args = args @ [ SugarQuantifier.mk_resolved q ] in
Expand All @@ -1191,7 +1192,7 @@ class main_traversal simple_tycon_env =
let tycon_env = SEnv.bind t (env_args, true, None) tycon_env in
let shared_effect_var : Types.meta_row_var Lazy.t =
lazy
(Unionfind.fresh (Types.Var (var, (PrimaryKind.Row, (lin_unl, res_effect)), `Rigid)))
(Unionfind.fresh (Types.Var (var, (PrimaryKind.Row, (default_effect_lin, res_effect)), `Rigid)))
in
let shared_var_env =
StringMap.add t (Some shared_effect_var) shared_var_env
Expand Down Expand Up @@ -1324,6 +1325,20 @@ module Untyped = struct
let name = "effects"

let program state program' =
(* (
match program' with
| (bindings, body) ->
(match body with
| None -> ()
| Some body ->
print_string "---------- BEGIN desugarEffects input -----------\n";
let () = print_string "bindings:\n" in
let _ = if (bindings = []) then () else (print_string -<- show_binding) <| List.hd bindings in print_string "\n";
print_string "body:\n";
(print_string -<- show_phrase) body;
print_string "\n";
print_string "---------- END desugarEffects input -----------\n";)
); *)
let open Types in
let tyenv = Context.typing_environment (context state) in
let program' = program tyenv.tycon_env program' in
Expand Down
6 changes: 4 additions & 2 deletions core/desugarSessionExceptions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ object (o : 'self_type)
let ty =
Types.fresh_type_variable (CommonTypes.lin_any, CommonTypes.res_any) in
let with_pos x = SourceCode.WithPos.make ~pos x in
let doOp = DoOperation (with_pos (Operation failure_op_name), [], Some (Types.empty_type)) in
(* FIXME: WT: I don't know whether should it be lindo or not *)
Copy link
Member

Choose a reason for hiding this comment

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

Lets leave as-is for now.

Copy link
Member

Choose a reason for hiding this comment

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

Though, I see that you have changed the signature of SessionFail below... I would be inclined not to change any of the session exceptions stuff.

Copy link
Contributor Author

@thwfhk thwfhk Jun 9, 2023

Choose a reason for hiding this comment

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

I have to change the SessionFail operation to be linear to make the functions in prelude.links well-typed. Otherwise we would get a type error because we use the unlimited operation SessionFail with linear channels. I also need to change some signatures of functions in lib.ml, because by default e::Row is e::Row(Unl,Any), which can only be unified with linear operations.

This is also a challenge for hiding this feature behind a new flag; it is not entirely compatible with the current Links syntax.

Copy link
Member

Choose a reason for hiding this comment

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

I see... The settings logic does allow you specify transitive dependencies, so if session exceptions are toggled, then you can forcibly toggle linear continuations too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually it is fine to not toggle control_flow_linearity, but we still need to make sure that the existing library code is compatible with it. FOr example, using unlimited row variable e::Row(Any, Any) by default and use the linear operation signature =@ for SessionFail.

let doOp = DoOperation (with_pos (Operation failure_op_name), [], Some (Types.empty_type), DeclaredLinearity.Lin) in
(o, with_pos (Switch (with_pos doOp, [], Some ty)), ty)
| { node = TryInOtherwise (_, _, _, _, None); _} -> assert false
| { node = TryInOtherwise (try_phr, pat, as_phr, otherwise_phr, (Some dt)); pos }
Expand Down Expand Up @@ -122,7 +123,8 @@ object (o : 'self_type)
(Utility.gensym ~prefix:"dsh" ()) in

let otherwise_pat : Sugartypes.Pattern.with_pos =
with_dummy_pos (Pattern.Operation (failure_op_name, [], cont_pat)) in
(* FIXME: WT: I don't know whether should it be lincase or not *)
thwfhk marked this conversation as resolved.
Show resolved Hide resolved
with_dummy_pos (Pattern.Operation (failure_op_name, [], cont_pat, DeclaredLinearity.Lin)) in

let otherwise_clause = (otherwise_pat, otherwise_phr) in

Expand Down
Loading
Loading