Skip to content

Commit 4968289

Browse files
EliasCKiko
authored and
Kiko
committed
Beta support for Kappa (parapluu#744)
* Beta support for Kappa Commit history below: Experimental support for linear capabilities This commit lets the programmer annotate traits with modes. An included trait must have a mode, either given at inclusion time, or given in the trait header (this works as a default). There are currently two modes; `linear` whose references must be treated linearly, and `unsafe` which works just like a regular type (no restrictions on aliasing). A value of class type may be cast into a trait type (like in `master`) or a (composite) capability type. All modes must match exactly. Here is a small example program to show what is currently possible: ``` -- Foo defaults to unsafe unsafe trait Foo<t> require f : t def foo() : void print "Foo!" -- Inclusion of Bar requires specifying a mode trait Bar -- C can be aliased freely passive class C<t> : Foo<t> + unsafe Bar f : t -- D must be treated linearly unless upcast to Foo<int> passive class D : Foo<int> + linear Bar f : int class Main def main() : void let x = new D y = consume x : linear Bar + unsafe Foo<int> z = consume y : Foo<int> w = z in w.foo() ``` The checking that linearity is preserved is done using an additional pass, implemented in the file `src/typechecker/Capturechecker.hs`. This keeps the changes made to `Typechecker.hs` to a minimum, which is nice. Basically, the capturechecker figures out where references are captured and when a linear reference is safe to capture. The linear references **should** be interoperable with all the features currently in Encore, but testing is very welcome! It should be noted that consumes are currently not atomic (the value is read and nullified sequentially), but this shouldn't matter in a race-free program. Linear streams are possible but not very useful, since both `get` and `getNext` require you to consume the stream. For any useful patterns we would need a way to `get` and move the stream forward in one go (maybe `consume s` should be the way to do this?). Experimental support for borrowing This commit adds support for declaring parameters as stackbound (`S(Foo)`). A linear reference can be passed as a stack bound parameter without consuming, as the linear alias is buried for the duration of the call (borrowing requires a synchronous call). A stackbound may be locally aliased (but not returned from functions or methods), but cannot be used in parallel (so far this means that it may not be passed to an active object). The following program exemplifies most uses of borrowing: ``` -- Stackbounds cannot be returned from functions or methods -- def steal(x : S(Token)) : S(Token) -- x def borrow(x : S(Token)) : void{ let thief = new Thief<Token> y = x -- Stackbounds can be locally aliased in{ -- thief.steal(x); -- Stackbounds cannot be passed to active objects borrowAgain(y); -- Stackbounds can be passed to other borrowing functions/methods } } def borrowAgain(x : S(Token)) : void{ x.foo(); -- You can call methods and access fields on stackbound objects } class Main -- f : S(Foo<int>) -- Fields cannot be stackbound def borrow(x : S(Token)) : void borrow(x.f) -- Fields of linear objects can be borrowed as long as each node in the path is linear def main() : void let x = new Token thief = new Thief<Token> in{ -- thief.steal(x); -- Linear values cannot be borrowed by active objects x.f = new Token; this.borrow(x); -- Synchronous method calls can borrow linear values } trait Foo def foo() : void print "Foo!" passive class Token : linear Foo f : Token class Thief<t> def steal(x : S(t)) : void () ``` Borrowing should interplay nicely with arrays and most other programming constructs, but please do report anomalies of any kind. made embeds free Support for read mode This was actually implemented before, but was lost in a git related accident... A trait with the `read` mode can only require `val` fields of safe types. The safe types are active objects, primitives and passive objects with a safe mode (currently only `read` itself). Support for subordinate mode Types containing a capability type with the `subord` mode cannot be returned from its surrounding aggregate. This is checked by controlling the target of a method call or field access and throwing an error unless the target is `this` or is fully encapsulated itself (i.e. is a capability type with *only* subordinate capabilities). Traits without manifest modes are typechecked as if they were subordinate, but they still need to be given an explicit mode at inclusion. A known issue is that subordinate state can be leaked via closures. Support for thread mode Capabilities with the thread mode may not leave the active object that created it. This is checked when calling methods on active objects; if an argument is thread or the return type is thread, the target must either be this, or itself be thread. This also means that fields with the thread mode can only be declared in traits that are manifestly thread. Experimental splitting support A let expression can now have several variables *per expression*, and each variable can optionally have a type annotation: ``` let i, f : float = 42; print i; -- 42 print f; -- 42.000000 ``` If the types of the variables are linear, these are checked to be in conjunction in the type of the right hand side: ``` let p = new Person(); let a : Aged, n : Named = consume p; ``` If the types are not linear any splitting is allowed. Not giving a type to a variable defaults to the type of the right hand side (meaning any splitting will fail). Fix parapluu#451, parsing error with manifest modes Since `read` is not a reserved keyword, the following code: ``` class T read trait Get ``` was parsed as ``` class T read <Expecting ':'> trait Get ``` This is fixed by adding a `try` that allows the parser to backtrack when reading a field fails. Read mode must be manifestly set To simplify typechecking, the read mode must be manifestly set. It can still be used on inclusion, but only if the included trait already is manifestly read. Fix parapluu#452, consider safe composite types safe This commit considers `Maybe T`, `Stream T`, `Fut T` and `Par T` as safe types iff `T` is a safe type. Tuples are safe if all the constituent types are safe. Ranges are also safe. Arrays are not safe since they can be written to. In the future one could consider a type like `[val T]` to be safe (an array that can only be read from). I'll leave that design up to @glundi. Fixed upcasting bug Before this commit, a composite type was only checked for subsumption of operations, meaning that `T1 + T2` could be cast to `T1 * T2`. Now an upcast must preserve the conjunctions of the subtype (i.e. not introduce new ones). It is still safe to forget conjunctions by casting `T1 * T2` into `T1 + T2`. Fix parapluu#450, check conjunctiveness when splitting Before this commit, there was a bug when checking of whether an unpack was allowed that lead to code like `let read Get, linear Set = consume Cell` compiling. This is now fixed so that all types in an unpack needs to be separated by a conjunction in the capability being unpacked, unless all the types can safely be (at least locally) aliased. A known bug is that we also need to check that the modes are preserved for all types in an upcast, meaning that the `read Get` above should also be `linear` (assuming `Get` and `Set` are conjunctive in `Cell`). Fixed capture bug when matching on tuples This commit fixes a bug where matching on a tuple with a linear value would not allow you to capture that value in a pattern: ``` let x = new LinearThing(); match (42, consume x) with (n, y) => () -- Cannot capture linear expression y ``` The solution is a bit clunky but works. It adds a boolean in the meta-blob to mark which expressions are patterns. Patterns can then be ignored by the capture checker. Disallow sending borrowed values to constructors Fix parapluu#604, a bug where borrowed values could be passed as (non-borrowed) arguments to constructors. Constructors now work like normal method calls. Also moved all (both) tests regarding capabilities to a test-suite of their own and added more tests regarding borrowing. Prevent arrays of borrowed elements Disallow the type `[borrowed T]`, since it can be used to duplicate linear values (see the test `arrayBorrow.enc`). Support for safe closures A closure is now annotated when capturing a `thread`, `subord` or `borrowed` capability, meaning you cannot cheat the type system by capturing state in closures. See tests `capabilities/closure*` for examples. Check overriding of methods in read traits This commit adapts method overriding and provision to consider read traits. A required method in a read trait can only be provided by another read trait. A read trait can only be extended with `val` fields of safe type. See tests `readTrait*` for examples. A current issue is that there is no way to consider passive classes safe. Currently, there is a special case for the class `String`, but we should come up with a way that makes it possible to consider classes safe. Thread mode => Local mode Remove unsafe traits from tests Check for clashing manifest modes Read and subordinate are minor modes Support for moded classes (NO TESTS) Support for looking up marker trait (ignore mode) Translated capability test suite Translated basic tests to Kappa Translated trait tests to Kappa Translated concurrency tests Translated the remaining tests, modulo shared Support for shared mode (very imprecise...) Only allow safe type arguments Warnings for unsafe operations Currently passing arrays and splitting conjunctions Fixed bug in capability splitting Support for strong updates (dropping linear capabilities) Fix regression Updated last bits of standard library Allow local in active Added tests and moved old tests around * Remove comments and unused code * Translated Savina benchmarks to Kappa * Removed null-check hack from Savina * Refactoring after Kiko's suggentions * Fixed bug when giving mode to unmoded class * Fixed bug with implicitly safe class * Set formal arrow type * Merged errors * Merge capturechecking errors with typechecking errors * Relax polymorphism to allow local type arguments Instead, polymorphic values cannot be passed between actors (currently this is only warned about). * Improve error messages * Removed unused Makefiles
1 parent 830199c commit 4968289

File tree

510 files changed

+4194
-2970
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

510 files changed

+4194
-2970
lines changed

emacs/encore-mode/encore-mode-expansions.el

+5-3
Original file line numberDiff line numberDiff line change
@@ -462,14 +462,15 @@
462462
(let (p1 p2 (case-fold-search nil))
463463
(progn
464464
(move-end-of-line nil)
465-
(re-search-backward "\\<class\\|trait\\>")
465+
(re-search-backward "\\<class\\>\\|\\<\.+\>? *\\<trait\\>")
466466
(move-end-of-line nil)
467467
(skip-chars-forward " \t\n")
468468
(setq p1 (point))
469469

470470
(while (and (not (eobp))
471471
(not (string-match "^[ \t]*\\<end\\>" (current-line))))
472-
(if (string-match encore-block-open-regex (current-line))
472+
(if (and (not (string-match "\\<require\\>" (current-line)))
473+
(string-match encore-block-open-regex (current-line)))
473474
(encore-skip-block))
474475
(forward-line 1))
475476
(skip-chars-backward " \t\n")
@@ -493,7 +494,8 @@
493494
(forward-line 1)
494495
(while (and (not (eobp))
495496
(not (string-match "^[ \t]*\\<end\\>" (current-line))))
496-
(if (string-match encore-block-open-regex (current-line))
497+
(if (and (not (string-match "\\<require\\>" (current-line)))
498+
(string-match encore-block-open-regex (current-line)))
497499
(encore-skip-block))
498500
(forward-line 1))
499501

emacs/encore-mode/encore-mode.el

+21-13
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,24 @@
5252
;; Please keep these lists sorted
5353
(setq encore-keywords
5454
'(
55+
"active"
5556
"as"
5657
"and"
5758
"async"
5859
"await"
60+
"borrowed"
5961
"break"
6062
"by"
6163
"case"
6264
"chain"
6365
"class"
66+
"consume"
6467
"def"
6568
"do"
6669
"else"
6770
"end"
6871
"eos"
6972
"for"
70-
"foreach"
7173
"fun"
7274
"get"
7375
"getNext"
@@ -79,18 +81,22 @@
7981
"let"
8082
"liftf"
8183
"liftv"
84+
"linear"
85+
"local"
8286
"match"
8387
"module"
8488
"new"
8589
"not"
8690
"or"
87-
"passive"
8891
"print"
8992
"println"
9093
"qualified"
94+
"read"
9195
"repeat"
9296
"require"
97+
"shared"
9398
"stream"
99+
"subord"
94100
"suspend"
95101
"then"
96102
"this"
@@ -111,6 +117,7 @@
111117
"BODY"
112118
"EMBED"
113119
"END"
120+
"unsafe"
114121
))
115122
(setq encore-constants
116123
'(
@@ -203,12 +210,10 @@
203210
(if (equal first "def")
204211
(if (string-match "\\<def\\>" line)
205212
(match-beginning 0)
206-
(if (string-match "\\<passive\\>" line)
213+
(if (string-match "\\(\\<.+\\>\\)? *\\<class\\>" line)
207214
(+ (match-beginning 0) encore-tab-width)
208-
(if (string-match "\\<class\\>" line)
209-
(+ (match-beginning 0) encore-tab-width)
210-
(if (string-match "\\<trait\\>" line)
211-
(+ (match-beginning 0) encore-tab-width)))))
215+
(if (string-match "\\(\\<.+\\>\\)? *\\<trait\\>" line)
216+
(+ (match-beginning 0) encore-tab-width))))
212217

213218
(if (equal first "fun")
214219
(if (string-match "\\<where\\>" line)
@@ -230,7 +235,7 @@
230235
(match-beginning 1)
231236
(if (string-match "\\<class\\>" line)
232237
(match-beginning 0)
233-
(if (string-match "\\<trait\\>" line)
238+
(if (string-match "\\<.+\\>? *\\<trait\\>" line)
234239
(match-beginning 0)))))
235240

236241
(if (equal first "else")
@@ -305,14 +310,14 @@
305310

306311
(setq encore-block-open-regex
307312
(concat "\\<def\\>" "\\|"
308-
"\\<class\\>" "\\|"
309-
"\\<passive" "\\|"
310-
"\\<trait\\>" "\\|"
313+
"\\<fun\\>" "\\|"
314+
"\\(\\<.+\\>\\)? *\\<class\\>" "\\|"
315+
"\\(\\<.+\\>\\)? *\\<trait\\>" "\\|"
311316
"\\<while\\>" "\\|"
312317
"\\<for\\>" "\\|"
313318
"\\<repeat\\>" "\\|"
314319
"\\<do\\>" "\\|"
315-
"\\<fun\\>[^=>\n]*\\($\\|--\\)" "\\|"
320+
"\\<fun\\>" "\\|"
316321
"\\<let\\>" "\\|"
317322
"\\<if\\>" "\\|"
318323
"\\<unless\\>" "\\|"
@@ -420,7 +425,7 @@
420425
(defvar encore-imenu-generic-expression
421426
'(("passive class" "^\s*passive\s+class\s*\\(\\<\\w+\\>\\) *\\(\\[.*\\]\\)? *:?.*" 1)
422427
("active class" "^\s*class\s*\\(\\<\\w+\\>\\)" 1)
423-
("trait" "^\s*trait\s*\\(\\<\\w+\\>\\)" 1)
428+
("trait" "^\\<.+\\>?\s*trait\s*\\(\\<\\w+\\>\\)" 1)
424429
("method definition" "^\s*def\s*\\(.*\s+\\)*\\(\\<\\w+\\>\\) *\\(\\[.*\\]\\)?(" 2)
425430
("function definition" "^\s*fun\s*\\(\\w+\\) *\\(\\[.*\\]\\)?(" 1))
426431
"Contains regexes to parse Encore with imenu")
@@ -466,6 +471,9 @@
466471
(error " *** Error during typechecking *** \n"
467472
"\"" (file-name) "\"" " (line " line ", column " column ")\n"
468473
(message))
474+
(error " *** Error during capturechecking *** \n"
475+
"\"" (file-name) "\"" " (line " line ", column " column ")\n"
476+
(message))
469477
(info line-start "Importing module" (message) line-end)
470478
)
471479
:modes encore-mode)

emacs/encore-mode/snippets/encore-mode/main

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# key: main
44
# expand-env: ((yas-also-auto-indent-first-line 't))
55
# --
6-
class Main
6+
active class Main
77
def main(args : [String]) : unit
88
$0
99
end

modules/standard/Active/Active.enc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- This file was automatically converted by encorec
22

33
module Active
4-
class Active[inner]
4+
active class Active[inner]
55
var state : inner
66
def init(factory : () -> inner) : unit
77
this.state = factory()

modules/standard/Active/ActiveTest.enc

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import Active.Active
44
fun getActiveInt() : Active[int]
55
new Active[int](fun () => 1)
66
end
7+
8+
-- TODO: This won't compile until parametric types can be
9+
-- instantiated with local or linear types
710
fun getActiveFoo() : Active[Foo]
811
new Active[Foo](fun () => new Foo())
912
end
@@ -29,7 +32,7 @@ fun testApply() : unit
2932
assertTrue(2 == (get(a.getState())).read())
3033
end
3134
end
32-
passive class Foo
35+
local class Foo
3336
var f : int
3437
def init() : unit
3538
this.f = 1
@@ -41,7 +44,7 @@ passive class Foo
4144
this.f
4245
end
4346
end
44-
class Main
47+
active class Main
4548
def main() : unit
4649
testInit()
4750
testMap()

modules/standard/Boxed/Bool.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
module Bool
44

5-
passive class Bool
6-
var value : bool
5+
read class Bool
6+
val value : bool
77
def init(x : bool) : unit
88
this.value = x
99
end

modules/standard/Boxed/BoxedTest.enc

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Boxed.Real
44
import Boxed.Bool
55
import Boxed.Unit
66

7-
class Main
7+
active class Main
88
def main() : unit
99
val maxReal = EMBED (real) 1.7976931348623157E+308; END
1010
val minReal = EMBED (real) -1.7976931348623157E+308; END

modules/standard/Boxed/Char.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
module Char
44

5-
passive class Char
6-
var value : char
5+
read class Char
6+
val value : char
77
def init(x : char) : unit
88
this.value = x
99
end

modules/standard/Boxed/Integer.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
module Integer
44

5-
passive class Integer
6-
var value : int
5+
read class Integer
6+
val value : int
77
def init(x : int) : unit
88
this.value = x
99
end

modules/standard/Boxed/Real.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
module Real
44

5-
passive class Real
6-
var value : real
5+
read class Real
6+
val value : real
77
def init(x : real) : unit
88
this.value = x
99
end

modules/standard/Boxed/Unit.enc

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module Unit
44

5-
passive class Unit
5+
read class Unit
66
def show() : String
77
"()"
88
end

modules/standard/Data/Either.enc

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ end
77
fun Right[a, b](x : b) : Either[a, b]
88
new Right[a, b](x) : Either[a, b]
99
end
10-
trait Either[a, b]
10+
read trait Either[a, b]
1111
require def Left() : Maybe[a]
1212
require def Right() : Maybe[b]
1313
def map(f : b -> b) : Either[a, b]
@@ -44,8 +44,8 @@ trait Either[a, b]
4444
end
4545
end
4646
end
47-
passive class Left[a, b] : Either[a, b](x)
48-
var x : a
47+
class Left[a, b] : Either[a, b](x)
48+
val x : a
4949
def init(x : a) : unit
5050
this.x = x
5151
end
@@ -56,8 +56,8 @@ passive class Left[a, b] : Either[a, b](x)
5656
Nothing
5757
end
5858
end
59-
passive class Right[a, b] : Either[a, b](x)
60-
var x : b
59+
class Right[a, b] : Either[a, b](x)
60+
val x : b
6161
def init(x : b) : unit
6262
this.x = x
6363
end

modules/standard/Random.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ fun random(max : int) : int
1818

1919
END
2020
end
21-
passive class Random
22-
var seed : EMBED unsigned int END
21+
read class Random
22+
val seed : EMBED unsigned int END
2323
def Random_trace() : unit
2424
()
2525
end

modules/standard/Regex/Regex.enc

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ END
1313

1414
typedef PMatch = EMBED regmatch_t* END
1515
typedef RegexT = EMBED regex_t END
16-
passive class Regex
16+
local class Regex
1717
var pattern : String
1818
var maxSize : int
1919
var flagICase : bool
@@ -23,7 +23,7 @@ passive class Regex
2323
var executed : EMBED char* END
2424
def Regex_trace() : unit
2525
EMBED (unit)
26-
_enc__passive_String_t* _enc__field_pattern = _this->_enc__field_pattern;
26+
_enc__read_String_t* _enc__field_pattern = _this->_enc__field_pattern;
2727
encore_trace_object((*_ctx), _enc__field_pattern, _enc__trace_String);
2828

2929
regmatch_t* _enc__field_pMatch = _this->_enc__field_pMatch;

modules/standard/Regex/RegexTest.enc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- This file was automatically converted by encorec
22

33
import Regex
4-
class Main
4+
active class Main
55
def findRegex(regex : Regex, s : String) : unit
66
match regex.exec(s) with
77
case Just(true) =>

modules/standard/Set/OrderedSet.enc

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- This file was automatically converted by encorec
22

33
module OrderedSet
4-
passive class OrderedSet[t]
4+
local class OrderedSet[t]
55
var root : Node[t]
66
var size : int
77
var cmp : (t, t) -> int
@@ -46,7 +46,7 @@ passive class OrderedSet[t]
4646
end
4747
end
4848
end
49-
passive class Node[t]
49+
local class Node[t] : Id
5050
var elem : t
5151
var left : Node[t]
5252
var right : Node[t]
@@ -150,7 +150,7 @@ passive class Node[t]
150150
end
151151
end
152152
end
153-
passive class OrderedSetIterator[t]
153+
local class OrderedSetIterator[t]
154154
var cur : Node[t]
155155
def init(n : Node[t]) : unit
156156
if n == null then

modules/standard/Set/OrderedSetTest.enc

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ fun testSetSemantics() : bool
178178
os.size() == 4
179179
end
180180
end
181-
class Main
181+
active class Main
182182
def main() : unit
183183
assertTrue(testInit())
184184
assertTrue(testAdd())

modules/standard/Std.enc

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ module Std
22

33
-- This is currenly just a marker interface. In the future, id
44
-- might contain the logic for giving objects ids.
5-
trait Id
5+
read trait Id
66
end
77

88
-- Equivalence is currently only supported on a given type. In
9-
-- the future, other solutions are planned, possibly including a
10-
-- self type instead of this type parameter.
11-
trait Eq[t]
9+
-- the future, other solutions are planned, possibly including a
10+
-- self type instead of this type parameter.
11+
read trait Eq[t]
1212
require def eq(other:t) : bool
1313
end

0 commit comments

Comments
 (0)