takuto.ikuta added a comment.

In https://reviews.llvm.org/D51340#1278799, @hans wrote:

> I've been thinking more about your example with static locals in lambda and 
> how this works with regular dllexport.
>
> It got me thinking more about the problem of exposing inline functions from a 
> library. For example:
>
> `lib.h`:
>
>   #ifndef LIB_H
>   #define LIB_H
>  
>   int foo();
>  
>   struct __declspec(dllimport) S {
>     int bar() { return foo(); }
>   };
>  
>   #endif
>  
>
>
> `lib.cc`:
>
>   #include "lib.h"
>  
>   int foo() { return 123; }
>
>
> `main.cc`:
>
>   #include "lib.h"
>  
>   int main() {
>     S s;
>     return s.bar();
>   }
>
>
> Here, Clang will not emit the body of `S::bar()`, because it references the 
> non-dllimport function `foo()`, and trying to referencing `foo()` outside the 
> library would result in a link error. This is what the 
> `DLLImportFunctionVisitor` is used for. For the same reason, MSVC will also 
> not inline dllimport functions.
>
> Now, with `/Zc:dllexportInlines-`, we no longer mark `S::bar()` dllimport, 
> and so we do emit it, causing that link error. The same problem happens with 
> `-fvisibility-inlines-hidden`: substitute the `declspec` above for 
> `__attribute__((visibility("default")))` above and try it:
>
>   $ g++ -fvisibility=hidden -fvisibility-inlines-hidden -fpic -shared -o 
> lib.so lib.cc
>   $ g++ main.cc lib.so
>   /tmp/cc557J3i.o: In function `S::bar()':
>   main.cc:(.text._ZN1S3barEv[_ZN1S3barEv]+0xd): undefined reference to `foo()'
>
>
> So this is a known issue with `-fvisibility-inlines-hidden`. This doesn't 
> come up often in practice, but when it does the developer needs to deal with 
> it.


Yeah, that is the reason of few chromium code changes.
https://chromium-review.googlesource.com/c/chromium/src/+/1212379

> However, the static local problem is much scarier, because that leads to 
> run-time bugs:
> 
> `lib.h`:
> 
>   #ifndef LIB_H
>   #define LIB_H
>   
>   inline int foo() { static int x = 0; return x++; }
>   
>   struct __attribute__((visibility("default"))) S {
>     int bar() { return foo(); }
>     int baz();
>   };
>   
>   #endif
> 
> 
> `lib.cc`:
> 
>   #include "lib.h"
>   
>   int S::baz() { return foo(); }
> 
> 
> `main.cc`:
> 
>   #include <stdio.h>
>   #include "lib.h"
>   
>   int main() {
>     S s;
>     printf("s.bar(): %d\n", s.bar());
>     printf("s.baz(): %d\n", s.baz());
>     return 0;
>   }
> 
> 
> If we build the program normally, we get the expected output:
> 
>   $ g++ lib.cc main.cc && ./a.out
>   s.bar(): 0
>   s.baz(): 1
> 
> 
> but building as a shared library shows the problem:
> 
>   $ g++ -fvisibility=hidden -fvisibility-inlines-hidden -fpic -shared -o 
> lib.so lib.cc
>   $ g++ main.cc lib.so
>   $ LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ./a.out
>   s.bar(): 0
>   s.baz(): 0
> 
> 
> Oops.
> 
> This does show that it's a pre-existing problem with the model of not 
> exporting inline functions though. I don't think we need to solve this 
> specifically for Windows, I think we should match what 
> `-fvisibility-inlines-hidden` is doing.

Currently this CL doesn't take care of inline function that is not member of a 
class.

`lib.h`:

  #ifndef LIB_H
  #define LIB_H
  
  struct __attribute__((visibility("default"))) S {
    int bar() {
      static int x = 0; return x++;
    }
    int baz();
  };
  
  #endif

`lib.cc`:

  #include "lib.h"
  
  int S::baz() {
    return bar();
  }

Then, static local in inline function is treated correctly.

  $ g++ -fvisibility=hidden -fvisibility-inlines-hidden -fpic -shared -o lib.so 
lib.cc
  $ g++ main.cc lib.so
  $ LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ./a.out
  s.bar(): 0
  s.baz(): 1

This is the same behavior with `/Zc:dllexportInlines-`.



================
Comment at: clang/lib/Sema/SemaDeclCXX.cpp:5719
+          TSK != TSK_ExplicitInstantiationDeclaration &&
+          TSK != TSK_ExplicitInstantiationDefinition) {
+        if (ClassExported) {
----------------
hans wrote:
> takuto.ikuta wrote:
> > takuto.ikuta wrote:
> > > takuto.ikuta wrote:
> > > > hans wrote:
> > > > > takuto.ikuta wrote:
> > > > > > hans wrote:
> > > > > > > I still don't understand why we need these checks for template 
> > > > > > > instantiations. Why does it matter whether the functions get 
> > > > > > > inlined or not?
> > > > > > When function of dllimported class is not inlined, such funtion 
> > > > > > needs to be dllexported from implementation library.
> > > > > > 
> > > > > > c.h
> > > > > > ```
> > > > > > template<typename T>
> > > > > > class EXPORT C {
> > > > > >  public:
> > > > > >   void f() {}
> > > > > > };
> > > > > > ```
> > > > > > 
> > > > > > cuser.cc
> > > > > > ```
> > > > > > #include "c.h"
> > > > > > 
> > > > > > void cuser() {
> > > > > >   C<int> c;
> > > > > >   c.f();  // This function may not be inlined when EXPORT is 
> > > > > > __declspec(dllimport), so link may fail.
> > > > > > }
> > > > > > ```
> > > > > > 
> > > > > > When cuser.cc and c.h are built to different library, cuser.cc 
> > > > > > needs to be able to see dllexported C::f() when C::f() is not 
> > > > > > inlined.
> > > > > > This is my understanding.
> > > > > Your example doesn't use explicit instantiation definition or 
> > > > > declaration, so doesn't apply here I think.
> > > > > 
> > > > > As long as the dllexport and dllimport attributes matches it's fine. 
> > > > > It doesn't matter whether the function gets inlined or not, the only 
> > > > > thing that matters is that if it's marked dllimport on the "consumer 
> > > > > side", it must be dllexport when building the dll.
> > > > Hmm, I changed the linkage for functions having 
> > > > DLLExport/ImportStaticLocal Attr.
> > > > 
> > > I changed linkage in ASTContext so that inline function is emitted when 
> > > it is necessary when we use fno-dllexport-inlines.
> > I revived explicit template instantiation check. 
> > Found that this is necessary.
> > 
> > For explicit template instantiation, inheriting dll attribute from function 
> > for local static var is run before inheriting dll attribute from class for 
> > member functions. So I cannot detect local static var of inline function 
> > after class level dll attribute processing for explicit template 
> > instantiation.
> > 
> Oh I see, it's a static local problem..
> Can you provide a concrete example that does not work without your check?
> Maybe this is the right thing to do, but I would like to understand exactly 
> what the problem is.
For the following code
```
template<typename T>
class M{
public:

  T Inclass() {
    static T static_x;
    ++static_x;
    return static_x;
  }
};

template class __declspec(dllexport) M<int>;

extern template class __declspec(dllimport) M<short>;

int f (){
  M<int> mi;
  M<short> ms;
  return mi.Inclass() + ms.Inclass();
}

```

llvm code without instantiation check become like below. Both inline functions 
of M<int> and M<short> is not dllimported/exported.
```
$"?Inclass@?$M@H@@QEAAHXZ" = comdat any

$"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA" = comdat any

@"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA" = linkonce_odr dso_local global i32 
0, comdat, align 4

; Function Attrs: noinline nounwind optnone
define weak_odr dso_local i32 @"?Inclass@?$M@H@@QEAAHXZ"(%class.M* %this) #0 
comdat align 2 {
entry:
  %this.addr = alloca %class.M*, align 8
  store %class.M* %this, %class.M** %this.addr, align 8
  %this1 = load %class.M*, %class.M** %this.addr, align 8
  %0 = load i32, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  %inc = add nsw i32 %0, 1
  store i32 %inc, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  %1 = load i32, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  ret i32 %1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @"?f@@YAHXZ"() #0 {
entry:
  %mi = alloca %class.M, align 1
  %ms = alloca %class.M.0, align 1
  %call = call i32 @"?Inclass@?$M@H@@QEAAHXZ"(%class.M* %mi)
  %call1 = call i16 @"?Inclass@?$M@F@@QEAAFXZ"(%class.M.0* %ms)
  %conv = sext i16 %call1 to i32
  %add = add nsw i32 %call, %conv
  ret i32 %add
}

declare dso_local i16 @"?Inclass@?$M@F@@QEAAFXZ"(%class.M.0*) #1
```


With the check, both functions are dllimported/exported and static local vars 
will be treated correctly.
```
$"??4?$M@H@@QEAAAEAV0@AEBV0@@Z" = comdat any

$"?Inclass@?$M@H@@QEAAHXZ" = comdat any

$"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA" = comdat any

@"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA" = linkonce_odr dso_local global i32 
0, comdat, align 4

; Function Attrs: noinline nounwind optnone
define weak_odr dso_local dllexport dereferenceable(1) %class.M* 
@"??4?$M@H@@QEAAAEAV0@AEBV0@@Z"(%class.M* %this, %class.M* dereferenceable(1)) 
#0 comdat align 2 {
entry:
  %.addr = alloca %class.M*, align 8
  %this.addr = alloca %class.M*, align 8
  store %class.M* %0, %class.M** %.addr, align 8
  store %class.M* %this, %class.M** %this.addr, align 8
  %this1 = load %class.M*, %class.M** %this.addr, align 8
  ret %class.M* %this1
}

; Function Attrs: noinline nounwind optnone
define weak_odr dso_local dllexport i32 @"?Inclass@?$M@H@@QEAAHXZ"(%class.M* 
%this) #0 comdat align 2 {
entry:
  %this.addr = alloca %class.M*, align 8
  store %class.M* %this, %class.M** %this.addr, align 8
  %this1 = load %class.M*, %class.M** %this.addr, align 8
  %0 = load i32, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  %inc = add nsw i32 %0, 1
  store i32 %inc, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  %1 = load i32, i32* @"?static_x@?2??Inclass@?$M@H@@QEAAHXZ@4HA", align 4
  ret i32 %1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @"?f@@YAHXZ"() #0 {
entry:
  %mi = alloca %class.M, align 1
  %ms = alloca %class.M.0, align 1
  %call = call i32 @"?Inclass@?$M@H@@QEAAHXZ"(%class.M* %mi)
  %call1 = call i16 @"?Inclass@?$M@F@@QEAAFXZ"(%class.M.0* %ms)
  %conv = sext i16 %call1 to i32
  %add = add nsw i32 %call, %conv
  ret i32 %add
}

declare dllimport i16 @"?Inclass@?$M@F@@QEAAFXZ"(%class.M.0*) #1
```


https://reviews.llvm.org/D51340



_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to