diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index 9f236caadd16..6204e764b9b1 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -7,11 +7,16 @@ #include #include +#include "clang/AST/ASTContext.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/MultiplexExternalSemaSource.h" +#include "clang/Sema/Sema.h" #include "common/check.h" #include "common/map.h" #include "common/pretty_stack_trace_function.h" #include "toolchain/check/check_unit.h" #include "toolchain/check/context.h" +#include "toolchain/check/cpp/generate_ast.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/diagnostic_emitter.h" #include "toolchain/check/diagnostic_helpers.h" @@ -23,6 +28,7 @@ #include "toolchain/parse/tree.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/formatter.h" +#include "toolchain/sem_ir/read_only_ast_source.h" #include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -492,14 +498,42 @@ auto CheckParseTrees( } } + // C++ domains used across files. When compiling with a single ASTContext + // (`options.share_cpp_ast`), there is only a single shared domain. + llvm::SmallVector> cpp_domains; + if (options.share_cpp_ast) { + // TODO: Remove dependence on properties of the first unit here. + auto shared_cpp_domain = InitializeCppDomain( + unit_infos.front().err_tracker, + unit_infos.front().unit->sem_ir->filename(), fs, + unit_infos.front().unit->llvm_context, clang_invocation); + if (shared_cpp_domain) { + cpp_domains.push_back(shared_cpp_domain); + for (auto& target_info : unit_infos) { + target_info.cpp_domain = shared_cpp_domain; + } + } + } else { + for (auto& unit_info : unit_infos) { + if (unit_info.cpp_imports.empty()) { + continue; + } + unit_info.cpp_domain = InitializeCppDomain( + unit_info.err_tracker, unit_info.unit->sem_ir->filename(), fs, + unit_info.unit->llvm_context, clang_invocation); + if (unit_info.cpp_domain) { + cpp_domains.push_back(unit_info.cpp_domain); + } + } + } + // Check everything with no dependencies. Earlier entries with dependencies // will be checked as soon as all their dependencies have been checked. for (int check_index = 0; check_index < static_cast(ready_to_check.size()); ++check_index) { auto* unit_info = ready_to_check[check_index]; - CheckUnit(unit_info, &tree_and_subtrees_getters, fs, - unit_info->unit->llvm_context, clang_invocation, - options.vlog_stream, options.mangle_string_fingerprint) + CheckUnit(unit_info, &tree_and_subtrees_getters, options.vlog_stream, + options.mangle_string_fingerprint) .Run(); for (auto* incoming_import : unit_info->incoming_imports) { --incoming_import->imports_remaining; @@ -547,14 +581,18 @@ auto CheckParseTrees( // incomplete imports. for (auto& unit_info : unit_infos) { if (unit_info.imports_remaining > 0) { - CheckUnit(&unit_info, &tree_and_subtrees_getters, fs, - unit_info.unit->llvm_context, clang_invocation, - options.vlog_stream, options.mangle_string_fingerprint) + CheckUnit(&unit_info, &tree_and_subtrees_getters, options.vlog_stream, + options.mangle_string_fingerprint) .Run(); } } } + // Finalize all C++ domains at the end of checking. + for (const auto& domain : cpp_domains) { + FinalizeCppDomain(*domain); + } + MaybeDumpSemIR(units, tree_and_subtrees_getters, options); MaybeDumpCppAST(units, options); } diff --git a/toolchain/check/check.h b/toolchain/check/check.h index 668f2962b113..04f3ced60631 100644 --- a/toolchain/check/check.h +++ b/toolchain/check/check.h @@ -32,6 +32,8 @@ struct Unit { int total_ir_count; }; +struct CppDomain; + struct CheckParseTreesOptions { // Options must be set individually, not through initialization. explicit CheckParseTreesOptions() = default; @@ -79,6 +81,9 @@ struct CheckParseTreesOptions { // Whether to use the string form of the fingerprint from mangling instead of // the hash form. bool mangle_string_fingerprint = false; + + // Whether to share a single Clang ASTContext across all files. + bool share_cpp_ast = false; }; // Checks a group of parse trees. This will use imports to decide the order of diff --git a/toolchain/check/check_unit.cpp b/toolchain/check/check_unit.cpp index 9a1d9e431cff..81a07c29f37c 100644 --- a/toolchain/check/check_unit.cpp +++ b/toolchain/check/check_unit.cpp @@ -61,16 +61,10 @@ static auto GetImportedIRCount(UnitAndImports* unit_and_imports) -> int { CheckUnit::CheckUnit( UnitAndImports* unit_and_imports, const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr clang_invocation, llvm::raw_ostream* vlog_stream, bool mangle_string_fingerprint) : unit_and_imports_(unit_and_imports), tree_and_subtrees_getter_(tree_and_subtrees_getters->Get( unit_and_imports->unit->sem_ir->check_ir_id())), - fs_(std::move(fs)), - llvm_context_(llvm_context), - clang_invocation_(std::move(clang_invocation)), emitter_(&unit_and_imports_->err_tracker, tree_and_subtrees_getters, unit_and_imports_->unit->sem_ir), context_(&emitter_, tree_and_subtrees_getter_, @@ -165,10 +159,8 @@ auto CheckUnit::InitPackageScopeAndImports() -> void { CARBON_CHECK(context_.scope_stack().PeekIndex() == ScopeIndex::Package); ImportOtherPackages(namespace_type_id); - const auto& cpp_imports = unit_and_imports_->cpp_imports; - if (!cpp_imports.empty()) { - ImportCpp(context_, cpp_imports, fs_, llvm_context_, clang_invocation_); - } + ImportCpp(context_, unit_and_imports_->cpp_imports, + unit_and_imports_->cpp_domain.get()); } auto CheckUnit::CollectDirectImports( diff --git a/toolchain/check/check_unit.h b/toolchain/check/check_unit.h index 786e5577e073..9815167fb1e3 100644 --- a/toolchain/check/check_unit.h +++ b/toolchain/check/check_unit.h @@ -20,6 +20,7 @@ class CompilerInvocation; namespace Carbon::Check { struct UnitAndImports; +struct CppDomain; // A file's imports corresponding to a single package, for // `UnitAndImports::package_imports`. @@ -101,6 +102,9 @@ struct UnitAndImports { // List of the `import Cpp` imports. llvm::SmallVector cpp_imports; + // The C++ domain for this unit. + std::shared_ptr cpp_domain; + // The remaining number of imports which must be checked before this unit can // be processed. int32_t imports_remaining = 0; @@ -129,9 +133,6 @@ class CheckUnit { explicit CheckUnit( UnitAndImports* unit_and_imports, const Parse::GetTreeAndSubtreesStore* tree_and_subtrees_getters, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr clang_invocation, llvm::raw_ostream* vlog_stream, bool mangle_string_fingerprint = false); // Produces and checks the IR for the provided unit. @@ -192,9 +193,6 @@ class CheckUnit { UnitAndImports* unit_and_imports_; Parse::GetTreeAndSubtreesFn tree_and_subtrees_getter_; - llvm::IntrusiveRefCntPtr fs_; - llvm::LLVMContext* llvm_context_; - std::shared_ptr clang_invocation_; DiagnosticEmitter emitter_; Context context_; diff --git a/toolchain/check/cpp/context.cpp b/toolchain/check/cpp/context.cpp index f82b1d4a22c3..1f81b7b689e5 100644 --- a/toolchain/check/cpp/context.cpp +++ b/toolchain/check/cpp/context.cpp @@ -11,18 +11,15 @@ namespace Carbon::Check { CppContext::CppContext(clang::CompilerInstance& instance, - std::unique_ptr parser) + std::shared_ptr parser, + std::unique_ptr listener) : ast_context_(&instance.getASTContext()), sema_(&instance.getSema()), - parser_(std::move(parser)) {} + parser_(std::move(parser)), + diagnostic_listener_(std::move(listener)) {} CppContext::~CppContext() = default; -auto CppContext::set_diagnostic_listener( - std::unique_ptr listener) -> void { - diagnostic_listener_ = std::move(listener); -} - auto CppContext::clang_mangle_context() -> clang::MangleContext& { if (!clang_mangle_context_) { clang_mangle_context_.reset(ast_context().createMangleContext()); diff --git a/toolchain/check/cpp/context.h b/toolchain/check/cpp/context.h index 83424c61ec85..cffcd5eb14b1 100644 --- a/toolchain/check/cpp/context.h +++ b/toolchain/check/cpp/context.h @@ -31,12 +31,14 @@ namespace Carbon::Check { class CppContext { public: explicit CppContext(clang::CompilerInstance& instance, - std::unique_ptr parser); + std::shared_ptr parser, + std::unique_ptr listener); ~CppContext(); auto ast_context() -> clang::ASTContext& { return *ast_context_; } auto sema() -> clang::Sema& { return *sema_; } auto parser() -> clang::Parser& { return *parser_; } + auto parser_ptr() const -> std::shared_ptr { return parser_; } auto clang_mangle_context() -> clang::MangleContext&; @@ -51,12 +53,6 @@ class CppContext { placement_new_decl_ = decl; } - auto diagnostic_listener() -> CppDiagnosticListener* { - return diagnostic_listener_.get(); - } - auto set_diagnostic_listener(std::unique_ptr listener) - -> void; - private: // The Clang AST context. clang::ASTContext* ast_context_; @@ -65,7 +61,7 @@ class CppContext { clang::Sema* sema_; // The Clang parser. - std::unique_ptr parser_; + std::shared_ptr parser_; // Per-Carbon-file start locations for corresponding Clang source buffers. // Owned and managed by code in location.cpp. diff --git a/toolchain/check/cpp/diagnostic_consumer.cpp b/toolchain/check/cpp/diagnostic_consumer.cpp index 726a8f54a32c..e4f4d977e394 100644 --- a/toolchain/check/cpp/diagnostic_consumer.cpp +++ b/toolchain/check/cpp/diagnostic_consumer.cpp @@ -8,6 +8,7 @@ #include #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceManager.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/TextDiagnostic.h" #include "common/check.h" @@ -25,6 +26,34 @@ namespace Carbon::Check { class CarbonClangDiagnosticConsumer; +// A diagnostic emitter that maps Clang SourceLocations to Carbon diagnostic +// locations. +class ClangLocDiagnosticEmitter + : public Diagnostics::Emitter { + public: + explicit ClangLocDiagnosticEmitter(Diagnostics::Consumer* consumer, + const clang::SourceManager* source_manager) + : Emitter(consumer), source_manager_(source_manager) {} + + protected: + auto ConvertLoc(clang::SourceLocation loc, ContextFnT /*context_fn*/) const + -> Diagnostics::ConvertedLoc override { + Diagnostics::Loc result_loc; + if (source_manager_ && loc.isValid()) { + clang::PresumedLoc presumed_loc = source_manager_->getPresumedLoc(loc); + if (presumed_loc.isValid()) { + result_loc.filename = presumed_loc.getFilename(); + result_loc.line_number = presumed_loc.getLine(); + result_loc.column_number = presumed_loc.getColumn(); + } + } + return {.loc = result_loc, .last_byte_offset = -1}; + } + + private: + const clang::SourceManager* source_manager_; +}; + // Returns the diagnostic to use for a given Clang diagnostic level. static auto GetDiagnostic(clang::DiagnosticsEngine::Level level) -> const Diagnostics::DiagnosticBase& { @@ -56,27 +85,30 @@ static auto GetDiagnostic(clang::DiagnosticsEngine::Level level) // Diagnostics::Consumer when no Carbon Context is active. class FallbackDiagnosticListener : public CppDiagnosticListener { public: - explicit FallbackDiagnosticListener(CarbonClangDiagnosticConsumer& consumer, - Diagnostics::Consumer& next_consumer) - : CppDiagnosticListener(consumer), next_consumer_(&next_consumer) {} - - ~FallbackDiagnosticListener() override; + explicit FallbackDiagnosticListener( + CarbonClangDiagnosticConsumer& clang_consumer, + Diagnostics::Consumer& carbon_consumer) + : CppDiagnosticListener(clang_consumer), + carbon_consumer_(&carbon_consumer) {} auto EmitDiagnostics(llvm::ArrayRef diags) -> void override { if (diags.empty()) { return; } - Diagnostics::NoLocEmitter emitter(next_consumer_); + ClangLocDiagnosticEmitter emitter(carbon_consumer_, + diags[0].source_manager); for (size_t i = 0; i != diags.size(); ++i) { const Diagnostic& info = diags[i]; auto builder = - emitter.Build(nullptr, GetDiagnostic(info.level), info.message); + emitter.Build(info.location, GetDiagnostic(info.level), info.message); builder.OverrideSnippet(info.snippet); for (; i + 1 < diags.size() && diags[i + 1].level == clang::DiagnosticsEngine::Note; ++i) { const Diagnostic& note_info = diags[i + 1]; - builder.Note(nullptr, GetDiagnostic(note_info.level), note_info.message) + builder + .Note(note_info.location, GetDiagnostic(note_info.level), + note_info.message) .OverrideSnippet(note_info.snippet); } builder.Emit(); @@ -84,7 +116,7 @@ class FallbackDiagnosticListener : public CppDiagnosticListener { } private: - Diagnostics::Consumer* next_consumer_; + Diagnostics::Consumer* carbon_consumer_; }; // A listener that converts Clang diagnostics to Carbon diagnostics using a @@ -95,8 +127,6 @@ class ContextDiagnosticListener : public CppDiagnosticListener { Context& context) : CppDiagnosticListener(consumer), context_(&context) {} - ~ContextDiagnosticListener() override; - auto EmitDiagnostics(llvm::ArrayRef diags) -> void override { if (diags.empty()) { return; @@ -213,8 +243,11 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer { diag_level, message, info.getRanges(), info.getFixItHints()); } + const clang::SourceManager* source_manager = + info.hasSourceManager() ? &info.getSourceManager() : nullptr; diagnostic_infos_.push_back({.level = diag_level, .location = info.getLocation(), + .source_manager = source_manager, .message = message.str().str(), .snippet = snippet_stream.TakeStr()}); } @@ -257,12 +290,6 @@ class CarbonClangDiagnosticConsumer : public clang::DiagnosticConsumer { std::shared_ptr invocation_; }; -FallbackDiagnosticListener::~FallbackDiagnosticListener() { - consumer().Flush(); -} - -ContextDiagnosticListener::~ContextDiagnosticListener() { consumer().Flush(); } - CppDiagnosticListener::CppDiagnosticListener( CarbonClangDiagnosticConsumer& consumer) : consumer_(&consumer) { @@ -281,6 +308,10 @@ auto MakeDiagnosticConsumer( std::move(invocation)); } +auto FlushDiagnosticConsumer(clang::DiagnosticConsumer& consumer) -> void { + static_cast(consumer).Flush(); +} + auto MakeContextDiagnosticListener(clang::DiagnosticConsumer& consumer, Context& context) -> std::unique_ptr { diff --git a/toolchain/check/cpp/diagnostic_consumer.h b/toolchain/check/cpp/diagnostic_consumer.h index 3b51574164e6..419e0e36269a 100644 --- a/toolchain/check/cpp/diagnostic_consumer.h +++ b/toolchain/check/cpp/diagnostic_consumer.h @@ -29,6 +29,10 @@ auto MakeDiagnosticConsumer( std::shared_ptr invocation) -> std::unique_ptr; +// Flushes any pending diagnostics in the given consumer, which must have been +// created by `MakeDiagnosticConsumer`. +auto FlushDiagnosticConsumer(clang::DiagnosticConsumer& consumer) -> void; + // Creates a diagnostic listener attached to the given Carbon context. The // returned listener must not outlive the context. auto MakeContextDiagnosticListener(clang::DiagnosticConsumer& consumer, diff --git a/toolchain/check/cpp/diagnostic_listener.h b/toolchain/check/cpp/diagnostic_listener.h index 08786133a348..710a1234c09c 100644 --- a/toolchain/check/cpp/diagnostic_listener.h +++ b/toolchain/check/cpp/diagnostic_listener.h @@ -11,6 +11,10 @@ #include "clang/Basic/SourceLocation.h" #include "llvm/ADT/ArrayRef.h" +namespace clang { +class SourceManager; +} // namespace clang + namespace Carbon::Check { class CarbonClangDiagnosticConsumer; @@ -25,6 +29,7 @@ class CppDiagnosticListener { struct Diagnostic { clang::DiagnosticsEngine::Level level; clang::SourceLocation location; + const clang::SourceManager* source_manager = nullptr; std::string message; std::string snippet; }; diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 19e369a1480c..1573f8bf6693 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -137,7 +137,9 @@ class CarbonExternalASTSource : public SemIR::ReadOnlyASTSource { explicit CarbonExternalASTSource(Context* context) : ReadOnlyASTSource(context->sem_ir()), context_(context) {} - auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override; + // Builds the top-level C++ namespace `Carbon` and adds it to the translation + // unit. + auto BuildCarbonNamespace() -> void; // Look up decls for `decl_name` inside `decl_context`, adding the decls to // `decl_context`. Returns true if any decls were added. @@ -178,10 +180,6 @@ class CarbonExternalASTSource : public SemIR::ReadOnlyASTSource { } private: - // Builds the top-level C++ namespace `Carbon` and adds it to the translation - // unit. - auto BuildCarbonNamespace() -> void; - // Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot // currently be represented in C++. auto MapInstIdToClangDeclOrType(LookupResult lookup) @@ -218,11 +216,6 @@ char CarbonExternalASTSource::id; } // namespace -void CarbonExternalASTSource::StartTranslationUnit( - clang::ASTConsumer* /*Consumer*/) { - BuildCarbonNamespace(); -} - auto CarbonExternalASTSource::MapInstIdToClangDeclOrType(LookupResult lookup) -> std::variant { auto target_inst_id = lookup.scope_result.target_inst_id(); @@ -321,16 +314,24 @@ auto CarbonExternalASTSource::BuildCarbonNamespace() -> void { auto& ast_context = context_->ast_context(); auto* identifier = &ast_context.Idents.get(carbon_namespace_name); - // Create the namespace and add it to the translation unit scope. auto* decl_context = ast_context.getTranslationUnitDecl(); - auto* carbon_cpp_namespace = clang::NamespaceDecl::Create( - ast_context, decl_context, /*Inline=*/false, clang::SourceLocation(), - clang::SourceLocation(), identifier, /*PrevDecl=*/nullptr, - /*Nested=*/false); - decl_context->addDecl(carbon_cpp_namespace); - // We provide custom lookup results within this namespace. - carbon_cpp_namespace->setHasExternalVisibleStorage(); + // Check if it already exists. + clang::NamespaceDecl* carbon_cpp_namespace = nullptr; + auto lookup_result = decl_context->lookup(identifier); + if (!lookup_result.empty()) { + carbon_cpp_namespace = cast(lookup_result.front()); + } else { + // Create it if it doesn't exist. + carbon_cpp_namespace = clang::NamespaceDecl::Create( + ast_context, decl_context, /*Inline=*/false, clang::SourceLocation(), + clang::SourceLocation(), identifier, /*PrevDecl=*/nullptr, + /*Nested=*/false); + decl_context->addDecl(carbon_cpp_namespace); + + // We provide custom lookup results within this namespace. + carbon_cpp_namespace->setHasExternalVisibleStorage(); + } // Register this file's package scope as corresponding to the `Carbon` // namespace in C++. @@ -610,34 +611,65 @@ static auto ParseTopLevelDecls(clang::Parser& parser, } } +// Injects the C++ code in `buffer` into the Clang preprocessor and parses it +// as top-level declarations. Returns true on success, false if entering the +// source file fails. +static auto InjectAndParse(Context& context, + std::unique_ptr buffer) -> bool { + auto* cpp_context = context.cpp_context(); + CARBON_CHECK(cpp_context); + + clang::Sema& sema = cpp_context->sema(); + clang::Preprocessor& preprocessor = sema.getPreprocessor(); + clang::Parser& parser = cpp_context->parser(); + + clang::FileID file_id = + preprocessor.getSourceManager().createFileID(std::move(buffer)); + if (preprocessor.EnterSourceFile(file_id, nullptr, clang::SourceLocation())) { + return false; + } + + if (parser.getCurToken().is(clang::tok::eof)) { + parser.ConsumeToken(); + } + ParseTopLevelDecls(parser, sema.getASTConsumer()); + + return true; +} + namespace { // An action and a set of registered Clang callbacks used to generate an AST // from a set of Cpp imports. class GenerateASTAction : public clang::ASTFrontendAction { public: - explicit GenerateASTAction( - Context& context, - std::unique_ptr diagnostic_listener) - : context_(&context), - diagnostic_listener_(std::move(diagnostic_listener)) {} + explicit GenerateASTAction(llvm::StringRef filename, + llvm::LLVMContext* llvm_context) + : filename_(filename), llvm_context_(llvm_context) {} + + auto code_generator() const -> clang::CodeGenerator* { + return code_generator_; + } + + auto TakeParser() -> std::unique_ptr { + return std::move(parser_); + } protected: auto CreateASTConsumer(clang::CompilerInstance& clang_instance, llvm::StringRef /*file*/) -> std::unique_ptr override { - auto& cpp_file = *context_->sem_ir().cpp_file(); - if (!cpp_file.llvm_context()) { + if (!llvm_context_) { return std::make_unique(); } auto code_generator = std::unique_ptr(clang::CreateLLVMCodeGen( - cpp_file.diagnostics(), context_->sem_ir().filename(), + clang_instance.getDiagnostics(), filename_, clang_instance.getVirtualFileSystemPtr(), clang_instance.getHeaderSearchOpts(), clang_instance.getPreprocessorOpts(), - clang_instance.getCodeGenOpts(), *cpp_file.llvm_context())); - cpp_file.SetCodeGenerator(code_generator.get()); + clang_instance.getCodeGenOpts(), *llvm_context_)); + code_generator_ = code_generator.get(); return code_generator; } @@ -657,18 +689,12 @@ class GenerateASTAction : public clang::ASTFrontendAction { clang_instance.createSema(getTranslationUnitKind(), /*CompletionConsumer=*/nullptr); - auto parser_ptr = std::make_unique( - clang_instance.getPreprocessor(), clang_instance.getSema(), - /*SkipFunctionBodies=*/false); - auto& parser = *parser_ptr; + parser_ = std::make_unique(clang_instance.getPreprocessor(), + clang_instance.getSema(), + /*SkipFunctionBodies=*/false); clang_instance.getPreprocessor().EnterMainSourceFile(); - parser.Initialize(); - - auto cpp_context = - std::make_unique(clang_instance, std::move(parser_ptr)); - cpp_context->set_diagnostic_listener(std::move(diagnostic_listener_)); - context_->set_cpp_context(std::move(cpp_context)); + parser_->Initialize(); if (auto* source = clang_instance.getASTContext().getExternalSource()) { source->StartTranslationUnit(&clang_instance.getASTConsumer()); @@ -676,40 +702,47 @@ class GenerateASTAction : public clang::ASTFrontendAction { clang_instance.getSema().ActOnStartOfTranslationUnit(); - ParseTopLevelDecls(parser, clang_instance.getASTConsumer()); + ParseTopLevelDecls(*parser_, clang_instance.getASTConsumer()); } private: - Context* context_; - std::unique_ptr diagnostic_listener_; + std::string filename_; + llvm::LLVMContext* llvm_context_; + clang::CodeGenerator* code_generator_ = nullptr; + std::unique_ptr parser_; }; } // namespace -auto GenerateAst(Context& context, - llvm::ArrayRef imports, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr base_invocation) - -> bool { - CARBON_CHECK(!context.cpp_context()); - CARBON_CHECK(!context.sem_ir().cpp_file()); +// Initializes the Clang state by building a new compiler invocation, +// creating a diagnostics engine, and parsing a dummy main file containing a +// semicolon. Returns the initialized state, or null on failure. +auto InitializeCppDomain( + Diagnostics::Consumer& consumer, llvm::StringRef filename, + llvm::IntrusiveRefCntPtr fs, + llvm::LLVMContext* llvm_context, + std::shared_ptr base_invocation) + -> std::shared_ptr { + std::shared_ptr clang_instance; + llvm::IntrusiveRefCntPtr diags; + // Build a new invocation. auto invocation = std::make_shared(*base_invocation); // Ask Clang to not leak memory. invocation->getFrontendOpts().DisableFree = false; - auto diagnostic_consumer = - MakeDiagnosticConsumer(context.emitter().consumer(), invocation); - auto* diagnostic_consumer_ptr = diagnostic_consumer.get(); - // Build a diagnostics engine. - llvm::IntrusiveRefCntPtr diags( - clang::CompilerInstance::createDiagnostics( - *fs, invocation->getDiagnosticOpts(), diagnostic_consumer.release(), - /*ShouldOwnClient=*/true)); + diags = clang::CompilerInstance::createDiagnostics( + *fs, invocation->getDiagnosticOpts(), + MakeDiagnosticConsumer(consumer, invocation).release(), + /*ShouldOwnClient=*/true); + + // Ensure any diagnostics emitted in this function are flushed before we + // return. + auto on_exit = + llvm::scope_exit([&]() { FlushDiagnosticConsumer(*diags->getClient()); }); // Extract the input from the frontend invocation and make sure it makes // sense. @@ -719,47 +752,31 @@ auto GenerateAst(Context& context, inputs[0].getKind().getFormat() == clang::InputKind::Source); llvm::StringRef file_name = inputs[0].getFile(); - // Remap the imports file name to the corresponding `#include`s. - // TODO: Modify the frontend options to specify this memory buffer as input - // instead of remapping the file. - std::string includes = GenerateCppIncludesHeaderCode(context, imports); - auto includes_buffer = - llvm::MemoryBuffer::getMemBufferCopy(includes, file_name); + // Remap the input file to a dummy buffer containing a semicolon to start + // with an empty AST. Clang requires at least one token in the main file + // to avoid assertion failures if it later encounters module declarations. + // TODO: See if we can fix this by injecting code into the main file rather + // than entering nested buffers. + auto empty_buffer = llvm::MemoryBuffer::getMemBuffer(";"); invocation->getPreprocessorOpts().addRemappedFile(file_name, - includes_buffer.release()); + empty_buffer.release()); - auto clang_instance_ptr = - std::make_unique(invocation); - auto& clang_instance = *clang_instance_ptr; - context.sem_ir().set_cpp_file(std::make_unique( - std::move(clang_instance_ptr), llvm_context)); + clang_instance = std::make_shared(invocation); - // Register an annotation scope to flush any Clang diagnostics when we return. - // This ensures C++ diagnostics get flushed before `diags` is destroyed, and - // that diagnostics created here don't interleave with later Carbon - // diagnostics. - Diagnostics::AnnotationScope annotate_diagnostics(&context.emitter(), - [](auto& /*builder*/) {}); - - clang_instance.setDiagnostics(diags); - clang_instance.setVirtualFileSystem(fs); - clang_instance.createFileManager(); - clang_instance.createSourceManager(); - if (!clang_instance.createTarget()) { - return false; + clang_instance->setDiagnostics(diags); + clang_instance->setVirtualFileSystem(fs); + clang_instance->createFileManager(); + clang_instance->createSourceManager(); + if (!clang_instance->createTarget()) { + return nullptr; } - GenerateASTAction action(context, MakeContextDiagnosticListener( - *diagnostic_consumer_ptr, context)); - if (!action.BeginSourceFile(clang_instance, inputs[0])) { - return false; + GenerateASTAction action(filename, llvm_context); + if (!action.BeginSourceFile(*clang_instance, inputs[0])) { + return nullptr; } - // The AST context is now available, so the mangle context (used to compute - // stable identities for imported C++ types) can be created. - context.sem_ir().cpp_file()->CreateMangleContext(); - - auto& ast = clang_instance.getASTContext(); + auto& ast = clang_instance->getASTContext(); // Always build a multiplex source, even if there's only one child // source. During lowering, the `CarbonExternalASTSource` can no longer be @@ -777,18 +794,73 @@ auto GenerateAst(Context& context, ast.getExternalSource())) { multiplex_source->AddSource(existing_source); } - multiplex_source->AddSource( - llvm::makeIntrusiveRefCnt(&context)); ast.setExternalSource(std::move(multiplex_source_ref_cnt_ptr)); if (llvm::Error error = action.Execute()) { // `Execute` currently never fails, but its contract allows it to. - context.TODO(SemIR::LocId::None, "failed to execute clang action: " + - llvm::toString(std::move(error))); - return false; + CARBON_FATAL("Failed to execute clang action: {0}", + llvm::toString(std::move(error))); } - return true; + auto parser = action.TakeParser(); + CARBON_CHECK(parser); + + return std::make_shared( + CppDomain{.clang_instance = std::move(clang_instance), + .parser = std::move(parser), + .code_generator = action.code_generator(), + .llvm_context = llvm_context}); +} + +auto GenerateAst(Context& context, + llvm::ArrayRef imports, + CppDomain& domain) -> bool { + CARBON_CHECK(!context.cpp_context()); + CARBON_CHECK(!context.sem_ir().cpp_file()); + + // Register an annotation scope to flush any Clang diagnostics when we + // return. This ensures C++ diagnostics get flushed before `diags` is + // destroyed, and that diagnostics created here don't interleave with later + // Carbon diagnostics. + Diagnostics::AnnotationScope annotate_diagnostics(&context.emitter(), + [](auto& /*builder*/) {}); + + auto clang_instance = domain.clang_instance; + auto parser = domain.parser; + + // Set up CppFile for the current SemIR::File. + auto cpp_file = + std::make_unique(clang_instance, domain.llvm_context); + if (domain.code_generator) { + cpp_file->SetCodeGenerator(domain.code_generator); + } + context.sem_ir().set_cpp_file(std::move(cpp_file)); + + // Set up CppContext for the current Context. + context.set_cpp_context(std::make_unique( + *clang_instance, parser, + MakeContextDiagnosticListener( + *clang_instance->getDiagnostics().getClient(), context))); + + // The AST context is now available, so the mangle context (used to compute + // stable identities for imported C++ types) can be created. + context.sem_ir().cpp_file()->CreateMangleContext(); + + // Add an external source referring to this context. + auto* multiplex_source = cast( + context.ast_context().getExternalSource()); + auto ast_source = + llvm::makeIntrusiveRefCnt(&context); + multiplex_source->AddSource(ast_source); + + // Map the package scope to the Carbon namespace. + ast_source->BuildCarbonNamespace(); + + // Inject the imports-as-#includes buffer. + std::string includes = GenerateCppIncludesHeaderCode(context, imports); + auto buffer = + llvm::MemoryBuffer::getMemBufferCopy(includes, ""); + return InjectAndParse(context, std::move(buffer)); } auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, @@ -796,10 +868,6 @@ auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, auto* cpp_context = context.cpp_context(); CARBON_CHECK(cpp_context); - clang::Sema& sema = cpp_context->sema(); - clang::Preprocessor& preprocessor = sema.getPreprocessor(); - clang::Parser& parser = cpp_context->parser(); - RawStringOstream code_stream; AppendInlineCode(context, code_stream, context.parse_tree().node_token(loc_id.node_id()), @@ -807,22 +875,9 @@ auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, auto buffer = llvm::MemoryBuffer::getMemBufferCopy(code_stream.TakeStr(), ""); - clang::FileID file_id = - preprocessor.getSourceManager().createFileID(std::move(buffer)); - - if (preprocessor.EnterSourceFile(file_id, nullptr, clang::SourceLocation())) { - // Clang will have generated a suitable error. There's nothing more to do - // here. - return; - } - - // The parser will typically have an EOF as its cached current token; consume - // that so we can reach the newly-injected tokens. - if (parser.getCurToken().is(clang::tok::eof)) { - parser.ConsumeToken(); - } - - ParseTopLevelDecls(parser, sema.getASTConsumer()); + // Clang will have generated a suitable error if this fails. There's nothing + // more to do here. + InjectAndParse(context, std::move(buffer)); } auto FinishAst(Context& context) -> void { @@ -830,7 +885,13 @@ auto FinishAst(Context& context) -> void { return; } - context.cpp_context()->sema().ActOnEndOfTranslationUnit(); + // Finalize the per-Context AST fragment. The final ActOnEndOfTranslationUnit + // call for the CppDomain is performed in FinalizeCppDomain once all files + // sharing the domain have been checked. + context.cpp_context()->sema().ActOnEndOfTranslationUnitFragment( + clang::TUFragmentKind::Normal); + FlushDiagnosticConsumer( + *context.cpp_context()->sema().getDiagnostics().getClient()); context.emitter().Flush(); // Remove the `CarbonExternalASTSource` installed in `GenerateAst` and @@ -841,8 +902,7 @@ auto FinishAst(Context& context) -> void { auto* multiplex_source = cast( context.ast_context().getExternalSource()); multiplex_source->EraseIf([](const auto& src) { - // `CarbonExternalASTSource` inherits from `ReadOnlyASTSource`. - return llvm::isa(src.get()); + return llvm::isa(src.get()); }); multiplex_source->AddSource( llvm::makeIntrusiveRefCnt(context.sem_ir())); @@ -851,4 +911,12 @@ auto FinishAst(Context& context) -> void { context.set_cpp_context(nullptr); } +auto FinalizeCppDomain(CppDomain& domain) -> void { + if (domain.clang_instance) { + domain.clang_instance->getSema().ActOnEndOfTranslationUnit(); + FlushDiagnosticConsumer( + *domain.clang_instance->getDiagnostics().getClient()); + } +} + } // namespace Carbon::Check diff --git a/toolchain/check/cpp/generate_ast.h b/toolchain/check/cpp/generate_ast.h index b4ffeef0d49a..d861d850418f 100644 --- a/toolchain/check/cpp/generate_ast.h +++ b/toolchain/check/cpp/generate_ast.h @@ -17,19 +17,43 @@ namespace clang { class CompilerInvocation; +class CompilerInstance; +class Parser; +class ExternalSemaSource; +class CodeGenerator; } // namespace clang +namespace Carbon::Diagnostics { +class Consumer; +} // namespace Carbon::Diagnostics + namespace Carbon::Check { +// A C++ compilation domain, including a live Clang instance that can be used to +// parse more code into that domain. May be shared across multiple Carbon files. +struct CppDomain { + std::shared_ptr clang_instance; + std::shared_ptr parser; + clang::CodeGenerator* code_generator = nullptr; + llvm::LLVMContext* llvm_context = nullptr; +}; + +// Initializes a Clang compilation instance, which can be used to parse C++ code +// within one or more Carbon files. Returns the initialized state, or null on +// failure. +auto InitializeCppDomain( + Diagnostics::Consumer& consumer, llvm::StringRef filename, + llvm::IntrusiveRefCntPtr fs, + llvm::LLVMContext* llvm_context, + std::shared_ptr base_invocation) + -> std::shared_ptr; + // Generates a Clang AST for the given C++ imports and sets it as the context's // `cpp_context` and the SemIR's `cpp_file`. Returns a bool that represents // whether compilation was successful. auto GenerateAst(Context& context, llvm::ArrayRef imports, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr base_invocation) - -> bool; + CppDomain& domain) -> bool; // Injects C++ code from `inline Cpp` into the active Clang AST context. // Returns a bool representing whether parsing was successful. @@ -40,6 +64,9 @@ auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, // steps such as template instantiation and warning on unused declarations. auto FinishAst(Context& context) -> void; +// Finalizes a C++ domain at the end of checking all files. +auto FinalizeCppDomain(CppDomain& domain) -> void; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_ diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index f417ff7f3f7e..b24d2761c338 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -118,9 +118,7 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id, auto ImportCpp(Context& context, llvm::ArrayRef imports, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr invocation) -> void { + CppDomain* domain) -> void { if (imports.empty()) { // TODO: Consider always having a (non-null) AST even if there are no Cpp // imports. @@ -137,7 +135,7 @@ auto ImportCpp(Context& context, SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id); name_scope.set_is_closed_import(true); - if (GenerateAst(context, imports, fs, llvm_context, std::move(invocation))) { + if (domain && GenerateAst(context, imports, *domain)) { name_scope.set_clang_decl_context_id( context.clang_decls().Add( {.key = SemIR::ClangDeclKey( diff --git a/toolchain/check/cpp/import.h b/toolchain/check/cpp/import.h index c9d7d2e6b857..b209307fd8ff 100644 --- a/toolchain/check/cpp/import.h +++ b/toolchain/check/cpp/import.h @@ -25,6 +25,8 @@ class VarDecl; namespace Carbon::Check { +struct CppDomain; + // Returns whether the given function is an object member function. This is true // if it's a non-static member function and not a constructor. Object member // functions correspond to Carbon functions with a `self` parameter. @@ -33,12 +35,11 @@ auto IsObjectMemberFunction(const clang::FunctionDecl& decl) -> bool; // Generates a C++ header that includes the imported cpp files, parses it, // generates the AST from it and links `SemIR::File` to it. Reports C++ errors -// and warnings. If successful, adds a `Cpp` namespace. +// and warnings. If successful, adds a `Cpp` namespace. `domain` should be +// non-null unless there was an error initializing Clang. auto ImportCpp(Context& context, llvm::ArrayRef imports, - llvm::IntrusiveRefCntPtr fs, - llvm::LLVMContext* llvm_context, - std::shared_ptr invocation) -> void; + CppDomain* domain) -> void; // Given a clang declaration ID that was previously imported into another file, // returns the corresponding clang declaration key in the current context. diff --git a/toolchain/check/testdata/interop/cpp/basics/inline/modules.carbon b/toolchain/check/testdata/interop/cpp/basics/inline/modules.carbon index d0ce04f26983..d5d2b2886784 100644 --- a/toolchain/check/testdata/interop/cpp/basics/inline/modules.carbon +++ b/toolchain/check/testdata/interop/cpp/basics/inline/modules.carbon @@ -21,7 +21,7 @@ import Cpp inline ''' // CHECK:STDERR: 13 | export module Foo; // CHECK:STDERR: | ^ // CHECK:STDERR: :1:1: note: add 'module;' to the start of the file to introduce a global module fragment [CppInteropParseNote] -// CHECK:STDERR: 1 | # 6 "fail_export_module_in_inline_cpp.carbon" +// CHECK:STDERR: 1 | ; // CHECK:STDERR: | ^ // CHECK:STDERR: export module Foo; @@ -37,7 +37,7 @@ import Cpp inline ''' // CHECK:STDERR: 17 | module Foo; // CHECK:STDERR: | ^ // CHECK:STDERR: :1:1: note: add 'module;' to the start of the file to introduce a global module fragment [CppInteropParseNote] -// CHECK:STDERR: 1 | # 6 "fail_module_in_inline_cpp.carbon" +// CHECK:STDERR: 1 | ; // CHECK:STDERR: | ^ // CHECK:STDERR: // CHECK:STDERR: fail_module_in_inline_cpp.carbon:[[@LINE+4]]:8: error: module 'Foo' not found [CppInteropParseError] @@ -65,7 +65,7 @@ int n; // CHECK:STDERR: 25 | module Foo; // CHECK:STDERR: | ^ // CHECK:STDERR: :1:1: note: add 'module;' to the start of the file to introduce a global module fragment [CppInteropParseNote] -// CHECK:STDERR: 1 | # 6 "fail_global_module_in_inline_cpp.carbon" +// CHECK:STDERR: 1 | ; // CHECK:STDERR: | ^ // CHECK:STDERR: // CHECK:STDERR: fail_global_module_in_inline_cpp.carbon:[[@LINE+4]]:8: error: module 'Foo' not found [CppInteropParseError] diff --git a/toolchain/check/testdata/interop/cpp/basics/share_ast.carbon b/toolchain/check/testdata/interop/cpp/basics/share_ast.carbon new file mode 100644 index 000000000000..32c473922980 --- /dev/null +++ b/toolchain/check/testdata/interop/cpp/basics/share_ast.carbon @@ -0,0 +1,64 @@ +// 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 +// +// ARGS: compile --phase=check --share-cpp-ast %s +// +// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/int.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/interop/cpp/basics/share_ast.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/basics/share_ast.carbon + +// --- a.h +#ifndef A_H +#define A_H +inline int GetA() { return 42; } +#endif + +// --- b.h +#ifndef B_H +#define B_H +#include "a.h" +inline int GetB() { return 100; } +#endif + +// --- a.carbon +library "[[@TEST_NAME]]"; +import Cpp library "a.h"; + +fn CallA() -> i32 { + return Cpp.GetA(); +} + +// --- todo_a_leaks.carbon +library "[[@TEST_NAME]]"; +import Cpp; + +fn CallLeakedA() -> i32 { + // TODO: This should not be found as we have not imported "a.h". + return Cpp.GetA(); +} + +// --- a_again.carbon +library "[[@TEST_NAME]]"; +import Cpp library "a.h"; + +fn CallAAgain() -> i32 { + // This should be accepted as we have imported "a.h", even though we will just reuse the version from `a.carbon`. + return Cpp.GetA(); +} + +// --- b.carbon +library "[[@TEST_NAME]]"; +import Cpp library "b.h"; + +fn CallAIndirect() -> i32 { + return Cpp.GetA(); +} + +fn CallB() -> i32 { + return Cpp.GetB(); +} diff --git a/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon index fc2cac2a5026..d5ae1866e152 100644 --- a/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon +++ b/toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon @@ -12,6 +12,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/export/thunk_ast.carbon // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> +// CHECK:STDOUT: |-EmptyDecl {{0x[a-f0-9]+}} <:1:1> col:1 // CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> referenced Carbon external-linkage // --- thunk_with_args_and_return.carbon diff --git a/toolchain/check/testdata/interop/cpp/function/import/function.carbon b/toolchain/check/testdata/interop/cpp/function/import/function.carbon index 270d2969ee18..843d7cb12eca 100644 --- a/toolchain/check/testdata/interop/cpp/function/import/function.carbon +++ b/toolchain/check/testdata/interop/cpp/function/import/function.carbon @@ -129,23 +129,22 @@ fn F() { // --- static.h +// TODO: Promote this warning to an error by default. +// CHECK:STDERR: ./static.h:[[@LINE+3]]:13: warning: function 'foo' has internal linkage but is not defined [CppInteropParseWarning] +// CHECK:STDERR: 6 | static auto foo() -> void; +// CHECK:STDERR: | ^ static auto foo() -> void; // --- todo_fail_import_static.carbon library "[[@TEST_NAME]]"; -// TODO: Promote this warning to an error by default. -// CHECK:STDERR: todo_fail_import_static.carbon:[[@LINE+4]]:10: in file included here [InCppInclude] -// CHECK:STDERR: ./static.h:2:13: warning: function 'foo' has internal linkage but is not defined [CppInteropParseWarning] -// CHECK:STDERR: 2 | static auto foo() -> void; -// CHECK:STDERR: | ^ import Cpp library "static.h"; fn F() { //@dump-sem-ir-begin // CHECK:STDERR: todo_fail_import_static.carbon:[[@LINE+4]]:11: note: used here [CppInteropParseNote] - // CHECK:STDERR: 17 | Cpp.foo(); + // CHECK:STDERR: 12 | Cpp.foo(); // CHECK:STDERR: | ^ // CHECK:STDERR: Cpp.foo(); diff --git a/toolchain/check/testdata/interop/cpp/function/import/inline.carbon b/toolchain/check/testdata/interop/cpp/function/import/inline.carbon index 58ec72be9574..9f0280ca443d 100644 --- a/toolchain/check/testdata/interop/cpp/function/import/inline.carbon +++ b/toolchain/check/testdata/interop/cpp/function/import/inline.carbon @@ -36,23 +36,22 @@ fn MyF() { // --- without_definition.h +// TODO: Promote this warning to an error by default. +// CHECK:STDERR: ./without_definition.h:[[@LINE+3]]:13: warning: inline function 'foo' is not defined [CppInteropParseWarning] +// CHECK:STDERR: 6 | inline void foo(); +// CHECK:STDERR: | ^ inline void foo(); // --- todo_fail_import_without_definition.carbon library "[[@TEST_NAME]]"; -// TODO: Promote this warning to an error by default. -// CHECK:STDERR: todo_fail_import_without_definition.carbon:[[@LINE+4]]:10: in file included here [InCppInclude] -// CHECK:STDERR: ./without_definition.h:2:13: warning: inline function 'foo' is not defined [CppInteropParseWarning] -// CHECK:STDERR: 2 | inline void foo(); -// CHECK:STDERR: | ^ import Cpp library "without_definition.h"; fn MyF() { //@dump-sem-ir-begin // CHECK:STDERR: todo_fail_import_without_definition.carbon:[[@LINE+4]]:11: note: used here [CppInteropParseNote] - // CHECK:STDERR: 17 | Cpp.foo(); + // CHECK:STDERR: 12 | Cpp.foo(); // CHECK:STDERR: | ^ // CHECK:STDERR: Cpp.foo(); @@ -134,12 +133,12 @@ fn MyF() { // CHECK:STDOUT: // CHECK:STDOUT: fn @MyF() { // CHECK:STDOUT: !entry: -// CHECK:STDOUT: %Cpp.ref.loc17: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] -// CHECK:STDOUT: %foo.ref.loc17: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value] -// CHECK:STDOUT: %foo.call.loc17: init %empty_tuple.type = call imports.%foo.decl() -// CHECK:STDOUT: %Cpp.ref.loc20: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] -// CHECK:STDOUT: %foo.ref.loc20: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value] -// CHECK:STDOUT: %foo.call.loc20: init %empty_tuple.type = call imports.%foo.decl() +// CHECK:STDOUT: %Cpp.ref.loc12: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] +// CHECK:STDOUT: %foo.ref.loc12: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value] +// CHECK:STDOUT: %foo.call.loc12: init %empty_tuple.type = call imports.%foo.decl() +// CHECK:STDOUT: %Cpp.ref.loc15: = name_ref Cpp, imports.%Cpp [concrete = imports.%Cpp] +// CHECK:STDOUT: %foo.ref.loc15: %foo.cpp_overload_set.type = name_ref foo, imports.%foo.cpp_overload_set.value [concrete = constants.%foo.cpp_overload_set.value] +// CHECK:STDOUT: %foo.call.loc15: init %empty_tuple.type = call imports.%foo.decl() // CHECK:STDOUT: // CHECK:STDOUT: } // CHECK:STDOUT: diff --git a/toolchain/check/testdata/interop/cpp/function/import/thunk_ast.carbon b/toolchain/check/testdata/interop/cpp/function/import/thunk_ast.carbon index 32b9f7d19d3e..1d4a05d52438 100644 --- a/toolchain/check/testdata/interop/cpp/function/import/thunk_ast.carbon +++ b/toolchain/check/testdata/interop/cpp/function/import/thunk_ast.carbon @@ -12,6 +12,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/interop/cpp/function/import/thunk_ast.carbon // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> +// CHECK:STDOUT: |-EmptyDecl {{0x[a-f0-9]+}} <:1:1> col:1 // CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> Carbon external-linkage // --- thunk_required.h @@ -33,6 +34,7 @@ auto foo(short a) -> void; // CHECK:STDOUT: |-InternalLinkageAttr {{0x[a-f0-9]+}} <> Implicit // CHECK:STDOUT: `-AsmLabelAttr {{0x[a-f0-9]+}} Implicit "_Z3foos.carbon_thunk._" // CHECK:STDOUT: TranslationUnitDecl {{0x[a-f0-9]+}} <> +// CHECK:STDOUT: |-EmptyDecl {{0x[a-f0-9]+}} <:1:1> col:1 // CHECK:STDOUT: |-NamespaceDecl {{0x[a-f0-9]+}} <> Carbon external-linkage // --- import_thunk_required.carbon diff --git a/toolchain/driver/compile_driver.cpp b/toolchain/driver/compile_driver.cpp index 26afb1be7494..a285bf4e54da 100644 --- a/toolchain/driver/compile_driver.cpp +++ b/toolchain/driver/compile_driver.cpp @@ -608,6 +608,7 @@ auto CompileDriver::Compile(DriverEnv& driver_env) -> DriverResult { 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(); diff --git a/toolchain/driver/compile_options.cpp b/toolchain/driver/compile_options.cpp index 9783164a326a..a4f288eaa485 100644 --- a/toolchain/driver/compile_options.cpp +++ b/toolchain/driver/compile_options.cpp @@ -403,6 +403,17 @@ the `prelude_import` flag is set to false, this is also silently set to false. arg_b.Default(true); arg_b.Set(&include_carbon_core); }); + b.AddFlag( + { + .name = "share-cpp-ast", + .help = R"""( +Share a single Clang ASTContext across all compiled files. + +TODO: This is a temporary measure and will be enabled by default and removed +once this mode is fully implemented. +)""", + }, + [&](auto& arg_b) { arg_b.Set(&share_cpp_ast); }); } auto CompileOptions::BuildForBuildSubcommand(CommandLine::CommandBuilder& b, diff --git a/toolchain/driver/compile_options.h b/toolchain/driver/compile_options.h index bf78e6333d8c..fb976e118101 100644 --- a/toolchain/driver/compile_options.h +++ b/toolchain/driver/compile_options.h @@ -122,6 +122,7 @@ struct CompileOptions { llvm::StringRef sem_ir_crash_dump; bool mangle_string_fingerprint = false; + bool share_cpp_ast = false; // Get the LLVM optimization level corresponding to a Carbon optimization // level. diff --git a/toolchain/lower/testdata/interop/cpp/debug_info.carbon b/toolchain/lower/testdata/interop/cpp/debug_info.carbon index 00879edec1f4..c26d42f83ae8 100644 --- a/toolchain/lower/testdata/interop/cpp/debug_info.carbon +++ b/toolchain/lower/testdata/interop/cpp/debug_info.carbon @@ -49,7 +49,7 @@ fn Call() -> i32 { // CHECK:STDOUT: !llvm.errno.tbaa = !{!9} // CHECK:STDOUT: // CHECK:STDOUT: !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) -// CHECK:STDOUT: !1 = !DIFile(filename: "", directory: "", checksumkind: CSK_MD5, checksum: "d41d8cd98f00b204e9800998ecf8427e") +// CHECK:STDOUT: !1 = !DIFile(filename: "", directory: "", checksumkind: CSK_MD5, checksum: "9eecb7db59d16c80417c72d1e1f4fbf1") // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) // CHECK:STDOUT: !3 = !DIFile(filename: "debug_info.carbon", directory: "") // CHECK:STDOUT: !4 = !{i32 7, !"Dwarf Version", i32 5} diff --git a/toolchain/sem_ir/cpp_file.cpp b/toolchain/sem_ir/cpp_file.cpp index 9477ea901c13..f7e97a3d50c6 100644 --- a/toolchain/sem_ir/cpp_file.cpp +++ b/toolchain/sem_ir/cpp_file.cpp @@ -11,7 +11,7 @@ namespace Carbon::SemIR { -CppFile::CppFile(std::unique_ptr clang, +CppFile::CppFile(std::shared_ptr clang, llvm::LLVMContext* llvm_context) : clang_(std::move(clang)), llvm_context_(llvm_context) {} diff --git a/toolchain/sem_ir/cpp_file.h b/toolchain/sem_ir/cpp_file.h index 3968ffdc83e8..ecab3f701a2d 100644 --- a/toolchain/sem_ir/cpp_file.h +++ b/toolchain/sem_ir/cpp_file.h @@ -28,7 +28,7 @@ namespace Carbon::SemIR { // imported C++ headers and any inline C++ fragments. class CppFile { public: - explicit CppFile(std::unique_ptr clang, + explicit CppFile(std::shared_ptr clang, llvm::LLVMContext* llvm_context); ~CppFile(); @@ -64,7 +64,7 @@ class CppFile { } private: - std::unique_ptr clang_; + std::shared_ptr clang_; llvm::LLVMContext* llvm_context_; clang::CodeGenerator* code_generator_ = nullptr; // Created by `CreateMangleContext()` once the AST context is available.