[m-rev.] for review: heap reclamation on failure for MLDS back-end
Fergus Henderson
fjh at cs.mu.OZ.AU
Fri Nov 23 02:51:55 AEDT 2001
Estimated hours taken: 3
Branches: main
Implement heap reclamation on failure for the MLDS back-end.
library/private_builtin.m:
Add impure procedures for saving and restoring the heap pointer.
compiler/add_heap_ops.m:
New file, similar to add_trail_ops.m.
An HLDS->HLDS transformation to add heap reclamation operations.
compiler/mercury_compile.m:
Call the new pass.
compiler/notes/compiler_design.html:
Mention the new pass.
Workspace: /home/earth/fjh/ws-earth3/mercury
Index: compiler/add_heap_ops.m
===================================================================
RCS file: compiler/add_heap_ops.m
diff -N compiler/add_heap_ops.m
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ compiler/add_heap_ops.m 22 Nov 2001 15:10:08 -0000
@@ -0,0 +1,355 @@
+%-----------------------------------------------------------------------------%
+% 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, quantification, modules, type_util.
+:- import_module instmap.
+
+:- 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 = [_|_] },
+
+ { goal_info_get_context(GoalInfo, Context) },
+
+ %
+ % Save the heap pointer so that we can
+ % restore it on back-tracking.
+ %
+ new_saved_hp_var(SavedHeapPointerVar),
+ gen_mark_hp(SavedHeapPointerVar, Context, MarkHeapPointerGoal),
+ disj_add_heap_ops(Goals0, yes, SavedHeapPointerVar, Goals),
+ { Goal = conj([MarkHeapPointerGoal, disj(Goals, B) - GoalInfo]) }.
+
+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) },
+ { 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 `true' rather than `fail' for the "then" part.
+ NewOuterGoal = if_then_else([], InnerGoal, True, True, SM)
+ ;
+ NewOuterGoal = if_then_else([], InnerGoal, Fail, 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),
+ %
+ % Save the heap pointer so that we can
+ % restore it if the condition fails.
+ %
+ 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_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",
+ [], det, 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, prog_var::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,
+ SavedHeapPointerVar, [Goal | Goals]) -->
+ { Goal0 = _ - GoalInfo0 },
+ { goal_info_get_context(GoalInfo0, Context) },
+ %
+ % First reset the heap pointer to
+ % undo the effects of any earlier branches
+ %
+ ( { IsFirstBranch = yes } ->
+ { UndoList = [] }
+ ;
+ gen_restore_hp(SavedHeapPointerVar, Context,
+ RestoreHeapPointerGoal),
+ { UndoList = [RestoreHeapPointerGoal] }
+ ),
+ %
+ % Then execute the disjunct goal
+ %
+ goal_add_heap_ops(Goal0, Goal1),
+ %
+ % Package up the stuff we built earlier.
+ %
+ { Goal1 = _ - GoalInfo1 },
+ { conj_list_to_goal(UndoList ++ [Goal1], GoalInfo1, Goal) },
+
+ % Recursively handle the remaining disjuncts
+ disj_add_heap_ops(Goals0, no, SavedHeapPointerVar, 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).
+
+%-----------------------------------------------------------------------------%
+
+:- func heap_pointer_type = (type).
+heap_pointer_type = c_pointer_type.
+
+%-----------------------------------------------------------------------------%
+
+% XXX copied from table_gen.m
+
+:- 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) :-
+ list__length(Args, Arity),
+ mercury_private_builtin_module(BuiltinModule),
+ module_info_get_predicate_table(Module, PredTable),
+ (
+ predicate_table_search_pred_m_n_a(PredTable,
+ BuiltinModule, PredName, Arity,
+ [PredId0])
+ ->
+ PredId = PredId0
+ ;
+ string__int_to_string(Arity, ArityS),
+ string__append_list(["can't locate ", PredName,
+ "/", ArityS], ErrorMessage),
+ error(ErrorMessage)
+ ),
+ module_info_pred_info(Module, PredId, PredInfo),
+ (
+ pred_info_procids(PredInfo, [ProcId0])
+ ->
+ ProcId = ProcId0
+ ;
+ string__int_to_string(Arity, ArityS),
+ string__append_list(["too many modes for pred ",
+ PredName, "/", ArityS], ErrorMessage),
+ error(ErrorMessage)
+
+ ),
+ Call = call(PredId, ProcId, Args, not_builtin, no,
+ qualified(BuiltinModule, PredName)),
+ set__init(NonLocals0),
+ set__insert_list(NonLocals0, Args, NonLocals),
+ determinism_components(Detism, _CanFail, NumSolns),
+ (
+ NumSolns = at_most_zero
+ ->
+ instmap_delta_init_unreachable(InstMapDelta)
+ ;
+ instmap_delta_from_assoc_list(InstMap, InstMapDelta)
+ ),
+ goal_info_init(NonLocals, InstMapDelta, Detism, CallGoalInfo0),
+ goal_info_set_context(CallGoalInfo0, Context, CallGoalInfo1),
+ (
+ MaybeFeature = yes(Feature),
+ goal_info_add_feature(CallGoalInfo1, Feature, CallGoalInfo)
+ ;
+ MaybeFeature = no,
+ CallGoalInfo = CallGoalInfo1
+ ),
+ CallGoal = Call - CallGoalInfo.
+
+%-----------------------------------------------------------------------------%
Index: compiler/mercury_compile.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/mercury_compile.m,v
retrieving revision 1.224
diff -u -d -r1.224 mercury_compile.m
--- compiler/mercury_compile.m 9 Nov 2001 18:20:48 -0000 1.224
+++ compiler/mercury_compile.m 22 Nov 2001 14:09:31 -0000
@@ -57,7 +57,7 @@
:- import_module bytecode_gen, bytecode.
% the MLDS back-end
-:- import_module add_trail_ops. % HLDS -> HLDS
+:- import_module add_trail_ops, add_heap_ops. % HLDS -> HLDS
:- import_module mark_static_terms. % HLDS -> HLDS
:- import_module mlds. % MLDS data structure
:- import_module ml_code_gen, rtti_to_mlds. % HLDS/RTTI -> MLDS
@@ -2330,6 +2330,40 @@
{ HLDS = HLDS0 }
).
+:- pred mercury_compile__maybe_add_heap_ops(module_info, bool, bool,
+ module_info, io__state, io__state).
+:- mode mercury_compile__maybe_add_heap_ops(in, in, in, out, di, uo)
+ is det.
+
+mercury_compile__maybe_add_heap_ops(HLDS0, Verbose, Stats, HLDS) -->
+ globals__io_get_gc_method(GC),
+ globals__io_lookup_bool_option(reclaim_heap_on_semidet_failure,
+ SemidetReclaim),
+ globals__io_lookup_bool_option(reclaim_heap_on_nondet_failure,
+ NondetReclaim),
+ ( { GC = conservative } ->
+ % we can't do heap reclamation with conservative GC
+ { HLDS = HLDS0 }
+ ; { SemidetReclaim = no, NondetReclaim = no } ->
+ { HLDS = HLDS0 }
+ ; { SemidetReclaim = yes, NondetReclaim = yes } ->
+ maybe_write_string(Verbose,
+ "% Adding heap reclamation operations...\n"),
+ maybe_flush_output(Verbose),
+ process_all_nonimported_procs(update_proc(add_heap_ops),
+ HLDS0, HLDS),
+ maybe_write_string(Verbose, "% done.\n"),
+ maybe_report_stats(Stats)
+ ;
+ io__write_strings([
+ "Sorry, not implemented: `--high-level-code' and\n",
+ "`--(no-)reclaim-heap-on-{semidet/nondet}-failure'.\n",
+ "Use `--(no-)reclaim-heap-on-failure' instead.\n"
+ ]),
+ io__set_exit_status(1),
+ { HLDS = HLDS0 }
+ ).
+
%-----------------------------------------------------------------------------%
:- pred mercury_compile__maybe_write_dependency_graph(module_info, bool, bool,
@@ -3190,7 +3224,11 @@
HLDS55),
mercury_compile__maybe_dump_hlds(HLDS55, "55", "add_trail_ops"),
- mercury_compile__maybe_mark_static_terms(HLDS55, Verbose, Stats,
+ mercury_compile__maybe_add_heap_ops(HLDS55, Verbose, Stats,
+ HLDS57),
+ mercury_compile__maybe_dump_hlds(HLDS57, "57", "add_heap_ops"),
+
+ mercury_compile__maybe_mark_static_terms(HLDS57, Verbose, Stats,
HLDS60),
mercury_compile__maybe_dump_hlds(HLDS60, "60", "mark_static"),
Index: compiler/notes/compiler_design.html
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/notes/compiler_design.html,v
retrieving revision 1.66
diff -u -d -r1.66 compiler_design.html
--- compiler/notes/compiler_design.html 11 Aug 2001 14:09:51 -0000 1.66
+++ compiler/notes/compiler_design.html 22 Nov 2001 13:17:30 -0000
@@ -897,6 +897,8 @@
resumes after backtracking, and whenever we do a commit.
The trail operations are represented as (and implemented as)
calls to impure procedures defined in library/private_builtin.m.
+<li> add_heap_ops.m is very similar to add_trail_ops.m;
+ it inserts code to do heap reclamation on backtracking.
</ul>
<h4> 4b. MLDS code generation </h4>
Index: library/private_builtin.m
===================================================================
RCS file: /home/mercury1/repository/mercury/library/private_builtin.m,v
retrieving revision 1.82
diff -u -d -r1.82 private_builtin.m
--- library/private_builtin.m 24 Oct 2001 07:43:17 -0000 1.82
+++ library/private_builtin.m 22 Nov 2001 13:43:13 -0000
@@ -1006,7 +1006,77 @@
"Sorry, not implemented:\n",
"for the MLDS back-end (`--high-level-code')\n",
"nondet `pragma c_code' or `pragma foreign_code'\n",
- "is not supported when trailing (`--use_trail') is enabled."
+ "is not supported when trailing (`--use-trail') is enabled."
+ ]),
+ error(Msg).
+
+%-----------------------------------------------------------------------------%
+
+ % This section of the module contains predicates that are used
+ % by the MLDS back-end, to implement heap reclamation on failure.
+ % (The LLDS back-end does not use these; instead it inserts
+ % the corresponding LLDS instructions directly during code
+ % generation.)
+ % These predicates should not be used by user programs directly.
+
+:- interface.
+
+:- type heap_pointer == c_pointer.
+
+ % For documentation, see the corresponding LLDS instructions
+ % in compiler/llds.m. See also compiler/notes/trailing.html.
+
+:- impure pred mark_hp(heap_pointer::out) is det.
+:- impure pred restore_hp(heap_pointer::in) is det.
+
+ % XXX currently we don't support nondet pragma
+ % foreign_code when trailing is enabled.
+ % Instead we generate code which calls this procedure,
+ % which will call error/1 with an appropriate message.
+:- pred reclaim_heap_nondet_pragma_foreign_code is erroneous.
+
+ % N.B. interface continued below.
+
+:- implementation.
+
+:- pragma foreign_proc("C", mark_hp(SavedHeapPointer::out),
+ [will_not_call_mercury, thread_safe],
+"
+#ifndef MR_CONSERVATIVE_GC
+ MR_mark_hp(SavedHeapPointer);
+#else
+ /* We can't do heap reclamation with conservative GC. */
+ SavedHeapPointer = 0;
+#endif
+").
+
+:- pragma foreign_proc("C", restore_hp(SavedHeapPointer::in),
+ [will_not_call_mercury, thread_safe],
+"
+#ifndef MR_CONSERVATIVE_GC
+ MR_restore_hp(SavedHeapPointer);
+#endif
+").
+
+:- pragma foreign_proc("MC++", mark_hp(SavedHeapPointer::out),
+ [will_not_call_mercury, thread_safe],
+"
+ /* We can't do heap reclamation on failure in the .NET back-end. */
+ SavedHeapPointer = 0;
+").
+
+:- pragma foreign_proc("MC++", restore_hp(SavedHeapPointer::in),
+ [will_not_call_mercury, thread_safe],
+"
+ /* We can't do heap reclamation on failure in the .NET back-end. */
+").
+
+reclaim_heap_nondet_pragma_foreign_code :-
+ Msg = string__append_list([
+ "Sorry, not implemented:\n",
+ "for the MLDS back-end (`--high-level-code')\n",
+ "nondet `pragma c_code' or `pragma foreign_code'\n",
+ "is not supported when `--reclaim-heap-on-failure' is enabled."
]),
error(Msg).
--
Fergus Henderson <fjh at cs.mu.oz.au> | "I have always known that the pursuit
The University of Melbourne | of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh> | -- the last words of T. S. Garp.
--------------------------------------------------------------------------
mercury-reviews mailing list
post: mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the reviews
mailing list