So here's a brief summary of the Felix execution model, and how this
impacts error handling options.

At present, I'm finding the error handling a right pain. Apart from the
enhanced C method or returning an error code -- using unions for
the enhancement -- Felix provides a non-local goto which works
in procedures but only sort of works from functions. There's a try/catch
which works around primitives which throw from C++.

Generally however, there's nothing consistent. Here's why:

Felix has two execution methods: procedures and functions.
Functions work like in C. The return address is on the machine
stack. This allows easy mixing of C/C++ functions and Felix ones
in expressions.

Procedures on the other hand use a spaghetti stack: linked lists
of heap allocated frames. These are called "stack frames" but they're
not (usually) on the stack. Procedures are called and returned by
doing a C level return of a pointer to the "stack frame" to be next
invoked, I call this a procedural continuation. This only works
if there is nothing on the machine stack, because the return must return
control to the scheduler.

When a procedure is called from inside a function, the compiler
generates a "mini-scheduler".

In practice, the optimiser gets rid of a lot of  what I just described.
Stuff is inlined away. Functions are converted to procedures
which store values at a pointer. Both function and procedures
frames are normally heap allocated but both can be optimised
onto the machine stack sometimes, and sometimes the
subroutines can be optimised to plain old ordinary C functions.
The "as if" rule allows this, the compiler decides is it's safe to
use the machine stack instead of the heap.

Now, non-local gotos are turned into local ones by inlining.
However you cannot "goto" across the machine stack.
Its cool to goto a code up the spaghetti stack because to do that
you just return the goto point and frame continuation to the
scheduler. If a function return address gets in the way on the
stack Felix actually solves this problem by *throwing* the
continuation as an exception, which is then caught by the next scheduler
routine and the continuation scheduled. This will create a mess if

(a) the scheduled frame is not active. In that case it will run until
it tries to return, however it will have stale variables in it.
If the stack is forcibly unwould by zeroing out return addresses
(which is current practice) the frame will just die. The Felix system
cannot catch jumping into dead frames at the moment.
So its not safe.

(b) there are interleaving layers of machine and spaghetti stacks.

All of this also makes "throwing" an exception difficult.
If you throw one in a procedure, it will be caught immediately
by the scheduler and the program terminated. The scheduler
has no idea where a handler might be.

However the C++ try/catch mechanism uses the machine stack,
so if you use this in a procedure and call a Felix procedure,
it will fail because the rule that the stack must be empty
is broken: try/catch ONLY works when wrapping primitives.

i am not concerned that C++ style exceptions don't work,
because the design is plain WRONG. Throwing and catching
by types is absurd. Ocaml does this better, throwing 
a single exception type which is a union. However Ocaml
exceptions are wrong too,. Undelimited exceptions are just wrong.

Why does Felix uses this weird execution model?

Well, we want sophisticated control options, in particular
control exchange. We use procedural continuations for that.
But we want to stick to the C/C++ ABI, so functions have to use
that. Hence the two kinds of stacks.

Now, the right way to fix this archaic C execution model
in C is to generate a single big function, and then then use
goto for control transfers, and maintain positions manually.
Either an integer and a fat case switch can be used or if
you have gcc a computed goto. Felix already does these
things but it generates multiple functions. It uses a gcc only
assembler label hack for non-local gotos if gcc is the compiler
(this is an optimisation).

So why not one big function? Because gcc and clang at 
least are pretty bad compilers, they just cannot compile
large functions, even with low levels of optimisation.
This is because they use non-linear optimisations within
functions and get away with their bad performance because
most programmers write programs with many small functions.

So Felix is stuck with a set of lousy compromises so it can
work with existing C/C++ compilers. Generating our own
assembler would solve this but you'd need a back end for
every processor and duplicate all the low level optimisations
done in existing compilers, and we aim to avoid that by 
leveraging existing compilers.
        
So Felix has nasty semantics, because it pushes C/C++ towards doing
things they weren't designed to do and aren't very good at supporting.
So we have rules like "you cannot use synchronous channels in
functions" although it works if you can inline away the functions.
the "inline" adjective enforces this for direct calls of non-recursive
functions. And Felix converts many functions to procedures anyhow.
Unfortunately neither operation applies when a closure is created,
because invocation follows a normal C call protocol which requires
the machine stack. [Nested schedulers can solve this problem,
and there's code that implements them manually, however replacing
the mini-scheduler the compiler generates with a full one may incurs
some nasty overheads so I need to find a way to predict when this
isn't necessary]

Note that machine stack swapping is standard conforming when
implemented by pre-emptive threads (its just horribly inefficient).

I want to briefly do some theory. There is a model of computation
called CPS = Continuation Passing model. In the function version
a function f passed argument a, is modified to also accept a
continuation p. When f is finsihed, it doesn't return, but calls p.
A continuation is "the whole rest of the program".

Since the call to p is a tail call, it is actually just a goto.
So in CPS we have no subroutines, we have blocks of flat
code and gotos which target location variables.

In C like languages, we ALSO use continuation passing.
The continuation is nothing but the return address.
It is passed to a function on the machine stack and
it is jumped to by a return instruction. The continuation is
not explicitly given, but underneath its continuation passing alright!

Now the reason for this digression: the "right" way to handle exceptions
is by invoking an *alternate* continuation. You just pass your function
two continuations: one for normal exit and one for errors. The error 
continuation
is more or less the "catch" part of a C++ try/catch. However there's a MAJOR
difference to exceptions: you cannot fail to "catch" an abnormal exit by 
invoking
a continuation.

With this model, which Felix supports using non-local gotos wrapped in
procedures, you may have to pass many continuations if you want
distinct actions for different errors. Since code finding an error has
to invoke a particular continuation is forces that to be passed as an
argument to the function, and this propagates up the call chain
to the point where the continuation is created. So the type system
enforces the provision of your error handlers.



--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
Try New Relic Now & We'll Send You this Cool Shirt
New Relic is the only SaaS-based application performance monitoring service 
that delivers powerful full stack analytics. Optimize and monitor your
browser, app, & servers with just a few lines of code. Try New Relic
and get this awesome Nerd Life shirt! http://p.sf.net/sfu/newrelic_d2d_may
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to