(VC++) How to prevent linker throwing away globals it fails to see being used?

Started by
9 comments, last by tanzanite7 10 years, 2 months ago

Distilled example case:


template<class T> class Test {
    static __declspec(allocate(".xyz")) int snafu;
};

template<class T> int Test<T>::snafu = 42;

Everything in ".xyz" section is actually used - indirectly. Is there any way to prevent the linker from removing "snafu"? Did i miss something?

Note 1: I auto generate bullshit access functions for them and prevent linkers eagerness that way - so, no standing problem. My question is towards: is there something i have missed and i can just tell it (VS toolchain via source code) to not mess with certain globals? Not expecting there to be, but it does not hurt to ask.

Note 2: The whole point of doing what the example code is related to is to see where i can get with it - so, sidestepping the problem as shown is completely nonsensical. Please do not.

edit update:

Blaming linker was premature - this case has more todo with funky template behavior. Short: if you instantiate a template then its static variables are only instantiated when you have reachable references to it. Instantiation won't happen even when the constructor of the variable has visible side effects elsewhere.

Advertisement

I think you'd have to use /INCLUDE:<mangled name of your variable>. Or just use the variable. You can use a descriptively named macro (FORCE_LINKER_INCLUDE or something), or a function registered with atexit to use the variables.

If you want to force all unused references (not just select variables) to be included, you'd use /OPT:NOREF

Interesting. I can imagine a few possible reasons for it to be removed.

A template is not necessarily generated or expanded. It is like a cookie cutter with a specific shape, and it is used to shape cookies on demand. If there is no demand for a specific cookie shape the compiler might not generate anything.

Second, even if it does generate the right cookie, the optimizer and linker might decide the cookie isn't used and throw it away.

There might be a few more, but those are the two biggest that spring to mind with that code.


Your declaration of snafu is incomplete; it is cookie cutter and not a shaped cookie. You need to actually define and specialize an object by filling in the type: template<> int Test<YourTypeHere>::snafu = 42; I'm pretty sure (but not 100% certain on the case of a static data member) that would generate the symbol inside a source file.

For the second case, you can prevent the linker from removing a symbol (assuming it exists) by forcing a symbol reference so it doesn't get optimized away at that level. In the ide for the project options look up Linker / Input / Force Symbol References for that.

You can use a descriptively named macro (FORCE_LINKER_INCLUDE or something)

That is pretty much exactly what i do currently.

A template is not necessarily generated or expanded.

Yeah, template is the worst case (i have to use FORCE_LINKER_INCLUDE kind of thing per specialization). Even when a static variable does have construction side effects etc - it won't be instantiated when not referenced.

Speaking of which, my OP is a bit misleading as with the example case i am pretty sure the linker never gets to even see it (did not realize that at the time).

Yeah, specialization of the member variable would almost surely help, but is essentially no different from the specializing FORCE_LINKER_INCLUDE macro. Except that the macro is guaranteed to work, whereas the specialization way could still be eaten by the linker.

... hm. Legally speaking: if i reference something in template that has side effects then the compiler should not be allowed to dump it afterwards even if the code that was the initial cause of reference will be completely optimized out.

... have to test that after dinner. Would rid me of specialized macros - not sure if the compiler is allowed to act differently.

edit: Do-nothing reference to static member variable that has side effects (*) does work. The variable will not be optimized out (Release build) nor dropped by linker. If the variable does not have side effects then it does get removed even with the do-nothing reference.

(*) side effect being: Touching/using the variable i actually care about, "snafu" in this case. Hence preventing it from disappearing.

... still, it is slightly better than using specializing macros. Annoyance being: i have to drop the do-nothing-reference into some used function of the template. Can not think of any way around that - yet.

I think what frob means is that unless you instatiate the template with a concrete type, it won't actually exist anywhere. Test<T>::snafu doesn't actually exist as an entity. If you create a Test<int> then Test<int>::snafu then exists but the template itself creates nothing, including static members.

I think what frob means is that unless you instatiate the template with a concrete type, it won't actually exist anywhere. Test<T>::snafu doesn't actually exist as an entity. If you create a Test<int> then Test<int>::snafu then exists but the template itself creates nothing, including static members.

That is it exactly.

A template allows the compiler to do something if it needs to. It does not by itself do anything. A template is not an instance, but instead is a pattern that can be used to make instances.

You essentially have a declaration, a statement that a template instance following that pattern can potentially have that value exist somewhere. Such a declaration still needs a definition, an actually defined thing that is code and data.

OT: does anyone know a workaround for the forum software bug where the formatting toolbar wont show up. Currently it went poof again - can not do any formatting.

I think what frob means is that unless you instatiate the template with a concrete type, it won't actually exist anywhere. Test<T>::snafu doesn't actually exist as an entity. If you create a Test<int> then Test<int>::snafu then exists but the template itself creates nothing, including static members.

Erm, that much is obvious. I of course do instantiate the Test class with various stuff - the problem is that if the "snafu" is not used in any code path then it wont be created with the rest of the instantiation.

Ie.


template<class T> class Test {
    static __declspec(allocate(".xyz")) int snafu1;
    static __declspec(allocate(".xyz")) int snafu2;
public:
    static void doSomething() { ... something with side effects ... _also accessing "snafu2"_ ... }
};

template<class T> int Test<T>::snafu1 = 42;
template<class T> int Test<T>::snafu2 = 42;
 
int main() {
    Test<int>::doSomething();
}

=> "Test<int>::snafu1" does not exist, whereas "Test<int>::snafu2" does exist!

Also worth noting: whether the constructor of "snafu" has visible side effects (ex: writing stuff to logging file) or not will not change anything - which is quite bizarre.

I would have expected that both would exist, but that is not the case.

(What does the standard say? Looks bonkers. There are no substitution problems - why is it allowed to be ignored?)

A more complete example of what i am talking about:


// not sure what headers it needs. OutputDebugStringW seems to need Windows.h
 
#pragma section(".xyz$a")
#pragma section(".xyz$b")
#pragma section(".xyz$c")

int answer = 42;

template<class T> class Test {
    static __declspec(allocate(".xyz$b")) int snafu1;
    static __declspec(allocate(".xyz$b")) int snafu2;
public:
    static void doSomething() { OutputDebugStringW(L"\ndoing something\n"); snafu1 = (answer + snafu1) / 2; }
};

template<class T> int Test<T>::snafu1 = 40;
template<class T> int Test<T>::snafu2 = 43;

__declspec(allocate(".xyz$a")) int section_a;
__declspec(allocate(".xyz$c")) int section_c;
 
int main() {
    Test<int>::doSomething();
    int *at = &section_a;
    if(answer) OutputDebugStringW(L"\n");
    do {
        if(*at == 41) OutputDebugStringW(L"\ngot 41 => snafu1 is present\n");
        if(*at == 43) OutputDebugStringW(L"\ngot 43 => snafu2 is present\n");
        at++;
    } while(at != &section_c);
    return 0;
}

This will result:

"doing something"

"got 41 => snafu1 is present"

"snafu2" is not present.

edit:

If one changes "snafu1" and "snafu2" to type "Int" a'la:


struct Int {
    int val;
    Int(int _val) : val(_val) { OutputDebugStringW(L"\nImportant stuff happening!\n"); }
    int operator=(int _val) { return val = _val; }
    operator int&() { return val; }
};

then the output will be:

"Important stuff happening!"

"doing something"

"got 41 => snafu1 is present"

Just bonkers.

I know the problem you're dealing with, this is tedious to solve (more so when static libraries are involved across multiple platforms smile.png ).
The issue you are seeing is not caused by the linker, but rather by the compiler. You can easily verify that by using dumpbin.exe /SYMBOLS on the object file - snafu2 won't be in there, whereas snafu1 will be. So the linker never even has a chance of seeing the symbol.

The problem is that snafu2 is not used directly anywhere in the code you posted. It is never accessed, nor is its address taken. The compiler (unfortunately) doesn't care about the __declspec(allocate()) statements and the fact that you are merging sections, in order to iterate through them later. It will simply never emit the symbol "static int Test<int>::snafu2" into the object file.

There is one workaround, however. Tell the compiler you use this symbol by handing it to something he can't see, e.g. a function with external linkage defined in another translation unit, something akin to the following:


anyTranslationUnit.cpp:
 
void DummyRegister(int)
{
  // doesn't need to do anything
}


and in your code:


template<class T> class Test {
    static __declspec(allocate(".xyz$b")) int snafu1;
    static __declspec(allocate(".xyz$b")) int snafu2;
public:
    static void doSomething() { OutputDebugStringW(L"\ndoing something\n"); snafu1 = (answer + snafu1) / 2; }
    static void Register()
    {
      extern void DummyRegister();
      DummyRegister(snafu1);
      DummyRegister(snafu2); 
    }
};
 

I guess the code you posted is the result of some macro invocations, or other means of automatic code generation. I hope you're able to change the process to also include the dummy-registration process. I fear there's no other way around your issue other than letting the compiler believe you are somehow using those symbols (at least not in MSVC).

HTH,

-tiv

What's the meta-problem behind the problem? There may be a more standard way to achieve your goals without tricking the tools and skirting around the fringes of the spec.

This topic is closed to new replies.

Advertisement