dblaikie added inline comments.

================
Comment at: clang/lib/CodeGen/CGDebugInfo.cpp:2292-2300
+  bool hasCtor = false;
+  for (const auto *Ctor : RD->ctors()) {
     if (Ctor->isTrivial() && !Ctor->isCopyOrMoveConstructor())
       return false;
+    if (!Ctor->isCopyOrMoveConstructor())
+      hasCtor = true;
+  }
----------------
akhuang wrote:
> rsmith wrote:
> > dblaikie wrote:
> > > rsmith wrote:
> > > > This looks pretty similar to:
> > > > 
> > > > ```
> > > > return RD->hasUserDeclaredConstructor() && 
> > > > !RD->hasTrivialDefaultConstructor();
> > > > ```
> > > > 
> > > > (other than its treatment of user-declared copy or move constructors), 
> > > > but I have to admit I don't really understand what the "at least one 
> > > > constructor and no trivial or constexpr constructors" rule aims to 
> > > > achieve, so it's hard to know if this is the right interpretation. The 
> > > > rule as written in the comment above is presumably not exactly right -- 
> > > > all classes have at least one constructor, and we're not excluding 
> > > > classes with trivial copy or move constructors, only those with trivial 
> > > > default constructors.
> > > > 
> > > > I wonder if the intent would be better captured by "at least one 
> > > > non-inline constructor" (that is, assuming all declared functions are 
> > > > defined, there is at least one translation unit in which a constructor 
> > > > is defined that can be used as a home for the class info).
> > > So the general goal is to detect any type where the construction of that 
> > > type somewhere must invoke a constructor that will be IR-generated.
> > > 
> > > Move and copy constructors are ignored because the assumption is they 
> > > must be moving/copying from some other object, which must've been 
> > > constructed, ultimately, by a non-move/copy constructor.
> > > 
> > > Ideally this would be usable even for inline ctors - even if the ctor 
> > > calls get optimized away later[^1], they'd still allow us to reduce the 
> > > number of places the type is emitted to only those places that call the 
> > > ctor.
> > > 
> > > 
> > > 
> > > [^1] actually, the way that should work doesn't seem to be working right 
> > > now (eg:
> > > type.cpp
> > > ```
> > > struct t1 { t1() { } };
> > > void f1(void*);
> > > int main() {
> > >   f1(new t1());
> > > }
> > > ```
> > > type2.cpp
> > > ```
> > > struct t1 { t1() { } };
> > > void f1(void* v) {
> > >   delete (t1*)v;
> > > }
> > > ```
> > > build: `clang++ type.cpp -g -Xclang -fuse-ctor-homing type2.cpp && 
> > > llvm-dwarfdump a.out`
> > > -> definition of "t1" in the DWARF
> > > build with optimizations: `clang++ -O3 type.cpp -g -Xclang 
> > > -fuse-ctor-homing type2.cpp && llvm-dwarfdump a.out`
> > > -> missing definition of "t1"
> > > `type.cpp` is chosen as a home for `t1` because it calls a user-defined 
> > > ctor, but then that ctor gets optimized away and there's no other mention 
> > > of `t1` in `type.cpp` so the type is dropped entirely. This could happen 
> > > even with a non-inline definition - under LTO the ctor could get 
> > > optimized away (though this would be safe in FullLTO - the other 
> > > references to `t1` would be made to refer to the definition and keep it 
> > > alive - but in ThinLTO the TU defining the ctor might be imported and 
> > > nothing else - leaving the type unused/dropped)
> > > To fix this we should put 'homed' types in the retained types list so 
> > > they are preserved even if all other code/references to the type are 
> > > dropped. I think I implemented this homed type pinning for explicit 
> > > template specialization definitions, because they have no code attachment 
> > > point, so similar logic could be used for ctor homing. (vtable homing 
> > > /might/ benefit from this with aggressive/whole program devirtualization? 
> > > Not sure - harder to actually optimize away all the references to a type, 
> > > but possible maybe?)
> > Oh, I see, that's clever and very neat.
> > 
> > So the intent is to identify types for which we know that either no 
> > instances are ever created, or some constructor must be actually emitted in 
> > some translation unit (prior to optimizations running). And that's why we 
> > want to ignore copy and move constructors [and could in fact ignore any 
> > constructor taking the class type by value or reference, directly or 
> > indirectly] (they cannot be the only place that creates an object) and also 
> > why we don't want to do this if there's a constexpr constructor or trivial 
> > default constructor (we might not generate code for them). And the "no 
> > constructors" check seems to effectively be checking whether aggregate 
> > initialization could be performed.
> > 
> > I think we can replace the loop with `return !RD->isAggregate() && 
> > !RD->hasTrivialDefaultConstructor();`.
> > 
> > (Minor aside, it is possible to create an instance of a type if its only 
> > constructor is a copy constructor, eg with `struct A { A(const A&) {} } a = 
> > a;`, but I doubt that's a practical concern for your purposes.)
> > So the intent is to identify types for which we know that either no 
> > instances are ever created, or some constructor must be actually emitted in 
> > some translation unit (prior to optimizations running). And that's why we 
> > want to ignore copy and move constructors [and could in fact ignore any 
> > constructor taking the class type by value or reference, directly or 
> > indirectly] (they cannot be the only place that creates an object) and also 
> > why we don't want to do this if there's a constexpr constructor or trivial 
> > default constructor (we might not generate code for them). And the "no 
> > constructors" check seems to effectively be checking whether aggregate 
> > initialization could be performed.
> 
> > I think we can replace the loop with return !RD->isAggregate() && 
> > !RD->hasTrivialDefaultConstructor();.
> Ok, I think that makes sense - I guess the loop was sort of attempting to do 
> what `RD->hasTrivialDefaultConstructor()`does. 
> 
> > To fix this we should put 'homed' types in the retained types list so they 
> > are preserved even if all other code/references to the type are dropped.
> Yeah, think you mentioned this before and then I never added it--I'll make a 
> separate patch for it. 
>  And that's why we want to ignore copy and move constructors [and could in 
> fact ignore any constructor taking the class type by value or reference, 
> directly or indirectly] (they cannot be the only place that creates an 
> object) 

Yeah, fair point - that'd be a nice generalization. (indirectly, you mean, for 
instance - ctor of A takes a reference to B that contains an A member?).

> I think we can replace the loop with return `!RD->isAggregate() && 
> !RD->hasTrivialDefaultConstructor();`.

OK - so any type with a user-defined ctor would be a non-aggregate, we don't 
care about the copy/move ctors so the only possibly trivial ctor that could 
sneak through would be the default one. Hmm - what cases would we miss by 
/only/ testing that the type doesn't have a trivial default ctor? Ah, right, 
the stuff we've just been discussing where the ctor isn't instantiated... and 
the !aggregate test will cover us there? OK, think I'm with it & really 
appreciate the nuance.

> (Minor aside, it is possible to create an instance of a type if its only 
> constructor is a copy constructor, eg with struct A { A(const A&) {} } a = 
> a;, but I doubt that's a practical concern for your purposes.)

Yeah, I take it that's valid/not UB? Figured there was that tiny case & yeah, 
I'm willing to let that slide/through, really.


================
Comment at: clang/test/CodeGenCXX/debug-info-limited-ctor.cpp:36-56
+
+// CHECK-DAG: ![[G:.*]] ={{.*}}!DICompositeType({{.*}}name: 
"G"{{.*}}DIFlagTypePassByValue
+// CHECK-DAG: !DICompositeType({{.*}}scope: ![[G]], {{.*}}DIFlagTypePassByValue
+struct G {
+  G() : g_(0) {}
+  struct {
+    int g_;
----------------
Probably good to have a comment on each of these tests to clarify their purpose.


================
Comment at: clang/test/CodeGenCXX/debug-info-limited-ctor.cpp:46-50
+// CHECK-DAG: !DICompositeType({{.*}}name: "H",{{.*}}DIFlagTypePassByValue
+struct H {
+  B b;
+};
+void f(H h) {}
----------------
This is to test an implicit, non-trivial default ctor of H?


================
Comment at: clang/test/CodeGenCXX/debug-info-limited-ctor.cpp:52-56
+// CHECK-DAG: !DICompositeType({{.*}}name: "I",{{.*}}DIFlagTypePassByValue
+struct I {
+  B b;
+};
+void f(decltype(I()) i) {}
----------------
Looks like another implicit non-trivial ctor? Oh, I guess the previous case is 
an implicit non-trivial default ctor that isn't instantiated/built? (so the 
implementation doesn't observe any default ctor in 'I' at all?)


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D87808/new/

https://reviews.llvm.org/D87808

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

Reply via email to