hans created this revision.
hans added reviewers: rsmith, rnk, thakis.
hans added subscribers: compnerd, majnemer, benlangmuir, cfe-commits.

This is useful when dealing with headers that are normally used on 
case-insensitive filesystems, such as the Windows SDK, when cross-compiling 
from a file-system that is case-sensitive (such as Linux). A prime example 
would be compiling "#include <windows.h>" (the file is really called Windows.h).

There has been a patch for this before (http://reviews.llvm.org/D2972), but 
this one is more general, as it works on the virtual filesystem layer, and also 
supports case-insensitive lookups of parent directories not just the filename.

I was initially worried about performance here. The most common case is trying 
to lookup a header file in the wrong include path, and instead of a simple 
stat, this patch will cause a full directory listing for each such attempt.

I tried a version of this patch that used a small Bloom filter for each 
directory to avoid unnecessary searches for non-existing files, but that broke 
a lot of modules tests. It seems those tests would end up writing to a module 
cache directory, but the vfs wasn't aware of those writes so subsequent 
attempts to open files in that directory would fail because of the filter (yay, 
cache invalidation). Maybe that could be fixed, and maybe such filtering would 
be useful for broadly too, but perhaps we could look at that separately.

I measured compile-time of a file in V8 on Linux with and without 
case-sensitive includes, and also with the filter:

  Case sensitive: (default)   8.60s +- 2.90%
  Case insensitive:           8.74s +- 1.18%
  Case insensitive w/ filter: 8.46s +- 0.82%

Adding some folks who were on Saleem's patch.

http://reviews.llvm.org/D21113

Files:
  include/clang/Basic/VirtualFileSystem.h
  include/clang/Driver/Options.td
  include/clang/Lex/HeaderSearchOptions.h
  lib/Basic/VirtualFileSystem.cpp
  lib/Driver/Tools.cpp
  lib/Frontend/CompilerInvocation.cpp
  test/Driver/cl-options.c
  test/Frontend/Inputs/case-insensitive-includes.h
  test/Frontend/case-insensitive-includes.c
  unittests/Basic/VirtualFileSystemTest.cpp

Index: unittests/Basic/VirtualFileSystemTest.cpp
===================================================================
--- unittests/Basic/VirtualFileSystemTest.cpp
+++ unittests/Basic/VirtualFileSystemTest.cpp
@@ -107,8 +107,15 @@
 
   vfs::directory_iterator dir_begin(const Twine &Dir,
                                     std::error_code &EC) override {
-    return vfs::directory_iterator(
+    auto I = vfs::directory_iterator(
         std::make_shared<DirIterImpl>(FilesAndDirs, Dir));
+
+    // Even if there is no entry for /foo, /foo/bar may exist, so only set the
+    // error code if /foo returns an empty iterator.
+    if (I == vfs::directory_iterator())
+      EC = status(Dir).getError();
+
+    return I;
   }
 
   void addEntry(StringRef Path, const vfs::Status &Status) {
@@ -1164,3 +1171,52 @@
   }
   EXPECT_EQ(I, E);
 }
+
+class CaseInsensitiveFileSystemTest : public ::testing::Test {
+private:
+  IntrusiveRefCntPtr<DummyFileSystem> Base;
+
+protected:
+  IntrusiveRefCntPtr<clang::vfs::FileSystem> FS;
+
+  CaseInsensitiveFileSystemTest()
+      : Base(new DummyFileSystem()),
+        FS(new clang::vfs::CaseInsensitiveFileSystem(Base)) {
+    Base->addRegularFile("/foo");
+    Base->addDirectory("/bar");
+    Base->addRegularFile("/bar/baz");
+  }
+};
+
+TEST_F(CaseInsensitiveFileSystemTest, Basic) {
+  // Not just accepting anything.
+  auto Status = FS->status("/F00");
+  ASSERT_EQ(llvm::errc::no_such_file_or_directory, Status.getError());
+
+  // Case-insensitive file is found.
+  Status = FS->status("/FoO");
+  ASSERT_FALSE(Status.getError());
+
+  // Case-insensitive dir works too.
+  Status = FS->status("/bAr/baZ");
+  ASSERT_FALSE(Status.getError());
+
+  // Test openFileForRead.
+  auto File = FS->openFileForRead("/F00");
+  ASSERT_EQ(llvm::errc::no_such_file_or_directory, File.getError());
+  File = FS->openFileForRead("/Foo");
+  ASSERT_FALSE(File.getError());
+  File = FS->openFileForRead("/Bar/Baz");
+  ASSERT_FALSE(File.getError());
+
+  // Test directory listing.
+  std::error_code EC;
+  auto Dir = FS->dir_begin("/b4r", EC);
+  ASSERT_EQ(llvm::errc::no_such_file_or_directory, EC);
+  Dir = FS->dir_begin("/bAr", EC);
+  ASSERT_FALSE(EC);
+  ASSERT_EQ("/bar/baz", Dir->getName());
+  Dir.increment(EC);
+  ASSERT_FALSE(EC);
+  ASSERT_EQ(vfs::directory_iterator(), Dir);
+}
Index: test/Frontend/case-insensitive-includes.c
===================================================================
--- /dev/null
+++ test/Frontend/case-insensitive-includes.c
@@ -0,0 +1,3 @@
+// RUN: %clang_cc1 -fsyntax-only -fcase-insensitive-includes -verify %s
+
+#include "InpUts/CasE-InsensitivE-Includes.h" // expected-no-diagnostics
Index: test/Driver/cl-options.c
===================================================================
--- test/Driver/cl-options.c
+++ test/Driver/cl-options.c
@@ -466,6 +466,7 @@
 // RUN:     -mllvm -disable-llvm-optzns \
 // RUN:     -Wunused-variable \
 // RUN:     -fmacro-backtrace-limit=0 \
+// RUN:     -fcase-insensitive-includes \
 // RUN:     -Werror /Zs -- %s 2>&1
 
 
Index: lib/Frontend/CompilerInvocation.cpp
===================================================================
--- lib/Frontend/CompilerInvocation.cpp
+++ lib/Frontend/CompilerInvocation.cpp
@@ -1447,6 +1447,8 @@
 
   for (const Arg *A : Args.filtered(OPT_ivfsoverlay))
     Opts.AddVFSOverlayFile(A->getValue());
+
+  Opts.CaseInsensitive = Args.hasArg(OPT_fcase_insensitive_includes);
 }
 
 void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK,
@@ -2538,12 +2540,8 @@
   GraveYard[Idx] = Ptr;
 }
 
-IntrusiveRefCntPtr<vfs::FileSystem>
-createVFSFromCompilerInvocation(const CompilerInvocation &CI,
-                                DiagnosticsEngine &Diags) {
-  if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty())
-    return vfs::getRealFileSystem();
-
+static IntrusiveRefCntPtr<vfs::FileSystem>
+getOverlayFS(const CompilerInvocation &CI, DiagnosticsEngine &Diags) {
   IntrusiveRefCntPtr<vfs::OverlayFileSystem>
     Overlay(new vfs::OverlayFileSystem(vfs::getRealFileSystem()));
   // earlier vfs files are on the bottom
@@ -2565,4 +2563,20 @@
   }
   return Overlay;
 }
+
+IntrusiveRefCntPtr<vfs::FileSystem>
+createVFSFromCompilerInvocation(const CompilerInvocation &CI,
+                                DiagnosticsEngine &Diags) {
+  IntrusiveRefCntPtr<vfs::FileSystem> FS;
+
+  if (CI.getHeaderSearchOpts().VFSOverlayFiles.empty())
+    FS = vfs::getRealFileSystem();
+  else
+    FS = getOverlayFS(CI, Diags);
+
+  if (CI.getHeaderSearchOpts().CaseInsensitive)
+    FS = new vfs::CaseInsensitiveFileSystem(FS);
+
+  return FS;
+}
 } // end namespace clang
Index: lib/Driver/Tools.cpp
===================================================================
--- lib/Driver/Tools.cpp
+++ lib/Driver/Tools.cpp
@@ -570,6 +570,9 @@
   // Add CUDA include arguments, if needed.
   if (types::isCuda(Inputs[0].getType()))
     getToolChain().AddCudaIncludeArgs(Args, CmdArgs);
+
+  if (Args.hasArg(options::OPT_fcase_insensitive_includes))
+    CmdArgs.push_back("-fcase-insensitive-includes");
 }
 
 // FIXME: Move to target hook.
Index: lib/Basic/VirtualFileSystem.cpp
===================================================================
--- lib/Basic/VirtualFileSystem.cpp
+++ lib/Basic/VirtualFileSystem.cpp
@@ -392,6 +392,94 @@
       std::make_shared<OverlayFSDirIterImpl>(Dir, *this, EC));
 }
 
+//===-----------------------------------------------------------------------===/
+// CaseInsensitiveFileSystem implementation
+//===-----------------------------------------------------------------------===/
+
+CaseInsensitiveFileSystem::CaseInsensitiveFileSystem(
+    IntrusiveRefCntPtr<FileSystem> Base)
+    : Base(Base) {}
+
+std::error_code CaseInsensitiveFileSystem::findCaseInsensitivePath(
+    StringRef Path, SmallVectorImpl<char> &FoundPath) {
+  StringRef FileName = llvm::sys::path::filename(Path);
+  StringRef Dir = llvm::sys::path::parent_path(Path);
+
+  // Open the directory for iterating. In the common case, the directory exists.
+  std::error_code EC;
+  directory_iterator I = Base->dir_begin(Dir, EC), E;
+
+  if (EC == errc::no_such_file_or_directory) {
+    // If the dir doesn't exist, try to find it and try again.
+    if (llvm::sys::path::parent_path(Dir).empty())
+      return EC;
+    SmallVector<char, 512> NewDir;
+    if ((EC = findCaseInsensitivePath(Dir, NewDir)))
+      return EC;
+    llvm::sys::path::append(NewDir, FileName);
+    return findCaseInsensitivePath(StringRef(NewDir.data(), NewDir.size()),
+                                   FoundPath);
+  }
+
+  for (; !EC && I != E; I.increment(EC)) {
+    StringRef DirEntry = llvm::sys::path::filename(I->getName());
+    if (DirEntry.equals_lower(FileName)) {
+      llvm::sys::path::append(FoundPath, Dir, DirEntry);
+      return std::error_code();
+    }
+  }
+
+  return EC ? EC : llvm::errc::no_such_file_or_directory;
+}
+
+llvm::ErrorOr<Status> CaseInsensitiveFileSystem::status(const Twine &Path) {
+  auto S = Base->status(Path);
+  if (S.getError() != llvm::errc::no_such_file_or_directory)
+    return S;
+
+  SmallVector<char, 512> NewPath;
+  if (std::error_code EC = findCaseInsensitivePath(Path.str(), NewPath))
+    return EC;
+
+  return Base->status(NewPath);
+}
+
+llvm::ErrorOr<std::unique_ptr<File>>
+CaseInsensitiveFileSystem::openFileForRead(const Twine &Path) {
+  auto F = Base->openFileForRead(Path);
+  if (F.getError() != llvm::errc::no_such_file_or_directory)
+    return F;
+
+  SmallVector<char, 512> NewPath;
+  if (std::error_code EC = findCaseInsensitivePath(Path.str(), NewPath))
+    return EC;
+
+  return Base->openFileForRead(NewPath);
+}
+
+directory_iterator CaseInsensitiveFileSystem::dir_begin(const Twine &Path,
+                                                        std::error_code &EC) {
+  auto I = Base->dir_begin(Path, EC);
+  if (EC != llvm::errc::no_such_file_or_directory)
+    return I;
+
+  SmallVector<char, 512> NewPath;
+  if ((EC = findCaseInsensitivePath(Path.str(), NewPath)))
+    return directory_iterator();
+
+  return Base->dir_begin(NewPath, EC);
+}
+
+llvm::ErrorOr<std::string>
+CaseInsensitiveFileSystem::getCurrentWorkingDirectory() const {
+  return Base->getCurrentWorkingDirectory();
+}
+
+std::error_code
+CaseInsensitiveFileSystem::setCurrentWorkingDirectory(const Twine &Path) {
+  return Base->setCurrentWorkingDirectory(Path);
+}
+
 namespace clang {
 namespace vfs {
 namespace detail {
Index: include/clang/Lex/HeaderSearchOptions.h
===================================================================
--- include/clang/Lex/HeaderSearchOptions.h
+++ include/clang/Lex/HeaderSearchOptions.h
@@ -161,6 +161,9 @@
   /// Whether header search information should be output as for -v.
   unsigned Verbose : 1;
 
+  /// Whether header search should be case-insensitive.
+  unsigned CaseInsensitive : 1;
+
   /// \brief If true, skip verifying input files used by modules if the
   /// module was already verified during this build session (see
   /// \c BuildSessionTimestamp).
Index: include/clang/Driver/Options.td
===================================================================
--- include/clang/Driver/Options.td
+++ include/clang/Driver/Options.td
@@ -525,6 +525,8 @@
 def fdiagnostics_color_EQ : Joined<["-"], "fdiagnostics-color=">, Group<f_Group>;
 def fansi_escape_codes : Flag<["-"], "fansi-escape-codes">, Group<f_Group>,
   Flags<[CoreOption, CC1Option]>, HelpText<"Use ANSI escape codes for diagnostics">;
+def fcase_insensitive_includes : Flag<["-"], "fcase-insensitive-includes">, Group<f_Group>,
+  Flags<[CC1Option, CoreOption]>, HelpText<"Make include lookups case-insensitive">;
 def fcomment_block_commands : CommaJoined<["-"], "fcomment-block-commands=">, Group<f_clang_Group>, Flags<[CC1Option]>,
   HelpText<"Treat each comma separated argument in <arg> as a documentation comment block command">,
   MetaVarName<"<arg>">;
Index: include/clang/Basic/VirtualFileSystem.h
===================================================================
--- include/clang/Basic/VirtualFileSystem.h
+++ include/clang/Basic/VirtualFileSystem.h
@@ -272,6 +272,24 @@
   iterator overlays_end() { return FSList.rend(); }
 };
 
+class CaseInsensitiveFileSystem : public FileSystem {
+  IntrusiveRefCntPtr<FileSystem> Base;
+
+  /// Try to find Path by means of case-insensitive lookup. Stores the result in
+  /// FoundPath on success, or returns an error code otherwise.
+  std::error_code findCaseInsensitivePath(StringRef Path,
+                                          SmallVectorImpl<char> &FoundPath);
+public:
+  CaseInsensitiveFileSystem(IntrusiveRefCntPtr<FileSystem> Base);
+
+  llvm::ErrorOr<Status> status(const Twine &Path) override;
+  llvm::ErrorOr<std::unique_ptr<File>>
+  openFileForRead(const Twine &Path) override;
+  directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override;
+  llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override;
+  std::error_code setCurrentWorkingDirectory(const Twine &Path) override;
+};
+
 namespace detail {
 class InMemoryDirectory;
 } // end namespace detail
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to