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