[m-rev.] for review: merge successive switches on the same variable
Peter Wang
novalazy at gmail.com
Thu Aug 17 17:18:00 AEST 2023
On Wed, 16 Aug 2023 03:30:58 +1000 "Zoltan Somogyi" <zoltan.somogyi at runbox.com> wrote:
> Merge consecutive switches on the same variable.
>
> compiler/simplify_goal_conj.m:
> When a switch on a variable is followed by a switch on the *same* variable,
> merge the two switches together, if doing so looks like it can save
> the execution of some branches for at least some input values.
>
> This transform is the second one to merge the goal following a switch
> into the switch itself. Move the parts of the code involved that are
> common between the two transformation to its caller, to reduce runtime
> overhead.
>
> compiler/simplify_goal.m:
> Add infrastructure for printing the pre- and post-simplification versions
> of specific goals. This helped debug the new code in simplify_goal_conj.m.
>
> Fix an inadvertent unification between two module_infos.
> This was caused by code that
>
> - defined the variable ModuleInfo, and then
> - reuses the "ModuleInfo" variable name for a slightly different purpose,
> but did so in the condition of an if-then-else, where the semidet nature
> of the implicit unification between the new value intended to be assigned
> to ModuleInfo, and its old value, was expected.
>
> compiler/simplify_proc.m:
> Add infrastructure for printing versions of the procedure body
> before and after quantification, instmap delta recomputation, and
> determinism analysis have been rerun. This helped debug the new code
> in simplify_goal_conj.m.
>
> The rest of the changes concern the option that
>
> - previously called only for the existing transformation to merge a test
> unification following a switch into a switch
>
> - and which now also calls for the merging of two succesive switches
> on the same variable.
>
> compiler/options.m:
> The option was named test_after_switch; this diff updates its name to
> merge_code_after_switch.
> diff --git a/compiler/simplify_goal_conj.m b/compiler/simplify_goal_conj.m
> index fbe23a9f3..4107a05eb 100644
> --- a/compiler/simplify_goal_conj.m
> +++ b/compiler/simplify_goal_conj.m
> @@ -221,14 +226,80 @@ simplify_conj(!.PrevGoals, [HeadGoal0 | TailGoals0], Goals, ConjInfo,
> % adjustments of the instmap_deltas of such containing goals.
> simplify_info_set_rerun_quant_instmap_delta(!Info)
> else
> - try_to_opt_test_after_switch(!PrevGoals, HeadGoal1,
> - TailGoals0, TailGoals1, ConjInfo, !Info),
> + ( if
> + simplify_do_merge_code_after_switch(!.Info),
> + HeadGoal1 = hlds_goal(HeadGoalExpr1, HeadGoalInfo1),
> + HeadGoalExpr1 =
> + switch(_SwitchVar, _SwitchCanFail1, _Cases1),
> + TailGoals0 = [HeadTailGoal0 | TailTailGoals0],
> + HeadTailGoal0 =
> + hlds_goal(HeadTailGoalExpr0, HeadTailGoalInfo0),
> + ( HeadTailGoalExpr0 = unify(_, _, _, _, _)
> + ; HeadTailGoalExpr0 = switch(_, _, _)
> + )
> + then
> + (
> + HeadTailGoalExpr0 = unify(_, _, _, _, _),
> + try_to_merge_unify_after_switch(!.Info, ConjInfo,
> + HeadGoalExpr1, HeadGoalInfo1,
> + HeadTailGoalExpr0, HeadTailGoalInfo0,
> + TailTailGoals0, MergeResult)
> + ;
> + HeadTailGoalExpr0 = switch(_, _, _),
> + try_to_merge_switch_after_switch(!.Info,
> + HeadGoalExpr1, HeadGoalInfo1,
> + HeadTailGoalExpr0, HeadTailGoalInfo0,
> + MergeResult)
> + ),
> + (
> + MergeResult = merge_unsuccessful,
> + !:PrevGoals = cord.snoc(!.PrevGoals, HeadGoal1),
> + TailGoals1 = TailGoals0,
> + TailInstMap = InstMap1
> + ;
> + MergeResult = merge_successful_new_code_simplified(
> + HeadGoal2, !:Info),
> + !:PrevGoals = cord.snoc(!.PrevGoals, HeadGoal2),
> + % Let the recursive call to simplify_conj below
> + % process TailTailGoals0 instead of TailGoals,
instead of TailGoals0
> + % since the effect of HeadTailGoal0 has now been
> + % folded into HeadGoal2.
> + TailGoals1 = TailTailGoals0,
> + TailInstMap = InstMap1
> + ;
> + MergeResult = merge_successful_new_code_not_simplified(
> + HeadGoal2),
> + % HeadGoal2 replaces both HeadGoal1 and HeadTailGoal0
> + % in front of TailTailGoals0. We have processed
> + % the HeadGoal1 part of HeadGoal2, but not the
> + % HeadTailGoal0 part, so we must process HeadGoal2
> + % again. That means starting again, with the initial
> + % versions of the instmap, the common structure,
> + % and the simplify_info structure.
> + TailGoals1 = [HeadGoal2 | TailTailGoals0],
> + TailInstMap = InstMap0,
> + !:Common = Common0,
> + !:Info = Info0,
> + % We do know that the merge of the two switches
> + % requires the recomputation of nonlocals sets and
> + % of instmap deltas, and that sometimes the
> + % recomputed determinisms can be more precise as well.
> + simplify_info_set_rerun_quant_instmap_delta(!Info),
> + simplify_info_set_rerun_det(!Info)
> + )
> + else
> + !:PrevGoals = cord.snoc(!.PrevGoals, HeadGoal1),
> + TailGoals1 = TailGoals0,
> + TailInstMap = InstMap1
> + ),
> simplify_conj(!.PrevGoals, TailGoals1, Goals, ConjInfo,
> - NestedContext0, InstMap1, !Common, !Info)
> + NestedContext0, TailInstMap, !Common, !Info)
> )
> )
> ).
> +:- pred compute_goal_info_for_merged_goal(
> + hlds_goal_info::in, hlds_goal_info::in, hlds_goal_info::out) is det.
> +
> +compute_goal_info_for_merged_goal(FirstGoalInfo, SecondGoalInfo, !:GoalInfo) :-
> + FirstDetism = goal_info_get_determinism(FirstGoalInfo),
> + SecondDetism = goal_info_get_determinism(SecondGoalInfo),
> + FirstPurity = goal_info_get_purity(FirstGoalInfo),
> + SecondPurity = goal_info_get_purity(SecondGoalInfo),
> + FirstInstMapDelta = goal_info_get_instmap_delta(FirstGoalInfo),
> + SecondInstMapDelta = goal_info_get_instmap_delta(SecondGoalInfo),
> + FirstNonLocals = goal_info_get_nonlocals(FirstGoalInfo),
> + SecondNonLocals = goal_info_get_nonlocals(SecondGoalInfo),
> + FirstFeatures = goal_info_get_features(FirstGoalInfo),
> + SecondFeatures = goal_info_get_features(SecondGoalInfo),
> +
> + % When we are computing the goal_info for the merged switch as a whole,
> + % it is possible for Detism to be too conservative, though still correct.
> + %
> + % Consider a situation where
> + %
> + % - the first switch is semidet, having N det arms and one semidet arm;
> + % - the second switch is det, having N det arms and one erroneous arm.
> + %
> + % The conjunction of the two switches will be considered semidet.
> + % However, in the process of merging the two switches, we may discover
> + % that in the merged switch, the semidet arm of the first switch
> + % is alwways followed by the erroneous arm of the second switch.
always
> + % Since execution cannot reach the end of that combined arm,
> + % and since all the combined arms whose ends *can* be reached are det,
> + % the determinism of the merged switch is actually det.
But execution can fail before the end of the combined arm,
so the merged switch *should* be semidet?
> + %
> + % This over-conservatism should not affect the rest of the simplification
> + % pass, and code higher up in the call tree will ask determinism analysis
> + % to be rerun to provide accurate info for later passes.
> + det_conjunction_detism(FirstDetism, SecondDetism, Detism),
> + Purity = worst_purity(FirstPurity, SecondPurity),
> + instmap_delta_apply_instmap_delta(FirstInstMapDelta, SecondInstMapDelta,
> + test_size, InstMapDelta),
> + % The NonLocals we compute here may be an overestimate, since it is
> + % possible for a variable to occur ONLY in FirstGoal and in SecondGoal.
> + % The code of the simplification pass should not mind this, and code
> + % higher up in the call tree will ask for quantification to be rerun
> + % to provide accurate info for later passes.
> + set_of_var.union(FirstNonLocals, SecondNonLocals, NonLocals),
> + set.union(FirstFeatures, SecondFeatures, Features),
> + % It does not matter whether we take FirstGoalInfo or SecondGoalInfo
> + % as the basis for GoalInfo, because we explicitly set up all of
> + % the fields that the
The sentence is incomplete.
> + !:GoalInfo = FirstGoalInfo,
> + goal_info_set_determinism(Detism, !GoalInfo),
> + goal_info_set_purity(Purity, !GoalInfo),
> + goal_info_set_instmap_delta(InstMapDelta, !GoalInfo),
> + goal_info_set_nonlocals(NonLocals, !GoalInfo),
> + goal_info_set_features(Features, !GoalInfo).
The rest looks okay, I think.
I'd be interested if you have any measurements.
Peter
More information about the reviews
mailing list