mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-05-01 01:04:43 +00:00
437 lines
20 KiB
Mathematica
437 lines
20 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2014-2025 The Mercury team.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: simplify_goal_unify.m.
|
|
%
|
|
% This module handles simplification of unifications.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module check_hlds.simplify.simplify_goal_unify.
|
|
:- interface.
|
|
|
|
:- import_module check_hlds.simplify.common.
|
|
:- import_module check_hlds.simplify.simplify_info.
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.instmap.
|
|
|
|
:- pred simplify_goal_unify(
|
|
hlds_goal_expr::in(goal_expr_unify), hlds_goal_expr::out,
|
|
hlds_goal_info::in, hlds_goal_info::out,
|
|
simplify_nested_context::in, instmap::in,
|
|
common_info::in, common_info::out,
|
|
simplify_info::in, simplify_info::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds.polymorphism_type_info.
|
|
:- import_module check_hlds.proc_requests.
|
|
:- import_module check_hlds.simplify.simplify_goal.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_markers.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.hlds_rtti.
|
|
:- import_module hlds.make_goal.
|
|
:- import_module hlds.pred_table.
|
|
:- import_module hlds.special_pred.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.builtin_modules.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_type.
|
|
:- import_module parse_tree.prog_type_test.
|
|
:- import_module parse_tree.set_of_var.
|
|
:- import_module parse_tree.var_table.
|
|
|
|
:- import_module bool.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module require.
|
|
:- import_module term_context.
|
|
:- import_module uint.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type cons_id_match
|
|
---> cons_id_must_match
|
|
; cons_id_cannot_match.
|
|
|
|
simplify_goal_unify(GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
NestedContext0, InstMap0, !Common, !Info) :-
|
|
GoalExpr0 = unify(LHSVar0, RHS0, UnifyMode, Unification0, UnifyContext),
|
|
(
|
|
RHS0 = rhs_lambda_goal(Purity, Groundness, PredOrFunc, NonLocals,
|
|
VarsModes, LambdaDeclaredDetism, LambdaGoal0),
|
|
determinism_to_code_model(LambdaDeclaredDetism, LambdaCodeModel),
|
|
(
|
|
( LambdaCodeModel = model_det
|
|
; LambdaCodeModel = model_semi
|
|
),
|
|
LambdaProcIsModelNon = no
|
|
;
|
|
LambdaCodeModel = model_non,
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
LambdaProcIsModelNon = yes(imp_lambda(Context))
|
|
),
|
|
NestedContext0 = simplify_nested_context(InsideDuplForSwitch,
|
|
_ProcModelNon, NumEnclosingBarriers, SwitchArmContexts),
|
|
( if goal_info_has_feature(GoalInfo0, feature_lambda_from_try) then
|
|
LambdaNumEnclosingBarriers = NumEnclosingBarriers
|
|
else
|
|
LambdaNumEnclosingBarriers = NumEnclosingBarriers + 1u
|
|
),
|
|
LambdaNestedContext = simplify_nested_context(InsideDuplForSwitch,
|
|
LambdaProcIsModelNon, LambdaNumEnclosingBarriers,
|
|
SwitchArmContexts),
|
|
|
|
simplify_info_get_module_info(!.Info, ModuleInfo),
|
|
instmap.pre_lambda_update(ModuleInfo, VarsModes,
|
|
InstMap0, LambdaInstMap0),
|
|
|
|
% Don't attempt to pass structs into lambda_goals, since that
|
|
% could change the curried non-locals of the lambda_goal, and
|
|
% that would be difficult to fix up.
|
|
simplify_info_get_simplify_tasks(!.Info, SimplifyTasks),
|
|
LambdaCommon0 = common_info_init(SimplifyTasks),
|
|
|
|
% Don't attempt to pass structs out of lambda_goals.
|
|
simplify_goal(LambdaGoal0, LambdaGoal, LambdaNestedContext,
|
|
LambdaInstMap0, LambdaCommon0, _, !Info),
|
|
|
|
RHS = rhs_lambda_goal(Purity, Groundness, PredOrFunc, NonLocals,
|
|
VarsModes, LambdaDeclaredDetism, LambdaGoal),
|
|
GoalExpr = unify(LHSVar0, RHS, UnifyMode, Unification0, UnifyContext),
|
|
GoalInfo = GoalInfo0
|
|
;
|
|
RHS0 = rhs_var(RHSVar0),
|
|
( if
|
|
% A unification of the form X = X can be safely optimised away.
|
|
RHSVar0 = LHSVar0
|
|
then
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
hlds_goal(GoalExpr, GoalInfo) = true_goal_with_context(Context)
|
|
else if
|
|
Unification0 = complicated_unify(ComplMode, CanFail, TypeInfoVars)
|
|
then
|
|
process_complicated_unify(LHSVar0, RHSVar0, ComplMode, CanFail,
|
|
TypeInfoVars, UnifyContext, GoalInfo0, GoalExpr1,
|
|
NestedContext0, InstMap0, !Common, !Info),
|
|
GoalExpr1 = hlds_goal(GoalExpr, GoalInfo)
|
|
else
|
|
common_optimise_unification(RHS0, UnifyMode, Unification0,
|
|
UnifyContext, GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
!Common, !Info)
|
|
)
|
|
;
|
|
RHS0 = rhs_functor(ConsId, IsExistConstr, RHSArgVars),
|
|
% If we know the function symbol(s) that LHSVar0 can be bound to,
|
|
% then compare them to ConsId.
|
|
instmap_lookup_var(InstMap0, LHSVar0, LHSVarInst0),
|
|
( if
|
|
LHSVarInst0 = bound(_Uniq, _InstResults0, LHSVarBoundFunctors),
|
|
( if
|
|
LHSVarBoundFunctors = [bound_functor(ConsId, [])],
|
|
RHSArgVars = []
|
|
then
|
|
% The unification must succeed; replace it with true.
|
|
%
|
|
% NOTE It would be nice to also handle the case where
|
|
% RHSArgVars is not the empty list, but
|
|
%
|
|
% - testing for that would require looking up the insts
|
|
% of all the variables in RHSArgVars, and comparing them
|
|
% with the argument insts of LHSVarBoundFunctors, and
|
|
%
|
|
% - it is very likely that (a) either some of those insts
|
|
% would not give us the exact shapes of the terms involved,
|
|
% (b) or that some of those shapes would turn out to be
|
|
% different between the LHS and the RHS.
|
|
%
|
|
% This would mean that these extra tests would probably
|
|
% succeed only quite rarely, meaning that any performance wins
|
|
% from optimizing the rare successes would not pay back
|
|
% the always-paid cost in compilation time.
|
|
ConsIdMatch = cons_id_must_match
|
|
else if
|
|
no_cons_id_can_match(ConsId, RHSArgVars, LHSVarBoundFunctors)
|
|
then
|
|
% The unification cannot succeed; replace it with fail.
|
|
ConsIdMatch = cons_id_cannot_match
|
|
else
|
|
fail
|
|
),
|
|
IsExistConstr = is_not_exist_constr,
|
|
RHSArgVars = []
|
|
then
|
|
( if simplify_do_warn_dodgy_simple_code(!.Info) then
|
|
% Replacing GoalExpr0/GoalInfo0 with TrueOrFailGoal
|
|
% can replace non-simple code with simple code, which
|
|
% in turn can lead to warnings about dodgy simple code
|
|
% that the user did not write. We therefore do not replace
|
|
% GoalExpr0/GoalInfo0 with TrueOrFailGoal *yet*. Instead,
|
|
% we set a flag that asks the top level of the simplification
|
|
% pass to rerun simplification, this time with warnings
|
|
% about dodgy simple code disabled. It is this repeated pass
|
|
% that will replace GoalExpr0/GoalInfo0 with TrueOrFailGoal.
|
|
%
|
|
% Note that one place in the compiler where this delay
|
|
% is needed is the get_target_host_min_word_size predicate
|
|
% in switch_util.m, which
|
|
%
|
|
% - calls int.bits_per_int(HostWordBits), which
|
|
% simplify_goal_call.m and const_prop.m effectively replace
|
|
% with either HostWordBits = 64 or HostWordBits = 32,
|
|
%
|
|
% - and then has an if-then-else chain whose conditions
|
|
% test whether HostWordBits is equal to 64 or to 32.
|
|
%
|
|
% Regardless of the platform word size, this results in
|
|
% replacing one condition with true and the other with fail,
|
|
% resulting in both conditions triggering the warning for
|
|
% simple dodgy code.
|
|
%
|
|
% NOTE Applying this technique of "don't replace, but ask
|
|
% for another simplify pass that does replace" for statically
|
|
% evaluating library calls turns out not to make any
|
|
% difference, as the compiler already does the right thing
|
|
% (meaning it replaces non-simple with simple code without
|
|
% generating a spurious warning) without that change.
|
|
simplify_info_set_rerun_simplify_no_warn_simple(!Info),
|
|
common_optimise_unification(RHS0, UnifyMode, Unification0,
|
|
UnifyContext, GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
!Common, !Info)
|
|
else
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
(
|
|
ConsIdMatch = cons_id_must_match,
|
|
TrueOrFailGoal = true_goal_with_context(Context)
|
|
;
|
|
ConsIdMatch = cons_id_cannot_match,
|
|
TrueOrFailGoal = fail_goal_with_context(Context)
|
|
),
|
|
TrueOrFailGoal = hlds_goal(GoalExpr, GoalInfo),
|
|
simplify_info_set_rerun_quant_instmap_delta(!Info),
|
|
simplify_info_set_rerun_det(!Info)
|
|
)
|
|
else
|
|
common_optimise_unification(RHS0, UnifyMode, Unification0,
|
|
UnifyContext, GoalExpr0, GoalExpr, GoalInfo0, GoalInfo,
|
|
!Common, !Info)
|
|
)
|
|
).
|
|
|
|
:- pred process_complicated_unify(prog_var::in, prog_var::in, unify_mode::in,
|
|
can_fail::in, list(prog_var)::in, unify_context::in, hlds_goal_info::in,
|
|
hlds_goal::out, simplify_nested_context::in, instmap::in,
|
|
common_info::in, common_info::out,
|
|
simplify_info::in, simplify_info::out) is det.
|
|
|
|
process_complicated_unify(XVar, YVar, UnifyMode, CanFail, _OldTypeInfoVars,
|
|
UnifyContext, GoalInfo0, Goal, NestedContext0, InstMap0,
|
|
!Common, !Info) :-
|
|
simplify_info_get_module_info(!.Info, ModuleInfo),
|
|
simplify_info_get_var_table(!.Info, VarTable),
|
|
lookup_var_type(VarTable, XVar, Type),
|
|
( if Type = type_variable(TypeVar, Kind) then
|
|
% Convert polymorphic unifications into calls to `unify/2',
|
|
% the general unification predicate, passing the appropriate type_info:
|
|
% unify(TypeInfoVar, X, Y)
|
|
% where TypeInfoVar is the type_info variable associated with
|
|
% the type of the variables that are being unified.
|
|
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
get_type_info_locn(TypeVar, Kind, Context, TypeInfoVar, ExtraGoals,
|
|
!Info),
|
|
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, !.Info,
|
|
UnifyContext, GoalInfo0, Call)
|
|
else if type_is_higher_order(Type) then
|
|
% Convert higher-order unifications into calls to
|
|
% builtin_unify_pred (which calls error/1).
|
|
Context = goal_info_get_context(GoalInfo0),
|
|
generate_plain_call(ModuleInfo, pf_predicate,
|
|
mercury_private_builtin_module, "builtin_unify_pred",
|
|
[], [XVar, YVar], instmap_delta_bind_no_var, mode_no(0),
|
|
detism_semi, purity_pure, [], Context, hlds_goal(Call0, _)),
|
|
simplify_goal_expr(Call0, Call1, GoalInfo0, GoalInfo,
|
|
NestedContext0, InstMap0, !Common, !Info),
|
|
Call = hlds_goal(Call1, GoalInfo),
|
|
ExtraGoals = []
|
|
else
|
|
type_to_ctor_and_args_det(Type, TypeCtor, TypeArgs),
|
|
determinism_components(Detism, CanFail, at_most_one),
|
|
lookup_mode_num(ModuleInfo, TypeCtor, UnifyMode, Detism, ProcId),
|
|
( if
|
|
hlds_pred.in_in_unification_proc_id(ProcId),
|
|
% For most imported types, we only generate unification
|
|
% predicate declarations if they are needed for complicated
|
|
% unifications other than proc_id 0. higher_order.m will
|
|
% specialize these cases if possible.
|
|
special_pred_is_generated_lazily(ModuleInfo, TypeCtor)
|
|
then
|
|
make_type_info_vars([Type], TypeInfoVars, ExtraGoals, !Info),
|
|
( if TypeInfoVars = [TypeInfoVarPrime] then
|
|
TypeInfoVar = TypeInfoVarPrime
|
|
else
|
|
unexpected($pred, "more than one typeinfo for one type var")
|
|
),
|
|
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, !.Info,
|
|
UnifyContext, GoalInfo0, Call)
|
|
else
|
|
% Convert other complicated unifications into calls to
|
|
% specific unification predicates, inserting extra typeinfo
|
|
% arguments if necessary.
|
|
make_type_info_vars(TypeArgs, TypeInfoVars, ExtraGoals, !Info),
|
|
call_specific_unify(TypeCtor, TypeInfoVars, XVar, YVar, ProcId,
|
|
ModuleInfo, UnifyContext, GoalInfo0, Call0, CallGoalInfo0),
|
|
simplify_goal_expr(Call0, Call1, CallGoalInfo0, CallGoalInfo1,
|
|
NestedContext0, InstMap0, !Common, !Info),
|
|
Call = hlds_goal(Call1, CallGoalInfo1)
|
|
)
|
|
),
|
|
Conjuncts = ExtraGoals ++ [Call],
|
|
conj_list_to_goal(Conjuncts, GoalInfo0, Goal).
|
|
|
|
:- pred call_generic_unify(prog_var::in, prog_var::in, prog_var::in,
|
|
module_info::in, simplify_info::in, unify_context::in,
|
|
hlds_goal_info::in, hlds_goal::out) is det.
|
|
|
|
call_generic_unify(TypeInfoVar, XVar, YVar, ModuleInfo, _, _, GoalInfo,
|
|
Call) :-
|
|
Context = goal_info_get_context(GoalInfo),
|
|
generate_plain_call(ModuleInfo, pf_predicate,
|
|
mercury_public_builtin_module, "unify",
|
|
[TypeInfoVar], [XVar, YVar], instmap_delta_bind_no_var, mode_no(0),
|
|
detism_semi, purity_pure, [], Context, Call).
|
|
|
|
:- pred call_specific_unify(type_ctor::in, list(prog_var)::in,
|
|
prog_var::in, prog_var::in, proc_id::in,
|
|
module_info::in, unify_context::in, hlds_goal_info::in,
|
|
hlds_goal_expr::out, hlds_goal_info::out) is det.
|
|
|
|
call_specific_unify(TypeCtor, TypeInfoVars, XVar, YVar, ProcId, ModuleInfo,
|
|
Context, GoalInfo0, CallExpr, CallGoalInfo) :-
|
|
% Create the new call goal.
|
|
ArgVars = TypeInfoVars ++ [XVar, YVar],
|
|
module_info_get_special_pred_maps(ModuleInfo, SpecialPredMaps),
|
|
UnifyMap = SpecialPredMaps ^ spm_unify_map,
|
|
map.lookup(UnifyMap, TypeCtor, PredId),
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
ModuleName = pred_info_module(PredInfo),
|
|
PredName = pred_info_name(PredInfo),
|
|
SymName = qualified(ModuleName, PredName),
|
|
CallContext = call_unify_context(XVar, rhs_var(YVar), Context),
|
|
CallExpr = plain_call(PredId, ProcId, ArgVars, not_builtin,
|
|
yes(CallContext), SymName),
|
|
|
|
% Add the extra type_info vars to the nonlocals for the call.
|
|
NonLocals0 = goal_info_get_nonlocals(GoalInfo0),
|
|
set_of_var.insert_list(TypeInfoVars, NonLocals0, NonLocals),
|
|
goal_info_set_nonlocals(NonLocals, GoalInfo0, CallGoalInfo).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred no_cons_id_can_match(cons_id::in, list(prog_var)::in,
|
|
list(bound_functor)::in) is semidet.
|
|
|
|
no_cons_id_can_match(_ConsId, _ArgVars, []).
|
|
no_cons_id_can_match(ConsId, ArgVars, [BoundFunctor | BoundFunctors]) :-
|
|
BoundFunctor = bound_functor(BoundConsId, BoundArgInsts),
|
|
( if
|
|
ConsId = BoundConsId,
|
|
list.same_length(ArgVars, BoundArgInsts)
|
|
then
|
|
% BoundFunctor can match the right hand side.
|
|
fail
|
|
else
|
|
no_cons_id_can_match(ConsId, ArgVars, BoundFunctors)
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred make_type_info_vars(list(mer_type)::in, list(prog_var)::out,
|
|
list(hlds_goal)::out, simplify_info::in, simplify_info::out) is det.
|
|
|
|
make_type_info_vars(Types, TypeInfoVars, TypeInfoGoals, !Info) :-
|
|
% Extract the information from simplify_info.
|
|
simplify_info_get_var_table(!.Info, VarTable0),
|
|
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
|
|
simplify_info_get_module_info(!.Info, ModuleInfo0),
|
|
simplify_info_get_pred_proc_id(!.Info, PredProcId),
|
|
|
|
some [!PredInfo, !ProcInfo] (
|
|
% The varset, var_table and rtti_varmaps get updated by the call to
|
|
% polymorphism_make_type_info_vars_raw_store below, which will get
|
|
% this information from the pred_info and proc_info.
|
|
module_info_pred_proc_info(ModuleInfo0, PredProcId,
|
|
!:PredInfo, !:ProcInfo),
|
|
proc_info_set_var_table(VarTable0, !ProcInfo),
|
|
proc_info_set_rtti_varmaps(RttiVarMaps0, !ProcInfo),
|
|
|
|
% Generate the code that creates the type_infos.
|
|
polymorphism_make_type_info_vars_mi(Types, dummy_context,
|
|
TypeInfoVars, TypeInfoGoals, ModuleInfo0, ModuleInfo1,
|
|
!PredInfo, !ProcInfo),
|
|
|
|
proc_info_get_var_table(!.ProcInfo, VarTable),
|
|
proc_info_get_rtti_varmaps(!.ProcInfo, RttiVarMaps),
|
|
simplify_info_set_var_table(VarTable, !Info),
|
|
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info),
|
|
|
|
% Put the new proc_info and pred_info back in the module_info
|
|
% and put the new module_info back in the simplify_info.
|
|
module_info_set_pred_proc_info(PredProcId, !.PredInfo, !.ProcInfo,
|
|
ModuleInfo1, ModuleInfo),
|
|
simplify_info_set_module_info(ModuleInfo, !Info)
|
|
).
|
|
|
|
:- pred get_type_info_locn(tvar::in, kind::in, prog_context::in, prog_var::out,
|
|
list(hlds_goal)::out, simplify_info::in, simplify_info::out) is det.
|
|
|
|
get_type_info_locn(TypeVar, Kind, Context, TypeInfoVar, Goals, !Info) :-
|
|
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps),
|
|
rtti_lookup_type_info_locn(RttiVarMaps, TypeVar, TypeInfoLocn),
|
|
(
|
|
% If the typeinfo is available in a variable, just use it.
|
|
TypeInfoLocn = type_info(TypeInfoVar),
|
|
Goals = []
|
|
;
|
|
% If the typeinfo is in a typeclass_info then we need to extract it.
|
|
TypeInfoLocn = typeclass_info(TypeClassInfoVar, Index),
|
|
extract_type_info(TypeVar, Kind, TypeClassInfoVar, Index, Context,
|
|
Goals, TypeInfoVar, !Info)
|
|
).
|
|
|
|
:- pred extract_type_info(tvar::in, kind::in, prog_var::in, int::in,
|
|
prog_context::in, list(hlds_goal)::out, prog_var::out,
|
|
simplify_info::in, simplify_info::out) is det.
|
|
|
|
extract_type_info(TypeVar, Kind, TypeClassInfoVar, Index, Context,
|
|
Goals, TypeInfoVar, !Info) :-
|
|
simplify_info_get_module_info(!.Info, ModuleInfo),
|
|
simplify_info_get_var_table(!.Info, VarTable0),
|
|
simplify_info_get_rtti_varmaps(!.Info, RttiVarMaps0),
|
|
|
|
polymorphism_type_info.gen_extract_type_info(ModuleInfo, TypeVar, Kind,
|
|
TypeClassInfoVar, iov_int(Index), Context, Goals, TypeInfoVar,
|
|
VarTable0, VarTable, RttiVarMaps0, RttiVarMaps),
|
|
|
|
simplify_info_set_var_table(VarTable, !Info),
|
|
simplify_info_set_rtti_varmaps(RttiVarMaps, !Info).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module check_hlds.simplify.simplify_goal_unify.
|
|
%---------------------------------------------------------------------------%
|