-
-
Notifications
You must be signed in to change notification settings - Fork 44
Lazy evaluation Semantics
Here's the warning!!
Felix INLINES STUFF. Whenever it can.
It does not understand fthreads and closures. It does not know they run asynchronously.
If you have a procedure which refers to a variable in its context, the onus is on YOU the programmer to make sure the context is the right one.
Use of a variable of a context MAY refer to the context at the time the closure runs, not the time it is created. And use of a parameter MAY refer to the value at the time of creation.
To make things work you have to disable inlining.
Felix inlines two kinds of things:
(a) procedures (b) values
In addition note loops are not tail recursions, they're loops. The same loop body is used for every iteration.
It turns out to be HARD to propagate the value of a variable to a closure. It just isn't obvious. You generally have two things you have to do:
-
Make sure the context is a noinline procedure so that it will be spawned on the heap every invocation. That means that the closure created inside it will refer to the stack frame at the time it was created, because the variable isn't modified inside a loop.
-
Make parameters "var" parameters to force eager evaluation.
You may be used to C/C++ where parameters are copied from the callers context to the callee's stack frame before calling the callee function.
Felix does NOT do that by default. It may inline the variable into the procedure, so that the reference to the parameter is replaced by a reference to the argument: this is called lazy evaluation.
If your parameter is unqualified or marked "val" you are letting the compiler choose whether to use eager or lazy evaluation.
To force eager evaluation (anywhere, not just for parameters) you have to use a "var".
Note in particular, a noinline procedure P will not BE inlined .. but arguments can still be inlined INTO it. So here:
noinline proc P (var x:int) () { println x; }
is what you need if you expect this to work:
var a =1;
p1 := P a;
++a;
p2 := P a;
++a;
p1();
p2();
If you don't put var x, P could end up saying:
noinline proc P () () { println a; };
which refers to the variable a which is 3 on both calls.
This is tricky! It's not intuitive that noinline has semantics! It's not intuitive that var is not merely a hint!
But it can't be fixed without getting rid of lazy evaluation, which is precisely what inlining IS. Without these semantics we end up with Haskell or C performance.
Felix is fast because the compiler assumes it is free to optimise anything however it likes: the defaults allow the compiler to choose between lazy and eager evaluation of both procedures (inlining) and variables (substitution of value for name).
C/C++ never do this. They only inline if the compiler is SURE, or the programmer didn't read the Standard. The prime example in C++ is the elision of copy constructors.
Felix may elide EVERYTHING unless you tell it otherwise.
Just to help get your head around this -- and note I find this very difficult -- C has lazy evaluation too: here's lazy variable in C:
#define x a
Of course, Felix's version is type safe and respects scoping rules, but the temporal semantics MAY be the same (the compiler may treat x as a #define OR it may use a storage location).