/
githubmirror
/
carbon-lang
Обзор
Документация
Войти
/
githubmirror
/
carbon-lang
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
toolchain/check/cpp/generate_ast.cpp
1 132 строки
45 KB
Nicholas Bishop
Add initial support for exporting generic classes (#7595)
10 авг 2026, 20:47
Не верифицирован
10 авг 2026, 20:47
de1cd70
Код
Авторство
О чём код?
// 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/check/cpp/generate_ast.h" #include <memory> #include <string> #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/Mangle.h" #include "clang/Basic/DiagnosticParse.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/Module.h" #include "clang/CodeGen/ModuleBuilder.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Frontend/MultiplexConsumer.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Parse/Parser.h" #include "clang/Sema/ExternalSemaSource.h" #include "clang/Sema/MultiplexExternalSemaSource.h" #include "clang/Sema/Sema.h" #include "common/check.h" #include "common/map.h" #include "common/raw_string_ostream.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" #include "toolchain/base/kind_switch.h" #include "toolchain/check/context.h" #include "toolchain/check/cpp/access.h" #include "toolchain/check/cpp/diagnostic_consumer.h" #include "toolchain/check/cpp/diagnostic_listener.h" #include "toolchain/check/cpp/export.h" #include "toolchain/check/cpp/import.h" #include "toolchain/check/cpp/location.h" #include "toolchain/check/cpp/type_mapping.h" #include "toolchain/check/import_ref.h" #include "toolchain/check/name_lookup.h" #include "toolchain/check/type_completion.h" #include "toolchain/diagnostics/diagnostic.h" #include "toolchain/diagnostics/emitter.h" #include "toolchain/diagnostics/format_providers.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/cpp_domain.h" #include "toolchain/sem_ir/cpp_file.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/read_only_ast_source.h" #include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { // Add a line marker directive pointing at the location of the `import Cpp` // declaration in the Carbon source file. This will cause Clang's diagnostics // machinery to track and report the location in Carbon code where the import // was written. static auto GenerateLineMarker(Context& context, llvm::raw_ostream& out, int line) { out << "# " << line << " \"" << FormatEscaped(context.tokens().source().filename()) << "\"\n"; } // Appends a line marker and the specified `code` to `out`, adjusting the // `line` number if the `code_token` represents a block string literal. static auto AppendInlineCode(Context& context, llvm::raw_ostream& out, Lex::TokenIndex code_token, llvm::StringRef code) -> void { // Compute the line number on which the C++ code starts. Usually the code // is specified as a block string literal and starts on the line after the // start of the string token. // TODO: Determine if this is a block string literal without calling // `GetTokenText`, which re-lexes the string. int line = context.tokens().GetLineNumber(code_token); if (context.tokens().GetTokenText(code_token).contains('\n')) { ++line; } GenerateLineMarker(context, out, line); out << code << "\n"; } namespace { // A wrapper around a clang::CompilerInvocation that allows us to make a shallow // copy of most of the invocation and only make a deep copy of the parts that we // want to change. // // clang::CowCompilerInvocation almost allows this, but doesn't derive from // CompilerInvocation or support shallow copies from a CompilerInvocation, so is // not useful to us as we can't build an ASTUnit from it. class ShallowCopyCompilerInvocation : public clang::CompilerInvocation { public: explicit ShallowCopyCompilerInvocation( const clang::CompilerInvocation& invocation) { shallow_copy_assign(invocation); // Make a deep copy of options that we modify. FrontendOpts = std::make_shared<clang::FrontendOptions>(*FrontendOpts); PPOpts = std::make_shared<clang::PreprocessorOptions>(*PPOpts); } }; // Provides clang AST nodes representing Carbon SemIR entities. class CarbonExternalASTSource : public SemIR::ReadOnlyASTSource { public: explicit CarbonExternalASTSource(Context* context) : ReadOnlyASTSource(context->sem_ir()), context_(context) {} // 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. auto FindExternalVisibleDeclsByName( const clang::DeclContext* decl_context, clang::DeclarationName decl_name, const clang::DeclContext* original_decl_context) -> bool override; auto LoadExternalSpecializations( const clang::Decl* decl, llvm::ArrayRef<clang::TemplateArgument> template_args) -> bool override { if (const auto* function_template_decl = llvm::dyn_cast<clang::FunctionTemplateDecl>(decl)) { return ExportFunctionSpecializationToCpp( *context_, const_cast<clang::FunctionTemplateDecl*>(function_template_decl), template_args); } if (const auto* class_template_decl = llvm::dyn_cast<clang::ClassTemplateDecl>(decl)) { return ExportClassSpecializationToCpp( *context_, const_cast<clang::ClassTemplateDecl*>(class_template_decl), template_args); } return false; } auto CompleteType(clang::TagDecl* tag_decl) -> void override; auto layoutRecordType( const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment, llvm::DenseMap<const clang::FieldDecl*, uint64_t>& field_offsets, llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>& base_offsets, llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>& vbase_offsets) -> bool override; auto isA(const void* class_id) const -> bool override { return class_id == &id || ReadOnlyASTSource::isA(class_id); } static auto classof(const ExternalASTSource* s) -> bool { return s->isA(&id); } private: // Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot // currently be represented in C++. auto MapInstIdToClangDeclOrType(LookupResult lookup) -> std::variant<clang::NamedDecl*, clang::QualType>; auto GetOrExportFunctionToCpp(SemIR::InstId target_inst_id, SemIR::FunctionId function_id) -> clang::NamedDecl*; // Get a current best-effort location for the current position within C++ // processing. auto GetCurrentCppLocId() -> SemIR::LocId { auto* cpp_context = context_->cpp_context(); CARBON_CHECK(cpp_context); // Use the current token location when parsing. auto clang_source_loc = cpp_context->parser().getCurToken().getLocation(); if (auto& code_synthesis_contexts = cpp_context->sema().CodeSynthesisContexts; !code_synthesis_contexts.empty()) { // Use the current point of instantiation during template instantiation. clang_source_loc = code_synthesis_contexts.back().PointOfInstantiation; } return AddImportIRInst(context_->sem_ir(), clang_source_loc); } // For LLVM RTTI. static char id; Check::Context* context_; }; char CarbonExternalASTSource::id; } // namespace auto CarbonExternalASTSource::MapInstIdToClangDeclOrType(LookupResult lookup) -> std::variant<clang::NamedDecl*, clang::QualType> { auto target_inst_id = lookup.scope_result.target_inst_id(); auto target_const_id = context_->constant_values().Get(target_inst_id); auto target_inst = context_->constant_values().GetInst(target_const_id); if (target_inst.type_id() == SemIR::TypeType::TypeId) { auto type_id = context_->types().GetTypeIdForTypeConstantId(target_const_id); auto type = MapToCppType(*context_, type_id); if (type.isNull()) { context_->TODO(GetCurrentCppLocId(), "interop with unsupported type"); return nullptr; } return type; } CARBON_KIND_SWITCH(target_inst) { case CARBON_KIND(SemIR::Namespace namespace_info): { auto* decl_context = ExportNameScopeToCpp(*context_, SemIR::LocId(target_inst_id), namespace_info.name_scope_id); if (!decl_context) { return nullptr; } if (isa<clang::TranslationUnitDecl>(decl_context)) { context_->TODO(GetCurrentCppLocId(), "interop with translation unit decl"); return nullptr; } return cast<clang::NamedDecl>(decl_context); } case SemIR::StructValue::Kind: { auto type_inst_id = context_->types().GetTypeInstId(target_inst.type_id()); auto callee = GetCallee(context_->sem_ir(), target_inst_id); if (auto* callee_function = std::get_if<SemIR::CalleeFunction>(&callee)) { return GetOrExportFunctionToCpp(target_inst_id, callee_function->function_id); } else if (auto generic_class = context_->insts().TryGetAs<SemIR::GenericClassType>( type_inst_id)) { return ExportGenericClassToCpp(*context_, type_inst_id, *generic_class); } return nullptr; } case CARBON_KIND(SemIR::FieldDecl field_decl): { return ExportFieldToCpp(*context_, target_inst_id, field_decl, lookup.specific_id); } case CARBON_KIND(SemIR::VarStorage var_storage): { return ExportVarToCpp(*context_, target_inst_id, var_storage); } default: return nullptr; } } auto CarbonExternalASTSource::GetOrExportFunctionToCpp( SemIR::InstId target_inst_id, SemIR::FunctionId function_id) -> clang::NamedDecl* { SemIR::Function& function = context_->functions().Get(function_id); if (const auto* clang_decl = context_->clang_decls().Lookup(function.first_decl_id())) { return cast<clang::NamedDecl>(clang_decl->decl()); } auto* named_decl = ExportFunctionToCpp(*context_, SemIR::LocId(target_inst_id), function_id); if (!named_decl) { return nullptr; } if (auto* function_template_decl = llvm::dyn_cast<clang::FunctionTemplateDecl>(named_decl)) { context_->clang_decls().Add( {.key = SemIR::ClangDeclKey::ForNonFunctionDecl(function_template_decl), .inst_id = function.first_decl_id()}); return function_template_decl; } auto* clang_function_decl = llvm::cast<clang::FunctionDecl>(named_decl); SemIR::ClangDeclSignature thunk_signature; thunk_signature.kind = SemIR::ClangDeclSignature::Normal; thunk_signature.num_params = static_cast<int32_t>(clang_function_decl->getNumParams()); thunk_signature.passing_modes.assign( thunk_signature.num_params, SemIR::ClangDeclSignature::PassingMode::ByValue); context_->clang_decls().Add( {.key = SemIR::ClangDeclKey::ForFunctionDecl( clang_function_decl, context_->clang_decl_signatures().Add(std::move(thunk_signature))), .inst_id = function.first_decl_id()}); return clang_function_decl; } auto CarbonExternalASTSource::BuildCarbonNamespace() -> void { static const llvm::StringLiteral carbon_namespace_name = "Carbon"; auto& ast_context = context_->ast_context(); auto* identifier = &ast_context.Idents.get(carbon_namespace_name); auto* decl_context = ast_context.getTranslationUnitDecl(); // 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<clang::NamespaceDecl>(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++. // TODO: For mangling purposes, include the package as a sub-namespace. auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace); auto clang_decl_id = context_->clang_decls().Add( {.key = key, .inst_id = SemIR::Namespace::PackageInstId}); context_->name_scopes() .Get(SemIR::NameScopeId::Package) .set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false); } auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( const clang::DeclContext* decl_context, clang::DeclarationName decl_name, const clang::DeclContext* /*OriginalDC*/) -> bool { // Find the Carbon declaration corresponding to this Clang declaration. auto* decl = cast<clang::Decl>( const_cast<clang::DeclContext*>(decl_context->getPrimaryContext())); if (isa<clang::FunctionDecl>(decl)) { // Functions don't meaningfully have visible decls, but bail out early since // we can't form a `ClangDeclKey` for a function in the abstract. return false; } auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(decl); auto decl_id = context_->clang_decls().LookupId(key); if (!decl_id.has_value()) { return false; } auto clang_decl = context_->clang_decls().Get(decl_id); if (clang_decl.is_imported) { // This is imported from C++, presumably from a Clang AST file, so it's not // our responsibility to provide its name lookup results. return false; } llvm::SmallVector<Check::LookupScope> lookup_scopes; // LocId::None seems fine here because we shouldn't produce any diagnostics // here - completeness should've been checked by clang before this point. if (!AppendLookupScopesForConstant( *context_, SemIR::LocId::None, context_->constant_values().Get(clang_decl.inst_id), SemIR::ConstantId::None, /*extended_scope=*/false, &lookup_scopes)) { return false; } clang::IdentifierInfo* identifier = nullptr; switch (decl_name.getNameKind()) { case clang::DeclarationName::Identifier: { identifier = decl_name.getAsIdentifierInfo(); break; } case clang::DeclarationName::CXXConstructorName: { // The Carbon counterpart of a constructor is a function whose name // matches the class name. identifier = llvm::cast<clang::CXXRecordDecl>(decl_context)->getIdentifier(); break; } default: return false; } auto name_id = AddIdentifierName(*context_, identifier->getName()); // `required=false` so Carbon doesn't diagnose a failure, let Clang diagnose // it or even SFINAE. LookupResult result = LookupQualifiedName(*context_, SemIR::LocId::None, name_id, lookup_scopes, /*required=*/false); if (!result.scope_result.is_found()) { return false; } // Map the found Carbon entity to a Clang NamedDecl. CARBON_KIND_SWITCH(MapInstIdToClangDeclOrType(result)) { case CARBON_KIND(clang::NamedDecl* clang_decl): { if (clang_decl) { SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl}); return true; } else { SetNoExternalVisibleDeclsForName(decl_context, decl_name); return false; } } case CARBON_KIND(clang::QualType type): { // Create a typedef declaration to model the type result. // TODO: If the type is a tag type that was declared with this name in // this context, use the tag decl directly. auto& ast_context = context_->ast_context(); auto loc = GetCppLocation( *context_, SemIR::LocId(result.scope_result.target_inst_id())); auto* typedef_decl = clang::TypedefDecl::Create( ast_context, const_cast<clang::DeclContext*>(decl_context), loc, loc, identifier, ast_context.getTrivialTypeSourceInfo(type, loc)); if (isa<clang::CXXRecordDecl>(decl_context)) { typedef_decl->setAccess( MapToCppAccess(result.scope_result.access_kind())); } SetExternalVisibleDeclsForName(decl_context, decl_name, {typedef_decl}); return true; } } } auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { auto* class_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl); if (!class_decl) { // TODO: If we start producing clang EnumTypes, we may have to handle them // here too. return; } auto carbon_class_info = SemIR::GetAsCarbonOwnedClass(context_->sem_ir(), tag_decl); if (!carbon_class_info) { return; } auto& [class_type_id, class_type] = *carbon_class_info; auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {}; if (!RequireCompleteType(*context_, class_type_id, GetCurrentCppLocId(), context_fn)) { return; } auto& class_info = context_->classes().Get(class_type.class_id); class_decl->startDefinition(); CARBON_CHECK(class_decl->hasDefinition()); // If the Carbon class is final, mark the C++ class as also being `final`. // Abstract classes are handled when generating the destructor declaration. if (class_info.inheritance_kind == SemIR::Class::InheritanceKind::Final) { // TODO: Find the location of the `final` modifier and use it here. class_decl->addAttr(clang::FinalAttr::Create( context_->ast_context(), GetCppLocation(*context_, SemIR::LocId(class_info.definition_id)))); } // If the Carbon class has a base class that we can map into C++, add that as // a C++ base class. auto base_type_id = class_info.GetBaseType(context_->sem_ir(), class_type.specific_id); if (base_type_id.has_value()) { auto base_loc = GetCppLocation(*context_, SemIR::LocId(class_info.base_id)); if (auto base_type = MapToCppType(*context_, base_type_id); !base_type.isNull() && base_type->isStructureOrClassType() && !context_->clang_sema().RequireCompleteType( base_loc, base_type, clang::diag::err_incomplete_base_class)) { bool is_virtual = false; bool is_base_of_class = true; clang::CXXBaseSpecifier base( base_loc, is_virtual, is_base_of_class, clang::AS_public, context_->ast_context().getTrivialTypeSourceInfo(base_type, base_loc), /*EllipsisLoc=*/clang::SourceLocation()); clang::CXXBaseSpecifier* bases[1] = {&base}; CARBON_CHECK(class_decl->hasDefinition()); class_decl->setBases(bases, 1); } } ExportAllFieldsToCpp(*context_, context_->types().GetTypeInstId(class_type_id)); // TODO: support exporting destructors for generic classes. if (!llvm::isa<clang::ClassTemplateSpecializationDecl>(class_decl)) { class_decl->addDecl( ExportDestructorToCpp(*context_, class_info, class_decl)); } // TODO: Import any special member functions that affect class properties. // Virtual functions whose definitions we have deferred generating until the // class is complete. struct PendingVirtualFunction { SemIR::LocId loc_id; SemIR::FunctionId function_id; clang::CXXMethodDecl* method_decl; }; llvm::SmallVector<PendingVirtualFunction> pending_virtual_functions; if (class_info.vtable_decl_id.has_value()) { auto vtable_inst_block = context_->inst_blocks().Get( context_->vtables() .Get(context_->insts() .GetAs<SemIR::VtableDecl>(class_info.vtable_decl_id) .vtable_id) .virtual_functions_id); for (auto vtable_entry_id : vtable_inst_block) { if (!vtable_entry_id.has_value()) { continue; } const auto callee_function = GetCalleeAsFunction(context_->sem_ir(), vtable_entry_id); const SemIR::Function& function = context_->functions().Get(callee_function.function_id); // If this is a member of a base class, nothing to do here. if (function.parent_scope_id != class_info.scope_id) { continue; } auto* method_decl = cast_or_null<clang::CXXMethodDecl>(ExportVirtualFunctionDeclToCpp( *context_, SemIR::LocId(vtable_entry_id), class_decl, callee_function.function_id)); if (!method_decl) { continue; } context_->clang_sema().AddOverriddenMethods(class_decl, method_decl); context_->clang_decls().Add( {.key = SemIR::ClangDeclKey::ForFunctionDecl( method_decl, MakeVirtualFunctionSignature(*context_, method_decl)), .inst_id = function.first_decl_id()}); // An abstract function has no definition, so it doesn't need a thunk. if (function.virtual_modifier == SemIR::Function::VirtualModifier::Abstract) { continue; } pending_virtual_functions.push_back( {.loc_id = SemIR::LocId(vtable_entry_id), .function_id = callee_function.function_id, .method_decl = method_decl}); } } class_decl->completeDefinition(); // Now the class is complete, we can define the virtual function thunks. for (auto virtual_fn : pending_virtual_functions) { DefineExportedVirtualFunction(*context_, virtual_fn.loc_id, virtual_fn.function_id, virtual_fn.method_decl); } } auto CarbonExternalASTSource::layoutRecordType( const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment, llvm::DenseMap<const clang::FieldDecl*, uint64_t>& field_offsets, llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>& base_offsets, llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>& vbase_offsets) -> bool { auto carbon_class_info = SemIR::GetAsCarbonOwnedClass(context_->sem_ir(), record_decl); if (!carbon_class_info) { return false; } auto& [class_type_id, class_type] = *carbon_class_info; // Clang should not have asked for the layout of an incomplete type, but check // now to be sure, and to generate a specific definition if needed. // TODO: Add a test for layout of a specific class once they're supported in // general. CompleteTypeOrCheckFail(*context_, class_type_id); ExportAllFieldsToCpp(*context_, context_->types().GetTypeInstId(class_type_id)); return ReadOnlyASTSource::layoutRecordType( record_decl, size, alignment, field_offsets, base_offsets, vbase_offsets); } // Parses a sequence of top-level declarations and forms a corresponding // representation in the Clang AST. Unlike clang::ParseAST, does not finish the // translation unit when EOF is reached. static auto ParseTopLevelDecls(clang::Parser& parser, clang::ASTConsumer& consumer) -> void { // Don't allow C++20 module declarations in inline Cpp code fragments. auto module_import_state = clang::Sema::ModuleImportState::NotACXX20Module; // Parse top-level declarations until we see EOF. Do not parse EOF, as that // will cause the parser to end the translation unit prematurely. while (parser.getCurToken().isNot(clang::tok::eof)) { clang::Parser::DeclGroupPtrTy decl_group; bool eof = parser.ParseTopLevelDecl(decl_group, module_import_state); CARBON_CHECK(!eof, "Should not parse decls at EOF"); if (decl_group && !consumer.HandleTopLevelDecl(decl_group.get())) { // If the consumer rejects the declaration, bail out of parsing. // // TODO: In this case, we shouldn't parse any more declarations even in // separate inline C++ fragments. But our current AST consumer only ever // returns true. break; } } } // Generate a Clang module corresponding to the current Carbon file. static auto CreateModuleForCarbonFile(SemIR::CppDomain& domain, const SemIR::File& file) -> clang::Module* { // TODO: Consider creating a parent module to hold all Carbon modules. // Consider naming the module after the package and library rather than using // the filename. auto& module_map = domain.clang_instance() .getPreprocessor() .getHeaderSearchInfo() .getModuleMap(); auto* module = module_map.createModule(file.filename(), /*Parent=*/nullptr, /*IsFramework=*/false, /*IsExplicit=*/true); auto insert_result = domain.file_modules().Insert(file.check_ir_id(), module); CARBON_CHECK(insert_result.is_inserted()); return module; } // Generates a Clang module corresponding to the given C++ header name. Note // that this is separate from Clang's header -> module mapping. Even if a C++ // header is imported into Carbon, C++-side #includes of the same header are // still treated as textual inclusions. // Returns the module and a bool indicating whether it was newly created. static auto GetOrCreateModuleForHeader(SemIR::CppDomain& domain, llvm::StringRef header_name) -> std::pair<clang::Module*, bool> { auto [it, added] = domain.header_modules().insert({header_name, nullptr}); if (!added) { CARBON_CHECK(it->second); return {it->second, false}; } auto& module_map = domain.clang_instance() .getPreprocessor() .getHeaderSearchInfo() .getModuleMap(); it->second = module_map.createModule(header_name, /*Parent=*/nullptr, /*IsFramework=*/false, /*IsExplicit=*/true); return {it->second, true}; } // Parse the tokens that have been injected into the preprocessor in the given // context. static auto ParseInjectedTokens(CppContext& cpp_context) -> void { clang::Sema& sema = cpp_context.sema(); clang::Parser& parser = cpp_context.parser(); CARBON_CHECK(parser.getCurToken().is(clang::tok::eof)); parser.ConsumeToken(); ParseTopLevelDecls(parser, sema.getASTConsumer()); } // Injects the C++ code in `buffer` into the Clang preprocessor. Returns the // file ID of the injected buffer. static auto InjectBuffer(CppContext& cpp_context, llvm::StringRef contents, llvm::StringRef name, clang::SourceLocation import_loc) -> clang::FileID { auto buffer = llvm::MemoryBuffer::getMemBufferCopy(contents, name); clang::Preprocessor& preprocessor = cpp_context.sema().getPreprocessor(); clang::FileID file_id = preprocessor.getSourceManager().createFileID(std::move(buffer)); if (preprocessor.EnterSourceFile(file_id, nullptr, import_loc)) { CARBON_FATAL("Failed to enter buffer"); } return file_id; } // Instruct the Clang preprocessor and Sema to enter the scope of the given // module. static auto EnterModule(CppContext& cpp_context, clang::Module* mod, clang::SourceLocation loc) -> void { auto& preprocessor = cpp_context.sema().getPreprocessor(); preprocessor.EnterSubmodule(mod, loc, /*ForPragma=*/false); preprocessor.EnterAnnotationToken(loc, clang::tok::annot_module_begin, mod); ParseInjectedTokens(cpp_context); } // Leave the current Clang module. static auto LeaveModule(CppContext& cpp_context, clang::SourceLocation loc) -> void { CARBON_CHECK(loc.isValid()); auto& preprocessor = cpp_context.sema().getPreprocessor(); auto* mod = preprocessor.LeaveSubmodule(/*ForPragma=*/false); CARBON_CHECK(mod); // We *should* only need to enter one annotation token, but Clang has some // error recovery where Sema enters and never leaves an additional module if // it sees a `module;` directive in the source. So recover from this by // leaving modules until we find the preprocessor's module. while (true) { auto* sema_mod = cpp_context.sema().getCurrentModule(); CARBON_CHECK(sema_mod, "Sema prematurely exited Carbon module"); preprocessor.EnterAnnotationToken(loc, clang::tok::annot_module_end, sema_mod); ParseInjectedTokens(cpp_context); if (sema_mod == mod) { break; } } } // Imports the module `import_mod` into the current Clang state. static auto ImportModule(CppContext& cpp_context, clang::Module* import_mod, clang::SourceLocation loc) -> void { CARBON_CHECK(import_mod); cpp_context.sema().getModuleLoader().makeModuleVisible( import_mod, clang::Module::AllVisible, loc); cpp_context.sema().getPreprocessor().makeModuleVisible(import_mod, loc); cpp_context.sema().makeModuleVisible(import_mod, loc); } // Imports the header specified by the given import declaration. static auto ImportHeader(Context& context, clang::Module* mod, const Parse::Tree::PackagingNames& import) -> void { auto* cpp_context = context.cpp_context(); CARBON_CHECK(cpp_context); clang::SourceLocation import_loc = GetCppLocation(context, import.node_id); // Import the corresponding module. auto name = context.string_literal_values().Get(import.library_id); auto [header_mod, added] = GetOrCreateModuleForHeader(cpp_context->domain(), name); // Re-export the header. // TODO: Only do this if the header is `export import`ed. For now we don't // syntactically allow `export` on `import Cpp ...` declarations. mod->Exports.push_back({header_mod, false}); // If this is the first time we've seen an import of this header, build // the contents of its module now. if (added) { EnterModule(*cpp_context, header_mod, import_loc); // The header module re-exports everything it imports. header_mod->Exports.push_back({nullptr, true}); RawStringOstream code_stream; GenerateLineMarker(context, code_stream, context.tokens().GetLineNumber( context.parse_tree().node_token(import.node_id))); if (name.starts_with('<') && name.ends_with('>')) { code_stream << "#include <" << FormatEscaped(name.drop_front().drop_back()) << ">\n"; } else { code_stream << "#include \"" << FormatEscaped(name) << "\"\n"; } InjectBuffer(*cpp_context, code_stream.TakeStr(), "<header import>", clang::SourceLocation()); ParseInjectedTokens(*cpp_context); LeaveModule(*cpp_context, import_loc); } ImportModule(*cpp_context, header_mod, import_loc); } // Injects code to import the given set of headers into Clang and parses it as // top-level declarations. static auto ParseImports(Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports) -> void { auto* cpp_context = context.cpp_context(); CARBON_CHECK(cpp_context); auto& preprocessor = cpp_context->sema().getPreprocessor(); auto filename = context.sem_ir().filename(); // Enter the module for this file. Generate a placeholder empty buffer so we // can provide a location for entering the module. auto file_id = InjectBuffer(*cpp_context, "", filename, clang::SourceLocation()); auto loc = preprocessor.getSourceManager().getLocForStartOfFile(file_id); auto* mod = CreateModuleForCarbonFile(cpp_context->domain(), context.sem_ir()); EnterModule(*cpp_context, mod, loc); // Import the modules for all the imported IRs. for (const auto& import_ir : context.import_irs().values()) { if (!import_ir.sem_ir) { continue; } if (auto lookup = cpp_context->domain().file_modules().Lookup( import_ir.sem_ir->check_ir_id())) { auto* import_mod = lookup.value(); ImportModule(*cpp_context, import_mod, loc); if (import_ir.is_export) { mod->Exports.push_back({import_mod, false}); } else { mod->Imports.push_back(import_mod); } } } // For each imported C++ header, generate a module and include the header into // that module. For imported inline code, parse the code directly. for (const Parse::Tree::PackagingNames& import : imports) { if (import.inline_body_id.has_value()) { // `import Cpp inline "foo";` behaves the same as `inline Cpp "foo";`. auto code_token = context.parse_tree().node_token(import.inline_body_id); InjectAstFromInlineCode( context, import.inline_body_id, context.string_literal_values().Get( context.tokens().GetStringLiteralValue(code_token))); } else if (import.library_id.has_value()) { ImportHeader(context, mod, import); } } } 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(llvm::ArrayRef<SemIR::CppInputFile> inputs, llvm::LLVMContext* llvm_context) : inputs_(inputs), llvm_context_(llvm_context) {} auto code_generators() const -> llvm::ArrayRef<clang::CodeGenerator*> { return code_generators_; } auto TakeParser() -> std::unique_ptr<clang::Parser> { return std::move(parser_); } protected: auto CreateASTConsumer(clang::CompilerInstance& clang_instance, llvm::StringRef /*file*/) -> std::unique_ptr<clang::ASTConsumer> override { if (!llvm_context_) { return std::make_unique<clang::ASTConsumer>(); } // Build a code generator for each object file we will be building. For now // we assume that we want one object file per Carbon source file. // TODO: Only build CodeGenerators for the files we're actually generating // code for. // TODO: Consider supporting generating code for multiple Carbon files into // a single object file, for a faster `carbon build` mode. std::vector<std::unique_ptr<clang::ASTConsumer>> consumers; for (const auto& input : inputs_) { if (!input.is_lowered) { code_generators_.push_back(nullptr); continue; } // TODO: Filter what goes into each code generator. If there are strong // external C++ definitions in a Carbon file (for example, in inline C++ // code), they should be emitted only in that one file. auto code_generator = std::unique_ptr<clang::CodeGenerator>(clang::CreateLLVMCodeGen( clang_instance.getDiagnostics(), input.filename, clang_instance.getVirtualFileSystemPtr(), clang_instance.getHeaderSearchOpts(), clang_instance.getPreprocessorOpts(), clang_instance.getCodeGenOpts(), *llvm_context_)); code_generators_.push_back(code_generator.get()); consumers.push_back(std::move(code_generator)); } return std::make_unique<clang::MultiplexConsumer>(std::move(consumers)); } auto BeginSourceFileAction(clang::CompilerInstance& /*clang_instance*/) -> bool override { return true; } // Parse the imports and inline C++ fragments. This is notionally very similar // to `clang::ParseAST`, which `ASTFrontendAction::ExecuteAction` calls, but // this version doesn't parse C++20 modules and stops just before reaching the // end of the translation unit. auto ExecuteAction() -> void override { clang::CompilerInstance& clang_instance = getCompilerInstance(); clang_instance.createSema(getTranslationUnitKind(), /*CompletionConsumer=*/nullptr); parser_ = std::make_unique<clang::Parser>(clang_instance.getPreprocessor(), clang_instance.getSema(), /*SkipFunctionBodies=*/false); clang_instance.getPreprocessor().enableIncrementalProcessing(); clang_instance.getPreprocessor().EnterMainSourceFile(); parser_->Initialize(); if (auto* source = clang_instance.getASTContext().getExternalSource()) { source->StartTranslationUnit(&clang_instance.getASTConsumer()); } clang_instance.getSema().ActOnStartOfTranslationUnit(); ParseTopLevelDecls(*parser_, clang_instance.getASTConsumer()); } private: llvm::ArrayRef<SemIR::CppInputFile> inputs_; llvm::LLVMContext* llvm_context_; llvm::SmallVector<clang::CodeGenerator*> code_generators_; std::unique_ptr<clang::Parser> parser_; }; } // namespace // 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::ArrayRef<SemIR::CppInputFile> inputs, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, llvm::LLVMContext* llvm_context, std::shared_ptr<clang::CompilerInvocation> base_invocation) -> std::unique_ptr<SemIR::CppDomain> { std::shared_ptr<clang::CompilerInstance> clang_instance; llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diags; // Build a new invocation. auto invocation = std::make_shared<ShallowCopyCompilerInvocation>(*base_invocation); // Ask Clang to not leak memory. invocation->getFrontendOpts().DisableFree = false; // Build a diagnostics engine. 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. const auto& clang_inputs = invocation->getFrontendOpts().Inputs; CARBON_CHECK(clang_inputs.size() == 1); CARBON_CHECK(clang_inputs[0].getKind().getLanguage() == clang::Language::CXX); CARBON_CHECK(clang_inputs[0].getKind().getFormat() == clang::InputKind::Source); llvm::StringRef file_name = clang_inputs[0].getFile(); // 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, empty_buffer.release()); clang_instance = std::make_shared<clang::CompilerInstance>(invocation); clang_instance->setDiagnostics(diags); clang_instance->setVirtualFileSystem(fs); clang_instance->createFileManager(); clang_instance->createSourceManager(); if (!clang_instance->createTarget()) { return nullptr; } GenerateASTAction action(inputs, llvm_context); if (!action.BeginSourceFile(*clang_instance, clang_inputs[0])) { return nullptr; } auto& ast = clang_instance->getASTContext(); // Create an AST reader before we set up our own source. Clang does this // automatically later if we don't do it now, and will overwrite our external // source with its own when it does so. clang_instance->createASTReader(); // Always build a multiplex source, even if there's only one child // source. During lowering, the `CarbonExternalASTSource` can no longer be // used (because it uses `Check::Context`), so a `ReadOnlyASTSource` is // installed instead. However, clang internally keeps pointers to the // top-level `ExternalASTSource` installed via `setExternalSource`, and // those pointers aren't updated if `setExternalSource` is called again. By // using `MultiplexExternalSemaSource`, we can keep the top-level // `ExternalASTSource` pointer the same, and only update its children. auto multiplex_source_ref_cnt_ptr = llvm::makeIntrusiveRefCnt<clang::MultiplexExternalSemaSource>(); auto* multiplex_source = cast<clang::MultiplexExternalSemaSource>( multiplex_source_ref_cnt_ptr.get()); if (auto* existing_source = llvm::cast_or_null<clang::ExternalSemaSource>( ast.getExternalSource())) { multiplex_source->AddSource(existing_source); } 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. CARBON_FATAL("Failed to execute clang action: {0}", llvm::toString(std::move(error))); } auto parser = action.TakeParser(); CARBON_CHECK(parser); CARBON_CHECK(action.code_generators().size() == inputs.size()); return std::make_unique<SemIR::CppDomain>( std::move(clang_instance), std::move(parser), inputs, action.code_generators(), llvm_context); } auto GenerateAst(Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports, SemIR::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_ptr(); auto mangle_context = std::unique_ptr<clang::MangleContext>( clang_instance->getASTContext().createMangleContext()); // Set up CppFile for the current SemIR::File. context.sem_ir().set_cpp_file(std::make_unique<SemIR::CppFile>( clang_instance, std::move(mangle_context), domain.llvm_context(), domain.GetCodeGenerator(context.sem_ir().check_ir_id()), &domain)); // Set up CppContext for the current Context. context.set_cpp_context(std::make_unique<CppContext>( domain, MakeContextDiagnosticListener( *clang_instance->getDiagnostics().getClient(), context))); // Add an external source referring to this context. auto* multiplex_source = cast<clang::MultiplexExternalSemaSource>( context.ast_context().getExternalSource()); auto ast_source = llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context); multiplex_source->AddSource(ast_source); // Map the package scope to the Carbon namespace. ast_source->BuildCarbonNamespace(); // Parse the imports-as-#includes buffer. ParseImports(context, imports); return true; } auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id, llvm::StringRef source_code) -> void { auto* cpp_context = context.cpp_context(); CARBON_CHECK(cpp_context); RawStringOstream code_stream; AppendInlineCode(context, code_stream, context.parse_tree().node_token(loc_id.node_id()), source_code); // Clang will have generated a suitable error if this fails. There's nothing // more to do here. InjectBuffer(*cpp_context, code_stream.TakeStr(), "<inline c++>", GetCppLocation(context, loc_id)); ParseInjectedTokens(*cpp_context); } auto FinishAst(Context& context) -> void { if (!context.cpp_context()) { return; } // Leave the module we entered to encapsulate the contents of this Carbon // file. auto end_loc_id = SemIR::LocId(*(context.sem_ir().parse_tree().postorder().end() - 1)); // Shuffle the end of file location back by one character to work around a // Clang bug: if we give Clang the end-of-file location, it will replace the // location with the include location without checking whether the file was // actually included, and then crash because it picked an invalid location! // There is always at least one token in a file with a `Cpp` import, so this // location adjustment is safe. LeaveModule(*context.cpp_context(), GetCppLocation(context, end_loc_id).getLocWithOffset(-1)); // 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 // replace it with a `ReadOnlyASTSource`. This is necessary because // the source may be accessed later during lowering, but the // `CarbonExternalASTSource` has a pointer to `Check::Context` that // will not remain valid. auto* multiplex_source = cast<clang::MultiplexExternalSemaSource>( context.ast_context().getExternalSource()); multiplex_source->EraseIf([](const auto& src) { return llvm::isa<CarbonExternalASTSource>(src.get()); }); multiplex_source->AddSource( llvm::makeIntrusiveRefCnt<SemIR::ReadOnlyASTSource>(context.sem_ir())); // We don't call FrontendAction::EndSourceFile, because that destroys the AST. context.set_cpp_context(nullptr); } auto FinalizeCppDomain(SemIR::CppDomain& domain) -> void { if (domain.clang_instance_ptr()) { domain.clang_instance().getSema().ActOnEndOfTranslationUnit(); FlushDiagnosticConsumer( *domain.clang_instance().getDiagnostics().getClient()); } } } // namespace Carbon::Check