mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-16 06:14:59 +00:00
Estimated hours taken: 2 Branches: main Define the `heap_pointer' type in private_builtin.m as a new builtin type with representation MR_TYPECTOR_REP_HP, rather than as equivalent to `c_pointer'. This is needed so that the accurate garbage collector can tell saved heap pointer values apart from other c_pointer values, which it needs to do in order to handle saved heap pointer values. library/private_builtin.m: runtime/mercury.h: runtime/mercury.c: Define the type_ctor_info etc. for the heap_pointer type. compiler/type_util.m: Add a new function `heap_pointer_type'. compiler/add_heap_ops.m: Use `heap_pointer_type' from type_util.m.
354 lines
12 KiB
Mathematica
354 lines
12 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2000-2001 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.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Author: fjh.
|
|
%
|
|
% This module is an HLDS-to-HLDS transformation that inserts code to
|
|
% handle heap reclamation on backtracking, by saving and restoring
|
|
% the values of the heap pointer.
|
|
% The transformation involves adding calls to impure
|
|
% predicates defined in library/private_builtin.m, which in turn call
|
|
% the MR_mark_hp() and MR_restore_hp() macros defined in
|
|
% runtime/mercury_heap.h.
|
|
%
|
|
% This pass is currently only used for the MLDS back-end.
|
|
% For some reason (perhaps efficiency?? or more likely just historical?),
|
|
% the LLDS back-end inserts the heap operations as it is generating
|
|
% LLDS code, rather than via an HLDS to HLDS transformation.
|
|
%
|
|
% This module is very similar to add_trail_ops.m.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% XXX check goal_infos for correctness
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module add_heap_ops.
|
|
:- interface.
|
|
:- import_module hlds_pred, hlds_module.
|
|
|
|
:- pred add_heap_ops(proc_info::in, module_info::in, proc_info::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module prog_data, prog_util, (inst).
|
|
:- import_module hlds_goal, hlds_data.
|
|
:- import_module goal_util, quantification, modules, type_util.
|
|
:- import_module instmap, code_model, code_util.
|
|
|
|
:- import_module bool, string.
|
|
:- import_module assoc_list, list, map, set, varset, std_util, require, term.
|
|
|
|
|
|
%
|
|
% As we traverse the goal, we add new variables to hold the
|
|
% saved values of the heap pointer.
|
|
% So we need to thread a varset and a vartypes mapping through,
|
|
% to record the names and types of the new variables.
|
|
%
|
|
% We also keep the module_info around, so that we can use
|
|
% the predicate table that it contains to lookup the pred_ids
|
|
% for the builtin procedures that we insert calls to.
|
|
% We do not update the module_info as we're traversing the goal.
|
|
%
|
|
|
|
:- type heap_ops_info --->
|
|
heap_ops_info(
|
|
varset :: prog_varset,
|
|
var_types :: vartypes,
|
|
module_info :: module_info
|
|
).
|
|
|
|
add_heap_ops(Proc0, ModuleInfo0, Proc) :-
|
|
proc_info_goal(Proc0, Goal0),
|
|
proc_info_varset(Proc0, VarSet0),
|
|
proc_info_vartypes(Proc0, VarTypes0),
|
|
TrailOpsInfo0 = heap_ops_info(VarSet0, VarTypes0, ModuleInfo0),
|
|
goal_add_heap_ops(Goal0, Goal, TrailOpsInfo0, TrailOpsInfo),
|
|
TrailOpsInfo = heap_ops_info(VarSet, VarTypes, _),
|
|
proc_info_set_goal(Proc0, Goal, Proc1),
|
|
proc_info_set_varset(Proc1, VarSet, Proc2),
|
|
proc_info_set_vartypes(Proc2, VarTypes, Proc3),
|
|
% The code below does not maintain the non-local variables,
|
|
% so we need to requantify.
|
|
% XXX it would be more efficient to maintain them
|
|
% rather than recomputing them every time.
|
|
requantify_proc(Proc3, Proc).
|
|
|
|
:- pred goal_add_heap_ops(hlds_goal::in, hlds_goal::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
goal_add_heap_ops(GoalExpr0 - GoalInfo, Goal) -->
|
|
goal_expr_add_heap_ops(GoalExpr0, GoalInfo, Goal).
|
|
|
|
:- pred goal_expr_add_heap_ops(hlds_goal_expr::in, hlds_goal_info::in,
|
|
hlds_goal::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
goal_expr_add_heap_ops(conj(Goals0), GI, conj(Goals) - GI) -->
|
|
conj_add_heap_ops(Goals0, Goals).
|
|
|
|
goal_expr_add_heap_ops(par_conj(Goals0, SM), GI, par_conj(Goals, SM) - GI) -->
|
|
conj_add_heap_ops(Goals0, Goals).
|
|
|
|
goal_expr_add_heap_ops(disj([], B), GI, disj([], B) - GI) --> [].
|
|
|
|
goal_expr_add_heap_ops(disj(Goals0, B), GoalInfo, Goal - GoalInfo) -->
|
|
{ Goals0 = [FirstDisjunct | _] },
|
|
|
|
{ goal_info_get_context(GoalInfo, Context) },
|
|
{ goal_info_get_code_model(GoalInfo, CodeModel) },
|
|
|
|
%
|
|
% If necessary, save the heap pointer so that we can
|
|
% restore it on back-tracking.
|
|
% We don't need to do this here if it is a model_det or model_semi
|
|
% disjunction and the first disjunct won't allocate any heap --
|
|
% in that case, we delay saving the heap pointer until just before
|
|
% the first disjunct that might allocate heap.
|
|
%
|
|
(
|
|
{ CodeModel = model_non
|
|
; code_util__goal_may_allocate_heap(FirstDisjunct)
|
|
}
|
|
->
|
|
new_saved_hp_var(SavedHeapPointerVar),
|
|
gen_mark_hp(SavedHeapPointerVar, Context, MarkHeapPointerGoal),
|
|
disj_add_heap_ops(Goals0, yes, yes(SavedHeapPointerVar),
|
|
GoalInfo, Goals),
|
|
{ Goal = conj([MarkHeapPointerGoal, disj(Goals, B) -
|
|
GoalInfo]) }
|
|
;
|
|
disj_add_heap_ops(Goals0, yes, no, GoalInfo, Goals),
|
|
{ Goal = disj(Goals, B) }
|
|
).
|
|
|
|
goal_expr_add_heap_ops(switch(A, B, Cases0, D), GI,
|
|
switch(A, B, Cases, D) - GI) -->
|
|
cases_add_heap_ops(Cases0, Cases).
|
|
|
|
goal_expr_add_heap_ops(not(InnerGoal), OuterGoalInfo, Goal) -->
|
|
%
|
|
% We handle negations by converting them into if-then-elses:
|
|
% not(G) ===> (if G then fail else true)
|
|
%
|
|
{ goal_info_get_context(OuterGoalInfo, Context) },
|
|
{ InnerGoal = _ - InnerGoalInfo },
|
|
{ goal_info_get_determinism(InnerGoalInfo, Determinism) },
|
|
{ determinism_components(Determinism, _CanFail, NumSolns) },
|
|
{ true_goal(Context, True) },
|
|
{ fail_goal(Context, Fail) },
|
|
{ map__init(SM) },
|
|
ModuleInfo =^ module_info,
|
|
{ NumSolns = at_most_zero ->
|
|
% The "then" part of the if-then-else will be unreachable,
|
|
% but to preserve the invariants that the MLDS back-end
|
|
% relies on, we need to make sure that it can't fail.
|
|
% So we use a call to `private_builtin__unused' (which
|
|
% will call error/1) rather than `fail' for the "then" part.
|
|
generate_call("unused", [], det, no, [], ModuleInfo, Context,
|
|
ThenGoal)
|
|
;
|
|
ThenGoal = Fail
|
|
},
|
|
{ NewOuterGoal = if_then_else([], InnerGoal, ThenGoal, True, SM) },
|
|
goal_expr_add_heap_ops(NewOuterGoal, OuterGoalInfo, Goal).
|
|
|
|
goal_expr_add_heap_ops(some(A, B, Goal0), GoalInfo,
|
|
some(A, B, Goal) - GoalInfo) -->
|
|
goal_add_heap_ops(Goal0, Goal).
|
|
|
|
goal_expr_add_heap_ops(if_then_else(A, Cond0, Then0, Else0, E), GoalInfo,
|
|
Goal - GoalInfo) -->
|
|
goal_add_heap_ops(Cond0, Cond),
|
|
goal_add_heap_ops(Then0, Then),
|
|
goal_add_heap_ops(Else0, Else1),
|
|
%
|
|
% If the condition can allocate heap space,
|
|
% save the heap pointer so that we can
|
|
% restore it if the condition fails.
|
|
%
|
|
( { code_util__goal_may_allocate_heap(Cond0) } ->
|
|
new_saved_hp_var(SavedHeapPointerVar),
|
|
{ goal_info_get_context(GoalInfo, Context) },
|
|
gen_mark_hp(SavedHeapPointerVar, Context, MarkHeapPointerGoal),
|
|
%
|
|
% Generate code to restore the heap pointer,
|
|
% and insert that code at the start of the Else branch.
|
|
%
|
|
gen_restore_hp(SavedHeapPointerVar, Context,
|
|
RestoreHeapPointerGoal),
|
|
{ Else1 = _ - Else1GoalInfo },
|
|
{ Else = conj([RestoreHeapPointerGoal, Else1]) -
|
|
Else1GoalInfo },
|
|
{ IfThenElse = if_then_else(A, Cond, Then, Else, E) -
|
|
GoalInfo },
|
|
{ Goal = conj([MarkHeapPointerGoal, IfThenElse]) }
|
|
;
|
|
{ Goal = if_then_else(A, Cond, Then, Else1, E) }
|
|
).
|
|
|
|
|
|
goal_expr_add_heap_ops(call(A,B,C,D,E,F), GI, call(A,B,C,D,E,F) - GI) --> [].
|
|
|
|
goal_expr_add_heap_ops(generic_call(A,B,C,D), GI, generic_call(A,B,C,D) - GI)
|
|
--> [].
|
|
|
|
goal_expr_add_heap_ops(unify(A,B,C,D,E), GI, unify(A,B,C,D,E) - GI) --> [].
|
|
|
|
goal_expr_add_heap_ops(PragmaForeign, GoalInfo, Goal) -->
|
|
{ PragmaForeign = foreign_proc(_,_,_,_,_,_,Impl) },
|
|
( { Impl = nondet(_,_,_,_,_,_,_,_,_) } ->
|
|
% XXX Implementing heap reclamation for nondet pragma
|
|
% foreign_code via transformation is difficult,
|
|
% because there's nowhere in the HLDS pragma_foreign_code
|
|
% goal where we can insert the heap reclamation operations.
|
|
% For now, we don't support this.
|
|
% Instead, we just generate a call to a procedure which
|
|
% will at runtime call error/1 with an appropriate
|
|
% "Sorry, not implemented" error message.
|
|
ModuleInfo =^ module_info,
|
|
{ goal_info_get_context(GoalInfo, Context) },
|
|
{ generate_call("reclaim_heap_nondet_pragma_foreign_code",
|
|
[], erroneous, no, [], ModuleInfo, Context,
|
|
SorryNotImplementedCode) },
|
|
{ Goal = SorryNotImplementedCode }
|
|
;
|
|
{ Goal = PragmaForeign - GoalInfo }
|
|
).
|
|
|
|
goal_expr_add_heap_ops(shorthand(_), _, _) -->
|
|
% these should have been expanded out by now
|
|
{ error("goal_expr_add_heap_ops: unexpected shorthand") }.
|
|
|
|
:- pred conj_add_heap_ops(hlds_goals::in, hlds_goals::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
conj_add_heap_ops(Goals0, Goals) -->
|
|
list__map_foldl(goal_add_heap_ops, Goals0, Goals).
|
|
|
|
:- pred disj_add_heap_ops(hlds_goals::in, bool::in, maybe(prog_var)::in,
|
|
hlds_goal_info::in, hlds_goals::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
disj_add_heap_ops([], _, _, _, []) --> [].
|
|
disj_add_heap_ops([Goal0 | Goals0], IsFirstBranch, MaybeSavedHeapPointerVar,
|
|
DisjGoalInfo, DisjGoals) -->
|
|
goal_add_heap_ops(Goal0, Goal1),
|
|
{ Goal1 = _ - GoalInfo },
|
|
{ goal_info_get_context(GoalInfo, Context) },
|
|
%
|
|
% If needed, reset the heap pointer before executing the goal,
|
|
% to reclaim heap space allocated in earlier branches.
|
|
%
|
|
(
|
|
{ IsFirstBranch = no },
|
|
{ MaybeSavedHeapPointerVar = yes(SavedHeapPointerVar0) }
|
|
->
|
|
gen_restore_hp(SavedHeapPointerVar0, Context,
|
|
RestoreHeapPointerGoal),
|
|
{ conj_list_to_goal([RestoreHeapPointerGoal, Goal1], GoalInfo,
|
|
Goal) }
|
|
;
|
|
{ Goal = Goal1 }
|
|
),
|
|
|
|
%
|
|
% Save the heap pointer, if we haven't already done so,
|
|
% and if this disjunct might allocate heap space.
|
|
%
|
|
(
|
|
{ MaybeSavedHeapPointerVar = no },
|
|
{ code_util__goal_may_allocate_heap(Goal) }
|
|
->
|
|
% Generate code to save the heap pointer
|
|
new_saved_hp_var(SavedHeapPointerVar),
|
|
gen_mark_hp(SavedHeapPointerVar, Context, MarkHeapPointerGoal),
|
|
% Recursively handle the remaining disjuncts
|
|
disj_add_heap_ops(Goals0, no, yes(SavedHeapPointerVar),
|
|
DisjGoalInfo, Goals1),
|
|
% Put this disjunct and the remaining disjuncts in a
|
|
% nested disjunction, so that the heap pointer variable
|
|
% can scope over these disjuncts
|
|
{ map__init(StoreMap) },
|
|
{ Disj = disj([Goal | Goals1], StoreMap) - DisjGoalInfo },
|
|
{ DisjGoals = [conj([MarkHeapPointerGoal, Disj]) -
|
|
DisjGoalInfo] }
|
|
;
|
|
% Just recursively handle the remaining disjuncts
|
|
disj_add_heap_ops(Goals0, no, MaybeSavedHeapPointerVar,
|
|
DisjGoalInfo, Goals),
|
|
{ DisjGoals = [Goal | Goals] }
|
|
).
|
|
|
|
:- pred cases_add_heap_ops(list(case)::in, list(case)::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
cases_add_heap_ops([], []) --> [].
|
|
cases_add_heap_ops([Case0 | Cases0], [Case | Cases]) -->
|
|
{ Case0 = case(ConsId, Goal0) },
|
|
{ Case = case(ConsId, Goal) },
|
|
goal_add_heap_ops(Goal0, Goal),
|
|
cases_add_heap_ops(Cases0, Cases).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred gen_mark_hp(prog_var::in, prog_context::in, hlds_goal::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
gen_mark_hp(SavedHeapPointerVar, Context, MarkHeapPointerGoal) -->
|
|
ModuleInfo =^ module_info,
|
|
{ generate_call("mark_hp", [SavedHeapPointerVar],
|
|
det, yes(impure),
|
|
[SavedHeapPointerVar - ground_inst],
|
|
ModuleInfo, Context, MarkHeapPointerGoal) }.
|
|
|
|
:- pred gen_restore_hp(prog_var::in, prog_context::in, hlds_goal::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
gen_restore_hp(SavedHeapPointerVar, Context, RestoreHeapPointerGoal) -->
|
|
ModuleInfo =^ module_info,
|
|
{ generate_call("restore_hp", [SavedHeapPointerVar],
|
|
det, yes(impure), [],
|
|
ModuleInfo, Context, RestoreHeapPointerGoal) }.
|
|
|
|
:- func ground_inst = (inst).
|
|
ground_inst = ground(unique, none).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred new_saved_hp_var(prog_var::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
new_saved_hp_var(Var) -->
|
|
new_var("HeapPointer", heap_pointer_type, Var).
|
|
|
|
:- pred new_var(string::in, (type)::in, prog_var::out,
|
|
heap_ops_info::in, heap_ops_info::out) is det.
|
|
|
|
new_var(Name, Type, Var, TOI0, TOI) :-
|
|
VarSet0 = TOI0 ^ varset,
|
|
VarTypes0 = TOI0 ^ var_types,
|
|
varset__new_named_var(VarSet0, Name, Var, VarSet),
|
|
map__det_insert(VarTypes0, Var, Type, VarTypes),
|
|
TOI = ((TOI0 ^ varset := VarSet)
|
|
^ var_types := VarTypes).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred generate_call(string::in, list(prog_var)::in, determinism::in,
|
|
maybe(goal_feature)::in, assoc_list(prog_var, inst)::in,
|
|
module_info::in, term__context::in, hlds_goal::out) is det.
|
|
|
|
generate_call(PredName, Args, Detism, MaybeFeature, InstMap, Module, Context,
|
|
CallGoal) :-
|
|
mercury_private_builtin_module(BuiltinModule),
|
|
goal_util__generate_simple_call(BuiltinModule, PredName, Args, Detism,
|
|
MaybeFeature, InstMap, Module, Context, CallGoal).
|
|
|
|
%-----------------------------------------------------------------------------%
|