mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 05:12:33 +00:00
Estimated hours taken: 6 Eliminated a lot of the dependencies on the the `code_model' type, and move that type from llds.m into a new module `code_model'. The aim of this change is to improve the modularity of the compiler by reducing the number of places in the compiler front-end that depend on back-end concepts and the number of places in the MLDS back-end which depend on the LLDS. compiler/code_model.m: New module. Contains the code_model type and associated procedures. compiler/llds.m: Move the code_model type into code_model.m. compiler/hlds_goal.m: Move the goal_info_get_code_model procedure into code_model.m, to avoid having the HLDS modules import code_model. compiler/hlds_out.m: Delete `hlds_out__write_code_model', since it wasn't being used. compiler/hlds_pred.m: Move the proc_info_interface_code_model procedure into code_model.m, to avoid having the HLDS modules import code_model. compiler/goal_path.m: When computing the `maybe_cut' field for `some' goals, compute it by comparing the determinism rather than by comparing the goal_infos. compiler/unique_modes.m: Use determinism and test for soln_count = at_most_many rather than using code_model and testing for model_non. compiler/inlining.m: Test for determinism nondet/multi rather than testing for code_model model_non. compiler/hlds_pred.m: compiler/det_report.m: Change valid_code_model_for_eval_method, which succeeded unless the eval_method was minimal_model and the code_model was model_det, to valid_determinism_for_eval_method, which succeeds unless the eval_method is minimal_model and the determinism cannot fail. As well as avoiding a dependency on code_model in the HLDS modules, this also fixes a bug where det_report could give misleading error messages, saying that `multi' was a valid determinism for `minimal_model' predicates, when in fact the compiler will always report a determinism error if you declare a `minimal_model' predicate with determinism `multi'. (Actually the code in which this bug occurs is in fact unreachable, but this is no doubt also a bug... I'll address that one in a separate change.) compiler/lookup_switch.m: Simplify the code a bit by using globals__lookup_*_option rather than globals__get_option and then getopt__lookup_option. compiler/*.m: Add `import_module' declarations for `code_model', and in some cases remove `import_module' declarations for `llds'.
361 lines
11 KiB
Mathematica
361 lines
11 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2000 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 ite_gen.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds_goal, code_model, llds, code_info.
|
|
|
|
:- pred ite_gen__generate_ite(code_model::in, hlds_goal::in, hlds_goal::in,
|
|
hlds_goal::in, store_map::in, code_tree::out,
|
|
code_info::in, code_info::out) is det.
|
|
|
|
:- pred ite_gen__generate_negation(code_model::in, hlds_goal::in,
|
|
code_tree::out, code_info::in, code_info::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module prog_data, tree, builtin_ops.
|
|
:- import_module code_gen, code_util, trace, options, globals, instmap.
|
|
|
|
:- import_module bool, set, term, list, map, std_util, require.
|
|
|
|
ite_gen__generate_ite(CodeModel, CondGoal0, ThenGoal, ElseGoal, StoreMap, Code)
|
|
-->
|
|
{ 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(CondInfo0, no_resume_point,
|
|
CondInfo),
|
|
CondGoal = CondExpr - CondInfo
|
|
;
|
|
error("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),
|
|
|
|
% 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(Globals),
|
|
{
|
|
globals__lookup_bool_option(Globals,
|
|
reclaim_heap_on_semidet_failure, yes),
|
|
code_util__goal_may_allocate_heap(CondGoal)
|
|
->
|
|
ReclaimHeap = yes
|
|
;
|
|
ReclaimHeap = no
|
|
},
|
|
code_info__maybe_save_hp(ReclaimHeap, SaveHpCode, MaybeHpSlot),
|
|
|
|
% Maybe save the current trail state before the condition
|
|
{ globals__lookup_bool_option(Globals, use_trail, UseTrail) },
|
|
code_info__maybe_save_ticket(UseTrail, SaveTicketCode,
|
|
MaybeTicketSlot),
|
|
|
|
code_info__remember_position(BranchStart),
|
|
|
|
code_info__prepare_for_ite_hijack(EffCodeModel, HijackInfo,
|
|
PrepareHijackCode),
|
|
|
|
code_info__make_resume_point(ResumeVars, ResumeLocs, ResumeMap,
|
|
ResumePoint),
|
|
code_info__effect_resume_point(ResumePoint, EffCodeModel,
|
|
EffectResumeCode),
|
|
|
|
% Generate the condition
|
|
trace__maybe_generate_internal_event_code(CondGoal, CondTraceCode),
|
|
code_gen__generate_goal(CondCodeModel, CondGoal, CondCode),
|
|
|
|
code_info__ite_enter_then(HijackInfo, ThenNeckCode, ElseNeckCode),
|
|
|
|
% Kill again any variables that have become zombies
|
|
code_info__pickup_zombies(Zombies),
|
|
code_info__make_vars_forward_dead(Zombies),
|
|
|
|
% 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),
|
|
code_info__maybe_reset_prune_and_release_ticket(
|
|
MaybeTicketSlot, commit, ResetTicketCode)
|
|
),
|
|
|
|
code_info__get_instmap(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)
|
|
;
|
|
% Generate the then branch
|
|
trace__maybe_generate_internal_event_code(ThenGoal,
|
|
ThenTraceCode),
|
|
code_gen__generate_goal(CodeModel, ThenGoal, ThenCode),
|
|
code_info__generate_branch_end(StoreMap, no,
|
|
MaybeEnd0, ThenSaveCode)
|
|
),
|
|
|
|
% Generate the entry to the else branch
|
|
code_info__reset_to_position(BranchStart),
|
|
code_info__generate_resume_point(ResumePoint, ResumeCode),
|
|
|
|
% Restore the heap pointer and solver state if necessary.
|
|
code_info__maybe_restore_and_release_hp(MaybeHpSlot, RestoreHpCode),
|
|
code_info__maybe_reset_discard_and_release_ticket(
|
|
MaybeTicketSlot, undo, RestoreTicketCode),
|
|
|
|
% Generate the else branch
|
|
trace__maybe_generate_internal_event_code(ElseGoal, ElseTraceCode),
|
|
code_gen__generate_goal(CodeModel, ElseGoal, ElseCode),
|
|
code_info__generate_branch_end(StoreMap, MaybeEnd0, MaybeEnd,
|
|
ElseSaveCode),
|
|
|
|
code_info__get_next_label(EndLabel),
|
|
{ JumpToEndCode = node([
|
|
goto(label(EndLabel))
|
|
- "Jump to the end of if-then-else"
|
|
]) },
|
|
{ EndLabelCode = node([
|
|
label(EndLabel)
|
|
- "end of if-then-else"
|
|
]) },
|
|
{ Code =
|
|
tree(FlushCode,
|
|
tree(SaveHpCode,
|
|
tree(SaveTicketCode,
|
|
tree(PrepareHijackCode,
|
|
tree(EffectResumeCode,
|
|
tree(CondTraceCode,
|
|
tree(CondCode,
|
|
tree(ThenNeckCode,
|
|
tree(ResetTicketCode,
|
|
tree(ThenTraceCode,
|
|
tree(ThenCode,
|
|
tree(ThenSaveCode,
|
|
tree(JumpToEndCode,
|
|
tree(ResumeCode,
|
|
tree(ElseNeckCode,
|
|
tree(RestoreHpCode,
|
|
tree(RestoreTicketCode,
|
|
tree(ElseTraceCode,
|
|
tree(ElseCode,
|
|
tree(ElseSaveCode,
|
|
EndLabelCode))))))))))))))))))))
|
|
},
|
|
code_info__after_all_branches(StoreMap, MaybeEnd).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
ite_gen__generate_negation(CodeModel, Goal0, Code) -->
|
|
{ CodeModel = model_non ->
|
|
error("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(GoalInfo0, no_resume_point,
|
|
GoalInfo),
|
|
Goal = GoalExpr - GoalInfo
|
|
;
|
|
error("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(CodeAddr),
|
|
code_info__get_globals(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),
|
|
code_info__produce_variable(L, CodeL, ValL),
|
|
code_info__produce_variable(R, CodeR, ValR),
|
|
code_info__variable_type(L, Type),
|
|
{ Type = term__functor(term__atom("string"), [], _) ->
|
|
Op = str_eq
|
|
; Type = term__functor(term__atom("float"), [], _) ->
|
|
Op = float_eq
|
|
;
|
|
Op = eq
|
|
},
|
|
{ TestCode = node([
|
|
if_val(binop(Op, ValL, ValR), CodeAddr) -
|
|
"test inequality"
|
|
]) },
|
|
code_info__leave_simple_neg(GoalInfo, SimpleNeg),
|
|
{ Code = tree(tree(CodeL, CodeR), TestCode) }
|
|
;
|
|
generate_negation_general(CodeModel, Goal,
|
|
ResumeVars, ResumeLocs, Code)
|
|
).
|
|
|
|
% The code of generate_negation_general is a cut-down version
|
|
% of the code for if-then-elses.
|
|
|
|
:- pred generate_negation_general(code_model::in, hlds_goal::in,
|
|
set(prog_var)::in, resume_locs::in, code_tree::out,
|
|
code_info::in, code_info::out) is det.
|
|
|
|
generate_negation_general(CodeModel, Goal, ResumeVars, ResumeLocs, Code) -->
|
|
|
|
code_info__produce_vars(ResumeVars, ResumeMap, FlushCode),
|
|
|
|
% 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(Globals),
|
|
{
|
|
globals__lookup_bool_option(Globals,
|
|
reclaim_heap_on_semidet_failure, yes),
|
|
code_util__goal_may_allocate_heap(Goal)
|
|
->
|
|
ReclaimHeap = yes
|
|
;
|
|
ReclaimHeap = no
|
|
},
|
|
code_info__maybe_save_hp(ReclaimHeap, SaveHpCode, MaybeHpSlot),
|
|
|
|
{ globals__lookup_bool_option(Globals, use_trail, UseTrail) },
|
|
code_info__maybe_save_ticket(UseTrail, SaveTicketCode,
|
|
MaybeTicketSlot),
|
|
|
|
code_info__prepare_for_ite_hijack(CodeModel, HijackInfo,
|
|
PrepareHijackCode),
|
|
|
|
code_info__make_resume_point(ResumeVars, ResumeLocs, ResumeMap,
|
|
ResumePoint),
|
|
code_info__effect_resume_point(ResumePoint, CodeModel,
|
|
EffectResumeCode),
|
|
|
|
% 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, EnterTraceCode),
|
|
code_gen__generate_goal(model_semi, Goal, GoalCode),
|
|
|
|
code_info__ite_enter_then(HijackInfo, ThenNeckCode, ElseNeckCode),
|
|
|
|
% Kill again any variables that have become zombies
|
|
code_info__pickup_zombies(Zombies),
|
|
code_info__make_vars_forward_dead(Zombies),
|
|
|
|
code_info__get_forward_live_vars(LiveVars),
|
|
|
|
( { CodeModel = model_det } ->
|
|
% the then branch will never be reached
|
|
{ PruneTicketCode = empty },
|
|
{ FailTraceCode = empty },
|
|
{ FailCode = empty }
|
|
;
|
|
code_info__remember_position(AfterNegatedGoal),
|
|
% The call to reset_ticket(..., commit) here is necessary
|
|
% in order to properly detect floundering.
|
|
code_info__maybe_release_hp(MaybeHpSlot),
|
|
code_info__maybe_reset_prune_and_release_ticket(
|
|
MaybeTicketSlot, commit, PruneTicketCode),
|
|
trace__maybe_generate_negated_event_code(Goal, neg_failure,
|
|
FailTraceCode),
|
|
code_info__generate_failure(FailCode),
|
|
% 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)
|
|
),
|
|
|
|
% Generate the entry to the else branch
|
|
code_info__generate_resume_point(ResumePoint, ResumeCode),
|
|
|
|
code_info__set_forward_live_vars(LiveVars),
|
|
|
|
% Restore the heap pointer and solver state if necessary.
|
|
code_info__maybe_restore_and_release_hp(MaybeHpSlot, RestoreHpCode),
|
|
code_info__maybe_reset_discard_and_release_ticket(
|
|
MaybeTicketSlot, undo, RestoreTicketCode),
|
|
trace__maybe_generate_negated_event_code(Goal, neg_success,
|
|
SuccessTraceCode),
|
|
|
|
{ Code =
|
|
tree(FlushCode,
|
|
tree(PrepareHijackCode,
|
|
tree(EffectResumeCode,
|
|
tree(SaveHpCode,
|
|
tree(SaveTicketCode,
|
|
tree(EnterTraceCode,
|
|
tree(GoalCode,
|
|
tree(ThenNeckCode,
|
|
tree(PruneTicketCode,
|
|
tree(FailTraceCode,
|
|
tree(FailCode,
|
|
tree(ResumeCode,
|
|
tree(ElseNeckCode,
|
|
tree(RestoreTicketCode,
|
|
tree(RestoreHpCode,
|
|
SuccessTraceCode)))))))))))))))
|
|
}.
|
|
|
|
%---------------------------------------------------------------------------%
|