Last summer I started with a renovation of the back-end of the Stratego compiler. The implementation of failure handling was based on the C exception handling API (setjmp/longjmp). However, this turned out to be costly. In the new compilation scheme failure is now represented as a NULL ATerm.
The second part of the renovation was to get rid of nested functions in the generated C code. After a year I have finally managed to finish this part of the renovation. In this blog I’ll explain the old and new compilation schemes.
At the same time as the setjmp/longjmp were introduced in the Stratego compiler (somewhere in 2001), I discovered that gcc supported nested functions in C. The following example illustrates why this was a useful feature.
The strategy bottomup
is defined as follows:
bottomup(s) = all(bottomup(s)); s
A recursive invocation of bottomup(s)
is passed to the one-level traversal
operator all
.
In the generated C code, strategy operators are implemented as functions that take a function
pointer as argument. For instance, the all
operator is implemented by the function
SRTS_all
with signature
ATerm SRTS_all(ATerm f(ATerm), ATerm);
However, a strategy expression such as bottomup(s)
is clearly not something with
a function pointer. Therefore, the compiler lifts such expressions from argument positions into
local definitions. Thus, the definition of bottomup
is rewritten to:
bottomup_1_0(e_1 : ATerm() -> ATerm()|) = let a_0(|) = bottomup_1_0(e_1|) in all(a_0|) end ; e_1
Now the argument of all
is just a function name, which can be translated
as passing a function pointer. The definition of a_0
has to be nested in the definition of bottomup
since it refers to the argument e_1
of
that definition.
Nested functions in C provide a very convenient feature for translating such local definitions.
A nested function is an ordinary C function that is defined within the scope of another C function. Thus, it can refer to all arguments and local variables of the enclosing function. This
enables a straightforward implementation of local definitions in Stratego; a local definition is
replaced with a nested function. Using this approach the rewritten definition of bottomup
above is translated as follows:
ATerm bottomup_1_0 (ATerm e_1 (ATerm), ATerm t) { auto ATerm a_0 (ATerm t); ATerm a_0 (ATerm t) { t = bottomup_1_0(e_1, t); if((t == NULL)) goto fail_1 ; return(t); fail_1 : return(NULL); } t = SRTS_all(a_0, t); if((t == NULL)) goto fail_0 ; t = e_1(t); if((t == NULL)) goto fail_0 ; return(t); fail_0 : return(NULL); }
While nested functions make the translation of nested definitions straightforward, they posed several problems.
In the first place, nested functions are only supported by gcc, reducing the portability of code generated by the Stratego compiler.
Secondly, nested functions in gcc are implemented by means of trampolines, a small piece of code that is generated at run-time and stored on the stack. This code essentially stores the closure of the nested function, that is, a pointer to the actual function and (pointers to) the variables in the enclosing stack-frame(s) to which it has access. The problem with this implementation is that it executes code stored on the stack, which is forbidden on more and more platforms, as it poses a security risk. Thus, we were confronted with users not being able to use Stratego/XT on some platforms.
Finally, the implementation of nested functions in gcc does not have a high priority with gcc developers, which caused build failures for the Stratego/XT distribution with some versions of gcc, especially on MacOSX.
While nested functions were quite convenient, they had to go and be replaced with an explicit
mechanism for handling closures. The mechanism chosen was to use static links to reach stack
frames of enclosing functions and an explicit closure data-structure to represent a function
pointer with its environment. For example, in the new translation scheme, the signature of the SRTS_all
operator becomes:
ATerm SRTS_all(StrSL sl, StrCL s, ATerm t)
StrSL
represents static links and StrCL
is the type of closures.
Locally defined strategies are now translated to top-level (static) functions and an explicit
closure is allocated in the enclosing function. Thus, the bottomup
strategy
is translated to the following pair of C functions:
ATerm bottomup_1_0 (StrSL sl, StrCL e_1, ATerm t) { sl_decl(sl); sl_funs(1); sl_init_fun(0, e_1); { struct str_closure a_0 = { &(lifted_0) , &(frame) }; StrCL lifted_0_cl = &(a_0); t = SRTS_all(sl, lifted_0_cl, t); if((t == NULL)) goto fail_1 ; t = cl_fun(e_1)(cl_sl(e_1), t); if((t == NULL)) goto fail_1 ; } return(t); fail_1 : return(NULL); } static ATerm lifted_0 (StrSL sl, ATerm t) { sl_decl(sl); t = bottomup_1_0(sl_up(sl), sl_fun_cl(0, sl), t); if((t == NULL)) goto fail_2 ; return(t); fail_2 : return(NULL); }
The declaration
struct str_closure a_0 = { &(lifted_0) , &(frame) };
allocates a closure consisting of a pointer to the function lifted_0
and
a pointer to the current frame
, which is an explicit data-structure in
which escaping functions and variables are stored. The frame
variable is
declared by the macro sl_decl
at the start of the bottomup_1_0
function. A pointer to this closure (lifted_0_cl
) is passed to the invocation of SRTS_all
.
Note that a call to a function passed as a closure needs unwrapping of the closure into a function pointer and a static link:
t = cl_fun(e_1)(cl_sl(e_1), t);
While I started the implementation of this new translation scheme already in September 2005, I didn’t get around to finishing it until yesterday. We spent a large effort last Fall to get the documentation of Stratego/XT in better shape, and after that I was busy teaching and doing god knows what, but it was not hacking the Stratego compiler.
With this stumbling block out of the way, the door is open to a whole new set of innovations to the compiler and the Stratego language. A better module system is high on the wish list. Also we want to move from a separation of the compiler in phases to a compiler library that will allow larger parts of the compilation process to be applied to a single definition and elimination of intermediate ATerm files.