This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
[C++ PATCH] Implement new "force_static" attribute
- From: David Malcolm <dmalcolm at redhat dot com>
- To: gcc-patches at gcc dot gnu dot org
- Date: Mon, 01 Jul 2013 11:36:58 -0400
- Subject: [C++ PATCH] Implement new "force_static" attribute
My plan for removal of global variables in gcc 4.9 [1] calls for several
hundred new classes, which will be singletons in a classic monolithic
build, but have multiple instances in a shared-library build.
In order to avoid the register pressure of passing a redundant "this"
pointer around for the classic case, I've been looking at optimizing
singletons.
I'm attaching an optimization for this: a new "force_static" attribute
for the C++ frontend, which when added to a class implicitly adds
"static" to all members of said class. This gives a way of avoiding a
"this" pointer in the classic build (in stages 2 and 3, once the
attribute is recognized), whilst supporting it in a shared-library
build, with relatively little boilerplate, preprocessor hackery or
syntactic differences.
See:
http://dmalcolm.fedorapeople.org/gcc/global-state/singletons.html#another-singleton-removal-optimization
for more information on how this would be used in GCC itself.
With this optimization, the generated machine code *with classes* (with
"methods" and "fields") is identical to that with just functions and
global variables (apart from the ordering of the functions/"methods"
within the .text sections of their respective .o files). [2]
FWIW I've also been looking at another approach:
http://dmalcolm.fedorapeople.org/gcc/global-state/singletons.html#a-singleton-removal-optimization
which is even lower boilerplate, though I don't have that working yet;
it touches the internals of classes and methods much more deeply.
BTW, I'm not 100% sold on "force_static" as the name of the attribute;
would "implicit_static" be a better name? (the latter is growing on me).
Successfully bootstrapped on x86_64-unknown-linux-gnu; all old testcases
have the same results as an unpatched build, and all new testcases pass
(using r200562 as the baseline).
Dave
[1] See http://gcc.gnu.org/ml/gcc/2013-06/msg00215.html
[2] I've written an "asmdiff" tool to help check this:
https://github.com/davidmalcolm/asmdiff
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 2931ac5..17cf35e 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1416,6 +1416,7 @@ struct GTY(()) lang_type_class {
unsigned has_complex_move_assign : 1;
unsigned has_constexpr_ctor : 1;
unsigned is_final : 1;
+ unsigned force_static : 1;
/* When adding a flag here, consider whether or not it ought to
apply to a template instance if it applies to the template. If
@@ -1424,7 +1425,7 @@ struct GTY(()) lang_type_class {
/* There are some bits left to fill out a 32-bit word. Keep track
of this by updating the size of this bitfield whenever you add or
remove a flag. */
- unsigned dummy : 2;
+ unsigned dummy : 1;
tree primary_base;
vec<tree_pair_s, va_gc> *vcall_indices;
@@ -1536,6 +1537,10 @@ struct GTY((variable_size)) lang_type {
#define CLASSTYPE_FINAL(NODE) \
(LANG_TYPE_CLASS_CHECK (NODE)->is_final)
+/* Nonzero means that NODE (a class type) has the force_static
+ attribute. */
+#define CLASSTYPE_FORCE_STATIC(NODE) \
+ (LANG_TYPE_CLASS_CHECK (NODE)->force_static)
/* Nonzero means that this _CLASSTYPE node overloads operator=(X&). */
#define TYPE_HAS_COPY_ASSIGN(NODE) (LANG_TYPE_CLASS_CHECK (NODE)->has_copy_assign)
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 9eb1d12..d12b2a8 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -7575,10 +7575,24 @@ grokfndecl (tree ctype,
{
if (quals)
{
- error (ctype
- ? G_("static member function %qD cannot have cv-qualifier")
- : G_("non-member function %qD cannot have cv-qualifier"),
- decl);
+ if (ctype)
+ {
+ if (CLASS_TYPE_P (ctype) && CLASSTYPE_FORCE_STATIC (ctype))
+ /* It's useful to be able to mark methods of force_static
+ classes as "const" etc so that we can toggle the
+ presence of the "force_static" attribute without needing
+ to remove the const qualifiers.
+
+ Silently discard such markings. */
+ ;
+ else
+ error (G_("static member function %qD cannot have"
+ " cv-qualifier"),
+ decl);
+ }
+ else
+ error (G_("non-member function %qD cannot have cv-qualifier"),
+ decl);
quals = TYPE_UNQUALIFIED;
}
@@ -9243,7 +9257,9 @@ grokdeclarator (const cp_declarator *declarator,
explicitp = decl_spec_seq_has_spec_p (declspecs, ds_explicit);
storage_class = declspecs->storage_class;
- if (storage_class == sc_static)
+ if (storage_class == sc_static
+ || (current_class_type &&
+ CLASSTYPE_FORCE_STATIC (current_class_type)))
staticp = 1 + (decl_context == FIELD);
if (virtualp && staticp == 2)
diff --git a/gcc/cp/ptree.c b/gcc/cp/ptree.c
index f4ca003..f4e5cd0 100644
--- a/gcc/cp/ptree.c
+++ b/gcc/cp/ptree.c
@@ -165,6 +165,8 @@ cxx_print_type (FILE *file, tree node, int indent)
fprintf (file, " interface-only");
if (CLASSTYPE_INTERFACE_UNKNOWN (node))
fprintf (file, " interface-unknown");
+ if (CLASSTYPE_FORCE_STATIC (node))
+ fprintf (file, " force_static");
}
}
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 8524f6c..b5d5879 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -47,6 +47,7 @@ static tree handle_java_interface_attribute (tree *, tree, tree, int, bool *);
static tree handle_com_interface_attribute (tree *, tree, tree, int, bool *);
static tree handle_init_priority_attribute (tree *, tree, tree, int, bool *);
static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *);
+static tree handle_force_static_attribute (tree *, tree, tree, int, bool *);
/* If REF is an lvalue, returns the kind of lvalue that REF is.
Otherwise, returns clk_none. */
@@ -3157,6 +3158,8 @@ const struct attribute_spec cxx_attribute_table[] =
handle_init_priority_attribute, false },
{ "abi_tag", 1, -1, false, false, false,
handle_abi_tag_attribute, true },
+ { "force_static", 0, 0, false, true, false,
+ handle_force_static_attribute, false },
{ NULL, 0, 0, false, false, false, NULL, false }
};
@@ -3378,6 +3381,39 @@ handle_abi_tag_attribute (tree* node, tree name, tree args,
return NULL_TREE;
}
+/* Handle a "force_static" attribute; arguments as in
+ struct attribute_spec.handler. */
+static tree
+handle_force_static_attribute (tree* node,
+ tree name,
+ tree /*args*/,
+ int /*flags*/,
+ bool* /*no_add_attrs*/)
+{
+ if (!CLASS_TYPE_P (*node))
+ {
+ warning (OPT_Wattributes, "%qE attribute can only be applied "
+ "to class definitions", name);
+ return NULL_TREE;
+ }
+
+ /* The attribute affects how decls within the class body are grokked.
+ If the attribute is seen after the body of the class has been handled,
+ it's too late. */
+ if (TYPE_FIELDS (*node))
+ {
+ warning (OPT_Wattributes,
+ ("%qE attribute must be applied after the class keyword,"
+ " not after the closing brace"),
+ name);
+ return NULL_TREE;
+ }
+
+ CLASSTYPE_FORCE_STATIC (*node) = 1;
+
+ return NULL_TREE;
+}
+
/* Return a new PTRMEM_CST of the indicated TYPE. The MEMBER is the
thing pointed to by the constant. */
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 6ce26ef..6d33191 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -5539,6 +5539,46 @@ and caught in another, the class must have default visibility.
Otherwise the two shared objects are unable to use the same
typeinfo node and exception handling will break.
+@item force_static
+In C++, a class or struct can be marked with the ``force_static'' attribute
+to make it easier to implement singletons.
+
+This implicitly adds the ``static'' keyword to all methods and data, so
+that the methods lose the implicit ``this'' that they would otherwise
+have, thus allowing for more efficient generated code.
+
+The attribute must appear between the ``class'' or ``struct'' keyword and
+the name of the type; it may not appear between the closing parenthesis
+of the body of the type and the trailing semicolon.
+
+@smallexample
+class __attribute__((force_static)) foo
+@{
+public:
+ void some_method ();
+
+private:
+ int some_field;
+@};
+
+/* Instances of the type can still be created, but they become empty. */
+extern foo the_foo;
+
+/* Any instances share the same data, as if there was just one
+ instance. */
+int foo::some_field;
+
+void bar (void)
+@{
+ /* You can invoke methods on such a class... */
+ the_foo.some_method ();
+
+ /* ...where the attribute makes the above be implicitly equivalent to
+ this: */
+ foo::some_method ();
+@}
+@end smallexample
+
@end table
To specify multiple attributes, separate them by commas within the
diff --git a/gcc/testsuite/g++.dg/ext/force_static/attribute.C b/gcc/testsuite/g++.dg/ext/force_static/attribute.C
new file mode 100644
index 0000000..5f315e4
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/attribute.C
@@ -0,0 +1,7 @@
+class __attribute__((force_static)) foo
+{
+private:
+ int i;
+};
+
+int foo::i;
diff --git a/gcc/testsuite/g++.dg/ext/force_static/callsite.C b/gcc/testsuite/g++.dg/ext/force_static/callsite.C
new file mode 100644
index 0000000..3a11884
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/callsite.C
@@ -0,0 +1,19 @@
+// { dg-options "-fdump-tree-original" }
+
+class __attribute__((force_static)) foo
+{
+public:
+ int some_method (int i);
+
+private:
+ int some_field;
+};
+
+int test_callsite ()
+{
+ extern foo the_foo;
+ return the_foo.some_method (42);
+}
+
+// { dg-final { scan-tree-dump "foo::some_method" "original" } }
+// { dg-final { cleanup-tree-dump original } }
diff --git a/gcc/testsuite/g++.dg/ext/force_static/const.C b/gcc/testsuite/g++.dg/ext/force_static/const.C
new file mode 100644
index 0000000..5f2581f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/const.C
@@ -0,0 +1,7 @@
+class __attribute__((force_static)) foo
+{
+public:
+ /* It's harmless to have a "const" qualifier on a method of
+ such a class. */
+ void bar () const;
+};
diff --git a/gcc/testsuite/g++.dg/ext/force_static/ctor.C b/gcc/testsuite/g++.dg/ext/force_static/ctor.C
new file mode 100644
index 0000000..7d499ec
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/ctor.C
@@ -0,0 +1,5 @@
+class __attribute__((force_static)) foo
+{
+public:
+ foo(); /* { dg-error "constructor cannot be static member function" } */
+};
diff --git a/gcc/testsuite/g++.dg/ext/force_static/dtor.C b/gcc/testsuite/g++.dg/ext/force_static/dtor.C
new file mode 100644
index 0000000..a62ba57
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/dtor.C
@@ -0,0 +1,5 @@
+class __attribute__((force_static)) foo
+{
+public:
+ ~foo(); /* { dg-error "destructor cannot be static member function" } */
+};
diff --git a/gcc/testsuite/g++.dg/ext/force_static/method.C b/gcc/testsuite/g++.dg/ext/force_static/method.C
new file mode 100644
index 0000000..b94de84
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/method.C
@@ -0,0 +1,19 @@
+// { dg-options "-fdump-tree-original" }
+
+class __attribute__((force_static)) foo
+{
+public:
+ int some_method (int i);
+
+private:
+ int some_field;
+};
+
+int foo::some_method (int i)
+{
+ some_field += i;
+ return some_field;
+}
+
+// { dg-final { scan-tree-dump-not "this" "original" } }
+// { dg-final { cleanup-tree-dump original } }
diff --git a/gcc/testsuite/g++.dg/ext/force_static/not-a-class.C b/gcc/testsuite/g++.dg/ext/force_static/not-a-class.C
new file mode 100644
index 0000000..0cda06c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/not-a-class.C
@@ -0,0 +1 @@
+int i __attribute__((force_static)); /* { dg-warning "'force_static' attribute can only be applied to class definitions" } */
diff --git a/gcc/testsuite/g++.dg/ext/force_static/struct.C b/gcc/testsuite/g++.dg/ext/force_static/struct.C
new file mode 100644
index 0000000..8ba5a50
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/struct.C
@@ -0,0 +1,6 @@
+struct __attribute__((force_static)) foo
+{
+ int i;
+};
+
+int foo::i;
diff --git a/gcc/testsuite/g++.dg/ext/force_static/trailing.C b/gcc/testsuite/g++.dg/ext/force_static/trailing.C
new file mode 100644
index 0000000..b6dfa2f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/trailing.C
@@ -0,0 +1,3 @@
+class foo
+{
+} __attribute__((force_static)); /* { dg-warning "'force_static' attribute must be applied after the class keyword, not after the closing brace" } */
diff --git a/gcc/testsuite/g++.dg/ext/force_static/vfunc.C b/gcc/testsuite/g++.dg/ext/force_static/vfunc.C
new file mode 100644
index 0000000..b0da35d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/force_static/vfunc.C
@@ -0,0 +1,5 @@
+class __attribute__((force_static)) foo
+{
+public:
+ virtual void bar (); /* { dg-error "member 'bar' cannot be declared both virtual and static" } */
+};