ilya-biryukov added inline comments.

================
Comment at: clang/include/clang/AST/ExprCXX.h:4824
+  const Expr *getArrayFiller() const {
+    return const_cast<CXXParenListInitExpr *>(this)->getArrayFiller();
+  }
----------------
NIT: maybe just use `return ArrayFiller`? The code is much simpler and since 
functions are very close it's pretty much impossible that anyone would refactor 
one of those without looking at the other.


================
Comment at: clang/lib/AST/ExprConstant.cpp:10000
     ImplicitValueInitExpr VIE(Field->getType());
-    const Expr *InitExpr = E->getNumInits() ? E->getInit(0) : &VIE;
+    const Expr *InitExpr = Args.size() ? Args[0] : &VIE;
 
----------------
NIT: `!Args.empty() ? Args[0] : &VIE`
`Args.size()` also works, but arguably captures the intent of the code a bit 
less clearly.


================
Comment at: clang/lib/CodeGen/CGExprAgg.cpp:581
+  Expr *filler = nullptr;
+  if (auto *ILE = dyn_cast<InitListExpr>(ExprToVisit))
+    filler = ILE->getArrayFiller();
----------------
ayzhao wrote:
> ayzhao wrote:
> > ilya-biryukov wrote:
> > > ilya-biryukov wrote:
> > > > ayzhao wrote:
> > > > > ilya-biryukov wrote:
> > > > > > - Should we have a filler for `CXXParenInitListExpr` too?
> > > > > >   It seems like an important optimization and could have large 
> > > > > > effect on compile times if we don't 
> > > > > > 
> > > > > > - Same question for semantic and syntactic-form (similar to 
> > > > > > `InitListExpr`): should we have it here?
> > > > > >   I am not sure if it's semantically required (probably not?), but 
> > > > > > that's definitely something that `clang-tidy` and other source 
> > > > > > tools will rely on.
> > > > > > 
> > > > > > We should probably share the code there, I suggest moving it to a 
> > > > > > shared base class and using it where appropriate instead of the 
> > > > > > derived classes.
> > > > > > Should we have a filler for CXXParenInitListExpr too? It seems like 
> > > > > > an important optimization and could have large effect on compile 
> > > > > > times if we don't
> > > > > 
> > > > > This feels like premature optimization - presumably, wouldn't this 
> > > > > only be an issue with extraordinarily large (say, O(1000)) arrays?
> > > > > 
> > > > > > Same question for semantic and syntactic-form (similar to 
> > > > > > InitListExpr): should we have it here? I am not sure if it's 
> > > > > > semantically required (probably not?), but that's definitely 
> > > > > > something that clang-tidy and other source tools will rely on
> > > > > 
> > > > > IIRC this doesn't apply to paren list aggregate expressions, as the 
> > > > > syntatic form would be the enclosing `ParenListExpr`.
> > > > > This feels like premature optimization - presumably, wouldn't this 
> > > > > only be an issue with extraordinarily large (say, O(1000)) arrays?
> > > > Yes, this should only happen with large arrays. Normally I would agree, 
> > > > but it's surprising that changing `{}` to `()` in the compiler would 
> > > > lead to performance degradation.
> > > > In that sense, this premature optimization is already implemented, we 
> > > > are rather degrading performance for a different syntax to do the same 
> > > > thing.
> > > > 
> > > > Although we could also land without it, but in that case could you open 
> > > > a GH issue and add a FIXME to track the implementation of this 
> > > > particular optimization?
> > > > This should increase the chances of users finding the root cause of the 
> > > > issue if they happen to hit it.
> > > > 
> > > > > IIRC this doesn't apply to paren list aggregate expressions, as the 
> > > > > syntatic form would be the enclosing ParenListExpr.
> > > > Do we even have the enclosing `ParenListExpr`? If I dump the AST with 
> > > > `clang -fsyntax-only -Xclang=-ast-dump ...`  for the following code:
> > > > ```
> > > > struct pair { int a; int b = 2; };
> > > > int main() {
> > > >   pair(1); pair p(1);
> > > >   pair b{1}; pair{1};
> > > > }
> > > > ```
> > > > 
> > > > I get 
> > > > ```
> > > > `-FunctionDecl 0x557d79717e98 <line:2:1, line:5:1> line:2:5 main 'int 
> > > > ()'
> > > >   `-CompoundStmt 0x557d797369d0 <col:12, line:5:1>
> > > >     |-CXXFunctionalCastExpr 0x557d79718528 <line:3:3, col:9> 
> > > > 'pair':'pair' functional cast to pair <NoOp>
> > > >     | `-CXXParenListInitExpr 0x557d79718500 <col:3> 'pair':'pair'
> > > >     |   |-IntegerLiteral 0x557d79718010 <col:8> 'int' 1
> > > >     |   `-IntegerLiteral 0x557d79717e18 <line:1:30> 'int' 2
> > > >     |-DeclStmt 0x557d79718650 <line:3:12, col:21>
> > > >     | `-VarDecl 0x557d79718568 <col:12, col:17> col:17 p 'pair':'pair' 
> > > > parenlistinit
> > > >     |   `-CXXParenListInitExpr 0x557d79718610 <col:17> 'pair':'pair'
> > > >     |     |-IntegerLiteral 0x557d797185d0 <col:19> 'int' 1
> > > >     |     `-IntegerLiteral 0x557d79717e18 <line:1:30> 'int' 2
> > > >     |-DeclStmt 0x557d797187d8 <line:4:3, col:12>
> > > >     | `-VarDecl 0x557d79718680 <col:3, col:11> col:8 b 'pair':'pair' 
> > > > listinit
> > > >     |   `-InitListExpr 0x557d79718750 <col:9, col:11> 'pair':'pair'
> > > >     |     |-IntegerLiteral 0x557d797186e8 <col:10> 'int' 1
> > > >     |     `-CXXDefaultInitExpr 0x557d797187a0 <col:11> 'int'
> > > >     `-CXXFunctionalCastExpr 0x557d797369a8 <col:14, col:20> 
> > > > 'pair':'pair' functional cast to pair <NoOp>
> > > >       `-InitListExpr 0x557d79718868 <col:18, col:20> 'pair':'pair'
> > > >         |-IntegerLiteral 0x557d79718800 <col:19> 'int' 1
> > > >         `-CXXDefaultInitExpr 0x557d797188b8 <col:20> 'int'
> > > > ```
> > > > It feels like the `ParentListExpr` is replaced during semantic analysis 
> > > > and there is no way to get it back. I also tried running `clang-query` 
> > > > and trying to `match parenListExpr()` and go 0 results. Is it just 
> > > > missing in the AST dump and I run `clang-query` incorrectly or do we 
> > > > actually not have the syntactic form of this expression after all?
> > > Another important thing to address from the dump: notice how braced 
> > > initialization creates `CXXDefaultInitExpr` and `CXXParenListInitExpr` 
> > > copies the default argument expression directly. It's really important to 
> > > use the former form, here's the example that currently breaks:
> > > 
> > > 
> > > ```
> > > #include <iostream>
> > > 
> > > struct func_init { int some_int; const char* fn = __builtin_FUNCTION(); };
> > > 
> > > int main() {
> > >   func_init a(10);
> > >   std::cout << "From paren init:" << a.fn << std::endl;
> > > 
> > >   func_init b{10};
> > >   std::cout << "From braced init: " << b.fn << std::endl;
> > > }
> > > ```
> > > 
> > > The program above is expected to report `main` for both cases, but 
> > > instead we get:
> > > ```
> > > From paren init:
> > > From braced init: main
> > > ```
> > The following have now been implemented:
> > 
> > * `CXXDefaultInitExpr` and `ImplicitValueInitExpr`, which includes adding a 
> > test for `__builtin_FUNCTION()`
> > * Array fillers
> > * Semantic forms vs syntactic forms
> Instead of implementing separate syntactic and semantic forms, I think it 
> might be simpler and cleaner to store an `unsigned NumUserSpecifiedExprs`.
> 
> The reason for implementing it this way is that the purpose of having 
> separate syntactic and semantic forms is to determine what the form in the 
> code was originally as written and what the form is after semantic analysis. 
> For `InitListExpr`, we need separate objects because there can be a 
> significant difference in the two forms (e.g. designated initializers, etc.). 
> However, `CXXParenListInitExpr` is much simpler - the first k expressions are 
> user-specified, while the remaining are default/compiler-generated. 
> Therefore, we can eliminate the complexity required for maintaining two forms 
> (say, in `TreeTransform`) by just storing k. 
> 
> N.B. this also resolves a build failure with an earlier diff where the libc++ 
> bots were failing.
> 
> There is also precedence for this kind of structure - function calls with 
> default arguments use similar logic instead of creating two `Expr` objects.
That looks fine indeed, I agree that `CXXParenInitListExpr` is simpler.


================
Comment at: clang/lib/Sema/SemaInit.cpp:5281
+
+  // llvm::errs() << "zzzz VerifyOnly is " << VerifyOnly << '\n';
+
----------------
NIT: reminder to remove this and other debug prints


================
Comment at: clang/lib/Sema/SemaInit.cpp:5326
+            E->getExprLoc(), /*isDirectInit=*/false, E);
+        InitializationSequence Seq(S, SubEntity, SubKind, E);
+
----------------
NIT: maybe rename to `SubSeq` or something similar?
Having both `Seq` and `Sequence` in scope with the same type is error-prone and 
hard to comprehend.


================
Comment at: clang/lib/Sema/SemaInit.cpp:5380
+          }
+          InitExprs.push_back(ER.get());
+        }
----------------
ayzhao wrote:
> ayzhao wrote:
> > So the libc++ test compile failures are due to this line.
> > 
> > One example of a failing unit test is 
> > [range.take.while/ctor.view.pass](https://github.com/llvm/llvm-project/blob/main/libcxx/test/std/ranges/range.adaptors/range.take.while/ctor.view.pass.cpp).
> >  Clang calls this function twice in `TreeTransform.h` - once with 
> > `VerifyOnly` set to `true`, once with it set to `false`.
> > 
> > For some reason, when this function tries to value-initialize the member 
> > `MoveOnly mo` in `View`, `Seq.Failed()` returns false after 
> > `TryValueInitialization(...)`, but the resulting `ExprResult` is `nullptr`, 
> > causing the segfault we see when we push `nullptr` to `InitExprs` and pass 
> > `InitExprs` to the constructor of `CXXParenListInitExpr`. One way to be fix 
> > this is to move the line `ExprResult ER = Seq.Perform(...)` out of the `if 
> > (!VerifyOnly)` block and check for `ER.isInvalid()` instead of 
> > `Seq.Failed()`, but that results in test failures due to excess diagnostic 
> > messages in `Seq.Perform(...)`
> > 
> > I'm still looking into this, but if anyone has any ideas, they would be 
> > very welcome.
> > 
> > To repro the buildbot failures, just build clang with this patch, and then 
> > in a separate build directory, build the target `check-cxx` using the 
> > previously built clang.
> I was able to get the above workaround to pass the test by clearing the 
> diagnostics after calling `Seq.Perform(...)`.
> 
> IMO, this should be OK for now, but I'm open to better ideas if anyone has 
> any.
Clearing all the diagnostics is a nuclear options and definitely seems off 
here. We should not call `Perform()` when `VerifyOnly` is `true` to avoid 
producing the diagnostics in the first place.

It's fine for the call with `VerifyOnly = true` to return no errors and later 
produce diagnostics with `VerifyOnly = false`, I believe this is what 
`InitListChecker` is already doing.
I have been playing around with the old version of the code, but couldn't fix 
it fully. I do have a small example that breaks, we should add it to the test 
and it should also be easier to understand what's going on:

```
struct MoveOnly
{
  MoveOnly(int data = 1);
  MoveOnly(const MoveOnly&) = delete;
  MoveOnly(MoveOnly&&) = default;
};

struct View {
  int a;
  MoveOnly mo;
};

void test() {
  View{0};
  View(0); // should work, but crashes and produces invalid diagnostics.
}
```

In general, my approach would be to try mimicing what `InitListChecker` is 
doing as much as possible, trimming all the unnecessary complexity that 
braced-init-lists entail.
Hope it's helpful.


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D129531

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

Reply via email to