Files
mercury/browser/declarative_debugger.m
Zoltan Somogyi 048f8357cf Until now, programmers could add `tabled_for_io' annotations to foreign_procs
Estimated hours taken: 12
Branches: main

Until now, programmers could add `tabled_for_io' annotations to foreign_procs
that do I/O, which asks the compiler to make those foreign_procs idempotent,
i.e. ensures that they are performed at most once even in the presence of a
retry operation in the debugger. This change adds a compiler option,
--trace-table-io-require, which generates an error if a foreign_proc that does
I/O does not have this annotation. Specifying this option thus ensures
that all I/O done by the program is idempotent.

In the future, we may want to have this option turned on in all debugging
grades. Until we decide about, the new option is not yet documented.

compiler/options.m:
	Add the new option --trace-table-io-require.

compiler/handle_options.m:
	Make --trace-table-io-require imply --trace-table-io.

compiler/table_gen.m:
	If --trace-table-io-require is enabled, require all I/O primitives
	to have the tabled_for_io annotation.

compiler/mercury_compile.m:
	Pass I/O states to table_gen.m, since it can now generate error
	messages.

trace/mercury_trace_util.h:
trace/mercury_trace_vars.c:
	When calling Mercury code from the trace directory, disable I/O
	tabling, since any I/O actions executed by Mercury code in the browser
	directory (or by library code called from there) should not be tabled,
	not being part of the user program.

	Due to the depth of nesting, make mercury_trace_vars.c use four-space
	indentation.

browser/collect_lib.m:
browser/declarative_debugger.m:
browser/declarative_execution.m:
browser/dl.m:
browser/io_action.m:
browser/mdb.m:
browser/name_mangle.m:
browser/util.m:
compiler/gcc.m:
compiler/mercury_compile.m:
compiler/passes_aux.m:
compiler/process_util.m:
compiler/stack_layout.m:
library/io.m:
library/time.m:
tests/debugger/declarative/tabled_read_decl.m:
	Add a whole lot of tabled_for_io annotations, to enable the compiler to
	bootstrap with --trace-table-io-require enabled.

	In many cases, this required turning old-style pragma c_code into
	pragma foreign_proc. While doing that, I standardized the layouts
	of pragma foreign_procs.

browser/util.m:
	Turn an impure semidet predicate into a pure det predicate with I/O
	states, to allow it to be tabled. Make it return a Mercury bool
	to indicate success or failure.

library/bool.m:
	Add functions that allow C code to get their hands on the constants
	`yes' and `no', for communication with Mercury code.

library/table_builtin.m:
	Add debugging code to the main primitive of I/O tabling. This is
	controlled both by the macro for retry debugging and a boolean global.

library/mercury_trace_base.[ch]:
	Add the boolean global variable to switch the new debugging code in
	table_builtin.m on and off.

library/mercury_trace_internal.c:
	When starting I/O tabling with retry debug enabled, turn on the switch.

tests/debugger/queens.exp3:
	New expected output file that applies when the library is compiled with
	--trace-table-io-require.
2002-07-22 07:13:14 +00:00

1369 lines
42 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 1999-2002 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% File: declarative_debugger.m
% Author: Mark Brown
%
% This module has two main purposes:
% - to define the interface between the front and back ends of
% a Mercury declarative debugger, and
% - to implement a front end.
%
% The interface is defined by a procedure that can be called from
% the back end to perform diagnosis, and a typeclass which represents
% a declarative view of execution used by the front end.
%
% The front end implemented in this module analyses the EDT it is
% passed to diagnose a bug. It does this by a simple top-down search.
%
:- module mdb__declarative_debugger.
:- interface.
:- import_module mdb__declarative_execution, mdb__program_representation.
:- import_module mdb__io_action.
:- import_module io, bool, list, std_util.
% This type represents the possible truth values for nodes
% in the EDT.
%
:- type decl_truth == bool.
% This type represents the possible responses to being
% asked to confirm that a node is a bug.
%
:- type decl_confirmation
---> confirm_bug
; overrule_bug
; abort_diagnosis.
% This type represents the bugs which can be diagnosed.
% The parameter of the constructor is the type of EDT nodes.
%
:- type decl_bug
% An EDT whose root node is incorrect,
% but whose children are all correct.
%
---> e_bug(decl_e_bug)
% An EDT whose root node is incorrect, and
% which has no incorrect children but at
% least one inadmissible one.
%
; i_bug(decl_i_bug).
:- type decl_e_bug
---> incorrect_contour(
final_decl_atom,% The head of the clause, in its
% final state of instantiation.
decl_contour, % The path taken through the body.
event_number % The exit event.
)
; partially_uncovered_atom(
init_decl_atom, % The called atom, in its initial
% state.
event_number % The fail event.
)
; unhandled_exception(
init_decl_atom, % The called atom, in its initial
% state.
decl_exception, % The exception thrown.
event_number % The excp event.
).
:- type decl_i_bug
---> inadmissible_call(
init_decl_atom, % The parent atom, in its initial
% state.
decl_position, % The location of the call in the
% parent's body.
init_decl_atom, % The inadmissible child, in its
% initial state.
event_number % The call event.
).
% XXX not yet implemented.
%
:- type decl_contour == unit.
:- type decl_position == unit.
% Values of the following two types represent questions from the
% analyser to the oracle about some aspect of program behaviour,
% and responses from the oracle, respectively. In both cases the
% type parameter is for the type of EDT nodes -- each question and
% answer keeps a reference to the node which generated it, so that
% the analyser is able to figure out what to do when the answer
% arrives back from the oracle.
%
:- type decl_question(T)
% The node is a suspected wrong answer. The first
% argument is the EDT node the question came from.
% The second argument is the atom in its final
% state of instantiatedness (ie. at the EXIT event).
%
---> wrong_answer(T, final_decl_atom)
% The node is a suspected missing answer. The
% first argument is the EDT node the question came
% from. The second argument is the atom in its
% initial state of instantiatedness (ie. at the
% CALL event), and the third argument is the list
% of solutions.
%
; missing_answer(T, init_decl_atom, list(final_decl_atom))
% The node is a possibly unexpected exception.
% The first argument is the EDT node the question
% came from. The second argument is the atom in
% its initial state of instantiation, and the third
% argument is the exception thrown.
%
; unexpected_exception(T, init_decl_atom, decl_exception).
:- type decl_answer(T)
% The oracle knows the truth value of this node.
%
---> truth_value(T, decl_truth)
% The oracle does not say anything about the truth
% value, but is suspicious of the subterm at the
% given term_path and arg_pos.
%
; suspicious_subterm(T, arg_pos, term_path).
% Extract the EDT node from a question.
%
:- func get_decl_question_node(decl_question(T)) = T.
:- type some_decl_atom
---> init(init_decl_atom)
; final(final_decl_atom).
:- type init_decl_atom
---> init_decl_atom(
init_atom :: trace_atom
).
:- type final_decl_atom
---> final_decl_atom(
final_atom :: trace_atom,
final_io_actions :: list(io_action)
).
:- type decl_exception == univ.
% The diagnoser eventually responds with a value of this type
% after it is called.
%
:- type diagnoser_response
% There was a bug found and confirmed. The
% event number is for a call port (inadmissible
% call), an exit port (incorrect contour),
% or a fail port (partially uncovered atom).
%
---> bug_found(event_number)
% There was no symptom found, or the diagnoser
% aborted before finding a bug.
%
; no_bug_found
% The analyser requires the back end to reproduce
% part of the annotated trace, with a greater
% depth bound. The event number and sequence
% number are for the final event required (the
% first event required is the call event with
% the same sequence number).
%
; require_subtree(event_number, sequence_number).
:- type diagnoser_state(R).
:- pred diagnoser_state_init(io_action_map::in, io__input_stream::in,
io__output_stream::in, diagnoser_state(R)::out) is det.
:- pred diagnosis(S::in, R::in, int::in, int::in, int::in,
diagnoser_response::out,
diagnoser_state(R)::in, diagnoser_state(R)::out,
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
:- pred unravel_decl_atom(some_decl_atom::in, trace_atom::out,
list(io_action)::out) is det.
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module mdb__declarative_analyser, mdb__declarative_oracle.
:- import_module require, int, char, string, assoc_list, map.
unravel_decl_atom(DeclAtom, TraceAtom, IoActions) :-
(
DeclAtom = init(init_decl_atom(TraceAtom)),
IoActions = []
;
DeclAtom = final(final_decl_atom(TraceAtom, IoActions))
).
get_decl_question_node(wrong_answer(Node, _)) = Node.
get_decl_question_node(missing_answer(Node, _, _)) = Node.
get_decl_question_node(unexpected_exception(Node, _, _)) = Node.
%-----------------------------------------------------------------------------%
:- type diagnoser_state(R)
---> diagnoser(
analyser_state :: analyser_state(edt_node(R)),
oracle_state :: oracle_state
).
:- pred diagnoser_get_analyser(diagnoser_state(R),
analyser_state(edt_node(R))).
:- mode diagnoser_get_analyser(in, out) is det.
diagnoser_get_analyser(diagnoser(Analyser, _), Analyser).
:- pred diagnoser_set_analyser(diagnoser_state(R), analyser_state(edt_node(R)),
diagnoser_state(R)).
:- mode diagnoser_set_analyser(in, in, out) is det.
diagnoser_set_analyser(diagnoser(_, B), A, diagnoser(A, B)).
:- pred diagnoser_get_oracle(diagnoser_state(R), oracle_state).
:- mode diagnoser_get_oracle(in, out) is det.
diagnoser_get_oracle(diagnoser(_, Oracle), Oracle).
:- pred diagnoser_set_oracle(diagnoser_state(R), oracle_state,
diagnoser_state(R)).
:- mode diagnoser_set_oracle(in, in, out) is det.
diagnoser_set_oracle(diagnoser(A, _), B, diagnoser(A, B)).
diagnoser_state_init(IoActionMap, InStr, OutStr, Diagnoser) :-
analyser_state_init(IoActionMap, Analyser),
oracle_state_init(InStr, OutStr, Oracle),
Diagnoser = diagnoser(Analyser, Oracle).
diagnosis(Store, NodeId, UseOldIoActionMap, IoActionStart, IoActionEnd,
Response, Diagnoser0, Diagnoser) -->
( { UseOldIoActionMap > 0 } ->
{ Diagnoser1 = Diagnoser0 },
{ diagnoser_get_analyser(Diagnoser1, Analyser1) }
;
make_io_action_map(IoActionStart, IoActionEnd, IoActionMap),
{ Analyser0 = Diagnoser0 ^ analyser_state },
{ analyser_state_replace_io_map(IoActionMap,
Analyser0, Analyser1) },
{ Diagnoser1 = Diagnoser0 ^ analyser_state := Analyser1 }
),
{ start_analysis(wrap(Store), dynamic(NodeId), AnalyserResponse,
Analyser1, Analyser) },
{ diagnoser_set_analyser(Diagnoser1, Analyser, Diagnoser2) },
{ debug_analyser_state(Analyser, MaybeOrigin) },
handle_analyser_response(Store, AnalyserResponse, MaybeOrigin,
Response, Diagnoser2, Diagnoser).
:- pred handle_analyser_response(S::in, analyser_response(edt_node(R))::in,
maybe(subterm_origin(edt_node(R)))::in, diagnoser_response::out,
diagnoser_state(R)::in, diagnoser_state(R)::out,
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
handle_analyser_response(_, no_suspects, _, no_bug_found, D, D) -->
io__write_string("No bug found.\n").
handle_analyser_response(_, bug_found(Bug), _, Response, Diagnoser0,
Diagnoser) -->
confirm_bug(Bug, Response, Diagnoser0, Diagnoser).
handle_analyser_response(Store, oracle_queries(Queries), MaybeOrigin, Response,
Diagnoser0, Diagnoser) -->
{ diagnoser_get_oracle(Diagnoser0, Oracle0) },
debug_origin(Flag),
(
{ MaybeOrigin = yes(Origin) },
{ Flag > 0 }
->
io__write_string("Origin: "),
write_origin(wrap(Store), Origin),
io__nl
;
[]
),
query_oracle(Queries, OracleResponse, Oracle0, Oracle),
{ diagnoser_set_oracle(Diagnoser0, Oracle, Diagnoser1) },
handle_oracle_response(Store, OracleResponse, Response, Diagnoser1,
Diagnoser).
handle_analyser_response(Store, require_explicit(Tree), _, Response,
Diagnoser, Diagnoser) -->
{
edt_subtree_details(Store, Tree, Event, Seqno),
Response = require_subtree(Event, Seqno)
}.
:- pred handle_oracle_response(S::in, oracle_response(edt_node(R))::in,
diagnoser_response::out,
diagnoser_state(R)::in, diagnoser_state(R)::out,
io__state::di, io__state::uo) is cc_multi <= annotated_trace(S, R).
handle_oracle_response(Store, oracle_answers(Answers), Response, Diagnoser0,
Diagnoser) -->
{ diagnoser_get_analyser(Diagnoser0, Analyser0) },
{ continue_analysis(wrap(Store), Answers, AnalyserResponse,
Analyser0, Analyser) },
{ diagnoser_set_analyser(Diagnoser0, Analyser, Diagnoser1) },
{ debug_analyser_state(Analyser, MaybeOrigin) },
handle_analyser_response(Store, AnalyserResponse, MaybeOrigin,
Response, Diagnoser1, Diagnoser).
handle_oracle_response(_, no_oracle_answers, no_bug_found, D, D) -->
[].
handle_oracle_response(_, abort_diagnosis, no_bug_found, D, D) -->
io__write_string("Diagnosis aborted.\n").
:- pred confirm_bug(decl_bug::in, diagnoser_response::out,
diagnoser_state(R)::in, diagnoser_state(R)::out,
io__state::di, io__state::uo) is cc_multi.
confirm_bug(Bug, Response, Diagnoser0, Diagnoser) -->
{ diagnoser_get_oracle(Diagnoser0, Oracle0) },
oracle_confirm_bug(Bug, Confirmation, Oracle0, Oracle),
{ diagnoser_set_oracle(Diagnoser0, Oracle, Diagnoser) },
{
Confirmation = confirm_bug,
decl_bug_get_event_number(Bug, Event),
Response = bug_found(Event)
;
Confirmation = overrule_bug,
Response = no_bug_found
;
Confirmation = abort_diagnosis,
Response = no_bug_found
}.
% Export a monomorphic version of diagnosis_state_init/4, to
% make it easier to call from C code.
%
:- pred diagnoser_state_init_store(io__input_stream, io__output_stream,
diagnoser_state(trace_node_id)).
:- mode diagnoser_state_init_store(in, in, out) is det.
:- pragma export(diagnoser_state_init_store(in, in, out),
"MR_DD_decl_diagnosis_state_init").
diagnoser_state_init_store(InStr, OutStr, Diagnoser) :-
diagnoser_state_init(map__init, InStr, OutStr, Diagnoser).
% Export a monomorphic version of diagnosis/10, to make it
% easier to call from C code.
%
:- pred diagnosis_store(trace_node_store::in, trace_node_id::in,
int::in, int::in, int::in, diagnoser_response::out,
diagnoser_state(trace_node_id)::in,
diagnoser_state(trace_node_id)::out, io__state::di, io__state::uo)
is cc_multi.
:- pragma export(diagnosis_store(in, in, in, in, in, out, in, out, di, uo),
"MR_DD_decl_diagnosis").
diagnosis_store(Store, Node, UseOldIoActionMap, IoActionStart, IoActionEnd,
Response, State0, State) -->
diagnosis(Store, Node, UseOldIoActionMap, IoActionStart, IoActionEnd,
Response, State0, State).
% Export some predicates so that C code can interpret the
% diagnoser response.
%
:- pred diagnoser_bug_found(diagnoser_response, event_number).
:- mode diagnoser_bug_found(in, out) is semidet.
:- pragma export(diagnoser_bug_found(in, out), "MR_DD_diagnoser_bug_found").
diagnoser_bug_found(bug_found(Event), Event).
:- pred diagnoser_require_subtree(diagnoser_response, event_number,
sequence_number).
:- mode diagnoser_require_subtree(in, out, out) is semidet.
:- pragma export(diagnoser_require_subtree(in, out, out),
"MR_DD_diagnoser_require_subtree").
diagnoser_require_subtree(require_subtree(Event, SeqNo), Event, SeqNo).
%-----------------------------------------------------------------------------%
%
% This section defines an instance of the EDT in terms of
% any instance of execution tree.
%
% The type of nodes in our implementation of EDTs. The parameter
% is meant to be the type of references to trace nodes. In
% particular, the references should be to trace nodes that could
% be considered nodes in the EDT, namely those for exit, fail
% and exception events.
%
:- type edt_node(R)
---> dynamic(R).
:- instance mercury_edt(wrap(S), edt_node(R)) <= annotated_trace(S, R)
where [
pred(edt_root_question/4) is trace_root_question,
pred(edt_root_e_bug/4) is trace_root_e_bug,
pred(edt_children/3) is trace_children,
pred(edt_dependency/6) is trace_dependency
].
% The wrap/1 around the first argument of the instance is
% required by the language.
%
:- type wrap(S) ---> wrap(S).
%-----------------------------------------------------------------------------%
:- func exit_node_decl_atom(io_action_map::in, S::in,
trace_node(R)::in(trace_node_exit)) = (final_decl_atom::out) is det
<= annotated_trace(S, R).
exit_node_decl_atom(IoActionMap, Store, ExitNode) = DeclAtom :-
ExitAtom = ExitNode ^ exit_atom,
CallId = ExitNode ^ exit_call,
call_node_from_id(Store, CallId, Call),
CallIoSeq = Call ^ call_io_seq_num,
ExitIoSeq = ExitNode ^ exit_io_seq_num,
IoActions = make_io_actions(IoActionMap, CallIoSeq, ExitIoSeq),
DeclAtom = final_decl_atom(ExitAtom, IoActions).
:- func call_node_decl_atom(S, R) = init_decl_atom <= annotated_trace(S, R).
call_node_decl_atom(Store, CallId) = DeclAtom :-
call_node_from_id(Store, CallId, CallNode),
CallAtom = CallNode ^ call_atom,
DeclAtom = init_decl_atom(CallAtom).
:- func make_io_actions(io_action_map, int, int) = list(io_action).
make_io_actions(IoActionMap, InitIoSeq, ExitIoSeq) =
( InitIoSeq = ExitIoSeq ->
[]
;
[map__lookup(IoActionMap, InitIoSeq) |
make_io_actions(IoActionMap, InitIoSeq + 1, ExitIoSeq)]
).
%-----------------------------------------------------------------------------%
:- pred trace_root_question(io_action_map::in, wrap(S)::in, edt_node(R)::in,
decl_question(edt_node(R))::out) is det <= annotated_trace(S, R).
trace_root_question(IoActionMap, wrap(Store), dynamic(Ref), Root) :-
det_edt_return_node_from_id(Store, Ref, Node),
(
Node = fail(_, CallId, RedoId, _),
DeclAtom = call_node_decl_atom(Store, CallId),
get_answers(IoActionMap, Store, RedoId, [], Answers),
Root = missing_answer(dynamic(Ref), DeclAtom, Answers)
;
Node = exit(_, _, _, _, _, _),
DeclAtom = exit_node_decl_atom(IoActionMap, Store, Node),
Root = wrong_answer(dynamic(Ref), DeclAtom)
;
Node = excp(_, CallId, _, Exception, _),
DeclAtom = call_node_decl_atom(Store, CallId),
Root = unexpected_exception(dynamic(Ref), DeclAtom, Exception)
).
:- pred get_answers(io_action_map::in, S::in, R::in,
list(final_decl_atom)::in, list(final_decl_atom)::out) is det
<= annotated_trace(S, R).
get_answers(IoActionMap, Store, RedoId, DeclAtoms0, DeclAtoms) :-
(
maybe_redo_node_from_id(Store, RedoId, redo(_, ExitId))
->
exit_node_from_id(Store, ExitId, ExitNode),
NextId = ExitNode ^ exit_prev_redo,
DeclAtom = exit_node_decl_atom(IoActionMap, Store, ExitNode),
get_answers(IoActionMap, Store, NextId,
[DeclAtom | DeclAtoms0], DeclAtoms)
;
DeclAtoms = DeclAtoms0
).
:- pred trace_root_e_bug(io_action_map::in, wrap(S)::in, edt_node(R)::in,
decl_e_bug::out) is det <= annotated_trace(S, R).
trace_root_e_bug(IoActionMap, wrap(Store), dynamic(Ref), Bug) :-
det_edt_return_node_from_id(Store, Ref, Node),
(
Node = exit(_, _, _, _, Event, _),
DeclAtom = exit_node_decl_atom(IoActionMap, Store, Node),
Bug = incorrect_contour(DeclAtom, unit, Event)
;
Node = fail(_, CallId, _, Event),
DeclAtom = call_node_decl_atom(Store, CallId),
Bug = partially_uncovered_atom(DeclAtom, Event)
;
Node = excp(_, CallId, _, Exception, Event),
DeclAtom = call_node_decl_atom(Store, CallId),
Bug = unhandled_exception(DeclAtom, Exception, Event)
).
:- pred trace_children(wrap(S), edt_node(R), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode trace_children(in, in, out) is semidet.
trace_children(wrap(Store), dynamic(Ref), Children) :-
det_edt_return_node_from_id(Store, Ref, Node),
(
Node = fail(PrecId, CallId, _, _),
not_at_depth_limit(Store, CallId),
missing_answer_children(Store, PrecId, [], Children)
;
Node = exit(PrecId, CallId, _, _, _, _),
not_at_depth_limit(Store, CallId),
wrong_answer_children(Store, PrecId, [], Children)
;
Node = excp(PrecId, CallId, _, _, _),
not_at_depth_limit(Store, CallId),
unexpected_exception_children(Store, PrecId, [], Children)
).
:- pred not_at_depth_limit(S, R) <= annotated_trace(S, R).
:- mode not_at_depth_limit(in, in) is semidet.
not_at_depth_limit(Store, Ref) :-
call_node_from_id(Store, Ref, CallNode),
CallNode ^ call_at_max_depth = no.
:- pred wrong_answer_children(S, R, list(edt_node(R)), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode wrong_answer_children(in, in, in, out) is det.
wrong_answer_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _, _, _)
; Node = neg(_, _, _)
; Node = cond(_, _, failed)
)
->
%
% We have reached the end of the contour.
%
Ns = Ns0
;
Node = excp(_, _, _, _, _)
->
error("wrong_answer_children: exception handling not supported")
;
(
Node = exit(_, _, _, _, _, _)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_left_in_contour(Store, Node),
wrong_answer_children(Store, Next, Ns1, Ns)
).
:- pred missing_answer_children(S, R, list(edt_node(R)), list(edt_node(R)))
<= annotated_trace(S, R).
:- mode missing_answer_children(in, in, in, out) is det.
missing_answer_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _, _, _)
; Node = neg(_, _, _)
; Node = cond(_, _, failed)
)
->
%
% We have reached the boundary of the stratum.
%
Ns = Ns0
;
Node = excp(_, _, _, _, _)
->
error(
"missing_answer_children: exception handling not supported")
;
(
( Node = exit(_, _, _, _, _, _)
; Node = fail(_, _, _, _)
)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
Node = neg_fail(Prec, _)
->
%
% There is a nested successful context.
%
wrong_answer_children(Store, Prec, Ns0, Ns1)
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested failed context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_in_stratum(Store, Node),
missing_answer_children(Store, Next, Ns1, Ns)
).
:- pred unexpected_exception_children(S, R, list(edt_node(R)),
list(edt_node(R))) <= annotated_trace(S, R).
:- mode unexpected_exception_children(in, in, in, out) is det.
unexpected_exception_children(Store, NodeId, Ns0, Ns) :-
det_trace_node_from_id(Store, NodeId, Node),
(
( Node = call(_, _, _, _, _, _, _, _, _)
; Node = neg(_, _, failed)
; Node = cond(_, _, failed)
)
->
%
% We have reached the end of the contour.
%
Ns = Ns0
;
(
( Node = exit(_, _, _, _, _, _)
; Node = excp(_, _, _, _, _)
)
->
%
% Add a child for this node.
%
Ns1 = [dynamic(NodeId) | Ns0]
;
( Node = else(Prec, _)
; Node = neg_succ(Prec, _)
)
->
%
% There is a nested context.
%
missing_answer_children(Store, Prec, Ns0, Ns1)
;
Ns1 = Ns0
),
Next = step_left_in_contour(Store, Node),
unexpected_exception_children(Store, Next, Ns1, Ns)
).
%-----------------------------------------------------------------------------%
%
% Tracking a subterm dependency.
%
% We are given an EDT node, an argument position, and a path to the selected
% subterm. We wish to find the origin of that subterm within the body of the
% given node, or within the body of its parent. We can figure out the mode of
% the top of the selected subterm.
%
% If the mode is `in', the origin could be:
% - a primitive (unification of foreign_proc) within the body of the
% parent,
% - an output subterm in a sibling node, or
% - an input subterm of the parent node.
% In this case we look at the contour leading up to the call event associated
% with the given node. This contour will be wholly within the parent call.
%
% If the mode is `out', the origin could be:
% - a primitive (unification or foreign_proc) within the body of the
% call,
% - an output subterm of a child of the node, or
% - an input subterm of the node itself.
% In this case we look at the contour leading up to the exit or exception event
% associated with the given node. This contour will be wholly within the
% current call.
%
% Our algorithm for finding the origin has three phases.
%
% In the first phase, we materialize a list of the nodes in the contour.
%
% In the second phase, we use this list of nodes to construct a list of the
% primitive goals along that contour in the body of the relevant procedure,
% leading up to either the call event (if subterm_mode is `in') or the exit
% event (if subterm_mode is `out').
%
% In the third phase, we traverse the list of primitive goals backwards, from
% the most recently executed primitive to the earliest one, keeping track of
% the variable which contains the selected subterm, and the location within
% this variable.
:- type dependency_chain_start(R)
---> chain_start(
start_loc(R),
int, % The argument number of the selected
% position in the full list of
% arguments, including the
% compiler-generated ones.
R, % The id of the node preceding the exit
% node, if start_loc is cur_goal
% and the id of the node preceding the
% call node if start_loc is
% parent_goal.
maybe(goal_path),
% No if start_loc is cur_goal;
% and yes wrapped around the goal path
% of the call in the parent procedure
% if start_loc is parent_goal.
maybe(proc_rep) % The body of the procedure indicated
% by start_loc.
).
:- type start_loc(R)
---> cur_goal
; parent_goal(R, trace_node(R)).
:- type goal_and_path ---> goal_and_path(goal_rep, goal_path).
:- type goal_and_path_list == list(goal_and_path).
:- type annotated_primitive(R)
---> primitive(
string, % filename
int, % line number
list(var_rep), % vars bound by the atomic goal
atomic_goal_rep,% the atomic goal itself
goal_path, % its goal path
maybe(R)
% if the atomic goal is a call,
% the id of the call's exit event
).
:- pred trace_dependency(wrap(S)::in, edt_node(R)::in,
arg_pos::in, term_path::in, subterm_mode::out,
subterm_origin(edt_node(R))::out) is det <= annotated_trace(S, R).
trace_dependency(wrap(Store), dynamic(Ref), ArgPos, TermPath, Mode, Origin) :-
find_chain_start(Store, Ref, ArgPos, TermPath, ChainStart),
ChainStart = chain_start(StartLoc, ArgNum, NodeId, StartPath,
MaybeProcRep),
Mode = start_loc_to_subterm_mode(StartLoc),
(
MaybeProcRep = no,
Origin = not_found
;
MaybeProcRep = yes(ProcRep),
det_trace_node_from_id(Store, NodeId, Node),
materialize_contour(Store, NodeId, Node, [], Contour0),
(
StartLoc = parent_goal(CallId, CallNode),
Contour = list__append(Contour0, [CallId - CallNode])
;
StartLoc = cur_goal,
Contour = Contour0
),
ProcRep = proc_rep(HeadVars, GoalRep),
make_primitive_list(Store, [goal_and_path(GoalRep, [])],
Contour, StartPath, ArgNum, HeadVars, Var,
[], Primitives),
traverse_primitives(Primitives, Var, TermPath,
Store, ProcRep, Origin)
).
:- pred find_chain_start(S::in, R::in, arg_pos::in, term_path::in,
dependency_chain_start(R)::out) is det <= annotated_trace(S, R).
find_chain_start(Store, Ref, ArgPos, TermPath, ChainStart) :-
det_edt_return_node_from_id(Store, Ref, Node),
(
Node = exit(_, CallId, _, ExitAtom, _, _),
call_node_from_id(Store, CallId, CallNode),
CallAtom = CallNode ^ call_atom,
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
find_chain_start_inside(Store, CallId, CallNode,
ArgPos, ChainStart)
; trace_atom_subterm_is_ground(ExitAtom, ArgPos, TermPath) ->
find_chain_start_outside(CallNode, Node, ArgPos,
ChainStart)
;
error("find_chain_start: unbound wrong answer term")
)
;
Node = fail(_, CallId, _, _),
call_node_from_id(Store, CallId, CallNode),
CallAtom = CallNode ^ call_atom,
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
find_chain_start_inside(Store, CallId, CallNode,
ArgPos, ChainStart)
;
error("find_chain_start: unbound missing answer term")
)
;
Node = excp(_, CallId, _, _, _),
call_node_from_id(Store, CallId, CallNode),
CallAtom = CallNode ^ call_atom,
%
% XXX we don't yet handle tracking of the exception value.
%
( trace_atom_subterm_is_ground(CallAtom, ArgPos, TermPath) ->
find_chain_start_inside(Store, CallId, CallNode,
ArgPos, ChainStart)
;
error("find_chain_start: unbound exception term")
)
).
:- pred find_chain_start_inside(S::in, R::in,
trace_node(R)::in(trace_node_call), arg_pos::in,
dependency_chain_start(R)::out) is det <= annotated_trace(S, R).
find_chain_start_inside(Store, CallId, CallNode, ArgPos, ChainStart) :-
CallPrecId = CallNode ^ call_preceding,
CallAtom = CallNode ^ call_atom,
CallPathStr = CallNode ^ call_goal_path,
path_from_string_det(CallPathStr, CallPath),
StartLoc = parent_goal(CallId, CallNode),
absolute_arg_num(ArgPos, CallAtom, ArgNum),
StartId = CallPrecId,
StartPath = yes(CallPath),
parent_proc_rep(Store, CallId, StartRep),
ChainStart = chain_start(StartLoc, ArgNum, StartId, StartPath,
StartRep).
:- pred find_chain_start_outside(trace_node(R)::in(trace_node_call),
trace_node(R)::in(trace_node_exit), arg_pos::in,
dependency_chain_start(R)::out) is det.
find_chain_start_outside(CallNode, ExitNode, ArgPos, ChainStart) :-
StartLoc = cur_goal,
ExitAtom = ExitNode ^ exit_atom,
absolute_arg_num(ArgPos, ExitAtom, ArgNum),
StartId = ExitNode ^ exit_preceding,
StartPath = no,
StartRep = CallNode ^ call_proc_rep,
ChainStart = chain_start(StartLoc, ArgNum, StartId,
StartPath, StartRep).
:- pred parent_proc_rep(S::in, R::in, maybe(proc_rep)::out)
is det <= annotated_trace(S, R).
parent_proc_rep(Store, CallId, ProcRep) :-
call_node_from_id(Store, CallId, Call),
CallPrecId = Call ^ call_preceding,
( trace_node_from_id(Store, CallPrecId, CallPrecNode) ->
step_left_to_call(Store, CallPrecNode, ParentCallNode),
ProcRep = ParentCallNode ^ call_proc_rep
;
% The parent call is outside the annotated trace.
ProcRep = no
).
:- pred step_left_to_call(S::in, trace_node(R)::in,
trace_node(R)::out(trace_node_call)) is det <= annotated_trace(S, R).
step_left_to_call(Store, Node, ParentCallNode) :-
( Node = call(_, _, _, _, _, _, _, _, _) ->
ParentCallNode = Node
;
( Node = neg(NegPrec, _, _) ->
PrevNodeId = NegPrec
;
PrevNodeId = step_left_in_contour(Store, Node)
),
det_trace_node_from_id(Store, PrevNodeId, PrevNode),
step_left_to_call(Store, PrevNode, ParentCallNode)
).
:- pred materialize_contour(S::in, R::in, trace_node(R)::in,
assoc_list(R, trace_node(R))::in, assoc_list(R, trace_node(R))::out)
is det <= annotated_trace(S, R).
materialize_contour(Store, NodeId, Node, Nodes0, Nodes) :-
( Node = call(_, _, _, _, _, _, _, _, _) ->
Nodes = Nodes0
;
( Node = neg(NegPrec, _, _) ->
PrevNodeId = NegPrec
;
PrevNodeId = step_left_in_contour(Store, Node)
),
det_trace_node_from_id(Store, PrevNodeId, PrevNode),
( Node = then(_, _) ->
% The cond node is enough to tell us which way the
% if-then-else went; the then node would just
% complicate the job of make_primitive_list.
Nodes1 = Nodes0
;
Nodes1 = [NodeId - Node | Nodes0]
),
materialize_contour(Store, PrevNodeId, PrevNode,
Nodes1, Nodes)
).
:- pred make_primitive_list(S::in, goal_and_path_list::in,
assoc_list(R, trace_node(R))::in, maybe(goal_path)::in,
int::in, list(var_rep)::in, var_rep::out,
list(annotated_primitive(R))::in, list(annotated_primitive(R))::out)
is det <= annotated_trace(S, R).
make_primitive_list(Store, [goal_and_path(Goal, Path) | GoalPaths],
Contour, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives) :-
(
Goal = conj_rep(Conjs),
add_paths_to_conjuncts(Conjs, Path, 1, ConjPaths),
make_primitive_list(Store, list__append(ConjPaths, GoalPaths),
Contour, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
Goal = disj_rep(Disjs),
(
Contour = [_ - ContourHeadNode | ContourTail],
( ContourHeadNode = first_disj(_, DisjPathStr)
; ContourHeadNode = later_disj(_, DisjPathStr, _)
),
path_from_string_det(DisjPathStr, DisjPath),
list__append(Path, PathTail, DisjPath),
PathTail = [disj(N)]
->
list__index1_det(Disjs, N, Disj),
DisjAndPath = goal_and_path(Disj, DisjPath),
make_primitive_list(Store, [DisjAndPath | GoalPaths],
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
error("make_primitive_list: mismatch on disj")
)
;
Goal = switch_rep(Arms),
(
Contour = [_ - ContourHeadNode | ContourTail],
ContourHeadNode = switch(_, ArmPathStr),
path_from_string_det(ArmPathStr, ArmPath),
list__append(Path, PathTail, ArmPath),
PathTail = [switch(N)]
->
list__index1_det(Arms, N, Arm),
ArmAndPath = goal_and_path(Arm, ArmPath),
make_primitive_list(Store, [ArmAndPath | GoalPaths],
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
error("make_primitive_list: mismatch on switch")
)
;
Goal = ite_rep(Cond, Then, Else),
(
Contour = [_ - ContourHeadNode | ContourTail],
ContourHeadNode = cond(_, CondPathStr, _),
path_from_string_det(CondPathStr, CondPath),
list__append(Path, PathTail, CondPath),
PathTail = [ite_cond]
->
ThenPath = list__append(Path, [ite_then]),
CondAndPath = goal_and_path(Cond, CondPath),
ThenAndPath = goal_and_path(Then, ThenPath),
make_primitive_list(Store,
[CondAndPath, ThenAndPath | GoalPaths],
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
Contour = [_ - ContourHeadNode | ContourTail],
ContourHeadNode = else(_, ElseCondId),
cond_node_from_id(Store, ElseCondId, CondNode),
CondNode = cond(_, CondPathStr, _),
path_from_string_det(CondPathStr, CondPath),
list__append(Path, PathTail, CondPath),
PathTail = [ite_cond]
->
ElsePath = list__append(Path, [ite_else]),
ElseAndPath = goal_and_path(Else, ElsePath),
make_primitive_list(Store, [ElseAndPath | GoalPaths],
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
error("make_primitive_list: mismatch on if-then-else")
)
;
Goal = negation_rep(NegGoal),
(
Contour = [_ - ContourHeadNode | ContourTail],
ContourHeadNode = neg_succ(_, _)
->
% The negated goal cannot contribute any bindings.
make_primitive_list(Store, GoalPaths,
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
Contour = [_ - ContourHeadNode | ContourTail],
ContourHeadNode = neg(_, _, _)
->
% The end of the primitive list is somewhere inside
% NegGoal.
NegPath = list__append(Path, [neg]),
NegAndPath = goal_and_path(NegGoal, NegPath),
make_primitive_list(Store, [NegAndPath],
ContourTail, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
error("make_primitive_list: mismatch on negation")
)
;
Goal = some_rep(InnerGoal, MaybeCut),
InnerPath = list__append(Path, [exist(MaybeCut)]),
InnerAndPath = goal_and_path(InnerGoal, InnerPath),
make_primitive_list(Store, [InnerAndPath | GoalPaths],
Contour, MaybeEnd, ArgNum, HeadVars, Var,
Primitives0, Primitives)
;
Goal = atomic_goal_rep(_, File, Line, BoundVars, AtomicGoal),
GeneratesEvent = atomic_goal_generates_event(AtomicGoal),
(
GeneratesEvent = yes(Args),
(
Contour = [ContourHeadId - ContourHeadNode
| ContourTail],
CallId = ContourHeadNode ^ exit_call,
call_node_from_id(Store, CallId, CallNode),
CallPathStr = CallNode ^ call_goal_path,
path_from_string_det(CallPathStr, CallPath),
CallPath = Path,
\+ (
MaybeEnd = yes(EndPath),
EndPath = Path
)
->
Primitive = primitive(File, Line, BoundVars,
AtomicGoal, Path, yes(ContourHeadId)),
Primitives1 = [Primitive | Primitives0],
make_primitive_list(Store, GoalPaths,
ContourTail, MaybeEnd, ArgNum,
HeadVars, Var, Primitives1, Primitives)
;
Contour = [_ContourHeadId - ContourHeadNode],
CallPathStr = ContourHeadNode ^ call_goal_path,
path_from_string_det(CallPathStr, CallPath),
CallPath = Path,
MaybeEnd = yes(EndPath),
EndPath = Path
->
list__index1_det(Args, ArgNum, Var),
Primitives = Primitives0
;
error("make_primitive_list: mismatch on call")
)
;
GeneratesEvent = no,
Primitive = primitive(File, Line, BoundVars,
AtomicGoal, Path, no),
Primitives1 = [Primitive | Primitives0],
make_primitive_list(Store, GoalPaths,
Contour, MaybeEnd, ArgNum, HeadVars, Var,
Primitives1, Primitives)
)
).
make_primitive_list(_, [], Contour, MaybeEnd, ArgNum, HeadVars, Var,
Primitives, Primitives) :-
require(unify(Contour, []),
"make_primitive_list: nonempty contour at end"),
require(unify(MaybeEnd, no),
"make_primitive_list: found end when looking for call"),
list__index1_det(HeadVars, ArgNum, Var).
:- pred traverse_primitives(list(annotated_primitive(R))::in,
var_rep::in, term_path::in, S::in, proc_rep::in,
subterm_origin(edt_node(R))::out) is det <= annotated_trace(S, R).
traverse_primitives([], Var0, TermPath0, _, ProcRep, Origin) :-
ProcRep = proc_rep(HeadVars, _),
ArgPos = find_arg_pos(HeadVars, Var0),
Origin = input(ArgPos, TermPath0).
traverse_primitives([Prim | Prims], Var0, TermPath0, Store, ProcRep,
Origin) :-
Prim = primitive(File, Line, BoundVars, AtomicGoal, _GoalPath,
MaybeNodeId),
(
AtomicGoal = unify_construct_rep(_CellVar, _Cons, FieldVars),
( list__member(Var0, BoundVars) ->
(
TermPath0 = [],
Origin = primitive_op(File, Line)
;
TermPath0 = [TermPathStep0 | TermPath],
list__index1_det(FieldVars, TermPathStep0,
Var),
traverse_primitives(Prims, Var, TermPath,
Store, ProcRep, Origin)
)
;
traverse_primitives(Prims, Var0, TermPath0,
Store, ProcRep, Origin)
)
;
AtomicGoal = unify_deconstruct_rep(CellVar, _Cons, FieldVars),
( list__member(Var0, BoundVars) ->
( list__nth_member_search(FieldVars, Var0, Pos) ->
traverse_primitives(Prims,
CellVar, [Pos | TermPath0],
Store, ProcRep, Origin)
;
error("traverse_primitives: bad deconstruct")
)
;
traverse_primitives(Prims, Var0, TermPath0,
Store, ProcRep, Origin)
)
;
AtomicGoal = unify_assign_rep(ToVar, FromVar),
( list__member(Var0, BoundVars) ->
require(unify(Var0, ToVar),
"traverse_primitives: bad assign"),
traverse_primitives(Prims, FromVar, TermPath0,
Store, ProcRep, Origin)
;
traverse_primitives(Prims, Var0, TermPath0,
Store, ProcRep, Origin)
)
;
AtomicGoal = pragma_foreign_code_rep(_Args),
( list__member(Var0, BoundVars) ->
Origin = primitive_op(File, Line)
;
traverse_primitives(Prims, Var0, TermPath0,
Store, ProcRep, Origin)
)
;
AtomicGoal = unify_simple_test_rep(_LVar, _RVar),
( list__member(Var0, BoundVars) ->
error("traverse_primitives: bad test")
;
traverse_primitives(Prims, Var0, TermPath0,
Store, ProcRep, Origin)
)
;
AtomicGoal = higher_order_call_rep(_, Args),
traverse_call(BoundVars, no, Args, MaybeNodeId, Prims,
Var0, TermPath0, Store, ProcRep, Origin)
;
AtomicGoal = method_call_rep(_, _, Args),
traverse_call(BoundVars, no, Args, MaybeNodeId, Prims,
Var0, TermPath0, Store, ProcRep, Origin)
;
AtomicGoal = plain_call_rep(ModuleName, PredName, Args),
PlainCallInfo = plain_call_info(File, Line,
ModuleName, PredName),
traverse_call(BoundVars, yes(PlainCallInfo), Args, MaybeNodeId,
Prims, Var0, TermPath0, Store, ProcRep, Origin)
).
:- type plain_call_info
---> plain_call_info(
file_name :: string,
line_number :: int,
module_name :: string,
pred_name :: string
).
:- pred traverse_call(list(var_rep)::in, maybe(plain_call_info)::in,
list(var_rep)::in, maybe(R)::in,
list(annotated_primitive(R))::in, var_rep::in, term_path::in,
S::in, proc_rep::in, subterm_origin(edt_node(R))::out) is det
<= annotated_trace(S, R).
traverse_call(BoundVars, MaybePlainCallInfo, Args, MaybeNodeId,
Prims, Var, TermPath, Store, ProcRep, Origin) :-
( list__member(Var, BoundVars) ->
Pos = find_arg_pos(Args, Var),
(
MaybeNodeId = yes(NodeId),
Origin = output(dynamic(NodeId), Pos, TermPath)
;
MaybeNodeId = no,
(
MaybePlainCallInfo = yes(PlainCallInfo),
PlainCallInfo = plain_call_info(File, Line,
ModuleName, PredName),
call_is_primitive(ModuleName, PredName)
->
Origin = primitive_op(File, Line)
;
error("traverse_call: no node id")
)
)
;
traverse_primitives(Prims, Var, TermPath, Store, ProcRep,
Origin)
).
%-----------------------------------------------------------------------------%
:- pred add_paths_to_conjuncts(list(goal_rep)::in, goal_path::in, int::in,
goal_and_path_list::out) is det.
add_paths_to_conjuncts([], _, _, []).
add_paths_to_conjuncts([Goal | Goals], ParentPath, N,
[goal_and_path(Goal, Path) | GoalAndPaths]) :-
list__append(ParentPath, [conj(N)], Path),
add_paths_to_conjuncts(Goals, ParentPath, N + 1, GoalAndPaths).
%-----------------------------------------------------------------------------%
:- func start_loc_to_subterm_mode(start_loc(R)) = subterm_mode.
start_loc_to_subterm_mode(cur_goal) = subterm_out.
start_loc_to_subterm_mode(parent_goal(_, _)) = subterm_in.
%-----------------------------------------------------------------------------%
:- func find_arg_pos(list(var_rep), var_rep) = arg_pos.
find_arg_pos(HeadVars, Var) = ArgPos :-
find_arg_pos_2(HeadVars, Var, 1, ArgPos).
:- pred find_arg_pos_2(list(var_rep)::in, var_rep::in, int::in, arg_pos::out)
is det.
find_arg_pos_2([], _, _, _) :-
error("find_arg_pos_2: empty list").
find_arg_pos_2([HeadVar | HeadVars], Var, Pos, ArgPos) :-
( HeadVar = Var ->
ArgPos = any_head_var(Pos)
;
find_arg_pos_2(HeadVars, Var, Pos + 1, ArgPos)
).
%-----------------------------------------------------------------------------%
:- pred edt_subtree_details(S, edt_node(R), event_number, sequence_number)
<= annotated_trace(S, R).
:- mode edt_subtree_details(in, in, out, out) is det.
edt_subtree_details(Store, dynamic(Ref), Event, SeqNo) :-
det_edt_return_node_from_id(Store, Ref, Node),
(
Node = exit(_, Call, _, _, Event, _)
;
Node = fail(_, Call, _, Event)
;
Node = excp(_, Call, _, _, Event)
),
call_node_from_id(Store, Call, CallNode),
SeqNo = CallNode ^ call_seq.
:- inst edt_return_node =
bound( exit(ground, ground, ground, ground, ground, ground)
; fail(ground, ground, ground, ground)
; excp(ground, ground, ground, ground, ground)).
:- pred det_edt_return_node_from_id(S::in, R::in,
trace_node(R)::out(edt_return_node)) is det <= annotated_trace(S, R).
det_edt_return_node_from_id(Store, Ref, Node) :-
(
trace_node_from_id(Store, Ref, Node0),
(
Node0 = exit(_, _, _, _, _, _)
;
Node0 = fail(_, _, _, _)
;
Node0 = excp(_, _, _, _, _)
)
->
Node = Node0
;
error("det_edt_return_node_from_id: not a return node")
).
%-----------------------------------------------------------------------------%
:- pred trace_atom_subterm_is_ground(trace_atom, arg_pos, term_path).
:- mode trace_atom_subterm_is_ground(in, in, in) is semidet.
trace_atom_subterm_is_ground(atom(_, _, Args), ArgPos, _) :-
select_arg_at_pos(ArgPos, Args, ArgInfo),
ArgInfo = arg_info(_, _, MaybeArg),
MaybeArg = yes(_).
:- pred decl_bug_get_event_number(decl_bug, event_number).
:- mode decl_bug_get_event_number(in, out) is det.
decl_bug_get_event_number(e_bug(EBug), Event) :-
(
EBug = incorrect_contour(_, _, Event)
;
EBug = partially_uncovered_atom(_, Event)
;
EBug = unhandled_exception(_, _, Event)
).
decl_bug_get_event_number(i_bug(IBug), Event) :-
IBug = inadmissible_call(_, _, _, Event).
%-----------------------------------------------------------------------------%
:- pred write_origin(wrap(S)::in, subterm_origin(edt_node(R))::in,
io__state::di, io__state::uo) is det <= annotated_trace(S, R).
write_origin(wrap(Store), Origin) -->
( { Origin = output(dynamic(NodeId), ArgPos, TermPath) } ->
{ exit_node_from_id(Store, NodeId, ExitNode) },
{ ProcName = ExitNode ^ exit_atom ^ proc_name },
io__write_string("output("),
io__write_string(ProcName),
io__write_string(", "),
io__write(ArgPos),
io__write_string(", "),
io__write(TermPath),
io__write_string(")")
;
io__write(Origin)
).
:- pragma foreign_code("C",
"
/*
** The declarative debugger will print diagnostic information about the origins
** computed by dependency tracking if this flag has a positive value.
*/
int MR_DD_debug_origin = 0;
").
:- pragma foreign_decl("C",
"
extern int MR_DD_debug_origin;
").
:- pred debug_origin(int::out, io__state::di, io__state::uo) is det.
:- pragma foreign_proc("C",
debug_origin(Flag::out, IO0::di, IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"
Flag = MR_DD_debug_origin;
IO = IO0;
").