This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

PATCH: Avoid cleanup functions for C++ objects in static storage


This patch makes us generate smaller code for cleaning up C++ objects
with static storage duration.  

These functions must be destroyed when the program terminates.  In the
old C++ ABI, we called atexit to register these functions, or called
them from .dtors.  In either case, we had no way to pass a parameter
to the function.  So, we generated a cleanup function per object to be
cleaned up; that function then called the appropriate destructor,
passing it the address of the object as the "this" pointer.

The new ABI gave us __cxa_atexit, which provides a more efficient
alternative.  In particular, because __cxa_atexit allows us to
register both a function and a pointer argument to the function, we
can register the destructor and the "this" pointer directly.  That
avoids generating any additional functions, which makes the program
smaller.  (I suppose it may also make it marginally faster, in that
there's less code to load, and we avoid a bit of indirection when the
program exits, but the primary impact is space.)

I don't know how much of an impact this makes on most programs, but it
was low-hanging fruit, so ...

Tested on x86_64-unknown-linux-gnu, applied on the mainline.

--
Mark Mitchell
CodeSourcery
mark@codesourcery.com
(650) 331-3385 x713

2007-05-31  Mark Mitchell  <mark@codesourcery.com>

	* decl.c (get_atexit_fn_ptr_type): New function.
	(get_atexit_node): Use it.
	(start_cleanup_fn): Likewise.
	(register_dtor_fn): Use the object's destructor, instead of a
	separate cleanup function, where possible.
	* cp-tree.h (CPTI_ATEXIT_FN_PTR_TYPE): New enumerator.
	(atexit_fn_ptr_type_node): New macro.
	* decl2.c (build_cleanup): Use build_address.

2007-05-31  Mark Mitchell  <mark@codesourcery.com>

	* g++.dg/init/cleanup3.C: New test.

Index: gcc/cp/decl.c
===================================================================
--- gcc/cp/decl.c	(revision 125150)
+++ gcc/cp/decl.c	(working copy)
@@ -5413,6 +5413,33 @@ declare_global_var (tree name, tree type
   return decl;
 }
 
+/* Returns the type for the argument to "__cxa_atexit" (or "atexit",
+   if "__cxa_atexit" is not being used) corresponding to the function
+   to be called when the program exits.  */
+
+static tree
+get_atexit_fn_ptr_type (void)
+{
+  tree arg_types;
+  tree fn_type;
+
+  if (!atexit_fn_ptr_type_node)
+    {
+      if (flag_use_cxa_atexit 
+	  && !targetm.cxx.use_atexit_for_cxa_atexit ())
+	/* The parameter to "__cxa_atexit" is "void (*)(void *)".  */
+	arg_types = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
+      else
+	/* The parameter to "atexit" is "void (*)(void)".  */
+	arg_types = void_list_node;
+      
+      fn_type = build_function_type (void_type_node, arg_types);
+      atexit_fn_ptr_type_node = build_pointer_type (fn_type);
+    }
+
+  return atexit_fn_ptr_type_node;
+}
+
 /* Returns a pointer to the `atexit' function.  Note that if
    FLAG_USE_CXA_ATEXIT is nonzero, then this will actually be the new
    `__cxa_atexit' function specified in the IA64 C++ ABI.  */
@@ -5442,9 +5469,7 @@ get_atexit_node (void)
       use_aeabi_atexit = targetm.cxx.use_aeabi_atexit ();
       /* First, build the pointer-to-function type for the first
 	 argument.  */
-      arg_types = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
-      fn_type = build_function_type (void_type_node, arg_types);
-      fn_ptr_type = build_pointer_type (fn_type);
+      fn_ptr_type = get_atexit_fn_ptr_type ();
       /* Then, build the rest of the argument types.  */
       arg_types = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
       if (use_aeabi_atexit)
@@ -5473,8 +5498,7 @@ get_atexit_node (void)
 
 	 We build up the argument types and then then function type
 	 itself.  */
-      fn_type = build_function_type (void_type_node, void_list_node);
-      fn_ptr_type = build_pointer_type (fn_type);
+      fn_ptr_type = get_atexit_fn_ptr_type ();
       arg_types = tree_cons (NULL_TREE, fn_ptr_type, void_list_node);
       /* Build the final atexit type.  */
       fn_type = build_function_type (integer_type_node, arg_types);
@@ -5515,7 +5539,6 @@ static tree
 start_cleanup_fn (void)
 {
   char name[32];
-  tree parmtypes;
   tree fntype;
   tree fndecl;
   bool use_cxa_atexit = flag_use_cxa_atexit
@@ -5526,19 +5549,10 @@ start_cleanup_fn (void)
   /* No need to mangle this.  */
   push_lang_context (lang_name_c);
 
-  /* Build the parameter-types.  */
-  parmtypes = void_list_node;
-  /* Functions passed to __cxa_atexit take an additional parameter.
-     We'll just ignore it.  After we implement the new calling
-     convention for destructors, we can eliminate the use of
-     additional cleanup functions entirely in the -fnew-abi case.  */
-  if (use_cxa_atexit)
-    parmtypes = tree_cons (NULL_TREE, ptr_type_node, parmtypes);
-  /* Build the function type itself.  */
-  fntype = build_function_type (void_type_node, parmtypes);
   /* Build the name of the function.  */
   sprintf (name, "__tcf_%d", start_cleanup_cnt++);
   /* Build the function declaration.  */
+  fntype = TREE_TYPE (get_atexit_fn_ptr_type ());
   fndecl = build_lang_decl (FUNCTION_DECL, get_identifier (name), fntype);
   /* It's a function with internal linkage, generated by the
      compiler.  */
@@ -5590,50 +5604,96 @@ register_dtor_fn (tree decl)
   tree compound_stmt;
   tree args;
   tree fcall;
+  tree type;
+  bool use_dtor;
 
-  if (TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl)))
+  type = TREE_TYPE (decl);
+  if (TYPE_HAS_TRIVIAL_DESTRUCTOR (type))
     return void_zero_node;
 
-  /* Call build_cleanup before we enter the anonymous function so that
-     any access checks will be done relative to the current scope,
-     rather than the scope of the anonymous function.  */
-  build_cleanup (decl);
-
-  /* Now start the function.  */
-  cleanup = start_cleanup_fn ();
-
-  /* Now, recompute the cleanup.  It may contain SAVE_EXPRs that refer
-     to the original function, rather than the anonymous one.  That
-     will make the back end think that nested functions are in use,
-     which causes confusion.  */
-
-  push_deferring_access_checks (dk_no_check);
-  fcall = build_cleanup (decl);
-  pop_deferring_access_checks ();
-
-  /* Create the body of the anonymous function.  */
-  compound_stmt = begin_compound_stmt (BCS_FN_BODY);
-  finish_expr_stmt (fcall);
-  finish_compound_stmt (compound_stmt);
-  end_cleanup_fn ();
+  /* If we're using "__cxa_atexit" (or "__aeabi_atexit"), and DECL is
+     a class object, we can just pass the destructor to
+     "__cxa_atexit"; we don't have to build a temporary function to do
+     the cleanup.  */
+  use_dtor = (flag_use_cxa_atexit 
+	      && !targetm.cxx.use_atexit_for_cxa_atexit ()
+	      && CLASS_TYPE_P (type));
+  if (use_dtor)
+    {
+      int idx;
+
+      /* Find the destructor.  */
+      idx = lookup_fnfields_1 (type, complete_dtor_identifier);
+      gcc_assert (idx >= 0);
+      cleanup = VEC_index (tree, CLASSTYPE_METHOD_VEC (type), idx);
+      /* Make sure it is accessible.  */
+      perform_or_defer_access_check (TYPE_BINFO (type), cleanup, cleanup);
+    }
+  else
+    {
+      /* Call build_cleanup before we enter the anonymous function so
+	 that any access checks will be done relative to the current
+	 scope, rather than the scope of the anonymous function.  */
+      build_cleanup (decl);
+  
+      /* Now start the function.  */
+      cleanup = start_cleanup_fn ();
+      
+      /* Now, recompute the cleanup.  It may contain SAVE_EXPRs that refer
+	 to the original function, rather than the anonymous one.  That
+	 will make the back end think that nested functions are in use,
+	 which causes confusion.  */
+      push_deferring_access_checks (dk_no_check);
+      fcall = build_cleanup (decl);
+      pop_deferring_access_checks ();
+      
+      /* Create the body of the anonymous function.  */
+      compound_stmt = begin_compound_stmt (BCS_FN_BODY);
+      finish_expr_stmt (fcall);
+      finish_compound_stmt (compound_stmt);
+      end_cleanup_fn ();
+    }
 
   /* Call atexit with the cleanup function.  */
-  cxx_mark_addressable (cleanup);
   mark_used (cleanup);
-  cleanup = build_unary_op (ADDR_EXPR, cleanup, 0);
+  cleanup = build_address (cleanup);
   if (flag_use_cxa_atexit && !targetm.cxx.use_atexit_for_cxa_atexit ())
     {
+      tree addr;
+
+      if (use_dtor)
+	{
+	  /* We must convert CLEANUP to the type that "__cxa_atexit"
+	     expects.  */
+	  cleanup = build_nop (get_atexit_fn_ptr_type (), cleanup);
+	  /* "__cxa_atexit" will pass the address of DECL to the
+	     cleanup function.  */
+	  mark_used (decl);
+	  addr = build_address (decl);
+	  /* The declared type of the parameter to "__cxa_atexit" is
+	     "void *".  For plain "T*", we could just let the
+	     machinery in build_function_call convert it -- but if the
+	     type is "cv-qualified T *", then we need to convert it
+	     before passing it in, to avoid spurious errors.  */
+	  addr = build_nop (ptr_type_node, addr);
+	}
+      else
+	/* Since the cleanup functions we build ignore the address
+	   they're given, there's no reason to pass the actual address
+	   in, and, in general, it's cheaper to pass NULL than any
+	   other value.  */
+	addr = null_pointer_node;
       args = tree_cons (NULL_TREE,
 			build_unary_op (ADDR_EXPR, get_dso_handle_node (), 0),
 			NULL_TREE);
       if (targetm.cxx.use_aeabi_atexit ())
 	{
 	  args = tree_cons (NULL_TREE, cleanup, args);
-	  args = tree_cons (NULL_TREE, null_pointer_node, args);
+	  args = tree_cons (NULL_TREE, addr, args);
 	}
       else
 	{
-	  args = tree_cons (NULL_TREE, null_pointer_node, args);
+	  args = tree_cons (NULL_TREE, addr, args);
 	  args = tree_cons (NULL_TREE, cleanup, args);
 	}
     }
Index: gcc/cp/cp-tree.h
===================================================================
--- gcc/cp/cp-tree.h	(revision 125150)
+++ gcc/cp/cp-tree.h	(working copy)
@@ -614,6 +614,7 @@ enum cp_tree_index
     CPTI_JCLASS,
     CPTI_TERMINATE,
     CPTI_CALL_UNEXPECTED,
+    CPTI_ATEXIT_FN_PTR_TYPE,
     CPTI_ATEXIT,
     CPTI_DSO_HANDLE,
     CPTI_DCAST,
@@ -703,6 +704,10 @@ extern GTY(()) tree cp_global_trees[CPTI
 /* The declaration for "__cxa_call_unexpected".  */
 #define call_unexpected_node		cp_global_trees[CPTI_CALL_UNEXPECTED]
 
+/* The type of the function-pointer argument to "__cxa_atexit" (or
+   "std::atexit", if "__cxa_atexit" is not being used).  */
+#define atexit_fn_ptr_type_node         cp_global_trees[CPTI_ATEXIT_FN_PTR_TYPE]
+
 /* A pointer to `std::atexit'.  */
 #define atexit_node			cp_global_trees[CPTI_ATEXIT]
 
Index: gcc/cp/decl2.c
===================================================================
--- gcc/cp/decl2.c	(revision 125150)
+++ gcc/cp/decl2.c	(working copy)
@@ -2190,10 +2190,7 @@ build_cleanup (tree decl)
   if (TREE_CODE (type) == ARRAY_TYPE)
     temp = decl;
   else
-    {
-      cxx_mark_addressable (decl);
-      temp = build1 (ADDR_EXPR, build_pointer_type (type), decl);
-    }
+    temp = build_address (decl);
   temp = build_delete (TREE_TYPE (temp), temp,
 		       sfk_complete_destructor,
 		       LOOKUP_NORMAL|LOOKUP_NONVIRTUAL|LOOKUP_DESTRUCTOR, 0);
Index: gcc/testsuite/g++.dg/init/cleanup3.C
===================================================================
--- gcc/testsuite/g++.dg/init/cleanup3.C	(revision 0)
+++ gcc/testsuite/g++.dg/init/cleanup3.C	(revision 0)
@@ -0,0 +1,22 @@
+// Check that on targets with "__cxa_atexit" we use destructors,
+// rather than cleanup functions, to destroy objects with static
+// storage duration.
+
+// { dg-require-effective-target "cxa_atexit" }
+// Cleanup functions generated by G++ have the "_tcf" prefix.
+// { dg-final { scan-assembler-not "_tcf" } }
+
+struct S { 
+  ~S();
+};
+
+struct T { 
+  S s;
+};
+
+S s;
+T t;
+
+void f() {
+  static S s;
+}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]