locked
Linker discards static methods in release build

    Question

  • I have a simple Test class with only static methods. In Release build, the linker discards the methods. However, the winmd file still contains the reference to them so that the methods seem to be "callable" from JavaScript. However, the call of course fails but does not produce any exception or so.

    Whats going on here?

    namespace runtime {
      namespace protection {
    
        public ref class Tests sealed {
        public:
          static void division_by_zero() {
            SEHGuard guard;
    
            int i=100;
            int y=0;
            int result = i/y;
          }
    
          static void access_violation() {
            SEHGuard guard;
    
            int* address = (int*)0xdeadbead;
            BYTE buffer[16];
            memcpy(buffer, address, 16);
          }
        };
      }
    }
       Discarded "public: static void __cdecl runtime::protection::Tests::division_by_zero(void)" (?division_by_zero@Tests@protection@runtime@@SAXXZ) from WinRTComponent.obj
    1>      Discarded "public: static void __cdecl runtime::protection::Tests::access_violation(void)" (?access_violation@Tests@protection@runtime@@SAXXZ) from WinRTComponent.obj
    1>   

    I understand /OPT:NOREF should remove unreferenced functions, but the linker cannot know if the functions are not supposed to be referenced from outside the DLL only.

    How can I prevent the linker from removing the 2 methods?

    Thursday, May 24, 2012 9:23 AM

Answers

  • This isn't a hack, and the functions are not removed from the public surface area of your winrt component. If you call this code from a C++/CX app you can step through the disassembly and see the Tests component created and calls/jmps being made. There is a function there, and it is being called -- it just doesn't do anything after optimization.

    The issue here is that you are relying on side effects. Looking at division_by_zero, for instance -- you create two locals, and then assign a computation using those two locals to a third local and then immediately destroy all three locals by leaving the scope of division_by_zero. Since this is dead code and doesn't affect the program state, the compiler is free to optimize it away.

    What you are calling a hack is just an example of changing the functions so that they do affect outside of the local scope and the compiler no longer optimizes the no longer dead code out.

    Another option here is using a pragma to turn off the optimizations for your class

    #pragma optimize("",off)
    	public ref class Tests sealed {
    	public:		  
    	  static void division_by_zero()
    	  {
    		int i=100;
    		int y=0;
    		int result = i/y;
    	  }
    
    	  static void access_violation() {
    		int* address = (int*)0xdeadbead;
    		BYTE buffer[16];
    		memcpy(buffer, address, 16);
    	  }
    	};
    #pragma optimize("",on)

    If you're not familiar with the C++/CX projection and what is underlying it, watching the build presentation
    http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-690C is worth your time. The compiler builds a wrapper class which is the public surface area of your WinRT component. This wrapper in turn calls into your code.  Deon also talks about the /d1reportAllClassLayout and /d1ZWtokens compiler front end switches. You can use these to see what the code looks like before optimization:

    __declspec ( no_refcount ) inline long __stdcall ::WindowsRuntimeComponent1::runtime::protection::__TestsActivationFactory::__abi_WindowsRuntimeComponent1_runtime_protection___ITestsStatics____abi_division_by_zero() 
    { 
        long __hr = 0 ; 
        try 
        { 
    	division_by_zero ( ) ; 
        } 
      
        catch ( :: Platform :: Exception ^ __param0 ) 
        { 
        	__hr = Platform :: Details :: ReCreateFromException ( __param0 ) ; 
        } 
      
        catch ( ... ) 
        { 
    		__abi_FailFast ( ) ; 
        } 
      
        return __hr ; 
    }

    The wrapper function ::WindowsRuntimeComponent1::runtime::protection::__TestsActivationFactory::__abi_WindowsRuntimeComponent1_runtime_protection___ITestsStatics____abi_division_by_zero() is what is going to be seen by calling code. This is the public surface area that remains after division_by_zero is optimized away.


    Thursday, June 07, 2012 4:29 PM
    Moderator

All replies

  • Just a guess here, but assuming this is a linker bug, then you could try working around it by adding a dummy public instance method that calls both these static methods.

    http://blog.voidnish.com

    Thursday, May 24, 2012 10:36 PM
  • You can prevent the discard with Linker directives:

    #ifdef NDEBUG
    #pragma comment(linker, "/include:?division_by_zero@Tests@protection@runtime@@SAXXZ")
    #pragma comment(linker, "/include:?access_violation@Tests@protection@runtime@@SAXXZ")
    #endif

    The fact that we still generate the functions in the winmd seems a little strange, I'm contacting some people in the product group about that.

    Saturday, May 26, 2012 1:36 AM
    Moderator
  • Thanks Steve for looking into this. 

    I find it a little strange, that the linker decides to remove these public functions in release builds from a public WinRT component. How can he know that nobody will call these functions later (from script)?

    I will try your pragmas though for the time being.

    edit: the pragmas do not help. The code is still discarded.


    • Edited by phil_ke Tuesday, May 29, 2012 9:31 AM
    Tuesday, May 29, 2012 9:18 AM
  • Phil, just curious, but did you try my workaround by any chance?

    http://blog.voidnish.com

    Tuesday, May 29, 2012 12:04 PM
  • Have not tried... lemme check it.

    edit: Does not work :(

    • Edited by phil_ke Tuesday, May 29, 2012 12:19 PM
    Tuesday, May 29, 2012 12:15 PM
  • How are you testing for the discard? I used the /verbose link and verified that the discarded line wasn't being listed in the linker spew.
    Thursday, May 31, 2012 7:26 PM
    Moderator
  • Steve, please read my first post. I of course used the verbose linker arg and it printed out the discards. VS 11 Express from the Consumer Preview.

    Furthermore, the code is just not working. Calling the static functions from JS results in an error (which is not thrown back to the JS for some reason) but my unit tests fail. They succeed in debug mode.

    Thursday, May 31, 2012 7:49 PM
  • I've bugged this after testing from C++/CX to C++/CX and getting the same behavior (skipped methods). I'll reply back to the thread if I get a workaround.
    Monday, June 04, 2012 9:16 PM
    Moderator
  • I've bugged this after testing from C++/CX to C++/CX and getting the same behavior (skipped methods). I'll reply back to the thread if I get a workaround.

    Well, this surely needs a fix/workaround. It's a major bug in my opinion, so I hope it's not too late for the compiler guys to make a fix.

    http://blog.voidnish.com

    Tuesday, June 05, 2012 2:41 PM
  • After consulting with the compiler team, this is not a bug -- it's an example of dead code being optimized away (along with the code's side-effects).

    The public surface area of Tests is still there, it's why you can call it still from javascript. The C++/CX compiler generates WinRT wrapper functions around division_by_zero and access_violation and in the wrappers inlines the user defines. In debug mode you call these and the side effects are seen.

    In release mode, the compiler is optimizing away both function bodies as dead code, so no side effects.

    An example that does not contain dead code and will not get optimized away could be:

    namespace runtime {
    	  namespace protection {
    
    		int G;
    		BYTE buffer[16];
    
    		public ref class Tests sealed {
    		public:		  
    		  static void division_by_zero()
    		  {
    			int i=100;
    			int y=0;
    			//int result = i/y;
    			G = i/y;
    		  }
    
    		  static void access_violation() {
    			int* address = (int*)0xdeadbead;
    			//BYTE buffer[16];
    			memcpy(buffer, address, 16);
    		  }
    		};
    	  }
    	}

    Wednesday, June 06, 2012 10:27 PM
    Moderator
  • Thanks Steve for checking that with the compiler team. 

    The solution you present here though is clearly a hack, imho. And its still a bug that the compiler generates winmd metadata for functions that the linker will remove.

    The linker on the other hand must not remove code from a public interface/class that he cannot know is never been called. What if my code performs a network request or high performance logging? It is called from javascript. How can the linker know its never called? I don't know, it still feels like a major premature optimization bug for winmd components.

    At least please fix the generated winmd data so that the caller fails hard instead of the current situation where you dont know what just happend when you try to call the optimized away methods.

    Thursday, June 07, 2012 2:13 AM
  • This isn't a hack, and the functions are not removed from the public surface area of your winrt component. If you call this code from a C++/CX app you can step through the disassembly and see the Tests component created and calls/jmps being made. There is a function there, and it is being called -- it just doesn't do anything after optimization.

    The issue here is that you are relying on side effects. Looking at division_by_zero, for instance -- you create two locals, and then assign a computation using those two locals to a third local and then immediately destroy all three locals by leaving the scope of division_by_zero. Since this is dead code and doesn't affect the program state, the compiler is free to optimize it away.

    What you are calling a hack is just an example of changing the functions so that they do affect outside of the local scope and the compiler no longer optimizes the no longer dead code out.

    Another option here is using a pragma to turn off the optimizations for your class

    #pragma optimize("",off)
    	public ref class Tests sealed {
    	public:		  
    	  static void division_by_zero()
    	  {
    		int i=100;
    		int y=0;
    		int result = i/y;
    	  }
    
    	  static void access_violation() {
    		int* address = (int*)0xdeadbead;
    		BYTE buffer[16];
    		memcpy(buffer, address, 16);
    	  }
    	};
    #pragma optimize("",on)

    If you're not familiar with the C++/CX projection and what is underlying it, watching the build presentation
    http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-690C is worth your time. The compiler builds a wrapper class which is the public surface area of your WinRT component. This wrapper in turn calls into your code.  Deon also talks about the /d1reportAllClassLayout and /d1ZWtokens compiler front end switches. You can use these to see what the code looks like before optimization:

    __declspec ( no_refcount ) inline long __stdcall ::WindowsRuntimeComponent1::runtime::protection::__TestsActivationFactory::__abi_WindowsRuntimeComponent1_runtime_protection___ITestsStatics____abi_division_by_zero() 
    { 
        long __hr = 0 ; 
        try 
        { 
    	division_by_zero ( ) ; 
        } 
      
        catch ( :: Platform :: Exception ^ __param0 ) 
        { 
        	__hr = Platform :: Details :: ReCreateFromException ( __param0 ) ; 
        } 
      
        catch ( ... ) 
        { 
    		__abi_FailFast ( ) ; 
        } 
      
        return __hr ; 
    }

    The wrapper function ::WindowsRuntimeComponent1::runtime::protection::__TestsActivationFactory::__abi_WindowsRuntimeComponent1_runtime_protection___ITestsStatics____abi_division_by_zero() is what is going to be seen by calling code. This is the public surface area that remains after division_by_zero is optimized away.


    Thursday, June 07, 2012 4:29 PM
    Moderator