Skip to content

Add support for feature availability #10518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -829,10 +829,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
bool isInvalid() const { return Kind == FeatureAvailKind::None; }
};

std::map<StringRef, AvailabilityDomainInfo> AvailabilityDomainMap;
std::map<StringRef, VarDecl *> AvailabilityDomainMap;

void addAvailabilityDomainMap(StringRef Name, AvailabilityDomainInfo Info) {
AvailabilityDomainMap[Name] = Info;
void addAvailabilityDomainMap(StringRef Name, VarDecl *VD) {
AvailabilityDomainMap[Name] = VD;
}

std::pair<DomainAvailabilityAttr *, bool>
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/AST/ExternalASTSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ class ExternalASTSource : public RefCountedBase<ExternalASTSource> {
/// Update an out-of-date identifier.
virtual void updateOutOfDateIdentifier(const IdentifierInfo &II) {}

// Retrieve the variable declaration that has the information for the domain.
virtual Decl *getAvailabilityDomainDecl(StringRef DomainName) {
return nullptr;
}

/// Find all declarations with the given name in the given context,
/// and add them to the context by calling SetExternalVisibleDeclsForName
/// or SetNoExternalVisibleDeclsForName.
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -2457,6 +2457,8 @@ class IfStmt final

bool isObjCAvailabilityCheck() const;

bool isObjCAvailabilityCheckWithDomainName() const;

SourceLocation getBeginLoc() const { return getIfLoc(); }
SourceLocation getEndLoc() const LLVM_READONLY {
if (getElse())
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,9 @@ enum ASTRecordTypes {
UPDATE_MODULE_LOCAL_VISIBLE = 76,

UPDATE_TU_LOCAL_VISIBLE = 77,

/// Record code for availability domain table.
AVAILABILITY_DOMAIN_TABLE = 78,
};

/// Record types used within a source manager block.
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,7 @@ class ASTReader
bool AllowCompatibleConfigurationMismatch, ASTReaderListener *Listener,
bool ValidateDiagnosticOptions);

Decl *getAvailabilityDomainDecl(StringRef DomainName) override;
llvm::Error ReadASTBlock(ModuleFile &F, unsigned ClientLoadCapabilities);
llvm::Error ReadExtensionBlock(ModuleFile &F);
void ReadModuleOffsetMap(ModuleFile &F) const;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Serialization/ASTWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ class ASTWriter : public ASTDeserializationListener,
uint64_t &VisibleBlockOffset,
uint64_t &ModuleLocalBlockOffset,
uint64_t &TULocalBlockOffset);
void writeAvailabilityDomainDecls(ASTContext &Context);
void WriteTypeDeclOffsets();
void WriteFileDeclIDsMap();
void WriteComments(ASTContext &Context);
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Serialization/ModuleFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,10 @@ class ModuleFile {
/// the header files.
void *HeaderFileInfoTable = nullptr;

/// The on-disk hash table that contains information about availability
/// domains.
void *AvailabilityDomainTable = nullptr;

// === Submodule information ===

/// The number of submodules in this module.
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -949,9 +949,15 @@ ASTContext::getFeatureAvailInfo(StringRef FeatureName) const {
if (Kind != FeatureAvailKind::None)
return {Kind};

Decl *D = nullptr;
auto I = AvailabilityDomainMap.find(FeatureName);
if (I != AvailabilityDomainMap.end())
return I->second;
D = I->second;
else if (auto *Source = getExternalSource())
D = Source->getAvailabilityDomainDecl(FeatureName);

if (D)
return getFeatureAvailInfo(D).second;

return {};
}
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/Stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,13 @@ bool IfStmt::isObjCAvailabilityCheck() const {
return isa<ObjCAvailabilityCheckExpr>(getCond());
}

bool IfStmt::isObjCAvailabilityCheckWithDomainName() const {
if (auto *ACE = dyn_cast<ObjCAvailabilityCheckExpr>(getCond());
ACE && ACE->hasDomainName())
return true;
return false;
}

std::optional<Stmt *> IfStmt::getNondiscardedCase(const ASTContext &Ctx) {
if (!isConstexpr() || getCond()->isValueDependent())
return std::nullopt;
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Analysis/ReachableCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ static bool shouldTreatSuccessorsAsReachable(const CFGBlock *B,
if (const auto *IS = dyn_cast<IfStmt>(Term);
IS != nullptr && IS->isConstexpr())
return true;

// Do not treat successors of `if (@available(domain:))` as unreachable.
if (const auto *IS = dyn_cast<IfStmt>(Term))
if (const auto *AC = dyn_cast<ObjCAvailabilityCheckExpr>(
IS->getCond()->IgnoreParenImpCasts());
AC && AC->hasDomainName())
return true;
}

const Stmt *Cond = B->getTerminatorCondition(/* stripParens */ false);
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,15 @@ class ScalarExprEmitter
}

Value *VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) {
if (E->hasDomainName()) {
auto DomainName = E->getDomainName();
ASTContext::AvailabilityDomainInfo Info =
CGF.getContext().getFeatureAvailInfo(DomainName);
assert((Info.Kind == FeatureAvailKind::Dynamic && Info.Call) &&
"ObjCAvailabilityCheckExpr should have been constant evaluated");
return CGF.EmitScalarExpr(Info.Call);
}

VersionTuple Version = E->getVersionAsWritten();

// If we're checking for a platform older than our minimum deployment
Expand Down
15 changes: 15 additions & 0 deletions clang/lib/CodeGen/CGObjCMac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3119,6 +3119,8 @@ static void PushProtocolProperties(
SmallVectorImpl<const ObjCPropertyDecl *> &Properties,
const ObjCProtocolDecl *Proto, bool IsClassProperty) {
for (const auto *PD : Proto->properties()) {
if (Proto->getASTContext().hasUnavailableFeature(PD))
continue;
if (IsClassProperty != PD->isClassProperty())
continue;
if (!PropertySet.insert(PD->getIdentifier()).second)
Expand Down Expand Up @@ -3160,6 +3162,8 @@ llvm::Constant *CGObjCCommonMac::EmitPropertyList(
if (const ObjCInterfaceDecl *OID = dyn_cast<ObjCInterfaceDecl>(OCD))
for (const ObjCCategoryDecl *ClassExt : OID->known_extensions())
for (auto *PD : ClassExt->properties()) {
if (CGM.getContext().hasUnavailableFeature(PD))
continue;
if (IsClassProperty != PD->isClassProperty())
continue;
if (PD->isDirectProperty())
Expand All @@ -3169,6 +3173,8 @@ llvm::Constant *CGObjCCommonMac::EmitPropertyList(
}

for (const auto *PD : OCD->properties()) {
if (CGM.getContext().hasUnavailableFeature(PD))
continue;
if (IsClassProperty != PD->isClassProperty())
continue;
// Don't emit duplicate metadata for properties that were already in a
Expand Down Expand Up @@ -5222,6 +5228,9 @@ void IvarLayoutBuilder::visitAggregate(Iterator begin, Iterator end,
for (; begin != end; ++begin) {
auto field = *begin;

if (CGM.getContext().hasUnavailableFeature(field))
continue;

// Skip over bitfields.
if (field->isBitField()) {
continue;
Expand Down Expand Up @@ -6644,6 +6653,9 @@ void CGObjCNonFragileABIMac::GenerateCategory(const ObjCCategoryImplDecl *OCD) {
void CGObjCNonFragileABIMac::emitMethodConstant(ConstantArrayBuilder &builder,
const ObjCMethodDecl *MD,
bool forProtocol) {
if (CGM.getContext().hasUnavailableFeature(MD))
return;

auto method = builder.beginStruct(ObjCTypes.MethodTy);
method.add(GetMethodVarName(MD->getSelector()));
method.add(GetMethodVarType(MD));
Expand Down Expand Up @@ -6841,6 +6853,9 @@ CGObjCNonFragileABIMac::EmitIvarList(const ObjCImplementationDecl *ID) {
if (!IVD->getDeclName())
continue;

if (CGM.getContext().hasUnavailableFeature(IVD))
continue;

auto ivar = ivars.beginStruct(ObjCTypes.IvarnfABITy);
ivar.add(EmitIvarOffsetVar(ID->getClassInterface(), IVD,
ComputeIvarBaseOffset(CGM, ID, IVD)));
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/CodeGen/CGStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -900,7 +900,7 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {
// the condition and the dead arm of the if/else.
bool CondConstant;
if (ConstantFoldsToSimpleInteger(S.getCond(), CondConstant,
S.isConstexpr())) {
(S.isConstexpr() || S.isObjCAvailabilityCheckWithDomainName()))) {
// Figure out which block (then or else) is executed.
const Stmt *Executed = S.getThen();
const Stmt *Skipped = Else;
Expand All @@ -909,7 +909,7 @@ void CodeGenFunction::EmitIfStmt(const IfStmt &S) {

// If the skipped block has no labels in it, just emit the executed block.
// This avoids emitting dead code and simplifies the CFG substantially.
if (S.isConstexpr() || !ContainsLabel(Skipped)) {
if ((S.isConstexpr() || S.isObjCAvailabilityCheckWithDomainName()) || !ContainsLabel(Skipped)) {
if (CondConstant)
incrementProfileCounter(&S);
if (Executed) {
Expand Down
16 changes: 15 additions & 1 deletion clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5342,6 +5342,9 @@ void CodeGenModule::EmitTentativeDefinition(const VarDecl *D) {
return;
}

if (Context.hasUnavailableFeature(D))
return;

// The tentative definition is the only definition.
EmitGlobalVarDefinition(D);
}
Expand Down Expand Up @@ -6864,6 +6867,9 @@ ConstantAddress CodeGenModule::GetAddrOfGlobalTemporary(
void CodeGenModule::EmitObjCPropertyImplementations(const
ObjCImplementationDecl *D) {
for (const auto *PID : D->property_impls()) {
if (Context.hasUnavailableFeature(PID->getPropertyDecl()))
continue;

// Dynamic is just for type-checking.
if (PID->getPropertyImplementation() == ObjCPropertyImplDecl::Synthesize) {
ObjCPropertyDecl *PD = PID->getPropertyDecl();
Expand All @@ -6888,9 +6894,12 @@ void CodeGenModule::EmitObjCPropertyImplementations(const
static bool needsDestructMethod(ObjCImplementationDecl *impl) {
const ObjCInterfaceDecl *iface = impl->getClassInterface();
for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin();
ivar; ivar = ivar->getNextIvar())
ivar; ivar = ivar->getNextIvar()) {
if (impl->getASTContext().hasUnavailableFeature(ivar))
continue;
if (ivar->getType().isDestructedType())
return true;
}

return false;
}
Expand Down Expand Up @@ -7022,6 +7031,9 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
if (auto *FD = dyn_cast<FunctionDecl>(D); FD && FD->isImmediateFunction())
return;

if (Context.hasUnavailableFeature(D))
return;

switch (D->getKind()) {
case Decl::CXXConversion:
case Decl::CXXMethod:
Expand Down Expand Up @@ -7152,6 +7164,8 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
}
case Decl::ObjCMethod: {
auto *OMD = cast<ObjCMethodDecl>(D);
if (Context.hasUnavailableFeature(OMD->getClassInterface()))
break;
// If this is not a prototype, emit the body.
if (OMD->getBody())
CodeGenFunction(*this).GenerateObjCMethod(OMD);
Expand Down
7 changes: 2 additions & 5 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15950,11 +15950,8 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
}
}

std::pair<StringRef, ASTContext::AvailabilityDomainInfo> ADInfo =
getASTContext().getFeatureAvailInfo(VD);

if (!ADInfo.first.empty())
getASTContext().addAvailabilityDomainMap(ADInfo.first, ADInfo.second);
if (auto *Attr = VD->getAttr<AvailabilityDomainAttr>())
getASTContext().addAvailabilityDomainMap(Attr->getName()->getName(), VD);

const DeclContext *DC = VD->getDeclContext();
// If there's a #pragma GCC visibility in scope, and this isn't a class
Expand Down
7 changes: 0 additions & 7 deletions clang/lib/Sema/SemaFeatureAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,6 @@ class DiagnoseUnguardedFeatureAvailability
return true;
}

bool VisitLabelStmt(LabelStmt *LS) {
if (isConditionallyGuardedByFeature())
SemaRef.Diag(LS->getBeginLoc(),
diag::err_label_in_conditionally_guarded_feature);
return true;
}

bool VisitTypeLoc(TypeLoc Ty);

void IssueDiagnostics() {
Expand Down
76 changes: 76 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3473,6 +3473,74 @@ ASTReader::ReadControlBlock(ModuleFile &F,
}
}

AvailabilityDomainsTableReaderTrait::hash_value_type
AvailabilityDomainsTableReaderTrait::ComputeHash(internal_key_type Key) {
return llvm::djbHash(Key);
}

std::pair<unsigned, unsigned>
AvailabilityDomainsTableReaderTrait::ReadKeyDataLength(const uint8_t *&Data) {
unsigned KeyLength =
llvm::support::endian::readNext<uint16_t, llvm::endianness::little>(Data);
unsigned DataLength =
llvm::support::endian::readNext<uint16_t, llvm::endianness::little>(Data);
return {KeyLength, DataLength};
}

AvailabilityDomainsTableReaderTrait::internal_key_type
AvailabilityDomainsTableReaderTrait::ReadKey(const uint8_t *Data,
unsigned Length) {
return llvm::StringRef(reinterpret_cast<const char *>(Data), Length);
}

AvailabilityDomainsTableReaderTrait::data_type
AvailabilityDomainsTableReaderTrait::ReadData(internal_key_type Key,
const uint8_t *Data,
unsigned Length) {
return llvm::support::endian::readNext<data_type, llvm::endianness::little>(
Data);
}

class AvailabilityDomainVisitor {
std::optional<Decl *> D;
StringRef DomainName;
ASTReader &Reader;

public:
explicit AvailabilityDomainVisitor(StringRef DomainName, ASTReader &Reader)
: DomainName(DomainName), Reader(Reader) {}

bool operator()(ModuleFile &M) {
AvailabilityDomainLookupTable *Table =
static_cast<AvailabilityDomainLookupTable *>(M.AvailabilityDomainTable);

if (!Table)
return false;

// Look in the on-disk hash table for an entry for this domain name.
AvailabilityDomainLookupTable::iterator Pos = Table->find(DomainName);
if (Pos == Table->end())
return false;

LocalDeclID LocalID = LocalDeclID::get(Reader, M, *Pos);
GlobalDeclID ID = Reader.getGlobalDeclID(M, LocalID);
D = Reader.GetDecl(ID);
return true;
}

std::optional<Decl *> getDecl() const { return D; }
};

Decl *ASTReader::getAvailabilityDomainDecl(StringRef DomainName) {
AvailabilityDomainVisitor Visitor(DomainName, *this);
ModuleMgr.visit(Visitor);

if (std::optional<Decl *> D = Visitor.getDecl())
return *D;

return nullptr;
}

llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
unsigned ClientLoadCapabilities) {
BitstreamCursor &Stream = F.Stream;
Expand Down Expand Up @@ -3815,6 +3883,14 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
UnusedFileScopedDecls.push_back(ReadDeclID(F, Record, I));
break;

case AVAILABILITY_DOMAIN_TABLE: {
auto Data = Blob.data();
F.AvailabilityDomainTable = AvailabilityDomainLookupTable::Create(
(const unsigned char *)Data + Record[0], (const unsigned char *)Data,
AvailabilityDomainsTableReaderTrait());
break;
}

case DELEGATING_CTORS:
for (unsigned I = 0, N = Record.size(); I != N; /*in loop*/)
DelegatingCtorDecls.push_back(ReadDeclID(F, Record, I));
Expand Down
Loading