1311116Sdim//===-Caching.cpp - LLVM Link Time Optimizer Cache Handling ---------------===//
2311116Sdim//
3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4353358Sdim// See https://llvm.org/LICENSE.txt for license information.
5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6311116Sdim//
7311116Sdim//===----------------------------------------------------------------------===//
8311116Sdim//
9311116Sdim// This file implements the Caching for ThinLTO.
10311116Sdim//
11311116Sdim//===----------------------------------------------------------------------===//
12311116Sdim
13311116Sdim#include "llvm/LTO/Caching.h"
14311116Sdim#include "llvm/ADT/StringExtras.h"
15321369Sdim#include "llvm/Support/Errc.h"
16311116Sdim#include "llvm/Support/MemoryBuffer.h"
17311116Sdim#include "llvm/Support/Path.h"
18327952Sdim#include "llvm/Support/Process.h"
19311116Sdim#include "llvm/Support/raw_ostream.h"
20311116Sdim
21341825Sdim#if !defined(_MSC_VER) && !defined(__MINGW32__)
22341825Sdim#include <unistd.h>
23341825Sdim#else
24341825Sdim#include <io.h>
25341825Sdim#endif
26341825Sdim
27311116Sdimusing namespace llvm;
28311116Sdimusing namespace llvm::lto;
29311116Sdim
30321369SdimExpected<NativeObjectCache> lto::localCache(StringRef CacheDirectoryPath,
31321369Sdim                                            AddBufferFn AddBuffer) {
32321369Sdim  if (std::error_code EC = sys::fs::create_directories(CacheDirectoryPath))
33321369Sdim    return errorCodeToError(EC);
34311116Sdim
35311116Sdim  return [=](unsigned Task, StringRef Key) -> AddStreamFn {
36321369Sdim    // This choice of file name allows the cache to be pruned (see pruneCache()
37321369Sdim    // in include/llvm/Support/CachePruning.h).
38321369Sdim    SmallString<64> EntryPath;
39321369Sdim    sys::path::append(EntryPath, CacheDirectoryPath, "llvmcache-" + Key);
40311116Sdim    // First, see if we have a cache hit.
41341825Sdim    SmallString<64> ResultPath;
42353358Sdim    Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead(
43353358Sdim        Twine(EntryPath), sys::fs::OF_UpdateAtime, &ResultPath);
44353358Sdim    std::error_code EC;
45353358Sdim    if (FDOrErr) {
46341825Sdim      ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
47353358Sdim          MemoryBuffer::getOpenFile(*FDOrErr, EntryPath,
48353358Sdim                                    /*FileSize=*/-1,
49353358Sdim                                    /*RequiresNullTerminator=*/false);
50353358Sdim      sys::fs::closeFile(*FDOrErr);
51341825Sdim      if (MBOrErr) {
52341825Sdim        AddBuffer(Task, std::move(*MBOrErr));
53341825Sdim        return AddStreamFn();
54341825Sdim      }
55341825Sdim      EC = MBOrErr.getError();
56353358Sdim    } else {
57353358Sdim      EC = errorToErrorCode(FDOrErr.takeError());
58311116Sdim    }
59311116Sdim
60341825Sdim    // On Windows we can fail to open a cache file with a permission denied
61341825Sdim    // error. This generally means that another process has requested to delete
62341825Sdim    // the file while it is still open, but it could also mean that another
63341825Sdim    // process has opened the file without the sharing permissions we need.
64341825Sdim    // Since the file is probably being deleted we handle it in the same way as
65341825Sdim    // if the file did not exist at all.
66341825Sdim    if (EC != errc::no_such_file_or_directory && EC != errc::permission_denied)
67321369Sdim      report_fatal_error(Twine("Failed to open cache file ") + EntryPath +
68341825Sdim                         ": " + EC.message() + "\n");
69321369Sdim
70311116Sdim    // This native object stream is responsible for commiting the resulting
71321369Sdim    // file to the cache and calling AddBuffer to add it to the link.
72311116Sdim    struct CacheStream : NativeObjectStream {
73321369Sdim      AddBufferFn AddBuffer;
74327952Sdim      sys::fs::TempFile TempFile;
75311116Sdim      std::string EntryPath;
76311116Sdim      unsigned Task;
77311116Sdim
78321369Sdim      CacheStream(std::unique_ptr<raw_pwrite_stream> OS, AddBufferFn AddBuffer,
79327952Sdim                  sys::fs::TempFile TempFile, std::string EntryPath,
80311116Sdim                  unsigned Task)
81321369Sdim          : NativeObjectStream(std::move(OS)), AddBuffer(std::move(AddBuffer)),
82327952Sdim            TempFile(std::move(TempFile)), EntryPath(std::move(EntryPath)),
83327952Sdim            Task(Task) {}
84311116Sdim
85311116Sdim      ~CacheStream() {
86327952Sdim        // Make sure the stream is closed before committing it.
87311116Sdim        OS.reset();
88321369Sdim
89327952Sdim        // Open the file first to avoid racing with a cache pruner.
90321369Sdim        ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
91353358Sdim            MemoryBuffer::getOpenFile(
92353358Sdim                sys::fs::convertFDToNativeFile(TempFile.FD), TempFile.TmpName,
93353358Sdim                /*FileSize=*/-1, /*RequiresNullTerminator=*/false);
94321369Sdim        if (!MBOrErr)
95327952Sdim          report_fatal_error(Twine("Failed to open new cache file ") +
96327952Sdim                             TempFile.TmpName + ": " +
97327952Sdim                             MBOrErr.getError().message() + "\n");
98327952Sdim
99327952Sdim        // On POSIX systems, this will atomically replace the destination if
100327952Sdim        // it already exists. We try to emulate this on Windows, but this may
101327952Sdim        // fail with a permission denied error (for example, if the destination
102327952Sdim        // is currently opened by another process that does not give us the
103327952Sdim        // sharing permissions we need). Since the existing file should be
104327952Sdim        // semantically equivalent to the one we are trying to write, we give
105327952Sdim        // AddBuffer a copy of the bytes we wrote in that case. We do this
106327952Sdim        // instead of just using the existing file, because the pruner might
107327952Sdim        // delete the file before we get a chance to use it.
108327952Sdim        Error E = TempFile.keep(EntryPath);
109327952Sdim        E = handleErrors(std::move(E), [&](const ECError &E) -> Error {
110327952Sdim          std::error_code EC = E.convertToErrorCode();
111327952Sdim          if (EC != errc::permission_denied)
112327952Sdim            return errorCodeToError(EC);
113327952Sdim
114327952Sdim          auto MBCopy = MemoryBuffer::getMemBufferCopy((*MBOrErr)->getBuffer(),
115327952Sdim                                                       EntryPath);
116327952Sdim          MBOrErr = std::move(MBCopy);
117327952Sdim
118327952Sdim          // FIXME: should we consume the discard error?
119327952Sdim          consumeError(TempFile.discard());
120327952Sdim
121327952Sdim          return Error::success();
122327952Sdim        });
123327952Sdim
124327952Sdim        if (E)
125327952Sdim          report_fatal_error(Twine("Failed to rename temporary file ") +
126327952Sdim                             TempFile.TmpName + " to " + EntryPath + ": " +
127327952Sdim                             toString(std::move(E)) + "\n");
128327952Sdim
129341825Sdim        AddBuffer(Task, std::move(*MBOrErr));
130311116Sdim      }
131311116Sdim    };
132311116Sdim
133311116Sdim    return [=](size_t Task) -> std::unique_ptr<NativeObjectStream> {
134311116Sdim      // Write to a temporary to avoid race condition
135327952Sdim      SmallString<64> TempFilenameModel;
136321369Sdim      sys::path::append(TempFilenameModel, CacheDirectoryPath, "Thin-%%%%%%.tmp.o");
137327952Sdim      Expected<sys::fs::TempFile> Temp = sys::fs::TempFile::create(
138327952Sdim          TempFilenameModel, sys::fs::owner_read | sys::fs::owner_write);
139327952Sdim      if (!Temp) {
140327952Sdim        errs() << "Error: " << toString(Temp.takeError()) << "\n";
141311116Sdim        report_fatal_error("ThinLTO: Can't get a temporary file");
142311116Sdim      }
143311116Sdim
144311116Sdim      // This CacheStream will move the temporary file into the cache when done.
145360784Sdim      return std::make_unique<CacheStream>(
146360784Sdim          std::make_unique<raw_fd_ostream>(Temp->FD, /* ShouldClose */ false),
147327952Sdim          AddBuffer, std::move(*Temp), EntryPath.str(), Task);
148311116Sdim    };
149311116Sdim  };
150311116Sdim}
151