Home | History | Annotate | Line # | Download | only in LTO
      1 //===-Caching.cpp - LLVM Link Time Optimizer Cache Handling ---------------===//
      2 //
      3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      4 // See https://llvm.org/LICENSE.txt for license information.
      5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      6 //
      7 //===----------------------------------------------------------------------===//
      8 //
      9 // This file implements the Caching for ThinLTO.
     10 //
     11 //===----------------------------------------------------------------------===//
     12 
     13 #include "llvm/LTO/Caching.h"
     14 #include "llvm/ADT/StringExtras.h"
     15 #include "llvm/Support/Errc.h"
     16 #include "llvm/Support/FileSystem.h"
     17 #include "llvm/Support/MemoryBuffer.h"
     18 #include "llvm/Support/Path.h"
     19 #include "llvm/Support/Process.h"
     20 #include "llvm/Support/raw_ostream.h"
     21 
     22 #if !defined(_MSC_VER) && !defined(__MINGW32__)
     23 #include <unistd.h>
     24 #else
     25 #include <io.h>
     26 #endif
     27 
     28 using namespace llvm;
     29 using namespace llvm::lto;
     30 
     31 Expected<NativeObjectCache> lto::localCache(StringRef CacheDirectoryPath,
     32                                             AddBufferFn AddBuffer) {
     33   if (std::error_code EC = sys::fs::create_directories(CacheDirectoryPath))
     34     return errorCodeToError(EC);
     35 
     36   return [=](unsigned Task, StringRef Key) -> AddStreamFn {
     37     // This choice of file name allows the cache to be pruned (see pruneCache()
     38     // in include/llvm/Support/CachePruning.h).
     39     SmallString<64> EntryPath;
     40     sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key);
     41     // First, see if we have a cache hit.
     42     SmallString<64> ResultPath;
     43     Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead(
     44         Twine(EntryPath), sys::fs::OF_UpdateAtime, &ResultPath);
     45     std::error_code EC;
     46     if (FDOrErr) {
     47       ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
     48           MemoryBuffer::getOpenFile(*FDOrErr, EntryPath,
     49                                     /*FileSize=*/-1,
     50                                     /*RequiresNullTerminator=*/false);
     51       sys::fs::closeFile(*FDOrErr);
     52       if (MBOrErr) {
     53         AddBuffer(Task, std::move(*MBOrErr));
     54         return AddStreamFn();
     55       }
     56       EC = MBOrErr.getError();
     57     } else {
     58       EC = errorToErrorCode(FDOrErr.takeError());
     59     }
     60 
     61     // On Windows we can fail to open a cache file with a permission denied
     62     // error. This generally means that another process has requested to delete
     63     // the file while it is still open, but it could also mean that another
     64     // process has opened the file without the sharing permissions we need.
     65     // Since the file is probably being deleted we handle it in the same way as
     66     // if the file did not exist at all.
     67     if (EC != errc::no_such_file_or_directory && EC != errc::permission_denied)
     68       report_fatal_error(Twine("Failed to open cache file ") + EntryPath +
     69                          ": " + EC.message() + "\n");
     70 
     71     // This native object stream is responsible for commiting the resulting
     72     // file to the cache and calling AddBuffer to add it to the link.
     73     struct CacheStream : NativeObjectStream {
     74       AddBufferFn AddBuffer;
     75       sys::fs::TempFile TempFile;
     76       std::string EntryPath;
     77       unsigned Task;
     78 
     79       CacheStream(std::unique_ptr<raw_pwrite_stream> OS, AddBufferFn AddBuffer,
     80                   sys::fs::TempFile TempFile, std::string EntryPath,
     81                   unsigned Task)
     82           : NativeObjectStream(std::move(OS)), AddBuffer(std::move(AddBuffer)),
     83             TempFile(std::move(TempFile)), EntryPath(std::move(EntryPath)),
     84             Task(Task) {}
     85 
     86       ~CacheStream() {
     87         // Make sure the stream is closed before committing it.
     88         OS.reset();
     89 
     90         // Open the file first to avoid racing with a cache pruner.
     91         ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
     92             MemoryBuffer::getOpenFile(
     93                 sys::fs::convertFDToNativeFile(TempFile.FD), TempFile.TmpName,
     94                 /*FileSize=*/-1, /*RequiresNullTerminator=*/false);
     95         if (!MBOrErr)
     96           report_fatal_error(Twine("Failed to open new cache file ") +
     97                              TempFile.TmpName + ": " +
     98                              MBOrErr.getError().message() + "\n");
     99 
    100         // On POSIX systems, this will atomically replace the destination if
    101         // it already exists. We try to emulate this on Windows, but this may
    102         // fail with a permission denied error (for example, if the destination
    103         // is currently opened by another process that does not give us the
    104         // sharing permissions we need). Since the existing file should be
    105         // semantically equivalent to the one we are trying to write, we give
    106         // AddBuffer a copy of the bytes we wrote in that case. We do this
    107         // instead of just using the existing file, because the pruner might
    108         // delete the file before we get a chance to use it.
    109         Error E = TempFile.keep(EntryPath);
    110         E = handleErrors(std::move(E), [&](const ECError &E) -> Error {
    111           std::error_code EC = E.convertToErrorCode();
    112           if (EC != errc::permission_denied)
    113             return errorCodeToError(EC);
    114 
    115           auto MBCopy = MemoryBuffer::getMemBufferCopy((*MBOrErr)->getBuffer(),
    116                                                        EntryPath);
    117           MBOrErr = std::move(MBCopy);
    118 
    119           // FIXME: should we consume the discard error?
    120           consumeError(TempFile.discard());
    121 
    122           return Error::success();
    123         });
    124 
    125         if (E)
    126           report_fatal_error(Twine("Failed to rename temporary file ") +
    127                              TempFile.TmpName + " to " + EntryPath + ": " +
    128                              toString(std::move(E)) + "\n");
    129 
    130         AddBuffer(Task, std::move(*MBOrErr));
    131       }
    132     };
    133 
    134     return [=](size_t Task) -> std::unique_ptr<NativeObjectStream> {
    135       // Write to a temporary to avoid race condition
    136       SmallString<64> TempFilenameModel;
    137       sys::path::append(TempFilenameModel, CacheDirectoryPath, "Thin-%%%%%%.tmp.o");
    138       Expected<sys::fs::TempFile> Temp = sys::fs::TempFile::create(
    139           TempFilenameModel, sys::fs::owner_read | sys::fs::owner_write);
    140       if (!Temp) {
    141         errs() << "Error: " << toString(Temp.takeError()) << "\n";
    142         report_fatal_error("ThinLTO: Can't get a temporary file");
    143       }
    144 
    145       // This CacheStream will move the temporary file into the cache when done.
    146       return std::make_unique<CacheStream>(
    147           std::make_unique<raw_fd_ostream>(Temp->FD, /* ShouldClose */ false),
    148           AddBuffer, std::move(*Temp), std::string(EntryPath.str()), Task);
    149     };
    150   };
    151 }
    152