Conversation
- This commit imports an updated version of the local_exn.diff patch submitted on http://caml.inria.fr/mantis/view.php?id=5879 - Adds local exception declarations to expressions: let exception C of ... in ... (This introduces a conflict in the grammer, to be investigated.) - Compile uses of such local exceptions to staticcatch/staticraise when the constructor is used only locally as an exception (i.e. raised or caught) in the current function block. The optimization is done as a rewriting in the Lambda code, which requires to reverse-engineer the code compiled for the declaration, the raise, and the catch. Alternatively, one could trigger the static compilation scheme in translcore while compiling the local exception expression.
… the local exception constructor) to trigger a warning when the local exception is not compiled with the static scheme.
|
This seems awesome ! Please write a short description for everybody to understand what it is without looking at the changes :) |
|
Here is a description of the changes, recovered from Alain's clear commit messages:
|
utils/warnings.ml
Outdated
There was a problem hiding this comment.
Just out of curiosity, why it's 54 instead of 53 here ? Thanks
There was a problem hiding this comment.
Just a typo, thank!
…l need to use the bound exception value.
Do you really have two different syntaxes for the same thing? By analogy with Other than that, I like the feature but I'm a bit worried by the "reverse engineering" part of the implementation. |
This is indeed not very satisfactory. Detecting that a local exception can be compiled into a static one during the translation from Typedtree to Lambda would seem preferable, but this is not straightforward either, in particular if one wants to support try..with block with both static and non-static exceptions (perhaps combined with or-patterns). |
|
If I understand well the purpose of this patch, we should introduce a new construct in the language, that does not make it more expressive, but just allows the compiler to optimize the static case. I don't think it is a good idea to extend the core language for such purposes. That's the purpose of annotations, while the core language should remain simple and clean. For your case, I would argue to add the annotation on existing exception definitions: It is more verbose, but at the same time, such optimizations should remain rare in standard code. Note that, from what I understand, |
|
Fully agree. We should really have |
but still, "let module M = struct exception E end in ... " does not pollute the global env either.
It would be worth checking that this one allocation is not removed by flambda, as the module itself is probably never used. Anyway, my whole argument is that we should not ruine the language just to be a bit faster. The whole point of an optimizing compiler is to avoid that, by optimizing such patterns.
I am sure we can be very creative to make OCaml programs unreadable and unmaintainable. I strictly prefer to because code from the first one can be copied directly to toplevel, whereas code from the second one needs to be changed to be moved upwards.
I would actually expect the contrary: the optimization should be done today on exceptions defined in local modules (which can already be defined), and maybe later in local exceptions if they are accepted in the core language. Submitting both together is misleading, as people might want the optimization and thus support the PR, and then are forced to accept local exceptions that they don't care about. |
I cannot imagine that someone wanting to use the optimization would prefer to use "let module M = struct exception ... end in" over "let exception ... in". |
No, let exception is not just syntactic sugar, and cannot be treated as such. Try creating, say, a camlp4 extension to support it, and you'll only be able to create a fragile approximation it. Consider: There is no way to now purely syntactically that the raised E is indeed the local exception (M could redefine another exception E).
|
I do. Actually, I even had such a patch, that optimized only that pattern. And I hate when you have to explain to beginners that their "let module M = struct exception E end in ..." was not optimized because they didn't know that there was another construct "let exception E in..." that was the only one that was optimized, and is just in the language for that reason. I think we should stop extending OCaml "because it is possible", and extend it only "when it is needed". |
|
@lefessan does your patch also optimize let f x =
let module M = struct
exception A of a
exception B of b
end in
....If I need several local exceptions, I would undoubtely write this and possibly get the unoptimized behavior. At least local exceptions remove that temptation. |
|
Being forced to invent two names (M/E) just for creating a forward jump label is just ugly (being forced to declare the jump is already quite heavy, but I'm ready to compromise on it). Yes, it is not "needed" to introduce local exceptions, but I maintain that it is a natural addition to the language. Once introduced, nobody would think about writing |
|
I completely agree with Alain here. Many times I've needed something like a local exception, and instead had to add a global exception. I never even thought about wrapping it in a local module, which is the problem -- thinking in terms of local modules is not simple or very natural IMO. |
I agree with this, it is better to use a more general analysis to detect whether an exception can escape rather than only optimizing a specific syntax. However, at the moment quite a few similar optimizations are annoyingly tied to syntax and until we improve the mechanisms in the middle-end for performing such analyses that is probably not going to change.
Adding support for |
It depends exactly what you have in mind. Would the local module contain only one exception? Several of them? Also other components? In any case, it would add some complexity to the "reverse engineering" part, which is already quite intricate. More importantly, I don't see any compelling reason to do that: anyone aware of the feature would naturally use the local exception form. It should be quite straightforward to know in advance when the optimization would be triggered (as for tail-calls), so making its detection more and more clever for no direct benefit doesn't seem a good direction to me. |
|
I would like to understand the reverse-engineering part better then -- whether it could be avoided by asking Luc to do part of the work in the pattern-matching part, and why it is problematic to support `let module). My cursory understanding is that you are reverse-engineering the case-tree produced by the pattern-matching compiler from the Wouldn't it be easier to do this analysis and rewriting at the typedtree level, so you don't have to do any reverse-engineering? P.S.: the reason I'm wondering about this is that I would like to have the property that |
|
The current approach does not impose that Doing the work during the translation from Typedtree to Lambda is the other option, but it requires indeed a stronger interaction with the pattern matching compiler. My guess is that it would make the implementation more complex, although perhaps more robust. And conceptually, optimizations are typically done in later phases than this Typedtree->Lambda translation, which allows to better interact with other optimization (which could argue to do that later than Lambda, but I'm interested to have static exceptions available for js_of_ocaml). |
|
I was thinking of doing a transformation pass on the typedtree, instead of having to interact with the pattern-matching compiler. I agree that Lambda (or later) seems a more natural target for optimizations, but we would need an escape analysis approach that is more robust that the current reverse-engineering. Note: flambda should have some framework in place for non-escaping allocations, would it make sense to do the optimization there? Re. js_of_ocaml, @hnrgrgr worked on delaying the bytecode generation pass to after flambda-generation, and while this is not in discussion anymore for now, I think this should be kept in mind as an indication that flambda could eventually be appropriate even for js_of_ocaml scenarios. |
This is going to be tricky if you want to preserve sharing of handlers for static and non-static exceptions: let exception E in
try ...
with E | Exit -> ... |
This is good to know, thanks. |
You could first rewrite this into |
|
Indeed, it might be worth trying something along these lines. Cases such as let exception E of t in
...
try ...
with Exit | E _ -> ...should be treated with care (inventing an argument-less local exception?). |
|
In fact I don't think such a rewrite would be necessary, if you try by doing it at the typedtree level by lifting static{raise,catch} to the typedtree (I think you had patches around doing just that). In this case you can just recover sharing by having both cases static-raise to a shared handler, or do whichever clever thing your current implementation is doing, at the typedtree level. |
|
It would be quite weird to include in the Typedtree representation some constructions not available in the surface language, just to be able to express some optimizations as Typedtree->Typedtree transformations. The Typedtree is supposed to keep the same structure as the Parsetree (annotated with type information). People already complain that optimizations are too tightly coupled to the concrete syntax... |
|
On the contrary, I find that expressing interesting optimizations as source-to-source optimisations (whenever possible) is quite elegant. But you obviously have more experience on OCaml optimisations so I'd trust your gut feelings. Then I'd suggest to maybe look at the flambda level if it can avoid some of the reverse-engineering work (by using more general analyses already implemented). |
I agree! But the logical consequence would be to extend the surface language with a dedicated construct for static raises (even if it does not allow to mix static and non-static exceptions in a or-pattern), which was my first choice. |
|
Another direction to simplify the reverse engineering part would be to be more restrictive in the optimization, not supporting mixed try...with combining static and non-static exceptions (in the same clause or different ones); and perhaps annotating trywith nodes in the lambda code with the information of whether they include a catch-all case (this must disable the optimization). |
It would be more than weird. How would you implement |
|
I like the feature, as @damiendoligez , the implementation is not very satisfied. Actually, I like the original proposal on mantis(I think its implementation is also more elegant compared with this one and more lightweight). For the javascript backend, the local exception is not just an optimization, it's a feature as a control flow(the real exception in Javascript is terribly slow) |
|
Now that local exceptions are present as a language feature, are there any plans to add the optimization? |
…alues on every call (ocaml#260)
This is a follow up to http://caml.inria.fr/mantis/view.php?id=5879 . More specially, this is an updated version of the local_exn.diff patch submitted there.
Expressions are extended with local exception definition:
This can be useful in itself when an exception is used for local control flow, and is not intended to be used externally.
More interestingly, an optimization pass detects when the exception is used purely locally. This means that the exception is only used in
raisecontext, or pattern matched by atry..withormatch...with exception; moreover, eachraiseof the exception must be captured by a handler in the same function (both can be in a nested function, but they must be at the same level). When it is the case, the exception cannot escape, and each it is trivial to associate to eachraiseits corresponding handler. The exception is then translated to "staticraise/staticcatch" in the lambda intermediate language, which corresponds to jumps (and they can cross try...with boundaries).All the optimization is implemented in simplif.ml, by reverse engineering the code compiled for generic
try...withhandler. This can be fragile (e.g. the code is quite different in bytecode -g mode because other simplifications are disabled, which makes the detection harder), especially to detect cases where the sametry...withhandler matches both static and non-static exceptions.An attribute
[@static]or[@ocaml.static]can be used to get a warning when the local exception is not compiled to a static one:In the Mantis ticket, I argued for a really new language construct to express these static exceptions. The advantage was a potentially lighter syntax (no need to declare the jump labels), and more explicit guarantees (without requiring attributes). As discussed on Mantis, it seems impossible to turn exceptions into static ones if they are not local, hence the introduction of a new language construct (local exceptions) here, but arguably, this one fits better in the current language design that "static exceptions".