Difference between revisions of "DIP45"

From D Wiki
Jump to: navigation, search
(Rationale)
 
(7 intermediate revisions by 4 users not shown)
Line 7: Line 7:
 
|-  
 
|-  
 
|Version:  
 
|Version:  
|2
+
|3
 
|-  
 
|-  
 
|Status:  
 
|Status:  
Line 16: Line 16:
 
|-  
 
|-  
 
|Last Modified:  
 
|Last Modified:  
|2013-09-06
+
|2017-01-30
 
|-  
 
|-  
 
|Author:  
 
|Author:  
|Benjamin Thaut, Martin Nowak  
+
|Benjamin Thaut, Martin Nowak, David Nadlinger
 
|-  
 
|-  
 
|Links:  
 
|Links:  
 
|
 
|
 +
 +
DConf 2016 Talk: https://www.youtube.com/watch?v=MQRHxI2SrYM
 +
 
*http://forum.dlang.org/post/5112D61B.5010905@digitalmars.com
 
*http://forum.dlang.org/post/5112D61B.5010905@digitalmars.com
 
*http://forum.dlang.org/post/kvhu2c$2ikq$1@digitalmars.com
 
*http://forum.dlang.org/post/kvhu2c$2ikq$1@digitalmars.com
Line 38: Line 41:
  
 
* The '''export''' protection level should be turned into a '''export''' attribute.
 
* The '''export''' protection level should be turned into a '''export''' attribute.
* If a module contains a single symbol annotated with the 'export' attribute all compiler internal symbols of this module should recieve the 'export' attribute too (e.g. module info).  
+
* '''export''' might appear in front of '''module''' to indicate that the implementation specific module symbols should be exported.
* If a class is annotated with the 'export' attribute, all of its public and protected functions and members will automatically recieve the 'export' attribute. Also all its hidden compiler specific symbols will recieve the 'export' attribute.  
+
* If a class is annotated with the 'export' attribute, all of its public and protected functions and members will automatically recieve the 'export' attribute. Also all its hidden compiler specific symbols will recieve the 'export' attribute.
* There should be only one meaning of 'export'.
+
* There should be only one meaning of 'export'.
* It should be possible to access TLS variables across DLL / shared library boundaries.  
 
 
* On *nix systems default symbol visibility is changed to hidden, and only symbols marked with export become visible.
 
* On *nix systems default symbol visibility is changed to hidden, and only symbols marked with export become visible.
* When compiling a static library with -lib all '''export''' attributes are ignored.
 
* When passing object files to dmd the -lib flag will cause all exported symbols to be removed from the object files before invoking the linker.
 
* A new compiler flag -libexports will be added to disable the previous behavior changes to -lib
 
  
 
==Rationale==
 
==Rationale==
Line 53: Line 52:
 
Currently '''export''' is a protection level, the highest level of
 
Currently '''export''' is a protection level, the highest level of
 
visibility actually. This however conflicts with the need to export
 
visibility actually. This however conflicts with the need to export
'protected' symbols.  Consider a Base class in a shared library.
+
'protected' and 'private' symbols.  Consider a Base class in a shared library.
  
<syntaxhighlight lang=D> module sharedLib;
+
<syntaxhighlight lang=D>
 +
module sharedLib;
  
 
class Base {  
 
class Base {  
Line 63: Line 63:
  
  
<syntaxhighlight lang=D> module executable; import sharedLib;
+
<syntaxhighlight lang=D>
 +
module executable;
 +
import sharedLib;
  
 
class Derived : Base {  
 
class Derived : Base {  
Line 78: Line 80:
 
orthogonal to protection.
 
orthogonal to protection.
  
===Implicitly exporting compiler internal symbols===
+
Also consider the following example in which the template will access a private function. Because the template is instanced on the user side and not within the shared library it is required to export the private function so that the template can access it from outside the shared library.
 +
 
 +
<syntaxhighlight lang=D>
 +
module dll;
 +
 
 +
void copy(T)(T val)
 +
{
 +
  copyImpl(&val, T.sizeof);
 +
}
 +
 
 +
export private copyImpl(void* mem, size_t size)
 +
{
 +
  ...
 +
}
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang=D>
 +
module exe;
 +
import dll;
 +
 
 +
void main(string[] args)
 +
{
 +
  int bar = 0;
 +
  copy(bar); // template will be instanciated in the exe but needs access to the copyImpl function.
 +
}
 +
</syntaxhighlight>
 +
 
 +
Another special case are voldemord types. If a voldemord type is used it needs to be exported explictily.
 +
<syntaxhighlight lang=D>
 +
module lib;
 +
 
 +
export auto makeSomething(int v)
 +
{
 +
  export struct Something
 +
  {
 +
    int i;
 +
  }
 +
 
 +
  return Something(v);
 +
}
 +
</syntaxhighlight>
 +
 
 +
At first glance exporting a template doesn't make much sense. But consider the following example:
 +
 
 +
<syntaxhighlight lang=D>
 +
module lib;
 +
import std.stdio;
 +
 
 +
export struct Foo(T)
 +
{
 +
  T value;
 +
  void print() { writefln("%s", value); }
 +
}
 +
 
 +
__gshared Foo!int g_inst = Foo!int(5);
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang=D>
 +
module exe;
 +
import lib;
  
All compiler internal symbols need to be treated as exported if using
+
void main(string[] args)
an exported symbol might implicitly reference them to avoid link
+
{
errors. The most prominent example is the ModuleInfo which needs
+
  auto f = Foo!int(5);
linkage if the module has a ''static this()''.
+
  f.print();
 +
}
 +
</syntaxhighlight>
  
===export attribute inference===
+
When compiling the executable module exe which uses the module lib compiled into a dll the compiler will attempt to reuse the instance of Foo!int from the lib module. This however only works if the instance has been exported from the dll. As a result exporting a tempalte should be equivalent to exporting any instance created from this template. In code:
 +
 
 +
<syntaxhighlight lang=D>
 +
export struct(T) { ... }
 +
 
 +
// is equivalent to
 +
template (T)
 +
{
 +
  export struct { ... }
 +
}
 +
</syntaxhighlight>
 +
 
 +
===Exporting module compiler internal symbols===
 +
 
 +
Each D module has a set of compiler internal symbols which may be referenced. To allow for exporting these symbols '''export''' is allowed in front of '''module''' to indicate that the compiler internal symbols should be exported. Example:
 +
 
 +
<syntaxhighlight lang=D> export module sharedLib;
  
Currently export has to be specified in a lot of places to export all
+
void testSomething(T)(T val)
neccessary functions and data symbols. Export should be transitive in
+
{
such a sense that it only needs to be specified once in a module to
+
    assert(T.sizeof > 8);
export all of its functions / data members including classes and their
+
}
members / data symbols. Consider the following example:
+
</syntaxhighlight>
  
<syntaxhighlight lang=D> module sharedLib:
+
Please note that in the above example there is not a single member of the module marked with '''export'''. But still it is required to export the compiler internal module symbols as the template will be instanciated on the user side of the shared library and thus will access the 'assert' module symbol. If the 'assert' module symbol is not exported this would lead to a linker error.
 +
The compiler internal module symbols should not be exported by default. Consider building a D-Dll with a pure C interface. In this case you don't want to export any compiler internal symbols as you want to have a very well defined C interface of your dll.
  
export:
+
===export attribute inference===
  
__gshared int g_Var; // should be exported
+
Currently export has to be specified in a lot of places to export all
 +
neccessary functions and data symbols. Export should be transitive for aggregate types (structs/classes) so that when exporting a aggregate type export is applied to all public & protected members without the need to add export to every single public and protected member.
  
void globalFunc() { ... } // should be exported
+
<syntaxhighlight lang="D">
 +
module sharedLib;
  
class A // compiler internal members should be exported  
+
export class A                         // compiler internal members should be exported (e.g. vtable, type info)
 
{  
 
{  
 
   private:  
 
   private:  
 
     int m_a;
 
     int m_a;
  
     static int s_b; // should not be exported
+
     static int s_b;             // should not be exported
  
     void internalFunc() { ... } // should not be exported
+
     void internalFunc() { ... } // should not be exported
  
 
   protected:  
 
   protected:  
     void interalFunc2() { ... } // should be exported
+
     void interalFunc2() { ... } // should be exported
  
 
   public:  
 
   public:  
     class Inner // compiler internal members should be exported
+
     class Inner                 // compiler internal members should be exported
 
     {  
 
     {  
       static s_inner; // should be exported
+
       __gshared int s_inner;           // should be exported
  
 
       void innerMethod() { ... } // should be exported  
 
       void innerMethod() { ... } // should be exported  
 
     }
 
     }
  
     void method() { ... } // should be exported  
+
     void method() { ... }       // should be exported  
 
}
 
}
 
private class C // should not be exported
 
{
 
  public void method() {... } // should not be exported
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 148: Line 225:
  
 
Currently it is not possible to access TLS variables across shared
 
Currently it is not possible to access TLS variables across shared
library boundaries on Windows. This should be implemented (see
+
library boundaries on Windows. This might be implemented in the future. (see
 
[[#TLS_variables|implementation details]] for a proposal).
 
[[#TLS_variables|implementation details]] for a proposal).
  
Line 165: Line 242:
 
dependencies making it harder to maintain libraries.
 
dependencies making it harder to maintain libraries.
  
===Changes to -lib dmd flag===
+
Furthermore, hiding functions by default enables much more aggressive compiler optimizations, to the benefit of both executable performance and code size. Some examples for this are elision of completely inlined functions, optimization of function signatures/calling conventions, partial inlining/constant propagation, … Some of these optimization opportunities also positively affect compile times, as evidenced by an experimental LDC patch (see [https://github.com/ldc-developers/ldc/pull/483 LDC #483], although LTO is required to fully exploit this).
 
 
When creating shared libraries, library creators usually want to commit to a certain interface. Strictly speaking this interface consits of every exported symbol from the library.
 
If other third party libraries are linked statically into the shared library beeing created, the exported symbols from that third party library should not end up in the final
 
shared library because they would become part of the interface. To accomplish this, the described changes to the -lib flag are necessary. The only exception, where this behaviour is actually wanted, is linking druntime into phobos. This case is special because phobos and druntime are merged into one big library. For this case the -libexports flag will be added.
 
  
 
==Implementation Details==
 
==Implementation Details==
Line 176: Line 249:
  
 
==== Data Symbols ====
 
==== Data Symbols ====
 +
 +
===== Accessing through code =====
  
 
For data symbols the 'export' attribute always means 'dllexport' when
 
For data symbols the 'export' attribute always means 'dllexport' when
Line 212: Line 287:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
===== Referencing in constant data ======
 +
 +
When referencing data symbols in the contents of some other data symbol there will be a additional level of indirection which needs to be removed during program startup.
 +
 +
<syntaxhighlight lang=D>
 +
module dll;
 +
 +
__gshared int var = 5;
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang=D>
 +
module exe;
 +
import dll;
 +
 +
__gshared int* pvar = &var; // address not known at compile time
 +
</syntaxhighlight>
 +
 +
As the address of var is not known at compile time so pvar will point to the entry in the import table for 'var'. At program startup, before any other D code runs, pvar will be dereferenced once. E.g. the following operation will be executed on pvar.
 +
 +
<syntaxhighlight lang=D>
 +
pvar = *cast(int**)pvar;
 +
</syntaxhighlight>
 +
 +
This removes the additional indirection added by the import table and correctly initializes the static memory for pvar. This might happen in various other cases, mostly when generating initializers, type infos, vtables, module infos and other static data the compiler produces.
  
 
==== Function Symbols ====
 
==== Function Symbols ====
  
 
For function symbols the 'export' attribute always means 'dllexport'
 
For function symbols the 'export' attribute always means 'dllexport'
when defining a function and 'dllimport' when calling a function.
+
when defining a function and will be ignored when calling it.
 
Calling an exported function is always done through the original symbol.
 
Calling an exported function is always done through the original symbol.
In an import library the original symbol is redifined as trampoline that simply
+
In an import library the original symbol is defined as trampoline that simply calls the
dereferences the _imp_ pointer to the DLL function.
+
dereferenced _imp_ pointer.
 
Thus calling an exported function will be compatible with both import
 
Thus calling an exported function will be compatible with both import
 
libraries and static libraries, in the later case without indirection.
 
libraries and static libraries, in the later case without indirection.
Line 259: Line 359:
  
 
==== TLS variables ====
 
==== TLS variables ====
 +
 +
Note: This is not implemented at the moment.
  
 
For each exported TLS variable the compiler should generate a function
 
For each exported TLS variable the compiler should generate a function
Line 300: Line 402:
  
 
=== *nix ===
 
=== *nix ===
 +
 +
Note: This is not yet implemented.
  
 
On *nix systems the default symbols visibility should be changed to
 
On *nix systems the default symbols visibility should be changed to
 
hidden, i.e. -fvisibility=hidden argument of gcc.  Only symbols marked
 
hidden, i.e. -fvisibility=hidden argument of gcc.  Only symbols marked
 
with '''export''' should get the attribute visible.
 
with '''export''' should get the attribute visible.
 +
 +
This is trivial to implement on GDC and LDC.
 +
 +
=== Linking archives into shared libraries ===
 +
 +
When linking archives into shared libraries the exported symbols from the archive will also be exported from the resulting shared library. Most often this is unwanted and may lead to inadvertent ABI dependencies.
 +
To accomplish this we'll provide a tool that strips export attributes from archives.
  
 
== Copyright ==
 
== Copyright ==

Latest revision as of 12:39, 31 January 2017

Title: making export an attribute
DIP: 45
Version: 3
Status: Draft
Created: 2013-08-27
Last Modified: 2017-01-30
Author: Benjamin Thaut, Martin Nowak, David Nadlinger
Links:

DConf 2016 Talk: https://www.youtube.com/watch?v=MQRHxI2SrYM

Abstract

Export and its behavior need to be changed in serveral ways to make it work on Windows and allow better code generation for other plattforms. The Rationale section explains the problems and shows how this DIP solves them.

Description

  • The export protection level should be turned into a export attribute.
  • export might appear in front of module to indicate that the implementation specific module symbols should be exported.
  • If a class is annotated with the 'export' attribute, all of its public and protected functions and members will automatically recieve the 'export' attribute. Also all its hidden compiler specific symbols will recieve the 'export' attribute.
  • There should be only one meaning of 'export'.
  • On *nix systems default symbol visibility is changed to hidden, and only symbols marked with export become visible.

Rationale

Turning export into an attribute

Currently export is a protection level, the highest level of visibility actually. This however conflicts with the need to export 'protected' and 'private' symbols. Consider a Base class in a shared library.

module sharedLib;

class Base { 
  protected final void doSomething() { ... } 
}


module executable;
import sharedLib;

class Derived : Base { 
  public void func() 
  { 
    doSomething(); 
  } 
}

In the above example 'doSomething' should only be visible to derived classes but it still needs to be exportable from a shared library. Therefor export should become a normal attribute which behaves orthogonal to protection.

Also consider the following example in which the template will access a private function. Because the template is instanced on the user side and not within the shared library it is required to export the private function so that the template can access it from outside the shared library.

module dll;

void copy(T)(T val)
{
  copyImpl(&val, T.sizeof);
}

export private copyImpl(void* mem, size_t size)
{
  ...
}
module exe;
import dll;

void main(string[] args)
{
  int bar = 0;
  copy(bar); // template will be instanciated in the exe but needs access to the copyImpl function.
}

Another special case are voldemord types. If a voldemord type is used it needs to be exported explictily.

module lib;

export auto makeSomething(int v)
{
  export struct Something
  {
    int i;
  }

  return Something(v);
}

At first glance exporting a template doesn't make much sense. But consider the following example:

module lib;
import std.stdio;

export struct Foo(T)
{
  T value;
  void print() { writefln("%s", value); }
}

__gshared Foo!int g_inst = Foo!int(5);
module exe;
import lib;

void main(string[] args)
{
  auto f = Foo!int(5);
  f.print();
}

When compiling the executable module exe which uses the module lib compiled into a dll the compiler will attempt to reuse the instance of Foo!int from the lib module. This however only works if the instance has been exported from the dll. As a result exporting a tempalte should be equivalent to exporting any instance created from this template. In code:

export struct(T) { ... }

// is equivalent to
template (T)
{
  export struct { ... }
}

Exporting module compiler internal symbols

Each D module has a set of compiler internal symbols which may be referenced. To allow for exporting these symbols export is allowed in front of module to indicate that the compiler internal symbols should be exported. Example:

 export module sharedLib;

void testSomething(T)(T val)
{
    assert(T.sizeof > 8);
}

Please note that in the above example there is not a single member of the module marked with export. But still it is required to export the compiler internal module symbols as the template will be instanciated on the user side of the shared library and thus will access the 'assert' module symbol. If the 'assert' module symbol is not exported this would lead to a linker error. The compiler internal module symbols should not be exported by default. Consider building a D-Dll with a pure C interface. In this case you don't want to export any compiler internal symbols as you want to have a very well defined C interface of your dll.

export attribute inference

Currently export has to be specified in a lot of places to export all neccessary functions and data symbols. Export should be transitive for aggregate types (structs/classes) so that when exporting a aggregate type export is applied to all public & protected members without the need to add export to every single public and protected member.

module sharedLib;

export class A                          // compiler internal members should be exported (e.g. vtable, type info)
{ 
  private: 
    int m_a;

    static int s_b;              // should not be exported

    void internalFunc() { ... }  // should not be exported

  protected: 
    void interalFunc2() { ... }  // should be exported

  public: 
    class Inner                  // compiler internal members should be exported
    { 
      __gshared int s_inner;            // should be exported

      void innerMethod() { ... } // should be exported 
    }

    void method() { ... }        // should be exported 
}

A single meaning of export

The classical solution to handle dllexport/dllimport attributes on Windows is to define a macro that depending on the current build setting expands to __declspec(dllexport) or to __declspec(dllimport). This complicates the build setup and means that object files for a static library can't be mixed well with object files for a DLL. Instead we propose that exported data definitions are accompanied with an _imp_ pointer and always accessed through them. See the implementation detail section for how this will work for data symbols and function symbols. That way a compiled object file can be used for a DLL or a static library. And vice versa an object file can be linked against an import library or a static library.

Access TLS variables

Currently it is not possible to access TLS variables across shared library boundaries on Windows. This might be implemented in the future. (see implementation details for a proposal).

Change symbol visibility on *nix systems

When building shared libraries on *nix systems all symbols are visible by default. This is a main reason for the performance impact of PIC because every data access and every function call go through the GOT or PLT indirection. It also leads to long loading time because an excessive number of relocations have to be processed. Making all symbols hidden by default significantly reduces the size of the dynamic symbol table (faster lookup and smaller libraries). See http://gcc.gnu.org/wiki/Visibility and http://people.redhat.com/drepper/dsohowto.pdf for more details.

Also making every symbol accessible can inadvertently cause ABI dependencies making it harder to maintain libraries.

Furthermore, hiding functions by default enables much more aggressive compiler optimizations, to the benefit of both executable performance and code size. Some examples for this are elision of completely inlined functions, optimization of function signatures/calling conventions, partial inlining/constant propagation, … Some of these optimization opportunities also positively affect compile times, as evidenced by an experimental LDC patch (see LDC #483, although LTO is required to fully exploit this).

Implementation Details

Windows

Data Symbols

Accessing through code

For data symbols the 'export' attribute always means 'dllexport' when defining a symbol and 'dllimport' when accessing a symbol. That is accessing an exported variable is done through dereferencing it's corresponding import symbols. When defining an exported variable the compiler will emit a corresponding import symbol that is initialized with address of the variable. The import symbol can be located in the read only data segment. The mangling of the import symbol consists of the '_imp_'/'__imp_' (Win32/Win64) prefix followed by the mangled name of the variable. Import symbols itself are not exported. When an exported variable of the same module is accessed the compiler might avoid the indirection and perform a direct access.

module a;

export __gshared int var = 5;
__gshared int* _imp__D1a3vari = &var; // import symbol generated by the compiler

void func()
{
   var = 3; // accesses var directly, because in the same module
}


module b;
import a;

void bar()
{
    var = 5; // accesses through indirection because var is marked as export and in a different module
    // *_imp__D1a3vari = 5; // code generated by the compiler
}
Referencing in constant data =

When referencing data symbols in the contents of some other data symbol there will be a additional level of indirection which needs to be removed during program startup.

module dll;

__gshared int var = 5;
module exe;
import dll;

__gshared int* pvar = &var; // address not known at compile time

As the address of var is not known at compile time so pvar will point to the entry in the import table for 'var'. At program startup, before any other D code runs, pvar will be dereferenced once. E.g. the following operation will be executed on pvar.

pvar = *cast(int**)pvar;

This removes the additional indirection added by the import table and correctly initializes the static memory for pvar. This might happen in various other cases, mostly when generating initializers, type infos, vtables, module infos and other static data the compiler produces.

Function Symbols

For function symbols the 'export' attribute always means 'dllexport' when defining a function and will be ignored when calling it. Calling an exported function is always done through the original symbol. In an import library the original symbol is defined as trampoline that simply calls the dereferenced _imp_ pointer. Thus calling an exported function will be compatible with both import libraries and static libraries, in the later case without indirection.

module a;

export void func()
{
}

void bar()
{
    func(); // call func; // directly
}


module b;
import a;

void bar()
{
    func(); // call func; // through trampoline
}

// definitions in the import library generated by implib
void func()
{
    asm
    {
        naked;
        jmp [_imp_func];
    }
}
void function() _imp_func = &func; // filled at runtime with the DLL address of func

TLS variables

Note: This is not implemented at the moment.

For each exported TLS variable the compiler should generate a function that returns the address of the TLS variable in the current thread. These internal methods should have some kind of unified prefix to mark them as TLS import helpers. I propose "__tlsstub_". These internal methods are also exported. So when accessing an exported TLS variable the compiler will insert a call to '_imp__D1a15__tlsstub_g_tlsFZPi' instead. As an optimization accesses to exported TLS variables within the same module can be performed directly.

module a;

export int g_tls = 5; // thread local storage

export int* __tlsstub__g_tls() // generated by the compiler
{
    return &g_tls;
}
alias _imp___tlsstub__g_tls = __tlsstub__g_tls; // also generated by the compiler

void func()
{
    g_tls = 3; // direct access because marked as export and in the same module
}


module b;
import a;

void bar()
{
    g_tls = 10; // access through _imp___tlsstub__g_tls function because marked as export and in a different module
    // *_imp___tlsstub__g_tls() = 10; // code generated by the compiler
}

*nix

Note: This is not yet implemented.

On *nix systems the default symbols visibility should be changed to hidden, i.e. -fvisibility=hidden argument of gcc. Only symbols marked with export should get the attribute visible.

This is trivial to implement on GDC and LDC.

Linking archives into shared libraries

When linking archives into shared libraries the exported symbols from the archive will also be exported from the resulting shared library. Most often this is unwanted and may lead to inadvertent ABI dependencies. To accomplish this we'll provide a tool that strips export attributes from archives.

Copyright

This document has been placed in the Public Domain.