Files
mercury/compiler/try_expand.m
Zoltan Somogyi d69ba1a1f0 Include the type_ctor in cons_ids for user-defined types.
Estimated hours taken: 32
Branches: main

Include the type_ctor in cons_ids for user-defined types. The intention is
two-fold:

- It prepares for a future in which we allow more than one function symbol to
  with the same name to be defined in a module.

- It makes the HLDS code more self-contained. In many places, processing
  construction and deconstruction unifications required knowing which type
  the cons_id belongs to, but until now, code couldn't know that unless it
  kept track of the type of the variable unified with the cons_id.

With this diff, user-defined cons_ids are represented as

	cons(SymName, Arity, TypeCtor)

The last field is filled in during post-typecheck. After that time, any module
qualification in the SymName (which may initially be partial) is redundant,
since it is also available in the TypeCtor.

In the future, we could make all those SymNames be just unqualified(_) at that
time. We could also replace the current maps in HLDS type definitions with
full cons_id keys with just name/arity keys (since the module qualifier is a
given for any given type definition), we could also support partially
qualified cons_ids in source code using a map from name/arity pairs to a list
of all the type_ctors that have function symbols with that name/arity, instead
of our current practice of inserting all possible partially module qualified
version of every cons_id into a single giant table, and we could do the same
thing with the field names table.

This diff also separates tuples out from user-defined types, since in many
respects they are different (they don't have a single type_ctor, for starters).
It also separates out character constants, since they were alreay treated
specially in most places, though not in some places where they *ought* to
have been treated specially. Take the opportunity to give some other cons_ids
better names.

compiler/prog_data.m:
	Make the change described above, and document it.

	Put the implementations of the predicates declared in each part
	of this module next to the declarations, instead of keeping all the
	code until the very end (where it was usually far from their
	declarations).

	Remove three predicates with identical definitions from inst_match.m,
	inst_util.m and mode_constraints.m, and put the common definition
	in prog_data.m.

library/term_io.m:
	Add a new predicate that is basically a reversible version of
	the existing function espaced_char, since the definition of char_consts
	needs reversibilty.

compiler/post_typecheck.m:
	For functors of user-defined types, record their type_ctor. For tuples
	and char constants, record them as such.

compiler/builtin_lib_types.m:
compiler/parse_tree.m:
compiler/notes/compiler_design.html:
	New module to centralize knowledge about builtin types, specially
	handled library types, and their function symbols. Previously,
	the stuff now in this module used to be in several different places,
	including prog_type.m and stm_expand.m, and some of it was duplicated.

mdbcomp/prim_data.m:
	Add some predicates now needed by builtin_lib_types.m.

compiler/builtin_ops.m:
	Factor out some duplicated code.

compiler/add_type.m:
	Include the relevant type_ctors in the cons_ids generated in type
	definitions.

compiler/hlds_data.m:
	Document an existing type better.

	Rename a cons_tag in sync with its corresponding cons_id.

	Put some declarations into logical order.

compiler/hlds_out.m:
	Rename a misleadingly-named predicate.

compiler/prog_ctgc.m:
compiler/term_constr_build.m:
	Add XXXs for questionable existing code.

compiler/add_clause.m:
compiler/add_heap_ops.m:
compiler/add_pragma.m:
compiler/add_pred.m:
compiler/add_trail_ops.m:
compiler/assertion.m:
compiler/bytecode_gen.m:
compiler/closure_analysis.m:
compiler/code_info.m:
compiler/complexity.m:
compiler/ctgc_selector.m:
compiler/dead_proc_elim.m:
compiler/deep_profiling.m:
compiler/delay_partial_inst.m:
compiler/dependency_graph.m:
compiler/det_analysis.m:
compiler/det_report.m:
compiler/distance_granularity.m:
compiler/erl_rtti.m:
compiler/erl_unify_gen.m:
compiler/export.m:
compiler/field_access.m:
compiler/foreign.m:
compiler/format_call.m:
compiler/hhf.m:
compiler/higher_order.m:
compiler/hlds_code_util.m:
compiler/hlds_desc.m:
compiler/hlds_goal.m:
compiler/implementation_defined_literals.m:
compiler/inst_check.m:
compiler/inst_graph.m:
compiler/inst_match.m:
compiler/inst_util.m:
compiler/instmap.m:
compiler/intermod.m:
compiler/interval.m:
compiler/lambda.m:
compiler/lco.m:
compiler/make_tags.m:
compiler/mercury_compile.m:
compiler/mercury_to_mercury.m:
compiler/middle_rec.m:
compiler/ml_closure_gen.m:
compiler/ml_code_gen.m:
compiler/ml_code_util.m:
compiler/ml_switch_gen.m:
compiler/ml_type_gen.m:
compiler/ml_unify_gen.m:
compiler/ml_util.m:
compiler/mlds_to_c.m:
compiler/mlds_to_java.m:
compiler/mode_constraints.m:
compiler/mode_errors.m:
compiler/mode_ordering.m:
compiler/mode_util.m:
compiler/modecheck_unify.m:
compiler/modes.m:
compiler/module_qual.m:
compiler/polymorphism.m:
compiler/prog_ctgc.m:
compiler/prog_event.m:
compiler/prog_io_util.m:
compiler/prog_mode.m:
compiler/prog_mutable.m:
compiler/prog_out.m:
compiler/prog_type.m:
compiler/prog_util.m:
compiler/purity.m:
compiler/qual_info.m:
compiler/rbmm.add_rbmm_goal_infos.m:
compiler/rbmm.execution_path.m:
compiler/rbmm.points_to_analysis.m:
compiler/rbmm.region_transformation.m:
compiler/recompilation.usage.m:
compiler/rtti.m:
compiler/rtti_out.m:
compiler/rtti_to_mlds.m:
compiler/simplify.m:
compiler/simplify.m:
compiler/special_pred.m:
compiler/ssdebug.m:
compiler/stack_opt.m:
compiler/stm_expand.m:
compiler/stratify.m:
compiler/structure_reuse.direct.detect_garbagem:
compiler/superhomoegenous.m:
compiler/switch_detection.m:
compiler/switch_gen.m:
compiler/switch_util.m:
compiler/table_gen.m:
compiler/term_constr_build.m:
compiler/term_norm.m:
compiler/try_expand.m:
compiler/type_constraints.m:
compiler/type_ctor_info.m:
compiler/type_util.m:
compiler/typecheck.m:
compiler/typecheck_errors.m:
compiler/unify_gen.m:
compiler/unify_proc.m:
compiler/unify_modes.m:
compiler/untupling.m:
compiler/unused_imports.m:
compiler/xml_documentation.m:
	Minor changes, mostly to ignore the type_ctor in cons_ids in places
	where it is not needed, take the type_ctor from the cons_id in places
	where it is more convenient, conform to the new names of some cons_ids,
	conform to the changes in hlds_out.m, and/or add now-needed imports
	of builtin_lib_types.m.

	In some places, the handling previously applied to cons/2 (which
	included tuples and character constants as well as user-defined
	function symbols) is now applied only to user-defined function symbols
	or to user-defined function symbols and tuples, as appropriate,
	with character constants being handled more like the other kinds of
	constants.

	In inst_match.m, rename a whole bunch of predicates to avoid
	ambiguities.

	In prog_util.m, remove two predicates that did almost nothing yet were
	far too easy to misuse.
2009-06-11 07:00:38 +00:00

930 lines
35 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2009 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public Licence - see the file COPYING in the Mercury distribution.
%-----------------------------------------------------------------------------%
%
% File: try_expand.m
% Author: wangp.
%
% Try goals are implemented by replacing them with calls to predicates in the
% `exception' module. For example, goal such as:
%
% (try [] p(X, Y)
% then q(X, Y)
% else r
% catch ...
% )
%
% is expanded to:
%
% exception.try(
% (pred(OutputTuple::out) is semidet :-
% p(X, Y),
% OutputTuple = {X, Y}
% ), TryResult),
% (
% TryResult = succeeded({X, Y}),
% q(X, Y)
% ;
% TryResult = failed,
% r
% ;
% TryResult = exception(Excp),
% ...exception handling...
% )
%
% The transformation requires us to know which variables are bound in the
% higher order term that is passed to `try', as well as the determinism, so we
% can't transform them immediately when converting from the parse tree to HLDS
% representations. But try goals have very complex control flows and we don't
% really want to write and maintain mode, determinism, and other analyses to
% work on them directly either.
%
% Instead, we cheat a little and "pre-transform" try goals very early on (when
% converting from a parse tree to an HLDS) into something that resembles
% somewhat the final forms, e.g.
%
% magic_exception_result(TryResult), % out(cannot_fail)
% (
% TryResult = succeeded({}),
% ( p(X, Y) ->
% q(X, Y)
% ;
% r
% )
% ;
% TryResult = exception(Excp),
% ...exception handling...
% )
%
% We let the semantic checks work on these pre-transformed goals. Afterwards
% we pick out the various pieces and construct the proper, final goals.
%
%-----------------------------------------------------------------------------%
%
% PRE-TRANSFORMATION (implemented in add_clause.m)
%
% 1. try goal without I/O but with an else part
%
% magic_exception_result(TryResult), % out(cannot_fail)
% (
% TryResult = succeeded({}),
% ( <Goal> ->
% <Then>
% ;
% <Else>
% )
% ;
% TryResult = exception(Excp),
% <ExcpHandling>
% )
%
% As intended, variables bound in <Goal> are only in scope within <Then>,
% not <Else> nor <ExcpHandling>.
%
% 2. try goal without I/O and without an else part, or
% 3. try goal with I/O
%
% magic_exception_result(TryResult), % out(cannot_fail)
% (
% TryResult = succeeded({}),
% some [] <Goal>,
% some [] <Then>
% ;
% TryResult = exception(Excp),
% <ExcpHandling>
% )
%
% The `some' scopes there so that we can distinguish between <Goal> and
% <Then> later. (They act as barrier scopes, except we can't introduce
% barrier scopes then. We depend on the early analyses not to move things
% in and out of `some' scopes.)
%
%-----------------------------------------------------------------------------%
%
% POST-TRANSFORMATION (implemented in this module)
%
% 1. try goal without I/O, and can fail
%
% try((pred(OutputTuple::out) is semidet :-
% <Goal>,
% OutputTuple = { <BoundVars> }
% ), TryResult),
% (
% TryResult = succeeded(TmpTupleVar),
% inst_cast(TmpTupleVar, TupleVar),
% TupleVar = { <BoundVars> },
% <Then>
% ;
% TryResult = failed,
% <Else>
% ;
% TryResult = exception(Excp),
% <ExcpHandling>
% )
%
% 2. try goal without I/O, and cannot fail
%
% try((pred(OutputTuple::out) is det :-
% <Goal>,
% OutputTuple = { <BoundVars> }
% ), TryResult),
% (
% TryResult = succeeded(TmpTupleVar),
% inst_cast(TmpTupleVar, TupleVar),
% TupleVar = { <BoundVars> },
% <Then>
% ;
% TryResult = exception(Excp),
% <ExcpHandling>
% )
%
% 3. try goal with I/O
%
% try_io((pred(OutputTuple::out, !.IO::di, !:IO::uo) is det :-
% <Goal>,
% OutputTuple = { <BoundVars> }
% ), TryResult, !IO),
% (
% TryResult = succeeded(TmpTupleVar),
% inst_cast(TmpTupleVar, TupleVar),
% TupleVar = { <BoundVars> },
% <Then>
% ;
% TryResult = exception(Excp),
% <ExcpHandling>
% )
%
% We have to rename an io.state variable in ExcpHandling so that the sequence
% begins with the output I/O state of the `try_io' call.
%
% The inst casts preserve the known insts of the BoundVars, which were lost due
% to the calls to try*.
%
% The <ExcpHandling> parts can be passed through from the pre-transformation.
% If a `catch_any' is present the exception handling looks like this:
%
% ( exc_univ_to_type(Excp, <CatchPattern1>) ->
% <CatchGoal1>
% ; exc_univ_to_type(Excp, <CatchPattern2>) ->
% <CatchGoal2>
% ;
% CatchAnyVar = exc_univ_value(Excp),
% <CatchAnyGoal>
% )
%
% Otherwise, if `catch_any' is not present:
%
% ( exc_univ_to_type(Excp, <CatchPattern1>) ->
% <CatchGoal1>
% ; exc_univ_to_type(Excp, <CatchPattern2>) ->
% <CatchGoal2>
% ;
% rethrow(TryResult)
% )
%
%-----------------------------------------------------------------------------%
:- module check_hlds.try_expand.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module parse_tree.prog_data.
:- import_module io.
%-----------------------------------------------------------------------------%
:- pred expand_try_goals(module_info::in, module_info::out, io::di, io::uo)
is det.
% try_expand_may_introduce_calls(PredName, Arity):
%
% Succeed if the transformation may introduce calls to a predicate
% or function with the given name in the exception module.
%
:- pred try_expand_may_introduce_calls(string::in, arity::in) is semidet.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.det_analysis.
:- import_module check_hlds.det_report.
:- import_module check_hlds.inst_util.
:- import_module check_hlds.modes.
:- import_module check_hlds.polymorphism.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module hlds.instmap.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_table.
:- import_module hlds.quantification.
:- import_module libs.
:- import_module libs.compiler_util.
:- import_module mdbcomp.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.error_util.
:- import_module parse_tree.prog_mode.
:- import_module parse_tree.prog_type.
:- import_module bool.
:- import_module int.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module set.
:- import_module string.
:- import_module term.
%-----------------------------------------------------------------------------%
expand_try_goals(!ModuleInfo, !IO) :-
% The exception module is implicitly imported if any try goals were seen,
% so if the exception module is not imported then we know there are no try
% goals to be expanded.
module_info_get_imported_module_specifiers(!.ModuleInfo, ImportedModules),
( set.contains(ImportedModules, mercury_exception_module) ->
some [!Globals] (
module_info_get_globals(!.ModuleInfo, !:Globals),
disable_det_warnings(OptionsToRestore, !Globals),
module_info_set_globals(!.Globals, !ModuleInfo),
module_info_predids(PredIds, !ModuleInfo),
list.foldl2(expand_try_goals_in_pred, PredIds, !ModuleInfo, !IO),
module_info_get_globals(!.ModuleInfo, !:Globals),
restore_det_warnings(OptionsToRestore, !Globals),
module_info_set_globals(!.Globals, !ModuleInfo)
)
;
true
).
:- pred expand_try_goals_in_pred(pred_id::in,
module_info::in, module_info::out, io::di, io::uo) is det.
expand_try_goals_in_pred(PredId, !ModuleInfo, !IO) :-
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
ProcIds = pred_info_non_imported_procids(PredInfo),
list.foldl2(expand_try_goals_in_proc(PredId), ProcIds, !ModuleInfo, !IO).
:- pred expand_try_goals_in_proc(pred_id::in, proc_id::in,
module_info::in, module_info::out, io::di, io::uo) is det.
expand_try_goals_in_proc(PredId, ProcId, !ModuleInfo, !IO) :-
some [!PredInfo, !ProcInfo] (
module_info_pred_proc_info(!.ModuleInfo, PredId, ProcId,
!:PredInfo, !:ProcInfo),
proc_info_get_goal(!.ProcInfo, Goal0),
proc_info_get_initial_instmap(!.ProcInfo, !.ModuleInfo, InitInstmap),
Info0 = trys_info(!.ModuleInfo, !.PredInfo, !.ProcInfo, no),
expand_try_goals_in_goal(InitInstmap, Goal0, Goal, Info0, Info),
Info = trys_info(!:ModuleInfo, !:PredInfo, !:ProcInfo, Changed),
(
Changed = yes,
update_changed_proc(Goal, PredId, ProcId, !.PredInfo, !.ProcInfo,
!ModuleInfo, !IO),
module_info_clobber_dependency_info(!ModuleInfo)
;
Changed = no
)
).
:- pred update_changed_proc(hlds_goal::in, pred_id::in, proc_id::in,
pred_info::in, proc_info::in, module_info::in, module_info::out,
io::di, io::uo) is det.
update_changed_proc(Goal, PredId, ProcId, PredInfo, !.ProcInfo, !ModuleInfo,
!IO) :-
proc_info_set_goal(Goal, !ProcInfo),
requantify_proc(!ProcInfo),
module_info_set_pred_proc_info(PredId, ProcId, PredInfo, !.ProcInfo,
!ModuleInfo),
modecheck_proc(ProcId, PredId, !ModuleInfo, ErrorSpecs, _Changed, !IO),
module_info_get_globals(!.ModuleInfo, Globals),
write_error_specs(ErrorSpecs, Globals, 0, _NumWarnings, 0, NumErrors, !IO),
module_info_incr_num_errors(NumErrors, !ModuleInfo),
( NumErrors > 0 ->
% In some cases we may detect mode errors after expanding try goals
% which were missed before, so don't abort the compiler (but we'll stop
% compiling not long after this pass).
true
;
determinism_check_proc(ProcId, PredId, !ModuleInfo, DetSpecs),
(
DetSpecs = []
;
DetSpecs = [_ | _],
unexpected(this_file, "determinism check fails when repeated")
)
).
%-----------------------------------------------------------------------------%
:- type trys_info
---> trys_info(
ti_module_info :: module_info,
ti_pred_info :: pred_info,
ti_proc_info :: proc_info,
ti_changed :: bool
).
:- pred expand_try_goals_in_goal(instmap::in, hlds_goal::in, hlds_goal::out,
trys_info::in, trys_info::out) is det.
expand_try_goals_in_goal(Instmap, Goal0, Goal, !Info) :-
Goal0 = hlds_goal(GoalExpr0, GoalInfo0),
(
GoalExpr0 = unify(_, _, _, _, _),
Goal = Goal0
;
GoalExpr0 = conj(ConjType, Conjuncts0),
expand_try_goals_in_conj(Instmap, Conjuncts0, Conjuncts, !Info),
GoalExpr = conj(ConjType, Conjuncts),
Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = disj(Disjuncts0),
expand_try_goals_in_disj(Instmap, Disjuncts0, Disjuncts, !Info),
GoalExpr = disj(Disjuncts),
Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = negation(SubGoal0),
expand_try_goals_in_goal(Instmap, SubGoal0, SubGoal, !Info),
GoalExpr = negation(SubGoal),
Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = switch(Var, CanFail, Cases0),
expand_try_goals_in_cases(Instmap, Cases0, Cases, !Info),
GoalExpr = switch(Var, CanFail, Cases),
Goal = hlds_goal(GoalExpr, GoalInfo0)
;
GoalExpr0 = scope(Reason, InnerGoal0),
(
Reason = from_ground_term(_, from_ground_term_construct),
% There can be no try goals inside this scope.
Goal = Goal0
;
( Reason = exist_quant(_)
; Reason = promise_solutions(_, _)
; Reason = promise_purity(_)
; Reason = commit(_)
; Reason = barrier(_)
; Reason = from_ground_term(_, from_ground_term_deconstruct)
; Reason = from_ground_term(_, from_ground_term_other)
; Reason = trace_goal(_, _, _, _, _)
),
expand_try_goals_in_goal(Instmap, InnerGoal0, InnerGoal, !Info),
GoalExpr = scope(Reason, InnerGoal),
Goal = hlds_goal(GoalExpr, GoalInfo0)
)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
expand_try_goals_in_if_then_else(Instmap, Cond0, Cond, Then0, Then,
Else0, Else, !Info),
GoalExpr = if_then_else(Vars, Cond, Then, Else),
Goal = hlds_goal(GoalExpr, GoalInfo0)
;
( GoalExpr0 = generic_call(_, _, _, _)
; GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
),
Goal = Goal0
;
% This should be expanded out at this stage
GoalExpr0 = shorthand(ShortHand0),
(
ShortHand0 = try_goal(_, _, _),
expand_try_goal(Instmap, ShortHand0, Goal, !Info)
;
ShortHand0 = atomic_goal(AtomicGoalType, Outer, Inner,
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
expand_try_goals_in_goal(Instmap, MainGoal0, MainGoal, !Info),
expand_try_goals_in_disj(Instmap, OrElseGoals0, OrElseGoals,
!Info),
GoalExpr = atomic_goal(AtomicGoalType, Outer, Inner,
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners),
Goal = hlds_goal(shorthand(GoalExpr), GoalInfo0)
;
ShortHand0 = bi_implication(_, _),
unexpected(this_file, "expand_try_goals_in_goal: bi_implication")
)
).
:- pred expand_try_goals_in_conj(instmap::in,
list(hlds_goal)::in, list(hlds_goal)::out,
trys_info::in, trys_info::out) is det.
expand_try_goals_in_conj(_Instmap0, [], [], !Info).
expand_try_goals_in_conj(Instmap0, [Goal0 | Goals0], [Goal | Goals], !Info) :-
expand_try_goals_in_goal(Instmap0, Goal0, Goal, !Info),
Goal0 = hlds_goal(_, GoalInfo),
InstmapDelta = goal_info_get_instmap_delta(GoalInfo),
apply_instmap_delta(Instmap0, InstmapDelta, Instmap),
expand_try_goals_in_conj(Instmap, Goals0, Goals, !Info).
:- pred expand_try_goals_in_disj(instmap::in,
list(hlds_goal)::in, list(hlds_goal)::out,
trys_info::in, trys_info::out) is det.
expand_try_goals_in_disj(Instmap0, Goals0, Goals, !Info) :-
list.map_foldl(expand_try_goals_in_goal(Instmap0), Goals0, Goals, !Info).
:- pred expand_try_goals_in_cases(instmap::in, list(case)::in, list(case)::out,
trys_info::in, trys_info::out) is det.
expand_try_goals_in_cases(_Instmap0, [], [], !Info).
expand_try_goals_in_cases(Instmap0, [Case0 | Cases0], [Case | Cases], !Info) :-
Case0 = case(MainConsId, OtherConsIds, Goal0),
expand_try_goals_in_goal(Instmap0, Goal0, Goal, !Info),
expand_try_goals_in_cases(Instmap0, Cases0, Cases, !Info),
Case = case(MainConsId, OtherConsIds, Goal).
:- pred expand_try_goals_in_if_then_else(instmap::in,
hlds_goal::in, hlds_goal::out, hlds_goal::in,
hlds_goal::out, hlds_goal::in, hlds_goal::out,
trys_info::in, trys_info::out) is det.
expand_try_goals_in_if_then_else(Instmap0, Cond0, Cond, Then0, Then,
Else0, Else, !Info) :-
expand_try_goals_in_goal(Instmap0, Cond0, Cond, !Info),
Cond0 = hlds_goal(_, CondInfo),
CondInstmapDelta = goal_info_get_instmap_delta(CondInfo),
apply_instmap_delta(Instmap0, CondInstmapDelta, InstmapAfterCond),
expand_try_goals_in_goal(InstmapAfterCond, Then0, Then, !Info),
expand_try_goals_in_goal(Instmap0, Else0, Else, !Info).
%-----------------------------------------------------------------------------%
:- inst try_goal
---> try_goal(ground, ground, ground).
:- pred expand_try_goal(instmap::in, shorthand_goal_expr::in(try_goal),
hlds_goal::out, trys_info::in, trys_info::out) is det.
expand_try_goal(Instmap, TryGoal, FinalGoal, !Info) :-
TryGoal = try_goal(MaybeIO, ResultVar, IntermediateGoal),
extract_intermediate_goal_parts(!.Info ^ ti_module_info, ResultVar,
IntermediateGoal, Goal0, Then0, MaybeElse0, ExcpHandling0),
% Handle nested try goals.
expand_try_goals_in_goal(Instmap, Goal0, Goal1, !Info),
update_instmap(Goal0, Instmap, InstmapAfterGoal),
expand_try_goals_in_goal(InstmapAfterGoal, Then0, Then1, !Info),
(
MaybeElse0 = yes(Else0),
expand_try_goals_in_goal(Instmap, Else0, Else1, !Info),
MaybeElse1 = yes(Else1)
;
MaybeElse0 = no,
MaybeElse1 = no
),
expand_try_goals_in_goal(Instmap, ExcpHandling0, ExcpHandling1, !Info),
% Find the output variables. Note we use Goal0, not Goal1, as any nested
% tries would have been transformed will mess up the calculation.
bound_nonlocals_in_goal(!.Info ^ ti_module_info, Instmap, Goal0,
GoalOutputVarsSet0),
(
MaybeIO = yes(try_io_state_vars(_IOStateVarInitial, IOStateVarFinal)),
set.delete(GoalOutputVarsSet0, IOStateVarFinal, GoalOutputVarsSet)
;
MaybeIO = no,
GoalOutputVarsSet = GoalOutputVarsSet0
),
some [!ModuleInfo, !PredInfo, !ProcInfo, !VarTypes] (
!.Info = trys_info(!:ModuleInfo, !:PredInfo, !:ProcInfo, _),
expand_try_goal_2(MaybeIO, ResultVar, Goal1, Then1, MaybeElse1,
ExcpHandling1, InstmapAfterGoal, GoalOutputVarsSet, FinalGoal,
!PredInfo, !ProcInfo, !ModuleInfo),
!:Info = trys_info(!.ModuleInfo, !.PredInfo, !.ProcInfo, yes)
).
:- pred expand_try_goal_2(maybe(try_io_state_vars)::in, prog_var::in,
hlds_goal::in, hlds_goal::in, maybe(hlds_goal)::in, hlds_goal::in,
instmap::in, set(prog_var)::in, hlds_goal::out,
pred_info::in, pred_info::out, proc_info::in, proc_info::out,
module_info::in, module_info::out) is det.
expand_try_goal_2(MaybeIO, ResultVar, Goal1, Then1, MaybeElse1, ExcpHandling1,
Instmap, GoalOutputVarsSet, FinalGoal,
!PredInfo, !ProcInfo, !ModuleInfo) :-
some [!VarTypes] (
% Get the type of the output tuple.
proc_info_get_vartypes(!.ProcInfo, !:VarTypes),
GoalOutputVars = set.to_sorted_list(GoalOutputVarsSet),
map.apply_to_list(GoalOutputVars, !.VarTypes, GoalOutputVarTypes),
OutputTupleType = tuple_type(GoalOutputVarTypes, kind_star),
% Fix the type of the result of the try call, now that we know what it
% should be.
RealResultVarType = defined_type(
qualified(mercury_exception_module, "exception_result"),
[OutputTupleType], kind_star),
map.det_update(!.VarTypes, ResultVar, RealResultVarType, !:VarTypes),
proc_info_set_vartypes(!.VarTypes, !ProcInfo)
),
make_try_lambda(Goal1, GoalOutputVarsSet, OutputTupleType, MaybeIO,
LambdaVar, AssignLambdaVar, !ProcInfo),
GoalPurity = goal_get_purity(Goal1),
(
MaybeIO = yes(try_io_state_vars(GoalInitialIOVar, GoalFinalIOVar)),
% We need to rearrange I/O state variables a bit.
%
% Let Goal take the I/O state from GoalInitialIOVar to GoalFinalIOVar.
% The input to try_io will be GoalInitialIOVar.
% Let the output of try_io to be TryIOOutputVar.
%
% Due to the pre-transformation, ExcpHandling also takes the I/O state
% from GoalInitialIOVar to GoalFinalIOVar. We need to rename
% GoalInitialIOVar to TryIOOutputVar as the exception handling code
% follows the try_io call.
%
% We cannot let TryIOOutputVar be GoalFinalIOVar, as the latter may
% already appear somewhere in ExcpHandling. TryIOOutputVar must be a
% new variable.
%
% The Then part starts the I/O state sequence from GoalFinalIOVar, so
% we need to unify "GoalFinalIOVar = TryIOOutputVar". We don't use
% renaming in this case because GoalFinalIOVar might not even occur in
% the Then part; then renaming would lead to a mode error.
proc_info_create_var_from_type(io_state_type, yes("TryIOOutput"),
TryIOOutputVar, !ProcInfo),
make_try_call("try_io", LambdaVar, ResultVar,
[GoalInitialIOVar, TryIOOutputVar], OutputTupleType, GoalPurity,
CallTryGoal, !PredInfo, !ProcInfo, !ModuleInfo),
create_pure_atomic_complicated_unification(GoalFinalIOVar,
rhs_var(TryIOOutputVar), term.context_init,
umc_implicit("try_expand"), [], UnifyThenInitialIOVar),
conjoin_goals(UnifyThenInitialIOVar, Then1, Then),
RenamingExcp = map.from_assoc_list([GoalInitialIOVar - TryIOOutputVar]),
rename_some_vars_in_goal(RenamingExcp, ExcpHandling1, ExcpHandling)
;
MaybeIO = no,
make_try_call("try", LambdaVar, ResultVar, [], OutputTupleType,
GoalPurity, CallTryGoal, !PredInfo, !ProcInfo, !ModuleInfo),
Then = Then1,
ExcpHandling = ExcpHandling1
),
goal_info_init(GoalInfo),
% The `succeeded' case.
proc_info_create_var_from_type(OutputTupleType, yes("TmpOutputTuple"),
TmpTupleVar, !ProcInfo),
proc_info_create_var_from_type(OutputTupleType, yes("OutputTuple"),
TupleVar, !ProcInfo),
deconstruct_functor(ResultVar, exception_succeeded_functor, [TmpTupleVar],
DeconstructSucceeded),
instmap_lookup_vars(Instmap, GoalOutputVars, TupleArgInsts),
make_output_tuple_inst_cast(TmpTupleVar, TupleVar, TupleArgInsts,
CastOutputTuple),
deconstruct_tuple(TupleVar, GoalOutputVars, DeconstructOutputs),
conj_list_to_goal([DeconstructSucceeded, CastOutputTuple,
DeconstructOutputs, Then], GoalInfo, DeconstructsThen),
SucceededCase = case(exception_succeeded_functor, [], DeconstructsThen),
% The `exception' case.
ExceptionCase = case(exception_exception_functor, [], ExcpHandling),
% The `failed' case.
(
MaybeElse1 = yes(Else1),
FailedCase = case(exception_failed_functor, [], Else1),
MaybeFailedCase = [FailedCase]
;
MaybeElse1 = no,
MaybeFailedCase = []
),
Cases = [SucceededCase, ExceptionCase | MaybeFailedCase],
ResultSwitch = hlds_goal(switch(ResultVar, cannot_fail, Cases), GoalInfo),
conj_list_to_goal([AssignLambdaVar, CallTryGoal, ResultSwitch], GoalInfo,
FinalGoal).
% Pick out the parts of the original try goal from a pre-transformed goal.
%
:- pred extract_intermediate_goal_parts(module_info::in, prog_var::in,
hlds_goal::in, hlds_goal::out, hlds_goal::out, maybe(hlds_goal)::out,
hlds_goal::out) is det.
extract_intermediate_goal_parts(ModuleInfo, ResultVar, IntermediateGoal,
Goal, Then, MaybeElse, ExcpHandling) :-
(
extract_intermediate_goal_parts_2(ModuleInfo, ResultVar,
IntermediateGoal, GoalPrime, ThenPrime, MaybeElsePrime,
ExcpHandlingPrime)
->
Goal = GoalPrime,
Then = ThenPrime,
MaybeElse = MaybeElsePrime,
ExcpHandling = ExcpHandlingPrime
;
unexpected(this_file, "find_subparts: unexpected goal form")
).
:- pred extract_intermediate_goal_parts_2(module_info::in, prog_var::in,
hlds_goal::in, hlds_goal::out, hlds_goal::out, maybe(hlds_goal)::out,
hlds_goal::out) is semidet.
extract_intermediate_goal_parts_2(ModuleInfo, ResultVar, IntermediateGoal,
Goal, Then, MaybeElse, ExcpHandling) :-
IntermediateGoal = hlds_goal(conj(plain_conj, Conjuncts), _),
Conjuncts = [
hlds_goal(MagicCall, _),
hlds_goal(Switch, _)
],
MagicCall = plain_call(_, _, [ResultVar], _, _, _),
Switch = switch(ResultVar, cannot_fail, Cases),
lookup_case_goal(Cases, exception_succeeded_functor, SucceededGoal),
extract_from_succeeded_goal(ModuleInfo, SucceededGoal, Goal, Then,
MaybeElse),
lookup_case_goal(Cases, exception_exception_functor, ExcpHandling).
% There are two forms we could extract when TryResult has the
% functor exception.succeeded/1.
%
% TryResult = exception.succeeded(V),
% V = {},
% ( Goal ->
% Then
% ;
% Else
% ),
% Rest
%
% or:
%
% TryResult = exception.succeeded(V),
% V = {},
% some [] Goal,
% some [] Then,
% Rest
%
:- pred extract_from_succeeded_goal(module_info::in, hlds_goal::in,
hlds_goal::out, hlds_goal::out, maybe(hlds_goal)::out) is semidet.
extract_from_succeeded_goal(ModuleInfo, SucceededGoal, Goal, Then,
MaybeElse) :-
SucceededGoal = hlds_goal(conj(plain_conj, Conjuncts0), _),
Conjuncts0 = [DeconstructResult, TestNullTuple | Conjuncts1],
DeconstructResult = hlds_goal(unify(_ResultVar, _, _, _, _), _),
TestNullTuple = hlds_goal(unify(_, TestRHS, _, _, _), _),
TestRHS = rhs_functor(tuple_cons(0), no, []),
(
Conjuncts1 = [hlds_goal(IfThenElse, _) | Rest],
IfThenElse = if_then_else(_, GoalPrime, Then0, Else0)
->
Goal = GoalPrime,
% If Goal is erroneous the Then part may have been optimised away to
% `true'. However, we will be separating the Goal and Then parts in
% the final goal, so the knowledge that Goal won't succeed will be lost
% to the mode checker. In that case we replace the Then goal by a call
% to an `erroneous' procedure.
Goal = hlds_goal(_, GoalInfo),
GoalDetism = goal_info_get_determinism(GoalInfo),
determinism_components(GoalDetism, _, GoalMaxSoln),
(
GoalMaxSoln = at_most_zero,
make_unreachable_call(ModuleInfo, Then)
;
( GoalMaxSoln = at_most_one
; GoalMaxSoln = at_most_many_cc
; GoalMaxSoln = at_most_many
),
conjoin_goal_and_goal_list(Then0, Rest, Then)
),
conjoin_goal_and_goal_list(Else0, Rest, Else),
MaybeElse = yes(Else)
;
Conjuncts1 = [SomeGoal | AfterSomeGoal],
SomeGoal = hlds_goal(scope(exist_quant([]), Goal), _),
(
AfterSomeGoal = [SomeThen | Rest],
SomeThen = hlds_goal(scope(exist_quant([]), Then0), _)
->
conjoin_goal_and_goal_list(Then0, Rest, Then),
MaybeElse = no
;
% If "some [] Then" is missing then "some [] Goal" must be
% `erroneous'. Make the Then part into a call to an erroneous
% procedure.
Goal = hlds_goal(_, GoalInfo),
GoalDetism = goal_info_get_determinism(GoalInfo),
determinism_components(GoalDetism, _, GoalMaxSoln),
(
GoalMaxSoln = at_most_zero,
make_unreachable_call(ModuleInfo, Then),
MaybeElse = no
;
( GoalMaxSoln = at_most_one
; GoalMaxSoln = at_most_many_cc
; GoalMaxSoln = at_most_many
),
unexpected(this_file, "find_subparts_2: Goal not erroneous")
)
)
).
:- pred lookup_case_goal(list(case)::in, cons_id::in, hlds_goal::out) is det.
lookup_case_goal([], ConsId, _) :-
unexpected(this_file, "lookup_case_goal: couldn't find " ++ string(ConsId)).
lookup_case_goal([H | T], ConsId, Goal) :-
( H = case(ConsId, [], GoalPrime) ->
Goal = GoalPrime
;
lookup_case_goal(T, ConsId, Goal)
).
:- pred bound_nonlocals_in_goal(module_info::in, instmap::in, hlds_goal::in,
set(prog_var)::out) is det.
bound_nonlocals_in_goal(ModuleInfo, InstMap, Goal, BoundNonLocals) :-
Goal = hlds_goal(_, GoalInfo),
NonLocals = goal_info_get_nonlocals(GoalInfo),
InstmapDelta = goal_info_get_instmap_delta(GoalInfo),
BoundNonLocals = set.filter(var_is_bound_in_instmap_delta(ModuleInfo,
InstMap, InstmapDelta), NonLocals).
:- pred make_try_lambda(hlds_goal::in, set(prog_var)::in, mer_type::in,
maybe(try_io_state_vars)::in, prog_var::out, hlds_goal::out,
proc_info::in, proc_info::out) is det.
make_try_lambda(Body0, OutputVarsSet, OutputTupleType, MaybeIO,
LambdaVar, AssignLambdaVar, !ProcInfo) :-
Body0 = hlds_goal(_, BodyInfo0),
NonLocals0 = goal_info_get_nonlocals(BodyInfo0),
set.difference(NonLocals0, OutputVarsSet, NonLocals1),
proc_info_create_var_from_type(OutputTupleType, yes("OutputTuple"),
OutputTupleVar, !ProcInfo),
(
MaybeIO = yes(try_io_state_vars(IOVarInitial, IOVarFinal)),
LambdaParams = [OutputTupleVar, IOVarInitial, IOVarFinal],
LambdaParamTypes = [OutputTupleType, io_state_type, io_state_type],
LambdaParamModes = [out_mode, di_mode, uo_mode],
set.delete(NonLocals1, IOVarFinal, NonLocals)
;
MaybeIO = no,
LambdaParams = [OutputTupleVar],
LambdaParamTypes = [OutputTupleType],
LambdaParamModes = [out_mode],
NonLocals = NonLocals0
),
LambdaType = higher_order_type(LambdaParamTypes, no, purity_pure,
lambda_normal),
proc_info_create_var_from_type(LambdaType, yes("TryLambda"), LambdaVar,
!ProcInfo),
% Add the construction of OutputTuple to the body.
construct_tuple(OutputTupleVar, set.to_sorted_list(OutputVarsSet),
MakeOutputTuple),
conjoin_goals(Body0, MakeOutputTuple, LambdaBody0),
% Rename away output variables in the lambda body.
proc_info_get_varset(!.ProcInfo, VarSet0),
proc_info_get_vartypes(!.ProcInfo, VarTypes0),
clone_variables(set.to_sorted_list(OutputVarsSet), VarSet0, VarTypes0,
VarSet0, VarSet, VarTypes0, VarTypes, map.init, Renaming),
proc_info_set_varset(VarSet, !ProcInfo),
proc_info_set_vartypes(VarTypes, !ProcInfo),
rename_some_vars_in_goal(Renaming, LambdaBody0, LambdaBody),
% Get the determinism of the lambda.
LambdaBody = hlds_goal(_, BodyGoalInfo),
BodyDetism = goal_info_get_determinism(BodyGoalInfo),
detism_to_try_lambda_detism(BodyDetism, LambdaDetism),
% Make the lambda assignment.
RHS = rhs_lambda_goal(purity_pure, ho_ground, pf_predicate,
lambda_normal, set.to_sorted_list(NonLocals),
LambdaParams, LambdaParamModes, LambdaDetism, LambdaBody),
create_pure_atomic_complicated_unification(LambdaVar, RHS,
term.context_init, umc_implicit("try_expand"), [], AssignLambdaVar).
% try* don't cover all possible determinisms so we have generate lambdas
% with less restrictive determinisms.
%
:- pred detism_to_try_lambda_detism(determinism::in, determinism::out) is det.
detism_to_try_lambda_detism(detism_det, detism_det).
detism_to_try_lambda_detism(detism_semi, detism_semi).
detism_to_try_lambda_detism(detism_multi, detism_cc_multi).
detism_to_try_lambda_detism(detism_non, detism_cc_non).
detism_to_try_lambda_detism(detism_cc_multi, detism_cc_multi).
detism_to_try_lambda_detism(detism_cc_non, detism_cc_non).
detism_to_try_lambda_detism(detism_erroneous, detism_det).
detism_to_try_lambda_detism(detism_failure, detism_semi).
:- pred make_try_call(string::in, prog_var::in, prog_var::in,
list(prog_var)::in, mer_type::in, purity::in, hlds_goal::out,
pred_info::in, pred_info::out, proc_info::in, proc_info::out,
module_info::in, module_info::out) is det.
make_try_call(PredName, LambdaVar, ResultVar, ExtraArgs, OutputTupleType,
GoalPurity, OverallGoal, !PredInfo, !ProcInfo, !ModuleInfo) :-
create_poly_info(!.ModuleInfo, !.PredInfo, !.ProcInfo, PolyInfo0),
polymorphism_make_type_info_var(OutputTupleType, term.context_init,
TypeInfoVar, MakeTypeInfoGoals, PolyInfo0, PolyInfo),
poly_info_extract(PolyInfo, !PredInfo, !ProcInfo, !:ModuleInfo),
% The mode will be fixed up by a later analysis.
Mode = mode_no(0),
Args = [TypeInfoVar, LambdaVar, ResultVar] ++ ExtraArgs,
Features = [],
InstMapSrc = [],
generate_simple_call(mercury_exception_module, PredName,
pf_predicate, Mode, detism_cc_multi, purity_pure, Args, Features,
InstMapSrc, !.ModuleInfo, term.context_init, CallGoal0),
goal_info_init(GoalInfo),
% The try* predicates are only implemented for pure lambdas. If the lambda
% is actually non-pure, retain that in the call to try* with a purity
% scope.
(
GoalPurity = purity_pure,
CallGoal = CallGoal0
;
( GoalPurity = purity_semipure
; GoalPurity = purity_impure
),
ScopeReason = promise_purity(GoalPurity),
CallGoal = hlds_goal(scope(ScopeReason, CallGoal0), GoalInfo)
),
conj_list_to_goal(MakeTypeInfoGoals ++ [CallGoal], GoalInfo, OverallGoal).
:- pred make_unreachable_call(module_info::in, hlds_goal::out) is det.
make_unreachable_call(ModuleInfo, Goal) :-
generate_simple_call(mercury_exception_module, "unreachable",
pf_predicate, only_mode, detism_erroneous, purity_pure,
[], [], [], ModuleInfo, term.context_init, Goal).
:- pred make_output_tuple_inst_cast(prog_var::in, prog_var::in,
list(mer_inst)::in, hlds_goal::out) is det.
make_output_tuple_inst_cast(TmpTupleVar, TupleVar, TupleArgInsts,
CastOrUnify) :-
% If all the arguments have inst `ground' then a unification is enough.
(
list.member(ArgInst, TupleArgInsts),
ArgInst \= ground(_, none)
->
TupleArity = list.length(TupleArgInsts),
TupleInst = bound(shared, [
bound_functor(tuple_cons(TupleArity), TupleArgInsts)
]),
generate_cast_with_insts(unsafe_type_inst_cast, TmpTupleVar, TupleVar,
ground_inst, TupleInst, term.context_init, CastOrUnify)
;
create_pure_atomic_complicated_unification(TupleVar,
rhs_var(TmpTupleVar), term.context_init,
umc_implicit("try_expand"), [], CastOrUnify)
).
%-----------------------------------------------------------------------------%
try_expand_may_introduce_calls("try", 2).
try_expand_may_introduce_calls("try_io", 4).
try_expand_may_introduce_calls("unreachable", 0).
%-----------------------------------------------------------------------------%
:- func this_file = string.
this_file = "try_expand.m".
%-----------------------------------------------------------------------------%
:- end_module check_hlds.try_expand.
%-----------------------------------------------------------------------------%