3

I think this is a bug in clang or the OSX linker, but I wanted to ask here to make sure.

I have the following (simplified) setup in a C++ program. A singleton Repository class:

class Repository {
public:
    static Repository *instance();
    void registerWidget(const char *name, Widget *w) {}
};

A widget interface:

class Widget {
public:
    virtual void widgetify() = 0;
};

Finally a simple widget:

class SimpleWidget : public Widget {
public:
    virtual void widgetify() {}
};

Inside SimpleWidget I want to register the widget to the Repository at run-time automagically. Normally I do this using an anonymous namespace and a register function. Something like this:

namespace {
    bool registrar() {
        Registrar::instance() -> registerWidget("SimpleWidget", new SimpleWidget);
        return true;
    }

    bool R = registrar();
}

In a current project using Qt targeting iOS and Android I've run into a problem with this system. clang (or the linker) is identifying SimpleWidget as dead code and stripping it (and thus it doesn't show up in Repository since registrar() is never called.) It seems to do this despite the fact that SimpleWidget is obviously referenced in registrar(). If I make a reference to SimpleWidget in any of my other translation units, then SimpleWidget is no longer stripped and everything works properly.

Am I missing something about how dead code stripping should work, or is this a legit tool chain bug?

Tim
  • 4,560
  • 2
  • 40
  • 64
  • Could you modify your simple example so that it's actually compilable? There's the potential for undefined behavior depending on how you implement things. – Bill Lynch Jan 13 '15 at 17:07
  • Does this, by chance, involve a static library? – Wintermute Jan 13 '15 at 17:12
  • @Wintermute, yes it does. Although Repository and SimpleWidget are in the same static library. – Tim Jan 13 '15 at 17:13
  • 1
    The only reference to anything in the translation unit from outside the translation unit is the implicit call from the C++ startup facility. That generally won't prevent the unit from being stripped-- which is not a bug. You probably need some linker flags to prevent the stripping. – antlersoft Jan 13 '15 at 17:14
  • You can keep the linker from stripping things with `--whole-archive` (or `-Wl,--whole-archive` if you're calling `ld` through the compiler). The interesting part is whether you can tell the linker to keep these particular objects but still strip other unused code. Let me check something. – Wintermute Jan 13 '15 at 17:16
  • Hm, there's not the easy way I was hoping for, at least (there may be other easy ways that I don't know about; if so, please enlighten me). You could put the object files that are needed even if none of their symbols are used in a static library of their own and pass `-Wl,--whole-archive libthisthing.a -Wl,--no-whole-archive`, but that seems to be as far as it goes. – Wintermute Jan 13 '15 at 17:22
  • The implementation is allowed to delay dynamic initialization until the first odr-use of something defined in that translation unit, so if you never odr-use anything in that TU, dynamic initialization can be delayed indefinitely. – T.C. Jan 13 '15 at 17:24
  • So the consensus appears to be that this isn't a real bug? – Tim Jan 13 '15 at 18:38
  • Try to access your R variable somewhere in the code. R might be considered dead code since it's never used, thus initialization is dead code then your Simple Widget becomes dead code. – MichaelCMS Jan 14 '15 at 08:24
  • @Wintermute: the problem is that to do **proper** unused-code optimization, you need at least 3 things: individually packaged code and data fragments, a compiler which emits the real dependencies, and a linker which starting from `main` includes the whole dependency tree but nothing more. The first bit works, the second part is quite flaky, and the third part isn't much worked on since the problems in the preceding part. – MSalters Jan 14 '15 at 14:59

1 Answers1

1

All objects in a translation unit (such as R) need to be created before the first call to a function in that Translation Unit (.cpp file). You don't have a call to a single function in your TU, so R does not need to be created, so registrar() is indeed dead code, and anything called exclusively from registrar() is equally dead.

The toolchain is correct.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Thanks. I thought static/anonymous namespaces didn't were always called, hence working around the problem. Always nice to learn something new :) – Tim Jan 14 '15 at 13:35