Tail self-call optimization in generated C code?

Eric Brunel eric.brunel at pragmadev.com
Mon Aug 21 13:14:31 UTC 2017


Le 2017-08-21 14:55, PR a écrit :
> 2017-08-21 14:11 GMT+02:00, Eric Brunel <eric.brunel at pragmadev.com>:
>> Hello all,
>> 
>> I'm trying to get ECL to optimize tail self-calls in the C code
>> generated from the Lisp files and it looks like I'm missing something,
>> because I can't find a way to do that.
> 
> Here is an example that works (not written by me, it's from cliki.net):
> 
> (defun fib (n)
>   "Tail-recursive computation of the nth element."
>   (check-type n (integer 0 *))
>   (labels ((fib-aux (n f1 f2)
>              (if (zerop n)
>                  f1
>                  (fib-aux (1- n) f2 (+ f1 f2)))))
>     (fib-aux n 0 1)))
> 
> The generated C code uses goto, as you can see:
> 
> static cl_object LC1fib_aux(cl_object v1n, cl_object v2f1, cl_object 
> v3f2)
> {
>  cl_object env0;
>  const cl_env_ptr cl_env_copy = ecl_process_env();
>  cl_object value0;
>  ecl_cs_check(cl_env_copy,value0);
>  {
> TTL:
>   if (!(ecl_zerop(v1n))) { goto L1; }
>   value0 = v2f1;
>   cl_env_copy->nvalues = 1;
>   return value0;
> L1:;
>   v1n = ecl_one_minus(v1n);
>   {
>    cl_object v4;
>    v4 = v3f2;
>    v3f2 = ecl_plus(v2f1,v3f2);
>    v2f1 = v4;
>   }
>   goto TTL;
>  }
> }
> 

It does indeed, and I think I got it: the optional parameters to my 
function seem to prevent the optimization from happening. If I rewrite 
my function as:

(defun enumerate-aux (l index result)
   (cond
     ((null l) (reverse result))
     (t (enumerate-aux (cdr l) (+ index 1) (cons (list index (car l)) 
result)))
   )
)
(defun enumerate (l) (enumerate-aux l 0 nil))

the generated code for the enumerate-aux function is as expected:

static cl_object L1enumerate_aux(cl_object v1l, cl_object v2index, 
cl_object v3result)
{
  cl_object T0, T1;
  cl_object env0;
  const cl_env_ptr cl_env_copy = ecl_process_env();
  cl_object value0;
  ecl_cs_check(cl_env_copy,value0);
  {
TTL:
   if (!(v1l==ECL_NIL)) { goto L1; }
   value0 = cl_reverse(v3result);
   return value0;
L1:;
   {
    cl_object v4;
    v4 = ecl_cdr(v1l);
    {
     cl_object v5;
     v5 = ecl_plus(v2index,ecl_make_fixnum(1));
     T0 = ecl_car(v1l);
     T1 = cl_list(2, v2index, T0);
     v3result = CONS(T1,v3result);
     v2index = v5;
     v1l = v4;
    }
   }
   goto TTL;
  }
}

Not sure I understand why, but I have a solution.

Thank you very much!
  - Eric -


> 
> Paul
> 
> 
>> 
>> Here is the behavior I'm getting for this simple Lisp function:
>> 
>> (defun enumerate (l &optional (index 0) (result nil))
>>    (cond
>>      ((null l) (reverse result))
>>      (t (enumerate (cdr l) (+ index 1) (cons (list index (car l))
>> result)))
>>    )
>> )
>> 
>> As far as I can see, this function is properly tail-recursive, so I
>> expected the generated C code to take the into account and generate a
>> goto instead of a recursive call. But what I'm getting is this:
>> 
>> static cl_object L1enumerate(cl_narg narg, cl_object v1l, ...)
>> {
>>   cl_object T0, T1, T2, T3, T4;
>>   cl_object env0;
>>   const cl_env_ptr cl_env_copy = ecl_process_env();
>>   cl_object value0;
>>   ecl_cs_check(cl_env_copy,value0);
>>   if (ecl_unlikely(narg<1)) FEwrong_num_arguments_anonym();
>>   if (ecl_unlikely(narg>3)) FEwrong_num_arguments_anonym();
>>   {
>>    cl_object v2index;
>>    cl_object v3result;
>>    va_list args; va_start(args,v1l);
>>    {
>>     int i = 1;
>>     if (i >= narg) {
>>      v2index = ecl_make_fixnum(0);
>>     } else {
>>      i++;
>>      v2index = va_arg(args,cl_object);
>>     }
>>     if (i >= narg) {
>>      v3result = ECL_NIL;
>>     } else {
>>      i++;
>>      v3result = va_arg(args,cl_object);
>>     }
>>    }
>>    va_end(args);
>>    if (!(v1l==ECL_NIL)) { goto L3; }
>>    value0 = cl_reverse(v3result);
>>    return value0;
>> L3:;
>>    T0 = ecl_cdr(v1l);
>>    T1 = ecl_plus(v2index,ecl_make_fixnum(1));
>>    T2 = ecl_car(v1l);
>>    T3 = cl_list(2, v2index, T2);
>>    T4 = CONS(T3,v3result);
>>    value0 = L1enumerate(3, T0, T1, T4);
>>    return value0;
>>   }
>> }
>> 
>> The recursive call is generated as a recursive call in C too...
>> 
>> I'm almost sure I've seen C code generated from ECL that generated 
>> self
>> tail calls as goto's, and I know it works when using the bytecode, 
>> where
>> you just have to compile the function. But my search for a way to
>> trigger this behavior for the generated C code has been unsuccessful 
>> so
>> far.
>> 
>> I'm using ECL 16.1.3. I used "ecl -c ... --compile ..." to get the C
>> code, but my main build uses asdf:make-build with an ASD file.
>> 
>> 



More information about the ecl-devel mailing list