[m-users.] Weird compilation error
Zoltan Somogyi
zoltan.somogyi at runbox.com
Fri Oct 2 17:50:31 AEST 2026
On Thu, 01 Oct 2026 06:23:39 +0200, Volker Wysk <post at volker-wysk.de> wrote:
> > It seems some compiler pass is "optimizing away" or otherwise hiding
> > the recursive call.
> >
> > Without a complete compilable module, and its exact compilation command,
> > finding out why this happens would be a futile guessing game.
>
> You can clone it from here:
With the ability to reproduce the error, finding the issue was not hard.
The reason for the missing recursive call is that the loop invariant
optimization finds two small unifications (neither of which appears
in the source code, with both added by the compiler itself, as part of its
usual housekeeping) that compute the same result on each iteration.
It therefore replaces the last (and only) recursive call to master_loop_normal
with a call to its loop-invariant-optimized version, which takes the
two variables constructed by these unifications as additional arguments.
This is not so much as a bug as an unfinished design, because we have not
really considered until now what the relationship should be between the
require tailrec pragma and optimizations like this that can redirect recursive
calls.
I see four obvious ways forward.
One: disable optimizations that can redirect recursive calls for predicates
and functions that have require tailrec pragmas. I don't like this option,
because people use such pragmas to help ensure efficiency, and this
course of action works by deliberately foregoing possible optimizations.
Two: when we find that a predicate with a require tailrec pragma
has no recursive calls at all, check whether it contains calls to predicates
that were derived from itself. (Such derivations are recorded as part of
the "origin story" of each predicate.) If it does, then print an informational
message about being unable to verify tail recursion. This does not forego
any optimization, but leaves the pragma useless.
Three: have the loop invariant transformation effectively *transfer*
the pragma to the optimized predicate. This is the most useful option,
but it is also the one that takes the most work to implement.
Four: check require tailrec pragmas before we perform optimizations
that can redirect recursive calls. This should be as effective as option three,
and is a bit simpler to implement, but it is not as future proof. This is because
there is the possibility that some compiler pass that comes later in the
compilation process, either a pass that exists now or one that will be added
in the future, takes tail recursive code and makes it non-tail recursive.
I am pretty sure that we will never add any such pass *intentionally*,
but bugs happen, and therefore we may add them *unintentionally*.
Note that the fourth alternative requires detecting tail recursion twice.
The first time, after semantic checks are complete and optimizations
have not yet begun, has the task of checking for violations of require
tailrec pragmas, while the second time is after all program transformations
are complete, and has the task of marking tail recursive calls for the
code generator.
Zoltan.
More information about the users
mailing list