/*
|
* Copyright (C) 2016 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#include "Driver.h"
|
|
#include <err.h>
|
#include <string.h>
|
|
#include <chrono>
|
#include <mutex>
|
#include <string>
|
#include <thread>
|
#include <unordered_map>
|
#include <vector>
|
|
#include <clang/AST/ASTConsumer.h>
|
#include <clang/Basic/Diagnostic.h>
|
#include <clang/Basic/TargetInfo.h>
|
#include <clang/Driver/Compilation.h>
|
#include <clang/Driver/Driver.h>
|
#include <clang/Frontend/CompilerInstance.h>
|
#include <clang/Frontend/CompilerInvocation.h>
|
#include <clang/Frontend/FrontendAction.h>
|
#include <clang/Frontend/FrontendActions.h>
|
#include <clang/Frontend/TextDiagnosticPrinter.h>
|
#include <clang/Frontend/Utils.h>
|
#include <clang/FrontendTool/Utils.h>
|
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
#include <llvm/ADT/SmallVector.h>
|
#include <llvm/ADT/StringRef.h>
|
#include <llvm/Option/Option.h>
|
#include <llvm/Support/VirtualFileSystem.h>
|
|
#include "Arch.h"
|
#include "DeclarationDatabase.h"
|
#include "versioner.h"
|
|
using namespace std::chrono_literals;
|
using namespace std::string_literals;
|
|
using namespace clang;
|
|
class VersionerASTConsumer : public clang::ASTConsumer {
|
public:
|
HeaderDatabase* header_database;
|
CompilationType type;
|
|
VersionerASTConsumer(HeaderDatabase* header_database, CompilationType type)
|
: header_database(header_database), type(type) {
|
}
|
|
void HandleTranslationUnit(ASTContext& ctx) override {
|
header_database->parseAST(type, ctx);
|
}
|
};
|
|
class VersionerASTAction : public clang::ASTFrontendAction {
|
public:
|
HeaderDatabase* header_database;
|
CompilationType type;
|
|
VersionerASTAction(HeaderDatabase* header_database, CompilationType type)
|
: header_database(header_database), type(type) {
|
}
|
|
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance&, llvm::StringRef) override {
|
return std::make_unique<VersionerASTConsumer>(header_database, type);
|
}
|
};
|
|
static IntrusiveRefCntPtr<DiagnosticsEngine> constructDiags() {
|
IntrusiveRefCntPtr<DiagnosticOptions> diag_opts(new DiagnosticOptions());
|
auto diag_printer = std::make_unique<TextDiagnosticPrinter>(llvm::errs(), diag_opts.get());
|
IntrusiveRefCntPtr<DiagnosticIDs> diag_ids(new DiagnosticIDs());
|
IntrusiveRefCntPtr<DiagnosticsEngine> diags(
|
new DiagnosticsEngine(diag_ids.get(), diag_opts.get(), diag_printer.release()));
|
return diags;
|
}
|
|
// clang's driver is slow compared to the work it performs to compile our headers.
|
// Run it once to generate flags for each target, and memoize the results.
|
static std::unordered_map<CompilationType, std::vector<std::string>> cc1_flags;
|
static const char* filename_placeholder = "__VERSIONER_PLACEHOLDER__";
|
static void generateTargetCC1Flags(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs,
|
CompilationType type,
|
const std::vector<std::string>& include_dirs) {
|
std::vector<std::string> cmd = { "versioner" };
|
if (type.cpp) {
|
cmd.push_back("-std=gnu++11");
|
cmd.push_back("-x");
|
cmd.push_back("c++");
|
} else {
|
cmd.push_back("-std=gnu11");
|
cmd.push_back("-x");
|
cmd.push_back("c");
|
}
|
|
cmd.push_back("-fsyntax-only");
|
|
cmd.push_back("-Wall");
|
cmd.push_back("-Wextra");
|
cmd.push_back("-Weverything");
|
cmd.push_back("-Werror");
|
cmd.push_back("-Wundef");
|
cmd.push_back("-Wno-unused-macros");
|
cmd.push_back("-Wno-unused-function");
|
cmd.push_back("-Wno-unused-variable");
|
cmd.push_back("-Wno-unknown-attributes");
|
cmd.push_back("-Wno-pragma-once-outside-header");
|
|
cmd.push_back("-target");
|
cmd.push_back(arch_targets[type.arch]);
|
|
cmd.push_back("-DANDROID");
|
cmd.push_back("-D__ANDROID_API__="s + std::to_string(type.api_level));
|
// FIXME: Re-enable FORTIFY properly once our clang in external/ is new enough
|
// to support diagnose_if without giving us syntax errors.
|
#if 0
|
cmd.push_back("-D_FORTIFY_SOURCE=2");
|
#else
|
cmd.push_back("-D_FORTIFY_SOURCE=0");
|
cmd.push_back("-D__BIONIC_DECLARE_FORTIFY_HELPERS");
|
#endif
|
cmd.push_back("-D_GNU_SOURCE");
|
cmd.push_back("-D_FILE_OFFSET_BITS="s + std::to_string(type.file_offset_bits));
|
|
cmd.push_back("-nostdinc");
|
|
if (add_include) {
|
cmd.push_back("-include");
|
cmd.push_back("android/versioning.h");
|
}
|
|
for (const auto& dir : include_dirs) {
|
cmd.push_back("-isystem");
|
cmd.push_back(dir);
|
}
|
|
cmd.push_back("-include");
|
cmd.push_back(filename_placeholder);
|
cmd.push_back("-");
|
|
auto diags = constructDiags();
|
driver::Driver driver("versioner", llvm::sys::getDefaultTargetTriple(), *diags, vfs);
|
driver.setCheckInputsExist(false);
|
|
llvm::SmallVector<const char*, 32> driver_args;
|
for (const std::string& str : cmd) {
|
driver_args.push_back(str.c_str());
|
}
|
|
std::unique_ptr<driver::Compilation> Compilation(driver.BuildCompilation(driver_args));
|
const driver::JobList& jobs = Compilation->getJobs();
|
if (jobs.size() != 1) {
|
errx(1, "driver returned %zu jobs for %s", jobs.size(), to_string(type).c_str());
|
}
|
|
const driver::Command& driver_cmd = llvm::cast<driver::Command>(*jobs.begin());
|
const llvm::opt::ArgStringList& cc_args = driver_cmd.getArguments();
|
|
if (cc_args.size() == 0) {
|
errx(1, "driver returned empty command for %s", to_string(type).c_str());
|
}
|
|
std::vector<std::string> result(cc_args.begin(), cc_args.end());
|
|
{
|
static std::mutex cc1_init_mutex;
|
std::unique_lock<std::mutex> lock(cc1_init_mutex);
|
if (cc1_flags.count(type) > 0) {
|
errx(1, "attemped to generate cc1 flags for existing CompilationType %s",
|
to_string(type).c_str());
|
}
|
|
cc1_flags.emplace(std::make_pair(type, std::move(result)));
|
}
|
}
|
|
static std::vector<const char*> getCC1Command(CompilationType type, const std::string& filename) {
|
const auto& target_flag_it = cc1_flags.find(type);
|
if (target_flag_it == cc1_flags.end()) {
|
errx(1, "failed to find target flags for CompilationType %s", to_string(type).c_str());
|
}
|
|
std::vector<const char*> result;
|
for (const std::string& flag : target_flag_it->second) {
|
if (flag == "-disable-free") {
|
continue;
|
} else if (flag == filename_placeholder) {
|
result.push_back(filename.c_str());
|
} else {
|
result.push_back(flag.c_str());
|
}
|
}
|
return result;
|
}
|
|
void initializeTargetCC1FlagCache(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs,
|
const std::set<CompilationType>& types,
|
const std::unordered_map<Arch, CompilationRequirements>& reqs) {
|
if (!cc1_flags.empty()) {
|
errx(1, "reinitializing target CC1 flag cache?");
|
}
|
|
auto start = std::chrono::high_resolution_clock::now();
|
std::vector<std::thread> threads;
|
for (const CompilationType type : types) {
|
threads.emplace_back([type, &vfs, &reqs]() {
|
const auto& arch_req_it = reqs.find(type.arch);
|
if (arch_req_it == reqs.end()) {
|
errx(1, "CompilationRequirement map missing entry for CompilationType %s",
|
to_string(type).c_str());
|
}
|
|
generateTargetCC1Flags(vfs, type, arch_req_it->second.dependencies);
|
});
|
}
|
for (auto& thread : threads) {
|
thread.join();
|
}
|
auto end = std::chrono::high_resolution_clock::now();
|
|
if (verbose) {
|
auto diff = (end - start) / 1.0ms;
|
printf("Generated compiler flags for %zu targets in %0.2Lfms\n", types.size(), diff);
|
}
|
|
if (cc1_flags.empty()) {
|
errx(1, "failed to initialize target CC1 flag cache");
|
}
|
}
|
|
void compileHeader(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs,
|
HeaderDatabase* header_database, CompilationType type,
|
const std::string& filename) {
|
auto diags = constructDiags();
|
std::vector<const char*> cc1_flags = getCC1Command(type, filename);
|
auto invocation = std::make_unique<CompilerInvocation>();
|
if (!CompilerInvocation::CreateFromArgs(*invocation.get(), &cc1_flags.front(),
|
&cc1_flags.front() + cc1_flags.size(), *diags)) {
|
errx(1, "failed to create CompilerInvocation");
|
}
|
|
clang::CompilerInstance Compiler;
|
|
Compiler.setInvocation(std::move(invocation));
|
Compiler.setDiagnostics(diags.get());
|
Compiler.setVirtualFileSystem(vfs);
|
|
VersionerASTAction versioner_action(header_database, type);
|
if (!Compiler.ExecuteAction(versioner_action)) {
|
errx(1, "compilation generated warnings or errors");
|
}
|
|
if (diags->getNumWarnings() || diags->hasErrorOccurred()) {
|
errx(1, "compilation generated warnings or errors");
|
}
|
}
|