Opened 3 weeks ago
Last modified 3 weeks ago
#291 new defect
Cannot Lift Reference Initialization
Reported by: | ajbeach | Owned by: | |
---|---|---|---|
Priority: | major | Component: | cfa-cc |
Version: | 1.0 | Keywords: | |
Cc: |
Description
The initial symptom of this error is a problem in the box pass. But that is because of a floating node, which you can catch with the invariant check, revealing that it comes from Fix Init.
Here is code to reproduce the error:
int var = 4; int & getVar() { return var; } int & ref = getVar();
There are various ways this can cause errors, but the root cause seems to be fairly consistent. That is reference initializers cannot be hoisted into the __global_init__
function. This decision is made inside the resolver even though the actual lifting happens later.
The decision is communicated from the resolver to the hoisting pass (Fix Global Init, part of Fix Init) by wrapping the initializer in a ConstructorInit?. There are two sets of fields on the ConstructorInit?, ctor/dtor for a managed type and init for an unmanaged type.
At least that appears to be the intent, a lone Init couldn't be hoisted to an assignment as is, but also that entire option isn't used here so that is just a guess at what the intent is. The result is that only constructors are hoisted.
On a very basic level, all non-constant expressions need to be hoisted, because C will not evaluate them at the global scope. All functions are not constant, hence all constructors are not constant and do need to be hoisted. However, some non-constructor initializers also need to be lifted.
Original issue was discovered with the following example. This has the issue with a non-constant expressions but also some polymorphic code that is not specialized outside of a function and a temporary value that is lost with no block to insert it into.
#include <stdlib.hfa> struct S { int x; }; S & s = (*malloc()){ 2 };
Change History (2)
comment:1 Changed 3 weeks ago by
comment:2 Changed 3 weeks ago by
Oh yes, and the code generation problem involves resolving implicit references and cast expressions acting as annotations, so doing it post resolver does confuse the issue further.
During clean-up of the research I did for this. This is less stable information but might help out whoever comes back to this issue.
The decision about how to handle the initializer happens in the
Resolver::previsit
forObjectDecl
. Or you could say that is where the result is encoded into the output. For ObjectDecl? initializers (not inside an enumeration) the initializer is either left as is or is wrapped in a ConstructorInit?. That decision comes fromResolver::shouldGenCtorInit
, which callsInitTweak::tryConstruct
(and some other functions) and that does some other work and then callsInitTweak::isConstructable
which is effectively just some low level types that don't have constructors, and one of those is ReferenceType?, no no constructor is ever created for it. (And of these types could in theory have the same problem.)One of those other functions called by
shouldGenCtorInit
isInitTweak::isConstExpr
, and that is actually a function we need to take into account because if it is false, then the initializer has to be hoisted at some point. So the Resolver needs to detect that, create a resolved assignment (because it needs to be hoisted away from the declaration, so a C init will not work) and pass on the result to the hoisting pass.The hoisting takes place in
InitTweak::fixGlobalInit
(a subpass ofInitTweak::fix
, and unlike most subpasses there is actually in a different file). This pass always short circuits and just goes along the top of the AST. At the top of the ObjectDecl? previsit is a check if the initializer is a ConstructorInit?, if it is there it is lifted.One of my attempted solutions was check for a non-constant expression after that check and hoist it in that case as well. This does do some rechecking but also, generating the correct code didn't work out. Maybe it is possible, but getting the code in and resolved post resolver is problematic.