4 Replies Latest reply: Nov 29, 2011 7:16 PM by koval RSS

    Weak symbols while instantiating templated static member functions

    877216
      Hello!

      I'm currently working on getting a fairly large C++ codebase to build on OpenSolaris with Sun Studio (<tt>CC: Sun C++ 5.10 SunOS_i386 128229-02 2009/09/21</tt>) linking against libCstd. As part of this codebase, there's a plugin mechanism that relies on the compiler's behavior with regard to templated member functions. And here it seems Sun Studio behaves differently from g++, whose behavior the source relies on.

      I've distilled the troublesome bit of code into the following testcase. I apologize for the length, I've made it as short as practical to illustrate the discrepancy between CC and g++.

      Factory.h:
      #include <string>
      #include <map>
      
      class Factory {
      
      public:
      
              Factory(int);
              void kickoff();
      
      protected:
              //Function pointer type to a function that instantiates a plugin.
              typedef void* (*CreateInstanceFunction)();
      
              template<class T>
              void registerPlugin(const std::string &keyword = std::string(), CreateInstanceFunction instanceFunction
                        = &createInstance<T>    )
              {
                      registerPlugin(keyword, instanceFunction, 98);
              }
      
              template<class impl>
              static void* createInstance()
              {
                      return new impl();
              }
      
      private:
              void registerPlugin(const std::string &keyword, CreateInstanceFunction instanceFunction, int extra_arg);
      
              std::map<std::string, CreateInstanceFunction> pluginMap;
      };
      Factory.cpp:
      #include "Factory.h"
      #include <stdio.h>
      #include <string>
      #include <iostream>
      
      Factory::Factory(int f)
      {
              std::cout << "Instantiating Factory." << std::endl;
      }
      
      void Factory::registerPlugin(const std::string &keyword, CreateInstanceFunction instanceFunction, int extra_arg)
      {
              char addrbuf[20];
              std::sprintf(addrbuf, "Ox%X", instanceFunction);
              std::string instanceFunctionAddress = std::string( addrbuf );
              std::cout << "registerPlugin " << keyword << " instanceFunction=" << instanceFunctionAddress << std::endl;
              pluginMap.insert(std::pair<std::string, CreateInstanceFunction>(keyword, instanceFunction));
      }
      
      
      void Factory::kickoff(){
              std::cout << "Starting plugin registered under \"c\"" << std::endl;
              std::map<std::string, CreateInstanceFunction>::iterator somePluginIterator = pluginMap.find(std::string("c"));
              if (pluginMap.end() != somePluginIterator)
                      * (CreateInstanceFunction) somePluginIterator->second();
      }
      main.cpp:
      #include "Factory.h"
      #include <iostream>
      #include <stdio.h>
      
      class FooFactory : public Factory
      {
              public:
                      FooFactory(int);
      };
      
      #define CREATE_PLUGIN(type) \
      class Plugin##type \
      { \
              public: \
                      Plugin##type () \
                      { \
                      std::cout << "Instantiated " << #type << std::endl; \
                      } \
      }; \
      
      CREATE_PLUGIN(Alpha)
      CREATE_PLUGIN(Bravo)
      CREATE_PLUGIN(Charlie)
      CREATE_PLUGIN(Delta)
      CREATE_PLUGIN(Echo)
      
      //FooFactory constructor registers these plugin classes
      FooFactory::FooFactory(int arg)
              : Factory(arg)
      {
              registerPlugin<PluginAlpha>("a");
              registerPlugin<PluginBravo>("b");
              registerPlugin<PluginCharlie>("c");
              registerPlugin<PluginDelta>("d");
              registerPlugin<PluginEcho>("e");
      }
      
      int main (int argc, char** argv)
      {
              FooFactory testFactory(33);
              testFactory.kickoff();
              return 0;
      }
      When I compile these files with Sun Studio:
      CC main.cpp -c -o main.o; CC Factory.cpp -c -o Factory.o; CC main.o Factory.o -o testcase
      We'd expect to run <tt>createInstance<Charlie>()</tt> in kickoff(), which would construct a <tt>PluginCharlie</tt> object, but the testcase yields the following incorrect output:
      Instantiating Factory.
      registerPlugin a instanceFunction=0x80535E0
      registerPlugin b instanceFunction=0x80535E0
      registerPlugin c instanceFunction=0x80535E0
      registerPlugin d instanceFunction=0x80535E0
      registerPlugin e instanceFunction=0x80535E0
      Starting plugin registered under "c"
      Instantiated Alpha
      And these are the symbols for <tt>registerPlugin</tt> and <tt>createInstance</tt>:
      994-mboyer@duke:~/Hacks/factory_testcase$ nm -C testcase | egrep -i '(registerplugin|createinstance)'
      [129]   | 134569792|       540|FUNC |GLOB |0    |13     |void Factory::registerPlugin(const std::basic_string<char,std::char_traits<char>,std::allocator<char> >&,void*(*)(),int)
                                                             [__1cHFactoryOregisterPlugin6MrknDstdMbasic_string4Ccn0BLchar_traits4Cc__n0BJallocator4Cc____pF_pvi_v_]
      [161]   | 134559200|       144|FUNC |GLOB |0    |13     |void*Factory::createInstance<PluginAlpha>()
                                                             [__1cHFactoryOcreateInstance4nLPluginAlpha__6F_pv_]
      So we don't have templated symbols for <tt>registerPlugin(const string, CreateInstanceFunction)</tt> (2 arguments only!) and only one templated symbol for <tt>createInstance()</tt> with the first template with which it is instantiated.

      By contrast, when g++ is used (gcc version 3.4.3 (csl-sol210-3_4-20050802)):
      g++ main.cpp -c -o gmain.o; g++ Factory.cpp -c -o gFactory.o; g++ gmain.o gFactory.o -o gtestcase
      ...the testcase behaves correctly:
      Instantiating Factory.
      registerPlugin a instanceFunction=0x8053CF0
      registerPlugin b instanceFunction=0x8053D9E
      registerPlugin c instanceFunction=0x8053E4C
      registerPlugin d instanceFunction=0x8053EFA
      registerPlugin e instanceFunction=0x8053FA8
      Starting plugin registered under "c"
      Instantiated Charlie
      And we see the following symbols in the executable:
      [94]    | 134561008|        81|FUNC |WEAK |0    |14     |Factory::createInstance<PluginAlpha>(void*, void)
                                                             [_ZN7Factory14createInstanceI11PluginAlphaEEPvv]
      [212]   | 134561182|        81|FUNC |WEAK |0    |14     |Factory::createInstance<PluginBravo>(void*, void)
                                                             [_ZN7Factory14createInstanceI11PluginBravoEEPvv]
      [182]   | 134561356|        81|FUNC |WEAK |0    |14     |Factory::createInstance<PluginCharlie>(void*, void)
                                                             [_ZN7Factory14createInstanceI13PluginCharlieEEPvv]
      [96]    | 134561530|        81|FUNC |WEAK |0    |14     |Factory::createInstance<PluginDelta>(void*, void)
                                                             [_ZN7Factory14createInstanceI11PluginDeltaEEPvv]
      [85]    | 134561704|        81|FUNC |WEAK |0    |14     |Factory::createInstance<PluginEcho>(void*, void)
                                                             [_ZN7Factory14createInstanceI10PluginEchoEEPvv]
      [207]   | 134562628|       417|FUNC |GLOB |0    |14     |Factory::registerPlugin(std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)(), int)
                                                             [_ZN7Factory14registerPluginERKSsPFPvvEi]
      [110]   | 134561154|        27|FUNC |WEAK |0    |14     |Factory::registerPlugin<PluginAlpha>(void, std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)())
                                                             [_ZN7Factory14registerPluginI11PluginAlphaEEvRKSsPFPvvE]
      [93]    | 134561328|        27|FUNC |WEAK |0    |14     |Factory::registerPlugin<PluginBravo>(void, std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)())
                                                             [_ZN7Factory14registerPluginI11PluginBravoEEvRKSsPFPvvE]
      [210]   | 134561502|        27|FUNC |WEAK |0    |14     |Factory::registerPlugin<PluginCharlie>(void, std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)())
                                                             [_ZN7Factory14registerPluginI13PluginCharlieEEvRKSsPFPvvE]
      [97]    | 134561676|        27|FUNC |WEAK |0    |14     |Factory::registerPlugin<PluginDelta>(void, std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)())
                                                             [_ZN7Factory14registerPluginI11PluginDeltaEEvRKSsPFPvvE]
      [158]   | 134561850|        27|FUNC |WEAK |0    |14     |Factory::registerPlugin<PluginEcho>(void, std::basic_string < char, std::char_traits<char>, std::allocator<char> >, void* (*)())
                                                             [_ZN7Factory14registerPluginI10PluginEchoEEvRKSsPFPvvE]
      So here we have weak symbols for all templated instantiations of <tt>registerPlugin(const string, CreateInstanceFunction)</tt> as well as <tt>createInstance()</tt>, plus one global symbol for registerPlugin(const string, CreateInstanceFunction, int) (3 arguments!).

      Is there anything I could do to entice Sun Studio to process these templated functions in a manner similar to g++?

      Thanks,

      - Matt Boyer

      Edited by: Matt Boyer on Oct 14, 2011 8:23 PM
        • 1. Re: Weak symbols while instantiating templated static member functions
          Steve.Clamage-Oracle
          It looks like you have run into a bug in C++ 5.10 (Studio 12u1).
          The code behaves as you expect using C++ 5.11 (Studio 12.2) with current patches. (The original release of C++ 5.11 has the same bug.)

          If you have a service contract and have the option of upgrading to Studio 12.2, you can get current patches to fix this problem.

          Studio 12.3 (C++ 5.12) is planned for release this year and will also have the bug fix. (Studio 12.3 beta still has the bug.)
          • 2. Re: Weak symbols while instantiating templated static member functions
            877216
            Hey Steve,

            That's a very helpful answer ; thanks so much! Do you know the patchid for Sol Studio 12.2?

            Cheers!

            - Matt Boyer
            • 3. Re: Weak symbols while instantiating templated static member functions
              Steve.Clamage-Oracle
              The compiler I tried had C++ patch
              145730-04 2011/07/19 (sparc)
              145731-04 2011/07/19 (x86)

              But it's generally a good idea to get all the current patches, not just one. You can get a complete list of patches for Studio 12.2 at My Oracle Support (support.oracle.com)
              • 4. Re: Weak symbols while instantiating templated static member functions
                koval
                In one of our projects we had similar code. To make it more general I refactored the classes a little to avoid having a centralized function that registers all creators.
                The solution depends on explicit template class instantiation that works well in <tt>CC</tt>

                Plugin.h:
                // instead of just void*
                class Plugin { /* ... */ };
                
                // instead of bare function pointer
                struct PluginCreator {
                    virtual ~PluginCreator() {}
                    virtual Plugin* createPlugin() const = 0;
                };
                Factory.h:
                #include <string>
                #include "Plugin.h"
                
                // helper class that registers a creator for a given keyword
                class FactorySubscriber {
                    std::string keyword_;
                    const PluginCreator* creator_;
                public:
                    FactorySubscriber(const std::string&, const PluginCreator*);
                    ~FactorySubscriber();
                };
                
                class Factory {
                    friend class FactorySubscriber;
                public:
                    static Factory& getInstance();
                    Plugin* kickoff(const std::string&);
                private:
                    Factory();
                };
                PluginImpl.h - for client code that creates new plugin types
                #include "Plugin.h"
                
                template<typename T>
                class PluginImpl: public Plugin
                {
                    static FactorySubscriber __factory_subscriber;
                public:
                    PluginImpl() { T::init(); } // all possible Plugin virtual methods might also be implemented by forwarding to T's static methods
                };
                
                // this class plays the role of Factory::createInstance
                template<typename T>
                struct DefaultPluginCreator: PluginCreator {
                    virtual Plugin* createPlugin() const
                    {
                        return new PluginImpl<T>();
                    }
                };
                
                // This ensures that every instantiated DefaultPluginCreator gets registered in the factory
                template<typename T>
                FactorySubscriber PluginImpl<T>::__factory_subscriber(T::keyword(), new DefaultPluginCreator<T>());
                Factory.cpp:
                #include <map>
                #include <iostream>
                #include <stdexcept>
                #include "Factory.h"
                
                class CreatorRegistry {
                    typedef std::map<std::string, const PluginCreator*> PluginMap;
                    PluginMap pluginMap;
                public:
                    CreatorRegistry& getInstance();
                    void registerPlugin(const std::string& keyword, const PluginCreator* creator);
                    const PluginCreator* get(const std::string& keyword) const;
                };
                
                CreatorRegistry& CreatorRegistry::getInstance()
                {
                    static CreatorRegistry instance;
                    return instance;
                }
                
                void CreatorRegistry::registerPlugin(const std::string& keyword, const PluginCreator* creator)
                {
                    std::cout << "registerPlugin " << keyword << " creator=" << creator << std::endl;
                    pluginMap.insert(PluginMap::value_type(keyword, creator));
                }
                
                const PluginCreator* CreatorRegistry::get(const std::string& keyword)
                {
                    PluginMap::const_iterator i(pluginMap.find(keyword));
                    if (i != pluginMap.end())
                        return i->second;
                    throw std::invalid_argument("No plugin found for key \"" + keyword + "\"");
                }
                
                Factory::Factory()
                {
                    std::cout << "Instantiating Factory." << std::endl;
                }
                
                Factory& Factory::getInstance()
                {
                    static Factory instance;
                    return instance;
                }
                
                Plugin* Factory::kickoff(const std::string& keyword)
                {
                    std::cout << "Starting plugin registered under \"" << keyword << "\"" << std::endl;
                    return CreatorRegistry::getInstance().get(keyword)->createPlugin();
                }
                
                FactorySubscriber::FactorySubscriber(const std::string& keyword, const PluginCreator* creator):
                    keyword_(keyword),
                    creator_(creator)
                {
                    CreatorRegistry::getInstance().registerPlugin(keyword, creator);
                }
                
                FactorySubscriber::~FactorySubscriber()
                {
                    //here we could unregister the plugin if registry implements that CreatorRegistry::getInstance().unregisterPlugin(keyword_, creator_);
                    delete creator_;
                    creator_ = 0;
                }
                main.cpp:
                #include <iostream>
                #include "Factory.h"
                #include "PluginImpl.h"
                
                // Plugin implementation
                #define CREATE_PLUGIN(type, key) \
                struct type \
                { \
                    static const char* keyword() { return #key; } \
                    static void init() \
                    { \
                        std::cout << "Instantiated " #type << std::endl; \
                    } \
                }; \
                template class PluginImpl<type> /* explicit instantiation */
                
                // No need to explicitly register anything!
                CREATE_PLUGIN(Alpha, a);
                CREATE_PLUGIN(Bravo, b);
                CREATE_PLUGIN(Charlie, c);
                CREATE_PLUGIN(Delta, d);
                CREATE_PLUGIN(Echo, d);
                
                int main()
                {
                    Factory::getInstance().kickoff("c");
                    return 0;
                }
                What is nice about this solution is that you compile <tt>Factory</tt> into a library and the client is able to automatically register new plugins without extending <tt>Factory</tt>.
                By moving the map to a separate class (<tt>CreatorRegistry</tt>) the client also does not need to include <tt><map></tt>.

                We could also use types generated by <tt>CREATE_PLUGIN</tt> directly as plugins if we changed first line of the macro to read: <tt>struct type: public DefaultPluginImpl<type></tt> (magic of CRTP)

                This is the result from my code, compiled with <tt>CC: Sun C++ 5.9 SunOS_sparc Patch 124863-01 2007/07/25</tt> which seems to be a 2 years older patchlevel:
                >
                registerPlugin a creator=49f18
                registerPlugin b creator=49f28
                registerPlugin c creator=49f38
                registerPlugin d creator=49f48
                registerPlugin d creator=49f58
                Instantiating Factory.
                Starting plugin registered under "c"
                Instantiated Charlie
                >

                You can see that registerPlugin messages appear before Instantiating Factory because registering is now done in <tt>CreatorRegistry</tt> prior to a call to <tt>Factory::getInstance()</tt>