[clang][modules] Unify "context hash" and "specific module cache path"#176215
Merged
jansvoboda11 merged 1 commit intollvm:mainfrom Jan 15, 2026
Merged
[clang][modules] Unify "context hash" and "specific module cache path"#176215jansvoboda11 merged 1 commit intollvm:mainfrom
jansvoboda11 merged 1 commit intollvm:mainfrom
Conversation
Member
|
@llvm/pr-subscribers-clang-modules @llvm/pr-subscribers-clang Author: Jan Svoboda (jansvoboda11) ChangesThis PR unifies the terminology for:
NFCI Patch is 22.82 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/176215.diff 16 Files Affected:
diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h
index d0b41e9f18a1b..f56da69a05caf 100644
--- a/clang/include/clang/Frontend/CompilerInstance.h
+++ b/clang/include/clang/Frontend/CompilerInstance.h
@@ -738,9 +738,9 @@ class CompilerInstance : public ModuleLoader {
GetDependencyDirectives = std::move(Getter);
}
- std::string getSpecificModuleCachePath(StringRef ModuleHash);
+ std::string getSpecificModuleCachePath(StringRef ContextHash);
std::string getSpecificModuleCachePath() {
- return getSpecificModuleCachePath(getInvocation().getModuleHash());
+ return getSpecificModuleCachePath(getInvocation().computeContextHash());
}
/// Create the AST context.
diff --git a/clang/include/clang/Frontend/CompilerInvocation.h b/clang/include/clang/Frontend/CompilerInvocation.h
index 903583e3fe79c..6fa6cd5d95534 100644
--- a/clang/include/clang/Frontend/CompilerInvocation.h
+++ b/clang/include/clang/Frontend/CompilerInvocation.h
@@ -308,9 +308,11 @@ class CompilerInvocation : public CompilerInvocationBase {
const LangOptions &LangOpts,
const llvm::Triple &Triple);
- /// Retrieve a module hash string that is suitable for uniquely
- /// identifying the conditions under which the module was built.
- std::string getModuleHash() const;
+ /// Compute the context hash - a string that uniquely identifies compiler
+ /// settings.
+ /// This is currently used mainly for distinguishing different variants of the
+ /// same implicitly-built Clang module.
+ std::string computeContextHash() const;
/// Check that \p Args can be parsed and re-serialized without change,
/// emiting diagnostics for any differences.
diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h
index 5369c872ac1cd..252e421e796f4 100644
--- a/clang/include/clang/Lex/HeaderSearch.h
+++ b/clang/include/clang/Lex/HeaderSearch.h
@@ -276,11 +276,11 @@ class HeaderSearch {
/// a system header.
std::vector<std::pair<std::string, bool>> SystemHeaderPrefixes;
- /// The hash used for module cache paths.
- std::string ModuleHash;
+ /// The context hash used in SpecificModuleCachePath (unless suppressed).
+ std::string ContextHash;
- /// The path to the module cache.
- std::string ModuleCachePath;
+ /// The specific module cache path containing ContextHash (unless suppressed).
+ std::string SpecificModuleCachePath;
/// All of the preprocessor-specific data about files that are
/// included, indexed by the FileEntry's UID.
@@ -433,19 +433,21 @@ class HeaderSearch {
return {};
}
- /// Set the hash to use for module cache paths.
- void setModuleHash(StringRef Hash) { ModuleHash = std::string(Hash); }
+ /// Set the context hash to use for module cache paths.
+ void setContextHash(StringRef Hash) { ContextHash = std::string(Hash); }
- /// Set the path to the module cache.
- void setModuleCachePath(StringRef CachePath) {
- ModuleCachePath = std::string(CachePath);
+ /// Set the module cache path with the context hash (unless suppressed).
+ void setSpecificModuleCachePath(StringRef Path) {
+ SpecificModuleCachePath = std::string(Path);
}
- /// Retrieve the module hash.
- StringRef getModuleHash() const { return ModuleHash; }
+ /// Retrieve the context hash.
+ StringRef getContextHash() const { return ContextHash; }
- /// Retrieve the path to the module cache.
- StringRef getModuleCachePath() const { return ModuleCachePath; }
+ /// Retrieve the module cache path with the context hash (unless suppressed).
+ StringRef getSpecificModuleCachePath() const {
+ return SpecificModuleCachePath;
+ }
/// Forget everything we know about headers so far.
void ClearFileInfo() {
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 2b3fa6d52f163..d1c1b98f78f2c 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -2025,7 +2025,7 @@ class ASTReader
const PCHContainerReader &PCHContainerRdr, const LangOptions &LangOpts,
const CodeGenOptions &CGOpts, const TargetOptions &TargetOpts,
const PreprocessorOptions &PPOpts, const HeaderSearchOptions &HSOpts,
- StringRef ExistingModuleCachePath,
+ StringRef SpecificModuleCachePath,
bool RequireStrictOptionMatches = false);
/// Returns the suggested contents of the predefines buffer,
diff --git a/clang/lib/DependencyScanning/ModuleDepCollector.cpp b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
index 39bd2e2ab0032..70c94bca10275 100644
--- a/clang/lib/DependencyScanning/ModuleDepCollector.cpp
+++ b/clang/lib/DependencyScanning/ModuleDepCollector.cpp
@@ -636,7 +636,7 @@ void ModuleDepCollectorPP::EndOfMainFile() {
handleTopLevelModule(M);
MDC.Consumer.handleContextHash(
- MDC.ScanInstance.getInvocation().getModuleHash());
+ MDC.ScanInstance.getInvocation().computeContextHash());
MDC.Consumer.handleDependencyOutputOpts(*MDC.Opts);
diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp
index 0570b8ece24f8..ee22e16bc202d 100644
--- a/clang/lib/Frontend/ASTUnit.cpp
+++ b/clang/lib/Frontend/ASTUnit.cpp
@@ -763,7 +763,7 @@ std::unique_ptr<ASTUnit> ASTUnit::LoadFromASTFile(
AST->getHeaderSearchOpts(), AST->getSourceManager(),
AST->getDiagnostics(), AST->getLangOpts(),
/*Target=*/nullptr);
- AST->HeaderInfo->setModuleCachePath(SpecificModuleCachePath);
+ AST->HeaderInfo->setSpecificModuleCachePath(SpecificModuleCachePath);
AST->PP = std::make_shared<Preprocessor>(
*AST->PPOpts, AST->getDiagnostics(), *AST->LangOpts,
diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp
index 088538c7449d2..2cd7888b0d192 100644
--- a/clang/lib/Frontend/CompilerInstance.cpp
+++ b/clang/lib/Frontend/CompilerInstance.cpp
@@ -487,10 +487,10 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) {
PP->setPreprocessedOutput(getPreprocessorOutputOpts().ShowCPP);
if (PP->getLangOpts().Modules && PP->getLangOpts().ImplicitModules) {
- std::string ModuleHash = getInvocation().getModuleHash();
- PP->getHeaderSearchInfo().setModuleHash(ModuleHash);
- PP->getHeaderSearchInfo().setModuleCachePath(
- getSpecificModuleCachePath(ModuleHash));
+ std::string ContextHash = getInvocation().computeContextHash();
+ PP->getHeaderSearchInfo().setContextHash(ContextHash);
+ PP->getHeaderSearchInfo().setSpecificModuleCachePath(
+ getSpecificModuleCachePath(ContextHash));
}
// Handle generating dependencies, if requested.
@@ -546,7 +546,8 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) {
PP->setDependencyDirectivesGetter(*GetDependencyDirectives);
}
-std::string CompilerInstance::getSpecificModuleCachePath(StringRef ModuleHash) {
+std::string
+CompilerInstance::getSpecificModuleCachePath(StringRef ContextHash) {
assert(FileMgr && "Specific module cache path requires a FileManager");
// Set up the module path, including the hash for the module-creation options.
@@ -554,7 +555,7 @@ std::string CompilerInstance::getSpecificModuleCachePath(StringRef ModuleHash) {
normalizeModuleCachePath(*FileMgr, getHeaderSearchOpts().ModuleCachePath,
SpecificModuleCache);
if (!SpecificModuleCache.empty() && !getHeaderSearchOpts().DisableModuleHash)
- llvm::sys::path::append(SpecificModuleCache, ModuleHash);
+ llvm::sys::path::append(SpecificModuleCache, ContextHash);
return std::string(SpecificModuleCache);
}
@@ -1162,7 +1163,8 @@ std::unique_ptr<CompilerInstance> CompilerInstance::cloneForModuleCompileImpl(
DiagnosticOptions &DiagOpts = Invocation->getDiagnosticOpts();
DiagOpts.VerifyDiagnostics = 0;
- assert(getInvocation().getModuleHash() == Invocation->getModuleHash() &&
+ assert(getInvocation().computeContextHash() ==
+ Invocation->computeContextHash() &&
"Module hash mismatch!");
// Construct a compiler instance that will be used to actually create the
@@ -1621,7 +1623,10 @@ void CompilerInstance::createASTReader() {
// If we're implicitly building modules but not currently recursively
// building a module, check whether we need to prune the module cache.
if (getSourceManager().getModuleBuildStack().empty() &&
- !getPreprocessor().getHeaderSearchInfo().getModuleCachePath().empty())
+ !getPreprocessor()
+ .getHeaderSearchInfo()
+ .getSpecificModuleCachePath()
+ .empty())
ModCache->maybePrune(getHeaderSearchOpts().ModuleCachePath,
getHeaderSearchOpts().ModuleCachePruneInterval,
getHeaderSearchOpts().ModuleCachePruneAfter);
@@ -2176,7 +2181,10 @@ void CompilerInstance::makeModuleVisible(Module *Mod,
GlobalModuleIndex *CompilerInstance::loadGlobalModuleIndex(
SourceLocation TriggerLoc) {
- if (getPreprocessor().getHeaderSearchInfo().getModuleCachePath().empty())
+ if (getPreprocessor()
+ .getHeaderSearchInfo()
+ .getSpecificModuleCachePath()
+ .empty())
return nullptr;
if (!TheASTReader)
createASTReader();
@@ -2191,10 +2199,12 @@ GlobalModuleIndex *CompilerInstance::loadGlobalModuleIndex(
if (!GlobalIndex && shouldBuildGlobalModuleIndex() && hasFileManager() &&
hasPreprocessor()) {
llvm::sys::fs::create_directories(
- getPreprocessor().getHeaderSearchInfo().getModuleCachePath());
+ getPreprocessor().getHeaderSearchInfo().getSpecificModuleCachePath());
if (llvm::Error Err = GlobalModuleIndex::writeIndex(
getFileManager(), getPCHContainerReader(),
- getPreprocessor().getHeaderSearchInfo().getModuleCachePath())) {
+ getPreprocessor()
+ .getHeaderSearchInfo()
+ .getSpecificModuleCachePath())) {
// FIXME this drops the error on the floor. This code is only used for
// typo correction and drops more than just this one source of errors
// (such as the directory creation failure above). It should handle the
@@ -2228,7 +2238,9 @@ GlobalModuleIndex *CompilerInstance::loadGlobalModuleIndex(
if (RecreateIndex) {
if (llvm::Error Err = GlobalModuleIndex::writeIndex(
getFileManager(), getPCHContainerReader(),
- getPreprocessor().getHeaderSearchInfo().getModuleCachePath())) {
+ getPreprocessor()
+ .getHeaderSearchInfo()
+ .getSpecificModuleCachePath())) {
// FIXME As above, this drops the error on the floor.
consumeError(std::move(Err));
return nullptr;
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index ab14661e06e11..5a79634773866 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -5156,7 +5156,7 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Invocation,
Invocation, DummyInvocation, CommandLineArgs, Diags, Argv0);
}
-std::string CompilerInvocation::getModuleHash() const {
+std::string CompilerInvocation::computeContextHash() const {
// FIXME: Consider using SHA1 instead of MD5.
llvm::HashBuilder<llvm::MD5, llvm::endianness::native> HBuilder;
diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp
index 5c2ac862b5e75..7810f0999f7d6 100644
--- a/clang/lib/Frontend/FrontendAction.cpp
+++ b/clang/lib/Frontend/FrontendAction.cpp
@@ -1317,7 +1317,7 @@ llvm::Error FrontendAction::Execute() {
if (CI.shouldBuildGlobalModuleIndex() && CI.hasFileManager() &&
CI.hasPreprocessor()) {
StringRef Cache =
- CI.getPreprocessor().getHeaderSearchInfo().getModuleCachePath();
+ CI.getPreprocessor().getHeaderSearchInfo().getSpecificModuleCachePath();
if (!Cache.empty()) {
if (llvm::Error Err = GlobalModuleIndex::writeIndex(
CI.getFileManager(), CI.getPCHContainerReader(), Cache)) {
diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp
index b2ed24f765dab..5f52d62bd36ed 100644
--- a/clang/lib/Lex/HeaderSearch.cpp
+++ b/clang/lib/Lex/HeaderSearch.cpp
@@ -243,11 +243,11 @@ std::string HeaderSearch::getPrebuiltImplicitModuleFileName(Module *Module) {
getModuleMap().getModuleMapFileForUniquing(Module);
StringRef ModuleName = Module->Name;
StringRef ModuleMapPath = ModuleMap->getName();
- StringRef ModuleCacheHash = HSOpts.DisableModuleHash ? "" : getModuleHash();
+ StringRef ContextHash = HSOpts.DisableModuleHash ? "" : getContextHash();
for (const std::string &Dir : HSOpts.PrebuiltModulePaths) {
SmallString<256> CachePath(Dir);
FileMgr.makeAbsolutePath(CachePath);
- llvm::sys::path::append(CachePath, ModuleCacheHash);
+ llvm::sys::path::append(CachePath, ContextHash);
std::string FileName =
getCachedModuleFileNameImpl(ModuleName, ModuleMapPath, CachePath);
if (!FileName.empty() && getFileMgr().getOptionalFileRef(FileName))
@@ -259,7 +259,7 @@ std::string HeaderSearch::getPrebuiltImplicitModuleFileName(Module *Module) {
std::string HeaderSearch::getCachedModuleFileName(StringRef ModuleName,
StringRef ModuleMapPath) {
return getCachedModuleFileNameImpl(ModuleName, ModuleMapPath,
- getModuleCachePath());
+ getSpecificModuleCachePath());
}
std::string HeaderSearch::getCachedModuleFileNameImpl(StringRef ModuleName,
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 66cf484bb5cb6..5cafcc56871c6 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -944,15 +944,15 @@ bool SimpleASTReaderListener::ReadPreprocessorOptions(
/// \returns true when the module cache paths differ.
static bool checkModuleCachePath(
llvm::vfs::FileSystem &VFS, StringRef SpecificModuleCachePath,
- StringRef ExistingModuleCachePath, StringRef ASTFilename,
+ StringRef ExistingSpecificModuleCachePath, StringRef ASTFilename,
DiagnosticsEngine *Diags, const LangOptions &LangOpts,
const PreprocessorOptions &PPOpts, const HeaderSearchOptions &HSOpts,
const HeaderSearchOptions &ASTFileHSOpts) {
if (!LangOpts.Modules || PPOpts.AllowPCHWithDifferentModulesCachePath ||
- SpecificModuleCachePath == ExistingModuleCachePath)
+ SpecificModuleCachePath == ExistingSpecificModuleCachePath)
return false;
auto EqualOrErr =
- VFS.equivalent(SpecificModuleCachePath, ExistingModuleCachePath);
+ VFS.equivalent(SpecificModuleCachePath, ExistingSpecificModuleCachePath);
if (EqualOrErr && *EqualOrErr)
return false;
if (Diags) {
@@ -965,7 +965,8 @@ static bool checkModuleCachePath(
Diags->Report(clang::diag::warn_ast_file_config_mismatch) << ASTFilename;
else
Diags->Report(diag::err_ast_file_modulecache_mismatch)
- << SpecificModuleCachePath << ExistingModuleCachePath << ASTFilename;
+ << SpecificModuleCachePath << ExistingSpecificModuleCachePath
+ << ASTFilename;
}
return true;
}
@@ -977,7 +978,7 @@ bool PCHValidator::ReadHeaderSearchOptions(const HeaderSearchOptions &HSOpts,
const HeaderSearch &HeaderSearchInfo = PP.getHeaderSearchInfo();
return checkModuleCachePath(
Reader.getFileManager().getVirtualFileSystem(), SpecificModuleCachePath,
- HeaderSearchInfo.getModuleCachePath(), ASTFilename,
+ HeaderSearchInfo.getSpecificModuleCachePath(), ASTFilename,
Complain ? &Reader.Diags : nullptr, PP.getLangOpts(),
PP.getPreprocessorOpts(), HeaderSearchInfo.getHeaderSearchOpts(), HSOpts);
}
@@ -1628,9 +1629,9 @@ bool ASTReader::ReadSpecializations(ModuleFile &M, BitstreamCursor &Cursor,
void ASTReader::Error(StringRef Msg) const {
Error(diag::err_fe_ast_file_malformed, Msg);
if (PP.getLangOpts().Modules &&
- !PP.getHeaderSearchInfo().getModuleCachePath().empty()) {
+ !PP.getHeaderSearchInfo().getSpecificModuleCachePath().empty()) {
Diag(diag::note_module_cache_path)
- << PP.getHeaderSearchInfo().getModuleCachePath();
+ << PP.getHeaderSearchInfo().getSpecificModuleCachePath();
}
}
@@ -4757,10 +4758,10 @@ bool ASTReader::loadGlobalIndex() {
// Try to load the global index.
TriedLoadingGlobalIndex = true;
- StringRef ModuleCachePath
- = getPreprocessor().getHeaderSearchInfo().getModuleCachePath();
+ StringRef SpecificModuleCachePath =
+ getPreprocessor().getHeaderSearchInfo().getSpecificModuleCachePath();
std::pair<GlobalModuleIndex *, llvm::Error> Result =
- GlobalModuleIndex::readIndex(ModuleCachePath);
+ GlobalModuleIndex::readIndex(SpecificModuleCachePath);
if (llvm::Error Err = std::move(Result.second)) {
assert(!Result.first);
consumeError(std::move(Err)); // FIXME this drops errors on the floor.
@@ -5755,7 +5756,7 @@ namespace {
const TargetOptions &ExistingTargetOpts;
const PreprocessorOptions &ExistingPPOpts;
const HeaderSearchOptions &ExistingHSOpts;
- std::string ExistingModuleCachePath;
+ std::string ExistingSpecificModuleCachePath;
FileManager &FileMgr;
bool StrictOptionMatches;
@@ -5765,13 +5766,13 @@ namespace {
const TargetOptions &ExistingTargetOpts,
const PreprocessorOptions &ExistingPPOpts,
const HeaderSearchOptions &ExistingHSOpts,
- StringRef ExistingModuleCachePath, FileManager &FileMgr,
- bool StrictOptionMatches)
+ StringRef ExistingSpecificModuleCachePath,
+ FileManager &FileMgr, bool StrictOptionMatches)
: ExistingLangOpts(ExistingLangOpts), ExistingCGOpts(ExistingCGOpts),
ExistingTargetOpts(ExistingTargetOpts),
ExistingPPOpts(ExistingPPOpts), ExistingHSOpts(ExistingHSOpts),
- ExistingModuleCachePath(ExistingModuleCachePath), FileMgr(FileMgr),
- StrictOptionMatches(StrictOptionMatches) {}
+ ExistingSpecificModuleCachePath(ExistingSpecificModuleCachePath),
+ FileMgr(FileMgr), StrictOptionMatches(StrictOptionMatches) {}
bool ReadLanguageOptions(const LangOptions &LangOpts,
StringRef ModuleFilename, bool Complain,
@@ -5800,8 +5801,8 @@ namespace {
bool Complain) override {
return checkModuleCachePath(
FileMgr.getVirtualFileSystem(), SpecificModuleCachePath,
- ExistingModuleCachePath, ASTFilename, nullptr, ExistingLangOpts,
- ExistingPPOpts, ExistingHSOpts, HSOpts);
+ ExistingSpecificModuleCachePath, ASTFilename, nullptr,
+ ExistingLangOpts, ExistingPPOpts, ExistingHSOpts, HSOpts);
}
bool ReadPreprocessorOptions(const PreprocessorOptions &PPOpts,
@@ -6144,9 +6145,9 @@ bool ASTReader::isAcceptableASTFile(
const PCHContainerReader &PCHContainerRdr, const LangOptions &LangOpts,
const CodeGenOptions &CGOpts, const TargetOptions &TargetOpts,
const PreprocessorOptions &PPOpts, const HeaderSearchOptions &HSOpts,
- StringRef ExistingModuleCachePath, bool RequireStrictOptionMatches) {
+ StringRef SpecificModuleCachePath, bool RequireStrictOptionMatches) {
SimplePCHValidator validator(LangOpts, CGOpts, TargetOpts, PPOpts, HSOpts,
- ExistingModuleCachePath, FileMgr,
+ SpecificModuleCachePath, FileMgr,
RequireStrictOptionMatches);
return !readASTFileControlBlock(Filename, FileMgr, ModCache, PCHContainerRdr,
/*FindModuleFileExtensions=*/false, validator,
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 39104da10d0b7..cd8a843c77305 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -1715,7 +1715,9 @@ void ASTWriter::WriteControlBlock(Preprocessor &PP, StringRef isysroot) {
Record.push_back(HSOpts.UseStandardCXXIncludes);
Record.push_back(HSOpts.UseLibcx...
[truncated]
|
cyndyishida
approved these changes
Jan 15, 2026
Priyanshu3820
pushed a commit
to Priyanshu3820/llvm-project
that referenced
this pull request
Jan 18, 2026
llvm#176215) This PR unifies the terminology for: * "context hash" - previously ambiguously referred to as "module hash" or as overly specific "module context hash" * "specific module cache path" - previously referred to as just "module cache path" - hard to distinguish from the command-line-provided module cache path without the context hash NFCI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR unifies the terminology for:
NFCI