mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-02 22:02:51 +01:00
Move `--output-last-file-only` and output filename synthesis logic out of the general-purpose compile driver and into the `carbon compile` subcommand, which is the only thing that should be using them. Track on CompilationUnit whether it is being lowered, or whether it exists only to be imported into other units. `carbon compile` now never lowers inputs that it discovered for itself, only inputs that were specified on the command line. In particular, it doesn't lower (and throw away the result of lowering) the prelude any more. This makes the toolchain tests about 10% faster in my crude measurements. Also, we now do not create a clang `CodeGenerator` for input files that we are not lowering, similarly saving compilation time for units that exist only to be imported, not lowered. One minor change: we use the same mechanism to determine whether an input is being lowered and to determine what the output filename is. This means that `--phase=lower` and `--phase=optimize`, which lower but don't produce an output file, still need an output filename to be specified now in some cases. Given those are just debugging tools, I think that's fine. Assisted-by: Gemini via Antigravity
665 lines
23 KiB
C++
665 lines
23 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include "toolchain/driver/compile_driver.h"
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <utility>
|
|
|
|
#include "common/error.h"
|
|
#include "common/ostream.h"
|
|
#include "common/pretty_stack_trace_function.h"
|
|
#include "common/vlog.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/ScopeExit.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/MC/TargetRegistry.h"
|
|
#include "llvm/Passes/OptimizationLevel.h"
|
|
#include "llvm/Passes/PassBuilder.h"
|
|
#include "llvm/Passes/StandardInstrumentations.h"
|
|
#include "llvm/Support/SaveAndRestore.h"
|
|
#include "toolchain/base/clang_invocation.h"
|
|
#include "toolchain/base/timings.h"
|
|
#include "toolchain/check/check.h"
|
|
#include "toolchain/codegen/codegen.h"
|
|
#include "toolchain/diagnostics/emitter.h"
|
|
#include "toolchain/diagnostics/format_providers.h"
|
|
#include "toolchain/lex/lex.h"
|
|
#include "toolchain/lower/lower.h"
|
|
#include "toolchain/lower/options.h"
|
|
#include "toolchain/parse/parse.h"
|
|
#include "toolchain/parse/tree_and_subtrees.h"
|
|
#include "toolchain/sem_ir/ids.h"
|
|
#include "toolchain/source/source_buffer.h"
|
|
|
|
namespace Carbon {
|
|
|
|
CompilationUnit::CompilationUnit(
|
|
SemIR::CheckIRId check_ir_id, int total_ir_count, DriverEnv* driver_env,
|
|
const CompileOptions* options, Diagnostics::Consumer* consumer,
|
|
llvm::StringRef input_filename, std::string output_filename,
|
|
const llvm::Target* target, llvm::LLVMContext* llvm_context)
|
|
: check_ir_id_(check_ir_id),
|
|
total_ir_count_(total_ir_count),
|
|
driver_env_(driver_env),
|
|
options_(options),
|
|
target_(target),
|
|
input_filename_(input_filename),
|
|
output_filename_(std::move(output_filename)),
|
|
vlog_stream_(driver_env_->vlog_stream),
|
|
llvm_context_(llvm_context) {
|
|
if (vlog_stream_ != nullptr || options_->stream_errors) {
|
|
consumer_ = consumer;
|
|
} else {
|
|
sorting_consumer_ = Diagnostics::SortingConsumer(*consumer);
|
|
consumer_ = &*sorting_consumer_;
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::IncludeInDumps() -> bool {
|
|
return cache_->include_in_dumps().Get(check_ir_id_);
|
|
}
|
|
|
|
auto CompilationUnit::SetMultiUnitCache(MultiUnitCache* cache) -> void {
|
|
CARBON_CHECK(!cache_, "Called SetMultiUnitCache twice");
|
|
cache_ = cache;
|
|
|
|
// Collect memory usage if this unit dumps it, or if the caller provided a
|
|
// `MemUsage` to merge it into (see `PostCompile`).
|
|
if ((options_->dump_mem_usage || driver_env_->mem_usage) &&
|
|
IncludeInDumps()) {
|
|
CARBON_CHECK(!mem_usage_);
|
|
mem_usage_ = MemUsage();
|
|
}
|
|
if (options_->dump_timings && IncludeInDumps()) {
|
|
CARBON_CHECK(!timings_);
|
|
timings_ = Timings();
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::RunLex() -> void {
|
|
CARBON_CHECK(cache_, "Must call SetMultiUnitCache first");
|
|
CARBON_CHECK(!tokens_, "Called RunLex twice");
|
|
|
|
LogCall("SourceBuffer::MakeFromFileOrStdin", "source", [&] {
|
|
source_ = SourceBuffer::MakeFromFileOrStdin(*driver_env_->fs,
|
|
input_filename_, *consumer_);
|
|
});
|
|
|
|
if (!source_) {
|
|
success_ = false;
|
|
return;
|
|
}
|
|
|
|
if (mem_usage_) {
|
|
mem_usage_->Add("source_", source_->text().size(), source_->text().size());
|
|
}
|
|
|
|
CARBON_VLOG("*** SourceBuffer ***\n```\n{0}\n```\n", source_->text());
|
|
|
|
LogCall("Lex::Lex", "lex", [&] {
|
|
Lex::LexOptions options;
|
|
options.consumer = consumer_;
|
|
options.vlog_stream = vlog_stream_;
|
|
if (options_->dump_tokens && IncludeInDumps()) {
|
|
options.dump_stream = driver_env_->output_stream;
|
|
options.omit_file_boundary_tokens = options_->omit_file_boundary_tokens;
|
|
}
|
|
tokens_ = Lex::Lex(value_stores_, *source_, options);
|
|
});
|
|
if (mem_usage_) {
|
|
mem_usage_->Collect("tokens_", *tokens_);
|
|
}
|
|
if (tokens_->has_errors()) {
|
|
success_ = false;
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::RunParse() -> void {
|
|
LogCall("Parse::Parse", "parse", [&] {
|
|
Parse::ParseOptions options;
|
|
options.consumer = consumer_;
|
|
options.vlog_stream = vlog_stream_;
|
|
if (options_->dump_parse_tree && IncludeInDumps()) {
|
|
options.dump_stream = driver_env_->output_stream;
|
|
options.dump_format = options_->parse_dump_format;
|
|
}
|
|
parse_tree_ = Parse::Parse(*tokens_, options);
|
|
});
|
|
if (mem_usage_) {
|
|
mem_usage_->Collect("parse_tree_", *parse_tree_);
|
|
}
|
|
if (parse_tree_->has_errors()) {
|
|
success_ = false;
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::GetCheckUnit() -> Check::Unit {
|
|
CARBON_CHECK(parse_tree_, "Must call RunParse first");
|
|
CARBON_CHECK(!sem_ir_, "Called GetCheckUnit twice");
|
|
|
|
tree_and_subtrees_getter_ = [this]() -> const Parse::TreeAndSubtrees& {
|
|
return this->GetParseTreeAndSubtrees();
|
|
};
|
|
sem_ir_.emplace(&*parse_tree_, check_ir_id_, parse_tree_->packaging_decl(),
|
|
value_stores_, input_filename_);
|
|
return {.consumer = consumer_,
|
|
.value_stores = &value_stores_,
|
|
.timings = timings_ ? &*timings_ : nullptr,
|
|
.sem_ir = &*sem_ir_,
|
|
.llvm_context = llvm_context_,
|
|
.is_lowered = is_lowered(),
|
|
.total_ir_count = total_ir_count_};
|
|
}
|
|
|
|
auto CompilationUnit::PostCheck() -> void {
|
|
CARBON_CHECK(sem_ir_, "Must call GetCheckUnit first");
|
|
|
|
// We've finished all steps that can produce diagnostics. Emit the
|
|
// diagnostics now, so that the developer sees them sooner and doesn't need
|
|
// to wait for code generation.
|
|
consumer_->Flush();
|
|
|
|
if (mem_usage_) {
|
|
mem_usage_->Collect("sem_ir_", *sem_ir_);
|
|
}
|
|
|
|
if (sem_ir_->has_errors()) {
|
|
success_ = false;
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::RunLower() -> void {
|
|
CARBON_CHECK(is_lowered(), "Should not lower this compilation unit");
|
|
|
|
LogCall("Lower::LowerToLLVM", "lower", [&] {
|
|
Lower::LowerToLLVMOptions options;
|
|
options.llvm_verifier_stream =
|
|
options_->run_llvm_verifier ? driver_env_->error_stream : nullptr;
|
|
options.want_debug_info = options_->include_debug_info;
|
|
options.vlog_stream = vlog_stream_;
|
|
options.opt_level = options_->opt_level;
|
|
options.mangle_string_fingerprint = options_->mangle_string_fingerprint;
|
|
module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs,
|
|
cache_->tree_and_subtrees_getters(), *sem_ir_,
|
|
total_ir_count_, options);
|
|
});
|
|
}
|
|
|
|
auto CompilationUnit::MakeTargetMachine(
|
|
const clang::CompilerInvocation& clang_invocation) -> void {
|
|
CARBON_CHECK(module_, "Must call RunLower first");
|
|
CARBON_CHECK(!target_machine_, "Should not call this multiple times");
|
|
|
|
// Set the target on the module.
|
|
// TODO: We should do this earlier. Lower should be passed the target triple
|
|
// so it can create the module with this already set.
|
|
llvm::Triple target_triple(options_->codegen_options->target);
|
|
module_->setTargetTriple(target_triple);
|
|
|
|
// TODO: Provide flags to control these.
|
|
constexpr llvm::StringLiteral CPU = "generic";
|
|
constexpr llvm::StringLiteral Features = "";
|
|
|
|
const auto& codegen_opts = clang_invocation.getCodeGenOpts();
|
|
|
|
// TODO: Make the code in Clang's BackendUtil.cpp externally accessible and
|
|
// call it from here. This is doing a subset of the same work to translate
|
|
// Clang code generation options into target options.
|
|
llvm::TargetOptions target_opts;
|
|
target_opts.UseInitArray = codegen_opts.UseInitArray;
|
|
target_opts.FunctionSections = codegen_opts.FunctionSections;
|
|
target_opts.DataSections = codegen_opts.DataSections;
|
|
target_opts.UniqueSectionNames = codegen_opts.UniqueSectionNames;
|
|
target_machine_.reset(target_->createTargetMachine(
|
|
target_triple, CPU, Features, target_opts, llvm::Reloc::PIC_));
|
|
}
|
|
|
|
auto CompilationUnit::RunOptimize(
|
|
const clang::CompilerInvocation& clang_invocation) -> void {
|
|
CARBON_CHECK(module_, "Must call RunLower first");
|
|
|
|
// TODO: A lot of the work done here duplicates work done by Clang setting up
|
|
// its pass manager. Moreover, we probably want to pick up Clang's
|
|
// customizations and make use of its flags for controlling LLVM passes. We
|
|
// should consider whether we would be better off running Clang's pass
|
|
// pipeline rather than building one of our own, or factoring out enough of
|
|
// Clang's pipeline builder that we can reuse and further customize it.
|
|
|
|
MakeTargetMachine(clang_invocation);
|
|
|
|
// TODO: There's no way to set these automatically from an
|
|
// llvm::OptimizationLevel. Add such a mechanism to LLVM and use it from
|
|
// here. For now we reconstruct what Clang does by default.
|
|
llvm::PipelineTuningOptions pto;
|
|
bool opt_for_speed = options_->opt_level == Lower::OptimizationLevel::Speed;
|
|
bool opt_for_size_or_speed =
|
|
opt_for_speed || options_->opt_level == Lower::OptimizationLevel::Size;
|
|
// Loop unrolling is enabled by `--optimize=size` but isn't actually performed
|
|
// because we add `optsize` attributes to the function definitions we emit.
|
|
pto.LoopUnrolling = opt_for_size_or_speed;
|
|
pto.LoopInterleaving = opt_for_size_or_speed;
|
|
pto.LoopVectorization = opt_for_speed;
|
|
pto.SLPVectorization = opt_for_size_or_speed;
|
|
|
|
llvm::LoopAnalysisManager lam;
|
|
llvm::FunctionAnalysisManager fam;
|
|
llvm::CGSCCAnalysisManager cgam;
|
|
llvm::ModuleAnalysisManager mam;
|
|
|
|
llvm::PassInstrumentationCallbacks pic;
|
|
|
|
// Register standard pass instrumentations. This adds support for things like
|
|
// `-print-after-all`.
|
|
llvm::StandardInstrumentations si(module_->getContext(),
|
|
/*DebugLogging=*/false);
|
|
si.registerCallbacks(pic);
|
|
|
|
llvm::PassBuilder builder(target_machine_.get(), pto,
|
|
/*PGOOpt=*/std::nullopt, &pic);
|
|
|
|
// TODO: Add an AssignmentTrackingPass for at least `--optimize=debug`.
|
|
|
|
// Set up target library information and add an analysis pass to supply it.
|
|
std::unique_ptr<llvm::TargetLibraryInfoImpl> tlii(llvm::driver::createTLII(
|
|
module_->getTargetTriple(), llvm::driver::VectorLibrary::NoLibrary));
|
|
fam.registerPass([&] { return llvm::TargetLibraryAnalysis(*tlii); });
|
|
|
|
builder.registerModuleAnalyses(mam);
|
|
builder.registerCGSCCAnalyses(cgam);
|
|
builder.registerFunctionAnalyses(fam);
|
|
builder.registerLoopAnalyses(lam);
|
|
builder.crossRegisterProxies(lam, fam, cgam, mam);
|
|
|
|
llvm::ModulePassManager pass_manager = builder.buildPerModuleDefaultPipeline(
|
|
CompileOptions::GetLLVMOptimizationLevel(options_->opt_level));
|
|
|
|
if (vlog_stream_) {
|
|
CARBON_VLOG("*** Running pass pipeline: ");
|
|
pass_manager.printPipeline(
|
|
*vlog_stream_, [&pic](llvm::StringRef class_name) {
|
|
auto pass_name = pic.getPassNameForClassName(class_name);
|
|
return pass_name.empty() ? class_name : pass_name;
|
|
});
|
|
CARBON_VLOG(" ***\n");
|
|
}
|
|
|
|
LogCall("ModulePassManager::run", "optimize",
|
|
[&] { pass_manager.run(*module_, mam); });
|
|
|
|
if (vlog_stream_) {
|
|
CARBON_VLOG("*** Optimized llvm::Module ***\n");
|
|
module_->print(*vlog_stream_, /*AAW=*/nullptr,
|
|
/*ShouldPreserveUseListOrder=*/false,
|
|
/*IsForDebug=*/true);
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::PostLower() -> void {
|
|
CARBON_CHECK(module_, "Must call RunLower first");
|
|
if (options_->dump_llvm_ir && IncludeInDumps()) {
|
|
*driver_env_->output_stream << "; ---\n";
|
|
module_->print(*driver_env_->output_stream, /*AAW=*/nullptr,
|
|
/*ShouldPreserveUseListOrder=*/true);
|
|
*driver_env_->output_stream << "\n";
|
|
}
|
|
}
|
|
|
|
auto CompilationUnit::RunCodeGen() -> void {
|
|
CARBON_CHECK(module_, "Must call RunLower first");
|
|
LogCall("CodeGen", "codegen", [&] { success_ = RunCodeGenHelper(); });
|
|
}
|
|
|
|
auto CompilationUnit::PostCompile() -> void {
|
|
if (options_->dump_shared_values && IncludeInDumps()) {
|
|
Yaml::Print(*driver_env_->output_stream,
|
|
value_stores_.OutputYaml(input_filename_));
|
|
}
|
|
if (mem_usage_) {
|
|
mem_usage_->Collect("value_stores_", value_stores_);
|
|
if (options_->dump_mem_usage && IncludeInDumps()) {
|
|
Yaml::Print(*driver_env_->output_stream,
|
|
mem_usage_->OutputYaml(input_filename_));
|
|
}
|
|
// Merge this file's usage into the caller-provided sink, if any, so it can
|
|
// be queried programmatically.
|
|
if (driver_env_->mem_usage) {
|
|
driver_env_->mem_usage->Add(*mem_usage_);
|
|
}
|
|
}
|
|
if (timings_) {
|
|
Yaml::Print(*driver_env_->output_stream,
|
|
timings_->OutputYaml(input_filename_));
|
|
}
|
|
|
|
// The diagnostics consumer must be flushed before compilation artifacts are
|
|
// destructed, because diagnostics can refer to their state.
|
|
consumer_->Flush();
|
|
}
|
|
|
|
auto CompilationUnit::RunCodeGenHelper() -> bool {
|
|
CARBON_CHECK(module_, "Must call RunLower first");
|
|
CARBON_CHECK(target_machine_, "Must call MakeTargetMachine first");
|
|
|
|
CodeGen codegen(module_.get(), target_machine_.get(), consumer_);
|
|
if (vlog_stream_) {
|
|
CARBON_VLOG("*** Assembly ***\n");
|
|
codegen.EmitAssembly(*vlog_stream_);
|
|
}
|
|
|
|
if (output_filename_ == "-") {
|
|
// TODO: The output file name, forcing object output, and requesting
|
|
// textual assembly output are all somewhat linked flags. We should add
|
|
// some validation that they are used correctly.
|
|
if (options_->force_obj_output) {
|
|
if (!codegen.EmitObject(*driver_env_->output_stream)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!codegen.EmitAssembly(*driver_env_->output_stream)) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
CARBON_VLOG("Writing output to: {0}\n", output_filename_);
|
|
|
|
std::error_code ec;
|
|
llvm::raw_fd_ostream output_file(output_filename_, ec,
|
|
llvm::sys::fs::OF_None);
|
|
if (ec) {
|
|
// TODO: Consider rephrasing the diagnostic to use the file as the `Emit`
|
|
// location.
|
|
CARBON_DIAGNOSTIC(CompileOutputFileOpenError, Error,
|
|
"could not open output file `{0}`: {1}", std::string,
|
|
std::string);
|
|
driver_env_->emitter.Emit(CompileOutputFileOpenError, output_filename_,
|
|
ec.message());
|
|
return false;
|
|
}
|
|
if (options_->asm_output) {
|
|
if (!codegen.EmitAssembly(output_file)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!codegen.EmitObject(output_file)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto CompilationUnit::GetParseTreeAndSubtrees() const
|
|
-> const Parse::TreeAndSubtrees& {
|
|
if (!parse_tree_and_subtrees_) {
|
|
parse_tree_and_subtrees_ = Parse::TreeAndSubtrees(*tokens_, *parse_tree_);
|
|
if (mem_usage_) {
|
|
mem_usage_->Collect("parse_tree_and_subtrees_",
|
|
*parse_tree_and_subtrees_);
|
|
}
|
|
}
|
|
return *parse_tree_and_subtrees_;
|
|
}
|
|
|
|
auto CompilationUnit::LogCall(llvm::StringLiteral logging_label,
|
|
llvm::StringLiteral timing_label,
|
|
llvm::function_ref<auto()->void> fn) -> void {
|
|
PrettyStackTraceFunction trace_file([&](llvm::raw_ostream& out) {
|
|
out << "Filename: " << input_filename_ << "\n";
|
|
});
|
|
CARBON_VLOG("*** {0}: {1} ***\n", logging_label, input_filename_);
|
|
Timings::ScopedTiming timing(timings_ ? &*timings_ : nullptr, timing_label);
|
|
fn();
|
|
CARBON_VLOG("*** {0} done ***\n", logging_label);
|
|
}
|
|
|
|
CompileDriver::CompileDriver(CompileOptions* options) : options_(options) {}
|
|
|
|
auto CompileDriver::Initialize(
|
|
DriverEnv& driver_env,
|
|
llvm::function_ref<auto(llvm::StringRef)->std::string> map_input) -> bool {
|
|
if (!options_->ValidatePhase(driver_env.emitter)) {
|
|
return false;
|
|
}
|
|
|
|
// Validate the target before passing it to Clang.
|
|
const llvm::Target* target;
|
|
if (auto t = options_->ValidateTarget(driver_env.emitter); !t.ok()) {
|
|
return false;
|
|
} else {
|
|
target = *t;
|
|
}
|
|
|
|
if (auto i = options_->BuildClangInvocation(driver_env); !i.ok()) {
|
|
return false;
|
|
} else {
|
|
clang_invocation_ = std::move(*i);
|
|
}
|
|
|
|
// Find the files comprising the prelude if we are importing it.
|
|
// TODO: Replace this with a search for library api files in a
|
|
// package-specific search path based on the library name.
|
|
llvm::SmallVector<std::string> prelude;
|
|
if (options_->prelude_import && !options_->custom_core &&
|
|
options_->phase >= CompileOptions::Phase::Check) {
|
|
if (auto find = driver_env.installation->ReadPreludeManifest();
|
|
!find.ok()) {
|
|
// TODO: Change ReadPreludeManifest to produce diagnostics.
|
|
CARBON_DIAGNOSTIC(CompilePreludeManifestError, Error, "{0}", std::string);
|
|
driver_env.emitter.Emit(CompilePreludeManifestError,
|
|
PrintToString(find.error()));
|
|
return false;
|
|
} else {
|
|
prelude = std::move(*find);
|
|
}
|
|
}
|
|
|
|
// Append the Core library files on request.
|
|
llvm::SmallVector<std::string> core_library;
|
|
if (options_->include_carbon_core && !options_->custom_core &&
|
|
options_->phase >= CompileOptions::Phase::Check) {
|
|
if (auto find = driver_env.installation->ReadCarbonCoreManifest();
|
|
!find.ok()) {
|
|
CARBON_DIAGNOSTIC(CompileCoreManifestError, Error, "{0}", std::string);
|
|
driver_env.emitter.Emit(CompileCoreManifestError,
|
|
PrintToString(find.error()));
|
|
return false;
|
|
} else {
|
|
// Note we also compile any .impl files, as we will need to include them
|
|
// in the subsequent link step.
|
|
core_library = std::move(*find);
|
|
}
|
|
}
|
|
|
|
llvm_context_ = std::make_unique<llvm::LLVMContext>();
|
|
|
|
// Prepare CompilationUnits before building scope exit handlers.
|
|
int unit_index = -1;
|
|
int total_unit_count =
|
|
prelude.size() + core_library.size() + options_->input_filenames.size();
|
|
auto unit_builder = [&](llvm::StringRef filename) {
|
|
++unit_index;
|
|
return std::make_unique<CompilationUnit>(
|
|
SemIR::CheckIRId(unit_index), total_unit_count, &driver_env, options_,
|
|
driver_env.consumer, filename, map_input(filename), target,
|
|
llvm_context_.get());
|
|
};
|
|
llvm::append_range(units_, llvm::map_range(prelude, unit_builder));
|
|
llvm::append_range(units_, llvm::map_range(core_library, unit_builder));
|
|
input_filenames_index_ = units_.size();
|
|
llvm::append_range(units_,
|
|
llvm::map_range(options_->input_filenames, unit_builder));
|
|
CARBON_CHECK(units_.size() == static_cast<size_t>(total_unit_count));
|
|
|
|
// Add the cache to all units. This must be done after all units are created.
|
|
cache_ = std::make_unique<MultiUnitCache>(options_, units_);
|
|
for (auto& unit : units_) {
|
|
unit->SetMultiUnitCache(cache_.get());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
auto CompileDriver::Compile(DriverEnv& driver_env) -> DriverResult {
|
|
auto on_exit = llvm::scope_exit([&]() {
|
|
// Finish compilation units. This flushes their diagnostics in the order in
|
|
// which they were specified on the command line.
|
|
for (auto& unit : units_) {
|
|
unit->PostCompile();
|
|
}
|
|
|
|
driver_env.consumer->Flush();
|
|
});
|
|
|
|
PrettyStackTraceFunction flush_on_crash([&](llvm::raw_ostream& out) {
|
|
// When crashing, flush diagnostics. If sorting diagnostics, they can be
|
|
// redirected to the crash stream; if streaming, the original stream is
|
|
// flushed.
|
|
// TODO: Eventually we'll want to limit the count.
|
|
if (options_->stream_errors) {
|
|
out << "Flushing diagnostics\n";
|
|
} else {
|
|
out << "Pending diagnostics:\n";
|
|
}
|
|
|
|
// In non-streaming mode, swap out the consumer for one that writes to the
|
|
// given ostream before flushing the diagnostics.
|
|
Diagnostics::StreamConsumer stack_trace_consumer(&out);
|
|
llvm::SaveAndRestore<Diagnostics::Consumer*> restore(
|
|
driver_env.consumer,
|
|
options_->stream_errors ? driver_env.consumer : &stack_trace_consumer);
|
|
|
|
for (auto& unit : units_) {
|
|
unit->FlushForStackTrace();
|
|
}
|
|
driver_env.consumer->Flush();
|
|
});
|
|
|
|
// Returns a DriverResult object. Called whenever Compile returns.
|
|
auto make_result = [&]() {
|
|
DriverResult result = {.success = true};
|
|
for (const auto& unit : units_) {
|
|
result.success &= unit->success();
|
|
result.per_file_success.push_back(
|
|
{unit->input_filename().str(), unit->success()});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// Lex.
|
|
for (auto& unit : units_) {
|
|
unit->RunLex();
|
|
}
|
|
if (options_->phase == CompileOptions::Phase::Lex) {
|
|
return make_result();
|
|
}
|
|
cache_->ApplyPerFileIncludeInDumps();
|
|
// Parse and check phases examine `has_source` because they want to proceed if
|
|
// lex failed, but not if source doesn't exist. Later steps are skipped if
|
|
// anything failed, so don't need this.
|
|
|
|
// Parse.
|
|
for (auto& unit : units_) {
|
|
if (unit->has_source()) {
|
|
unit->RunParse();
|
|
}
|
|
}
|
|
if (options_->phase == CompileOptions::Phase::Parse) {
|
|
return make_result();
|
|
}
|
|
|
|
// Gather Check::Units.
|
|
llvm::SmallVector<Check::Unit> check_units;
|
|
check_units.reserve(units_.size());
|
|
for (auto& unit : units_) {
|
|
if (unit->has_source()) {
|
|
check_units.push_back(unit->GetCheckUnit());
|
|
}
|
|
}
|
|
|
|
// Execute the actual checking.
|
|
CARBON_VLOG_TO(driver_env.vlog_stream, "*** Check::CheckParseTrees ***\n");
|
|
Check::CheckParseTreesOptions options;
|
|
options.prelude_import = options_->prelude_import;
|
|
options.vlog_stream = driver_env.vlog_stream;
|
|
options.fuzzing = driver_env.fuzzing;
|
|
options.mangle_string_fingerprint = options_->mangle_string_fingerprint;
|
|
options.share_cpp_ast = options_->share_cpp_ast;
|
|
if (options.vlog_stream || options_->dump_sem_ir || options_->dump_cpp_ast ||
|
|
options_->dump_raw_sem_ir) {
|
|
options.include_in_dumps = &cache_->include_in_dumps();
|
|
if (options_->dump_sem_ir) {
|
|
options.dump_stream = driver_env.output_stream;
|
|
}
|
|
if (options_->dump_cpp_ast) {
|
|
options.dump_cpp_ast_stream = driver_env.output_stream;
|
|
}
|
|
if (options.vlog_stream || options_->dump_sem_ir) {
|
|
options.dump_sem_ir_ranges = options_->dump_sem_ir_ranges;
|
|
}
|
|
if (options_->dump_raw_sem_ir) {
|
|
options.raw_dump_stream = driver_env.output_stream;
|
|
options.dump_raw_sem_ir_builtins = options_->builtin_sem_ir;
|
|
}
|
|
options.sem_ir_crash_dump = options_->sem_ir_crash_dump;
|
|
}
|
|
|
|
Check::CheckParseTrees(check_units, cache_->tree_and_subtrees_getters(),
|
|
driver_env.fs, options, clang_invocation_);
|
|
CARBON_VLOG_TO(driver_env.vlog_stream,
|
|
"*** Check::CheckParseTrees done ***\n");
|
|
for (auto& unit : units_) {
|
|
if (unit->has_source()) {
|
|
unit->PostCheck();
|
|
}
|
|
}
|
|
if (options_->phase == CompileOptions::Phase::Check) {
|
|
return make_result();
|
|
}
|
|
|
|
// Unlike previous steps, errors block further progress.
|
|
if (llvm::any_of(units_,
|
|
[&](const auto& unit) { return !unit->success(); })) {
|
|
CARBON_VLOG_TO(driver_env.vlog_stream,
|
|
"*** Stopping before lowering due to errors ***\n");
|
|
return make_result();
|
|
}
|
|
|
|
// Lower and optimize.
|
|
for (const auto& unit : units_) {
|
|
if (!unit->is_lowered()) {
|
|
continue;
|
|
}
|
|
|
|
unit->RunLower();
|
|
|
|
if (options_->phase != CompileOptions::Phase::Lower) {
|
|
unit->RunOptimize(*clang_invocation_);
|
|
}
|
|
|
|
unit->PostLower();
|
|
}
|
|
if (options_->phase == CompileOptions::Phase::Lower ||
|
|
options_->phase == CompileOptions::Phase::Optimize) {
|
|
return make_result();
|
|
}
|
|
CARBON_CHECK(options_->phase == CompileOptions::Phase::CodeGen,
|
|
"CodeGen should be the last stage");
|
|
|
|
// Codegen.
|
|
for (const auto& unit : units_) {
|
|
if (unit->is_lowered()) {
|
|
unit->RunCodeGen();
|
|
}
|
|
}
|
|
return make_result();
|
|
}
|
|
|
|
} // namespace Carbon
|