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" } */ +};