Commit e7f373fa by Jan Hubicka Committed by Jan Hubicka

dwarf2out.c: Include tree-pass.h and gimple.h.


	* dwarf2out.c: Include tree-pass.h and gimple.h.
	(loc_list_plus_const): New function.
	(loc_descriptor_from_tree_1): Rename to ...
	(loc_descriptor_from_tree): ... remove original.
	(loc_list_from_tree): New function.
	(add_AT_location_description): Accept location list.
	(tls_mem_loc_descriptor): Update call of loc_descriptor_from_tree.
	(concatn_mem_loc_descriptor): Remove.
	(mem_loc_descriptor): Handle CONCAT/CONCATN and VAR_LOCATION by
	returning NULL.
	(secname_for_decl): Move up.
	(hidden_reference_p): New function; break out from ...
	(loc_by_refernece): ... here; move up.
	(dw_loc_list): New function.
	(single_element_loc_list): New function.
	(single_element_loc_list_p): New function.
	(add_loc_descr_to_each): New function.
	(add_loc_list): New function.
	(loc_descr_from_tree): Make wraper of loc_list_from_tree.
	(loc_list_from_tree): Reroganized from loc_descr_from_tree;
	add diagnostics why expansion failed.
	(add_location_or_const_value_attribute): Support location lists.
	(add_bound_info): Likewise.
	(descr_info_loc): Update call of loc_descriptor_from_tree.
	(gen_variable_die): Work on location lists.
	* final.c (pass_final): Add dump file.
	* Makefile.in (dwarf2out.o): Add new dependencies.

From-SVN: r151807
parent d2da4af2
2009-09-17 Jan Hubicka <jh@suse.cz>
* dwarf2out.c: Include tree-pass.h and gimple.h.
(loc_list_plus_const): New function.
(loc_descriptor_from_tree_1): Rename to ...
(loc_descriptor_from_tree): ... remove original.
(loc_list_from_tree): New function.
(add_AT_location_description): Accept location list.
(tls_mem_loc_descriptor): Update call of loc_descriptor_from_tree.
(concatn_mem_loc_descriptor): Remove.
(mem_loc_descriptor): Handle CONCAT/CONCATN and VAR_LOCATION by
returning NULL.
(secname_for_decl): Move up.
(hidden_reference_p): New function; break out from ...
(loc_by_refernece): ... here; move up.
(dw_loc_list): New function.
(single_element_loc_list): New function.
(single_element_loc_list_p): New function.
(add_loc_descr_to_each): New function.
(add_loc_list): New function.
(loc_descr_from_tree): Make wraper of loc_list_from_tree.
(loc_list_from_tree): Reroganized from loc_descr_from_tree;
add diagnostics why expansion failed.
(add_location_or_const_value_attribute): Support location lists.
(add_bound_info): Likewise.
(descr_info_loc): Update call of loc_descriptor_from_tree.
(gen_variable_die): Work on location lists.
* final.c (pass_final): Add dump file.
* Makefile.in (dwarf2out.o): Add new dependencies.
2009-09-17 Janis Johnson <janis187@us.ibm.com>
PR c/41049
......
......@@ -2731,7 +2731,7 @@ dwarf2out.o : dwarf2out.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \
output.h $(DIAGNOSTIC_H) $(REAL_H) hard-reg-set.h $(REGS_H) $(EXPR_H) \
libfuncs.h $(TOPLEV_H) dwarf2out.h reload.h $(GGC_H) $(EXCEPT_H) dwarf2asm.h \
$(TM_P_H) langhooks.h $(HASHTAB_H) gt-dwarf2out.h $(TARGET_H) $(CGRAPH_H) \
$(MD5_H) $(INPUT_H) $(FUNCTION_H) $(VARRAY_H)
$(MD5_H) $(INPUT_H) $(FUNCTION_H) $(VARRAY_H) $(GIMPLE_H) $(TREE_PASS_H)
dwarf2asm.o : dwarf2asm.c $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) \
$(FLAGS_H) $(RTL_H) $(TREE_H) output.h dwarf2asm.h $(TM_P_H) $(GGC_H) \
gt-dwarf2asm.h $(DWARF2_H) $(SPLAY_TREE_H) $(TARGET_H)
......
......@@ -89,6 +89,8 @@ along with GCC; see the file COPYING3. If not see
#include "hashtab.h"
#include "cgraph.h"
#include "input.h"
#include "gimple.h"
#include "tree-pass.h"
#ifdef DWARF2_DEBUGGING_INFO
static void dwarf2out_source_line (unsigned int, const char *, int, bool);
......@@ -4655,6 +4657,16 @@ loc_descr_plus_const (dw_loc_descr_ref *list_head, HOST_WIDE_INT offset)
}
}
/* Add a constant OFFSET to a location list. */
static void
loc_list_plus_const (dw_loc_list_ref list_head, HOST_WIDE_INT offset)
{
dw_loc_list_ref d;
for (d = list_head; d != NULL; d = d->dw_loc_next)
loc_descr_plus_const (&d->expr, offset);
}
/* Return the size of a location descriptor. */
static unsigned long
......@@ -5938,8 +5950,8 @@ static dw_loc_descr_ref concat_loc_descriptor (rtx, rtx,
enum var_init_status);
static dw_loc_descr_ref loc_descriptor (rtx, enum machine_mode mode,
enum var_init_status);
static dw_loc_descr_ref loc_descriptor_from_tree_1 (tree, int);
static dw_loc_descr_ref loc_descriptor_from_tree (tree);
static dw_loc_list_ref loc_list_from_tree (tree, int);
static dw_loc_descr_ref loc_descriptor_from_tree (tree, int);
static HOST_WIDE_INT ceiling (HOST_WIDE_INT, unsigned int);
static tree field_type (const_tree);
static unsigned int simple_type_align_in_bits (const_tree);
......@@ -5947,7 +5959,7 @@ static unsigned int simple_decl_align_in_bits (const_tree);
static unsigned HOST_WIDE_INT simple_type_size_in_bits (const_tree);
static HOST_WIDE_INT field_byte_offset (const_tree);
static void add_AT_location_description (dw_die_ref, enum dwarf_attribute,
dw_loc_descr_ref);
dw_loc_list_ref);
static void add_data_member_location_attribute (dw_die_ref, tree);
static void add_const_value_attribute (dw_die_ref, rtx);
static void insert_int (HOST_WIDE_INT, unsigned, unsigned char *);
......@@ -10958,36 +10970,6 @@ is_based_loc (const_rtx rtl)
&& CONST_INT_P (XEXP (rtl, 1)))));
}
/* Return a descriptor that describes the concatenation of N locations
used to form the address of a memory location. */
static dw_loc_descr_ref
concatn_mem_loc_descriptor (rtx concatn, enum machine_mode mode,
enum var_init_status initialized)
{
unsigned int i;
dw_loc_descr_ref cc_loc_result = NULL;
unsigned int n = XVECLEN (concatn, 0);
for (i = 0; i < n; ++i)
{
dw_loc_descr_ref ref;
rtx x = XVECEXP (concatn, 0, i);
ref = mem_loc_descriptor (x, mode, VAR_INIT_STATUS_INITIALIZED);
if (ref == NULL)
return NULL;
add_loc_descr (&cc_loc_result, ref);
add_loc_descr_op_piece (&cc_loc_result, GET_MODE_SIZE (GET_MODE (x)));
}
if (cc_loc_result && initialized == VAR_INIT_STATUS_UNINITIALIZED)
add_loc_descr (&cc_loc_result, new_loc_descr (DW_OP_GNU_uninit, 0, 0));
return cc_loc_result;
}
/* Try to handle TLS MEMs, for which mem_loc_descriptor on XEXP (mem, 0)
failed. */
......@@ -11006,7 +10988,7 @@ tls_mem_loc_descriptor (rtx mem)
|| !DECL_THREAD_LOCAL_P (base))
return NULL;
loc_result = loc_descriptor_from_tree_1 (MEM_EXPR (mem), 2);
loc_result = loc_descriptor_from_tree (MEM_EXPR (mem), 2);
if (loc_result == NULL)
return NULL;
......@@ -11016,6 +10998,26 @@ tls_mem_loc_descriptor (rtx mem)
return loc_result;
}
/* Output debug info about reason why we failed to expand expression as dwarf
expression. */
static void
expansion_failed (tree expr, rtx rtl, char const *reason)
{
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, "Failed to expand as dwarf: ");
if (expr)
print_generic_expr (dump_file, expr, dump_flags);
if (rtl)
{
fprintf (dump_file, "\n");
print_rtl (dump_file, rtl);
}
fprintf (dump_file, "\nReason: %s\n", reason);
}
}
/* The following routine converts the RTL for a variable or parameter
(resident in memory) into an equivalent Dwarf representation of a
mechanism for getting the address of that same variable onto the top of a
......@@ -11163,7 +11165,11 @@ mem_loc_descriptor (rtx rtl, enum machine_mode mode,
FIXME: might try to use DW_OP_const_value here, though
DW_OP_piece complicates it. */
if (!marked)
return 0;
{
expansion_failed (NULL_TREE, rtl,
"Constant was removed from constant pool.\n");
return 0;
}
}
if (GET_CODE (rtl) == SYMBOL_REF
......@@ -11193,6 +11199,14 @@ mem_loc_descriptor (rtx rtl, enum machine_mode mode,
VEC_safe_push (rtx, gc, used_rtx_array, rtl);
break;
case CONCAT:
case CONCATN:
case VAR_LOCATION:
expansion_failed (NULL_TREE, rtl,
"CONCAT/CONCATN/VAR_LOCATION is handled only by loc_descriptor");
gcc_unreachable ();
return 0;
case PRE_MODIFY:
/* Extract the PLUS expression nested inside and fall into
PLUS code below. */
......@@ -11322,11 +11336,6 @@ mem_loc_descriptor (rtx rtl, enum machine_mode mode,
mem_loc_result = int_loc_descriptor (INTVAL (rtl));
break;
case CONCATN:
mem_loc_result = concatn_mem_loc_descriptor (rtl, mode,
VAR_INIT_STATUS_INITIALIZED);
break;
case EQ:
op = DW_OP_eq;
goto do_scompare;
......@@ -11972,16 +11981,310 @@ loc_descriptor (rtx rtl, enum machine_mode mode,
return loc_result;
}
/* Similar, but generate the descriptor from trees instead of rtl. This comes
up particularly with variable length arrays. WANT_ADDRESS is 2 if this is
a top-level invocation of loc_descriptor_from_tree; is 1 if this is not a
top-level invocation, and we require the address of LOC; is 0 if we require
the value of LOC. */
/* We need to figure out what section we should use as the base for the
address ranges where a given location is valid.
1. If this particular DECL has a section associated with it, use that.
2. If this function has a section associated with it, use that.
3. Otherwise, use the text section.
XXX: If you split a variable across multiple sections, we won't notice. */
static const char *
secname_for_decl (const_tree decl)
{
const char *secname;
if (VAR_OR_FUNCTION_DECL_P (decl) && DECL_SECTION_NAME (decl))
{
tree sectree = DECL_SECTION_NAME (decl);
secname = TREE_STRING_POINTER (sectree);
}
else if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
{
tree sectree = DECL_SECTION_NAME (current_function_decl);
secname = TREE_STRING_POINTER (sectree);
}
else if (cfun && in_cold_section_p)
secname = crtl->subsections.cold_section_label;
else
secname = text_section_label;
return secname;
}
/* Return true when DECL_BY_REFERENCE is defined and set for DECL. */
static bool
decl_by_reference_p (tree decl)
{
return ((TREE_CODE (decl) == PARM_DECL || TREE_CODE (decl) == RESULT_DECL
|| TREE_CODE (decl) == VAR_DECL)
&& DECL_BY_REFERENCE (decl));
}
/* Dereference a location expression LOC if DECL is passed by invisible
reference. */
static dw_loc_descr_ref
loc_descriptor_from_tree_1 (tree loc, int want_address)
loc_by_reference (dw_loc_descr_ref loc, tree decl)
{
HOST_WIDE_INT size;
enum dwarf_location_atom op;
if (loc == NULL)
return NULL;
if (!decl_by_reference_p (decl))
return loc;
/* If loc is DW_OP_reg{0...31,x}, don't add DW_OP_deref, instead
change it into corresponding DW_OP_breg{0...31,x} 0. Then the
location expression is considered to be address of a memory location,
rather than the register itself. */
if (((loc->dw_loc_opc >= DW_OP_reg0 && loc->dw_loc_opc <= DW_OP_reg31)
|| loc->dw_loc_opc == DW_OP_regx)
&& (loc->dw_loc_next == NULL
|| (loc->dw_loc_next->dw_loc_opc == DW_OP_GNU_uninit
&& loc->dw_loc_next->dw_loc_next == NULL)))
{
if (loc->dw_loc_opc == DW_OP_regx)
{
loc->dw_loc_opc = DW_OP_bregx;
loc->dw_loc_oprnd2.v.val_int = 0;
}
else
{
loc->dw_loc_opc
= (enum dwarf_location_atom)
(loc->dw_loc_opc + (DW_OP_breg0 - DW_OP_reg0));
loc->dw_loc_oprnd1.v.val_int = 0;
}
return loc;
}
size = int_size_in_bytes (TREE_TYPE (decl));
if (size > DWARF2_ADDR_SIZE || size == -1)
return 0;
else if (size == DWARF2_ADDR_SIZE)
op = DW_OP_deref;
else
op = DW_OP_deref_size;
add_loc_descr (&loc, new_loc_descr (op, size, 0));
return loc;
}
/* Return dwarf representation of location list representing for
LOC_LIST of DECL. */
static dw_loc_list_ref
dw_loc_list (var_loc_list * loc_list, tree decl, bool toplevel)
{
const char *endname, *secname;
dw_loc_list_ref list;
rtx varloc;
enum var_init_status initialized;
struct var_loc_node *node;
dw_loc_descr_ref descr;
char label_id[MAX_ARTIFICIAL_LABEL_BYTES];
bool by_reference = decl_by_reference_p (decl);
/* Now that we know what section we are using for a base,
actually construct the list of locations.
The first location information is what is passed to the
function that creates the location list, and the remaining
locations just get added on to that list.
Note that we only know the start address for a location
(IE location changes), so to build the range, we use
the range [current location start, next location start].
This means we have to special case the last node, and generate
a range of [last location start, end of function label]. */
node = loc_list->first;
varloc = NOTE_VAR_LOCATION (node->var_loc_note);
secname = secname_for_decl (decl);
if (NOTE_VAR_LOCATION_LOC (node->var_loc_note))
initialized = NOTE_VAR_LOCATION_STATUS (node->var_loc_note);
else
initialized = VAR_INIT_STATUS_INITIALIZED;
if (!toplevel || by_reference)
{
gcc_assert (GET_CODE (varloc) == VAR_LOCATION);
/* Single part. */
if (GET_CODE (XEXP (varloc, 1)) != PARALLEL)
descr = loc_by_reference (mem_loc_descriptor (XEXP (XEXP (varloc, 1), 0),
TYPE_MODE (TREE_TYPE (decl)),
initialized),
decl);
else
descr = NULL;
}
else
descr = loc_descriptor (varloc, DECL_MODE (decl), initialized);
if (!current_function_decl)
endname = text_end_label;
else
{
ASM_GENERATE_INTERNAL_LABEL (label_id, FUNC_END_LABEL,
current_function_funcdef_no);
endname = ggc_strdup (label_id);
}
list = new_loc_list (descr, node->label, node->next ? node->next->label : endname, secname, 1);
node = node->next;
if (!node)
return NULL;
for (; node->next; node = node->next)
if (NOTE_VAR_LOCATION_LOC (node->var_loc_note) != NULL_RTX)
{
/* The variable has a location between NODE->LABEL and
NODE->NEXT->LABEL. */
enum var_init_status initialized =
NOTE_VAR_LOCATION_STATUS (node->var_loc_note);
varloc = NOTE_VAR_LOCATION (node->var_loc_note);
if (!toplevel || by_reference)
{
gcc_assert (GET_CODE (varloc) == VAR_LOCATION);
/* Single part. */
if (GET_CODE (XEXP (varloc, 1)) != PARALLEL)
descr = mem_loc_descriptor (XEXP (XEXP (varloc, 1), 0),
TYPE_MODE (TREE_TYPE (decl)), initialized);
else
descr = NULL;
descr = loc_by_reference (descr, decl);
}
else
descr = loc_descriptor (varloc, DECL_MODE (decl), initialized);
add_loc_descr_to_loc_list (&list, descr,
node->label, node->next->label, secname);
}
/* If the variable has a location at the last label
it keeps its location until the end of function. */
if (NOTE_VAR_LOCATION_LOC (node->var_loc_note) != NULL_RTX)
{
enum var_init_status initialized =
NOTE_VAR_LOCATION_STATUS (node->var_loc_note);
varloc = NOTE_VAR_LOCATION (node->var_loc_note);
if (!toplevel || by_reference)
{
gcc_assert (GET_CODE (varloc) == VAR_LOCATION);
/* Single part. */
if (GET_CODE (XEXP (varloc, 1)) != PARALLEL)
descr = mem_loc_descriptor (XEXP (XEXP (varloc, 1), 0),
TYPE_MODE (TREE_TYPE (decl)), initialized);
else
descr = NULL;
descr = loc_by_reference (descr, decl);
}
else
descr = loc_descriptor (varloc, DECL_MODE (decl), initialized);
add_loc_descr_to_loc_list (&list, descr, node->label, endname, secname);
}
return list;
}
/* Return single element location list containing loc descr REF. */
static dw_loc_list_ref
single_element_loc_list (dw_loc_descr_ref ref)
{
return new_loc_list (ref, NULL, NULL, NULL, 0);
}
/* Return if the loc_list has only single element and thus can be represented
as location description. */
static bool
single_element_loc_list_p (dw_loc_list_ref list)
{
dw_loc_descr_ref ret, ret1;
return (!list->dw_loc_next && !list->begin && !list->end);
}
/* To each location in list LIST add loc descr REF. */
static void
add_loc_descr_to_each (dw_loc_list_ref list, dw_loc_descr_ref ref)
{
dw_loc_descr_ref copy;
add_loc_descr (&list->expr, ref);
list = list->dw_loc_next;
while (list)
{
copy = GGC_CNEW (dw_loc_descr_node);
memcpy (copy, ref, sizeof (dw_loc_descr_node));
add_loc_descr (&list->expr, copy);
while (copy->dw_loc_next)
{
dw_loc_descr_ref new_copy = GGC_CNEW (dw_loc_descr_node);
memcpy (new_copy, copy->dw_loc_next, sizeof (dw_loc_descr_node));
copy->dw_loc_next = new_copy;
copy = new_copy;
}
list = list->dw_loc_next;
}
}
/* Given two lists RET and LIST
produce location list that is result of adding expression in LIST
to expression in RET on each possition in program.
Might be destructive on both RET and LIST.
TODO: We handle only simple cases of RET or LIST having at most one
element. General case would inolve sorting the lists in program order
and merging them that will need some additional work.
Adding that will improve quality of debug info especially for SRA-ed
structures. */
static void
add_loc_list (dw_loc_list_ref *ret, dw_loc_list_ref list)
{
if (!list)
return;
if (!*ret)
{
*ret = list;
return;
}
if (!list->dw_loc_next)
{
add_loc_descr_to_each (*ret, list->expr);
return;
}
if (!(*ret)->dw_loc_next)
{
add_loc_descr_to_each (list, (*ret)->expr);
*ret = list;
return;
}
expansion_failed (NULL_TREE, NULL_RTX,
"Don't know how to merge two non-trivial"
" location lists.\n");
*ret = NULL;
return;
}
/* Generate Dwarf location list representing LOC.
If WANT_ADDRESS is false, expression computing LOC will be computed
If WANT_ADDRESS is 1, expression computing address of LOC will be returned
if WANT_ADDRESS is 2, expression computing address useable in location
will be returned (i.e. DW_OP_reg can be used
to refer to register values)
TODO: Dwarf4 adds types to the stack machine that ought to be used here
DW_OP_stack_value will help in cases where we fail to find address of the
expression.
*/
static dw_loc_list_ref
loc_list_from_tree (tree loc, int want_address)
{
dw_loc_descr_ref ret = NULL, ret1 = NULL;
dw_loc_list_ref list_ret = NULL, list_ret1 = NULL;
int have_address = 0;
enum dwarf_location_atom op;
......@@ -11992,6 +12295,7 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
switch (TREE_CODE (loc))
{
case ERROR_MARK:
expansion_failed (loc, NULL_RTX, "ERROR_MARK");
return 0;
case PLACEHOLDER_EXPR:
......@@ -11999,25 +12303,32 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
position of other fields. We don't try to encode this here. The
only user of this is Ada, which encodes the needed information using
the names of types. */
expansion_failed (loc, NULL_RTX, "PLACEHOLDER_EXPR");
return 0;
case CALL_EXPR:
expansion_failed (loc, NULL_RTX, "CALL_EXPR");
/* There are no opcodes for these operations. */
return 0;
case PREINCREMENT_EXPR:
case PREDECREMENT_EXPR:
case POSTINCREMENT_EXPR:
case POSTDECREMENT_EXPR:
expansion_failed (loc, NULL_RTX, "PRE/POST INDCREMENT/DECREMENT");
/* There are no opcodes for these operations. */
return 0;
case ADDR_EXPR:
/* If we already want an address, there's nothing we can do. */
if (want_address)
return 0;
{
expansion_failed (loc, NULL_RTX, "need address of ADDR_EXPR");
return 0;
}
/* Otherwise, process the argument and look for the address. */
return loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 1);
return loc_list_from_tree (TREE_OPERAND (loc, 0), 1);
case VAR_DECL:
if (DECL_THREAD_LOCAL_P (loc))
......@@ -12030,7 +12341,7 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
if (targetm.have_tls)
{
/* If this is not defined, we have no way to emit the
data. */
data. */
if (!targetm.asm_out.output_dwarf_dtprel)
return 0;
......@@ -12077,17 +12388,24 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
case PARM_DECL:
if (DECL_HAS_VALUE_EXPR_P (loc))
return loc_descriptor_from_tree_1 (DECL_VALUE_EXPR (loc),
want_address);
return loc_list_from_tree (DECL_VALUE_EXPR (loc),
want_address);
/* FALLTHRU */
case RESULT_DECL:
case FUNCTION_DECL:
{
rtx rtl = rtl_for_decl_location (loc);
var_loc_list *loc_list = lookup_decl_loc (loc);
if (rtl == NULL_RTX)
return 0;
if (loc_list && loc_list->first
&& (list_ret = dw_loc_list (loc_list, loc, want_address == 2)))
have_address = 1;
else if (rtl == NULL_RTX)
{
expansion_failed (loc, NULL_RTX, "DECL has no RTL");
return 0;
}
else if (CONST_INT_P (rtl))
{
HOST_WIDE_INT val = INTVAL (rtl);
......@@ -12096,7 +12414,10 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
ret = int_loc_descriptor (val);
}
else if (GET_CODE (rtl) == CONST_STRING)
return 0;
{
expansion_failed (loc, NULL_RTX, "CONST_STRING");
return 0;
}
else if (CONSTANT_P (rtl))
{
ret = new_loc_descr (DW_OP_addr, 0, 0);
......@@ -12109,33 +12430,41 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
/* Certain constructs can only be represented at top-level. */
if (want_address == 2)
return loc_descriptor (rtl, VOIDmode,
VAR_INIT_STATUS_INITIALIZED);
mode = GET_MODE (rtl);
if (MEM_P (rtl))
{
rtl = XEXP (rtl, 0);
ret = loc_descriptor (rtl, VOIDmode,
VAR_INIT_STATUS_INITIALIZED);
have_address = 1;
}
ret = mem_loc_descriptor (rtl, mode, VAR_INIT_STATUS_INITIALIZED);
else
{
mode = GET_MODE (rtl);
if (MEM_P (rtl))
{
rtl = XEXP (rtl, 0);
have_address = 1;
}
ret = mem_loc_descriptor (rtl, mode, VAR_INIT_STATUS_INITIALIZED);
}
if (!ret)
expansion_failed (loc, rtl,
"failed to produce loc descriptor for rtl");
}
}
break;
case INDIRECT_REF:
ret = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 0);
list_ret = loc_list_from_tree (TREE_OPERAND (loc, 0), 0);
have_address = 1;
break;
case COMPOUND_EXPR:
return loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 1), want_address);
return loc_list_from_tree (TREE_OPERAND (loc, 1), want_address);
CASE_CONVERT:
case VIEW_CONVERT_EXPR:
case SAVE_EXPR:
case MODIFY_EXPR:
return loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), want_address);
return loc_list_from_tree (TREE_OPERAND (loc, 0), want_address);
case COMPONENT_REF:
case BIT_FIELD_REF:
......@@ -12151,26 +12480,35 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
obj = get_inner_reference (loc, &bitsize, &bitpos, &offset, &mode,
&unsignedp, &volatilep, false);
if (obj == loc)
return 0;
gcc_assert (obj != loc);
ret = loc_descriptor_from_tree_1 (obj, 1);
if (ret == 0
|| bitpos % BITS_PER_UNIT != 0 || bitsize % BITS_PER_UNIT != 0)
list_ret = loc_list_from_tree (obj, 1);
if (list_ret == 0)
return 0;
if (bitpos % BITS_PER_UNIT != 0 || bitsize % BITS_PER_UNIT != 0)
{
expansion_failed (loc, NULL_RTX,
"bitfield access");
return 0;
}
if (offset != NULL_TREE)
{
/* Variable offset. */
ret1 = loc_descriptor_from_tree_1 (offset, 0);
if (ret1 == 0)
list_ret1 = loc_list_from_tree (offset, 0);
if (list_ret1 == 0)
return 0;
add_loc_descr (&ret, ret1);
add_loc_descr (&ret, new_loc_descr (DW_OP_plus, 0, 0));
add_loc_list (&list_ret, list_ret1);
if (!list_ret)
return 0;
add_loc_descr_to_each (list_ret, new_loc_descr (DW_OP_plus, 0, 0));
}
bytepos = bitpos / BITS_PER_UNIT;
loc_descr_plus_const (&ret, bytepos);
if (bytepos > 0)
add_loc_descr_to_each (list_ret, new_loc_descr (DW_OP_plus_uconst, bytepos, 0));
else if (bytepos < 0)
loc_list_plus_const (list_ret, bytepos);
have_address = 1;
break;
......@@ -12180,7 +12518,11 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
if (host_integerp (loc, 0))
ret = int_loc_descriptor (tree_low_cst (loc, 0));
else
return 0;
{
expansion_failed (loc, NULL_RTX,
"Integer operand is not host integer");
return 0;
}
break;
case CONSTRUCTOR:
......@@ -12250,11 +12592,11 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
if (TREE_CODE (TREE_OPERAND (loc, 1)) == INTEGER_CST
&& host_integerp (TREE_OPERAND (loc, 1), 0))
{
ret = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 0);
if (ret == 0)
list_ret = loc_list_from_tree (TREE_OPERAND (loc, 0), 0);
if (list_ret == 0)
return 0;
loc_descr_plus_const (&ret, tree_low_cst (TREE_OPERAND (loc, 1), 0));
loc_list_plus_const (list_ret, tree_low_cst (TREE_OPERAND (loc, 1), 0));
break;
}
......@@ -12298,13 +12640,15 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
goto do_binop;
do_binop:
ret = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 0);
ret1 = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 1), 0);
if (ret == 0 || ret1 == 0)
list_ret = loc_list_from_tree (TREE_OPERAND (loc, 0), 0);
list_ret1 = loc_list_from_tree (TREE_OPERAND (loc, 1), 0);
if (list_ret == 0 || list_ret1 == 0)
return 0;
add_loc_descr (&ret, ret1);
add_loc_descr (&ret, new_loc_descr (op, 0, 0));
add_loc_list (&list_ret, list_ret1);
if (list_ret == 0)
return 0;
add_loc_descr_to_each (list_ret, new_loc_descr (op, 0, 0));
break;
case TRUTH_NOT_EXPR:
......@@ -12321,11 +12665,11 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
goto do_unop;
do_unop:
ret = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 0);
if (ret == 0)
list_ret = loc_list_from_tree (TREE_OPERAND (loc, 0), 0);
if (list_ret == 0)
return 0;
add_loc_descr (&ret, new_loc_descr (op, 0, 0));
add_loc_descr_to_each (list_ret, new_loc_descr (op, 0, 0));
break;
case MIN_EXPR:
......@@ -12345,29 +12689,29 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
case COND_EXPR:
{
dw_loc_descr_ref lhs
= loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 1), 0);
dw_loc_descr_ref rhs
= loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 2), 0);
= loc_descriptor_from_tree (TREE_OPERAND (loc, 1), 0);
dw_loc_list_ref rhs
= loc_list_from_tree (TREE_OPERAND (loc, 2), 0);
dw_loc_descr_ref bra_node, jump_node, tmp;
ret = loc_descriptor_from_tree_1 (TREE_OPERAND (loc, 0), 0);
if (ret == 0 || lhs == 0 || rhs == 0)
list_ret = loc_list_from_tree (TREE_OPERAND (loc, 0), 0);
if (list_ret == 0 || lhs == 0 || rhs == 0)
return 0;
bra_node = new_loc_descr (DW_OP_bra, 0, 0);
add_loc_descr (&ret, bra_node);
add_loc_descr_to_each (list_ret, bra_node);
add_loc_descr (&ret, rhs);
add_loc_list (&list_ret, rhs);
jump_node = new_loc_descr (DW_OP_skip, 0, 0);
add_loc_descr (&ret, jump_node);
add_loc_descr_to_each (list_ret, jump_node);
add_loc_descr (&ret, lhs);
add_loc_descr_to_each (list_ret, lhs);
bra_node->dw_loc_oprnd1.val_class = dw_val_class_loc;
bra_node->dw_loc_oprnd1.v.val_loc = lhs;
/* ??? Need a node to point the skip at. Use a nop. */
tmp = new_loc_descr (DW_OP_nop, 0, 0);
add_loc_descr (&ret, tmp);
add_loc_descr_to_each (list_ret, tmp);
jump_node->dw_loc_oprnd1.val_class = dw_val_class_loc;
jump_node->dw_loc_oprnd1.v.val_loc = tmp;
}
......@@ -12381,7 +12725,11 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
up, for instance, with the C STMT_EXPR. */
if ((unsigned int) TREE_CODE (loc)
>= (unsigned int) LAST_AND_UNUSED_TREE_CODE)
return 0;
{
expansion_failed (loc, NULL_RTX,
"language specific tree node");
return 0;
}
#ifdef ENABLE_CHECKING
/* Otherwise this is a generic code; we should just lists all of
......@@ -12394,32 +12742,60 @@ loc_descriptor_from_tree_1 (tree loc, int want_address)
#endif
}
if (!ret && !list_ret)
return 0;
/* Show if we can't fill the request for an address. */
if (want_address && !have_address)
return 0;
{
expansion_failed (loc, NULL_RTX,
"Want address and only have value");
return 0;
}
gcc_assert (!ret || !list_ret);
/* If we've got an address and don't want one, dereference. */
if (!want_address && have_address && ret)
if (!want_address && have_address)
{
HOST_WIDE_INT size = int_size_in_bytes (TREE_TYPE (loc));
if (size > DWARF2_ADDR_SIZE || size == -1)
return 0;
{
expansion_failed (loc, NULL_RTX,
"DWARF address size mismatch");
return 0;
}
else if (size == DWARF2_ADDR_SIZE)
op = DW_OP_deref;
else
op = DW_OP_deref_size;
add_loc_descr (&ret, new_loc_descr (op, size, 0));
if (ret)
add_loc_descr (&ret, new_loc_descr (op, size, 0));
else
add_loc_descr_to_each (list_ret, new_loc_descr (op, size, 0));
}
if (ret)
list_ret = single_element_loc_list (ret);
return ret;
return list_ret;
}
static inline dw_loc_descr_ref
loc_descriptor_from_tree (tree loc)
/* Same as above but return only single location expression. */
static dw_loc_descr_ref
loc_descriptor_from_tree (tree loc, int want_address)
{
return loc_descriptor_from_tree_1 (loc, 2);
dw_loc_list_ref ret = loc_list_from_tree (loc, want_address);
if (!ret)
return NULL;
if (ret->dw_loc_next)
{
expansion_failed (loc, NULL_RTX,
"Location list where only loc descriptor needed");
return NULL;
}
return ret->expr;
}
/* Given a value, round it up to the lowest multiple of `boundary'
......@@ -12529,9 +12905,9 @@ field_byte_offset (const_tree decl)
field_size_tree = DECL_SIZE (decl);
/* The size could be unspecified if there was an error, or for
a flexible array member. */
a flexible array member. */
if (!field_size_tree)
field_size_tree = bitsize_zero_node;
field_size_tree = bitsize_zero_node;
/* If the size of the field is not constant, use the type size. */
if (host_integerp (field_size_tree, 1))
......@@ -12626,10 +13002,14 @@ field_byte_offset (const_tree decl)
static inline void
add_AT_location_description (dw_die_ref die, enum dwarf_attribute attr_kind,
dw_loc_descr_ref descr)
dw_loc_list_ref descr)
{
if (descr != 0)
add_AT_loc (die, attr_kind, descr);
if (descr == 0)
return;
if (single_element_loc_list_p (descr))
add_AT_loc (die, attr_kind, descr->expr);
else
add_AT_loc_list (die, attr_kind, descr);
}
/* Attach the specialized form of location attribute used for data members of
......@@ -13267,36 +13647,6 @@ rtl_for_decl_location (tree decl)
return rtl;
}
/* We need to figure out what section we should use as the base for the
address ranges where a given location is valid.
1. If this particular DECL has a section associated with it, use that.
2. If this function has a section associated with it, use that.
3. Otherwise, use the text section.
XXX: If you split a variable across multiple sections, we won't notice. */
static const char *
secname_for_decl (const_tree decl)
{
const char *secname;
if (VAR_OR_FUNCTION_DECL_P (decl) && DECL_SECTION_NAME (decl))
{
tree sectree = DECL_SECTION_NAME (decl);
secname = TREE_STRING_POINTER (sectree);
}
else if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
{
tree sectree = DECL_SECTION_NAME (current_function_decl);
secname = TREE_STRING_POINTER (sectree);
}
else if (cfun && in_cold_section_p)
secname = crtl->subsections.cold_section_label;
else
secname = text_section_label;
return secname;
}
/* Check whether decl is a Fortran COMMON symbol. If not, NULL_TREE is
returned. If so, the decl for the COMMON block is returned, and the
value is the offset into the common block for the symbol. */
......@@ -13347,60 +13697,6 @@ fortran_common (tree decl, HOST_WIDE_INT *value)
return cvar;
}
/* Dereference a location expression LOC if DECL is passed by invisible
reference. */
static dw_loc_descr_ref
loc_by_reference (dw_loc_descr_ref loc, tree decl)
{
HOST_WIDE_INT size;
enum dwarf_location_atom op;
if (loc == NULL)
return NULL;
if ((TREE_CODE (decl) != PARM_DECL
&& TREE_CODE (decl) != RESULT_DECL
&& TREE_CODE (decl) != VAR_DECL)
|| !DECL_BY_REFERENCE (decl))
return loc;
/* If loc is DW_OP_reg{0...31,x}, don't add DW_OP_deref, instead
change it into corresponding DW_OP_breg{0...31,x} 0. Then the
location expression is considered to be address of a memory location,
rather than the register itself. */
if (((loc->dw_loc_opc >= DW_OP_reg0 && loc->dw_loc_opc <= DW_OP_reg31)
|| loc->dw_loc_opc == DW_OP_regx)
&& (loc->dw_loc_next == NULL
|| (loc->dw_loc_next->dw_loc_opc == DW_OP_GNU_uninit
&& loc->dw_loc_next->dw_loc_next == NULL)))
{
if (loc->dw_loc_opc == DW_OP_regx)
{
loc->dw_loc_opc = DW_OP_bregx;
loc->dw_loc_oprnd2.v.val_int = 0;
}
else
{
loc->dw_loc_opc
= (enum dwarf_location_atom)
(loc->dw_loc_opc + (DW_OP_breg0 - DW_OP_reg0));
loc->dw_loc_oprnd1.v.val_int = 0;
}
return loc;
}
size = int_size_in_bytes (TREE_TYPE (decl));
if (size > DWARF2_ADDR_SIZE || size == -1)
return 0;
else if (size == DWARF2_ADDR_SIZE)
op = DW_OP_deref;
else
op = DW_OP_deref_size;
add_loc_descr (&loc, new_loc_descr (op, size, 0));
return loc;
}
/* Generate *either* a DW_AT_location attribute or else a DW_AT_const_value
data attribute for a variable or a parameter. We generate the
DW_AT_const_value attribute only in those cases where the given variable
......@@ -13418,6 +13714,7 @@ add_location_or_const_value_attribute (dw_die_ref die, tree decl,
{
rtx rtl;
dw_loc_descr_ref descr;
dw_loc_list_ref descr_list;
var_loc_list *loc_list;
struct var_loc_node *node;
if (TREE_CODE (decl) == ERROR_MARK)
......@@ -13539,18 +13836,18 @@ add_location_or_const_value_attribute (dw_die_ref die, tree decl,
if (descr)
{
descr = loc_by_reference (descr, decl);
add_AT_location_description (die, attr, descr);
add_AT_location_description (die, attr, single_element_loc_list (descr));
return;
}
}
/* We couldn't get any rtl, so try directly generating the location
description from the tree. */
descr = loc_descriptor_from_tree (decl);
if (descr)
descr_list = loc_list_from_tree (decl, 2);
if (descr_list)
{
descr = loc_by_reference (descr, decl);
add_AT_location_description (die, attr, descr);
add_AT_location_description (die, attr, descr_list);
return;
}
/* None of that worked, so it must not really have a location;
......@@ -13967,7 +14264,7 @@ add_bound_info (dw_die_ref subrange_die, enum dwarf_attribute bound_attr, tree b
case RESULT_DECL:
{
dw_die_ref decl_die = lookup_decl_die (bound);
dw_loc_descr_ref loc;
dw_loc_list_ref loc;
/* ??? Can this happen, or should the variable have been bound
first? Probably it can, since I imagine that we try to create
......@@ -13978,7 +14275,7 @@ add_bound_info (dw_die_ref subrange_die, enum dwarf_attribute bound_attr, tree b
add_AT_die_ref (subrange_die, bound_attr, decl_die);
else
{
loc = loc_descriptor_from_tree_1 (bound, 0);
loc = loc_list_from_tree (bound, 0);
add_AT_location_description (subrange_die, bound_attr, loc);
}
break;
......@@ -13990,10 +14287,10 @@ add_bound_info (dw_die_ref subrange_die, enum dwarf_attribute bound_attr, tree b
evaluate the value of the array bound. */
dw_die_ref ctx, decl_die;
dw_loc_descr_ref loc;
dw_loc_list_ref list;
loc = loc_descriptor_from_tree (bound);
if (loc == NULL)
list = loc_list_from_tree (bound, 2);
if (list == NULL)
break;
if (current_function_decl == 0)
......@@ -14004,7 +14301,10 @@ add_bound_info (dw_die_ref subrange_die, enum dwarf_attribute bound_attr, tree b
decl_die = new_die (DW_TAG_variable, ctx, bound);
add_AT_flag (decl_die, DW_AT_artificial, 1);
add_type_attribute (decl_die, TREE_TYPE (bound), 1, 0, ctx);
add_AT_loc (decl_die, DW_AT_location, loc);
if (list->dw_loc_next)
add_AT_loc_list (decl_die, DW_AT_location, list);
else
add_AT_loc (decl_die, DW_AT_location, list->expr);
add_AT_die_ref (subrange_die, bound_attr, decl_die);
break;
......@@ -14610,12 +14910,12 @@ gen_array_type_die (tree type, dw_die_ref context_die)
&& DECL_P (TYPE_MAX_VALUE (TYPE_DOMAIN (type))))
{
tree szdecl = TYPE_MAX_VALUE (TYPE_DOMAIN (type));
dw_loc_descr_ref loc = loc_descriptor_from_tree (szdecl);
dw_loc_list_ref loc = loc_list_from_tree (szdecl, 2);
size = int_size_in_bytes (TREE_TYPE (szdecl));
if (loc && size > 0)
{
add_AT_loc (array_die, DW_AT_string_length, loc);
add_AT_location_description (array_die, DW_AT_string_length, loc);
if (size != DWARF2_ADDR_SIZE)
add_AT_unsigned (array_die, DW_AT_byte_size, size);
}
......@@ -14709,7 +15009,7 @@ descr_info_loc (tree val, tree base_decl)
CASE_CONVERT:
return descr_info_loc (TREE_OPERAND (val, 0), base_decl);
case VAR_DECL:
return loc_descriptor_from_tree_1 (val, 0);
return loc_descriptor_from_tree (val, 0);
case INTEGER_CST:
if (host_integerp (val, 0))
return int_loc_descriptor (tree_low_cst (val, 0));
......@@ -15015,7 +15315,7 @@ gen_formal_parameter_die (tree node, tree origin, dw_die_ref context_die)
{
tree type = TREE_TYPE (node);
add_name_and_src_coords_attributes (parm_die, node);
if (DECL_BY_REFERENCE (node))
if (decl_by_reference_p (node))
add_type_attribute (parm_die, TREE_TYPE (type), 0, 0,
context_die);
else
......@@ -15467,7 +15767,7 @@ gen_subprogram_die (tree decl, dw_die_ref context_die)
if (cfun->static_chain_decl)
add_AT_location_description (subr_die, DW_AT_static_link,
loc_descriptor_from_tree (cfun->static_chain_decl));
loc_list_from_tree (cfun->static_chain_decl, 2));
}
/* Generate child dies for template paramaters. */
......@@ -15637,7 +15937,7 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
{
tree field;
dw_die_ref com_die;
dw_loc_descr_ref loc;
dw_loc_list_ref loc;
die_node com_die_arg;
var_die = lookup_decl_die (decl_or_origin);
......@@ -15645,22 +15945,23 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
{
if (get_AT (var_die, DW_AT_location) == NULL)
{
loc = loc_descriptor_from_tree (com_decl);
loc = loc_list_from_tree (com_decl, off ? 1 : 2);
if (loc)
{
if (off)
{
/* Optimize the common case. */
if (loc->dw_loc_opc == DW_OP_addr
&& loc->dw_loc_next == NULL
&& GET_CODE (loc->dw_loc_oprnd1.v.val_addr)
if (single_element_loc_list_p (loc)
&& loc->expr->dw_loc_opc == DW_OP_addr
&& loc->expr->dw_loc_next == NULL
&& GET_CODE (loc->expr->dw_loc_oprnd1.v.val_addr)
== SYMBOL_REF)
loc->dw_loc_oprnd1.v.val_addr
= plus_constant (loc->dw_loc_oprnd1.v.val_addr, off);
loc->expr->dw_loc_oprnd1.v.val_addr
= plus_constant (loc->expr->dw_loc_oprnd1.v.val_addr, off);
else
loc_descr_plus_const (&loc, off);
loc_list_plus_const (loc, off);
}
add_AT_loc (var_die, DW_AT_location, loc);
add_AT_location_description (var_die, DW_AT_location, loc);
remove_AT (var_die, DW_AT_declaration);
}
}
......@@ -15676,7 +15977,7 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
com_die_arg.decl_id = DECL_UID (com_decl);
com_die_arg.die_parent = context_die;
com_die = (dw_die_ref) htab_find (common_block_die_table, &com_die_arg);
loc = loc_descriptor_from_tree (com_decl);
loc = loc_list_from_tree (com_decl, 2);
if (com_die == NULL)
{
const char *cnam
......@@ -15687,10 +15988,10 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
add_name_and_src_coords_attributes (com_die, com_decl);
if (loc)
{
add_AT_loc (com_die, DW_AT_location, loc);
add_AT_location_description (com_die, DW_AT_location, loc);
/* Avoid sharing the same loc descriptor between
DW_TAG_common_block and DW_TAG_variable. */
loc = loc_descriptor_from_tree (com_decl);
loc = loc_list_from_tree (com_decl, 2);
}
else if (DECL_EXTERNAL (decl))
add_AT_flag (com_die, DW_AT_declaration, 1);
......@@ -15701,8 +16002,8 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
}
else if (get_AT (com_die, DW_AT_location) == NULL && loc)
{
add_AT_loc (com_die, DW_AT_location, loc);
loc = loc_descriptor_from_tree (com_decl);
add_AT_location_description (com_die, DW_AT_location, loc);
loc = loc_list_from_tree (com_decl, 2);
remove_AT (com_die, DW_AT_declaration);
}
var_die = new_die (DW_TAG_variable, com_die, decl);
......@@ -15715,15 +16016,16 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
if (off)
{
/* Optimize the common case. */
if (loc->dw_loc_opc == DW_OP_addr
&& loc->dw_loc_next == NULL
&& GET_CODE (loc->dw_loc_oprnd1.v.val_addr) == SYMBOL_REF)
loc->dw_loc_oprnd1.v.val_addr
= plus_constant (loc->dw_loc_oprnd1.v.val_addr, off);
if (single_element_loc_list_p (loc)
&& loc->expr->dw_loc_opc == DW_OP_addr
&& loc->expr->dw_loc_next == NULL
&& GET_CODE (loc->expr->dw_loc_oprnd1.v.val_addr) == SYMBOL_REF)
loc->expr->dw_loc_oprnd1.v.val_addr
= plus_constant (loc->expr->dw_loc_oprnd1.v.val_addr, off);
else
loc_descr_plus_const (&loc, off);
loc_list_plus_const (loc, off);
}
add_AT_loc (var_die, DW_AT_location, loc);
add_AT_location_description (var_die, DW_AT_location, loc);
}
else if (DECL_EXTERNAL (decl))
add_AT_flag (var_die, DW_AT_declaration, 1);
......@@ -15786,10 +16088,7 @@ gen_variable_die (tree decl, tree origin, dw_die_ref context_die)
tree type = TREE_TYPE (decl);
add_name_and_src_coords_attributes (var_die, decl);
if ((TREE_CODE (decl) == PARM_DECL
|| TREE_CODE (decl) == RESULT_DECL
|| TREE_CODE (decl) == VAR_DECL)
&& DECL_BY_REFERENCE (decl))
if (decl_by_reference_p (decl))
add_type_attribute (var_die, TREE_TYPE (type), 0, 0, context_die);
else
add_type_attribute (var_die, type, TREE_READONLY (decl),
......@@ -17066,9 +17365,7 @@ gen_decl_die (tree decl, tree origin, dw_die_ref context_die)
/* Output any DIEs that are needed to specify the type of this data
object. */
if ((TREE_CODE (decl_or_origin) == RESULT_DECL
|| TREE_CODE (decl_or_origin) == VAR_DECL)
&& DECL_BY_REFERENCE (decl_or_origin))
if (decl_by_reference_p (decl_or_origin))
gen_type_die (TREE_TYPE (TREE_TYPE (decl_or_origin)), context_die);
else
gen_type_die (TREE_TYPE (decl_or_origin), context_die);
......
......@@ -4304,7 +4304,7 @@ struct rtl_opt_pass pass_final =
{
{
RTL_PASS,
NULL, /* name */
"final", /* name */
NULL, /* gate */
rest_of_handle_final, /* execute */
NULL, /* sub */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment