mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-18 07:15:19 +00:00
Estimated hours taken: 4 Branches: main compiler/*.m: Use expect/3 in place of require/2 throughout most of the compiler. Use unexpected/2 (or sorry/2) in place of error/1 in more places. Fix more dodgy assertion error messages. s/map(prog_var, mer_type)/vartypes/ where the latter is meant.
475 lines
17 KiB
Mathematica
475 lines
17 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2005 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: ite_gen.m.
|
|
% Main authors: conway, fjh, zs.
|
|
%
|
|
% The predicates of this module generate code for if-then-elses, and for
|
|
% negations (which are cut-down versions of if-then-elses, since not(G)
|
|
% is equivalent to (G -> fail ; true)).
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend__ite_gen.
|
|
:- interface.
|
|
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module ll_backend.code_info.
|
|
:- import_module ll_backend.llds.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred ite_gen__generate_ite(add_trail_ops::in, code_model::in,
|
|
hlds_goal::in, hlds_goal::in, hlds_goal::in, hlds_goal_info::in,
|
|
code_tree::out, code_info::in, code_info::out) is det.
|
|
|
|
:- pred ite_gen__generate_negation(add_trail_ops::in, code_model::in,
|
|
hlds_goal::in, hlds_goal_info::in, code_tree::out,
|
|
code_info::in, code_info::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.builtin_ops.
|
|
:- import_module hlds.goal_form.
|
|
:- import_module hlds.hlds_llds.
|
|
:- import_module hlds.instmap.
|
|
:- import_module libs.compiler_util.
|
|
:- import_module libs.globals.
|
|
:- import_module libs.options.
|
|
:- import_module libs.tree.
|
|
:- import_module ll_backend.code_gen.
|
|
:- import_module ll_backend.trace.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module bool.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module set.
|
|
:- import_module std_util.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
generate_ite(AddTrailOps, CodeModel, CondGoal0, ThenGoal, ElseGoal,
|
|
IteGoalInfo, Code, !CI) :-
|
|
CondGoal0 = CondExpr - CondInfo0,
|
|
goal_info_get_code_model(CondInfo0, CondCodeModel),
|
|
(
|
|
CodeModel = model_non,
|
|
CondCodeModel \= model_non
|
|
->
|
|
EffCodeModel = model_semi
|
|
;
|
|
EffCodeModel = CodeModel
|
|
),
|
|
|
|
goal_info_get_resume_point(CondInfo0, Resume),
|
|
(
|
|
Resume = resume_point(ResumeVarsPrime, ResumeLocsPrime)
|
|
->
|
|
ResumeVars = ResumeVarsPrime,
|
|
ResumeLocs = ResumeLocsPrime,
|
|
% The pre_goal_update sanity check insists on no_resume_point,
|
|
% to make sure that all resume points have been handled by
|
|
% surrounding code.
|
|
goal_info_set_resume_point(no_resume_point, CondInfo0, CondInfo),
|
|
CondGoal = CondExpr - CondInfo
|
|
;
|
|
unexpected(this_file,
|
|
"condition of an if-then-else has no resume point")
|
|
),
|
|
|
|
% Make sure that the variables whose values will be needed on backtracking
|
|
% to the else part are materialized into registers or stack slots.
|
|
% Their locations are recorded in ResumeMap.
|
|
code_info__produce_vars(ResumeVars, ResumeMap, FlushCode, !CI),
|
|
|
|
% Maybe save the heap state current before the condition.
|
|
% This is after code_info__produce_vars since code that
|
|
% flushes the cache may allocate memory we must not "recover".
|
|
code_info__get_globals(!.CI, Globals),
|
|
(
|
|
globals__lookup_bool_option(Globals,
|
|
reclaim_heap_on_semidet_failure, yes),
|
|
goal_may_allocate_heap(CondGoal)
|
|
->
|
|
ReclaimHeap = yes
|
|
;
|
|
ReclaimHeap = no
|
|
),
|
|
code_info__maybe_save_hp(ReclaimHeap, SaveHpCode, MaybeHpSlot, !CI),
|
|
|
|
% Maybe save the current trail state before the condition.
|
|
code_info__maybe_save_ticket(AddTrailOps, SaveTicketCode, MaybeTicketSlot,
|
|
!CI),
|
|
|
|
code_info__remember_position(!.CI, BranchStart),
|
|
|
|
code_info__prepare_for_ite_hijack(EffCodeModel, HijackInfo,
|
|
PrepareHijackCode, !CI),
|
|
|
|
code_info__make_resume_point(ResumeVars, ResumeLocs, ResumeMap,
|
|
ResumePoint, !CI),
|
|
code_info__effect_resume_point(ResumePoint, EffCodeModel, EffectResumeCode,
|
|
!CI),
|
|
|
|
% Generate the condition.
|
|
trace__maybe_generate_internal_event_code(CondGoal, IteGoalInfo,
|
|
CondTraceCode, !CI),
|
|
code_gen__generate_goal(CondCodeModel, CondGoal, CondCode, !CI),
|
|
|
|
code_info__ite_enter_then(HijackInfo, ThenNeckCode, ElseNeckCode, !CI),
|
|
|
|
% Kill again any variables that have become zombies.
|
|
code_info__pickup_zombies(Zombies, !CI),
|
|
code_info__make_vars_forward_dead(Zombies, !CI),
|
|
|
|
% Discard hp and prune trail ticket if the condition succeeded.
|
|
( CondCodeModel = model_non ->
|
|
% We cannot release the stack slots used for the heap pointer
|
|
% and the trail ticket if the condition can be backtracked
|
|
% into. Nor can we prune the trail ticket that we allocated,
|
|
% since the condition may have allocated other trail tickets
|
|
% since then which have not yet been pruned.
|
|
code_info__maybe_reset_ticket(MaybeTicketSlot, solve, ResetTicketCode)
|
|
;
|
|
code_info__maybe_release_hp(MaybeHpSlot, !CI),
|
|
code_info__maybe_reset_prune_and_release_ticket(
|
|
MaybeTicketSlot, commit, ResetTicketCode, !CI)
|
|
),
|
|
|
|
goal_info_get_store_map(IteGoalInfo, StoreMap),
|
|
code_info__get_instmap(!.CI, EndCondInstMap),
|
|
( instmap__is_unreachable(EndCondInstMap) ->
|
|
% If the instmap indicates we cannot reach the then part,
|
|
% do not attempt to generate it (may cause aborts).
|
|
ThenTraceCode = empty,
|
|
ThenCode = empty,
|
|
map__init(EmptyStoreMap),
|
|
code_info__generate_branch_end(EmptyStoreMap, no,
|
|
MaybeEnd0, ThenSaveCode, !CI)
|
|
;
|
|
% Generate the then branch.
|
|
trace__maybe_generate_internal_event_code(ThenGoal,
|
|
IteGoalInfo, ThenTraceCode, !CI),
|
|
code_gen__generate_goal(CodeModel, ThenGoal, ThenCode, !CI),
|
|
code_info__generate_branch_end(StoreMap, no,
|
|
MaybeEnd0, ThenSaveCode, !CI)
|
|
),
|
|
|
|
% Generate the entry to the else branch.
|
|
code_info__reset_to_position(BranchStart, !CI),
|
|
code_info__generate_resume_point(ResumePoint, ResumeCode, !CI),
|
|
|
|
% Restore the heap pointer and solver state if necessary.
|
|
code_info__maybe_restore_and_release_hp(MaybeHpSlot, RestoreHpCode, !CI),
|
|
code_info__maybe_reset_discard_and_release_ticket(MaybeTicketSlot, undo,
|
|
RestoreTicketCode, !CI),
|
|
|
|
% Generate the else branch.
|
|
trace__maybe_generate_internal_event_code(ElseGoal, IteGoalInfo,
|
|
ElseTraceCode, !CI),
|
|
code_gen__generate_goal(CodeModel, ElseGoal, ElseCode, !CI),
|
|
code_info__generate_branch_end(StoreMap, MaybeEnd0, MaybeEnd,
|
|
ElseSaveCode, !CI),
|
|
|
|
code_info__get_next_label(EndLabel, !CI),
|
|
JumpToEndCode = node([
|
|
goto(label(EndLabel)) - "Jump to the end of if-then-else"
|
|
]),
|
|
EndLabelCode = node([
|
|
label(EndLabel) - "end of if-then-else"
|
|
]),
|
|
make_pneg_context_wrappers(Globals, CondInfo, PNegCondCode, PNegThenCode,
|
|
PNegElseCode),
|
|
Code = tree_list([
|
|
FlushCode,
|
|
SaveHpCode,
|
|
SaveTicketCode,
|
|
PrepareHijackCode,
|
|
EffectResumeCode,
|
|
CondTraceCode,
|
|
PNegCondCode,
|
|
CondCode,
|
|
ThenNeckCode,
|
|
ResetTicketCode,
|
|
ThenTraceCode,
|
|
PNegThenCode,
|
|
ThenCode,
|
|
ThenSaveCode,
|
|
JumpToEndCode,
|
|
ResumeCode,
|
|
ElseNeckCode,
|
|
RestoreHpCode,
|
|
RestoreTicketCode,
|
|
ElseTraceCode,
|
|
PNegElseCode,
|
|
ElseCode,
|
|
ElseSaveCode,
|
|
EndLabelCode]),
|
|
code_info__after_all_branches(StoreMap, MaybeEnd, !CI).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
generate_negation(AddTrailOps, CodeModel, Goal0, NotGoalInfo, Code,
|
|
!CI) :-
|
|
( CodeModel = model_non ->
|
|
unexpected(this_file, "generate_negation: nondet negation.")
|
|
;
|
|
true
|
|
),
|
|
|
|
Goal0 = GoalExpr - GoalInfo0,
|
|
goal_info_get_resume_point(GoalInfo0, Resume),
|
|
( Resume = resume_point(ResumeVarsPrime, ResumeLocsPrime) ->
|
|
ResumeVars = ResumeVarsPrime,
|
|
ResumeLocs = ResumeLocsPrime,
|
|
goal_info_set_resume_point(no_resume_point, GoalInfo0, GoalInfo),
|
|
Goal = GoalExpr - GoalInfo
|
|
;
|
|
unexpected(this_file, "generate_negation: " ++
|
|
"negated goal has no resume point.")
|
|
),
|
|
%
|
|
% For a negated simple test, we can generate better code than the general
|
|
% mechanism, because we don't have to flush the cache.
|
|
%
|
|
(
|
|
CodeModel = model_semi,
|
|
GoalExpr = unify(_, _, _, simple_test(L, R), _),
|
|
code_info__failure_is_direct_branch(!.CI, CodeAddr),
|
|
code_info__get_globals(!.CI, Globals),
|
|
globals__lookup_bool_option(Globals, simple_neg, yes)
|
|
->
|
|
% Because we are generating the negated goal ourselves, we need to
|
|
% apply the pre- and post-goal updates that would normally be applied
|
|
% by code_gen__generate_goal.
|
|
|
|
code_info__enter_simple_neg(ResumeVars, GoalInfo, SimpleNeg, !CI),
|
|
code_info__produce_variable(L, CodeL, ValL, !CI),
|
|
code_info__produce_variable(R, CodeR, ValR, !CI),
|
|
Type = code_info__variable_type(!.CI, L),
|
|
( Type = builtin(string) ->
|
|
Op = str_eq
|
|
; Type = builtin(float) ->
|
|
Op = float_eq
|
|
;
|
|
Op = eq
|
|
),
|
|
TestCode = node([
|
|
if_val(binop(Op, ValL, ValR), CodeAddr) - "test inequality"
|
|
]),
|
|
code_info__leave_simple_neg(GoalInfo, SimpleNeg, !CI),
|
|
Code = tree(tree(CodeL, CodeR), TestCode)
|
|
;
|
|
generate_negation_general(AddTrailOps, CodeModel, Goal, NotGoalInfo,
|
|
ResumeVars, ResumeLocs, Code, !CI)
|
|
).
|
|
|
|
% The code of generate_negation_general is a cut-down version
|
|
% of the code for if-then-elses.
|
|
%
|
|
:- pred generate_negation_general(add_trail_ops::in, code_model::in,
|
|
hlds_goal::in, hlds_goal_info::in, set(prog_var)::in, resume_locs::in,
|
|
code_tree::out, code_info::in, code_info::out) is det.
|
|
|
|
generate_negation_general(AddTrailOps, CodeModel, Goal, NotGoalInfo,
|
|
ResumeVars, ResumeLocs, Code, !CI) :-
|
|
|
|
code_info__produce_vars(ResumeVars, ResumeMap, FlushCode, !CI),
|
|
%
|
|
% Maybe save the heap state current before the condition; this ought to be
|
|
% after we make the failure continuation because that causes the cache to
|
|
% get flushed.
|
|
%
|
|
code_info__get_globals(!.CI, Globals),
|
|
(
|
|
globals__lookup_bool_option(Globals,
|
|
reclaim_heap_on_semidet_failure, yes),
|
|
goal_may_allocate_heap(Goal)
|
|
->
|
|
ReclaimHeap = yes
|
|
;
|
|
ReclaimHeap = no
|
|
),
|
|
code_info__maybe_save_hp(ReclaimHeap, SaveHpCode, MaybeHpSlot, !CI),
|
|
|
|
code_info__maybe_save_ticket(AddTrailOps, SaveTicketCode,
|
|
MaybeTicketSlot, !CI),
|
|
|
|
code_info__prepare_for_ite_hijack(CodeModel, HijackInfo,
|
|
PrepareHijackCode, !CI),
|
|
|
|
code_info__make_resume_point(ResumeVars, ResumeLocs, ResumeMap,
|
|
ResumePoint, !CI),
|
|
code_info__effect_resume_point(ResumePoint, CodeModel,
|
|
EffectResumeCode, !CI),
|
|
|
|
% Generate the negated goal as a semi-deterministic goal; it cannot be
|
|
% nondet, since mode correctness requires it to have no output vars.
|
|
trace__maybe_generate_internal_event_code(Goal, NotGoalInfo,
|
|
EnterTraceCode, !CI),
|
|
code_gen__generate_goal(model_semi, Goal, GoalCode, !CI),
|
|
|
|
code_info__ite_enter_then(HijackInfo, ThenNeckCode, ElseNeckCode, !CI),
|
|
|
|
% Kill again any variables that have become zombies.
|
|
code_info__pickup_zombies(Zombies, !CI),
|
|
code_info__make_vars_forward_dead(Zombies, !CI),
|
|
|
|
code_info__get_forward_live_vars(!.CI, LiveVars),
|
|
|
|
( CodeModel = model_det ->
|
|
% The then branch will never be reached.
|
|
PruneTicketCode = empty,
|
|
FailTraceCode = empty,
|
|
FailCode = empty
|
|
;
|
|
code_info__remember_position(!.CI, AfterNegatedGoal),
|
|
% The call to reset_ticket(..., commit) here is necessary
|
|
% in order to properly detect floundering.
|
|
code_info__maybe_release_hp(MaybeHpSlot, !CI),
|
|
code_info__maybe_reset_prune_and_release_ticket(MaybeTicketSlot,
|
|
commit, PruneTicketCode, !CI),
|
|
trace__maybe_generate_negated_event_code(Goal, NotGoalInfo,
|
|
neg_failure, FailTraceCode, !CI),
|
|
code_info__generate_failure(FailCode, !CI),
|
|
% We want liveness after not(G) to be the same as after G.
|
|
% Information about what variables are where will be set
|
|
% by code_info__generate_resume_point.
|
|
code_info__reset_to_position(AfterNegatedGoal, !CI)
|
|
),
|
|
|
|
% Generate the entry to the else branch.
|
|
code_info__generate_resume_point(ResumePoint, ResumeCode, !CI),
|
|
|
|
code_info__set_forward_live_vars(LiveVars, !CI),
|
|
|
|
% Restore the heap pointer and solver state if necessary.
|
|
code_info__maybe_restore_and_release_hp(MaybeHpSlot, RestoreHpCode, !CI),
|
|
code_info__maybe_reset_discard_and_release_ticket(MaybeTicketSlot, undo,
|
|
RestoreTicketCode, !CI),
|
|
trace__maybe_generate_negated_event_code(Goal, NotGoalInfo,
|
|
neg_success, SuccessTraceCode, !CI),
|
|
|
|
make_pneg_context_wrappers(Globals, NotGoalInfo, PNegCondCode,
|
|
PNegThenCode, PNegElseCode),
|
|
Code = tree_list([
|
|
FlushCode,
|
|
PrepareHijackCode,
|
|
EffectResumeCode,
|
|
SaveHpCode,
|
|
SaveTicketCode,
|
|
EnterTraceCode,
|
|
PNegCondCode,
|
|
GoalCode,
|
|
ThenNeckCode,
|
|
PruneTicketCode,
|
|
FailTraceCode,
|
|
PNegThenCode,
|
|
FailCode,
|
|
ResumeCode,
|
|
ElseNeckCode,
|
|
RestoreTicketCode,
|
|
RestoreHpCode,
|
|
SuccessTraceCode,
|
|
PNegElseCode]).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% If the code in the condition depends on a consumer of a generator
|
|
% that is not complete by the time we finish executing the condition,
|
|
% then failure out of the condition does not necessarily mean that
|
|
% the condition has no solution; it may mean simply that the condition's
|
|
% solution depends on a generator solution that hasn't been produced yet
|
|
% and thus hasn't been given to the consumer yet.
|
|
%
|
|
% Detecting such situations requires knowing whether tabled subgoals
|
|
% (both generators and consumers) are started inside possibly negated
|
|
% contexts or not, which is why we wrap the condition inside
|
|
% MR_pneg_enter_{cond,then,exit}.
|
|
%
|
|
:- pred make_pneg_context_wrappers(globals::in, hlds_goal_info::in,
|
|
code_tree::out, code_tree::out, code_tree::out) is det.
|
|
|
|
make_pneg_context_wrappers(Globals, GoalInfo, PNegCondCode, PNegThenCode,
|
|
PNegElseCode) :-
|
|
globals__lookup_bool_option(Globals, use_minimal_model_stack_copy_pneg,
|
|
UseMinimalModelStackCopyPNeg),
|
|
(
|
|
UseMinimalModelStackCopyPNeg = yes,
|
|
goal_info_get_context(GoalInfo, Context),
|
|
term__context_file(Context, File),
|
|
term__context_line(Context, Line),
|
|
(
|
|
File \= "",
|
|
Line > 0
|
|
->
|
|
CtxtStr = "\"" ++ File ++ ":" ++ int_to_string(Line) ++ "\""
|
|
;
|
|
CtxtStr = "NULL"
|
|
),
|
|
|
|
PNegCondComponents = [
|
|
pragma_c_raw_code(
|
|
wrap_transient("\t\tMR_pneg_enter_cond();\n"),
|
|
cannot_branch_away, live_lvals_info(set__init))
|
|
],
|
|
PNegThenComponents = [
|
|
pragma_c_raw_code(
|
|
wrap_transient("\t\tMR_pneg_enter_then();\n"),
|
|
cannot_branch_away, live_lvals_info(set__init))
|
|
],
|
|
PNegElseComponents = [
|
|
pragma_c_raw_code(
|
|
wrap_transient("\t\tMR_pneg_enter_else(" ++ CtxtStr ++ ");\n"),
|
|
cannot_branch_away, live_lvals_info(set__init))
|
|
],
|
|
PNegCondCode = node([
|
|
pragma_c([], PNegCondComponents, will_not_call_mercury,
|
|
no, no, no, no, yes, yes) - ""
|
|
]),
|
|
PNegThenCode = node([
|
|
pragma_c([], PNegThenComponents, will_not_call_mercury,
|
|
no, no, no, no, yes, yes) - ""
|
|
]),
|
|
PNegElseCode = node([
|
|
pragma_c([], PNegElseComponents, will_not_call_mercury,
|
|
no, no, no, no, yes, yes) - ""
|
|
])
|
|
;
|
|
UseMinimalModelStackCopyPNeg = no,
|
|
PNegCondCode = empty,
|
|
PNegThenCode = empty,
|
|
PNegElseCode = empty
|
|
).
|
|
|
|
:- func wrap_transient(string) = string.
|
|
|
|
wrap_transient(Code) =
|
|
string__append_list([
|
|
"\t\tMR_save_transient_registers();\n",
|
|
Code,
|
|
"\t\tMR_restore_transient_registers();\n"]).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func this_file = string.
|
|
|
|
this_file = "ite_gen".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module ite_gen.
|
|
%-----------------------------------------------------------------------------%
|