[m-rev.] for review: document require_tail_recursion pragmas
Julien Fischer
jfischer at opturion.com
Wed Jun 24 21:30:37 AEST 2026
On Wed, 24 Jun 2026 at 02:57, Zoltan Somogyi <zoltan.somogyi at runbox.com> wrote:
...
> I came up with a design that identifies non-tail-recursive calls
> in the presence of trailing for both backends. Its implementation
> is attached, for your review.
> Make non-tail-recursion reports work with trailing.
...
> diff --git a/compiler/hlds_markers.m b/compiler/hlds_markers.m
> index b1736e9e5..322eaa82a 100644
> --- a/compiler/hlds_markers.m
> +++ b/compiler/hlds_markers.m
> @@ -464,13 +464,20 @@
> % inserting one or more of these lifted-out deconstructions
> % before the original switch.
>
> - ; feature_lambda_from_try.
> + ; feature_lambda_from_try
> % This lambda goal wraps the main part of a try goal,
> % the part that does the main job but may throw an exception.
> % This feaure is used to inform the code that warns about
Existing issue s/feaure/feature/
> % infinite recursion that the lambda goal *will* be executed
> % in context in which it is constructed.
>
> + ; feature_non_tailrec_reported.
> + % This goal is a (self or mutual) recursive call that is not
> + % TAIL recursive, and this fact has already had a report
> + % generated for it. Do not generate another report.
> + % (The precise reason for this is explained on the comment
*in* the comment
> + % on the ntrcr_program in mark_tail_calls.m.)
...
> diff --git a/compiler/mark_tail_calls.m b/compiler/mark_tail_calls.m
> index 00a8f732b..7550ced1d 100644
> --- a/compiler/mark_tail_calls.m
> +++ b/compiler/mark_tail_calls.m
...
> @@ -138,9 +141,39 @@
> :- pred maybe_override_warn_params_for_proc(proc_info::in,
> warn_non_tail_rec_params::in, warn_non_tail_rec_params::out) is det.
>
> +%---------------------------------------------------------------------------%
> +
> :- type nontail_rec_call_reason
> - ---> ntrcr_program
> - % The call is not a tail call in the program.
> + ---> ntrcr_program(set_tree234(later_op))
> + % The call is not a tail call in the program, because
> + % it is followed by some operations.
> + %
> + % If the set is nonempty, which it always should be when
> + % we generate a report from inside this module, the elements
> + % of the set describe the kinds of operations that will happen
> + % after the call.
> + %
> + % If the set is empty, which it always will be when
> + % we generate a report from outside this module, then we have
> + % no such information. (Our outside callers are part of the
> + % MLDS code generator, and its tasks do not include keeping track
> + % of such things).
> + %
> + % The ability of the MLDS code generator to generate diagnostics
> + % about non-tail recursion is mostly a historical accident.
> + % We should take away that ability as soon as we are sure that
> + % all the non-tail recursive calls that the code generator
> + % would report are reported by this module as well.
> + %
> + % However, until that is done, we do NOT way both (a) the code of
s/way/want/
> + % do_mark_tail_rec_calls_in_proc and (b) the MLDS code generator
> + % to generate reports for the same non-tail recursive call.
> + % This is because we add a short explanation for any later_op
> + % in the set that is not visible (or at least not easily visible)
> + % to users. The code that writes out error_specs removes any
> + % duplicates, but it cannot remove *almost* duplicates
> + % that differ only in the presence vs absence of these
> + % explanations. We therefore attach
>
> ; ntrcr_mlds_in_scc_not_in_tscc
> % The call is a tail call in the program, but the MLDS code
...
> @@ -547,16 +582,78 @@ find_output_args(ModuleInfo, Types, Modes, Vars, OutputVars) :-
>
> %---------------------------------------------------------------------------%
>
> - % Is the current position within the procedure a tail position?
> - % If it is, what are the output arguments?
> - %
> -:- type at_tail
> - ---> at_tail(list(prog_var))
> - ; not_at_tail(later_rec_call).
> +:- type at_tail_info
> + ---> at_tail_info(
> + % Is the current position within the procedure a tail position?
> + % The answer is "yes" if-and-only-if this set is empty.
> + set_tree234(later_op),
>
> -:- type later_rec_call
> - ---> have_seen_later_rec_call
> - ; have_not_seen_later_rec_call.
> + % If the current position within the procedure a tail position,
*is* a tail position
> + % then what are the output arguments? These will start out
> + % as the vector of the procedure's output arguments, but
> + % if our backwards traversal sees an assignment unification
> + % to one of these variables, then we replace it in this list
> + % with the id of the variable it is assigned from.
> + % (This is because any recursive call must define the
> + % assigned-from variable, since the mode system ensures
> + % that the assigned-to variable cannot have as generators
> + % *both* the recursive call and the assignment unification.)
> + %
> + % If the first argument is a nonempty set, then this field
> + % is meaningless. The reason why we do not have a separate
> + % function symbol in this type that eliminates this field
> + % in that event is that we have tried that, and it complicates
> + % all our code for managing branching goals.
> + list(prog_var)
> + ).
> +
> +:- type later_op
> + ---> later_unify
> + ; later_unify_assign
> + ; later_nonrec_call
> + ; later_rec_call
> +
> + ; later_par_join
> + % Each conjunct in a parallel conjunction is followed by
> + % the join of the parallel threads.
> +
> + ; later_disjunction
> + ; later_disjunction_fail
> + ; later_switch
> + ; later_ite
> + ; later_negation
> + % These mean that a branched goal (a disjunction, a switch,
> + % or an if-then-else) follows the goal whole initial at_tail_info
s/whole/whose/
> + % contains one of these later_ops.
> + %
> + % Just in case we can use these detaiils later, we distinguish
s/detaiils/details/
> + % empty disjunctions (later_disjunction_fail) from nonempty ones
> + % (later_disjunction), and negations (which are effectively
> + % specials case of if-then-elses) from their general form.
s/specials case/special cases/
> +
> + ; later_next_disjunct
> + % This applies to goals that represent non-last disjuncts.
> + % Such goals are followed by the code of the next disjunct.
> +
> + ; later_cond_end
> + % This applies to goal that represent an if-then-else's condition.
> + % Such goals are followed by the code that switches between
> + % the then part and the else part.
> +
> + ; later_negation_end
> + % This applies to goals inside negations. Such goals are
> + % followed by the code that flips success to failure or vice versa.
This one does not appear to be used (i.e. in a call to add_later_op/3).
> +
> + ; later_commit
> + % This applies to goals inside scopes that perform commits.
> + % Such goals are followed by the code that performs that commit.
> +
> + ; later_trail_prune.
> + % This applies to goals that represent disjuncts
> + % - in a model_det or model_semi disjunction
> + % - in trailing grades.
> + % Such goals are followed by an operation that prunes away
> + % the trail ticket that was created to represent the disjunction.
>
> :- type call_is_self_or_mutual_rec
...
> @@ -677,7 +771,7 @@ mark_tail_rec_calls_in_goal(Goal0, Goal, AtTail0, AtTail, !Info) :-
> % the conjunction into a loop control scope, and therefore any
> % parallel conjunctions we find at *this* point cannot support
> % tail calls.
> - not_at_tail(AtTail0, AtTail1)
> + add_later_op(later_par_join, AtTail0, AtTail1)
> ),
> list.reverse(Goals0, RevGoals0),
> mark_tail_rec_calls_in_rev_conj(RevGoals0, RevGoals, AtTail1, AtTail,
> @@ -685,40 +779,65 @@ mark_tail_rec_calls_in_goal(Goal0, Goal, AtTail0, AtTail, !Info) :-
> list.reverse(RevGoals, Goals),
> GoalExpr = conj(ConjType, Goals),
> Goal = hlds_goal(GoalExpr, GoalInfo0)
> + ;
> + % No goal inside a negation can be a *tail* call. However, a negation
> + % can contain recursive calls about whose non-tail-call nature
> + % we COULD generate a report.
> + GoalExpr0 = negation(SubGoal0),
> + mark_tail_rec_calls_in_goal(SubGoal0, SubGoal,
> + AtTail0, AtTail1, !Info),
Do you not need to add later_negation_end to AtTail0 here?
The rest looks ok.
Julien.
More information about the reviews
mailing list