Files
mercury/compiler/code_gen.m
Zoltan Somogyi fdccd65e30 The debugger's retry command at the moment works only from a final port for
Estimated hours taken: 60

The debugger's retry command at the moment works only from a final port for
the call to be retried, the reason being that the RTTI does not have the
information required to make sure that the state of the stacks is reset
correctly. If you invoke retry from a non-final port, the current
implementation skips forward to a final port and then does the retry;
this does not work if you get a core dump or a infinite loop during the forward
skip. This change adds the required info to the RTTI and thus enables
direct retries from the middle of calls.

The information added has two components. First, if a procedure that lives on
the nondet stack allocates any temporary nondet stack frames, then it must
record the old value of maxfr in a stack slot so that retry can restore it.
Second, if a procedure is tabled, then it must record the call table tip node
corresponding to the actual input arguments, so we can reset this node to
uninitialized (if we don't, then the retried call will find the active call
marker and report an infinite loop error).

The support for retries across minimal model calls is not finished yet.
Finding out what the right thing to do in such cases is a research project,
one that cannot even be started until minimal model tabling works reliably
in the *absence* of retries. However, such retries do grossly wrong things
at the moment; this change is a definite improvement. It attempts to perform
the retry from the fail port, since that is the only time when the minimal
model tabling data structures are quiescent. The "fail" command I added to
the debugger command set to let this be done is not complete yet and is
therefore undocumented; the problem is that a call to a model_non predicate
in a committed choice context will not get to the fail port. I added goal paths
to return layouts so that we will eventually be able to tell when execution
leaves a committed choice context surrounding an ancestor of a model_non
predicate call, but this functionality is not yet implemented.

compiler/stack_layout.m:
	Generate the three new fields: the evaluation method, (maybe) the id
	of the stack slot containing the saved value of maxfr, and (maybe)
	the id of the stack slot containing the call table tip.

compiler/continuation_info.m:
	Record the information about the new fields for later use by
	stack_layout.m.

	Add a new field to record the goal path of calls for their return
	layouts.

	Fix a screwed comment for the continuation_info data structure.

compiler/llds.m:
	Add a new field to call() instructions to hold the goal path of the
	call.

	Add a utility function for use by trace.m.

compiler/call_gen.m:
	Fill in this new field.

compiler/trace.m:
compiler/live_vars.m:
	Reserve the fixed stack slot for the saved maxfr if necessary,
	and if the call table tip node is needed, make sure that the variable
	holding its address is allocated a low-numbered stack slot (otherwise,
	its number may not fit into the MR_int_least8_t field in the
	proc_layout).

compiler/trace.m:
	If necessary, fill in the saved maxfr slot.

	If necessary, initialize the call table tip slot.

compiler/hlds_goal.m:
	Add a goal feature which marks its goal as defining the variable
	representing the call table tip node.

	Add a field to the goal path step representing quantification;
	the field says whether the quantification changes the determinism of
	the goal (i.e. whether it cuts away solutions).

compiler/hlds_pred.m:
compiler/hlds_out.m:
	Add two fields to proc_infos which (a) record which variable, if any,
	holds the call table tip node, and (b) record whether the procedure's
	layout structure needs to reserve a slot for the saved value of maxfr.

compiler/table_gen.m:
	Put this feature on the appropriate goal.

	Also, rename a predicate to make it reflect its purpose better.

compiler/code_gen.m:
	Generate code to put the call table tip variable in its stack slot
	immediately after it has been generated.

	Add a sanity check to ensure that if a procedure that lives on the det
	stack can create a temporary nondet frame, and debugging is enabled,
	then it did have a stack slot reserved for the saved maxfr.

compiler/code_util.m:
	Add a predicate to make a conservative prediction of whether a
	procedure may allocate a temporary nondet stack frame. We cannot
	just generate the code and see, because the code generator needs to
	know which variables live in which stack slots, and we cannot decide
	that until we know whether we need a stack slot for the saved value of
	maxfr.

	Make an unrelated predicate semidet procedure use a det helper, in
	order to make it more robust in the face of changes to the HLDS
	(e.g. it was missing code for handling bi_implications).

compiler/code_info.m:
	Record whether a procedure has in fact created a temporary nondet stack
	frame.

compiler/handle_options.m:
	Disable hijacks if debugging is enabled. The code we now use to
	restore the stacks for direct retries works only if the retry does not
	"backtrack" over a hijacked nondet stack frame whose hijack has not
	been undone. Note that code compiled without debugging may still hijack
	nondet stack frames. Execution may reemerge from the nondebugged region
	in one of two ways. If the nondebugged code returns, then it will have
	undone hijack, and the retry code will work. If the nondebugged code
	calls debugged code, there will be a region on the stacks containing
	no debugging information, and the retry command will refuse to perform
	retries that go into or beyond this region. Both cases preserve
	correctness.

compiler/*.m:
	Trivial changes to conform to changes in data structures.

runtime/mercury_stack_layout.h:
	Add three new fields to proc layouts: the numbers of the stack slots
	(if any) storing the saved maxfr and the call table tip, and a
	representation of the procedure's evaluation method.

runtime/mercury_stack_trace.[ch]:
	Now that return layouts contain goal paths, print them in stack dumps
	only if the include_trace_data flag is set (in mdb, this requires the
	-d flag of the "stack" command).

	Pass this flag around directly, instead of encoding its value in
	the NULL vs non-NULL values of sp and curfr.

runtime/mercury_regorder.h:
	Provide a mechanism to access the values of the first few rN registers
	from a save area, for use in debugging low-level C code in the runtime
	and the trace directories.

trace/mercury_trace.[ch]:
	Reimplement MR_trace_retry to allow retries from the middle.
	If the stack segment being retried over contains minimal model
	procedures, we must still arrange to skip to the end of the retried
	call. If this call is a minimal model generator, skipping to just any
	final port is not sufficient to guarantee correctness on retry; to
	ensure that subgoal is complete, we must skip to a fail port.

trace/mercury_trace.[ch]:
trace/mercury_trace_internal.c:
	Implement a debugger command, "fail", which skips to the fail port or
	the exception port of the specified ancestor. Since procedures that are
	not model_non are not guaranteed to get to such a port, this
	command reports an error if the specified call is not model_non.
	Actually, even calls to model_non procedures may not get to the fail
	port, as explained above; this is why the command is not yet
	documented.

trace/mercury_trace.c:
trace/mercury_trace_util.[ch]:
	Move some functions to print parts of the Mercury abstract machine
	state from mercury_trace to mercury_trace_util, so that they are
	available for use in debugging e.g. mercury_trace_declarative.

trace/mercury_trace_internal.c:
trace/mercury_trace_external.c:
trace/mercury_trace_declarative.c:
	Use the new implementation of retries. At the moment, only the
	internal debugger implements the full functionality. The declarative
	debugger issues retry commands only from situations where the missing
	functionality is not (yet) needed. The external debugger should
	continue to work correctly, but Erwan may wish to update it to
	exploit the availability of the fail command.

trace/mercury_trace*.[ch]:
	Fix MR_prefixes, and a signed/unsigned mismatch.

doc/user_guide.texi:
	Document the new "fail" command, but comment it out for now.

tests/debugger/retry.{m,inp,exp,exp2}:
	A new test case for exercising retry.

tests/debugger/Mmakefile:
	Enable the new test case.

tests/debugger/*.exp:
	Update the expected output, given the extra info now output e.g. for
	exception events and detailed stack traces.
2000-10-13 04:06:52 +00:00

1129 lines
38 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.
%---------------------------------------------------------------------------%
%
% Code generation - convert from HLDS to LLDS.
%
% Main authors: conway, zs.
%
% The two main tasks of this module are
%
% 1 to look after the aspects of generating code for a procedure
% that do not involve generating code for a specific goal, and
%
% 2 to provide a generic predicate that can be called from anywhere in
% the code generator to generate code for a goal.
%
% Code_gen forwards most of the actual construction of code for particular
% goals to other modules. The generation of code for unifications is done
% by unify_gen, for calls, higher-order calls and method calls by call_gen,
% for commits by commit_gen, for if-then-elses and negations by ite_gen,
% for switches by switch_gen and its subsidiary modules, for disjunctions
% by disj_gen, and for pragma_c_codes by pragma_c_gen. The only kind of goal
% handled directly by code_gen is the conjunction.
%
%---------------------------------------------------------------------------%
:- module code_gen.
:- interface.
:- import_module hlds_module, hlds_pred, hlds_goal, llds, code_info.
:- import_module list, io, counter.
% Translate a HLDS module to LLDS.
:- pred generate_code(module_info::in, module_info::out,
global_data::in, global_data::out, list(c_procedure)::out,
io__state::di, io__state::uo) is det.
% Translate a HLDS procedure to LLDS, threading through
% the data structure that records information about layout
% structures and the counter for ensuring the uniqueness
% of cell numbers.
:- pred generate_proc_code(pred_info::in, proc_info::in,
proc_id::in, pred_id::in, module_info::in,
global_data::in, global_data::out, counter::in, counter::out,
c_procedure::out) is det.
% Translate a HLDS goal to LLDS.
:- pred code_gen__generate_goal(code_model::in, hlds_goal::in, code_tree::out,
code_info::in, code_info::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module call_gen, unify_gen, ite_gen, switch_gen, disj_gen.
:- import_module par_conj_gen, pragma_c_gen, commit_gen.
:- import_module continuation_info, trace, trace_params, options, hlds_out.
:- import_module code_aux, middle_rec, passes_aux, llds_out.
:- import_module code_util, type_util, mode_util, goal_util.
:- import_module prog_data, prog_out, prog_util, instmap, globals.
:- import_module bool, char, int, string.
:- import_module map, assoc_list, set, term, tree, std_util, require, varset.
%---------------------------------------------------------------------------%
generate_code(ModuleInfo0, ModuleInfo, GlobalData0, GlobalData, Procedures) -->
% get a list of all the predicate ids
% for which we are going to generate code.
{ module_info_predids(ModuleInfo0, PredIds) },
% now generate the code for each predicate
generate_pred_list_code(ModuleInfo0, ModuleInfo,
GlobalData0, GlobalData, PredIds, Procedures).
% Translate a list of HLDS predicates to LLDS.
:- pred generate_pred_list_code(module_info::in, module_info::out,
global_data::in, global_data::out,
list(pred_id)::in, list(c_procedure)::out,
io__state::di, io__state::uo) is det.
:- pred generate_maybe_pred_code(module_info::in, module_info::out,
global_data::in, global_data::out, pred_id::in, list(c_procedure)::out,
io__state::di, io__state::uo) is det.
generate_pred_list_code(ModuleInfo, ModuleInfo, GlobalData, GlobalData,
[], []) --> [].
generate_pred_list_code(ModuleInfo0, ModuleInfo, GlobalData0, GlobalData,
[PredId | PredIds], Predicates) -->
generate_maybe_pred_code(ModuleInfo0, ModuleInfo1,
GlobalData0, GlobalData1, PredId, Predicates0),
generate_pred_list_code(ModuleInfo1, ModuleInfo,
GlobalData1, GlobalData, PredIds, Predicates1),
{ list__append(Predicates0, Predicates1, Predicates) }.
% Note that some of the logic of generate_maybe_pred_code is duplicated
% by mercury_compile__backend_pass_by_preds, so modifications here may
% also need to be repeated there.
generate_maybe_pred_code(ModuleInfo0, ModuleInfo, GlobalData0, GlobalData,
PredId, Predicates) -->
{ module_info_preds(ModuleInfo0, PredInfos) },
% get the pred_info structure for this predicate
{ map__lookup(PredInfos, PredId, PredInfo) },
% extract a list of all the procedure ids for this
% predicate and generate code for them
{ pred_info_non_imported_procids(PredInfo, ProcIds) },
(
{ ProcIds = []
; hlds_pred__pred_info_is_aditi_relation(PredInfo)
}
->
{ Predicates = [] },
{ ModuleInfo = ModuleInfo0 },
{ GlobalData = GlobalData0 }
;
{ module_info_globals(ModuleInfo0, Globals0) },
{ globals__lookup_bool_option(Globals0, very_verbose,
VeryVerbose) },
( { VeryVerbose = yes } ->
io__write_string("% Generating code for "),
hlds_out__write_pred_id(ModuleInfo0, PredId),
io__write_string("\n"),
{ globals__lookup_bool_option(Globals0,
statistics, Statistics) },
maybe_report_stats(Statistics)
;
[]
),
{
pred_info_module(PredInfo, PredModule),
pred_info_name(PredInfo, PredName),
pred_info_arity(PredInfo, PredArity),
no_type_info_builtin(PredModule, PredName, PredArity)
->
% These predicates should never be traced,
% since they do not obey typeinfo_liveness.
% Since they may be opt_imported into other
% modules, we must switch off the tracing
% of such preds on a pred-by-pred basis.
globals__get_trace_level(Globals0, TraceLevel),
globals__set_trace_level_none(Globals0, Globals1),
module_info_set_globals(ModuleInfo0, Globals1,
ModuleInfo1),
generate_pred_code(ModuleInfo1, ModuleInfo2,
GlobalData0, GlobalData,
PredId, PredInfo, ProcIds, Predicates),
module_info_globals(ModuleInfo2, Globals2),
globals__set_trace_level(Globals2, TraceLevel,
Globals),
module_info_set_globals(ModuleInfo2, Globals,
ModuleInfo)
;
generate_pred_code(ModuleInfo0, ModuleInfo,
GlobalData0, GlobalData,
PredId, PredInfo, ProcIds, Predicates)
}
).
% Translate a HLDS predicate to LLDS.
:- pred generate_pred_code(module_info::in, module_info::out,
global_data::in, global_data::out, pred_id::in, pred_info::in,
list(proc_id)::in, list(c_procedure)::out) is det.
generate_pred_code(ModuleInfo0, ModuleInfo, GlobalData0, GlobalData,
PredId, PredInfo, ProcIds, Code) :-
module_info_get_cell_counter(ModuleInfo0, CellCounter0),
generate_proc_list_code(ProcIds, PredId, PredInfo, ModuleInfo0,
GlobalData0, GlobalData, CellCounter0, CellCounter,
[], Code),
module_info_set_cell_counter(ModuleInfo0, CellCounter, ModuleInfo).
% Translate all the procedures of a HLDS predicate to LLDS.
:- pred generate_proc_list_code(list(proc_id)::in, pred_id::in, pred_info::in,
module_info::in, global_data::in, global_data::out,
counter::in, counter::out,
list(c_procedure)::in, list(c_procedure)::out) is det.
generate_proc_list_code([], _PredId, _PredInfo, _ModuleInfo,
GlobalData, GlobalData, CellCounter, CellCounter,
Procs, Procs).
generate_proc_list_code([ProcId | ProcIds], PredId, PredInfo, ModuleInfo0,
GlobalData0, GlobalData, CellCounter0, CellCounter,
Procs0, Procs) :-
pred_info_procedures(PredInfo, ProcInfos),
map__lookup(ProcInfos, ProcId, ProcInfo),
generate_proc_code(PredInfo, ProcInfo, ProcId, PredId, ModuleInfo0,
GlobalData0, GlobalData1, CellCounter0, CellCounter1, Proc),
generate_proc_list_code(ProcIds, PredId, PredInfo, ModuleInfo0,
GlobalData1, GlobalData, CellCounter1, CellCounter,
[Proc | Procs0], Procs).
%---------------------------------------------------------------------------%
% Values of this type hold information about stack frames that is
% generated when generating prologs and is used in generating epilogs
% and when massaging the code generated for the procedure.
:- type frame_info
---> frame(
int, % Number of slots in frame.
maybe(int), % Slot number of succip if succip is
% present in a general slot.
bool % Is this the frame of a model_non
% proc defined via pragma C code?
).
%---------------------------------------------------------------------------%
generate_proc_code(PredInfo, ProcInfo, ProcId, PredId, ModuleInfo,
GlobalData0, GlobalData, CellCounter0, CellCounter, Proc) :-
proc_info_interface_determinism(ProcInfo, Detism),
proc_info_interface_code_model(ProcInfo, CodeModel),
proc_info_goal(ProcInfo, Goal),
Goal = _ - GoalInfo,
goal_info_get_follow_vars(GoalInfo, MaybeFollowVars),
(
MaybeFollowVars = yes(FollowVars)
;
MaybeFollowVars = no,
map__init(FollowVarsMap),
FollowVars = follow_vars(FollowVarsMap, 1)
),
module_info_globals(ModuleInfo, Globals),
continuation_info__basic_stack_layout_for_proc(PredInfo, Globals,
BasicStackLayout, ForceProcId),
( BasicStackLayout = yes ->
SaveSuccip = yes
;
SaveSuccip = no
),
% Initialise the code_info structure. Generate_category_code
% below will use the returned OutsideResumePoint as the
% entry to the code that handles the failure of the procedure,
% if such code is needed. It is never needed for model_det
% procedures, always needed for model_semi procedures, and
% needed for model_non procedures only if we are doing
% execution tracing.
code_info__init(SaveSuccip, Globals, PredId, ProcId, ProcInfo,
FollowVars, ModuleInfo, CellCounter0, OutsideResumePoint,
TraceSlotInfo, CodeInfo0),
% Generate code for the procedure.
generate_category_code(CodeModel, Goal, OutsideResumePoint,
TraceSlotInfo, CodeTree, MaybeTraceCallLabel, FrameInfo,
CodeInfo0, CodeInfo),
code_info__get_max_reg_in_use_at_trace(MaxTraceReg, CodeInfo, _),
code_info__get_cell_counter(CellCounter, CodeInfo, _),
globals__get_trace_level(Globals, TraceLevel),
code_info__get_created_temp_frame(CreatedTempFrame, CodeInfo, _),
(
trace_level_is_none(TraceLevel) = no,
CreatedTempFrame = yes,
CodeModel \= model_non
->
% If tracing is enabled, the procedure lives on
% the det stack and the code created any temporary
% nondet stack frames, then we must have reserved a
% stack slot for storing the value of maxfr; if we
% didn't, a retry command in the debugger from a point
% in the middle of this procedure will do the wrong
% thing.
proc_info_get_need_maxfr_slot(ProcInfo, HaveMaxfrSlot),
require(unify(HaveMaxfrSlot, yes),
"should have reserved a slot for maxfr, but didn't")
;
true
),
% Turn the code tree into a list.
tree__flatten(CodeTree, FragmentList),
% Now the code is a list of code fragments (== list(instr)),
% so we need to do a level of unwinding to get a flat list.
list__condense(FragmentList, Instructions0),
FrameInfo = frame(TotalSlots, MaybeSuccipSlot, _),
(
MaybeSuccipSlot = yes(SuccipSlot)
->
% The set of recorded live values at calls (for value
% numbering) and returns (for accurate gc and execution
% tracing) do not yet record the stack slot holding the
% succip, so add it to those sets.
code_gen__add_saved_succip(Instructions0,
SuccipSlot, Instructions)
;
Instructions = Instructions0
),
( BasicStackLayout = yes ->
% Create the procedure layout structure.
code_info__get_layout_info(InternalMap, CodeInfo, _),
code_util__make_local_entry_label(ModuleInfo, PredId, ProcId,
no, EntryLabel),
proc_info_eval_method(ProcInfo, EvalMethod),
proc_info_get_initial_instmap(ProcInfo, ModuleInfo, InstMap0),
proc_info_varset(ProcInfo, VarSet),
ProcLayout = proc_layout_info(EntryLabel, Detism, TotalSlots,
MaybeSuccipSlot, EvalMethod, MaybeTraceCallLabel,
MaxTraceReg, Goal, InstMap0, TraceSlotInfo,
ForceProcId, VarSet, InternalMap),
global_data_add_new_proc_layout(GlobalData0,
proc(PredId, ProcId), ProcLayout, GlobalData1)
;
GlobalData1 = GlobalData0
),
code_info__get_non_common_static_data(NonCommonStatics, CodeInfo, _),
global_data_add_new_non_common_static_datas(GlobalData1,
NonCommonStatics, GlobalData2),
code_util__make_proc_label(ModuleInfo, PredId, ProcId, ProcLabel),
maybe_add_tabling_pointer_var(ModuleInfo, PredId, ProcId, ProcInfo,
ProcLabel, GlobalData2, GlobalData),
pred_info_name(PredInfo, Name),
pred_info_arity(PredInfo, Arity),
( goal_contains_reconstruction(Goal) ->
ContainsReconstruction = contains_reconstruction
;
ContainsReconstruction = does_not_contain_reconstruction
),
% Construct a c_procedure structure with all the information.
code_info__get_label_counter(LabelCounter, CodeInfo, _),
Proc = c_procedure(Name, Arity, proc(PredId, ProcId), Instructions,
ProcLabel, LabelCounter, ContainsReconstruction).
:- pred maybe_add_tabling_pointer_var(module_info::in,
pred_id::in, proc_id::in, proc_info::in, proc_label::in,
global_data::in, global_data::out) is det.
maybe_add_tabling_pointer_var(ModuleInfo, PredId, ProcId, ProcInfo, ProcLabel,
GlobalData0, GlobalData) :-
proc_info_eval_method(ProcInfo, EvalMethod),
( eval_method_has_per_proc_tabling_pointer(EvalMethod) = yes ->
module_info_name(ModuleInfo, ModuleName),
Var = tabling_pointer_var(ModuleName, ProcLabel),
global_data_add_new_proc_var(GlobalData0,
proc(PredId, ProcId), Var, GlobalData)
;
GlobalData = GlobalData0
).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
% Generate_category_code generates code for an entire procedure.
% Its algorithm has three or four main stages:
%
% - generate code for the body goal
% - generate code for the procedure entry
% - generate code for the procedure exit
% - generate code for the procedure fail (if needed)
%
% The first three tasks are forwarded to other procedures.
% The fourth task, if needed, is done by generate_category_code.
%
% The only caller of generate_category_code, generate_proc_code,
% has set up the code generator state to reflect what the machine
% state will be on entry to the procedure. Ensuring that the
% machine state at exit will conform to the expectation
% of the caller is the job of code_gen__generate_exit.
%
% The reason why we generate the entry code after the body is that
% information such as the total number of stack slots needed,
% which is needed in the procedure entry prologue, cannot be
% conveniently obtained before generating the body, since the
% code generator may allocate temporary variables to hold values
% such as saved heap and trail pointers.
%
% Code_gen__generate_entry cannot depend on the code generator
% state, since when it is invoked this state is not appropriate
% for the procedure entry. Nor can it change the code generator state,
% since that would confuse code_gen__generate_exit.
%
% Generating CALL trace events is done by generate_category_code,
% since only on entry to generate_category_code is the code generator
% state set up right. Generating EXIT trace events is done by
% code_gen__generate_exit. Generating FAIL trace events is done
% by generate_category_code, since this requires modifying how
% we generate code for the body of the procedure (failures must
% now branch to a different place). Since FAIL trace events are
% part of the failure continuation, generate_category_code takes
% care of the failure continuation as well. (Model_det procedures
% of course have no failure continuation. Model_non procedures have
% a failure continuation, but in the absence of tracing this
% continuation needs no code. Only model_semi procedures need code
% for the failure continuation at all times.)
:- pred generate_category_code(code_model::in, hlds_goal::in,
resume_point_info::in, trace_slot_info::in, code_tree::out,
maybe(label)::out, frame_info::out, code_info::in, code_info::out)
is det.
generate_category_code(model_det, Goal, ResumePoint, TraceSlotInfo, Code,
MaybeTraceCallLabel, FrameInfo) -->
% generate the code for the body of the clause
(
code_info__get_globals(Globals),
{ globals__lookup_bool_option(Globals, middle_rec, yes) },
middle_rec__match_and_generate(Goal, MiddleRecCode)
->
{ Code = MiddleRecCode },
{ MaybeTraceCallLabel = no },
{ FrameInfo = frame(0, no, no) }
;
{ Goal = _ - GoalInfo },
{ goal_info_get_context(GoalInfo, BodyContext) },
code_info__get_maybe_trace_info(MaybeTraceInfo),
( { MaybeTraceInfo = yes(TraceInfo) } ->
trace__generate_external_event_code(call, TraceInfo,
BodyContext, MaybeCallExternalInfo),
{
MaybeCallExternalInfo = yes(CallExternalInfo),
CallExternalInfo = external_event_info(
TraceCallLabel, _, TraceCallCode)
;
MaybeCallExternalInfo = no,
error("generate_category_code: call events suppressed")
},
{ MaybeTraceCallLabel = yes(TraceCallLabel) }
;
{ TraceCallCode = empty },
{ MaybeTraceCallLabel = no }
),
code_gen__generate_goal(model_det, Goal, BodyCode),
code_gen__generate_entry(model_det, Goal, ResumePoint,
FrameInfo, EntryCode),
code_gen__generate_exit(model_det, FrameInfo, TraceSlotInfo,
BodyContext, _, ExitCode),
{ Code =
tree(EntryCode,
tree(TraceCallCode,
tree(BodyCode,
ExitCode)))
}
).
generate_category_code(model_semi, Goal, ResumePoint, TraceSlotInfo, Code,
MaybeTraceCallLabel, FrameInfo) -->
{ set__singleton_set(FailureLiveRegs, reg(r, 1)) },
{ FailCode = node([
assign(reg(r, 1), const(false)) - "Fail",
livevals(FailureLiveRegs) - "",
goto(succip) - "Return from procedure call"
]) },
{ Goal = _ - GoalInfo },
{ goal_info_get_context(GoalInfo, BodyContext) },
code_info__get_maybe_trace_info(MaybeTraceInfo),
( { MaybeTraceInfo = yes(TraceInfo) } ->
trace__generate_external_event_code(call, TraceInfo,
BodyContext, MaybeCallExternalInfo),
{
MaybeCallExternalInfo = yes(CallExternalInfo),
CallExternalInfo = external_event_info(
TraceCallLabel, _, TraceCallCode)
;
MaybeCallExternalInfo = no,
error("generate_category_code: call events suppressed")
},
{ MaybeTraceCallLabel = yes(TraceCallLabel) },
code_gen__generate_goal(model_semi, Goal, BodyCode),
code_gen__generate_entry(model_semi, Goal, ResumePoint,
FrameInfo, EntryCode),
code_gen__generate_exit(model_semi, FrameInfo, TraceSlotInfo,
BodyContext, RestoreDeallocCode, ExitCode),
code_info__generate_resume_point(ResumePoint, ResumeCode),
{ code_info__resume_point_vars(ResumePoint, ResumeVarList) },
{ set__list_to_set(ResumeVarList, ResumeVars) },
code_info__set_forward_live_vars(ResumeVars),
% XXX A context that gives the end of the procedure
% definition would be better than BodyContext.
trace__generate_external_event_code(fail, TraceInfo,
BodyContext, MaybeFailExternalInfo),
{
MaybeFailExternalInfo = yes(FailExternalInfo),
FailExternalInfo = external_event_info(
_, _, TraceFailCode)
;
MaybeFailExternalInfo = no,
TraceFailCode = empty
},
{ Code =
tree(EntryCode,
tree(TraceCallCode,
tree(BodyCode,
tree(ExitCode,
tree(ResumeCode,
tree(TraceFailCode,
tree(RestoreDeallocCode,
FailCode)))))))
}
;
{ MaybeTraceCallLabel = no },
code_gen__generate_goal(model_semi, Goal, BodyCode),
code_gen__generate_entry(model_semi, Goal, ResumePoint,
FrameInfo, EntryCode),
code_gen__generate_exit(model_semi, FrameInfo, TraceSlotInfo,
BodyContext, RestoreDeallocCode, ExitCode),
code_info__generate_resume_point(ResumePoint, ResumeCode),
{ Code =
tree(EntryCode,
tree(BodyCode,
tree(ExitCode,
tree(ResumeCode,
tree(RestoreDeallocCode,
FailCode)))))
}
).
generate_category_code(model_non, Goal, ResumePoint, TraceSlotInfo, Code,
MaybeTraceCallLabel, FrameInfo) -->
code_info__get_maybe_trace_info(MaybeTraceInfo),
{ Goal = _ - GoalInfo },
{ goal_info_get_context(GoalInfo, BodyContext) },
( { MaybeTraceInfo = yes(TraceInfo) } ->
trace__generate_external_event_code(call, TraceInfo,
BodyContext, MaybeCallExternalInfo),
{
MaybeCallExternalInfo = yes(CallExternalInfo),
CallExternalInfo = external_event_info(
TraceCallLabel, _, TraceCallCode)
;
MaybeCallExternalInfo = no,
error("generate_category_code: call events suppressed")
},
{ MaybeTraceCallLabel = yes(TraceCallLabel) },
code_gen__generate_goal(model_non, Goal, BodyCode),
code_gen__generate_entry(model_non, Goal, ResumePoint,
FrameInfo, EntryCode),
code_gen__generate_exit(model_non, FrameInfo, TraceSlotInfo,
BodyContext, _, ExitCode),
code_info__generate_resume_point(ResumePoint, ResumeCode),
{ code_info__resume_point_vars(ResumePoint, ResumeVarList) },
{ set__list_to_set(ResumeVarList, ResumeVars) },
code_info__set_forward_live_vars(ResumeVars),
% XXX A context that gives the end of the procedure
% definition would be better than BodyContext.
trace__generate_external_event_code(fail, TraceInfo,
BodyContext, MaybeFailExternalInfo),
{
MaybeFailExternalInfo = yes(FailExternalInfo),
FailExternalInfo = external_event_info(
_, _, TraceFailCode)
;
MaybeFailExternalInfo = no,
TraceFailCode = empty
},
{ TraceSlotInfo ^ slot_trail = yes(_) ->
DiscardTraceTicketCode = node([
discard_ticket - "discard retry ticket"
])
;
DiscardTraceTicketCode = empty
},
{ FailCode = node([
goto(do_fail) - "fail after fail trace port"
]) },
{ Code =
tree(EntryCode,
tree(TraceCallCode,
tree(BodyCode,
tree(ExitCode,
tree(ResumeCode,
tree(TraceFailCode,
tree(DiscardTraceTicketCode,
FailCode)))))))
}
;
{ MaybeTraceCallLabel = no },
code_gen__generate_goal(model_non, Goal, BodyCode),
code_gen__generate_entry(model_non, Goal, ResumePoint,
FrameInfo, EntryCode),
code_gen__generate_exit(model_non, FrameInfo, TraceSlotInfo,
BodyContext, _, ExitCode),
{ Code =
tree(EntryCode,
tree(BodyCode,
ExitCode))
}
).
%---------------------------------------------------------------------------%
% Generate the prologue for a procedure.
%
% The prologue will contain
%
% a comment to mark prologue start
% a comment explaining the stack layout
% the procedure entry label
% code to allocate a stack frame
% code to fill in some special slots in the stack frame
% a comment to mark prologue end
%
% At the moment the only special slots are the succip slot, and
% the slots holding the call number and call depth for tracing.
%
% Not all frames will have all these components. For example, the code
% to allocate a stack frame will be missing if the procedure doesn't
% need a stack frame, and if the procedure is nondet, then the code
% to fill in the succip slot is subsumed by the mkframe.
:- pred code_gen__generate_entry(code_model::in, hlds_goal::in,
resume_point_info::in, frame_info::out, code_tree::out,
code_info::in, code_info::out) is det.
code_gen__generate_entry(CodeModel, Goal, OutsideResumePoint, FrameInfo,
EntryCode) -->
code_info__get_stack_slots(StackSlots),
code_info__get_varset(VarSet),
{ code_aux__explain_stack_slots(StackSlots, VarSet, SlotsComment) },
{ StartComment = node([
comment("Start of procedure prologue") - "",
comment(SlotsComment) - ""
]) },
code_info__get_total_stackslot_count(MainSlots),
code_info__get_pred_id(PredId),
code_info__get_proc_id(ProcId),
code_info__get_module_info(ModuleInfo),
{ code_util__make_local_entry_label(ModuleInfo, PredId, ProcId, no,
Entry) },
{ LabelCode = node([
label(Entry) - "Procedure entry point"
]) },
code_info__get_succip_used(Used),
(
% Do we need to save the succip across calls?
{ Used = yes },
% Do we need to use a general slot for storing succip?
{ CodeModel \= model_non }
->
{ SuccipSlot is MainSlots + 1 },
{ SaveSuccipCode = node([
assign(stackvar(SuccipSlot), lval(succip)) -
"Save the success ip"
]) },
{ TotalSlots = SuccipSlot },
{ MaybeSuccipSlot = yes(SuccipSlot) }
;
{ SaveSuccipCode = empty },
{ TotalSlots = MainSlots },
{ MaybeSuccipSlot = no }
),
code_info__get_maybe_trace_info(MaybeTraceInfo),
( { MaybeTraceInfo = yes(TraceInfo) } ->
trace__generate_slot_fill_code(TraceInfo, TraceFillCode)
;
{ TraceFillCode = empty }
),
{ predicate_module(ModuleInfo, PredId, ModuleName) },
{ predicate_name(ModuleInfo, PredId, PredName) },
{ predicate_arity(ModuleInfo, PredId, Arity) },
{ prog_out__sym_name_to_string(ModuleName, ModuleNameString) },
{ string__int_to_string(Arity, ArityStr) },
{ string__append_list([ModuleNameString, ":", PredName, "/", ArityStr],
PushMsg) },
(
{ CodeModel = model_non }
->
{ code_info__resume_point_stack_addr(OutsideResumePoint,
OutsideResumeAddress) },
(
{ Goal = pragma_foreign_code(_, _, _, _, _, _, _,
PragmaCode) - _},
{ PragmaCode = nondet(Fields, FieldsContext,
_,_,_,_,_,_,_) }
->
{ pragma_c_gen__struct_name(ModuleName, PredName,
Arity, ProcId, StructName) },
{ Struct = pragma_c_struct(StructName,
Fields, FieldsContext) },
{ string__format("#define\tMR_ORDINARY_SLOTS\t%d\n",
[i(TotalSlots)], DefineStr) },
{ DefineComponents = [pragma_c_raw_code(DefineStr)] },
{ NondetFrameInfo = ordinary_frame(PushMsg, TotalSlots,
yes(Struct)) },
{ AllocCode = node([
mkframe(NondetFrameInfo, OutsideResumeAddress)
- "Allocate stack frame",
pragma_c([], DefineComponents,
will_not_call_mercury, no, no, no, no)
- ""
]) },
{ NondetPragma = yes }
;
{ NondetFrameInfo = ordinary_frame(PushMsg, TotalSlots,
no) },
{ AllocCode = node([
mkframe(NondetFrameInfo, OutsideResumeAddress)
- "Allocate stack frame"
]) },
{ NondetPragma = no }
)
;
{ TotalSlots > 0 }
->
{ AllocCode = node([
incr_sp(TotalSlots, PushMsg) -
"Allocate stack frame"
]) },
{ NondetPragma = no }
;
{ AllocCode = empty },
{ NondetPragma = no }
),
{ FrameInfo = frame(TotalSlots, MaybeSuccipSlot, NondetPragma) },
{ EndComment = node([
comment("End of procedure prologue") - ""
]) },
{ EntryCode =
tree(StartComment,
tree(LabelCode,
tree(AllocCode,
tree(SaveSuccipCode,
tree(TraceFillCode,
EndComment)))))
}.
%---------------------------------------------------------------------------%
% Generate the success epilogue for a procedure.
%
% The success epilogue will contain
%
% a comment to mark epilogue start
% code to place the output arguments where their caller expects
% code to restore registers from some special slots
% code to deallocate the stack frame
% code to set r1 to TRUE (for semidet procedures only)
% a jump back to the caller, including livevals information
% a comment to mark epilogue end
%
% The parts of this that restore registers and deallocate the stack
% frame are also part of the failure epilog, which is handled by
% our caller; this is why we return RestoreDeallocCode.
%
% At the moment the only special slots are the succip slot, and
% the tracing slots (holding the call sequence number, call event
% number, call depth, from-full indication, and trail state).
%
% Not all frames will have all these components. For example, for
% nondet procedures we don't deallocate the stack frame before
% success.
%
% Epilogues for procedures defined by nondet pragma C codes do not
% follow the rules above. For such procedures, the normal functions
% of the epilogue are handled when traversing the pragma C code goal;
% we need only #undef a macro defined by the procedure prologue.
:- pred code_gen__generate_exit(code_model::in, frame_info::in,
trace_slot_info::in, prog_context::in, code_tree::out, code_tree::out,
code_info::in, code_info::out) is det.
code_gen__generate_exit(CodeModel, FrameInfo, TraceSlotInfo, BodyContext,
RestoreDeallocCode, ExitCode) -->
{ StartComment = node([
comment("Start of procedure epilogue") - ""
]) },
{ EndComment = node([
comment("End of procedure epilogue") - ""
]) },
{ FrameInfo = frame(TotalSlots, MaybeSuccipSlot, NondetPragma) },
( { NondetPragma = yes } ->
{ UndefStr = "#undef\tMR_ORDINARY_SLOTS\n" },
{ UndefComponents = [pragma_c_raw_code(UndefStr)] },
{ UndefCode = node([
pragma_c([], UndefComponents,
will_not_call_mercury, no, no, no, no)
- ""
]) },
{ RestoreDeallocCode = empty }, % always empty for nondet code
{ ExitCode =
tree(StartComment,
tree(UndefCode,
EndComment))
}
;
code_info__get_instmap(Instmap),
code_info__get_arginfo(ArgModes),
code_info__get_headvars(HeadVars),
{ assoc_list__from_corresponding_lists(HeadVars, ArgModes,
Args)},
(
{ instmap__is_unreachable(Instmap) }
->
{ OutLvals = set__init },
{ FlushCode = empty }
;
code_info__setup_return(Args, OutLvals, FlushCode)
),
{
MaybeSuccipSlot = yes(SuccipSlot)
->
RestoreSuccipCode = node([
assign(succip, lval(stackvar(SuccipSlot))) -
"restore the success ip"
])
;
RestoreSuccipCode = empty
},
{
( TotalSlots = 0 ; CodeModel = model_non )
->
DeallocCode = empty
;
DeallocCode = node([
decr_sp(TotalSlots) - "Deallocate stack frame"
])
},
{
TraceSlotInfo ^ slot_trail = yes(_),
CodeModel \= model_non
->
PruneTraceTicketCode = node([
prune_ticket - "prune retry ticket"
])
;
PruneTraceTicketCode = empty
},
{ RestoreDeallocCode =
tree(RestoreSuccipCode,
tree(DeallocCode,
PruneTraceTicketCode))
},
code_info__get_maybe_trace_info(MaybeTraceInfo),
( { MaybeTraceInfo = yes(TraceInfo) } ->
% XXX A context that gives the end of the
% procedure definition would be better than
% CallContext.
trace__generate_external_event_code(exit, TraceInfo,
BodyContext, MaybeExitExternalInfo),
{
MaybeExitExternalInfo = yes(ExitExternalInfo),
ExitExternalInfo = external_event_info(
_, TypeInfoDatas, TraceExitCode)
;
MaybeExitExternalInfo = no,
TypeInfoDatas = map__init,
TraceExitCode = empty
},
{ map__values(TypeInfoDatas, TypeInfoLocnSets) },
{ FindBaseLvals = lambda([Lval::out] is nondet, (
list__member(LocnSet, TypeInfoLocnSets),
set__member(Locn, LocnSet),
(
Locn = direct(Lval)
;
Locn = indirect(Lval, _)
)
)) },
{ solutions(FindBaseLvals, TypeInfoLvals) },
{ set__insert_list(OutLvals, TypeInfoLvals,
LiveLvals) }
;
{ TraceExitCode = empty },
{ LiveLvals = OutLvals }
),
(
{ CodeModel = model_det },
{ SuccessCode = node([
livevals(LiveLvals) - "",
goto(succip) - "Return from procedure call"
]) },
{ AllSuccessCode =
tree(TraceExitCode,
tree(RestoreDeallocCode,
SuccessCode))
}
;
{ CodeModel = model_semi },
{ set__insert(LiveLvals, reg(r, 1), SuccessLiveRegs) },
{ SuccessCode = node([
assign(reg(r, 1), const(true)) - "Succeed",
livevals(SuccessLiveRegs) - "",
goto(succip) - "Return from procedure call"
]) },
{ AllSuccessCode =
tree(TraceExitCode,
tree(RestoreDeallocCode,
SuccessCode))
}
;
{ CodeModel = model_non },
{ MaybeTraceInfo = yes(TraceInfo2) ->
trace__maybe_setup_redo_event(TraceInfo2,
SetupRedoCode)
;
SetupRedoCode = empty
},
{ SuccessCode = node([
livevals(LiveLvals) - "",
goto(do_succeed(no))
- "Return from procedure call"
]) },
{ AllSuccessCode =
tree(SetupRedoCode,
tree(TraceExitCode,
SuccessCode))
}
),
{ ExitCode =
tree(StartComment,
tree(FlushCode,
tree(AllSuccessCode,
EndComment)))
}
).
%---------------------------------------------------------------------------%
% Generate a goal. This predicate arranges for the necessary updates of
% the generic data structures before and after the actual code generation,
% which is delegated to goal-specific predicates.
code_gen__generate_goal(ContextModel, Goal - GoalInfo, Code) -->
% Make any changes to liveness before Goal
{ goal_is_atomic(Goal) ->
IsAtomic = yes
;
IsAtomic = no
},
code_info__pre_goal_update(GoalInfo, IsAtomic),
code_info__get_instmap(Instmap),
(
{ instmap__is_reachable(Instmap) }
->
{ goal_info_get_code_model(GoalInfo, CodeModel) },
% sanity check: code of some code models
% should occur only in limited contexts
{
CodeModel = model_det
;
CodeModel = model_semi,
( ContextModel \= model_det ->
true
;
error("semidet model in det context")
)
;
CodeModel = model_non,
( ContextModel = model_non ->
true
;
error("nondet model in det/semidet context")
)
},
code_gen__generate_goal_2(Goal, GoalInfo, CodeModel, GoalCode),
% If the predicate's evaluation method is memo,
% loopcheck or minimal model, the goal generated
% the variable that represents the call table tip,
% *and* tracing is enabled, then we save this variable
% to its stack slot. This is necessary to enable
% retries across this procedure to reset the call table
% entry to uninitialized, effectively removing the
% call table entry.
(
{ goal_info_get_features(GoalInfo, Features) },
{ set__member(call_table_gen, Features) },
code_info__get_proc_info(ProcInfo),
{ proc_info_get_call_table_tip(ProcInfo,
MaybeCallTableVar) },
% MaybeCallTableVar will be `no' unless
% tracing is enabled.
{ MaybeCallTableVar = yes(CallTableVar) }
->
code_info__save_variables_on_stack([CallTableVar],
SaveCode),
{ Code = tree(GoalCode, SaveCode) }
;
{ Code = GoalCode }
),
% Make live any variables which subsequent goals
% will expect to be live, but were not generated
code_info__set_instmap(Instmap),
code_info__post_goal_update(GoalInfo)
;
{ Code = empty }
).
%---------------------------------------------------------------------------%
:- pred code_gen__generate_goal_2(hlds_goal_expr::in, hlds_goal_info::in,
code_model::in, code_tree::out, code_info::in, code_info::out) is det.
code_gen__generate_goal_2(unify(_, _, _, Uni, _), _, CodeModel, Code) -->
unify_gen__generate_unification(CodeModel, Uni, Code).
code_gen__generate_goal_2(conj(Goals), _GoalInfo, CodeModel, Code) -->
code_gen__generate_goals(Goals, CodeModel, Code).
code_gen__generate_goal_2(par_conj(Goals, _SM), GoalInfo, CodeModel, Code) -->
par_conj_gen__generate_par_conj(Goals, GoalInfo, CodeModel, Code).
code_gen__generate_goal_2(disj(Goals, StoreMap), _, CodeModel, Code) -->
disj_gen__generate_disj(CodeModel, Goals, StoreMap, Code).
code_gen__generate_goal_2(not(Goal), _GoalInfo, CodeModel, Code) -->
ite_gen__generate_negation(CodeModel, Goal, Code).
code_gen__generate_goal_2(if_then_else(_Vars, Cond, Then, Else, StoreMap),
_GoalInfo, CodeModel, Code) -->
ite_gen__generate_ite(CodeModel, Cond, Then, Else, StoreMap, Code).
code_gen__generate_goal_2(switch(Var, CanFail, CaseList, StoreMap),
GoalInfo, CodeModel, Code) -->
switch_gen__generate_switch(CodeModel, Var, CanFail, CaseList,
StoreMap, GoalInfo, Code).
code_gen__generate_goal_2(some(_Vars, _, Goal), _GoalInfo, CodeModel, Code) -->
commit_gen__generate_commit(CodeModel, Goal, Code).
code_gen__generate_goal_2(generic_call(GenericCall, Args, Modes, Det),
GoalInfo, CodeModel, Code) -->
call_gen__generate_generic_call(CodeModel, GenericCall, Args,
Modes, Det, GoalInfo, Code).
code_gen__generate_goal_2(call(PredId, ProcId, Args, BuiltinState, _, _),
GoalInfo, CodeModel, Code) -->
(
{ BuiltinState = not_builtin }
->
call_gen__generate_call(CodeModel, PredId, ProcId, Args,
GoalInfo, Code)
;
call_gen__generate_builtin(CodeModel, PredId, ProcId, Args,
Code)
).
code_gen__generate_goal_2(pragma_foreign_code(c, Attributes,
PredId, ModeId, Args, ArgNames, OrigArgTypes, PragmaCode),
GoalInfo, CodeModel, Instr) -->
pragma_c_gen__generate_pragma_c_code(CodeModel, Attributes,
PredId, ModeId, Args, ArgNames, OrigArgTypes, GoalInfo,
PragmaCode, Instr).
code_gen__generate_goal_2(bi_implication(_, _), _, _, _) -->
% these should have been expanded out by now
{ error("code_gen__generate_goal_2: unexpected bi_implication") }.
%---------------------------------------------------------------------------%
% Generate a conjoined series of goals.
% Note of course, that with a conjunction, state information
% flows directly from one conjunct to the next.
:- pred code_gen__generate_goals(hlds_goals::in, code_model::in,
code_tree::out, code_info::in, code_info::out) is det.
code_gen__generate_goals([], _, empty) --> [].
code_gen__generate_goals([Goal | Goals], CodeModel, Instr) -->
code_gen__generate_goal(CodeModel, Goal, Instr1),
code_info__get_instmap(Instmap),
(
{ instmap__is_unreachable(Instmap) }
->
{ Instr = Instr1 }
;
code_gen__generate_goals(Goals, CodeModel, Instr2),
{ Instr = tree(Instr1, Instr2) }
).
%---------------------------------------------------------------------------%
:- pred code_gen__select_args_with_mode(assoc_list(prog_var, arg_info)::in,
arg_mode::in, list(prog_var)::out, list(lval)::out) is det.
code_gen__select_args_with_mode([], _, [], []).
code_gen__select_args_with_mode([Var - ArgInfo | Args], DesiredMode, Vs, Ls) :-
code_gen__select_args_with_mode(Args, DesiredMode, Vs0, Ls0),
ArgInfo = arg_info(Loc, Mode),
(
Mode = DesiredMode
->
code_util__arg_loc_to_register(Loc, Reg),
Vs = [Var | Vs0],
Ls = [Reg | Ls0]
;
Vs = Vs0,
Ls = Ls0
).
%---------------------------------------------------------------------------%
% Add the succip to the livevals before and after calls.
% Traverses the list of instructions looking for livevals and calls,
% adding succip in the stackvar number given as an argument.
:- pred code_gen__add_saved_succip(list(instruction)::in, int::in,
list(instruction)::out) is det.
code_gen__add_saved_succip([], _StackLoc, []).
code_gen__add_saved_succip([Instrn0 - Comment | Instrns0 ], StackLoc,
[Instrn - Comment | Instrns]) :-
(
Instrn0 = livevals(LiveVals0),
Instrns0 \= [goto(succip) - _ | _]
% XXX We should also test for tailcalls
% once we start generating them directly.
->
set__insert(LiveVals0, stackvar(StackLoc), LiveVals1),
Instrn = livevals(LiveVals1)
;
Instrn0 = call(Target, ReturnLabel, LiveVals0, Context, GP, CM)
->
map__init(Empty),
LiveVals = [live_lvalue(direct(stackvar(StackLoc)),
succip, Empty) | LiveVals0],
Instrn = call(Target, ReturnLabel, LiveVals, Context, GP, CM)
;
Instrn = Instrn0
),
code_gen__add_saved_succip(Instrns0, StackLoc, Instrns).
%---------------------------------------------------------------------------%