mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-17 14:57:03 +00:00
Estimated hours taken: 30
Branches: main
Implement a new compiler option, --exec-trace-tail-rec, that preserves direct
tail recursion in det and semidet procedures even when debugging is enabled.
This should allow the debugging of programs that previously ran out of stack.
The problem arose because even a directly tail-recursive call had some code
after it: the code for the EXIT event, like this:
p:
incr_sp
fill in the usual debug slots
CALL EVENT
...
/* tail call */
move arguments to registers as usual
call p, return to p_ret
p_ret:
/* code to move output arguments to right registers is empty */
EXIT EVENT
decr_sp
return
If the new option is enabled, the compiler will now generate code like this:
p:
incr_sp
fill in the usual debug slots
fill in new "stack frame reuse count" slot with 0
CALL EVENT
p_1:
...
/* tail call */
move arguments to registers as usual
update the usual debug slots
increment the "stack frame reuse count" slot
TAILCALL EVENT
goto p_1
The new TAIL event takes place in the caller's stack frame, so that the local
variables of the caller are available. This includes the arguments of the
recursive call (though if they are unnamed variables, the debugger will not
show them). The TAIL event serves as a replacement for the CALL event
of the recursive invocation.
compiler/options.m:
Add the new option.
compiler/handle_options.m:
Handle an implication of the new option: the declarative debugger
does not (yet) understand TAIL events.
compiler/mark_tail_calls.m:
New module to mark directly tail recursive calls and the procedures
containing them as such.
compiler/hlds.m:
compiler/notes/compiler_design.html:
Mention the new module.
compiler/mercury_compile.m:
Invoke the new module when the new option asks us to.
compiler/hlds_goal.m:
Add the feature used to mark tail recursive calls for the debugger.
Rename an existing feature with a similar but not identical purpose
to avoid possible confusion.
compiler/hlds_pred.m:
Add a field to proc_infos that says whether the procedure contains
tail recursive calls.
Minor style improvements.
compiler/passes_aux.m:
Minor change to accommodate the needs of the new module.
compiler/code_info.m:
Transmit the information from mark_tail_calls to the code generator.
compiler/call_gen.m:
Implement the new option.
compiler/trace_gen.m:
Reserve the extra slot needed for the new option.
Switch to state variable notation in the code that does the slot
allocation, since this is less error-prone than the previous approach.
compiler/layout.m:
compiler/layout_out.m:
compiler/stack_layout.m:
Remember what stack slot holds the stack frame reuse counter,
for transmission to the runtime system.
compiler/proc_gen.m:
Add the new label needed for tail recursion.
Put the arguments of some procedures into a more logical order.
compiler/deep_profiling.m:
compiler/deforest.m:
compiler/saved_vars.m:
compiler/table_gen.m:
Conform to the changes above.
compiler/trace_params.m:
mdbcomp/prim_data.m:
runtime/mercury_trace_base.[ch]:
Add the new event type.
Convert mercury_trace_base.h to four-space indentation.
runtime/mercury_stack_layout.h:
Add a field to the execution trace information we have for each
procedure that gives the number of the stack slot (if any) that holds
the stack frame reuse counter. Add a macro to get the value in the
counter.
Convert this header file to four-space indentation.
runtime/mercury_stack_trace.[ch]:
When walking the stack, we now have to be prepared to encounter stack
frames that have been reused. Modify the algorithms in this module
accordingly, and modify the interfaces of the exported functions
to allow the functions' callers to behave accordingly as well.
Group the information we gather about stack frame for printing into
one structure, and document it.
Convert the header to four-space indentation.
library/exception.m:
mdbcomp/trace_counts.m:
Conform to the changes above.
In trace_counts.m, fix an apparent cut-and-paste error (that hasn't
caused any test case failures yet).
trace/mercury_trace.c:
Modify the implementation of the "next" and "finish" commands
to accommodate the possibility that the procedure at the selected
depth may have had its stack frame reused. In such cases
tests/debugger/tailrec1.{m,inp,exp,data}:
A new test case to check the handling of tail recursive procedures.
280 lines
12 KiB
Mathematica
280 lines
12 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2001-2008 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File: layout.m.
|
|
% Author: zs.
|
|
%
|
|
% Definitions of Mercury types for representing layout structures within
|
|
% the compiler. Layout structures are generated by the compiler, and are
|
|
% used by the parts of the runtime system that need to look at the stacks
|
|
% (and sometimes the registers) and make sense of their contents. The parts
|
|
% of the runtime system that need to do this include exception handling,
|
|
% the debugger, the deep profiler and (eventually) the accurate garbage
|
|
% collector.
|
|
%
|
|
% When output by layout_out.m, values of most these types will correspond
|
|
% to the C types defined in runtime/mercury_stack_layout.h or
|
|
% runtime/mercury_deep_profiling.h; the documentation of those types
|
|
% can be found there. The names of the C types are listed next to the
|
|
% function symbol whose arguments represent their contents.
|
|
%
|
|
% The code to generate values of these types is in stack_layout.m and
|
|
% deep_profiling.m.
|
|
%
|
|
% This module should be, but as yet isn't, independent of whether we are
|
|
% compiling to LLDS or MLDS.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.layout.
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module hlds.hlds_rtti.
|
|
:- import_module libs.trace_params.
|
|
:- import_module ll_backend.llds.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% This type is for strings which may contain embedded null characters.
|
|
% When a string_with_0s is written, a null character will be written
|
|
% in between each string in the list.
|
|
:- type string_with_0s
|
|
---> string_with_0s(list(string)).
|
|
|
|
:- type event_set_layout_data
|
|
---> event_set_layout_data(
|
|
event_set_data,
|
|
map(int, rval) % Maps each event number to an rval
|
|
% that gives the vector of typeinfos
|
|
% for the arguments of that event.
|
|
).
|
|
|
|
:- type layout_data
|
|
---> label_layout_data( % defines MR_LabelLayout
|
|
proc_label :: proc_label,
|
|
label_num :: int,
|
|
proc_layout_name :: layout_name,
|
|
maybe_port :: maybe(trace_port),
|
|
maybe_is_hidden :: maybe(bool),
|
|
label_num_in_module :: int,
|
|
maybe_goal_path :: maybe(int), % offset
|
|
maybe_user_info :: maybe(user_event_data),
|
|
maybe_var_info :: maybe(label_var_info)
|
|
)
|
|
; proc_layout_data( % defines MR_ProcLayout
|
|
proc_layout_label :: rtti_proc_label,
|
|
proc_layout_trav :: proc_layout_stack_traversal,
|
|
proc_layout_more :: maybe_proc_id_and_more
|
|
)
|
|
; module_layout_common_data( % defines MR_ModuleCommonLayout
|
|
module_common_name :: module_name,
|
|
string_table_size :: int,
|
|
string_table :: string_with_0s
|
|
)
|
|
; module_layout_data( % defines MR_ModuleLayout
|
|
module_name :: module_name,
|
|
module_common :: layout_name,
|
|
proc_layout_names :: list(layout_name),
|
|
file_layouts :: list(file_layout_data),
|
|
trace_level :: trace_level,
|
|
suppressed_events :: int,
|
|
num_label_exec_count :: int,
|
|
maybe_event_specs :: maybe(event_set_layout_data)
|
|
)
|
|
; closure_proc_id_data( % defines MR_ClosureId
|
|
caller_proc_label :: proc_label,
|
|
caller_closure_seq_no :: int,
|
|
closure_proc_label :: proc_label,
|
|
closure_module_name :: module_name,
|
|
closure_file_name :: string,
|
|
closure_line_number :: int,
|
|
closure_origin :: pred_origin,
|
|
closure_goal_path :: string
|
|
)
|
|
; table_io_decl_data(
|
|
table_io_decl_proc_ptr :: rtti_proc_label,
|
|
table_io_decl_kind :: proc_layout_kind,
|
|
table_io_decl_num_ptis :: int,
|
|
table_io_decl_ptis :: rval,
|
|
% pseudo-typeinfos for headvars
|
|
table_io_decl_type_params :: rval
|
|
).
|
|
|
|
:- type user_event_data
|
|
---> user_event_data(
|
|
user_event_number :: int,
|
|
user_event_locns :: rval,
|
|
user_event_var_nums :: list(maybe(int))
|
|
).
|
|
|
|
:- type label_var_info
|
|
---> label_var_info( % part of MR_LabelLayout
|
|
encoded_var_count :: int,
|
|
locns_types :: rval,
|
|
var_nums :: rval,
|
|
type_params :: rval
|
|
).
|
|
|
|
:- type proc_layout_stack_traversal % defines MR_StackTraversal
|
|
---> proc_layout_stack_traversal(
|
|
entry_label :: maybe(label),
|
|
% The proc entry label; will be `no'
|
|
% if we don't have static code
|
|
% addresses.
|
|
succip_slot :: maybe(int),
|
|
stack_slot_count :: int,
|
|
detism :: determinism
|
|
).
|
|
|
|
% The deep_slot_info gives the stack slot numbers that hold
|
|
% the values returned by the call port code, which are needed to let
|
|
% exception.throw perform the work we need to do at the excp port.
|
|
% The old_outermost slot is needed only with the save/restore approach;
|
|
% the old_outermost field contain -1 otherwise. All fields will contain
|
|
% -1 if the variables are never saved on the stack because the
|
|
% predicate makes no calls (in which case it cannot throw exceptions,
|
|
% because to do that it would need to call exception.throw, directly or
|
|
% indirectly.)
|
|
:- type deep_excp_slots
|
|
---> deep_excp_slots(
|
|
top_csd :: int,
|
|
middle_csd :: int,
|
|
old_outermost :: int
|
|
).
|
|
|
|
:- type proc_layout_proc_static
|
|
---> proc_layout_proc_static(
|
|
hlds_proc_static :: hlds_proc_static,
|
|
deep_excp_slots :: deep_excp_slots,
|
|
deep_original_body :: deep_original_body
|
|
).
|
|
|
|
:- type maybe_proc_id_and_more
|
|
---> no_proc_id_and_more
|
|
; proc_id_and_more(
|
|
maybe_proc_static :: maybe(proc_layout_proc_static),
|
|
maybe_exec_trace :: maybe(proc_layout_exec_trace),
|
|
proc_body_bytes :: list(int),
|
|
% The procedure body represented as
|
|
% a list of bytecodes.
|
|
proc_module_common :: layout_name
|
|
% The name of the module_common_layout
|
|
% structure.
|
|
).
|
|
|
|
:- type proc_layout_exec_trace % defines MR_ExecTrace
|
|
---> proc_layout_exec_trace(
|
|
maybe_call_label_layout :: maybe(label_layout_details),
|
|
|
|
proc_label_layouts :: list(data_addr),
|
|
% The label layouts of the events in
|
|
% the predicate. Interface events
|
|
% first, internal events second.
|
|
|
|
maybe_table_info :: maybe(data_addr),
|
|
head_var_nums :: list(int),
|
|
% The variable numbers of the
|
|
% head variables, including the
|
|
% ones added by the compiler,
|
|
% in order. The length of the
|
|
% list must be the same as the
|
|
% procedure's arity.
|
|
|
|
var_names :: list(int),
|
|
% Each variable name is an offset into
|
|
% the module's string table.
|
|
|
|
max_var_num :: int,
|
|
max_r_num :: int,
|
|
maybe_from_full_slot :: maybe(int),
|
|
maybe_io_seq_slot :: maybe(int),
|
|
maybe_trail_slot :: maybe(int),
|
|
maybe_maxfr_slot :: maybe(int),
|
|
eval_method :: eval_method,
|
|
maybe_call_table_slot :: maybe(int),
|
|
maybe_tail_rec_slot :: maybe(int),
|
|
eff_trace_level :: trace_level,
|
|
exec_trace_flags :: int
|
|
).
|
|
|
|
:- type file_layout_data
|
|
---> file_layout_data(
|
|
file_name :: string,
|
|
line_no_label_list :: assoc_list(int, layout_name)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type layout_name
|
|
---> label_layout(proc_label, int, label_vars)
|
|
; user_event_layout(proc_label, int)
|
|
; user_event_attr_var_nums(proc_label, int)
|
|
; proc_layout(rtti_proc_label, proc_layout_kind)
|
|
% A proc layout structure for stack tracing, accurate gc,
|
|
% deep profiling and/or execution tracing.
|
|
; proc_layout_exec_trace(rtti_proc_label)
|
|
; proc_layout_label_layouts(rtti_proc_label)
|
|
; proc_layout_head_var_nums(rtti_proc_label)
|
|
% A vector of variable numbers, containing the numbers of the
|
|
% procedure's head variables, including the ones generated by
|
|
% the compiler.
|
|
; proc_layout_var_names(rtti_proc_label)
|
|
% A vector of variable names (represented as offsets into
|
|
% the string table) for a procedure layout structure.
|
|
; proc_layout_body_bytecode(rtti_proc_label)
|
|
; table_io_decl(rtti_proc_label)
|
|
; closure_proc_id(proc_label, int, proc_label)
|
|
; file_layout(module_name, int)
|
|
; file_layout_line_number_vector(module_name, int)
|
|
; file_layout_label_layout_vector(module_name, int)
|
|
; module_layout_string_table(module_name)
|
|
; module_layout_file_vector(module_name)
|
|
; module_layout_proc_vector(module_name)
|
|
; module_layout_label_exec_count(module_name, int)
|
|
; module_layout_event_set_desc(module_name)
|
|
; module_layout_event_arg_names(module_name, int)
|
|
; module_layout_event_synth_attrs(module_name, int)
|
|
; module_layout_event_synth_attr_args(module_name, int, int)
|
|
; module_layout_event_synth_attr_order(module_name, int, int)
|
|
; module_layout_event_synth_order(module_name, int)
|
|
; module_layout_event_specs(module_name)
|
|
; module_common_layout(module_name)
|
|
; module_layout(module_name)
|
|
; proc_static(rtti_proc_label)
|
|
; proc_static_call_sites(rtti_proc_label)
|
|
; proc_static_coverage_point_static(rtti_proc_label)
|
|
; proc_static_coverage_point_dynamic(rtti_proc_label).
|
|
|
|
:- type label_layout_details
|
|
---> label_layout_details(proc_label, int, label_vars).
|
|
|
|
:- type label_vars
|
|
---> label_has_var_info
|
|
; label_has_no_var_info.
|
|
|
|
:- type proc_layout_kind
|
|
---> proc_layout_traversal
|
|
; proc_layout_proc_id(proc_layout_user_or_uci).
|
|
|
|
:- type proc_layout_user_or_uci
|
|
---> user
|
|
; uci.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module layout.
|
|
%-----------------------------------------------------------------------------%
|