Created attachment 47339 [details] Listing of problematic code, and similar code that works correctly Given something like this:``` extern unsigned int get_vla_size(void); ``` This is ok:``` if (0) { (void)(int(*)[get_vla_size()])0; } else { (void)get_vla_size(); } ``` asm:``` call _Z12get_vla_sizev ``` However, re-writing this code to use the ?-operator causes `get_vla_size()` to be called twice:``` 0 ? ( (void)(int(*)[get_vla_size()])0 ) : ( (void)get_vla_size() ); ``` asm:``` call _Z12get_vla_sizev call _Z12get_vla_sizev ``` Note that the first call to `get_vla_size()` happens from within a dead branch, meaning that the call should have been fully removed, or at the very least have been skipped by an unconditional jump. When the first branch is wrapped in statement-expressions, code once again function as expected:``` 0 ? ({ (void)(int(*)[get_vla_size()])0; }) : ( (void)get_vla_size() ); ``` asm:``` call _Z12get_vla_sizev ``` This problem consistently appears when compiling for c++, regardless of optimization level, and if I had to guess, it's probably related to the scope in which some given type is declared, where `type-expr` in `{ int x; 0 ? (type-expr)expr : expr; }` is declared in the same scope as `x`, and thereby unconditionally initialized. The problem can be reproduced using:``` g++ -S attached-file.cc && cat attached-file.s ``` and inspecting the produced output Note: I hope that `middle-end` was the correct place to report this, and I'm sorry if it isn't.
The big question comes, where should the VLA type be finalized, at the use or at the beginning of the statement. Statement expressions create a new statement so you are seeing that. I don't know the correct answer. Since VLA types are not part of the C++ standard, what GCC does currently might be considered the correct answer (and most likely could not be implemented different either).
(In reply to Andrew Pinski from comment #1) > The big question comes, where should the VLA type be finalized, at the use > or at the beginning of the statement. > > Statement expressions create a new statement so you are seeing that. > > I don't know the correct answer. Since VLA types are not part of the C++ > standard, what GCC does currently might be considered the correct answer > (and most likely could not be implemented different either). That may be so, however I find it extremely disconcerting that it is possible to have the contents of a compile-time dead branch be able to affect the generated assembly in ways that can cause some very real side-effects (function calls) at runtime. When I write a macro like `#define ifelse(c, tt, ff) ((c) ? (tt) : (ff))`, then I really _have_ to be able to rely on the fact that whatever happens, and whatever it is passed, _only_ 1 of `tt` or `ff` get evaluated at runtime, no matter what happens between me invoking g++, and eventually running the produced binary. To answer the question as to when finalization of the type should happen: the naive (and probably most comprehensible) answer would be at the end of the dead ?-branch, though I can see how that might be difficult since that branch doesn't have its own scope. I sadly don't know enough of how gcc in particular generates assembly, however I do know how a generic compiler works, so one solution might be to compile `0 ? vla-expr : other-expr' as `jmp 1f; vla-expr; jmp 2f; 1: other-expr; 2:` and use peephole optimization to transform this into `other-expr;`
The C front end explicitly tracks VLA size expressions in type names in casts and ensures they are evaluated at an appropriate point using a C_MAYBE_CONST_EXPR (which later turns into a COMPOUND_EXPR); see c_cast_expr. Presumably C++ needs to do something similar to ensure size expressions in type names are evaluated at an appropriate point within the containing expression.