2012-08-01 04:29:50 +02:00
|
|
|
//===- llvm/unittest/Support/FileOutputBuffer.cpp - unit tests ------------===//
|
|
|
|
//
|
2019-01-19 09:50:56 +01:00
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
2012-08-01 04:29:50 +02:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2017-06-06 13:06:56 +02:00
|
|
|
#include "llvm/Support/FileOutputBuffer.h"
|
2014-09-11 22:30:02 +02:00
|
|
|
#include "llvm/Support/Errc.h"
|
2012-08-01 04:29:50 +02:00
|
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
|
|
#include "llvm/Support/FileSystem.h"
|
2018-06-28 20:49:09 +02:00
|
|
|
#include "llvm/Support/MemoryBuffer.h"
|
2013-06-12 00:21:28 +02:00
|
|
|
#include "llvm/Support/Path.h"
|
2012-08-01 04:29:50 +02:00
|
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
using namespace llvm::sys;
|
|
|
|
|
2019-08-14 15:59:04 +02:00
|
|
|
#define ASSERT_NO_ERROR(x) \
|
|
|
|
if (std::error_code ASSERT_NO_ERROR_ec = x) { \
|
|
|
|
SmallString<128> MessageStorage; \
|
|
|
|
raw_svector_ostream Message(MessageStorage); \
|
|
|
|
Message << #x ": did not return errc::success.\n" \
|
|
|
|
<< "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \
|
|
|
|
<< "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \
|
|
|
|
GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \
|
|
|
|
} else { \
|
|
|
|
}
|
2012-08-01 04:29:50 +02:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
TEST(FileOutputBuffer, Test) {
|
|
|
|
// Create unique temporary directory for these tests
|
|
|
|
SmallString<128> TestDirectory;
|
|
|
|
{
|
|
|
|
ASSERT_NO_ERROR(
|
2013-06-27 05:45:31 +02:00
|
|
|
fs::createUniqueDirectory("FileOutputBuffer-test", TestDirectory));
|
2012-08-01 04:29:50 +02:00
|
|
|
}
|
2012-12-03 23:09:52 +01:00
|
|
|
|
2012-08-01 04:29:50 +02:00
|
|
|
// TEST 1: Verify commit case.
|
|
|
|
SmallString<128> File1(TestDirectory);
|
2017-08-28 08:47:47 +02:00
|
|
|
File1.append("/file1");
|
2012-08-01 04:29:50 +02:00
|
|
|
{
|
2017-11-08 02:10:05 +01:00
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
2015-08-13 02:31:39 +02:00
|
|
|
FileOutputBuffer::create(File1, 8192);
|
2017-11-08 02:10:05 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
|
2015-08-13 02:31:39 +02:00
|
|
|
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
|
2012-08-01 04:29:50 +02:00
|
|
|
// Start buffer with special header.
|
|
|
|
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Write to end of buffer to verify it is writable.
|
|
|
|
memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Commit buffer.
|
2017-11-08 02:50:29 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
|
2012-08-01 04:29:50 +02:00
|
|
|
}
|
2014-06-11 23:53:22 +02:00
|
|
|
|
2012-08-01 04:29:50 +02:00
|
|
|
// Verify file is correct size.
|
|
|
|
uint64_t File1Size;
|
|
|
|
ASSERT_NO_ERROR(fs::file_size(Twine(File1), File1Size));
|
|
|
|
ASSERT_EQ(File1Size, 8192ULL);
|
2014-01-10 21:36:42 +01:00
|
|
|
ASSERT_NO_ERROR(fs::remove(File1.str()));
|
2012-08-01 04:29:50 +02:00
|
|
|
|
2016-09-02 03:10:53 +02:00
|
|
|
// TEST 2: Verify abort case.
|
2012-08-01 04:29:50 +02:00
|
|
|
SmallString<128> File2(TestDirectory);
|
2016-09-02 03:10:53 +02:00
|
|
|
File2.append("/file2");
|
2012-08-01 04:29:50 +02:00
|
|
|
{
|
2017-11-08 02:10:05 +01:00
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> Buffer2OrErr =
|
2015-08-13 02:31:39 +02:00
|
|
|
FileOutputBuffer::create(File2, 8192);
|
2017-11-08 02:10:05 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer2OrErr.takeError()));
|
2015-08-13 02:31:39 +02:00
|
|
|
std::unique_ptr<FileOutputBuffer> &Buffer2 = *Buffer2OrErr;
|
2012-08-01 04:29:50 +02:00
|
|
|
// Fill buffer with special header.
|
|
|
|
memcpy(Buffer2->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Do *not* commit buffer.
|
|
|
|
}
|
2014-01-24 18:20:08 +01:00
|
|
|
// Verify file does not exist (because buffer not committed).
|
2014-09-11 22:30:02 +02:00
|
|
|
ASSERT_EQ(fs::access(Twine(File2), fs::AccessMode::Exist),
|
|
|
|
errc::no_such_file_or_directory);
|
2014-01-10 21:36:42 +01:00
|
|
|
ASSERT_NO_ERROR(fs::remove(File2.str()));
|
2012-08-01 04:29:50 +02:00
|
|
|
|
|
|
|
// TEST 3: Verify sizing down case.
|
|
|
|
SmallString<128> File3(TestDirectory);
|
2017-08-28 08:47:47 +02:00
|
|
|
File3.append("/file3");
|
2012-08-01 04:29:50 +02:00
|
|
|
{
|
2017-11-08 02:10:05 +01:00
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
2015-08-13 02:31:39 +02:00
|
|
|
FileOutputBuffer::create(File3, 8192000);
|
2017-11-08 02:10:05 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
|
2015-08-13 02:31:39 +02:00
|
|
|
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
|
2012-08-01 04:29:50 +02:00
|
|
|
// Start buffer with special header.
|
|
|
|
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Write to end of buffer to verify it is writable.
|
|
|
|
memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
|
2017-11-08 02:50:29 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
|
2012-08-01 04:29:50 +02:00
|
|
|
}
|
2014-06-11 23:53:22 +02:00
|
|
|
|
2012-08-01 04:29:50 +02:00
|
|
|
// Verify file is correct size.
|
|
|
|
uint64_t File3Size;
|
|
|
|
ASSERT_NO_ERROR(fs::file_size(Twine(File3), File3Size));
|
2014-12-12 18:35:34 +01:00
|
|
|
ASSERT_EQ(File3Size, 8192000ULL);
|
2014-01-10 21:36:42 +01:00
|
|
|
ASSERT_NO_ERROR(fs::remove(File3.str()));
|
2012-08-01 04:29:50 +02:00
|
|
|
|
|
|
|
// TEST 4: Verify file can be made executable.
|
|
|
|
SmallString<128> File4(TestDirectory);
|
2017-08-28 08:47:47 +02:00
|
|
|
File4.append("/file4");
|
2012-08-01 04:29:50 +02:00
|
|
|
{
|
2017-11-08 02:10:05 +01:00
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
2015-08-13 02:31:39 +02:00
|
|
|
FileOutputBuffer::create(File4, 8192, FileOutputBuffer::F_executable);
|
2017-11-08 02:10:05 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
|
2015-08-13 02:31:39 +02:00
|
|
|
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
|
2012-08-01 04:29:50 +02:00
|
|
|
// Start buffer with special header.
|
|
|
|
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Commit buffer.
|
2017-11-08 02:50:29 +01:00
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
|
2012-08-01 04:29:50 +02:00
|
|
|
}
|
|
|
|
// Verify file exists and is executable.
|
|
|
|
fs::file_status Status;
|
|
|
|
ASSERT_NO_ERROR(fs::status(Twine(File4), Status));
|
|
|
|
bool IsExecutable = (Status.permissions() & fs::owner_exe);
|
|
|
|
EXPECT_TRUE(IsExecutable);
|
2014-01-10 21:36:42 +01:00
|
|
|
ASSERT_NO_ERROR(fs::remove(File4.str()));
|
2012-08-01 04:29:50 +02:00
|
|
|
|
[LLD][ELF] Support --[no-]mmap-output-file with F_no_mmap
Summary:
Add a flag `F_no_mmap` to `FileOutputBuffer` to support
`--[no-]mmap-output-file` in ELF LLD. LLD currently explicitly ignores
this flag for compatibility with GNU ld and gold.
We need this flag to speed up link time for large binaries in certain
scenarios. When we link some of our larger binaries we find that LLD
takes 50+ GB of memory, which causes memory pressure. The memory
pressure causes the VM to flush dirty pages of the output file to disk.
This is normally okay, since we should be flushing cold pages. However,
when using BtrFS with compression we need to write 128KB at a time when
we flush a page. If any page in that 128KB block is written again, then
it must be flushed a second time, and so on. Since LLD doesn't write
sequentially this causes write amplification. The same 128KB block will
end up being flushed multiple times, causing the linker to many times
more IO than necessary. We've observed 3-5x faster builds with
-no-mmap-output-file when we hit this scenario.
The bad scenario only applies to compressed filesystems, which group
together multiple pages into a single compressed block. I've tested
BtrFS, but the problem will be present for any compressed filesystem
on Linux, since it is caused by the VM.
Silently ignoring --no-mmap-output-file caused a silent regression when
we switched from gold to lld. We pass --no-mmap-output-file to fix this
edge case, but since lld silently ignored the flag we didn't realize it
wasn't being respected.
Benchmark building a 9 GB binary that exposes this edge case. I linked 3
times with --mmap-output-file and 3 times with --no-mmap-output-file and
took the average. The machine has 24 cores @ 2.4 GHz, 112 GB of RAM,
BtrFS mounted with -compress-force=zstd, and an 80% full disk.
| Mode | Time |
|---------|-------|
| mmap | 894 s |
| no mmap | 126 s |
When compression is disabled, BtrFS performs just as well with and
without mmap on this benchmark.
I was unable to reproduce the regression with any binaries in
lld-speed-test.
Reviewed By: ruiu, MaskRay
Differential Revision: https://reviews.llvm.org/D69294
2019-10-29 23:46:22 +01:00
|
|
|
// TEST 5: In-memory buffer works as expected.
|
|
|
|
SmallString<128> File5(TestDirectory);
|
|
|
|
File5.append("/file5");
|
|
|
|
{
|
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
|
|
|
FileOutputBuffer::create(File5, 8000, FileOutputBuffer::F_no_mmap);
|
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
|
|
|
|
std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr;
|
|
|
|
// Start buffer with special header.
|
|
|
|
memcpy(Buffer->getBufferStart(), "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
|
|
|
|
// Write to end of buffer to verify it is writable.
|
|
|
|
memcpy(Buffer->getBufferEnd() - 20, "AABBCCDDEEFFGGHHIIJJ", 20);
|
|
|
|
// Commit buffer.
|
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify file is correct size.
|
|
|
|
uint64_t File5Size;
|
|
|
|
ASSERT_NO_ERROR(fs::file_size(Twine(File5), File5Size));
|
|
|
|
ASSERT_EQ(File5Size, 8000ULL);
|
|
|
|
ASSERT_NO_ERROR(fs::remove(File5.str()));
|
2020-05-04 18:45:41 +02:00
|
|
|
|
|
|
|
// TEST 6: Create an empty file.
|
|
|
|
SmallString<128> File6(TestDirectory);
|
|
|
|
File6.append("/file6");
|
|
|
|
{
|
|
|
|
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr =
|
|
|
|
FileOutputBuffer::create(File6, 0);
|
|
|
|
ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError()));
|
|
|
|
ASSERT_NO_ERROR(errorToErrorCode((*BufferOrErr)->commit()));
|
|
|
|
}
|
|
|
|
uint64_t File6Size;
|
|
|
|
ASSERT_NO_ERROR(fs::file_size(Twine(File6), File6Size));
|
|
|
|
ASSERT_EQ(File6Size, 0ULL);
|
|
|
|
ASSERT_NO_ERROR(fs::remove(File6.str()));
|
|
|
|
|
2012-08-01 04:29:50 +02:00
|
|
|
// Clean up.
|
2014-01-10 21:36:42 +01:00
|
|
|
ASSERT_NO_ERROR(fs::remove(TestDirectory.str()));
|
2012-08-01 04:29:50 +02:00
|
|
|
}
|
|
|
|
} // anonymous namespace
|