[m-rev.] for review: fix mantis bug 539
Peter Wang
novalazy at gmail.com
Wed Oct 13 17:05:59 AEDT 2021
On Wed, 13 Oct 2021 15:14:18 +1100 "Zoltan Somogyi" <zoltan.somogyi at runbox.com> wrote:
> For review by Peter.
>
> Zoltan.
> Fix a bug in lco.m that left vars undefined.
>
> compiler/lco.m:
> Do not replace a plain call such as
>
> type_check(E, T)
>
> with
>
> LCMCpr_type_check_1(E, AddrOfT)
>
> which does NOT bind T, if later code in the procedure needs the value of T
> in the current stack frame.
>
> This fixes Mantis bug 539.
>
> tests/valid/Mmakefile:
> Enable the bug539 test case.
>
> tests/valid/Mercury.options:
> Do not force bug539.m to be compiled in asm_fast.gc; the bug
> is not grade dependent.
> diff --git a/compiler/lco.m b/compiler/lco.m
> index 10907e7a3..ac831fe00 100644
> --- a/compiler/lco.m
> +++ b/compiler/lco.m
> @@ -1559,20 +1561,70 @@ make_addr_vars([HeadVar0 | HeadVars0], [Mode0 | Modes0],
> unexpected($pred, "top_unused")
> ).
>
> -:- pred lco_transform_variant_goal(module_info::in, variant_map::in,
> - var_to_target::in, instmap::in, hlds_goal::in, hlds_goal::out, bool::out,
> + % The lco_transform_variant_* predicates can perform two transformations.
> + %
> + % The first transformation is done by lco_transform_variant_atomic_goal.
> + % It takes atomic goals that ground one or more of the output arguments
> + % of the current procedure that we are supposed to return not by the usual
> + % route, but by filling in a memory cell whose address we are given.
> + % We have to perform this transformation whereever it is applicable
> + % in the body of the procedure.
> + %
> + % The second transformation is done by lco_transform_variant_plain_call.
> + % It checks whether the callee of a plain call goal has an lco-optimized
> + % variants that is applicable to the call site, and if yes, calls that
variant (no s)
> + % variant instead of the original predicate. In the usual case where
> + % the callee is recursive, this is basically a short-circuiting operation:
> + % instead of calling first (say) p, which then calls lco-optimized-p
> + % which then calls itself, we call lco-optimized-p directly. The main
> + % benefit here is that the short-circuited code has a smaller footprint
> + % in the instruction cache, and will thus probably get fewer misses there.
> + %
> + % However, the second transformation replaces code that would record
> + % the value of the lco-optimized output variables of the callee in the
> + % current procedure's stack frame with code that puts the values of those
> + % variables in fields in heap cells. Any attempt by later code to look up
> + % the values of these variables would thus result in a compiler crash
> + % in the code generator (Mantis bug #539). We can therefore apply this
> + % tranformation (named lco_calls in this type) ONLY if the affected
> + % variables are NOT needed by later code. The argument of the second
> + % function symbol here gives the set of variables whose values *are*
> + % needed by later code.
> + %
> +:- type variant_transforms
> + ---> groundings_only
> + ; groundings_and_lco_calls(set_of_progvar).
Ok.
> +
> +:- pred lco_transform_variant_goal(module_info::in, variant_transforms::in,
> + variant_map::in, var_to_target::in, instmap::in,
> + hlds_goal::in, hlds_goal::out, bool::out,
> proc_info::in, proc_info::out) is det.
>
> -lco_transform_variant_goal(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> - Goal0, Goal, Changed, !ProcInfo) :-
> +lco_transform_variant_goal(ModuleInfo, Transforms, VariantMap, VarToAddr,
> + InstMap0, Goal0, Goal, Changed, !ProcInfo) :-
> Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
> (
> GoalExpr0 = conj(ConjType, Goals0),
> (
> ConjType = plain_conj,
> - lco_transform_variant_conj(ModuleInfo, VariantMap, VarToAddr,
> - InstMap0, Goals0, Goals, Changed, !ProcInfo),
> - GoalExpr = conj(ConjType, Goals),
> + % We want to process the conjuncts of the conjunction backwards,
> + % from last conjunct to first conjunct, so that when we process
> + % a conjunct, we know what variables the conjuncts to its right
> + % need as inputs. (This info is needed for the fix of Mantis
> + % bug #539.
Close bracket.
> + %
> + % However, processing each goal requires its initial instmap,
> + % and we can apply instmaps only from front to back. So we get
> + % rev_conj_and_attach_init_instmaps to record the initial instmap
> + % of each conjunct with that conjunct, and at the same time
> + % reverse the list. We then give this reversed, instmap-annotated
> + % list to lco_transform_variant_rev_conj.
> + rev_conj_and_attach_init_instmaps(InstMap0, Goals0,
> + [], RevGoalIMs0),
> + lco_transform_variant_rev_conj(ModuleInfo, Transforms, VariantMap,
> + VarToAddr, RevGoalIMs0, cord.init, GoalsCord, Changed,
> + !ProcInfo),
> + GoalExpr = conj(ConjType, cord.list(GoalsCord)),
> GoalInfo = GoalInfo0
> ;
> ConjType = parallel_conj,
Ok.
> @@ -1599,10 +1651,10 @@ lco_transform_variant_goal(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> ;
> GoalExpr0 = if_then_else(Vars, Cond, Then0, Else0),
> update_instmap(Cond, InstMap0, InstMap1),
> - lco_transform_variant_goal(ModuleInfo, VariantMap, VarToAddr, InstMap1,
> - Then0, Then, ThenChanged, !ProcInfo),
> - lco_transform_variant_goal(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> - Else0, Else, ElseChanged, !ProcInfo),
> + lco_transform_variant_goal(ModuleInfo, Transforms, VariantMap,
> + VarToAddr, InstMap1, Then0, Then, ThenChanged, !ProcInfo),
> + lco_transform_variant_goal(ModuleInfo, Transforms, VariantMap,
> + VarToAddr, InstMap0, Else0, Else, ElseChanged, !ProcInfo),
> Changed = bool.or(ThenChanged, ElseChanged),
> GoalExpr = if_then_else(Vars, Cond, Then, Else),
> GoalInfo = GoalInfo0
Reorder the two calls?
> +:- pred lco_transform_variant_rev_conj(module_info::in, variant_transforms::in,
> + variant_map::in, var_to_target::in,
> + list(goal_and_init_instmap)::in, cord(hlds_goal)::in, cord(hlds_goal)::out,
> bool::out, proc_info::in, proc_info::out) is det.
>
> -lco_transform_variant_conj(_, _, _, _, [], [], no, !ProcInfo).
> -lco_transform_variant_conj(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> - [Goal0 | Goals0], Conj, Changed, !ProcInfo) :-
> - lco_transform_variant_goal(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> - Goal0, Goal, HeadChanged, !ProcInfo),
> - update_instmap(Goal0, InstMap0, InstMap1),
> - lco_transform_variant_conj(ModuleInfo, VariantMap, VarToAddr, InstMap1,
> - Goals0, Goals, TailChanged, !ProcInfo),
> +lco_transform_variant_rev_conj(_, _, _, _, [], !Conjuncts, no, !ProcInfo).
> +lco_transform_variant_rev_conj(ModuleInfo, Transforms0, VariantMap, VarToAddr,
> + [RevGoalIM0 | RevGoalIMs0], !Conjuncts, Changed, !ProcInfo) :-
> + RevGoalIM0 = goal_and_init_instmap(RevGoal0, RevGoalInitInstMap),
Minor quibble: the "Rev" prefix on RevGoalIM0/RevGoal0/RevGoalInitInstMap
doesn't really make sense as it's not somehow "reversed".
> + lco_transform_variant_goal(ModuleInfo, Transforms0, VariantMap, VarToAddr,
> + RevGoalInitInstMap, RevGoal0, RevGoal, HeadChanged, !ProcInfo),
> + RevGoal = hlds_goal(RevGoalExpr, RevGoalInfo),
> + (
> + Transforms0 = groundings_only,
> + Transforms1 = groundings_only
> + ;
> + Transforms0 = groundings_and_lco_calls(NeededLaterVars0),
> + % XXX The value of Transforms starts out as groundings_and_lco_calls,
> + % and this is the only logical place where it could transition to
> + % groundings_only. We could e.g. switch to groundings_only
> + % when we encounter nonrecursive calls in our backward traversal.
> + %
> + % However, if we did that, then lco_transform_variant_plain_call's
> + % handling of groundings_and_lco_calls would not get tested
> + % when compiling tests/valid/bug539.m. Also, not switching
> + % to groundings_only here should yield very slightly faster code,
> + % as explained in the comment on the variant_transforms type.
> + get_input_vars_needed_by_goal(RevGoalInitInstMap, RevGoalInfo,
> + NeededVars),
> + set_of_var.union(NeededVars, NeededLaterVars0, NeededLaterVars1),
> + Transforms1 = groundings_and_lco_calls(NeededLaterVars1)
> + ),
> + lco_transform_variant_rev_conj(ModuleInfo, Transforms1, VariantMap,
> + VarToAddr, RevGoalIMs0, !Conjuncts, TailChanged, !ProcInfo),
> Changed = bool.or(HeadChanged, TailChanged),
> - ( if Goal = hlds_goal(conj(plain_conj, SubConj), _) then
> - Conj = SubConj ++ Goals
> + ( if RevGoalExpr = conj(plain_conj, RevGoalSubConjuncts) then
> + !:Conjuncts = !.Conjuncts ++ cord.from_list(RevGoalSubConjuncts)
> else
> - Conj = [Goal | Goals]
> + cord.snoc(RevGoal, !Conjuncts)
> ).
Ok.
> +:- pred get_input_vars_needed_by_goal(instmap::in, hlds_goal_info::in,
> + set_of_progvar::out) is det.
> +get_input_vars_needed_by_goal(InstMap0, GoalInfo, NeededVars) :-
> + % A goal may need the value of a variable from preceding code if
> + % - the variable already has an inst before the goal, and
> + % - the goal actually refers to the variable.
> + instmap_vars(InstMap0, InstMapVars),
> + NonLocals = goal_info_get_nonlocals(GoalInfo),
> + NeededVars = set_of_var.intersect(InstMapVars, NonLocals).
Do you need to restrict InstMapVars to non-free variables?
> @@ -1758,17 +1834,26 @@ lco_transform_variant_plain_call(ModuleInfo, VariantMap, VarToAddr, InstMap0,
> proc_info_get_argmodes(CalleeProcInfo, CalleeArgModes),
> ( if
> multi_map.search(VariantMap, CallPredProcId, ExistingVariants),
> - classify_proc_call_args(ModuleInfo, VarTypes, Args, CalleeArgModes,
> - _InArgs, OutArgs, _UnusedArgs),
> - grounding_to_variant_args(GroundingVarToAddr, 1, OutArgs, Subst,
> - VariantArgs),
> - match_existing_variant(ExistingVariants, VariantArgs,
> - ExistingVariant)
> + classify_proc_call_args(ModuleInfo, VarTypes,
> + ArgVars, CalleeArgModes,
> + _InArgVars, OutArgVars, _UnusedArgVars),
> + grounding_to_variant_args(GroundingVarToAddr, 1, OutArgVars, Subst,
> + VariantArgVars),
> + match_existing_variant(ExistingVariants, VariantArgVars,
> + ExistingVariant),
> +
> + % The need for the test done by the rest of this condition
> + % is explained by the comment on the variant_transforms type.
Or:
Do not transform a call if the transform would replace an
output variable needed by later code with a different variable.
> + Transforms = groundings_and_lco_calls(NeededVarsSet),
> + set_of_var.list_to_set(OutArgVars, OutArgVarsSet),
> + set_of_var.intersect(NeededVarsSet, OutArgVarsSet,
> + NeededOutArgVarsSet),
> + set_of_var.is_empty(NeededOutArgVarsSet)
You only need to intersect with VariantArgVars instead of OutArgVars,
right?
The rest looks fine. Thanks for looking at it.
Peter
More information about the reviews
mailing list