clang-hash.cc 11.8 KB
Newer Older
1
#include "hash-visitor.h"
Christian Dietrich's avatar
Christian Dietrich committed
2
3
4
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/Frontend/CompilerInstance.h"
Christian Dietrich's avatar
Christian Dietrich committed
5
#include "clang/Frontend/FrontendPluginRegistry.h"
Christian Dietrich's avatar
Christian Dietrich committed
6
#include "llvm/Support/raw_ostream.h"
7
#include <chrono>
8
9
#include <fstream>
#include <unistd.h>
10
#include <utime.h>
11
12
#include <sys/stat.h>
#include <sys/types.h>
13
14
#include <fcntl.h>

15

Christian Dietrich's avatar
Christian Dietrich committed
16

Christian Dietrich's avatar
Christian Dietrich committed
17
using namespace clang;
18
using namespace llvm;
Christian Dietrich's avatar
Christian Dietrich committed
19

20
static std::chrono::high_resolution_clock::time_point StartCompilation;
21

22
23
24
25
26
27
28
29
30
static enum {
    ATEXIT_NOP,
    ATEXIT_FROM_CACHE,
    ATEXIT_TO_CACHE,
} atexit_mode = ATEXIT_NOP;

static char *hashfile = NULL;
static char *hash_new = NULL;

31
32
static char *objectfile = NULL;
static char *objectfile_copy = NULL;
33

34
static void link_object_file() {
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    if (atexit_mode == ATEXIT_NOP) {
        return;
    }
    assert(objectfile != nullptr);
    assert(objectfile_copy != nullptr);

    char *src = nullptr, *dst = nullptr;

    if (atexit_mode == ATEXIT_FROM_CACHE) {
        src = objectfile_copy;
        dst = objectfile;
    } else {
        src = objectfile;
        dst = objectfile_copy;
    }

51
52
53
54
55
    // Record Events
    if (getenv("CLANG_HASH_LOGFILE")) {
        int fd = open(getenv("CLANG_HASH_LOGFILE"),
                      O_APPEND | O_WRONLY | O_CREAT,
                      0644);
56
57
	const char* fail = (atexit_mode == ATEXIT_FROM_CACHE ? "H" : "M");
        write(fd, fail, 1);
58
59
60
        close(fd);
    }

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    /* If destination exists, we have to unlink it. */
    struct stat dummy;
    if (stat(dst, &dummy) == 0) { // exists
        if (unlink(dst) != 0) { // unlink failed
            perror("clang-hash: unlink objectfile/objectfile copy");
            return;
        }
    }
    if (stat(src, &dummy) != 0) { // src exists
        perror("clang-hash: source objectfile/objectfile copy does not exist");
        return;
    }

    // Copy by hardlink
    if (link(src, dst) != 0) {
        perror("clang-hash: objectfile update failed");
        return;
    }

    // Write Hash to hashfile
    if (atexit_mode == ATEXIT_TO_CACHE) {
82
83
84
85
86
        if (hashfile != NULL) {
            FILE *f = fopen(hashfile, "w+");
            fwrite(hash_new, strlen(hash_new), 1, f);
            fclose(f);
        }
Christian Dietrich's avatar
Christian Dietrich committed
87
    } else if (atexit_mode == ATEXIT_FROM_CACHE) {
88
89
        // Update Timestamp
        utime(dst, NULL);
90
91
92
    }
}

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
struct ObjectCache {
    std::string m_cachedir;
    raw_ostream *m_terminal;

    ObjectCache(std::string cachedir, raw_ostream *Terminal)
        : m_cachedir(cachedir),
          m_terminal (Terminal) { }

    char * hash_filename(std::string objectfile) {
        if (m_cachedir == "") {
            std::string path(objectfile + ".hash");
            return strdup(path.c_str());
        }
        return NULL;
    }

    char * objectcopy_filename(std::string objectfile, std::string hash) {
        if (m_cachedir == "") {
            std::string path(objectfile + ".hash.copy");
            return strdup(path.c_str());
        }
        std::string dir(m_cachedir + "/" + hash.substr(0, 2));
        mkdir(dir.c_str(), 0755);
        std::string path(dir + "/" + hash.substr(2) + ".o");
        return strdup(path.c_str());
    }

    std::string find_object_from_hash(std::string objectfile, std::string hash) {
        if (m_cachedir != "") {
            std::string ObjectPath(m_cachedir + "/"
                                   + hash.substr(0,2)
                                   + "/" + hash.substr(2) + ".o");
            struct stat dummy;
            if (stat(ObjectPath.c_str(), &dummy) == 0) {
                // Found!
                return ObjectPath;
            }
            return "";
        } else {
            std::string OldHash;
            std::string HashPath(objectfile + ".hash");
            std::string ObjectPath(objectfile + ".hash.copy");
            std::ifstream FileStream(HashPath);
            if (FileStream.good()) {
                getline(FileStream, OldHash);
                if (m_terminal) {
                    (*m_terminal) << HashPath << ": old hash string: " << OldHash << "\n";
                }
            } else {
                if (m_terminal) {
                    (*m_terminal) << "Warning: could not open file \"" << HashPath
                           << "\", cannot read previous hash.\n";
                }
                return "";
            }

            // Hashes are equal, try to find the objectfile
            if (hash == OldHash) {
                struct stat dummy;
                if (stat(ObjectPath.c_str(), &dummy) == 0) {
                    // Found!
                    return ObjectPath;
                }
            }
            return "";
        }
    }
};

Christian Dietrich's avatar
Christian Dietrich committed
162
class HashTranslationUnitConsumer : public ASTConsumer {
Christian Dietrich's avatar
Christian Dietrich committed
163
public:
164
165
   HashTranslationUnitConsumer(raw_ostream *OS, bool StopIfSameHash)
      : Terminal(OS), StopIfSameHash(StopIfSameHash) {}
166

167
  virtual void HandleTranslationUnit(clang::ASTContext &Context) override {
168
    /// Step 1: Calculate Hash
169
    const auto StartHashing = std::chrono::high_resolution_clock::now();
170

171
172
173
    // Traversing the translation unit decl via a RecursiveASTVisitor
    // will visit all nodes in the AST.
    Visitor.hashDecl(Context.getTranslationUnitDecl());
174

175
    hashCommandLineArguments();
176

177
    const auto FinishHashing = std::chrono::high_resolution_clock::now();
178

179
    unsigned ProcessedBytes;
Ludwig Fueracker's avatar
Ludwig Fueracker committed
180
    const std::string HashString = Visitor.getHash(&ProcessedBytes);
181
    hash_new = strdup(HashString.c_str());
182

183
184
185
    char *cachedir = getenv("CLANG_HASH_CACHE");
    ObjectCache cache(cachedir ? cachedir : "", Terminal);

186
    // Step 2: Consequent Handling
187
188
189
190
191
192
193
194
195
196
197
198
199
    bool HashEqual;
    if (Context.getSourceManager().getDiagnostics().hasErrorOccurred()) {
        HashEqual = false;
    } else {
        std::string copy = cache.find_object_from_hash(objectfile, HashString);
        if (copy != "") {
            HashEqual = true;
            objectfile_copy = strdup(copy.c_str());
        } else {
            HashEqual = false;
        }
    }

200
    // Step 2.1: -hash-verbose
201
202
203
204
    // Sometimes we do terminal output
    if (Terminal) {
        *Terminal << "hash-start-time-ns "
                  << std::chrono::duration_cast<std::chrono::nanoseconds>(
205
                  StartHashing.time_since_epoch()).count() << "\n";
206
207
208
209
210
211
212
213
214
215
        *Terminal << "top-level-hash: " << HashString << "\n";
        *Terminal << "processed-bytes: " << ProcessedBytes << "\n";
        *Terminal << "parse-time-ns: "
                  << std::chrono::duration_cast<std::chrono::nanoseconds>(
                     StartHashing - StartCompilation).count() << "\n";
        *Terminal << "hash-time-ns: "
                  << std::chrono::duration_cast<std::chrono::nanoseconds>(
                     FinishHashing - StartHashing).count() << "\n";
        *Terminal << "element-hashes: [";
        for (const auto &SavedHash : Visitor.DeclSilo) {
216
            break;
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
            const Decl *D = SavedHash.first;
            const Hash::Digest &Dig = SavedHash.second;
            // Only Top-level declarations
            if (D->getDeclContext() &&
                isa<TranslationUnitDecl>(D->getDeclContext()) && isa<NamedDecl>(D)) {
                if (isa<FunctionDecl>(D))
                    *Terminal << "(\"function:";
                else if (isa<VarDecl>(D))
                    *Terminal << "(\"variable ";
                else if (isa<RecordDecl>(D))
                    *Terminal << "(\"record ";
                else
                    continue;

                *Terminal << cast<NamedDecl>(D)->getName();
                *Terminal << "\", \"";
                *Terminal << Dig.asString();
                *Terminal << "\"), ";
            }
        }
        *Terminal << "]\n";
238
239
        *Terminal << "hash-equal:" << HashEqual << "\n";
        *Terminal << "skipped:" << (HashEqual && StopIfSameHash) << "\n";
240
    }
241

242
243
244
245
246
247
248
    if (StopIfSameHash && objectfile != nullptr) {
        // We are in caching mode and there should be an objectfile
        atexit(link_object_file);
        if (HashEqual) {
            atexit_mode = ATEXIT_FROM_CACHE;
            exit(0);
        } else {
249
250
            hashfile = cache.hash_filename(objectfile);
            objectfile_copy = cache.objectcopy_filename(objectfile, HashString);
251
252
            atexit_mode = ATEXIT_TO_CACHE;
            // Continue with compilation
253
        }
254
    }
255
256
  }

257
private:
258
  // Returns true if the -stop-if-same-hash flag is set, else false.
259
  void hashCommandLineArguments() {
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    // Get command line arguments
    const std::string PPID{std::to_string(getppid())};
    const std::string FilePath = "/proc/" + PPID + "/cmdline";
    std::ifstream CommandLine{FilePath};
    if (CommandLine.good()) { // TODO: move this to own method?
      std::list<std::string> CommandLineArgs;
      std::string Arg;
      do {
        getline(CommandLine, Arg, '\0');
        if ("-o" == Arg) {
          // throw away next parameter (name of outfile)
          getline(CommandLine, Arg, '\0');
          continue;
        }
        if (Arg.size() > 2 && Arg.compare(Arg.size() - 2, 2, ".c") == 0)
275
	  continue;
276
        if (Arg.size() > 2 && Arg.compare(Arg.size() - 2, 2, ".i") == 0)
277
	  continue;
278

279
        if (Arg.find("-stop-if-same-hash") != std::string::npos) {
280
281
            continue; // also don't hash this (plugin argument)
        }
282
283
284
285
286
287
288
289
        if (Arg == "-I") {
            // throw away next parameter (include path)
            getline(CommandLine, Arg, '\0');
            continue;
        }
        if (Arg.substr(0,2) == "-I") {
            continue; // also don't hash include paths
        }
290
291
        if (Arg.find("-hash-verbose") != std::string::npos) {
            continue; // also don't hash this (plugin argument)
292
293
294
295
296
297
298
299
300
301
302
303
        }

        CommandLineArgs.push_back(Arg);
      } while (Arg.size());

      Visitor.hashCommandLine(CommandLineArgs);
    } else {
      errs() << "Warning: could not open file \"" << FilePath
             << "\", cannot hash command line arguments.\n";
    }
  }

304
  raw_ostream *const Terminal;
305
  TranslationUnitHashVisitor Visitor;
306
  bool StopIfSameHash;
Christian Dietrich's avatar
Christian Dietrich committed
307
308
};

Christian Dietrich's avatar
Christian Dietrich committed
309
310
class HashTranslationUnitAction : public PluginASTAction {
protected:
311
312
313
    bool StopIfSameHash;
    bool Verbose;

314
  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
315
                                                 StringRef) override {
316
    const std::string &OutputFile = CI.getFrontendOpts().OutputFile;
317
318
319
320
321
322
    if (OutputFile != "" && OutputFile != "/dev/null") {
        std::string tmp;
        objectfile = strdup(OutputFile.c_str());
    }

    // Write hash database to .o.hash if the compiler produces a object file
323
324
325
326
327
328
329
330
    if ((CI.getFrontendOpts().ProgramAction == frontend::EmitObj
         || CI.getFrontendOpts().ProgramAction == frontend::EmitBC
         || CI.getFrontendOpts().ProgramAction == frontend::EmitLLVM)
        && OutputFile != "" && OutputFile != "/dev/null") {
        /* OK.Let's run */
    } else if  (CI.getFrontendOpts().ProgramAction == frontend::ParseSyntaxOnly) {
        /* OK Let's run */
    } else {
331
332
333
        return make_unique<ASTConsumer>();
    }

334
335
    raw_ostream *Terminal = nullptr;
    if (Verbose) Terminal = &errs();
336
337

    return make_unique<HashTranslationUnitConsumer>(Terminal, StopIfSameHash);
338
339
340
  }

  bool ParseArgs(const CompilerInstance &CI,
341
342
                 const std::vector<std::string> &Args) override {
    StartCompilation = std::chrono::high_resolution_clock::now();
343
344
    Verbose = false;
    StopIfSameHash = false;
345
    for (const std::string &Arg : Args) {
346
347
348
349
350
351
        if (Arg == "-hash-verbose") {
            Verbose = true;
        }
        if (Arg == "-stop-if-same-hash") {
            StopIfSameHash = true;
        }
Christian Dietrich's avatar
Christian Dietrich committed
352
    }
353
    if (Args.size() && Args[0] == "help") {
354
      // FIXME
355
      PrintHelp(errs());
356
    }
357
358
359
360
361
362
363
    return true;
  }

  PluginASTAction::ActionType getActionType() override {
    return AddBeforeMainAction;
  }

364
365
  void PrintHelp(raw_ostream &Out) {
    Out << "Help for PrintFunctionNames plugin goes here\n";
366
  }
367

Christian Dietrich's avatar
Christian Dietrich committed
368
369
};

370

Christian Dietrich's avatar
Christian Dietrich committed
371
static FrontendPluginRegistry::Add<HashTranslationUnitAction>
372
    X("clang-hash", "hash translation unit");