Hi,
this extends the support for thunks present in the middle-end to accomodate
the Ada language, which can generate more diverse thunks than C++. The main
couple of enhancements are:
1. Indirect offsets used to implement thunks for self-referential types,
2. Local thunks used to implement thunks for types defined locally.
It's done entirely in the middle-end, i.e. the interface with the back-end is
not changed, the rationale being that the vast majority of thunks generated in
Ada are the same as those generated in C++.
This should be transparent for C++, except for the calls.c change which causes
thunks not generated by the back-end to use a tailcall even at -O0 when that's
possible, since that's what most of the back-ends do too.
Tested x86-64/Linux, OK for the mainline?
2018-09-25 Eric Botcazou <ebotca...@adacore.com>
Pierre-Marie de Rodat <dero...@adacore.com>
Javier Miranda <mira...@adacore.com>
* calls.c (expand_call): Try to do a tail call for thunks at -O0 too.
* cgraph.h (struct cgraph_thunk_info): Add indirect_offset.
(cgraph_node::create_thunk): Add indirect_offset parameter.
(thunk_adjust): Likewise.
* cgraph.c (cgraph_node::create_thunk): Add indirect_offset parameter
and initialize the corresponding field with it.
(cgraph_node::dump): Dump indirect_offset field.
* cgraphunit.c (cgraph_node::analyze): Be prepared for external thunks.
(thunk_adjust): Add indirect_offset parameter and deal with it.
(cgraph_node::expand_thunk): Deal with the indirect_offset field and
pass it to thunk_adjust. Do not call the target hook if it's non-zero
or if the thunk is external or local. Fix formatting. Do not chain
the RESULT_DECL to BLOCK_VARS. Pass the static chain to the target,
if any, in the GIMPLE representation.
* lto-cgraph.c (lto_output_node): Write indirect_offset field.
(input_node): Write indirect_offset field.
* tree-inline.c (expand_call_inline): Pass indirect_offset field in the
call to thunk_adjust.
* tree-nested.c (struct nesting_info): Add thunk_p field.
(create_nesting_tree): Set it.
(convert_all_function_calls): Copy static chain from targets to thunks.
(finalize_nesting_tree_1): Return early for thunks.
(unnest_nesting_tree_1): Do not finalize thunks.
(gimplify_all_functions): Do not gimplify thunks.
cp/
* method.c (use_thunk): Adjust call to cgraph_node::create_thunk.
ada/
* exp_disp.adb (Expand_Interface_Conversion): Use Present test.
(Expand_Interface_Thunk): Propagate debug info setting from target.
* exp_util.ads (Find_Interface_Tag): Adjust comment.
* exp_util.adb (Find_Interface_Tag): Remove assertions of success.
* sem_res.adb (Resolve_Actuals): If the formal is a class-wide type
conversion then do not skip resolving and expanding the conversion.
* sem_util.adb (Is_Variable_Size_Record): Only look at components
and robustify the implementation.
* fe.h (Find_Interface_Tag): Declare.
(Is_Variable_Size_Record): Likewise.
* gcc-interface/decl.c (is_cplusplus_method): Do not require C++
convention on Interfaces.
* gcc-interface/trans.c (Subprogram_Body_to_gnu): Try to create a
bona-fide thunk and hand it over to the middle-end.
(get_controlling_type): New function.
(use_alias_for_thunk_p): Likewise.
(thunk_labelno): New static variable.
(make_covariant_thunk): New function.
(maybe_make_gnu_thunk): Likewise.
* gcc-interface/utils.c (finish_subprog_decl): Set DECL_CONTEXT of the
result DECL here instead of...
(end_subprog_body): ...here.
--
Eric Botcazou
Index: ada/exp_disp.adb
===================================================================
--- ada/exp_disp.adb (revision 264525)
+++ ada/exp_disp.adb (working copy)
@@ -1454,7 +1454,7 @@ package body Exp_Disp is
end if;
Iface_Tag := Find_Interface_Tag (Operand_Typ, Iface_Typ);
- pragma Assert (Iface_Tag /= Empty);
+ pragma Assert (Present (Iface_Tag));
-- Keep separate access types to interfaces because one internal
-- function is used to handle the null value (see following comments)
@@ -2046,6 +2046,7 @@ package body Exp_Disp is
Set_Ekind (Thunk_Id, Ekind (Prim));
Set_Is_Thunk (Thunk_Id);
Set_Convention (Thunk_Id, Convention (Prim));
+ Set_Needs_Debug_Info (Thunk_Id, Needs_Debug_Info (Target));
Set_Thunk_Entity (Thunk_Id, Target);
-- Procedure case
Index: ada/exp_util.adb
===================================================================
--- ada/exp_util.adb (revision 264525)
+++ ada/exp_util.adb (working copy)
@@ -5529,7 +5529,6 @@ package body Exp_Util is
then
-- Skip the tag associated with the primary table
- pragma Assert (Etype (First_Tag_Component (Typ)) = RTE (RE_Tag));
AI_Tag := Next_Tag_Component (First_Tag_Component (Typ));
pragma Assert (Present (AI_Tag));
@@ -5590,14 +5589,12 @@ package body Exp_Util is
-- primary dispatch table.
if Is_Ancestor (Iface, Typ, Use_Full_View => True) then
- pragma Assert (Etype (First_Tag_Component (Typ)) = RTE (RE_Tag));
return First_Tag_Component (Typ);
-- Otherwise we need to search for its associated tag component
else
Find_Tag (Typ);
- pragma Assert (Found);
return AI_Tag;
end if;
end Find_Interface_Tag;
Index: ada/exp_util.ads
===================================================================
--- ada/exp_util.ads (revision 264525)
+++ ada/exp_util.ads (working copy)
@@ -585,8 +585,9 @@ package Exp_Util is
function Find_Interface_Tag
(T : Entity_Id;
Iface : Entity_Id) return Entity_Id;
- -- Ada 2005 (AI-251): Given a type T implementing the interface Iface,
- -- return the record component containing the tag of Iface.
+ -- Ada 2005 (AI-251): Given a type T and an interface Iface, return the
+ -- record component containing the tag of Iface if T implements Iface or
+ -- Empty if it does not.
function Find_Prim_Op (T : Entity_Id; Name : Name_Id) return Entity_Id;
-- Find the first primitive operation of a tagged type T with name Name.
Index: ada/fe.h
===================================================================
--- ada/fe.h (revision 264525)
+++ ada/fe.h (working copy)
@@ -159,8 +159,10 @@ extern void Get_External_Name (Entity_Id
/* exp_util: */
#define Is_Fully_Repped_Tagged_Type exp_util__is_fully_repped_tagged_type
+#define Find_Interface_Tag exp_util__find_interface_tag
extern Boolean Is_Fully_Repped_Tagged_Type (Entity_Id);
+extern Entity_Id Find_Interface_Tag (Entity_Id, Entity_Id);
/* lib: */
@@ -269,12 +271,14 @@ extern Boolean Is_OK_Static_Subtype (Ent
#define Defining_Entity sem_util__defining_entity
#define First_Actual sem_util__first_actual
#define Next_Actual sem_util__next_actual
+#define Is_Variable_Size_Record sem_util__is_variable_size_record
#define Requires_Transient_Scope sem_util__requires_transient_scope
extern Entity_Id Defining_Entity (Node_Id);
extern Node_Id First_Actual (Node_Id);
extern Node_Id Next_Actual (Node_Id);
-extern Boolean Requires_Transient_Scope (Entity_Id);
+extern Boolean Is_Variable_Size_Record (Entity_Id Id);
+extern Boolean Requires_Transient_Scope (Entity_Id);
/* sinfo: */
Index: ada/gcc-interface/decl.c
===================================================================
--- ada/gcc-interface/decl.c (revision 264525)
+++ ada/gcc-interface/decl.c (working copy)
@@ -4850,15 +4850,15 @@ is_cplusplus_method (Entity_Id gnat_enti
if (Convention (gnat_entity) != Convention_CPP)
return false;
- /* And that the type of the first parameter (indirectly) has it too. */
+ /* And that the type of the first parameter (indirectly) has it too, but
+ we make an exception for Interfaces because they need not be imported. */
Entity_Id gnat_first = First_Formal (gnat_entity);
if (No (gnat_first))
return false;
-
Entity_Id gnat_type = Etype (gnat_first);
if (Is_Access_Type (gnat_type))
gnat_type = Directly_Designated_Type (gnat_type);
- if (Convention (gnat_type) != Convention_CPP)
+ if (Convention (gnat_type) != Convention_CPP && !Is_Interface (gnat_type))
return false;
/* This is the main case: a C++ virtual method imported as a primitive
Index: ada/gcc-interface/trans.c
===================================================================
--- ada/gcc-interface/trans.c (revision 264525)
+++ ada/gcc-interface/trans.c (working copy)
@@ -250,6 +250,7 @@ static bool set_end_locus_from_node (tre
static int lvalue_required_p (Node_Id, tree, bool, bool);
static tree build_raise_check (int, enum exception_info_kind);
static tree create_init_temporary (const char *, tree, tree *, Node_Id);
+static bool maybe_make_gnu_thunk (Entity_Id gnat_thunk, tree gnu_thunk);
/* Hooks for debug info back-ends, only supported and used in a restricted set
of configurations. */
@@ -3794,6 +3795,11 @@ Subprogram_Body_to_gnu (Node_Id gnat_nod
if (Was_Expression_Function (gnat_node))
DECL_DISREGARD_INLINE_LIMITS (gnu_subprog_decl) = 1;
+ /* Try to create a bona-fide thunk and hand it over to the middle-end. */
+ if (Is_Thunk (gnat_subprog_id)
+ && maybe_make_gnu_thunk (gnat_subprog_id, gnu_subprog_decl))
+ return;
+
/* Initialize the information structure for the function. */
allocate_struct_function (gnu_subprog_decl, false);
gnu_subprog_language = ggc_cleared_alloc<language_function> ();
@@ -10316,6 +10322,242 @@ get_elaboration_procedure (void)
return gnu_elab_proc_stack->last ();
}
+/* Return the controlling type of a dispatching subprogram. */
+
+static Entity_Id
+get_controlling_type (Entity_Id subprog)
+{
+ /* This is modelled on Expand_Interface_Thunk. */
+ Entity_Id controlling_type = Etype (First_Formal (subprog));
+ if (Is_Access_Type (controlling_type))
+ controlling_type = Directly_Designated_Type (controlling_type);
+ controlling_type = Underlying_Type (controlling_type);
+ if (Is_Concurrent_Type (controlling_type))
+ controlling_type = Corresponding_Record_Type (controlling_type);
+ controlling_type = Base_Type (controlling_type);
+ return controlling_type;
+}
+
+/* Return whether we should use an alias for the TARGET of a thunk
+ in order to make the call generated in the thunk local. */
+
+static bool
+use_alias_for_thunk_p (tree target)
+{
+ /* We cannot generate a local call in this case. */
+ if (DECL_EXTERNAL (target))
+ return false;
+
+ /* The call is already local in this case. */
+ if (TREE_CODE (DECL_CONTEXT (target)) == FUNCTION_DECL)
+ return false;
+
+ return TARGET_USE_LOCAL_THUNK_ALIAS_P (target);
+}
+
+static GTY(()) unsigned long thunk_labelno = 0;
+
+/* Create an alias for TARGET to be used as the target of a thunk. */
+
+static tree
+make_alias_for_thunk (tree target)
+{
+ char buf[64];
+ targetm.asm_out.generate_internal_label (buf, "LTHUNK", thunk_labelno++);
+
+ tree alias = build_decl (DECL_SOURCE_LOCATION (target), TREE_CODE (target),
+ get_identifier (buf), TREE_TYPE (target));
+
+ DECL_LANG_SPECIFIC (alias) = DECL_LANG_SPECIFIC (target);
+ DECL_CONTEXT (alias) = DECL_CONTEXT (target);
+ TREE_READONLY (alias) = TREE_READONLY (target);
+ TREE_THIS_VOLATILE (alias) = TREE_THIS_VOLATILE (target);
+ DECL_ARTIFICIAL (alias) = 1;
+ DECL_INITIAL (alias) = error_mark_node;
+ DECL_ARGUMENTS (alias) = copy_list (DECL_ARGUMENTS (target));
+ TREE_ADDRESSABLE (alias) = 1;
+ SET_DECL_ASSEMBLER_NAME (alias, DECL_NAME (alias));
+
+ cgraph_node *n = cgraph_node::create_same_body_alias (alias, target);
+ gcc_assert (n);
+
+ return alias;
+}
+
+/* Create the covariant part of the {GNAT,GNU}_THUNK. */
+
+static tree
+make_covariant_thunk (Entity_Id gnat_thunk, tree gnu_thunk)
+{
+ tree gnu_name = create_concat_name (gnat_thunk, "CV");
+ tree gnu_cv_thunk
+ = build_decl (DECL_SOURCE_LOCATION (gnu_thunk), TREE_CODE (gnu_thunk),
+ gnu_name, TREE_TYPE (gnu_thunk));
+
+ DECL_ARGUMENTS (gnu_cv_thunk) = copy_list (DECL_ARGUMENTS (gnu_thunk));
+ DECL_RESULT (gnu_cv_thunk) = copy_node (DECL_RESULT (gnu_thunk));
+ DECL_CONTEXT (DECL_RESULT (gnu_cv_thunk)) = gnu_cv_thunk;
+
+ DECL_LANG_SPECIFIC (gnu_cv_thunk) = DECL_LANG_SPECIFIC (gnu_thunk);
+ DECL_CONTEXT (gnu_cv_thunk) = DECL_CONTEXT (gnu_thunk);
+ TREE_READONLY (gnu_cv_thunk) = TREE_READONLY (gnu_thunk);
+ TREE_THIS_VOLATILE (gnu_cv_thunk) = TREE_THIS_VOLATILE (gnu_thunk);
+ TREE_PUBLIC (gnu_cv_thunk) = TREE_PUBLIC (gnu_thunk);
+ DECL_ARTIFICIAL (gnu_cv_thunk) = 1;
+
+ return gnu_cv_thunk;
+}
+
+/* Try to create a GNU thunk for {GNAT,GNU}_THUNK and return true on success.
+
+ GNU thunks are more efficient than GNAT thunks because they don't call into
+ the runtime to retrieve the offset used in the displacement operation, but
+ they are tailored to C++ and thus too limited to support the full range of
+ thunks generated in Ada. Here's the complete list of limitations:
+
+ 1. Multi-controlling thunks, i.e thunks with more than one controlling
+ parameter, are simply not supported.
+
+ 2. Covariant thunks, i.e. thunks for which the result is also controlling,
+ are split into a pair of (this, covariant-only) thunks.
+
+ 3. Variable-offset thunks, i.e. thunks for which the offset depends on the
+ object and not only on its type, are supported as 2nd class citizens.
+
+ 4. External thunks, i.e. thunks for which the target is not declared in
+ the same unit as the thunk, are supported as 2nd class citizens.
+
+ 5. Local thunks, i.e. thunks generated for a local type, are supported as
+ 2nd class citizens. */
+
+static bool
+maybe_make_gnu_thunk (Entity_Id gnat_thunk, tree gnu_thunk)
+{
+ const Entity_Id gnat_target = Thunk_Entity (gnat_thunk);
+
+ /* Check that the first formal of the target is the only controlling one. */
+ Entity_Id gnat_formal = First_Formal (gnat_target);
+ if (!Is_Controlling_Formal (gnat_formal))
+ return false;
+ for (gnat_formal = Next_Formal (gnat_formal);
+ Present (gnat_formal);
+ gnat_formal = Next_Formal (gnat_formal))
+ if (Is_Controlling_Formal (gnat_formal))
+ return false;
+
+ /* Look for the types that control the target and the thunk. */
+ const Entity_Id gnat_controlling_type = get_controlling_type (gnat_target);
+ const Entity_Id gnat_interface_type = get_controlling_type (gnat_thunk);
+
+ /* Now compute whether the former covers the latter. */
+ const Entity_Id gnat_interface_tag
+ = Is_Interface (gnat_interface_type)
+ ? Find_Interface_Tag (gnat_controlling_type, gnat_interface_type)
+ : Empty;
+ tree gnu_interface_tag
+ = Present (gnat_interface_tag)
+ ? gnat_to_gnu_field_decl (gnat_interface_tag)
+ : NULL_TREE;
+ tree gnu_interface_offset
+ = gnu_interface_tag ? byte_position (gnu_interface_tag) : NULL_TREE;
+
+ /* There are three ways to retrieve the offset between the interface view
+ and the base object. Either the controlling type covers the interface
+ type and the offset of the corresponding tag is fixed, in which case it
+ can be statically encoded in the thunk (see FIXED_OFFSET below). Or the
+ controlling type doesn't cover the interface type but is of fixed size,
+ in which case the offset is stored in the dispatch table, two pointers
+ above the dispatch table address (see VIRTUAL_VALUE below). Otherwise,
+ the offset is variable and is stored right after the tag in every object
+ (see INDIRECT_OFFSET below). See also a-tags.ads for more details. */
+ HOST_WIDE_INT fixed_offset, virtual_value, indirect_offset;
+ tree virtual_offset;
+
+ if (gnu_interface_offset && TREE_CODE (gnu_interface_offset) == INTEGER_CST)
+ {
+ fixed_offset = - tree_to_shwi (gnu_interface_offset);
+ virtual_value = 0;
+ virtual_offset = NULL_TREE;
+ indirect_offset = 0;
+ }
+ else if (!gnu_interface_offset
+ && !Is_Variable_Size_Record (gnat_controlling_type))
+ {
+ fixed_offset = 0;
+ virtual_value = - 2 * (HOST_WIDE_INT) (POINTER_SIZE / BITS_PER_UNIT);
+ virtual_offset = build_int_cst (integer_type_node, virtual_value);
+ indirect_offset = 0;
+ }
+ else
+ {
+ /* Covariant thunks with variable offset are not supported. */
+ if (Has_Controlling_Result (gnat_target))
+ return false;
+
+ fixed_offset = 0;
+ virtual_value = 0;
+ virtual_offset = NULL_TREE;
+ indirect_offset = (HOST_WIDE_INT) (POINTER_SIZE / BITS_PER_UNIT);
+ }
+
+ tree gnu_target = gnat_to_gnu_entity (gnat_target, NULL_TREE, false);
+
+ /* Thunk and target must have the same nesting level, if any. */
+ gcc_assert (DECL_CONTEXT (gnu_thunk) == DECL_CONTEXT (gnu_target));
+
+ /* If the target returns by invisible reference and is external, apply the
+ same transformation as Subprogram_Body_to_gnu here. */
+ if (TREE_ADDRESSABLE (TREE_TYPE (gnu_target))
+ && DECL_EXTERNAL (gnu_target)
+ && !POINTER_TYPE_P (TREE_TYPE (DECL_RESULT (gnu_target))))
+ {
+ TREE_TYPE (DECL_RESULT (gnu_target))
+ = build_reference_type (TREE_TYPE (DECL_RESULT (gnu_target)));
+ relayout_decl (DECL_RESULT (gnu_target));
+ }
+
+ /* The thunk expander requires the return types of thunk and target to be
+ compatible, which is not fully the case with the CICO mechanism. */
+ if (TYPE_CI_CO_LIST (TREE_TYPE (gnu_thunk)))
+ {
+ tree gnu_target_type = TREE_TYPE (gnu_target);
+ gcc_assert (TYPE_CI_CO_LIST (gnu_target_type));
+ TYPE_CANONICAL (TREE_TYPE (TREE_TYPE (gnu_thunk)))
+ = TYPE_CANONICAL (TREE_TYPE (gnu_target_type));
+ }
+
+ cgraph_node *target_node = cgraph_node::get_create (gnu_target);
+
+ /* If the return type of the target is a controlling type, then we need
+ both an usual this thunk and a covariant thunk in this order:
+
+ this thunk --> covariant thunk --> target
+
+ For covariant thunks, we can only handle a fixed offset. */
+ if (Has_Controlling_Result (gnat_target))
+ {
+ gcc_assert (fixed_offset < 0);
+ tree gnu_cv_thunk = make_covariant_thunk (gnat_thunk, gnu_thunk);
+ target_node->create_thunk (gnu_cv_thunk, gnu_target, false,
+ - fixed_offset, 0, 0,
+ NULL_TREE, gnu_target);
+
+ gnu_target = gnu_cv_thunk;
+ }
+
+ /* We may also need to create an alias for the target in order to make
+ the call local, depending on the linkage of the target. */
+ tree gnu_alias = use_alias_for_thunk_p (gnu_target)
+ ? make_alias_for_thunk (gnu_target)
+ : gnu_target;
+
+ target_node->create_thunk (gnu_thunk, gnu_target, true,
+ fixed_offset, virtual_value, indirect_offset,
+ virtual_offset, gnu_alias);
+
+ return true;
+}
+
/* Initialize the table that maps GNAT codes to GCC codes for simple
binary and unary operations. */
Index: ada/gcc-interface/utils.c
===================================================================
--- ada/gcc-interface/utils.c (revision 264525)
+++ ada/gcc-interface/utils.c (working copy)
@@ -3293,6 +3293,7 @@ finish_subprog_decl (tree decl, tree asm
DECL_ARTIFICIAL (result_decl) = 1;
DECL_IGNORED_P (result_decl) = 1;
+ DECL_CONTEXT (result_decl) = decl;
DECL_BY_REFERENCE (result_decl) = TREE_ADDRESSABLE (type);
DECL_RESULT (decl) = result_decl;
@@ -3368,9 +3369,6 @@ end_subprog_body (tree body)
DECL_INITIAL (fndecl) = current_binding_level->block;
gnat_poplevel ();
- /* Mark the RESULT_DECL as being in this subprogram. */
- DECL_CONTEXT (DECL_RESULT (fndecl)) = fndecl;
-
/* The body should be a BIND_EXPR whose BLOCK is the top-level one. */
if (TREE_CODE (body) == BIND_EXPR)
{
Index: ada/sem_res.adb
===================================================================
--- ada/sem_res.adb (revision 264525)
+++ ada/sem_res.adb (working copy)
@@ -3809,6 +3809,7 @@ package body Sem_Res is
if Ekind (F) /= E_In_Parameter
and then Nkind (A) = N_Type_Conversion
and then not Is_Class_Wide_Type (Etype (Expression (A)))
+ and then not Is_Interface (Etype (A))
then
if Ekind (F) = E_In_Out_Parameter
and then Is_Array_Type (Etype (F))
Index: ada/sem_util.adb
===================================================================
--- ada/sem_util.adb (revision 264525)
+++ ada/sem_util.adb (working copy)
@@ -17714,9 +17714,9 @@ package body Sem_Util is
begin
pragma Assert (Is_Record_Type (E));
- Comp := First_Entity (E);
+ Comp := First_Component (E);
while Present (Comp) loop
- Comp_Typ := Etype (Comp);
+ Comp_Typ := Underlying_Type (Etype (Comp));
-- Recursive call if the record type has discriminants
@@ -17732,7 +17732,7 @@ package body Sem_Util is
return True;
end if;
- Next_Entity (Comp);
+ Next_Component (Comp);
end loop;
return False;
Index: calls.c
===================================================================
--- calls.c (revision 264525)
+++ calls.c (working copy)
@@ -3610,9 +3610,8 @@ expand_call (tree exp, rtx target, int i
pushed these optimizations into -O2. Don't try if we're already
expanding a call, as that means we're an argument. Don't try if
there's cleanups, as we know there's code to follow the call. */
-
if (currently_expanding_call++ != 0
- || !flag_optimize_sibling_calls
+ || (!flag_optimize_sibling_calls && !CALL_FROM_THUNK_P (exp))
|| args_size.var
|| dbg_cnt (tail_call) == false)
try_tail_call = 0;
Index: cgraph.c
===================================================================
--- cgraph.c (revision 264525)
+++ cgraph.c (working copy)
@@ -617,6 +617,7 @@ cgraph_node *
cgraph_node::create_thunk (tree alias, tree, bool this_adjusting,
HOST_WIDE_INT fixed_offset,
HOST_WIDE_INT virtual_value,
+ HOST_WIDE_INT indirect_offset,
tree virtual_offset,
tree real_alias)
{
@@ -635,6 +636,7 @@ cgraph_node::create_thunk (tree alias, t
node->thunk.fixed_offset = fixed_offset;
node->thunk.virtual_value = virtual_value;
+ node->thunk.indirect_offset = indirect_offset;
node->thunk.alias = real_alias;
node->thunk.this_adjusting = this_adjusting;
node->thunk.virtual_offset_p = virtual_offset != NULL;
@@ -2099,10 +2101,11 @@ cgraph_node::dump (FILE *f)
fprintf (f, " of %s (asm: %s)",
lang_hooks.decl_printable_name (thunk.alias, 2),
IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk.alias)));
- fprintf (f, " fixed offset %i virtual value %i has "
- "virtual offset %i)\n",
+ fprintf (f, " fixed offset %i virtual value %i indirect_offset %i "
+ "has virtual offset %i\n",
(int)thunk.fixed_offset,
(int)thunk.virtual_value,
+ (int)thunk.indirect_offset,
(int)thunk.virtual_offset_p);
}
if (alias && thunk.alias
Index: cgraph.h
===================================================================
--- cgraph.h (revision 264525)
+++ cgraph.h (working copy)
@@ -666,6 +666,10 @@ struct GTY(()) cgraph_thunk_info {
VIRTUAL_OFFSET_P is true. */
HOST_WIDE_INT virtual_value;
+ /* Offset from "this" to get the offset to adjust "this". Zero means: this
+ offset is to be ignored. */
+ HOST_WIDE_INT indirect_offset;
+
/* Thunk target, i.e. the method that this thunk wraps. Depending on the
TARGET_USE_LOCAL_THUNK_ALIAS_P macro, this may have to be a new alias. */
tree alias;
@@ -1033,6 +1037,7 @@ public:
cgraph_node * create_thunk (tree alias, tree, bool this_adjusting,
HOST_WIDE_INT fixed_offset,
HOST_WIDE_INT virtual_value,
+ HOST_WIDE_INT indirect_offset,
tree virtual_offset,
tree real_alias);
@@ -2373,7 +2378,8 @@ void cgraphunit_c_finalize (void);
IN_SSA is true if the gimple is in SSA. */
basic_block init_lowered_empty_function (tree, bool, profile_count);
-tree thunk_adjust (gimple_stmt_iterator *, tree, bool, HOST_WIDE_INT, tree);
+tree thunk_adjust (gimple_stmt_iterator *, tree, bool, HOST_WIDE_INT, tree,
+ HOST_WIDE_INT);
/* In cgraphclones.c */
tree clone_function_name_1 (const char *, const char *);
Index: cgraphunit.c
===================================================================
--- cgraphunit.c (revision 264525)
+++ cgraphunit.c (working copy)
@@ -623,20 +623,18 @@ cgraph_node::analyze (void)
callees->can_throw_external = !TREE_NOTHROW (t->decl);
/* Target code in expand_thunk may need the thunk's target
to be analyzed, so recurse here. */
- if (!t->analyzed)
+ if (!t->analyzed && t->definition)
t->analyze ();
if (t->alias)
{
t = t->get_alias_target ();
- if (!t->analyzed)
+ if (!t->analyzed && t->definition)
t->analyze ();
}
- if (!expand_thunk (false, false))
- {
- thunk.alias = NULL;
- return;
- }
+ bool ret = expand_thunk (false, false);
thunk.alias = NULL;
+ if (!ret)
+ return;
}
if (alias)
resolve_alias (cgraph_node::get (alias_target), transparent_alias);
@@ -1609,15 +1607,16 @@ init_lowered_empty_function (tree decl,
return bb;
}
-/* Adjust PTR by the constant FIXED_OFFSET, and by the vtable
- offset indicated by VIRTUAL_OFFSET, if that is
- non-null. THIS_ADJUSTING is nonzero for a this adjusting thunk and
- zero for a result adjusting thunk. */
+/* Adjust PTR by the constant FIXED_OFFSET, by the vtable offset indicated by
+ VIRTUAL_OFFSET, and by the indirect offset indicated by INDIRECT_OFFSET, if
+ it is non-null. THIS_ADJUSTING is nonzero for a this adjusting thunk and zero
+ for a result adjusting thunk. */
tree
thunk_adjust (gimple_stmt_iterator * bsi,
tree ptr, bool this_adjusting,
- HOST_WIDE_INT fixed_offset, tree virtual_offset)
+ HOST_WIDE_INT fixed_offset, tree virtual_offset,
+ HOST_WIDE_INT indirect_offset)
{
gassign *stmt;
tree ret;
@@ -1632,6 +1631,16 @@ thunk_adjust (gimple_stmt_iterator * bsi
gsi_insert_after (bsi, stmt, GSI_NEW_STMT);
}
+ if (!vtable_entry_type && (virtual_offset || indirect_offset != 0))
+ {
+ tree vfunc_type = make_node (FUNCTION_TYPE);
+ TREE_TYPE (vfunc_type) = integer_type_node;
+ TYPE_ARG_TYPES (vfunc_type) = NULL_TREE;
+ layout_type (vfunc_type);
+
+ vtable_entry_type = build_pointer_type (vfunc_type);
+ }
+
/* If there's a virtual offset, look up that value in the vtable and
adjust the pointer again. */
if (virtual_offset)
@@ -1640,16 +1649,6 @@ thunk_adjust (gimple_stmt_iterator * bsi
tree vtabletmp2;
tree vtabletmp3;
- if (!vtable_entry_type)
- {
- tree vfunc_type = make_node (FUNCTION_TYPE);
- TREE_TYPE (vfunc_type) = integer_type_node;
- TYPE_ARG_TYPES (vfunc_type) = NULL_TREE;
- layout_type (vfunc_type);
-
- vtable_entry_type = build_pointer_type (vfunc_type);
- }
-
vtabletmp =
create_tmp_reg (build_pointer_type
(build_pointer_type (vtable_entry_type)), "vptr");
@@ -1687,6 +1686,41 @@ thunk_adjust (gimple_stmt_iterator * bsi
GSI_CONTINUE_LINKING);
}
+ /* Likewise for an offset that is stored in the object that contains the
+ vtable. */
+ if (indirect_offset != 0)
+ {
+ tree offset_ptr, offset_tree;
+
+ /* Get the address of the offset. */
+ offset_ptr
+ = create_tmp_reg (build_pointer_type
+ (build_pointer_type (vtable_entry_type)),
+ "offset_ptr");
+ stmt = gimple_build_assign (offset_ptr,
+ build1 (NOP_EXPR, TREE_TYPE (offset_ptr),
+ ptr));
+ gsi_insert_after (bsi, stmt, GSI_NEW_STMT);
+
+ stmt = gimple_build_assign
+ (offset_ptr,
+ fold_build_pointer_plus_hwi_loc (input_location, offset_ptr,
+ indirect_offset));
+ gsi_insert_after (bsi, stmt, GSI_NEW_STMT);
+
+ /* Get the offset itself. */
+ offset_tree = create_tmp_reg (TREE_TYPE (TREE_TYPE (offset_ptr)),
+ "offset");
+ stmt = gimple_build_assign (offset_tree,
+ build_simple_mem_ref (offset_ptr));
+ gsi_insert_after (bsi, stmt, GSI_NEW_STMT);
+
+ /* Adjust the `this' pointer. */
+ ptr = fold_build_pointer_plus_loc (input_location, ptr, offset_tree);
+ ptr = force_gimple_operand_gsi (bsi, ptr, true, NULL_TREE, false,
+ GSI_CONTINUE_LINKING);
+ }
+
if (!this_adjusting
&& fixed_offset != 0)
/* Adjust the pointer by the constant. */
@@ -1725,6 +1759,7 @@ cgraph_node::expand_thunk (bool output_a
bool this_adjusting = thunk.this_adjusting;
HOST_WIDE_INT fixed_offset = thunk.fixed_offset;
HOST_WIDE_INT virtual_value = thunk.virtual_value;
+ HOST_WIDE_INT indirect_offset = thunk.indirect_offset;
tree virtual_offset = NULL;
tree alias = callees->callee->decl;
tree thunk_fndecl = decl;
@@ -1735,7 +1770,11 @@ cgraph_node::expand_thunk (bool output_a
if (thunk.add_pointer_bounds_args)
return false;
- if (!force_gimple_thunk && this_adjusting
+ if (!force_gimple_thunk
+ && this_adjusting
+ && indirect_offset == 0
+ && !DECL_EXTERNAL (alias)
+ && !DECL_STATIC_CHAIN (alias)
&& targetm.asm_out.can_output_mi_thunk (thunk_fndecl, fixed_offset,
virtual_value, alias))
{
@@ -1838,8 +1877,8 @@ cgraph_node::expand_thunk (bool output_a
resdecl = build_decl (input_location, RESULT_DECL, 0, restype);
DECL_ARTIFICIAL (resdecl) = 1;
DECL_IGNORED_P (resdecl) = 1;
+ DECL_CONTEXT (resdecl) = thunk_fndecl;
DECL_RESULT (thunk_fndecl) = resdecl;
- DECL_CONTEXT (DECL_RESULT (thunk_fndecl)) = thunk_fndecl;
}
else
resdecl = DECL_RESULT (thunk_fndecl);
@@ -1876,8 +1915,11 @@ cgraph_node::expand_thunk (bool output_a
restmp = resdecl;
if (VAR_P (restmp))
- add_local_decl (cfun, restmp);
- BLOCK_VARS (DECL_INITIAL (current_function_decl)) = restmp;
+ {
+ add_local_decl (cfun, restmp);
+ BLOCK_VARS (DECL_INITIAL (current_function_decl))
+ = restmp;
+ }
}
else
restmp = create_tmp_var (restype, "retval");
@@ -1894,7 +1936,7 @@ cgraph_node::expand_thunk (bool output_a
if (this_adjusting)
{
vargs.quick_push (thunk_adjust (&bsi, a, 1, fixed_offset,
- virtual_offset));
+ virtual_offset, indirect_offset));
arg = DECL_CHAIN (a);
i = 1;
}
@@ -1919,6 +1961,25 @@ cgraph_node::expand_thunk (bool output_a
call = gimple_build_call_vec (build_fold_addr_expr_loc (0, alias), vargs);
callees->call_stmt = call;
gimple_call_set_from_thunk (call, true);
+ if (DECL_STATIC_CHAIN (alias))
+ {
+ tree p = DECL_STRUCT_FUNCTION (alias)->static_chain_decl;
+ tree type = TREE_TYPE (p);
+ tree decl = build_decl (DECL_SOURCE_LOCATION (thunk_fndecl),
+ PARM_DECL, create_tmp_var_name ("CHAIN"),
+ type);
+ DECL_ARTIFICIAL (decl) = 1;
+ DECL_IGNORED_P (decl) = 1;
+ TREE_USED (decl) = 1;
+ DECL_CONTEXT (decl) = thunk_fndecl;
+ DECL_ARG_TYPE (decl) = type;
+ TREE_READONLY (decl) = 1;
+
+ struct function *sf = DECL_STRUCT_FUNCTION (thunk_fndecl);
+ sf->static_chain_decl = decl;
+
+ gimple_call_set_chain (call, decl);
+ }
/* Return slot optimization is always possible and in fact requred to
return values with DECL_BY_REFERENCE. */
@@ -1979,7 +2040,8 @@ cgraph_node::expand_thunk (bool output_a
}
restmp = thunk_adjust (&bsi, restmp, /*this_adjusting=*/0,
- fixed_offset, virtual_offset);
+ fixed_offset, virtual_offset,
+ indirect_offset);
if (true_label)
{
gimple *stmt;
Index: cp/method.c
===================================================================
--- cp/method.c (revision 264525)
+++ cp/method.c (working copy)
@@ -375,7 +375,7 @@ use_thunk (tree thunk_fndecl, bool emit_
gcc_checking_assert (funcn);
thunk_node = funcn->create_thunk (thunk_fndecl, function,
this_adjusting, fixed_offset, virtual_value,
- virtual_offset, alias);
+ 0, virtual_offset, alias);
if (DECL_ONE_ONLY (function))
thunk_node->add_to_same_comdat_group (funcn);
Index: lto-cgraph.c
===================================================================
--- lto-cgraph.c (revision 264525)
+++ lto-cgraph.c (working copy)
@@ -556,6 +556,7 @@ lto_output_node (struct lto_simple_outpu
+ (node->thunk.add_pointer_bounds_args != 0) * 8);
streamer_write_uhwi_stream (ob->main_stream, node->thunk.fixed_offset);
streamer_write_uhwi_stream (ob->main_stream, node->thunk.virtual_value);
+ streamer_write_uhwi_stream (ob->main_stream, node->thunk.indirect_offset);
}
streamer_write_hwi_stream (ob->main_stream, node->profile_id);
if (DECL_STATIC_CONSTRUCTOR (node->decl))
@@ -1271,10 +1272,12 @@ input_node (struct lto_file_decl_data *f
int type = streamer_read_uhwi (ib);
HOST_WIDE_INT fixed_offset = streamer_read_uhwi (ib);
HOST_WIDE_INT virtual_value = streamer_read_uhwi (ib);
+ HOST_WIDE_INT indirect_offset = streamer_read_uhwi (ib);
node->thunk.fixed_offset = fixed_offset;
- node->thunk.this_adjusting = (type & 2);
node->thunk.virtual_value = virtual_value;
+ node->thunk.indirect_offset = indirect_offset;
+ node->thunk.this_adjusting = (type & 2);
node->thunk.virtual_offset_p = (type & 4);
node->thunk.add_pointer_bounds_args = (type & 8);
}
Index: tree-inline.c
===================================================================
--- tree-inline.c (revision 264525)
+++ tree-inline.c (working copy)
@@ -4473,7 +4473,7 @@ expand_call_inline (basic_block bb, gimp
GSI_NEW_STMT);
gcc_assert (id->src_node->thunk.this_adjusting);
op = thunk_adjust (&iter, op, 1, id->src_node->thunk.fixed_offset,
- virtual_offset);
+ virtual_offset, id->src_node->thunk.indirect_offset);
gimple_call_set_arg (stmt, 0, op);
gimple_call_set_fndecl (stmt, edge->callee->decl);
Index: tree-nested.c
===================================================================
--- tree-nested.c (revision 264525)
+++ tree-nested.c (working copy)
@@ -104,6 +104,7 @@ struct nesting_info
tree chain_decl;
tree nl_goto_field;
+ bool thunk_p;
bool any_parm_remapped;
bool any_tramp_created;
bool any_descr_created;
@@ -834,6 +835,7 @@ create_nesting_tree (struct cgraph_node
info->mem_refs = new hash_set<tree *>;
info->suppress_expansion = BITMAP_ALLOC (&nesting_info_bitmap_obstack);
info->context = cgn->decl;
+ info->thunk_p = cgn->thunk.thunk_p;
for (cgn = cgn->nested; cgn ; cgn = cgn->next_nested)
{
@@ -2786,6 +2788,8 @@ convert_all_function_calls (struct nesti
within the debugger. */
FOR_EACH_NEST_INFO (n, root)
{
+ if (n->thunk_p)
+ continue;
tree decl = n->context;
if (!optimize)
{
@@ -2806,6 +2810,14 @@ convert_all_function_calls (struct nesti
chain_count += DECL_STATIC_CHAIN (decl);
}
+ FOR_EACH_NEST_INFO (n, root)
+ if (n->thunk_p)
+ {
+ tree decl = n->context;
+ tree alias = cgraph_node::get (decl)->thunk.alias;
+ DECL_STATIC_CHAIN (decl) = DECL_STATIC_CHAIN (alias);
+ }
+
/* Walk the functions and perform transformations. Note that these
transformations can induce new uses of the static chain, which in turn
require re-examining all users of the decl. */
@@ -2825,12 +2837,22 @@ convert_all_function_calls (struct nesti
FOR_EACH_NEST_INFO (n, root)
{
+ if (n->thunk_p)
+ continue;
tree decl = n->context;
walk_function (convert_tramp_reference_stmt,
convert_tramp_reference_op, n);
walk_function (convert_gimple_call, NULL, n);
chain_count += DECL_STATIC_CHAIN (decl);
}
+
+ FOR_EACH_NEST_INFO (n, root)
+ if (n->thunk_p)
+ {
+ tree decl = n->context;
+ tree alias = cgraph_node::get (decl)->thunk.alias;
+ DECL_STATIC_CHAIN (decl) = DECL_STATIC_CHAIN (alias);
+ }
}
while (chain_count != old_chain_count);
@@ -3055,12 +3077,13 @@ build_init_call_stmt (struct nesting_inf
static void
finalize_nesting_tree_1 (struct nesting_info *root)
{
- gimple_seq stmt_list;
+ gimple_seq stmt_list = NULL;
gimple *stmt;
tree context = root->context;
struct function *sf;
- stmt_list = NULL;
+ if (root->thunk_p)
+ return;
/* If we created a non-local frame type or decl, we need to lay them
out at this time. */
@@ -3340,7 +3363,8 @@ unnest_nesting_tree_1 (struct nesting_in
if (node->origin)
{
node->unnest ();
- cgraph_node::finalize_function (root->context, true);
+ if (!root->thunk_p)
+ cgraph_node::finalize_function (root->context, true);
}
}
@@ -3380,7 +3404,8 @@ gimplify_all_functions (struct cgraph_no
if (!gimple_body (root->decl))
gimplify_function_tree (root->decl);
for (iter = root->nested; iter; iter = iter->next_nested)
- gimplify_all_functions (iter);
+ if (!iter->thunk.thunk_p)
+ gimplify_all_functions (iter);
}
/* Main entry point for this pass. Process FNDECL and all of its nested