Bug 92641 - VLA type finalized at the beginging of the statement rather at the point of use
Summary: VLA type finalized at the beginging of the statement rather at the point of use
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 9.1.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-code
Depends on:
Blocks: C++VLA
  Show dependency treegraph
 
Reported: 2019-11-23 21:50 UTC by sagebar
Modified: 2024-01-01 03:32 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments
Listing of problematic code, and similar code that works correctly (372 bytes, text/plain)
2019-11-23 21:50 UTC, sagebar
Details

Note You need to log in before you can comment on or make changes to this bug.
Description sagebar 2019-11-23 21:50:11 UTC
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.
Comment 1 Andrew Pinski 2019-11-23 21:59:07 UTC
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).
Comment 2 sagebar 2019-11-23 22:28:37 UTC
(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;`
Comment 3 jsm-csl@polyomino.org.uk 2019-11-25 17:00:37 UTC
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.