Skip to content

Commit bfbe137

Browse files
committed
[clang][dataflow] Eliminate intermediate ReferenceValues from Environment::DeclToLoc.
For the wider context of this change, see the RFC at https://discourse.llvm.org/t/70086. After this change, global and local variables of reference type are associated directly with the `StorageLocation` of the referenced object instead of the `StorageLocation` of a `ReferenceValue`. Some tests that explicitly check for an existence of `ReferenceValue` for a variable of reference type have been modified accordingly. As discussed in the RFC, I have added an assertion to `Environment::join()` to check that if both environments contain an entry for the same declaration in `DeclToLoc`, they both map to the same `StorageLocation`. As discussed in https://discourse.llvm.org/t/70086/5, this also necessitates removing declarations from `DeclToLoc` when they go out of scope. In the RFC, I proposed a gradual migration for this change, but it appears that all of the callers of `Environment::setStorageLocation(const ValueDecl &, SkipPast` are in the dataflow framework itself, and that there are only a few of them. As this is the function whose semantics are changing in a way that callers potentially need to adapt to, I've decided to change the semantics of the function directly. The semantics of `getStorageLocation(const ValueDecl &, SkipPast SP` now no longer depend on the behavior of the `SP` parameter. (There don't appear to be any callers that use `SkipPast::ReferenceThenPointer`, so I've added an assertion that forbids this usage.) This patch adds a default argument for the `SP` parameter and removes the explicit `SP` argument at the callsites that are touched by this change. A followup patch will remove the argument from the remaining callsites, allowing the `SkipPast` parameter to be removed entirely. (I don't want to do that in this patch so that semantics-changing changes can be reviewed separately from semantics-neutral changes.) Reviewed By: ymandel, xazax.hun, gribozavr2 Differential Revision: https://reviews.llvm.org/D149144
1 parent d726f99 commit bfbe137

File tree

5 files changed

+186
-148
lines changed

5 files changed

+186
-148
lines changed

clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,24 @@ class Environment {
269269
///
270270
/// Requirements:
271271
///
272-
/// `D` must not be assigned a storage location in the environment.
272+
/// `D` must not already have a storage location in the environment.
273+
///
274+
/// If `D` has reference type, `Loc` must refer directly to the referenced
275+
/// object (if any), not to a `ReferenceValue`, and it is not permitted to
276+
/// later change `Loc` to refer to a `ReferenceValue.`
273277
void setStorageLocation(const ValueDecl &D, StorageLocation &Loc);
274278

275-
/// Returns the storage location assigned to `D` in the environment, applying
276-
/// the `SP` policy for skipping past indirections, or null if `D` isn't
277-
/// assigned a storage location in the environment.
278-
StorageLocation *getStorageLocation(const ValueDecl &D, SkipPast SP) const;
279+
/// Returns the storage location assigned to `D` in the environment, or null
280+
/// if `D` isn't assigned a storage location in the environment.
281+
///
282+
/// Note that if `D` has reference type, the storage location that is returned
283+
/// refers directly to the referenced object, not a `ReferenceValue`.
284+
///
285+
/// The `SP` parameter is deprecated and has no effect. In addition, it is
286+
/// not permitted to pass `SkipPast::ReferenceThenPointer` for this parameter.
287+
/// New uses of this function should use the default argument for `SP`.
288+
StorageLocation *getStorageLocation(const ValueDecl &D,
289+
SkipPast SP = SkipPast::None) const;
279290

280291
/// Assigns `Loc` as the storage location of `E` in the environment.
281292
///
@@ -320,7 +331,11 @@ class Environment {
320331

321332
/// Equivalent to `getValue(getStorageLocation(D, SP), SkipPast::None)` if `D`
322333
/// is assigned a storage location in the environment, otherwise returns null.
323-
Value *getValue(const ValueDecl &D, SkipPast SP) const;
334+
///
335+
/// The `SP` parameter is deprecated and has no effect. In addition, it is
336+
/// not permitted to pass `SkipPast::ReferenceThenPointer` for this parameter.
337+
/// New uses of this function should use the default argument for `SP`.
338+
Value *getValue(const ValueDecl &D, SkipPast SP = SkipPast::None) const;
324339

325340
/// Equivalent to `getValue(getStorageLocation(E, SP), SkipPast::None)` if `E`
326341
/// is assigned a storage location in the environment, otherwise returns null.

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,9 @@ void Environment::initFieldsGlobalsAndFuncs(const FunctionDecl *FuncDecl) {
247247
for (const VarDecl *D : Vars) {
248248
if (getStorageLocation(*D, SkipPast::None) != nullptr)
249249
continue;
250-
auto &Loc = createStorageLocation(*D);
250+
auto &Loc = createStorageLocation(D->getType().getNonReferenceType());
251251
setStorageLocation(*D, Loc);
252-
if (auto *Val = createValue(D->getType()))
252+
if (auto *Val = createValue(D->getType().getNonReferenceType()))
253253
setValue(Loc, *Val);
254254
}
255255

@@ -291,10 +291,16 @@ Environment::Environment(DataflowAnalysisContext &DACtx,
291291

292292
for (const auto *ParamDecl : FuncDecl->parameters()) {
293293
assert(ParamDecl != nullptr);
294-
auto &ParamLoc = createStorageLocation(*ParamDecl);
294+
// References aren't objects, so the reference itself doesn't have a
295+
// storage location. Instead, the storage location for a reference refers
296+
// directly to an object of the referenced type -- so strip off any
297+
// reference from the type.
298+
auto &ParamLoc =
299+
createStorageLocation(ParamDecl->getType().getNonReferenceType());
295300
setStorageLocation(*ParamDecl, ParamLoc);
296-
if (Value *ParamVal = createValue(ParamDecl->getType()))
297-
setValue(ParamLoc, *ParamVal);
301+
if (Value *ParamVal =
302+
createValue(ParamDecl->getType().getNonReferenceType()))
303+
setValue(ParamLoc, *ParamVal);
298304
}
299305

300306
QualType ReturnType = FuncDecl->getReturnType();
@@ -376,17 +382,19 @@ void Environment::pushCallInternal(const FunctionDecl *FuncDecl,
376382
continue;
377383

378384
const VarDecl *Param = *ParamIt;
379-
auto &Loc = createStorageLocation(*Param);
380-
setStorageLocation(*Param, Loc);
381385

382386
QualType ParamType = Param->getType();
383387
if (ParamType->isReferenceType()) {
384-
auto &Val = DACtx->arena().create<ReferenceValue>(*ArgLoc);
385-
setValue(Loc, Val);
386-
} else if (auto *ArgVal = getValue(*ArgLoc)) {
387-
setValue(Loc, *ArgVal);
388-
} else if (Value *Val = createValue(ParamType)) {
389-
setValue(Loc, *Val);
388+
setStorageLocation(*Param, *ArgLoc);
389+
} else {
390+
auto &Loc = createStorageLocation(*Param);
391+
setStorageLocation(*Param, Loc);
392+
393+
if (auto *ArgVal = getValue(*ArgLoc)) {
394+
setValue(Loc, *ArgVal);
395+
} else if (Value *Val = createValue(ParamType)) {
396+
setValue(Loc, *Val);
397+
}
390398
}
391399
}
392400
}
@@ -518,6 +526,10 @@ LatticeJoinEffect Environment::join(const Environment &Other,
518526
JoinedEnv.ReturnLoc = ReturnLoc;
519527
JoinedEnv.ThisPointeeLoc = ThisPointeeLoc;
520528

529+
// FIXME: Once we're able to remove declarations from `DeclToLoc` when their
530+
// lifetime ends, add an assertion that there aren't any entries in
531+
// `DeclToLoc` and `Other.DeclToLoc` that map the same declaration to
532+
// different storage locations.
521533
JoinedEnv.DeclToLoc = intersectDenseMaps(DeclToLoc, Other.DeclToLoc);
522534
if (DeclToLoc.size() != JoinedEnv.DeclToLoc.size())
523535
Effect = LatticeJoinEffect::Changed;
@@ -589,13 +601,23 @@ StorageLocation &Environment::createStorageLocation(const Expr &E) {
589601

590602
void Environment::setStorageLocation(const ValueDecl &D, StorageLocation &Loc) {
591603
assert(!DeclToLoc.contains(&D));
604+
assert(!isa_and_nonnull<ReferenceValue>(getValue(Loc)));
592605
DeclToLoc[&D] = &Loc;
593606
}
594607

595608
StorageLocation *Environment::getStorageLocation(const ValueDecl &D,
596609
SkipPast SP) const {
610+
assert(SP != SkipPast::ReferenceThenPointer);
611+
597612
auto It = DeclToLoc.find(&D);
598-
return It == DeclToLoc.end() ? nullptr : &skip(*It->second, SP);
613+
if (It == DeclToLoc.end())
614+
return nullptr;
615+
616+
StorageLocation *Loc = It->second;
617+
618+
assert(!isa_and_nonnull<ReferenceValue>(getValue(*Loc)));
619+
620+
return Loc;
599621
}
600622

601623
void Environment::setStorageLocation(const Expr &E, StorageLocation &Loc) {
@@ -662,6 +684,8 @@ Value *Environment::getValue(const StorageLocation &Loc) const {
662684
}
663685

664686
Value *Environment::getValue(const ValueDecl &D, SkipPast SP) const {
687+
assert(SP != SkipPast::ReferenceThenPointer);
688+
665689
auto *Loc = getStorageLocation(D, SP);
666690
if (Loc == nullptr)
667691
return nullptr;

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 67 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -223,80 +223,77 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
223223
if (DeclLoc == nullptr)
224224
return;
225225

226-
// If the value is already an lvalue, don't double-wrap it.
227-
if (isa_and_nonnull<ReferenceValue>(Env.getValue(*DeclLoc))) {
228-
// We only expect to encounter a `ReferenceValue` for a reference type
229-
// (always) or for `BindingDecl` (sometimes). For the latter, we can't
230-
// rely on type, because their type does not indicate whether they are a
231-
// reference type. The assert is not strictly necessary, since we don't
232-
// depend on its truth to proceed. But, it verifies our assumptions,
233-
// which, if violated, probably indicate a problem elsewhere.
234-
assert((VD->getType()->isReferenceType() || isa<BindingDecl>(VD)) &&
235-
"Only reference-typed declarations or `BindingDecl`s should map "
236-
"to `ReferenceValue`s");
237-
Env.setStorageLocation(*S, *DeclLoc);
238-
} else {
239-
auto &Loc = Env.createStorageLocation(*S);
240-
auto &Val = Env.create<ReferenceValue>(*DeclLoc);
241-
Env.setStorageLocation(*S, Loc);
242-
Env.setValue(Loc, Val);
243-
}
226+
Env.setStorageLocation(*S, *DeclLoc);
244227
}
245228

246229
void VisitDeclStmt(const DeclStmt *S) {
247230
// Group decls are converted into single decls in the CFG so the cast below
248231
// is safe.
249232
const auto &D = *cast<VarDecl>(S->getSingleDecl());
250233

234+
ProcessVarDecl(D);
235+
}
236+
237+
void ProcessVarDecl(const VarDecl &D) {
251238
// Static local vars are already initialized in `Environment`.
252239
if (D.hasGlobalStorage())
253240
return;
254241

255-
// The storage location for `D` could have been created earlier, before the
256-
// variable's declaration statement (for example, in the case of
257-
// BindingDecls).
258-
auto *MaybeLoc = Env.getStorageLocation(D, SkipPast::None);
259-
if (MaybeLoc == nullptr) {
260-
MaybeLoc = &Env.createStorageLocation(D);
261-
Env.setStorageLocation(D, *MaybeLoc);
262-
}
263-
auto &Loc = *MaybeLoc;
242+
if (D.getType()->isReferenceType()) {
243+
// If this is the holding variable for a `BindingDecl`, we may already
244+
// have a storage location set up -- so check. (See also explanation below
245+
// where we process the `BindingDecl`.)
246+
if (Env.getStorageLocation(D) == nullptr) {
247+
const Expr *InitExpr = D.getInit();
248+
assert(InitExpr != nullptr);
249+
if (auto *InitExprLoc =
250+
Env.getStorageLocation(*InitExpr, SkipPast::Reference)) {
251+
Env.setStorageLocation(D, *InitExprLoc);
252+
} else {
253+
// Even though we have an initializer, we might not get an
254+
// InitExprLoc, for example if the InitExpr is a CallExpr for which we
255+
// don't have a function body. In this case, we just invent a storage
256+
// location and value -- it's the best we can do.
257+
StorageLocation &Loc =
258+
Env.createStorageLocation(D.getType().getNonReferenceType());
259+
Env.setStorageLocation(D, Loc);
260+
if (Value *Val = Env.createValue(D.getType().getNonReferenceType()))
261+
Env.setValue(Loc, *Val);
262+
}
263+
}
264+
} else {
265+
// Not a reference type.
264266

265-
const Expr *InitExpr = D.getInit();
266-
if (InitExpr == nullptr) {
267-
// No initializer expression - associate `Loc` with a new value.
268-
if (Value *Val = Env.createValue(D.getType()))
269-
Env.setValue(Loc, *Val);
270-
return;
271-
}
267+
assert(Env.getStorageLocation(D) == nullptr);
268+
StorageLocation &Loc = Env.createStorageLocation(D);
269+
Env.setStorageLocation(D, Loc);
272270

273-
if (D.getType()->isReferenceType()) {
274-
// Initializing a reference variable - do not create a reference to
275-
// reference.
276-
// FIXME: reuse the ReferenceValue instead of creating a new one.
277-
if (auto *InitExprLoc =
278-
Env.getStorageLocation(*InitExpr, SkipPast::Reference)) {
279-
auto &Val = Env.create<ReferenceValue>(*InitExprLoc);
280-
Env.setValue(Loc, Val);
271+
const Expr *InitExpr = D.getInit();
272+
if (InitExpr == nullptr) {
273+
// No initializer expression - associate `Loc` with a new value.
274+
if (Value *Val = Env.createValue(D.getType()))
275+
Env.setValue(Loc, *Val);
276+
return;
281277
}
282-
} else if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None)) {
283-
Env.setValue(Loc, *InitExprVal);
284-
}
285278

286-
if (Env.getValue(Loc) == nullptr) {
287-
// We arrive here in (the few) cases where an expression is intentionally
288-
// "uninterpreted". There are two ways to handle this situation: propagate
289-
// the status, so that uninterpreted initializers result in uninterpreted
290-
// variables, or provide a default value. We choose the latter so that
291-
// later refinements of the variable can be used for reasoning about the
292-
// surrounding code.
293-
//
294-
// FIXME. If and when we interpret all language cases, change this to
295-
// assert that `InitExpr` is interpreted, rather than supplying a default
296-
// value (assuming we don't update the environment API to return
297-
// references).
298-
if (Value *Val = Env.createValue(D.getType()))
299-
Env.setValue(Loc, *Val);
279+
if (auto *InitExprVal = Env.getValue(*InitExpr, SkipPast::None))
280+
Env.setValue(Loc, *InitExprVal);
281+
282+
if (Env.getValue(Loc) == nullptr) {
283+
// We arrive here in (the few) cases where an expression is
284+
// intentionally "uninterpreted". There are two ways to handle this
285+
// situation: propagate the status, so that uninterpreted initializers
286+
// result in uninterpreted variables, or provide a default value. We
287+
// choose the latter so that later refinements of the variable can be
288+
// used for reasoning about the surrounding code.
289+
//
290+
// FIXME. If and when we interpret all language cases, change this to
291+
// assert that `InitExpr` is interpreted, rather than supplying a
292+
// default value (assuming we don't update the environment API to return
293+
// references).
294+
if (Value *Val = Env.createValue(D.getType()))
295+
Env.setValue(Loc, *Val);
296+
}
300297
}
301298

302299
// `DecompositionDecl` must be handled after we've interpreted the loc
@@ -322,15 +319,16 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
322319
if (auto *Loc = Env.getStorageLocation(*ME, SkipPast::Reference))
323320
Env.setStorageLocation(*B, *Loc);
324321
} else if (auto *VD = B->getHoldingVar()) {
325-
// Holding vars are used to back the BindingDecls of tuple-like
326-
// types. The holding var declarations appear *after* this statement,
327-
// so we have to create a location for them here to share with `B`. We
328-
// don't visit the binding, because we know it will be a DeclRefExpr
329-
// to `VD`. Note that, by construction of the AST, `VD` will always be
330-
// a reference -- either lvalue or rvalue.
331-
auto &VDLoc = Env.createStorageLocation(*VD);
332-
Env.setStorageLocation(*VD, VDLoc);
333-
Env.setStorageLocation(*B, VDLoc);
322+
// Holding vars are used to back the `BindingDecl`s of tuple-like
323+
// types. The holding var declarations appear after the
324+
// `DecompositionDecl`, so we have to explicitly process them here
325+
// to know their storage location. They will be processed a second
326+
// time when we visit their `VarDecl`s, so we have code that protects
327+
// against this above.
328+
ProcessVarDecl(*VD);
329+
auto *VDLoc = Env.getStorageLocation(*VD);
330+
assert(VDLoc != nullptr);
331+
Env.setStorageLocation(*B, *VDLoc);
334332
}
335333
}
336334
}
@@ -539,15 +537,7 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
539537
if (VarDeclLoc == nullptr)
540538
return;
541539

542-
if (VarDeclLoc->getType()->isReferenceType()) {
543-
assert(isa_and_nonnull<ReferenceValue>(Env.getValue((*VarDeclLoc))) &&
544-
"reference-typed declarations map to `ReferenceValue`s");
545-
Env.setStorageLocation(*S, *VarDeclLoc);
546-
} else {
547-
auto &Loc = Env.createStorageLocation(*S);
548-
Env.setStorageLocation(*S, Loc);
549-
Env.setValue(Loc, Env.create<ReferenceValue>(*VarDeclLoc));
550-
}
540+
Env.setStorageLocation(*S, *VarDeclLoc);
551541
return;
552542
}
553543
}

0 commit comments

Comments
 (0)