mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-16 06:14:59 +00:00
Estimated hours taken: 6 Branches: main compiler/*.m: Convert almost all remaining modules in the compiler to use "$module, $pred" instead of "this_file" in error messages. In a few cases, the old error message was misleading, since it contained an incorrect, out-of-date or cut-and-pasted predicate name. tests/invalid/unresolved_overloading.err_exp: Update an expected output containing an updated error message.
1601 lines
62 KiB
Mathematica
1601 lines
62 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2007-2011 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File: erl_code_gen.m.
|
|
% Main author: wangp.
|
|
%
|
|
% ELDS code generation -- convert from HLDS to ELDS.
|
|
%
|
|
% XXX more documentation to come later
|
|
%
|
|
% For now, the notation `Foo [[ Bar ]]' means to generate the code for
|
|
% expression `Foo', ultimately evaluating to the value `Bar' on success.
|
|
% Code which can fail currently always evaluates to the atom `fail' (this will
|
|
% be changed to improve the code generated for disjuncts, which should rather
|
|
% evaluate to an expression representing the rest of the disjunction on
|
|
% failure).
|
|
%
|
|
% TODO: (this is incomplete)
|
|
% - contexts are ignored at the moment
|
|
% - RTTI
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module erl_backend.erl_code_gen.
|
|
:- interface.
|
|
|
|
:- import_module erl_backend.elds.
|
|
:- import_module hlds.hlds_module.
|
|
|
|
:- import_module io.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate Erlang code for an entire module.
|
|
%
|
|
:- pred erl_code_gen(module_info::in, elds::out, io::di, io::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.foreign.
|
|
:- import_module check_hlds.type_util.
|
|
:- import_module erl_backend.erl_call_gen.
|
|
:- import_module erl_backend.erl_code_util.
|
|
:- import_module erl_backend.erl_unify_gen.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.hlds_data.
|
|
:- 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 libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module parse_tree.prog_data.
|
|
:- import_module parse_tree.prog_foreign.
|
|
:- import_module parse_tree.prog_type.
|
|
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
:- import_module varset.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
erl_code_gen(ModuleInfo, ELDS, !IO) :-
|
|
module_info_get_name(ModuleInfo, ModuleName),
|
|
erl_gen_preds(ModuleInfo, ProcDefns, !IO),
|
|
erl_gen_imports(ModuleInfo, Imports),
|
|
filter_erlang_foreigns(ModuleInfo, ForeignDecls, ForeignBodies,
|
|
PragmaExports, !IO),
|
|
erl_gen_foreign_exports(ProcDefns, PragmaExports, ForeignExportDefns),
|
|
% RTTI function definitions are added later by rtti_data_list_to_elds.
|
|
RttiDefns = [],
|
|
module_info_user_init_pred_procs(ModuleInfo, InitPredProcs),
|
|
module_info_user_final_pred_procs(ModuleInfo, FinalPredProcs),
|
|
ELDS = elds(ModuleName, Imports, ForeignDecls, ForeignBodies, ProcDefns,
|
|
ForeignExportDefns, RttiDefns, InitPredProcs, FinalPredProcs).
|
|
|
|
:- pred erl_gen_imports(module_info::in, set(module_name)::out) is det.
|
|
|
|
erl_gen_imports(ModuleInfo, AllImports) :-
|
|
module_info_get_all_deps(ModuleInfo, AllImports0),
|
|
% No module needs to import itself.
|
|
module_info_get_name(ModuleInfo, ThisModule),
|
|
AllImports = set.delete(AllImports0, ThisModule).
|
|
|
|
:- pred filter_erlang_foreigns(module_info::in, list(foreign_decl_code)::out,
|
|
list(foreign_body_code)::out, list(pragma_exported_proc)::out,
|
|
io::di, io::uo) is det.
|
|
|
|
filter_erlang_foreigns(ModuleInfo, ForeignDecls, ForeignBodies, PragmaExports,
|
|
!IO) :-
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.get_backend_foreign_languages(Globals, BackendForeignLanguages),
|
|
( BackendForeignLanguages = [lang_erlang] ->
|
|
true
|
|
;
|
|
unexpected($module, $pred, "foreign language other than Erlang")
|
|
),
|
|
module_info_get_foreign_decl(ModuleInfo, AllForeignDecls),
|
|
module_info_get_foreign_body_code(ModuleInfo, AllForeignBodys),
|
|
module_info_get_pragma_exported_procs(ModuleInfo, AllPragmaExports),
|
|
foreign.filter_decls(lang_erlang, AllForeignDecls, RevForeignDecls,
|
|
_OtherForeignDecls),
|
|
foreign.filter_bodys(lang_erlang, AllForeignBodys, RevForeignBodies,
|
|
_OtherForeignBodys),
|
|
foreign.filter_exports(lang_erlang, AllPragmaExports, RevPragmaExports,
|
|
_OtherForeignExports),
|
|
ForeignDecls = list.reverse(RevForeignDecls),
|
|
ForeignBodies = list.reverse(RevForeignBodies),
|
|
PragmaExports = list.reverse(RevPragmaExports).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred erl_gen_preds(module_info::in, list(elds_defn)::out, io::di, io::uo)
|
|
is det.
|
|
|
|
erl_gen_preds(ModuleInfo, PredDefns, !IO) :-
|
|
module_info_get_preds(ModuleInfo, PredTable),
|
|
map.keys(PredTable, PredIds),
|
|
erl_gen_preds_2(ModuleInfo, PredIds, PredTable, [], RevPredDefns, !IO),
|
|
PredDefns = list.reverse(RevPredDefns).
|
|
|
|
:- pred erl_gen_preds_2(module_info::in, list(pred_id)::in, pred_table::in,
|
|
list(elds_defn)::in, list(elds_defn)::out, io::di, io::uo) is det.
|
|
|
|
erl_gen_preds_2(ModuleInfo, PredIds0, PredTable, !Defns, !IO) :-
|
|
(
|
|
PredIds0 = [PredId | PredIds],
|
|
map.lookup(PredTable, PredId, PredInfo),
|
|
pred_info_get_import_status(PredInfo, ImportStatus),
|
|
(
|
|
(
|
|
ImportStatus = status_imported(_)
|
|
;
|
|
% XXX comment was from ml_code_gen.m, don't know if it applies.
|
|
% We generate incorrect and unnecessary code for the external
|
|
% special preds which are pseudo_imported, so just ignore them.
|
|
is_unify_or_compare_pred(PredInfo),
|
|
ImportStatus = status_external(status_pseudo_imported)
|
|
)
|
|
->
|
|
true
|
|
;
|
|
erl_gen_pred(ModuleInfo, PredId, PredInfo, ImportStatus,
|
|
!Defns, !IO)
|
|
),
|
|
erl_gen_preds_2(ModuleInfo, PredIds, PredTable, !Defns, !IO)
|
|
;
|
|
PredIds0 = []
|
|
).
|
|
|
|
% Generate ELDS definitions for all the non-imported procedures
|
|
% of a given predicate (or function).
|
|
%
|
|
:- pred erl_gen_pred(module_info::in, pred_id::in, pred_info::in,
|
|
import_status::in, list(elds_defn)::in, list(elds_defn)::out,
|
|
io::di, io::uo) is det.
|
|
|
|
erl_gen_pred(ModuleInfo, PredId, PredInfo, ImportStatus, !Defns, !IO) :-
|
|
( ImportStatus = status_external(_) ->
|
|
ProcIds = pred_info_procids(PredInfo)
|
|
;
|
|
ProcIds = pred_info_non_imported_procids(PredInfo)
|
|
),
|
|
(
|
|
ProcIds = []
|
|
;
|
|
ProcIds = [_ | _],
|
|
write_pred_progress_message("% Generating ELDS code for ",
|
|
PredId, ModuleInfo, !IO),
|
|
pred_info_get_procedures(PredInfo, ProcTable),
|
|
erl_gen_procs(ProcIds, ModuleInfo, PredId, PredInfo, ProcTable, !Defns)
|
|
).
|
|
|
|
:- pred erl_gen_procs(list(proc_id)::in, module_info::in, pred_id::in,
|
|
pred_info::in, proc_table::in, list(elds_defn)::in, list(elds_defn)::out)
|
|
is det.
|
|
|
|
erl_gen_procs([], _, _, _, _, !Defns).
|
|
erl_gen_procs([ProcId | ProcIds], ModuleInfo, PredId, PredInfo, ProcTable,
|
|
!Defns) :-
|
|
map.lookup(ProcTable, ProcId, ProcInfo),
|
|
(
|
|
erl_maybe_gen_simple_special_pred(ModuleInfo, PredId, ProcId,
|
|
PredInfo, ProcInfo, !Defns)
|
|
->
|
|
true
|
|
;
|
|
erl_gen_proc(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo, !Defns)
|
|
),
|
|
erl_gen_procs(ProcIds, ModuleInfo, PredId, PredInfo, ProcTable, !Defns).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% erl_maybe_gen_simple_special_pred(ModuleInfo, PredId, ProcId,
|
|
% PredInfo, ProcInfo, !Defns)
|
|
%
|
|
% If the procedure is a compiler generated unification or comparison
|
|
% procedure, and the arguments are ground, and the values of the types they
|
|
% are comparing do not have user-defined equality or comparison then
|
|
% generate simpler versions of those procedures using the Erlang comparison
|
|
% operators. Otherwise fail.
|
|
%
|
|
:- pred erl_maybe_gen_simple_special_pred(module_info::in,
|
|
pred_id::in, proc_id::in, pred_info::in, proc_info::in,
|
|
list(elds_defn)::in, list(elds_defn)::out) is semidet.
|
|
|
|
erl_maybe_gen_simple_special_pred(ModuleInfo, PredId, ProcId,
|
|
PredInfo, ProcInfo, !Defns) :-
|
|
PredName = pred_info_name(PredInfo),
|
|
PredArity = pred_info_orig_arity(PredInfo),
|
|
special_pred_name_arity(SpecialId, _, PredName, PredArity),
|
|
proc_info_get_headvars(ProcInfo, Args),
|
|
proc_info_get_vartypes(ProcInfo, VarTypes),
|
|
(
|
|
SpecialId = spec_pred_unify,
|
|
in_in_unification_proc_id(ProcId),
|
|
list.reverse(Args, [Y, X | _]),
|
|
map.lookup(VarTypes, Y, Type),
|
|
check_dummy_type(ModuleInfo, Type) = is_not_dummy_type,
|
|
type_definitely_has_no_user_defined_equality_pred(ModuleInfo, Type),
|
|
erl_gen_simple_in_in_unification(ModuleInfo, PredId, ProcId, X, Y,
|
|
ProcDefn)
|
|
;
|
|
SpecialId = spec_pred_compare,
|
|
list.reverse(Args, [Y, X, _Res | _]),
|
|
map.lookup(VarTypes, Y, Type),
|
|
check_dummy_type(ModuleInfo, Type) = is_not_dummy_type,
|
|
type_definitely_has_no_user_defined_equality_pred(ModuleInfo, Type),
|
|
erl_gen_simple_compare(ModuleInfo, PredId, ProcId, X, Y, ProcDefn)
|
|
),
|
|
!:Defns = [ProcDefn | !.Defns].
|
|
|
|
:- pred erl_gen_simple_in_in_unification(module_info::in,
|
|
pred_id::in, proc_id::in, prog_var::in, prog_var::in, elds_defn::out)
|
|
is det.
|
|
|
|
erl_gen_simple_in_in_unification(ModuleInfo, PredId, ProcId, X, Y, ProcDefn) :-
|
|
Info = erl_gen_info_init(ModuleInfo, PredId, ProcId),
|
|
erl_gen_info_get_input_vars(Info, InputVars),
|
|
|
|
% '__Unify__'(X, Y) ->
|
|
% case X =:= Y of
|
|
% true -> {};
|
|
% false -> fail
|
|
% end.
|
|
|
|
Clause = elds_clause(terms_from_vars(InputVars), ClauseExpr),
|
|
ClauseExpr = elds_case_expr(CompareXY, [TrueCase, FalseCase]),
|
|
CompareXY = elds_binop((=:=), expr_from_var(X), expr_from_var(Y)),
|
|
TrueCase = elds_case(elds_true, elds_term(elds_empty_tuple)),
|
|
FalseCase = elds_case(elds_false, elds_term(elds_fail)),
|
|
|
|
erl_gen_info_get_varset(Info, ProcVarSet),
|
|
erl_gen_info_get_env_vars(Info, EnvVarNames),
|
|
ProcDefn = elds_defn(proc(PredId, ProcId), ProcVarSet,
|
|
body_defined_here(Clause), EnvVarNames).
|
|
|
|
:- pred erl_gen_simple_compare(module_info::in, pred_id::in, proc_id::in,
|
|
prog_var::in, prog_var::in, elds_defn::out) is det.
|
|
|
|
erl_gen_simple_compare(ModuleInfo, PredId, ProcId, X, Y, ProcDefn) :-
|
|
Info = erl_gen_info_init(ModuleInfo, PredId, ProcId),
|
|
erl_gen_info_get_input_vars(Info, InputVars),
|
|
|
|
XExpr = expr_from_var(X),
|
|
YExpr = expr_from_var(Y),
|
|
|
|
% '__Compare__'(X, Y) ->
|
|
% case X =:= Y of
|
|
% true -> {'='};
|
|
% false ->
|
|
% case X < Y of
|
|
% true -> {'<'};
|
|
% false -> {'>'};
|
|
% end
|
|
% end.
|
|
%
|
|
Clause = elds_clause(terms_from_vars(InputVars), ClauseExpr),
|
|
ClauseExpr = elds_case_expr(CondEq, [IsEq, IsNotEq]),
|
|
|
|
CondEq = elds_binop((=:=), XExpr, YExpr),
|
|
IsEq = elds_case(elds_true, elds_term(make_enum_alternative("="))),
|
|
IsNotEq = elds_case(elds_false, elds_case_expr(CondLt, [IsLt, IsGt])),
|
|
|
|
CondLt = elds_binop((<), XExpr, YExpr),
|
|
IsLt = elds_case(elds_true, elds_term(make_enum_alternative("<"))),
|
|
IsGt = elds_case(elds_false, elds_term(make_enum_alternative(">"))),
|
|
|
|
erl_gen_info_get_varset(Info, ProcVarSet),
|
|
erl_gen_info_get_env_vars(Info, EnvVarNames),
|
|
ProcDefn = elds_defn(proc(PredId, ProcId), ProcVarSet,
|
|
body_defined_here(Clause), EnvVarNames).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for handling individual procedures
|
|
%
|
|
|
|
% Generate ELDS code for the specified procedure.
|
|
%
|
|
:- pred erl_gen_proc(module_info::in, pred_id::in, proc_id::in, pred_info::in,
|
|
proc_info::in, list(elds_defn)::in, list(elds_defn)::out) is det.
|
|
|
|
erl_gen_proc(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo, !Defns) :-
|
|
erl_gen_proc_defn(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo,
|
|
ProcVarSet, ProcBody, EnvVarNames),
|
|
ProcDefn = elds_defn(proc(PredId, ProcId), ProcVarSet, ProcBody,
|
|
EnvVarNames),
|
|
!:Defns = [ProcDefn | !.Defns].
|
|
|
|
% Generate an ELDS definition for the specified procedure.
|
|
%
|
|
:- pred erl_gen_proc_defn(module_info::in, pred_id::in, proc_id::in,
|
|
pred_info::in, proc_info::in, prog_varset::out, elds_body::out,
|
|
set(string)::out) is det.
|
|
|
|
erl_gen_proc_defn(ModuleInfo, PredId, ProcId, PredInfo, ProcInfo,
|
|
ProcVarSet, ProcBody, EnvVarNames) :-
|
|
pred_info_get_import_status(PredInfo, ImportStatus),
|
|
CodeModel = proc_info_interface_code_model(ProcInfo),
|
|
proc_info_get_headvars(ProcInfo, HeadVars),
|
|
proc_info_get_initial_instmap(ProcInfo, ModuleInfo, InstMap0),
|
|
proc_info_get_goal(ProcInfo, Goal0),
|
|
|
|
% The HLDS front-end sometimes over-estimates the set of non-locals.
|
|
% We need to restrict the set of non-locals for the top-level goal
|
|
% to just the headvars, because otherwise variables which occur in the
|
|
% top-level non-locals but which are not really non-local will not be
|
|
% declared.
|
|
|
|
Goal0 = hlds_goal(GoalExpr, GoalInfo0),
|
|
NonLocals0 = goal_info_get_code_gen_nonlocals(GoalInfo0),
|
|
set.list_to_set(HeadVars, HeadVarsSet),
|
|
set.intersect(HeadVarsSet, NonLocals0, NonLocals),
|
|
goal_info_set_code_gen_nonlocals(NonLocals, GoalInfo0, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
|
|
_Context = goal_info_get_context(GoalInfo),
|
|
|
|
some [!Info] (
|
|
!:Info = erl_gen_info_init(ModuleInfo, PredId, ProcId),
|
|
|
|
( ImportStatus = status_external(_) ->
|
|
% This procedure is externally defined.
|
|
pred_info_get_arg_types(PredInfo, ArgTypes),
|
|
proc_info_get_argmodes(ProcInfo, ArgModes),
|
|
erl_gen_arg_list(ModuleInfo, opt_dummy_args,
|
|
HeadVars, ArgTypes, ArgModes, InputArgs, _OutputArgs),
|
|
(
|
|
( CodeModel = model_det
|
|
; CodeModel = model_semi
|
|
),
|
|
Arity = list.length(InputArgs)
|
|
;
|
|
CodeModel = model_non,
|
|
% Extra argument for success continuation.
|
|
Arity = list.length(InputArgs) + 1
|
|
),
|
|
ProcBody = body_external(Arity)
|
|
;
|
|
erl_gen_proc_body(CodeModel, InstMap0, Goal, ProcClause,
|
|
!Info),
|
|
ProcBody = body_defined_here(ProcClause)
|
|
),
|
|
|
|
erl_gen_info_get_varset(!.Info, ProcVarSet),
|
|
erl_gen_info_get_env_vars(!.Info, EnvVarNames)
|
|
).
|
|
|
|
:- pred erl_gen_proc_body(code_model::in, instmap::in, hlds_goal::in,
|
|
elds_clause::out, erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_proc_body(CodeModel, InstMap0, Goal, ProcClause, !Info) :-
|
|
erl_gen_info_get_input_vars(!.Info, InputVars),
|
|
erl_gen_info_get_output_vars(!.Info, OutputVars),
|
|
OutputVarsExprs = exprs_from_vars(OutputVars),
|
|
(
|
|
CodeModel = model_det,
|
|
InputVarsTerms = terms_from_vars(InputVars),
|
|
%
|
|
% On success, the procedure returns either:
|
|
% - the single output variable, or
|
|
% - a tuple of its output variables if there are zero or two or more
|
|
% output variables.
|
|
%
|
|
SuccessExpr = tuple_or_single_expr(OutputVarsExprs),
|
|
InstMap = InstMap0
|
|
;
|
|
CodeModel = model_semi,
|
|
InputVarsTerms = terms_from_vars(InputVars),
|
|
%
|
|
% On success, the procedure returns a tuple of its output variables.
|
|
%
|
|
SuccessExpr = elds_term(elds_tuple(OutputVarsExprs)),
|
|
InstMap = InstMap0
|
|
;
|
|
CodeModel = model_non,
|
|
%
|
|
% On success, the procedure calls a continuation, passing the values of
|
|
% its output variables as arguments. The continuation is supplied as
|
|
% an extra argument to the current procedure.
|
|
%
|
|
erl_gen_info_new_named_var("SucceedHeadVar", SucceedVar, !Info),
|
|
ground_var_in_instmap(SucceedVar, InstMap0, InstMap),
|
|
InputVarsTerms = terms_from_vars(InputVars ++ [SucceedVar]),
|
|
SuccessExpr = elds_call(elds_call_ho(expr_from_var(SucceedVar)),
|
|
OutputVarsExprs)
|
|
),
|
|
erl_gen_goal(CodeModel, InstMap, Goal, yes(SuccessExpr), Statement, !Info),
|
|
ProcClause = elds_clause(InputVarsTerms, Statement).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Stuff to generate code for goals.
|
|
%
|
|
|
|
:- pred erl_gen_goal(code_model::in, instmap::in, hlds_goal::in,
|
|
maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
% Generate ELDS code for the specified goal in the specified code model.
|
|
%
|
|
% If MaybeSuccessExpr is `yes(SuccessExpr)' then SuccessExpr is the
|
|
% expression that the code generated for Goal must evaluate to, if the Goal
|
|
% succeeds. MaybeSuccessExpr can only be `no' for model_det code.
|
|
% On failure, model_semi code returns the atom `fail'.
|
|
% On failure, model_non code may return anything.
|
|
%
|
|
erl_gen_goal(CodeModel, InstMap, Goal, MaybeSuccessExpr0, Code, !Info) :-
|
|
Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
Context = goal_info_get_context(GoalInfo),
|
|
GoalCodeModel = goal_info_get_code_model(GoalInfo),
|
|
(
|
|
(
|
|
CodeModel = model_det,
|
|
GoalCodeModel = model_semi
|
|
;
|
|
CodeModel = model_det,
|
|
GoalCodeModel = model_non
|
|
;
|
|
CodeModel = model_semi,
|
|
GoalCodeModel = model_non
|
|
)
|
|
->
|
|
unexpected($module, $pred, "code model mismatch")
|
|
;
|
|
Determinism = goal_info_get_determinism(GoalInfo),
|
|
(
|
|
Determinism = detism_erroneous
|
|
->
|
|
% This goal can't succeed. Don't pass a success expression
|
|
% which, if inserted into the generated code, could contain
|
|
% references to unbound variables (since the goal may have
|
|
% aborted before binding them).
|
|
MaybeSuccessExpr = no
|
|
;
|
|
Determinism = detism_failure
|
|
->
|
|
% This goal can't succeed. As above we don't want to pass a
|
|
% success expression, but we must pass something to maintain the
|
|
% invariant that a model_semi goal has a success expression.
|
|
MaybeSuccessExpr = yes(elds_term(elds_fail))
|
|
;
|
|
MaybeSuccessExpr = MaybeSuccessExpr0
|
|
),
|
|
erl_gen_goal_expr(GoalExpr, GoalCodeModel, Determinism, InstMap,
|
|
Context, MaybeSuccessExpr, Code, !Info)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate code for a commit.
|
|
%
|
|
:- pred erl_gen_commit(hlds_goal::in, code_model::in, determinism::in,
|
|
instmap::in, prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_commit(Goal, CodeModel, ScopeDetism, InstMap, Context,
|
|
MaybeSuccessExpr, Statement, !Info) :-
|
|
Goal = hlds_goal(_, GoalInfo),
|
|
GoalCodeModel = goal_info_get_code_model(GoalInfo),
|
|
_GoalContext = goal_info_get_context(GoalInfo),
|
|
|
|
(
|
|
GoalCodeModel = model_non,
|
|
CodeModel = model_semi
|
|
->
|
|
( ScopeDetism = detism_failure ->
|
|
% If the scope has determinism `failure' then Goal can't succeed.
|
|
% The code is probably implementing a failure driven loop or
|
|
% something similar. No commit is required.
|
|
%
|
|
% model_non in failure context:
|
|
% <succeeded = Goal>
|
|
% ===>
|
|
% <Goal && SUCCEED()>,
|
|
% fail
|
|
%
|
|
erl_gen_goal(GoalCodeModel, InstMap, Goal, MaybeSuccessExpr,
|
|
GoalStatement, !Info),
|
|
Statement = join_exprs(GoalStatement, elds_term(elds_fail))
|
|
;
|
|
% model_non in semi context:
|
|
% <succeeded = Goal>
|
|
% ===>
|
|
%
|
|
% let Throw = ``throw({'MERCURY_COMMIT', {NonLocals, ...})''
|
|
% where NonLocals are variables bound by Goal.
|
|
%
|
|
% try
|
|
% <Goal && Throw()>
|
|
% of
|
|
% _ -> fail
|
|
% catch
|
|
% throw: {'MERCURY_COMMIT', {NonLocals, ...}} ->
|
|
% SuccessExpr
|
|
% end
|
|
|
|
erl_gen_commit_pieces(Goal, InstMap, Context, no,
|
|
GoalStatement, PackedNonLocals, !Info),
|
|
|
|
Statement = elds_try(GoalStatement, [AnyCase], yes(Catch), no),
|
|
AnyCase = elds_case(elds_anon_var, elds_term(elds_fail)),
|
|
Catch = elds_catch(elds_throw_atom,
|
|
elds_tuple([elds_commit_marker, PackedNonLocals]),
|
|
det_expr(MaybeSuccessExpr))
|
|
)
|
|
;
|
|
GoalCodeModel = model_non,
|
|
CodeModel = model_det
|
|
->
|
|
% model_non in det context:
|
|
% <do Goal>
|
|
% ===>
|
|
%
|
|
% let Throw = ``throw({'MERCURY_COMMIT', {NonLocals, ...}})''
|
|
% where NonLocals are variables bound by Goal.
|
|
%
|
|
% {NonLocals, ...} =
|
|
% (try
|
|
% <Goal && Throw()>
|
|
% catch
|
|
% throw: {'MERCURY_COMMIT', Results} -> Results
|
|
% end)
|
|
|
|
erl_gen_commit_pieces(Goal, InstMap, Context, yes,
|
|
GoalStatement, PackedNonLocals, !Info),
|
|
|
|
erl_gen_info_new_named_var("Results", ResultsVar, !Info),
|
|
ResultsVarExpr = expr_from_var(ResultsVar),
|
|
|
|
Statement = elds_eq(PackedNonLocals, TryExpr),
|
|
TryExpr = elds_try(GoalStatement, [], yes(Catch), no),
|
|
Catch = elds_catch(elds_throw_atom,
|
|
elds_tuple([elds_commit_marker, ResultsVarExpr]), ResultsVarExpr)
|
|
;
|
|
% No commit required.
|
|
erl_gen_goal(CodeModel, InstMap, Goal, MaybeSuccessExpr, Statement,
|
|
!Info)
|
|
).
|
|
|
|
:- pred erl_gen_commit_pieces(hlds_goal::in, instmap::in, prog_context::in,
|
|
bool::in, elds_expr::out, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_commit_pieces(Goal, InstMap, _Context, DoRenaming,
|
|
GoalStatement, PackedNonLocals, !Info) :-
|
|
% Find the nonlocal variables bound by the goal.
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap, Goal, NonLocalsSet),
|
|
NonLocals = set.to_sorted_list(NonLocalsSet),
|
|
|
|
% Throw = ``throw({'MERCURY_COMMIT', {NonLocals, ...})''
|
|
Throw = elds_throw(elds_term(ThrowValue)),
|
|
ThrowValue = elds_tuple([elds_commit_marker, PackedNonLocals]),
|
|
PackedNonLocals = elds_term(elds_tuple(exprs_from_vars(NonLocals))),
|
|
|
|
% Generate the goal expression such that it throws the exception
|
|
% at the first solution.
|
|
erl_gen_goal(model_non, InstMap, Goal, yes(Throw), GoalStatement0, !Info),
|
|
|
|
% Rename the nonlocal variables in the generated expression if we have to.
|
|
(
|
|
DoRenaming = yes,
|
|
erl_create_renaming(NonLocals, Subn, !Info),
|
|
erl_rename_vars_in_expr(Subn, GoalStatement0, GoalStatement)
|
|
;
|
|
DoRenaming = no,
|
|
GoalStatement = GoalStatement0
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Generate ELDS code for the different kinds of HLDS goals.
|
|
%
|
|
:- pred erl_gen_goal_expr(hlds_goal_expr::in, code_model::in, determinism::in,
|
|
instmap::in, prog_context::in, maybe(elds_expr)::in,
|
|
elds_expr::out, erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_goal_expr(GoalExpr, CodeModel, Detism, InstMap, Context,
|
|
MaybeSuccessExpr, Statement, !Info) :-
|
|
(
|
|
GoalExpr = switch(Var, CanFail, CasesList),
|
|
erl_gen_switch(Var, CanFail, CasesList, CodeModel, InstMap,
|
|
Context, MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GoalExpr = scope(Reason, SubGoal),
|
|
(
|
|
( Reason = promise_solutions(_, _)
|
|
; Reason = commit(_)
|
|
),
|
|
erl_gen_commit(SubGoal, CodeModel, Detism, InstMap, Context,
|
|
MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
Reason = require_detism(_),
|
|
unexpected($module, $pred, "require_detism")
|
|
;
|
|
Reason = require_complete_switch(_),
|
|
unexpected($module, $pred, "require_complete_switch")
|
|
;
|
|
( Reason = exist_quant(_)
|
|
; Reason = promise_purity(_)
|
|
; Reason = barrier(_)
|
|
; Reason = from_ground_term(_, _)
|
|
; Reason = trace_goal(_, _, _, _, _)
|
|
% Trace goals with run-time conditions are transformed into
|
|
% if-then-else goals where the condition is a special
|
|
% foreign_proc call and the then branch is the actual
|
|
% trace goal (i.e. this goal). Thus there is nothing special
|
|
% we have to do here.
|
|
),
|
|
SubGoal = hlds_goal(SubGoalExpr, SubGoalInfo),
|
|
SubGoalDetism = goal_info_get_determinism(SubGoalInfo),
|
|
erl_gen_goal_expr(SubGoalExpr, CodeModel, SubGoalDetism,
|
|
InstMap, Context, MaybeSuccessExpr, Statement, !Info)
|
|
)
|
|
;
|
|
GoalExpr = if_then_else(_Vars, Cond, Then, Else),
|
|
erl_gen_ite(CodeModel, InstMap, Cond, Then, Else, Context,
|
|
MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GoalExpr = negation(SubGoal),
|
|
erl_gen_negation(SubGoal, CodeModel, InstMap, Context, MaybeSuccessExpr,
|
|
Statement, !Info)
|
|
;
|
|
GoalExpr = conj(_ConjType, Goals),
|
|
% XXX Currently we treat parallel conjunction the same as
|
|
% sequential conjunction -- parallelism is not yet implemented.
|
|
erl_gen_conj(Goals, CodeModel, Detism, InstMap, Context,
|
|
MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GoalExpr = disj(Goals),
|
|
erl_gen_disj(Goals, CodeModel, InstMap, Context, MaybeSuccessExpr,
|
|
Statement, !Info)
|
|
;
|
|
GoalExpr = generic_call(GenericCall, Vars, Modes, CallDetism),
|
|
determinism_to_code_model(CallDetism, CallCodeModel),
|
|
expect(unify(CodeModel, CallCodeModel), $module, $pred,
|
|
"code model mismatch"),
|
|
(
|
|
GenericCall = higher_order(_, _, _, _),
|
|
erl_gen_higher_order_call(GenericCall, Vars, Modes, CallDetism,
|
|
Context, MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GenericCall = class_method(_, _, _, _),
|
|
erl_gen_class_method_call(GenericCall, Vars, Modes, CallDetism,
|
|
Context, MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GenericCall = event_call(_),
|
|
sorry($module, $pred, "event_calls in erlang backend")
|
|
;
|
|
GenericCall = cast(_),
|
|
erl_gen_cast(Context, Vars, MaybeSuccessExpr, Statement, !Info)
|
|
)
|
|
;
|
|
GoalExpr = plain_call(PredId, ProcId, ArgVars, BuiltinState, _, _),
|
|
(
|
|
BuiltinState = not_builtin,
|
|
erl_variable_types(!.Info, ArgVars, ActualArgTypes),
|
|
erl_gen_call(PredId, ProcId, ArgVars, ActualArgTypes,
|
|
CodeModel, Context, MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
BuiltinState = inline_builtin,
|
|
erl_gen_builtin(PredId, ProcId, ArgVars, CodeModel, Context,
|
|
MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
BuiltinState = out_of_line_builtin,
|
|
unexpected($module, $pred, "out_of_line_builtin")
|
|
)
|
|
;
|
|
GoalExpr = unify(_LHS, _RHS, _Mode, Unification, _UnifyContext),
|
|
erl_gen_unification(Unification, CodeModel, Context, MaybeSuccessExpr,
|
|
Statement, !Info)
|
|
;
|
|
GoalExpr = call_foreign_proc(_Attributes, _PredId, _ProcId,
|
|
Args, _ExtraArgs, MaybeTraceRuntimeCond, PragmaImpl),
|
|
erl_gen_foreign_code_call(Args, MaybeTraceRuntimeCond, PragmaImpl,
|
|
CodeModel, Context, MaybeSuccessExpr, Statement, !Info)
|
|
;
|
|
GoalExpr = shorthand(_),
|
|
% These should have been expanded out by now.
|
|
unexpected($module, $pred, "shorthand")
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for switches.
|
|
%
|
|
|
|
:- func duplicate_expr_limit = int.
|
|
|
|
duplicate_expr_limit = 10. % XXX arbitrary
|
|
|
|
:- func switch_strings_as_atoms_limit = int.
|
|
|
|
switch_strings_as_atoms_limit = 50. % XXX arbitrary
|
|
|
|
:- pred erl_gen_switch(prog_var::in, can_fail::in, list(hlds_goal.case)::in,
|
|
code_model::in, instmap::in, prog_context::in, maybe(elds_expr)::in,
|
|
elds_expr::out, erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_switch(Var, CanFail, CasesList, CodeModel, InstMap0, _Context,
|
|
MaybeSuccessExpr0, Statement, !Info) :-
|
|
%
|
|
% If the success expression is not too big, then we generate code for
|
|
% a switch like this:
|
|
%
|
|
% case Var of
|
|
% Pattern1 -> Expr1 [[ SuccessExpr ]] ;
|
|
% Pattern2 -> Expr2 [[ SuccessExpr ]] ;
|
|
% ...
|
|
% end
|
|
%
|
|
% Otherwise the success expression is put into a closure and the closure
|
|
% is called on success of each case:
|
|
%
|
|
% SuccessClosure = fun(Vars, ...) ->
|
|
% /* Vars are those variables bound by Expr<n> */
|
|
% SuccessExpr
|
|
% end,
|
|
% case Var of
|
|
% Pattern1 -> Expr1 [[ SuccessClosure() ]] ;
|
|
% Pattern2 -> Expr2 [[ SuccessClosure() ]] ;
|
|
% ...
|
|
% end
|
|
%
|
|
% If the switch can fail, a default case is added:
|
|
%
|
|
% _ -> fail
|
|
%
|
|
|
|
% Get the union of all nonlocal variables bound in all cases.
|
|
CasesGoals = list.map((func(case(_, _, Goal)) = Goal), CasesList),
|
|
union_bound_nonlocals_in_goals(!.Info, InstMap0, CasesGoals,
|
|
NonLocalsBoundInCases),
|
|
|
|
% Create a closure for the success expression if it is too large to
|
|
% duplicate into the disjuncts.
|
|
maybe_create_closure_for_success_expr(NonLocalsBoundInCases,
|
|
MaybeSuccessExpr0, MaybeMakeClosure, MaybeSuccessExpr,
|
|
InstMap0, InstMap, !Info),
|
|
|
|
erl_variable_type(!.Info, Var, VarType),
|
|
erl_gen_info_get_module_info(!.Info, ModuleInfo),
|
|
type_util.classify_type(ModuleInfo, VarType) = TypeCtorCategory,
|
|
|
|
(if
|
|
% The HiPE compiler is extremely slow compiling functions containing
|
|
% long case statements involving strings. Workaround: for a string
|
|
% switch with many cases, convert the string to an atom and switch on
|
|
% atoms instead.
|
|
TypeCtorCategory = ctor_cat_builtin(cat_builtin_string),
|
|
|
|
% list_to_atom could throw an exception for long strings, so we don't
|
|
% enable the workaround unless the user specifically passes
|
|
% --erlang-switch-on-strings-as-atoms.
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
globals.lookup_bool_option(Globals, erlang_switch_on_strings_as_atoms,
|
|
yes),
|
|
|
|
list.length(CasesList) > switch_strings_as_atoms_limit,
|
|
|
|
% The Erlang implementation limits atoms to be 255 characters (bytes)
|
|
% long or less, so we don't use the workaround if any cases are longer
|
|
% than that.
|
|
all [String] (
|
|
(
|
|
list.member(case(MainConsId, OtherConsIds, _), CasesList),
|
|
(
|
|
MainConsId = string_const(String)
|
|
;
|
|
list.member(string_const(String), OtherConsIds)
|
|
)
|
|
)
|
|
=>
|
|
string.length(String) =< 255
|
|
)
|
|
then
|
|
% Atom = list_to_atom(binary_to_list(Var))
|
|
erl_gen_info_new_named_var("Atom", AtomVar, !Info),
|
|
CharList = elds_call_builtin("binary_to_list", [expr_from_var(Var)]),
|
|
StringToAtom = elds_eq(expr_from_var(AtomVar),
|
|
elds_call_builtin("list_to_atom", [CharList])),
|
|
MaybeConvertToAtom = yes(StringToAtom),
|
|
SwitchVar = AtomVar,
|
|
GenCase = erl_gen_case_on_atom(CodeModel, InstMap,
|
|
NonLocalsBoundInCases, MaybeSuccessExpr)
|
|
else
|
|
MaybeConvertToAtom = no,
|
|
SwitchVar = Var,
|
|
GenCase = erl_gen_case(VarType,
|
|
CodeModel, InstMap, NonLocalsBoundInCases, MaybeSuccessExpr)
|
|
),
|
|
|
|
% Generate code for each case.
|
|
list.map_foldl(GenCase, CasesList, ErlCases0, !Info),
|
|
(
|
|
CanFail = can_fail,
|
|
% Add `_ -> fail' default case.
|
|
DefaultCase = elds_case(elds_anon_var, elds_term(elds_fail)),
|
|
ErlCases = ErlCases0 ++ [DefaultCase]
|
|
;
|
|
CanFail = cannot_fail,
|
|
ErlCases = ErlCases0
|
|
),
|
|
|
|
% Create the overall switch statement,.
|
|
CaseExpr = elds_case_expr(expr_from_var(SwitchVar), ErlCases),
|
|
Statement = maybe_join_exprs1(MaybeMakeClosure,
|
|
maybe_join_exprs1(MaybeConvertToAtom, CaseExpr)).
|
|
|
|
:- pred erl_gen_case(mer_type::in,
|
|
code_model::in, instmap::in, set(prog_var)::in,
|
|
maybe(elds_expr)::in, hlds_goal.case::in, elds_case::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_case(Type, CodeModel, InstMap, MustBindNonLocals, MaybeSuccessExpr,
|
|
Case, ELDSCase, !Info) :-
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
expect(unify(OtherConsIds, []), $module, $pred,
|
|
"multi-cons-id switch arms NYI"),
|
|
erl_gen_info_get_module_info(!.Info, ModuleInfo),
|
|
Size = cons_id_size(ModuleInfo, Type, MainConsId),
|
|
erl_gen_info_new_anonymous_vars(Size, DummyVars, !Info),
|
|
( cons_id_to_term(MainConsId, DummyVars, elds_anon_var, Pattern0, !Info) ->
|
|
Pattern = Pattern0
|
|
;
|
|
unexpected($module, $pred, "cannot pattern match on object")
|
|
),
|
|
erl_fix_success_expr(InstMap, Goal, MaybeSuccessExpr,
|
|
MaybeSuccessExprForCase, !Info),
|
|
erl_gen_goal(CodeModel, InstMap, Goal, MaybeSuccessExprForCase, Statement0,
|
|
!Info),
|
|
|
|
% To prevent warnings from the Erlang compiler we must make sure all cases
|
|
% bind the same set of variables. This might not be true if the Mercury
|
|
% compiler knows that a case calls a procedure which throws an exception.
|
|
|
|
erl_bind_unbound_vars(!.Info, MustBindNonLocals, Goal, InstMap,
|
|
Statement0, Statement),
|
|
ELDSCase = elds_case(Pattern, Statement).
|
|
|
|
% cons_id_size(ModuleInfo, Type, ConsId)
|
|
%
|
|
% Returns the size - 1 of the tuple which represents the
|
|
% type, Type, with cons_id, ConsId.
|
|
%
|
|
:- func cons_id_size(module_info, mer_type, cons_id) = int.
|
|
|
|
cons_id_size(ModuleInfo, Type, ConsId) = Size :-
|
|
(
|
|
type_to_ctor_and_args(Type, TypeCtor, _),
|
|
get_cons_defn(ModuleInfo, TypeCtor, ConsId, ConsDefn)
|
|
->
|
|
|
|
% There will be a cell for each existential type variable
|
|
% which isn't mentioned in a typeclass constraint and
|
|
% a cell for each constraint and for each arg.
|
|
|
|
Constraints = ConsDefn ^ cons_constraints,
|
|
constraint_list_get_tvars(Constraints, ConstrainedTVars),
|
|
ExistTVars = ConsDefn ^ cons_exist_tvars,
|
|
UnconstrainedTVars = list.delete_elems(ExistTVars, ConstrainedTVars),
|
|
|
|
Size = list.length(UnconstrainedTVars) + list.length(Constraints) +
|
|
list.length(ConsDefn ^ cons_args)
|
|
;
|
|
Size = 0
|
|
).
|
|
|
|
:- pred erl_gen_case_on_atom(code_model::in, instmap::in, set(prog_var)::in,
|
|
maybe(elds_expr)::in, hlds_goal.case::in, elds_case::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_case_on_atom(CodeModel, InstMap, MustBindNonLocals, MaybeSuccessExpr,
|
|
Case, ELDSCase, !Info) :-
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
expect(unify(OtherConsIds, []), $module, $pred,
|
|
"multi-cons-id switch arms NYI"),
|
|
( MainConsId = string_const(String0) ->
|
|
String = String0
|
|
;
|
|
unexpected($module, $pred, "non-string const")
|
|
),
|
|
erl_fix_success_expr(InstMap, Goal, MaybeSuccessExpr,
|
|
MaybeSuccessExprForCase, !Info),
|
|
erl_gen_goal(CodeModel, InstMap, Goal, MaybeSuccessExprForCase, Statement0,
|
|
!Info),
|
|
|
|
% To prevent warnings from the Erlang compiler we must make sure all cases
|
|
% bind the same set of variables. This might not be true if the Mercury
|
|
% compiler knows that a case calls a procedure which throws an exception.
|
|
|
|
erl_bind_unbound_vars(!.Info, MustBindNonLocals, Goal, InstMap,
|
|
Statement0, Statement),
|
|
ELDSCase = elds_case(elds_atom_raw(String), Statement).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% This code is shared by disjunctions and switches.
|
|
%
|
|
|
|
:- pred union_bound_nonlocals_in_goals(erl_gen_info::in, instmap::in,
|
|
hlds_goals::in, set(prog_var)::out) is det.
|
|
|
|
union_bound_nonlocals_in_goals(Info, InstMap, Goals, NonLocalsUnion) :-
|
|
IsBound = erl_bound_nonlocals_in_goal(Info, InstMap),
|
|
list.map(IsBound, Goals, NonLocalsLists),
|
|
NonLocalsUnion = set.union_list(NonLocalsLists).
|
|
|
|
% If a success expression is too large to duplicate but is required after
|
|
% two or more goals Gs, we generate a closure C containing the success
|
|
% expression which takes the nonlocal variables bound by Gs as arguments.
|
|
% Then we generate the code for Gs such that they call C on success.
|
|
%
|
|
:- pred maybe_create_closure_for_success_expr(set(prog_var)::in,
|
|
maybe(elds_expr)::in, maybe(elds_expr)::out, maybe(elds_expr)::out,
|
|
instmap::in, instmap::out, erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
maybe_create_closure_for_success_expr(NonLocals, MaybeSuccessExpr0,
|
|
MaybeMakeClosure, MaybeSuccessExpr, InstMap0, InstMap, !Info) :-
|
|
(if
|
|
MaybeSuccessExpr0 = yes(SuccessExpr0),
|
|
erl_expr_size(SuccessExpr0) > duplicate_expr_limit
|
|
then
|
|
erl_gen_info_new_named_var("SuccessClosure", ClosureVar, !Info),
|
|
ground_var_in_instmap(ClosureVar, InstMap0, InstMap),
|
|
ClosureVarExpr = expr_from_var(ClosureVar),
|
|
ClosureArgs0 = set.to_sorted_list(NonLocals),
|
|
|
|
% Ignore dummy variables.
|
|
erl_gen_info_get_module_info(!.Info, ModuleInfo),
|
|
erl_variable_types(!.Info, ClosureArgs0, ClosureArgsTypes),
|
|
ClosureArgs = list.filter_map_corresponding(non_dummy_var(ModuleInfo),
|
|
ClosureArgs0, ClosureArgsTypes),
|
|
ClosureArgsTerms = terms_from_vars(ClosureArgs),
|
|
ClosureArgsExprs = exprs_from_vars(ClosureArgs),
|
|
|
|
% ``SuccessClosure = fun(ClosureArgs, ...) -> SuccessExpr0 end''
|
|
MakeClosure = elds_eq(ClosureVarExpr, ClosureFun),
|
|
ClosureFun = elds_fun(elds_clause(ClosureArgsTerms, SuccessExpr0)),
|
|
|
|
% ``SuccessClosure(ClosureArgs, ...)''
|
|
CallClosure = elds_call(elds_call_ho(ClosureVarExpr),
|
|
ClosureArgsExprs),
|
|
|
|
MaybeMakeClosure = yes(MakeClosure),
|
|
MaybeSuccessExpr = yes(CallClosure)
|
|
else
|
|
InstMap = InstMap0,
|
|
MaybeMakeClosure = no,
|
|
MaybeSuccessExpr = MaybeSuccessExpr0
|
|
).
|
|
|
|
:- func non_dummy_var(module_info, prog_var, mer_type) = prog_var is semidet.
|
|
|
|
non_dummy_var(ModuleInfo, Var, Type) = Var :-
|
|
check_dummy_type(ModuleInfo, Type) = is_not_dummy_type.
|
|
|
|
:- pred ground_var_in_instmap(prog_var::in, instmap::in, instmap::out) is det.
|
|
|
|
ground_var_in_instmap(Var, !InstMap) :-
|
|
% Sometimes we introduce variables which aren't in the HLDS, but which need
|
|
% to be in an instmap so that they don't get renamed away (when we
|
|
% duplicate success expressions, we rename away all variables which were
|
|
% not bound before the place where the success expression will be
|
|
% inserted). For our purposes it doesn't matter what insts these variables
|
|
% have, other than not being free, so we just use `ground'.
|
|
instmap_set_var(Var, ground(shared, none), !InstMap).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for if-then-elses.
|
|
%
|
|
|
|
:- pred erl_gen_ite(code_model::in, instmap::in,
|
|
hlds_goal::in, hlds_goal::in, hlds_goal::in,
|
|
prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_ite(CodeModel, InstMap0, Cond, Then, Else, _Context, MaybeSuccessExpr0,
|
|
Statement, !Info) :-
|
|
Cond = hlds_goal(_, CondGoalInfo),
|
|
CondCodeModel = goal_info_get_code_model(CondGoalInfo),
|
|
(
|
|
% model_det Cond:
|
|
% <(Cond -> Then ; Else)>
|
|
% ===>
|
|
% <Cond>,
|
|
% <Then>
|
|
|
|
CondCodeModel = model_det,
|
|
erl_gen_goal(model_det, InstMap0, Cond, no, CondStatement, !Info),
|
|
CondDeterminism = goal_info_get_determinism(CondGoalInfo),
|
|
( CondDeterminism = detism_erroneous ->
|
|
% The `Then' code is unreachable.
|
|
Statement = CondStatement
|
|
;
|
|
update_instmap(Cond, InstMap0, CondInstMap),
|
|
erl_gen_goal(CodeModel, CondInstMap, Then, MaybeSuccessExpr0,
|
|
ThenStatement, !Info),
|
|
Statement = join_exprs(CondStatement, ThenStatement)
|
|
)
|
|
;
|
|
% model_semi cond:
|
|
% <(Cond -> Then ; Else)>
|
|
% ===>
|
|
% case
|
|
% <Cond [[ Outputs ]]>
|
|
% of
|
|
% {Outputs} -> <Then> ;
|
|
% fail -> <Else>
|
|
% end
|
|
%
|
|
% where Outputs is the set of variables bound by Bound. To avoid
|
|
% warnings from the Erlang compiler, we rename the set of output
|
|
% variables in the code generated for Cond itself, so they are only
|
|
% bound in the outer `case' statement.
|
|
%
|
|
|
|
CondCodeModel = model_semi,
|
|
|
|
% Find the non-local variables bound in the condition.
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap0, Cond, CondVars),
|
|
update_instmap(Cond, InstMap0, InstMap0PostCond),
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap0PostCond, Then, ThenVars),
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap0, Else, ElseVars),
|
|
CondVarsList = set.to_sorted_list(CondVars),
|
|
|
|
% Generate the condition goal, making it evaluate to a tuple of the
|
|
% non-local variables that it binds on success.
|
|
CondVarsTerm = elds_tuple(exprs_from_vars(CondVarsList)),
|
|
erl_gen_goal(model_semi, InstMap0, Cond,
|
|
yes(elds_term(CondVarsTerm)), CondStatement0, !Info),
|
|
|
|
% Rename the variables in the generated condition expression.
|
|
erl_create_renaming(CondVarsList, Subn, !Info),
|
|
erl_rename_vars_in_expr(Subn, CondStatement0, CondStatement),
|
|
|
|
% Create a closure for the success expression if it is too large to
|
|
% duplicate into the branches.
|
|
% (InstMap1 = InstMap0 + optionally a variable bound to a closure)
|
|
BoundNonLocals = set.union(ThenVars, ElseVars),
|
|
maybe_create_closure_for_success_expr(BoundNonLocals,
|
|
MaybeSuccessExpr0, MaybeMakeClosure, MaybeSuccessExpr,
|
|
InstMap0, InstMap1, !Info),
|
|
|
|
% Generate the Then and Else branches.
|
|
update_instmap(Cond, InstMap1, InstMap2),
|
|
erl_gen_goal(CodeModel, InstMap2, Then, MaybeSuccessExpr,
|
|
ThenStatement0, !Info),
|
|
erl_gen_goal(CodeModel, InstMap1, Else, MaybeSuccessExpr,
|
|
ElseStatement0, !Info),
|
|
|
|
% Make sure both branches bind the same sets of variables.
|
|
erl_bind_unbound_vars(!.Info, ElseVars, Then, InstMap1,
|
|
ThenStatement0, ThenStatement),
|
|
erl_bind_unbound_vars(!.Info, ThenVars, Else, InstMap0,
|
|
ElseStatement0, ElseStatement),
|
|
|
|
CondDeterminism = goal_info_get_determinism(CondGoalInfo),
|
|
( CondDeterminism = detism_failure ->
|
|
% If the condition cannot succeed then just concatenate the
|
|
% condition and the else branch.
|
|
IfStatement = join_exprs(CondStatement, ElseStatement)
|
|
;
|
|
CaseExpr = elds_case_expr(CondStatement, [TrueCase, FalseCase]),
|
|
TrueCase = elds_case(CondVarsTerm, ThenStatement),
|
|
FalseCase = elds_case(elds_anon_var, ElseStatement),
|
|
maybe_simplify_nested_cases(CaseExpr, IfStatement)
|
|
),
|
|
Statement = maybe_join_exprs1(MaybeMakeClosure, IfStatement)
|
|
;
|
|
CondCodeModel = model_non,
|
|
%
|
|
% model_non cond:
|
|
% <(Cond -> Then ; Else)>
|
|
% ===>
|
|
%
|
|
% let PutAndThen = ``put(Ref, true), <Then && SUCCEED()>''
|
|
%
|
|
% Ref = make_ref(), /* defaults to `undefined' */
|
|
% try
|
|
% <Cond && PutAndThen>
|
|
% case get(Ref) of
|
|
% true -> true ;
|
|
% _ -> <Else>
|
|
% end,
|
|
% after
|
|
% erase(Ref)
|
|
% end
|
|
%
|
|
|
|
erl_gen_info_new_named_var("Ref", Ref, !Info),
|
|
ground_var_in_instmap(Ref, InstMap0, InstMap1),
|
|
|
|
RefExpr = expr_from_var(Ref),
|
|
MakeRef = elds_eq(RefExpr, elds_call_builtin("make_ref", [])),
|
|
PutRef = elds_call_builtin("put", [RefExpr, elds_term(elds_true)]),
|
|
GetRef = elds_call_builtin("get", [RefExpr]),
|
|
EraseRef = elds_call_builtin("erase", [RefExpr]),
|
|
|
|
% Due to the way we generate code for model_non conjunctions, the
|
|
% success expression at this point should not be too large to
|
|
% duplicate.
|
|
|
|
update_instmap(Cond, InstMap1, InstMap2),
|
|
erl_gen_goal(CodeModel, InstMap2, Then, MaybeSuccessExpr0,
|
|
ThenStatement, !Info),
|
|
PutAndThen = join_exprs(PutRef, ThenStatement),
|
|
|
|
erl_gen_goal(CondCodeModel, InstMap1, Cond, yes(PutAndThen),
|
|
CondThen, !Info),
|
|
|
|
erl_gen_goal(CodeModel, InstMap1, Else, MaybeSuccessExpr0,
|
|
ElseStatement, !Info),
|
|
|
|
CaseElse = elds_case_expr(GetRef, [TrueCase, OtherCase]),
|
|
TrueCase = elds_case(elds_true, elds_term(elds_true)),
|
|
OtherCase = elds_case(elds_anon_var, ElseStatement),
|
|
|
|
Statement = join_exprs(MakeRef,
|
|
elds_try(join_exprs(CondThen, CaseElse), [], no, yes(EraseRef)))
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for negation.
|
|
%
|
|
|
|
:- pred erl_gen_negation(hlds_goal::in, code_model::in, instmap::in,
|
|
prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_negation(Cond, CodeModel, InstMap, _Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
Cond = hlds_goal(_, CondGoalInfo),
|
|
CondCodeModel = goal_info_get_code_model(CondGoalInfo),
|
|
(
|
|
% model_det negation:
|
|
% <not(Goal)>
|
|
% ===>
|
|
% <Goal> % must fail
|
|
|
|
CodeModel = model_det,
|
|
|
|
% The code generator expects semidet goals to always have a success
|
|
% expression, although in this case we know it won't arise at run time.
|
|
DummySuccessExpr = elds_term(elds_empty_tuple),
|
|
erl_gen_goal(model_semi, InstMap, Cond, yes(DummySuccessExpr),
|
|
CondStatement, !Info),
|
|
Statement = maybe_join_exprs(CondStatement, MaybeSuccessExpr)
|
|
;
|
|
% model_semi negation, model_det goal:
|
|
% <succeeded = not(Goal)>
|
|
% ===>
|
|
% <do Goal>,
|
|
% fail
|
|
|
|
CodeModel = model_semi, CondCodeModel = model_det,
|
|
erl_gen_goal(model_det, InstMap, Cond, no, CondStatement, !Info),
|
|
Statement = join_exprs(CondStatement, elds_term(elds_fail))
|
|
;
|
|
% model_semi negation, model_semi goal:
|
|
% <succeeded = not(Goal)>
|
|
% ===>
|
|
%
|
|
% case
|
|
% <Goal [[ true ]]>
|
|
% of
|
|
% fail ->
|
|
% <SuccessExpr> ;
|
|
% _ ->
|
|
% fail
|
|
% end
|
|
|
|
CodeModel = model_semi, CondCodeModel = model_semi,
|
|
|
|
OnSuccess = yes(elds_term(elds_true)), % anything other than fail
|
|
erl_gen_goal(model_semi, InstMap, Cond, OnSuccess, CondStatement,
|
|
!Info),
|
|
Statement = elds_case_expr(CondStatement, [FailCase, OtherCase]),
|
|
FailCase = elds_case(elds_fail, expr_or_void(MaybeSuccessExpr)),
|
|
OtherCase = elds_case(elds_anon_var, elds_term(elds_fail))
|
|
;
|
|
CodeModel = model_semi, CondCodeModel = model_non,
|
|
unexpected($module, $pred, "nondet cond")
|
|
;
|
|
CodeModel = model_non,
|
|
unexpected($module, $pred, "nondet negation")
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for conjunctions.
|
|
%
|
|
|
|
:- pred erl_gen_conj(hlds_goals::in, code_model::in, determinism::in,
|
|
instmap::in, prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_conj(Goals, CodeModel, Detism, InstMap, Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
erl_gen_conj_2(Goals, CodeModel, InstMap, Context, MaybeSuccessExpr,
|
|
Statement0, !Info),
|
|
( Detism = detism_erroneous ->
|
|
% This conjunction may be part of a conditional statement, in which
|
|
% this branch binds some variables Vars before throwing an exception.
|
|
% Another, non-erroneous, branch might not bind those Vars, leaving
|
|
% them to be bound after the conditional statement. We rename away the
|
|
% variables bound in this branch so that the Erlang compiler won't
|
|
% complain about variables not being bound in all branches of a
|
|
% conditional statement.
|
|
erl_gen_info_get_module_info(!.Info, ModuleInfo),
|
|
instmap_bound_vars(InstMap, ModuleInfo, BoundVars),
|
|
erl_rename_vars_in_expr_except(BoundVars, Statement0, Statement, !Info)
|
|
;
|
|
Statement = Statement0
|
|
).
|
|
|
|
:- pred erl_gen_conj_2(hlds_goals::in, code_model::in, instmap::in,
|
|
prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_conj_2([], CodeModel, _InstMap0, _Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
(
|
|
CodeModel = model_det,
|
|
Statement = expr_or_void(MaybeSuccessExpr)
|
|
;
|
|
( CodeModel = model_semi
|
|
; CodeModel = model_non
|
|
),
|
|
Statement = det_expr(MaybeSuccessExpr)
|
|
).
|
|
erl_gen_conj_2([SingleGoal], CodeModel, InstMap0, _Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
erl_gen_goal(CodeModel, InstMap0, SingleGoal, MaybeSuccessExpr,
|
|
Statement, !Info).
|
|
erl_gen_conj_2([First | Rest], CodeModel, InstMap0, Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
Rest = [_ | _],
|
|
First = hlds_goal(_, FirstGoalInfo),
|
|
FirstDeterminism = goal_info_get_determinism(FirstGoalInfo),
|
|
( determinism_components(FirstDeterminism, _, at_most_zero) ->
|
|
% the `Rest' code is unreachable
|
|
% There is no success expression in this case.
|
|
erl_gen_goal(CodeModel, InstMap0, First, no, Statement, !Info)
|
|
;
|
|
determinism_to_code_model(FirstDeterminism, FirstCodeModel),
|
|
update_instmap(First, InstMap0, InstMap1),
|
|
(
|
|
FirstCodeModel = model_det,
|
|
%
|
|
% model_det Goal:
|
|
% <Goal, Goals>
|
|
% ===>
|
|
% <do Goal>,
|
|
% <Goals>
|
|
%
|
|
erl_gen_goal(model_det, InstMap0, First, no,
|
|
FirstStatement, !Info),
|
|
erl_gen_conj_2(Rest, CodeModel, InstMap1, Context,
|
|
MaybeSuccessExpr, RestStatement, !Info),
|
|
Statement = join_exprs(FirstStatement, RestStatement)
|
|
;
|
|
FirstCodeModel = model_semi,
|
|
%
|
|
% model_semi Goal:
|
|
% <Goal, Goals>
|
|
% ===>
|
|
% case <Goal> of
|
|
% {Outputs, ...} ->
|
|
% <Goals> ;
|
|
% _ ->
|
|
% fail
|
|
% end
|
|
%
|
|
erl_gen_conj_2(Rest, CodeModel, InstMap1, Context,
|
|
MaybeSuccessExpr, RestStatement, !Info),
|
|
erl_gen_goal(model_semi, InstMap0, First, yes(RestStatement),
|
|
Statement, !Info)
|
|
;
|
|
FirstCodeModel = model_non,
|
|
%
|
|
% model_non Goal:
|
|
% <Goal, Goals>
|
|
% ===>
|
|
% SUCCEED1 = fun(Outputs, ...) ->
|
|
% <Goals && SUCCEED()>
|
|
% end,
|
|
% <Goal && SUCCEED1()>
|
|
%
|
|
|
|
% Generate the code for Rest.
|
|
erl_gen_conj_2(Rest, CodeModel, InstMap1, Context,
|
|
MaybeSuccessExpr, RestStatement, !Info),
|
|
|
|
% Find the variables bound by First.
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap0, First, NonLocalsSet),
|
|
NonLocals = set.to_sorted_list(NonLocalsSet),
|
|
|
|
% Make the success continuation. Rename apart any variables bound
|
|
% by First to avoid warnings about the closure shadowing variables.
|
|
SucceedFunc0 = elds_fun(elds_clause(terms_from_vars(NonLocals),
|
|
RestStatement)),
|
|
erl_create_renaming(NonLocals, Subst, !Info),
|
|
erl_rename_vars_in_expr(Subst, SucceedFunc0, SucceedFunc),
|
|
|
|
% MakeSucceed == "SucceedConj = fun(...) -> ... end "
|
|
% CallSucceed == "SucceedConj(...)"
|
|
erl_gen_info_new_named_var("SucceedConj", SucceedVar, !Info),
|
|
ground_var_in_instmap(SucceedVar, InstMap0, InstMap),
|
|
|
|
SucceedVarExpr = expr_from_var(SucceedVar),
|
|
MakeSucceed = elds_eq(SucceedVarExpr, SucceedFunc),
|
|
CallSucceed = elds_call(elds_call_ho(SucceedVarExpr),
|
|
exprs_from_vars(NonLocals)),
|
|
|
|
% Generate the code for First, such that it calls the success
|
|
% continuation on success.
|
|
erl_gen_goal(model_non, InstMap, First, yes(CallSucceed),
|
|
FirstStatement, !Info),
|
|
|
|
Statement = join_exprs(MakeSucceed, FirstStatement)
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for disjunctions.
|
|
%
|
|
|
|
:- pred erl_gen_disj(hlds_goals::in, code_model::in, instmap::in,
|
|
prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_disj([], CodeModel, _InstMap, _Context, _MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
% Handle empty disjunctions (a.ka. `fail').
|
|
(
|
|
CodeModel = model_det,
|
|
unexpected($module, $pred, "`fail' has determinism `det'")
|
|
;
|
|
( CodeModel = model_semi
|
|
; CodeModel = model_non
|
|
),
|
|
Statement = elds_term(elds_fail)
|
|
).
|
|
|
|
erl_gen_disj([SingleGoal], CodeModel, InstMap, _Context, MaybeSuccessExpr,
|
|
Statement, !Info) :-
|
|
% Handle singleton disjunctions.
|
|
erl_gen_goal(CodeModel, InstMap, SingleGoal, MaybeSuccessExpr,
|
|
Statement, !Info).
|
|
|
|
erl_gen_disj([First | Rest], CodeModel, InstMap0, Context, MaybeSuccessExpr0,
|
|
Statement, !Info) :-
|
|
Rest = [_ | _],
|
|
|
|
% Get the union of all nonlocal variables bound in all disjuncts.
|
|
union_bound_nonlocals_in_goals(!.Info, InstMap0, [First | Rest],
|
|
NonLocalsBoundInGoals),
|
|
|
|
% Create a closure for the success expression if it is too large to
|
|
% duplicate into the disjuncts.
|
|
maybe_create_closure_for_success_expr(NonLocalsBoundInGoals,
|
|
MaybeSuccessExpr0, MaybeMakeClosure, MaybeSuccessExpr,
|
|
InstMap0, InstMap, !Info),
|
|
|
|
erl_gen_disjunct([First | Rest], CodeModel, InstMap, Context,
|
|
MaybeSuccessExpr, DisjStatement, !Info),
|
|
Statement = maybe_join_exprs1(MaybeMakeClosure, DisjStatement).
|
|
|
|
:- pred erl_gen_disjunct(hlds_goals::in, code_model::in, instmap::in,
|
|
prog_context::in, maybe(elds_expr)::in, elds_expr::out,
|
|
erl_gen_info::in, erl_gen_info::out) is det.
|
|
|
|
erl_gen_disjunct([], CodeModel, _InstMap, _Context,
|
|
_MaybeSuccessExpr, Statement, !Info) :-
|
|
% Handle empty disjunctions (a.ka. `fail').
|
|
(
|
|
CodeModel = model_det,
|
|
unexpected($module, $pred, "`fail' has determinism `det'")
|
|
;
|
|
( CodeModel = model_semi
|
|
; CodeModel = model_non
|
|
),
|
|
Statement = elds_term(elds_fail)
|
|
).
|
|
|
|
erl_gen_disjunct([First | Rest], CodeModel, InstMap, Context,
|
|
MaybeSuccessExpr, Statement, !Info) :-
|
|
(
|
|
( CodeModel = model_det
|
|
; CodeModel = model_semi
|
|
),
|
|
|
|
% model_det/model_semi disj:
|
|
%
|
|
% model_det goal:
|
|
% <Goal ; Goals>
|
|
% ===>
|
|
% <Goal>
|
|
% /* <Goals> will never be reached */
|
|
%
|
|
% model_semi goal:
|
|
% <Goal ; Goals>
|
|
% ===>
|
|
% case
|
|
% <Goal [[ {NonLocals, ...} ]]>
|
|
% of
|
|
% {NonLocals, ...} ->
|
|
% <SuccessExpr> ;
|
|
% fail ->
|
|
% <Goals [[ SuccessExpr ]]>
|
|
% end
|
|
%
|
|
% where NonLocals are variables bound by Goal.
|
|
%
|
|
|
|
First = hlds_goal(_, FirstGoalInfo),
|
|
FirstCodeModel = goal_info_get_code_model(FirstGoalInfo),
|
|
FirstDeterminism = goal_info_get_determinism(FirstGoalInfo),
|
|
(
|
|
FirstCodeModel = model_det,
|
|
erl_fix_success_expr(InstMap, First, MaybeSuccessExpr,
|
|
MaybeSuccessExprForFirst, !Info),
|
|
erl_gen_goal(model_det, InstMap, First, MaybeSuccessExprForFirst,
|
|
Statement, !Info)
|
|
;
|
|
FirstCodeModel = model_semi,
|
|
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap, First, FirstVarsSet),
|
|
FirstVars = set.to_sorted_list(FirstVarsSet),
|
|
FirstVarsTerm = elds_tuple(exprs_from_vars(FirstVars)),
|
|
|
|
% Generate code for the first goal, making it return a tuple of the
|
|
% nonlocal variables it binds on success.
|
|
erl_gen_goal(model_semi, InstMap, First,
|
|
yes(elds_term(FirstVarsTerm)), FirstStatement0, !Info),
|
|
|
|
% Generate the rest of the disjunction.
|
|
erl_gen_disjunct(Rest, CodeModel, InstMap, Context,
|
|
MaybeSuccessExpr, RestStatement, !Info),
|
|
|
|
% Need to do some renaming otherwise FirstStatement and
|
|
% RestStatement end up binding the same variables which triggers a
|
|
% (spurious) warning from the Erlang compiler.
|
|
erl_create_renaming(FirstVars, Subn, !Info),
|
|
erl_rename_vars_in_expr(Subn, FirstStatement0, FirstStatement),
|
|
|
|
( FirstDeterminism = detism_failure ->
|
|
% Special case the situation when the first disjunct has
|
|
% determinism `failure'. This can avoid some spurious
|
|
% warnings from the Erlang compiler about "unsafe" variables
|
|
% (it doesn't know that a particular branch of a case
|
|
% statement will always be taken and therefore it doesn't
|
|
% matter that some variables aren't bound in other branches).
|
|
Statement = join_exprs(FirstStatement0, RestStatement)
|
|
;
|
|
erl_fix_success_expr(InstMap, First, MaybeSuccessExpr,
|
|
MaybeSuccessExprForFirst, !Info),
|
|
(
|
|
MaybeSuccessExprForFirst = yes(elds_term(FirstVarsTerm)),
|
|
RestStatement = elds_term(elds_fail)
|
|
->
|
|
% No need to wrap this with a case expression.
|
|
Statement = FirstStatement
|
|
;
|
|
Statement0 = elds_case_expr(FirstStatement,
|
|
[SucceedCase, FailCase]),
|
|
SucceedCase = elds_case(FirstVarsTerm,
|
|
expr_or_void(MaybeSuccessExprForFirst)),
|
|
FailCase = elds_case(elds_fail, RestStatement),
|
|
maybe_simplify_nested_cases(Statement0, Statement)
|
|
)
|
|
)
|
|
;
|
|
FirstCodeModel = model_non,
|
|
% simplify.m should get wrap commits around these.
|
|
unexpected($module, $pred,
|
|
"model_non disj in model_det disjunction")
|
|
)
|
|
;
|
|
CodeModel = model_non,
|
|
|
|
% model_non disj:
|
|
%
|
|
% <(Goal ; Goals) && SUCCEED()>
|
|
% ===>
|
|
% <Goal && SUCCEED()>
|
|
% <Goals && SUCCEED()>
|
|
%
|
|
|
|
% Generate the first disjunct, renaming apart variables bound by it.
|
|
% Otherwise the second and later disjuncts would try to bind the same
|
|
% variables to different values.
|
|
erl_fix_success_expr(InstMap, First, MaybeSuccessExpr,
|
|
MaybeSuccessExprForFirst, !Info),
|
|
erl_gen_goal(model_non, InstMap, First, MaybeSuccessExprForFirst,
|
|
FirstStatement0, !Info),
|
|
|
|
erl_bound_nonlocals_in_goal(!.Info, InstMap, First, FirstVarsSet),
|
|
FirstVars = set.to_sorted_list(FirstVarsSet),
|
|
erl_create_renaming(FirstVars, Subst, !Info),
|
|
erl_rename_vars_in_expr(Subst, FirstStatement0, FirstStatement),
|
|
|
|
% Generate the rest of the disjunction.
|
|
erl_gen_disjunct(Rest, model_non, InstMap, Context, MaybeSuccessExpr,
|
|
RestStatements, !Info),
|
|
|
|
Statement = join_exprs(FirstStatement, RestStatements)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code for generating foreign exported procedures.
|
|
%
|
|
|
|
:- pred erl_gen_foreign_exports(list(elds_defn)::in,
|
|
list(pragma_exported_proc)::in, list(elds_foreign_export_defn)::out)
|
|
is det.
|
|
|
|
erl_gen_foreign_exports(ProcDefns, PragmaExports, ForeignExportDefns) :-
|
|
list.map(erl_gen_foreign_export_defn(ProcDefns), PragmaExports,
|
|
ForeignExportDefns).
|
|
|
|
:- pred erl_gen_foreign_export_defn(list(elds_defn)::in,
|
|
pragma_exported_proc::in, elds_foreign_export_defn::out) is det.
|
|
|
|
erl_gen_foreign_export_defn(ProcDefns, PragmaExport, ForeignExportDefn) :-
|
|
PragmaExport = pragma_exported_proc(_Lang, PredId, ProcId, Name, _Context),
|
|
PredProcId = proc(PredId, ProcId),
|
|
( search_elds_defn(ProcDefns, PredProcId, TargetProc) ->
|
|
TargetProc = elds_defn(_TargetPPId, _TargetVarSet, TargetBody,
|
|
_EnvVarNames),
|
|
Arity = elds_body_arity(TargetBody),
|
|
|
|
% ``Name(Vars, ...) -> PredProcId(Vars, ...)''
|
|
varset.new_vars(Arity, Vars, varset.init, VarSet),
|
|
Clause = elds_clause(terms_from_vars(Vars),
|
|
elds_call(elds_call_plain(PredProcId), exprs_from_vars(Vars))),
|
|
ForeignExportDefn = elds_foreign_export_defn(Name, VarSet, Clause)
|
|
;
|
|
unexpected($module, $pred,
|
|
"missing definition of foreign exported procedure")
|
|
).
|
|
|
|
:- pred search_elds_defn(list(elds_defn)::in, pred_proc_id::in,
|
|
elds_defn::out) is semidet.
|
|
|
|
search_elds_defn([Defn0 | Defns], PredProcId, Defn) :-
|
|
( Defn0 = elds_defn(PredProcId, _, _, _) ->
|
|
Defn = Defn0
|
|
;
|
|
search_elds_defn(Defns, PredProcId, Defn)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module erl_backend.erl_code_gen.
|
|
%-----------------------------------------------------------------------------%
|