[clang] nonblocking/nonallocating attributes (was: nolock/noalloc) (PR #84983)

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Sat May 4 13:38:14 PDT 2024


================
@@ -5028,3 +5050,376 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
   Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
+
+FunctionEffect::Kind FunctionEffect::oppositeKind() const {
+  switch (kind()) {
+  case Kind::NonBlocking:
+    return Kind::Blocking;
+  case Kind::Blocking:
+    return Kind::NonBlocking;
+  case Kind::NonAllocating:
+    return Kind::Allocating;
+  case Kind::Allocating:
+    return Kind::NonAllocating;
+  case Kind::None:
+    return Kind::None;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+StringRef FunctionEffect::name() const {
+  switch (kind()) {
+  case Kind::NonBlocking:
+    return "nonblocking";
+  case Kind::NonAllocating:
+    return "nonallocating";
+  case Kind::Blocking:
+    return "blocking";
+  case Kind::Allocating:
+    return "allocating";
+  case Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffectDiff::shouldDiagnoseConversion(
+    QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
+    const FunctionEffectsRef &DstFX) const {
+
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+    // nonallocating can't be added (spoofed) during a conversion, unless we
+    // have nonblocking
+    if (DiffKind == Kind::Added) {
+      for (const auto &CFE : SrcFX) {
+        if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
+          return false;
+      }
+    }
+    [[fallthrough]];
+  case FunctionEffect::Kind::NonBlocking:
+    // nonblocking can't be added (spoofed) during a conversion.
+    switch (DiffKind) {
+    case Kind::Added:
+      return true;
+    case Kind::Removed:
+      return false;
+    case Kind::ConditionMismatch:
+      return true; // TODO: ???
+    }
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return false;
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
+    const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
+    const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+  case FunctionEffect::Kind::NonBlocking:
+    // nonblocking/nonallocating can't be removed in a redeclaration
+    switch (DiffKind) {
+    case Kind::Added:
+      return false; // No diagnostic.
+    case Kind::Removed:
+      return true; // Issue diagnostic
+    case Kind::ConditionMismatch:
+      // All these forms of mismatches are diagnosed.
+      return true;
+    }
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return false;
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+FunctionEffectDiff::OverrideResult
+FunctionEffectDiff::shouldDiagnoseMethodOverride(
+    const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+    const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+  case FunctionEffect::Kind::NonBlocking:
+    switch (DiffKind) {
+
+    // If added on an override, that's fine and not diagnosed.
+    case Kind::Added:
+      return OverrideResult::NoAction;
+
+    // If missing from an override (removed), propagate from base to derived.
+    case Kind::Removed:
+      return OverrideResult::Merge;
+
+    // If there's a mismatch involving the effect's polarity or condition,
+    // issue a warning.
+    case Kind::ConditionMismatch:
+      return OverrideResult::Warn;
+    }
+
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return OverrideResult::NoAction;
+
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
+  switch (kind()) {
+  case Kind::NonAllocating:
+  case Kind::NonBlocking: {
+    FunctionEffectsRef CalleeFX;
+    if (auto *FD = Callee.getAsFunction())
+      CalleeFX = FD->getFunctionEffects();
+    else if (auto *BD = dyn_cast<BlockDecl>(&Callee))
+      CalleeFX = BD->getFunctionEffects();
+    else
+      return false;
+    for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
+      // nonblocking/nonallocating cannot call allocating
+      if (CalleeEC.Effect.kind() == Kind::Allocating)
+        return false;
+      // nonblocking cannot call blocking
+      if (kind() == Kind::NonBlocking &&
+          CalleeEC.Effect.kind() == Kind::Blocking)
+        return false;
+    }
+  }
+    return true;
+
+  case Kind::Allocating:
+  case Kind::Blocking:
+    return false;
+
+  case Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::shouldDiagnoseFunctionCall(
+    bool Direct, ArrayRef<FunctionEffect> CalleeFX) const {
+  switch (kind()) {
+  case Kind::NonAllocating:
+  case Kind::NonBlocking: {
+    const Kind CallerKind = kind();
+    for (const auto &Effect : CalleeFX) {
+      const Kind EK = Effect.kind();
+      // Does callee have same or stronger constraint?
+      if (EK == CallerKind ||
+          (CallerKind == Kind::NonAllocating && EK == Kind::NonBlocking)) {
+        return false; // no diagnostic
+      }
+    }
+    return true; // warning
+  }
+  case Kind::Allocating:
+  case Kind::Blocking:
+    return false;
+  case Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+// =====
+
+void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const {
+  const bool HasConds = !Conditions.empty();
+
+  ID.AddInteger(size() | (HasConds << 31u));
+  for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) {
+    ID.AddInteger(Effects[Idx].toOpaqueInt32());
+    if (HasConds)
+      ID.AddPointer(Conditions[Idx].expr());
+  }
+}
+
+void FunctionEffectSet::insert(FunctionEffect Effect, Expr *Cond) {
+  // lower_bound would be overkill
+  unsigned Idx = 0;
+  for (unsigned Count = Effects.size(); Idx != Count; ++Idx) {
+    const auto &IterEffect = Effects[Idx];
+    if (IterEffect.kind() == Effect.kind()) {
+      // It's possible here to have incompatible combinations of polarity
+      // (asserted/denied) and condition; for now, we keep whichever came
+      // though this should be improved.
+      return;
+    }
+    if (Effect.kind() < IterEffect.kind())
+      break;
+  }
+
+  if (Cond) {
+    if (Conditions.empty() && !Effects.empty())
+      Conditions.resize(Effects.size());
+    Conditions.insert(Conditions.begin() + Idx, Cond);
+  }
+  Effects.insert(Effects.begin() + Idx, Effect);
+}
+
+void FunctionEffectSet::insert(const FunctionEffectsRef &Set) {
+  for (const auto &Item : Set)
+    insert(Item.Effect, Item.Cond.expr());
+}
+
+void FunctionEffectSet::insertIgnoringConditions(
+    const FunctionEffectsRef &Set) {
+  for (const auto &Item : Set)
+    insert(Item.Effect, nullptr);
+}
+
+void FunctionEffectSet::replaceItem(unsigned Idx,
+                                    const FunctionEffectWithCondition &Item) {
+  assert(Idx < Conditions.size());
+  Effects[Idx] = Item.Effect;
+  Conditions[Idx] = Item.Cond;
+
+  // Maintain invariant: If all conditions are null, the vector should be empty.
+  if (std::all_of(Conditions.begin(), Conditions.end(),
+                  [](const FunctionEffectCondition &C) {
+                    return C.expr() == nullptr;
+                  })) {
+    Conditions.clear();
+  }
+}
+
+void FunctionEffectSet::erase(unsigned Idx) {
+  assert(Idx < Effects.size());
+  Effects.erase(Effects.begin() + Idx);
+  if (!Conditions.empty()) {
+    assert(Idx < Conditions.size());
+    Conditions.erase(Conditions.begin() + Idx);
+  }
+}
+
+FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
+                                              FunctionEffectsRef RHS) {
+  // Optimize for either of the two sets being empty (very common).
+  if (LHS.empty())
+    return FunctionEffectSet(RHS);
+
+  FunctionEffectSet Result(LHS);
+  Result.insert(RHS);
+  return Result;
+}
+
+FunctionEffectSet::Differences
+FunctionEffectSet::differences(const FunctionEffectsRef &Old,
+                               const FunctionEffectsRef &New) {
+
+  FunctionEffectSet::Differences Result;
+
+  FunctionEffectsRef::iterator POld = Old.begin();
+  FunctionEffectsRef::iterator OldEnd = Old.end();
+  FunctionEffectsRef::iterator PNew = New.begin();
+  FunctionEffectsRef::iterator NewEnd = New.end();
+
+  while (true) {
+    int cmp = 0;
+    if (POld == OldEnd) {
+      if (PNew == NewEnd)
+        break;
+      cmp = 1;
+    } else if (PNew == NewEnd)
+      cmp = -1;
+    else {
+      FunctionEffectWithCondition Old = *POld;
+      FunctionEffectWithCondition New = *PNew;
+      if (Old.Effect.kind() < New.Effect.kind())
+        cmp = -1;
+      else if (New.Effect.kind() < Old.Effect.kind())
+        cmp = 1;
+      else {
+        cmp = 0;
+        if (Old.Cond.expr() != New.Cond.expr()) {
+          // TODO: Cases where the expressions are equivalent but
+          // don't have the same identity.
+          Result.push_back(FunctionEffectDiff{
+              Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
+              Old, New});
+        }
+      }
+    }
+
+    if (cmp < 0) {
+      // removal
+      FunctionEffectWithCondition Old = *POld;
+      Result.push_back(FunctionEffectDiff{
+          Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
+      ++POld;
+    } else if (cmp > 0) {
+      // addition
+      FunctionEffectWithCondition New = *PNew;
+      Result.push_back(FunctionEffectDiff{
+          New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
+      ++PNew;
+    } else {
+      ++POld;
+      ++PNew;
+    }
+  }
+  return Result;
+}
+
+LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const auto &CFE : *this) {
+    if (!First)
+      OS << ", ";
+    else
+      First = false;
+    OS << CFE.Effect.name();
+    if (Expr *E = CFE.Cond.expr()) {
+      OS << '(';
+      E->dump();
+      OS << ')';
+    }
+  }
+  OS << "}";
+}
+
+LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
+  FunctionEffectsRef(*this).dump(OS);
+}
+
+// TODO: inline?
+FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
+  if (QT->isReferenceType())
+    QT = QT.getNonReferenceType();
+  if (QT->isPointerType())
+    QT = QT->getPointeeType();
+
+  // TODO: Why aren't these included in isPointerType()?
+  if (const auto *BT = QT->getAs<BlockPointerType>()) {
+    QT = BT->getPointeeType();
+  } else if (const auto *MP = QT->getAs<MemberPointerType>()) {
+    if (MP->isMemberFunctionPointer()) {
+      QT = MP->getPointeeType();
+    }
+  }
----------------
dougsonos wrote:

Aha, thanks!

https://github.com/llvm/llvm-project/pull/84983


More information about the cfe-commits mailing list