DIP45

From D Wiki
Revision as of 19:39, 1 September 2013 by Ingrater (talk | contribs)
Jump to: navigation, search
Title: making export an attribute
DIP: 45
Version: 1
Status: Draft
Created: 2013-08-27
Last Modified: 2013-09-01
Author: Benjamin Thaut
Links:

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 problem and shows how this DIP solves it.

Description

  • 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).
  • 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.
  • 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 with export marked symbols are visible.

Rationale

Turning export into an attribute

Currently the export protection level is the highest level of visibility. This however does not allow exporting protected members. Exporting protected members is neccessary sometimes though. Consider the following example.

module sharedLib;

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

  public void func()
  {
    DoSomething();
  }
}


module executable;
import sharedLib;

class Derived
{
  protected final void DoSomethingAdditional() { ... }

  public ovveride void func()
  {
    DoSomething();
    DoSomethingAdditional();
  }
}

In the above example 'DoSomething' should only be visible to derived classes but should still be exported from a shared library otherwise it will lead to a linker error. This does not work with the current implementation of 'export'. Turning 'export' into an attribute will make this work.

Implicitly exporting compiler internal symbols of a module

Currently compiler internal symbols of modules are not exported at all. This leads to linker errors when linking against shared libraries on windows. By marking compiler internal symbols as 'export' as soon as there is a single exported symbol in the module will fix this problem.

export attribute inferrence

Currently export has to be specified in a lot of places to export all 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 export all of its functions / data members including classes and their members / data symbols. Consider the following example:

module sharedLib:

export:

__gshared int g_Var; // should be exported

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

class A // compiler internal members should be exported
{
  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
    {
       static s_inner; // should be exported

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

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

private class C // should not be exported
{
   public void method() { ... } // should not be exported
}

Access TLS variables

Currently it is not possible to access TLS variables across shared library boundaries. This should be implemented. See implementation details.

Change symbol visibility on *nix systems

When building shared libraries on *nix systems all symbols are visible by default. This can lead to long loading time of shared libraries if they contain a large number of symbols. Also everything visible by deafult makes it hard to commit to a certain stable interface, as one has no control over the visible symbols.

Implementation Details

Windows

Data Symbols

For data symbols the 'export' attribute always means 'dllexport' when compiling a module and always 'dllimport' when importing a module. When compiling a module the compiler will generate a import symbol containing the address of the data symbol for each data symbol it encounters. The mangling of the import symbol should be the same as that of the symbol it referes to with a additional prefix. This prefix is '_imp_' on windows 32-bit and '__imp_' on windows 64-bit. So if the data symbol's mangling is '_D5ivar' the import symbol mangling is '_imp__D5ivar' on 32-bit and '__imp__D5ivar' on 64-bit. The import symbol should NOT be expoted to avoid a conflict with the import symbols which are generated within the import library by the linker.

module a;
export __gshared int var = 5;
__gshared int* _imp_var = &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 var through the _imp_var indirection because var is marked as export and is located in a different module
  // *_imp_var = 5; // code generated by the compiler
}

When accessing data symbols which are marked with 'export' the access should always be done through the additional level of indirection using the import symbol unless the data symbol is located within the same module currently beeing compiled. Access through data symbols not marked with 'export' does not change in any way.

Function Symbols

For function symbols the 'export' attribute always means 'dllexport' when compiling a module and 'export' always equals a no-op when importing a module. Because the import library will generate method stubs with the correct symbol names function symbols can be called normally no matter if they are imported from a shared library or linked in from a static library.

TLS variables

On windows plattforms the compiler should generate a internal method for each TLS variable returing the addess of the TLS variable. These internal methods should have some kind of unified prefix to mark them as TLS import helpers. I propose "__access_tls_". These internal methods should also be exported from a shared library implicitly.

module a;
export int g_tls = 5; // thread local storage

export int* __access_tls_g_tls() // generated by the compiler
{
  return &g_tls;
}

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 __access_tls_g_tls function because marked as export and in a imported module
  // *__access_tls_g_tls() = 10; // code generated by the compiler
}

When accessing a TLS variable marked with export and located within the same module currently compiled direct access should be done. When accessing a TLS variable marked with export from a imported module the access should be going through a additional indirection using __access_tls_ helper function.

*nix

On *nix systems the default symbols visibility needs to be changed to hidden and only symbols marked with 'export' should be visible when compiling a shared library.

Copyright

This document has been placed in the Public Domain.