Skip to content

Commit 92e8af0

Browse files
committed
[Clang] Expose RequiresNullTerminator in FileManager.
This is needed to fix the reason 0a2be46 (Modules: Invalidate out-of-date PCMs as they're discovered) and 5b44a4b ([modules] Do not cache invalid state for modules that we attempted to load.) were reverted. These patches changed Clang to use `isVolatile` when loading modules. This had the side effect of not using mmap when loading modules, and thus greatly increased memory usage. The reason it wasn't using mmap is because `MemoryBuffer` plays some games with file size when you request null termination, and it has to disable these when `isVolatile` is set as the size may change by the time it's mmapped. Clang by default passes `RequiresNullTerminator = true`, and `shouldUseMmap` ignored if `RequiresNullTerminator` was even requested. This patch adds `RequiresNullTerminator` to the `FileManager` interface so Clang can use it when loading modules, and changes `shouldUseMmap` to only take volatility into account if `RequiresNullTerminator` is true. This is fine as both `mmap` and a `read` loop are vulnerable to modifying the file while reading, but are immune to the rename Clang does when replacing a module file. Differential Revision: https://reviews.llvm.org/D77772
1 parent 1eac2c5 commit 92e8af0

File tree

4 files changed

+49
-15
lines changed

4 files changed

+49
-15
lines changed

clang/include/clang/Basic/FileManager.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,15 +378,19 @@ class FileManager : public RefCountedBase<FileManager> {
378378
/// Open the specified file as a MemoryBuffer, returning a new
379379
/// MemoryBuffer if successful, otherwise returning null.
380380
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
381-
getBufferForFile(const FileEntry *Entry, bool isVolatile = false);
381+
getBufferForFile(const FileEntry *Entry, bool isVolatile = false,
382+
bool RequiresNullTerminator = true);
382383
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
383-
getBufferForFile(StringRef Filename, bool isVolatile = false) {
384-
return getBufferForFileImpl(Filename, /*FileSize=*/-1, isVolatile);
384+
getBufferForFile(StringRef Filename, bool isVolatile = false,
385+
bool RequiresNullTerminator = true) {
386+
return getBufferForFileImpl(Filename, /*FileSize=*/-1, isVolatile,
387+
RequiresNullTerminator);
385388
}
386389

387390
private:
388391
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
389-
getBufferForFileImpl(StringRef Filename, int64_t FileSize, bool isVolatile);
392+
getBufferForFileImpl(StringRef Filename, int64_t FileSize, bool isVolatile,
393+
bool RequiresNullTerminator);
390394

391395
public:
392396
/// Get the 'stat' information for the given \p Path.

clang/lib/Basic/FileManager.cpp

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,8 @@ void FileManager::fillRealPathName(FileEntry *UFE, llvm::StringRef FileName) {
458458
}
459459

460460
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
461-
FileManager::getBufferForFile(const FileEntry *Entry, bool isVolatile) {
461+
FileManager::getBufferForFile(const FileEntry *Entry, bool isVolatile,
462+
bool RequiresNullTerminator) {
462463
uint64_t FileSize = Entry->getSize();
463464
// If there's a high enough chance that the file have changed since we
464465
// got its size, force a stat before opening it.
@@ -468,28 +469,29 @@ FileManager::getBufferForFile(const FileEntry *Entry, bool isVolatile) {
468469
StringRef Filename = Entry->getName();
469470
// If the file is already open, use the open file descriptor.
470471
if (Entry->File) {
471-
auto Result =
472-
Entry->File->getBuffer(Filename, FileSize,
473-
/*RequiresNullTerminator=*/true, isVolatile);
472+
auto Result = Entry->File->getBuffer(Filename, FileSize,
473+
RequiresNullTerminator, isVolatile);
474474
Entry->closeFile();
475475
return Result;
476476
}
477477

478478
// Otherwise, open the file.
479-
return getBufferForFileImpl(Filename, FileSize, isVolatile);
479+
return getBufferForFileImpl(Filename, FileSize, isVolatile,
480+
RequiresNullTerminator);
480481
}
481482

482483
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
483484
FileManager::getBufferForFileImpl(StringRef Filename, int64_t FileSize,
484-
bool isVolatile) {
485+
bool isVolatile,
486+
bool RequiresNullTerminator) {
485487
if (FileSystemOpts.WorkingDir.empty())
486-
return FS->getBufferForFile(Filename, FileSize,
487-
/*RequiresNullTerminator=*/true, isVolatile);
488+
return FS->getBufferForFile(Filename, FileSize, RequiresNullTerminator,
489+
isVolatile);
488490

489491
SmallString<128> FilePath(Filename);
490492
FixupRelativePath(FilePath);
491-
return FS->getBufferForFile(FilePath, FileSize,
492-
/*RequiresNullTerminator=*/true, isVolatile);
493+
return FS->getBufferForFile(FilePath, FileSize, RequiresNullTerminator,
494+
isVolatile);
493495
}
494496

495497
/// getStatValue - Get the 'stat' information for the specified path,

llvm/lib/Support/MemoryBuffer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ static bool shouldUseMmap(sys::fs::file_t FD,
329329
// mmap may leave the buffer without null terminator if the file size changed
330330
// by the time the last page is mapped in, so avoid it if the file size is
331331
// likely to change.
332-
if (IsVolatile)
332+
if (IsVolatile && RequiresNullTerminator)
333333
return false;
334334

335335
// We don't use mmap for small files because this can severely fragment our

llvm/unittests/Support/MemoryBufferTest.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,32 @@ TEST_F(MemoryBufferTest, writeThroughFile) {
380380
ASSERT_EQ(16u, MB.getBufferSize());
381381
EXPECT_EQ("xxxxxxxxxxxxxxxx", MB.getBuffer());
382382
}
383+
384+
TEST_F(MemoryBufferTest, mmapVolatileNoNull) {
385+
// Verify that `MemoryBuffer::getOpenFile` will use mmap when
386+
// `RequiresNullTerminator = false`, `IsVolatile = true`, and the file is
387+
// large enough to use mmap.
388+
//
389+
// This is done because Clang should use this mode to open module files, and
390+
// falling back to malloc for them causes a huge memory usage increase.
391+
392+
int FD;
393+
SmallString<64> TestPath;
394+
ASSERT_NO_ERROR(sys::fs::createTemporaryFile(
395+
"MemoryBufferTest_mmapVolatileNoNull", "temp", FD, TestPath));
396+
FileRemover Cleanup(TestPath);
397+
raw_fd_ostream OF(FD, true);
398+
// Create a file large enough to mmap. A 32KiB file should be enough.
399+
for (unsigned i = 0; i < 0x1000; ++i)
400+
OF << "01234567";
401+
OF.flush();
402+
403+
auto MBOrError = MemoryBuffer::getOpenFile(FD, TestPath,
404+
/*FileSize=*/-1, /*RequiresNullTerminator=*/false, /*IsVolatile=*/true);
405+
ASSERT_NO_ERROR(MBOrError.getError())
406+
OwningBuffer MB = std::move(*MBOrError);
407+
EXPECT_EQ(MB->getBufferKind(), MemoryBuffer::MemoryBuffer_MMap);
408+
EXPECT_EQ(MB->getBufferSize(), std::size_t(0x8000));
409+
EXPECT_TRUE(MB->getBuffer().startswith("01234567"));
410+
}
383411
}

0 commit comments

Comments
 (0)