diff --git a/MODULE.bazel b/MODULE.bazel index b5ef04c47d30..fdfb0dbf2776 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -93,6 +93,7 @@ git_override( "//bazel/llvm_project:0004_Introduce_basic_sources_exporting_for_libunwind.patch", "//bazel/llvm_project:0005_Introduce_basic_sources_exporting_for_libcxx_and_libcxxabi.patch", "//bazel/llvm_project:0009_Introduce_starlark_exporting_compiler-rt_build_information.patch", + "//bazel/llvm_project:0011-Add-empty-constructor-and-GetSources-method-to-Multi.patch", ], remote = "https://github.com/llvm/llvm-project.git", ) diff --git a/bazel/llvm_project/0011-Add-empty-constructor-and-GetSources-method-to-Multi.patch b/bazel/llvm_project/0011-Add-empty-constructor-and-GetSources-method-to-Multi.patch new file mode 100644 index 000000000000..e98a01b355a4 --- /dev/null +++ b/bazel/llvm_project/0011-Add-empty-constructor-and-GetSources-method-to-Multi.patch @@ -0,0 +1,60 @@ +From 2c7bb43e8800ef29d815f37422a2e56ee0e724b1 Mon Sep 17 00:00:00 2001 +From: Nicholas Bishop +Date: Wed, 10 Jun 2026 20:19:57 -0400 +Subject: [PATCH] Add empty constructor and GetSources method to + MultiplexExternalSemaSource + +--- + .../include/clang/Sema/MultiplexExternalSemaSource.h | 11 ++++++++++- + clang/lib/Sema/MultiplexExternalSemaSource.cpp | 3 +++ + 2 files changed, 13 insertions(+), 1 deletion(-) + +diff --git a/clang/include/clang/Sema/MultiplexExternalSemaSource.h b/clang/include/clang/Sema/MultiplexExternalSemaSource.h +index 12015724b39f..9230d843e923 100644 +--- a/clang/include/clang/Sema/MultiplexExternalSemaSource.h ++++ b/clang/include/clang/Sema/MultiplexExternalSemaSource.h +@@ -39,10 +39,16 @@ class MultiplexExternalSemaSource : public ExternalSemaSource { + /// LLVM-style RTTI. + static char ID; + ++public: ++ using SourceVector = SmallVector, 2>; ++ + private: +- SmallVector, 2> Sources; ++ SourceVector Sources; + + public: ++ /// Constructs an empty multiplexing external sema source. ++ MultiplexExternalSemaSource(); ++ + /// Constructs a new multiplexing external sema source and appends the + /// given element to it. + /// +@@ -58,6 +64,9 @@ public: + /// + void AddSource(llvm::IntrusiveRefCntPtr Source); + ++ /// Returns a reference to the sources vector. ++ SourceVector& GetSources() { return Sources; } ++ + //===--------------------------------------------------------------------===// + // ExternalASTSource. + //===--------------------------------------------------------------------===// +diff --git a/clang/lib/Sema/MultiplexExternalSemaSource.cpp b/clang/lib/Sema/MultiplexExternalSemaSource.cpp +index be9582ce501f..4306143025c8 100644 +--- a/clang/lib/Sema/MultiplexExternalSemaSource.cpp ++++ b/clang/lib/Sema/MultiplexExternalSemaSource.cpp +@@ -16,6 +16,9 @@ using namespace clang; + + char MultiplexExternalSemaSource::ID; + ++/// Constructs an empty multiplexing external sema source. ++MultiplexExternalSemaSource::MultiplexExternalSemaSource() {} ++ + /// Constructs a new multiplexing external sema source and appends the + /// given element to it. + /// +-- +2.54.0.1136.gdb2ca164c4-goog + diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 5898ce637c6d..5e66adf09518 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -186,6 +186,7 @@ cc_library( "//toolchain/sem_ir:file", "//toolchain/sem_ir:formatter", "//toolchain/sem_ir:mangler", + "//toolchain/sem_ir:read_only_ast_source", "//toolchain/sem_ir:typed_insts", "@llvm-project//clang:ast", "@llvm-project//clang:basic", diff --git a/toolchain/check/cpp/export.cpp b/toolchain/check/cpp/export.cpp index 533548e62e56..7d34eeaa453f 100644 --- a/toolchain/check/cpp/export.cpp +++ b/toolchain/check/cpp/export.cpp @@ -177,39 +177,6 @@ auto ExportClassToCpp(Context& context, SemIR::LocId loc_id, return record_decl; } -// Get the `StructTypeField`s from a class's object repr. -static auto GetStructTypeFields(Context& context, - const SemIR::Class& class_info) - -> llvm::ArrayRef { - if (class_info.adapt_id.has_value()) { - // The representation of an adapter won't necessarily be a - // struct. Return an empty array since adapters can't declare - // fields. - return {}; - } - - auto object_repr_type_id = - class_info.GetObjectRepr(context.sem_ir(), SemIR::SpecificId::None); - if (object_repr_type_id == SemIR::ErrorInst::TypeId) { - return {}; - } - auto struct_type = - context.types().GetAs(object_repr_type_id); - return context.struct_type_fields().Get(struct_type.fields_id); -} - -static auto LookupClassFieldByStructField( - const Context& context, const SemIR::NameScope& class_scope, - const SemIR::StructTypeField& struct_field) - -> std::optional> { - if (auto entry_id = class_scope.Lookup(struct_field.name_id)) { - auto field_inst_id = - class_scope.GetEntry(*entry_id).result.target_inst_id(); - return context.insts().TryGetAsWithId(field_inst_id); - } - return std::nullopt; -} - static auto SetCppClassMemberAccess(const SemIR::NameScope& class_scope, SemIR::NameId member_name_id, clang::Decl* member) -> void { @@ -265,9 +232,10 @@ auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void { const auto& class_scope = context.name_scopes().Get(class_info.scope_id); - for (const auto& struct_field : GetStructTypeFields(context, class_info)) { - auto class_field = - LookupClassFieldByStructField(context, class_scope, struct_field); + for (const auto& struct_field : + class_info.GetStructTypeFields(context.sem_ir())) { + auto class_field = LookupClassFieldByStructField(context.sem_ir(), + class_scope, struct_field); if (!class_field) { continue; } @@ -314,42 +282,6 @@ auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id, return nullptr; } -auto CalculateCppFieldOffsets( - Context& context, SemIR::ClassId class_id, - llvm::DenseMap& field_offsets) -> bool { - auto class_info = context.classes().Get(class_id); - const auto& class_scope = context.name_scopes().Get(class_info.scope_id); - - auto class_layout = SemIR::ObjectLayout::Empty(); - for (const auto& struct_field : GetStructTypeFields(context, class_info)) { - auto field_type_id = context.sem_ir().types().GetTypeIdForTypeInstId( - struct_field.type_inst_id); - auto field_layout = context.sem_ir() - .types() - .GetCompleteTypeInfo(field_type_id) - .object_layout; - - // Use the field's name to look up the corresponding entry in the - // class. If it's a `FieldDecl`, write out the offset of the - // corresponding `clang::FieldDecl`. - auto class_field = - LookupClassFieldByStructField(context, class_scope, struct_field); - if (class_field) { - auto* cpp_field_decl = - ExportFieldToCpp(context, class_field->inst_id, class_field->inst); - if (!cpp_field_decl) { - return false; - } - field_offsets.insert( - {cpp_field_decl, class_layout.FieldOffset(field_layout).bits()}); - } - - class_layout.AppendField(field_layout); - } - - return true; -} - namespace { struct FunctionInfo { struct Param { diff --git a/toolchain/check/cpp/export.h b/toolchain/check/cpp/export.h index 0e6e95b9b8a2..1311d7d2c7e7 100644 --- a/toolchain/check/cpp/export.h +++ b/toolchain/check/cpp/export.h @@ -51,13 +51,6 @@ auto ExportAllFieldsToCpp(Context& context, SemIR::Class& class_info) -> void; auto ExportFieldToCpp(Context& context, SemIR::InstId field_inst_id, SemIR::FieldDecl field_decl) -> clang::FieldDecl*; -// Get the field offset for each field in a class. -// -// Returns true on success, false if any error occurs. -auto CalculateCppFieldOffsets( - Context& context, SemIR::ClassId class_id, - llvm::DenseMap& field_offsets) -> bool; - // Get a `clang::FunctionDecl` that can be used to call a Carbon function. auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id, SemIR::FunctionId function_id) -> clang::FunctionDecl*; diff --git a/toolchain/check/cpp/generate_ast.cpp b/toolchain/check/cpp/generate_ast.cpp index 5737d203ffa9..4b369a07a7a7 100644 --- a/toolchain/check/cpp/generate_ast.cpp +++ b/toolchain/check/cpp/generate_ast.cpp @@ -24,6 +24,7 @@ #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" @@ -41,6 +42,7 @@ #include "toolchain/diagnostics/format_providers.h" #include "toolchain/parse/node_ids.h" #include "toolchain/sem_ir/cpp_file.h" +#include "toolchain/sem_ir/read_only_ast_source.h" #include "toolchain/sem_ir/typed_insts.h" namespace Carbon::Check { @@ -323,9 +325,10 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation { }; // Provides clang AST nodes representing Carbon SemIR entities. -class CarbonExternalASTSource : public clang::ExternalSemaSource { +class CarbonExternalASTSource : public SemIR::ReadOnlyASTSource { public: - explicit CarbonExternalASTSource(Context* context) : context_(context) {} + explicit CarbonExternalASTSource(Context* context) + : ReadOnlyASTSource(context->sem_ir()), context_(context) {} auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override; @@ -345,6 +348,13 @@ class CarbonExternalASTSource : public clang::ExternalSemaSource { llvm::DenseMap& 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: // Builds the top-level C++ namespace `Carbon` and adds it to the translation // unit. @@ -380,9 +390,14 @@ class CarbonExternalASTSource : public clang::ExternalSemaSource { SemIR::ImportIRInst(clang_source_loc_id)); } + // For LLVM RTTI. + static char id; + Check::Context* context_; }; +char CarbonExternalASTSource::id; + } // namespace void CarbonExternalASTSource::StartTranslationUnit( @@ -575,54 +590,6 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName( } } -// If this declaration declares a class type that is "owned" by Carbon, and not -// imported from C++, returns the corresponding type ID and `ClassType`. -// Otherwise returns `nullopt`. -static auto GetAsCarbonOwnedClass(Context& context, - const clang::TagDecl* tag_decl) - -> std::optional> { - // Quickly check whether we could possibly own this class. - // TODO: Once we multiplex with the ASTReader, handle - // ASTReader::completeVisibleDeclsMap setting this to `false`. - if (!tag_decl->hasExternalVisibleStorage()) { - return std::nullopt; - } - - auto key = SemIR::ClangDeclKey::ForNonFunctionDecl( - const_cast(tag_decl->getFirstDecl())); - auto clang_decl_id = context.clang_decls().LookupId(key); - if (!clang_decl_id.has_value()) { - return std::nullopt; - } - - auto inst_id = context.clang_decls().Get(clang_decl_id).inst_id; - auto const_id = context.constant_values().Get(inst_id); - if (!const_id.has_value()) { - return std::nullopt; - } - - auto class_type = - context.constant_values().TryGetInstAs(const_id); - if (!class_type) { - return std::nullopt; - } - - // Determine whether this class was imported from C++. - // TODO: This currently can't happen, because only Carbon classes have - // external lexical storage, but will happen once we support importing C++ - // classes from AST files. Add a test once that is supported. - // TODO: Consider setting `extern_library_id` on classes imported from C++ to - // indicate the current file does not own them. - const auto& class_info = context.classes().Get(class_type->class_id); - if (class_info.parent_scope_id.has_value() && - context.name_scopes().Get(class_info.parent_scope_id).is_cpp_scope()) { - return std::nullopt; - } - - auto class_type_id = context.types().GetTypeIdForTypeConstantId(const_id); - return std::make_pair(class_type_id, *class_type); -} - auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { auto* class_decl = dyn_cast(tag_decl); if (!class_decl) { @@ -631,7 +598,8 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void { return; } - auto carbon_class_info = GetAsCarbonOwnedClass(*context_, tag_decl); + auto carbon_class_info = + SemIR::GetAsCarbonOwnedClass(context_->sem_ir(), tag_decl); if (!carbon_class_info) { return; } @@ -736,7 +704,8 @@ auto CarbonExternalASTSource::layoutRecordType( llvm::DenseMap& base_offsets, llvm::DenseMap& vbase_offsets) -> bool { - auto carbon_class_info = GetAsCarbonOwnedClass(*context_, record_decl); + auto carbon_class_info = + SemIR::GetAsCarbonOwnedClass(context_->sem_ir(), record_decl); if (!carbon_class_info) { return false; } @@ -748,37 +717,11 @@ auto CarbonExternalASTSource::layoutRecordType( // general. CompleteTypeOrCheckFail(*context_, class_type_id); - // Set the overall size and alignment. We round up the size to an integer - // number of bytes in order to avoid surprising Clang too much. - auto layout = context_->sem_ir() - .types() - .GetCompleteTypeInfo(class_type_id) - .object_layout; - size = layout.size.bytes() * 8; - alignment = layout.alignment.bits(); + auto& class_info = context_->classes().Get(class_type.class_id); + ExportAllFieldsToCpp(*context_, class_info); - // Fill in `field_offsets`. - CalculateCppFieldOffsets(*context_, class_type.class_id, field_offsets); - - // Add offset for base class, if any. - if (const auto* class_decl = dyn_cast(record_decl); - class_decl && !class_decl->bases().empty()) { - CARBON_CHECK(class_decl->getNumBases() == 1, - "Carbon class with multiple bases"); - const auto& base = *class_decl->bases_begin(); - // TODO: If this class introduced a vptr, the base will be at an offset of - // `sizeof(void*)`, not 0. - base_offsets.insert( - {base.getType()->getAsCXXRecordDecl()->getCanonicalDecl(), - clang::CharUnits::Zero()}); - - // TODO: Support deriving from a C++ class with virtual bases. - CARBON_CHECK(class_decl->getNumVBases() == 0, - "Carbon class with multiple bases"); - static_cast(vbase_offsets); - } - - return true; + return ReadOnlyASTSource::layoutRecordType( + record_decl, size, alignment, field_offsets, base_offsets, vbase_offsets); } // Parses a sequence of top-level declarations and forms a corresponding @@ -945,17 +888,26 @@ auto GenerateAst(Context& context, context.sem_ir().cpp_file()->CreateMangleContext(); auto& ast = clang_instance.getASTContext(); - llvm::IntrusiveRefCntPtr carbon_source = - llvm::makeIntrusiveRefCnt(&context); + + // 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(); + auto* multiplex_source = cast( + multiplex_source_ref_cnt_ptr.get()); if (auto* existing_source = llvm::cast_or_null( ast.getExternalSource())) { - auto multiplex_source = - llvm::makeIntrusiveRefCnt( - existing_source, std::move(carbon_source)); - ast.setExternalSource(std::move(multiplex_source)); - } else { - ast.setExternalSource(std::move(carbon_source)); + 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. @@ -1008,6 +960,21 @@ auto FinishAst(Context& context) -> void { context.cpp_context()->sema().ActOnEndOfTranslationUnit(); + // 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( + context.ast_context().getExternalSource()); + auto& child_sources = multiplex_source->GetSources(); + llvm::erase_if(child_sources, [](const auto& src) { + // `CarbonExternalASTSource` inherits from `ReadOnlyASTSource`. + return llvm::isa(src.get()); + }); + multiplex_source->AddSource( + llvm::makeIntrusiveRefCnt(context.sem_ir())); + // We don't call FrontendAction::EndSourceFile, because that destroys the AST. context.set_cpp_context(nullptr); diff --git a/toolchain/lower/testdata/interop/cpp/issue7142.carbon b/toolchain/lower/testdata/interop/cpp/issue7142.carbon new file mode 100644 index 000000000000..e8b40eeb8cf5 --- /dev/null +++ b/toolchain/lower/testdata/interop/cpp/issue7142.carbon @@ -0,0 +1,146 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/full.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/interop/cpp/issue7142.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/interop/cpp/issue7142.carbon + +import Cpp; + +class A { var price: i32; }; +fn Get() -> A*; + +inline Cpp ''' +struct S { + static int Vend() { + return Carbon::Get()->price; + } +}; + +void f() { + S::Vend(); +} +'''; + +// CHECK:STDOUT: ; ModuleID = 'issue7142.carbon' +// CHECK:STDOUT: source_filename = "issue7142.carbon" +// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu" +// CHECK:STDOUT: +// CHECK:STDOUT: %"class.Carbon::A" = type { i32 } +// CHECK:STDOUT: +// CHECK:STDOUT: $_ZN1S4VendEv = comdat any +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable +// CHECK:STDOUT: define dso_local void @_Z1fv() #0 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %call = call noundef i32 @_ZN1S4VendEv() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: mustprogress uwtable +// CHECK:STDOUT: define linkonce_odr dso_local noundef i32 @_ZN1S4VendEv() #0 comdat align 2 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %call = call noundef ptr @_ZN6CarbonL3GetEv() +// CHECK:STDOUT: %price = getelementptr inbounds nuw %"class.Carbon::A", ptr %call, i32 0, i32 0 +// CHECK:STDOUT: %0 = load i32, ptr %price, align 4, !tbaa !11 +// CHECK:STDOUT: ret i32 %0 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: declare ptr @_CGet.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define void @_CGet__carbon_thunk.Main(ptr %_) #1 !dbg !13 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %Get.call = call ptr @_CGet.Main(), !dbg !19 +// CHECK:STDOUT: store ptr %Get.call, ptr %_, align 8, !dbg !19 +// CHECK:STDOUT: ret void, !dbg !19 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline nounwind +// CHECK:STDOUT: define void @"_C__destroy_thunk:thunk.A.Main"(ptr %self) #2 !dbg !20 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @"_COp.68011419f123494e:core.Destroy.Core"(ptr %self), !dbg !23 +// CHECK:STDOUT: ret void, !dbg !23 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.5e27612b9dd31a14:core.Destroy.Core"(ptr %self) #1 !dbg !24 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !30 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.dd803598fd1f0f08:core.Destroy.Core"(ptr %self) #1 !dbg !31 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !34 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: nounwind +// CHECK:STDOUT: define weak_odr void @"_COp.68011419f123494e:core.Destroy.Core"(ptr %self) #1 !dbg !35 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: ret void, !dbg !38 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: ; Function Attrs: alwaysinline mustprogress nounwind uwtable +// CHECK:STDOUT: define internal noundef ptr @_ZN6CarbonL3GetEv() #3 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %retval = alloca ptr, align 8 +// CHECK:STDOUT: call void @_CGet__carbon_thunk.Main(ptr noundef nonnull align 8 dereferenceable(8) %retval) +// CHECK:STDOUT: %0 = load ptr, ptr %retval, align 8 +// CHECK:STDOUT: ret ptr %0 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: attributes #0 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: attributes #1 = { nounwind } +// CHECK:STDOUT: attributes #2 = { alwaysinline nounwind } +// CHECK:STDOUT: attributes #3 = { alwaysinline mustprogress nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +// CHECK:STDOUT: +// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4} +// CHECK:STDOUT: !llvm.dbg.cu = !{!5} +// CHECK:STDOUT: !llvm.errno.tbaa = !{!7} +// CHECK:STDOUT: +// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5} +// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3} +// CHECK:STDOUT: !2 = !{i32 8, !"PIC Level", i32 2} +// CHECK:STDOUT: !3 = !{i32 7, !"PIE Level", i32 2} +// CHECK:STDOUT: !4 = !{i32 7, !"uwtable", i32 2} +// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug) +// CHECK:STDOUT: !6 = !DIFile(filename: "issue7142.carbon", directory: "") +// CHECK:STDOUT: !7 = !{!8, !8, i64 0} +// CHECK:STDOUT: !8 = !{!"int", !9, i64 0} +// CHECK:STDOUT: !9 = !{!"omnipotent char", !10, i64 0} +// CHECK:STDOUT: !10 = !{!"Simple C++ TBAA"} +// CHECK:STDOUT: !11 = !{!12, !8, i64 0} +// CHECK:STDOUT: !12 = !{!"_ZTSN6Carbon1AE", !8, i64 0} +// CHECK:STDOUT: !13 = distinct !DISubprogram(name: "Get__carbon_thunk", linkageName: "_CGet__carbon_thunk.Main", scope: null, file: !6, line: 16, type: !14, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !17) +// CHECK:STDOUT: !14 = !DISubroutineType(types: !15) +// CHECK:STDOUT: !15 = !{null, !16} +// CHECK:STDOUT: !16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64) +// CHECK:STDOUT: !17 = !{!18} +// CHECK:STDOUT: !18 = !DILocalVariable(arg: 1, scope: !13, type: !16) +// CHECK:STDOUT: !19 = !DILocation(line: 16, column: 1, scope: !13) +// CHECK:STDOUT: !20 = distinct !DISubprogram(name: "__destroy_thunk", linkageName: "_C__destroy_thunk:thunk.A.Main", scope: null, file: !6, line: 15, type: !14, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !21) +// CHECK:STDOUT: !21 = !{!22} +// CHECK:STDOUT: !22 = !DILocalVariable(arg: 1, scope: !20, type: !16) +// CHECK:STDOUT: !23 = !DILocation(line: 15, column: 1, scope: !20) +// CHECK:STDOUT: !24 = distinct !DISubprogram(name: "Op", linkageName: "_COp.5e27612b9dd31a14:core.Destroy.Core", scope: null, file: !6, line: 15, type: !25, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !28) +// CHECK:STDOUT: !25 = !DISubroutineType(types: !26) +// CHECK:STDOUT: !26 = !{null, !27} +// CHECK:STDOUT: !27 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +// CHECK:STDOUT: !28 = !{!29} +// CHECK:STDOUT: !29 = !DILocalVariable(arg: 1, scope: !24, type: !27) +// CHECK:STDOUT: !30 = !DILocation(line: 15, column: 1, scope: !24) +// CHECK:STDOUT: !31 = distinct !DISubprogram(name: "Op", linkageName: "_COp.dd803598fd1f0f08:core.Destroy.Core", scope: null, file: !6, line: 15, type: !14, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !32) +// CHECK:STDOUT: !32 = !{!33} +// CHECK:STDOUT: !33 = !DILocalVariable(arg: 1, scope: !31, type: !16) +// CHECK:STDOUT: !34 = !DILocation(line: 15, column: 1, scope: !31) +// CHECK:STDOUT: !35 = distinct !DISubprogram(name: "Op", linkageName: "_COp.68011419f123494e:core.Destroy.Core", scope: null, file: !6, line: 15, type: !14, spFlags: DISPFlagDefinition, unit: !5, retainedNodes: !36) +// CHECK:STDOUT: !36 = !{!37} +// CHECK:STDOUT: !37 = !DILocalVariable(arg: 1, scope: !35, type: !16) +// CHECK:STDOUT: !38 = !DILocation(line: 15, column: 1, scope: !35) diff --git a/toolchain/sem_ir/BUILD b/toolchain/sem_ir/BUILD index 378bdceee0d4..aba7c5053ddc 100644 --- a/toolchain/sem_ir/BUILD +++ b/toolchain/sem_ir/BUILD @@ -382,3 +382,13 @@ cc_library( "@llvm-project//llvm:Support", ], ) + +cc_library( + name = "read_only_ast_source", + srcs = ["read_only_ast_source.cpp"], + hdrs = ["read_only_ast_source.h"], + deps = [ + "//toolchain/sem_ir:file", + "@llvm-project//clang:sema", + ], +) diff --git a/toolchain/sem_ir/class.cpp b/toolchain/sem_ir/class.cpp index dfa0dc66bfd5..0eb072dc9ffa 100644 --- a/toolchain/sem_ir/class.cpp +++ b/toolchain/sem_ir/class.cpp @@ -4,6 +4,7 @@ #include "toolchain/sem_ir/class.h" +#include "clang/AST/Decl.h" #include "toolchain/base/value_store_impl.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/generic.h" @@ -51,6 +52,80 @@ auto Class::GetObjectRepr(const File& file, SpecificId specific_id) const .object_repr_type_inst_id); } +auto Class::GetStructTypeFields(const File& sem_ir) const + -> llvm::ArrayRef { + if (adapt_id.has_value()) { + // The representation of an adapter won't necessarily be a + // struct. Return an empty array since adapters can't declare + // fields. + return {}; + } + + auto object_repr_type_id = GetObjectRepr(sem_ir, SemIR::SpecificId::None); + if (object_repr_type_id == SemIR::ErrorInst::TypeId) { + return {}; + } + auto struct_type = + sem_ir.types().GetAs(object_repr_type_id); + return sem_ir.struct_type_fields().Get(struct_type.fields_id); +} + +auto GetAsCarbonOwnedClass(const File& sem_ir, const clang::TagDecl* tag_decl) + -> std::optional> { + // Quickly check whether we could possibly own this class. + // TODO: Once we multiplex with the ASTReader, handle + // ASTReader::completeVisibleDeclsMap setting this to `false`. + if (!tag_decl->hasExternalVisibleStorage()) { + return std::nullopt; + } + + auto key = SemIR::ClangDeclKey::ForNonFunctionDecl( + const_cast(tag_decl->getFirstDecl())); + auto clang_decl_id = sem_ir.clang_decls().LookupId(key); + if (!clang_decl_id.has_value()) { + return std::nullopt; + } + + auto inst_id = sem_ir.clang_decls().Get(clang_decl_id).inst_id; + auto const_id = sem_ir.constant_values().Get(inst_id); + if (!const_id.has_value()) { + return std::nullopt; + } + + auto class_type = + sem_ir.constant_values().TryGetInstAs(const_id); + if (!class_type) { + return std::nullopt; + } + + // Determine whether this class was imported from C++. + // TODO: This currently can't happen, because only Carbon classes have + // external lexical storage, but will happen once we support importing C++ + // classes from AST files. Add a test once that is supported. + // TODO: Consider setting `extern_library_id` on classes imported from C++ to + // indicate the current file does not own them. + const auto& class_info = sem_ir.classes().Get(class_type->class_id); + if (class_info.parent_scope_id.has_value() && + sem_ir.name_scopes().Get(class_info.parent_scope_id).is_cpp_scope()) { + return std::nullopt; + } + + auto class_type_id = sem_ir.types().GetTypeIdForTypeConstantId(const_id); + return std::make_pair(class_type_id, *class_type); +} + +auto LookupClassFieldByStructField(const File& sem_ir, + const SemIR::NameScope& class_scope, + const SemIR::StructTypeField& struct_field) + -> std::optional> { + if (auto entry_id = class_scope.Lookup(struct_field.name_id)) { + auto field_inst_id = + class_scope.GetEntry(*entry_id).result.target_inst_id(); + return sem_ir.insts().TryGetAsWithId(field_inst_id); + } + return std::nullopt; +} + } // namespace Carbon::SemIR namespace Carbon { diff --git a/toolchain/sem_ir/class.h b/toolchain/sem_ir/class.h index 1c6b5b7fdb3e..476d884b861c 100644 --- a/toolchain/sem_ir/class.h +++ b/toolchain/sem_ir/class.h @@ -5,13 +5,24 @@ #ifndef CARBON_TOOLCHAIN_SEM_IR_CLASS_H_ #define CARBON_TOOLCHAIN_SEM_IR_CLASS_H_ +#include + #include "common/map.h" #include "toolchain/base/value_store.h" #include "toolchain/sem_ir/entity_with_params_base.h" #include "toolchain/sem_ir/ids.h" +#include "toolchain/sem_ir/inst.h" +#include "toolchain/sem_ir/struct_type_field.h" + +namespace clang { +class TagDecl; +} namespace Carbon::SemIR { +class File; +class NameScope; + // Class-specific fields. struct ClassFields { enum InheritanceKind : int8_t { @@ -129,10 +140,25 @@ struct Class : public EntityWithParamsBase, // Gets the object representation for this class. Returns `None` if the class // is not yet defined. auto GetObjectRepr(const File& file, SpecificId specific_id) const -> TypeId; + + // Get the `StructTypeField`s from a class's object repr. + auto GetStructTypeFields(const File& sem_ir) const + -> llvm::ArrayRef; }; using ClassStore = ValueStore>; +// If this declaration declares a class type that is "owned" by Carbon, and not +// imported from C++, returns the corresponding type ID and `ClassType`. +// Otherwise returns `nullopt`. +auto GetAsCarbonOwnedClass(const File& sem_ir, const clang::TagDecl* tag_decl) + -> std::optional>; + +auto LookupClassFieldByStructField(const File& sem_ir, + const NameScope& class_scope, + const StructTypeField& struct_field) + -> std::optional>; + } // namespace Carbon::SemIR namespace Carbon { diff --git a/toolchain/sem_ir/read_only_ast_source.cpp b/toolchain/sem_ir/read_only_ast_source.cpp new file mode 100644 index 000000000000..fd0eacb87655 --- /dev/null +++ b/toolchain/sem_ir/read_only_ast_source.cpp @@ -0,0 +1,93 @@ +// 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/sem_ir/read_only_ast_source.h" + +namespace Carbon::SemIR { + +char ReadOnlyASTSource::id; + +// Get the field offset for each field in a class. +// +// Returns true on success, false if any error occurs. +static auto CalculateCppFieldOffsets( + const File& sem_ir, SemIR::ClassId class_id, + llvm::DenseMap& field_offsets) -> bool { + auto class_info = sem_ir.classes().Get(class_id); + const auto& class_scope = sem_ir.name_scopes().Get(class_info.scope_id); + + auto class_layout = SemIR::ObjectLayout::Empty(); + for (const auto& struct_field : class_info.GetStructTypeFields(sem_ir)) { + auto field_type_id = + sem_ir.types().GetTypeIdForTypeInstId(struct_field.type_inst_id); + auto field_layout = + sem_ir.types().GetCompleteTypeInfo(field_type_id).object_layout; + + // Use the field's name to look up the corresponding entry in the + // class. If it's a `FieldDecl`, write out the offset of the + // corresponding `clang::FieldDecl`. + auto class_field = + LookupClassFieldByStructField(sem_ir, class_scope, struct_field); + if (class_field) { + const auto* clang_decl = + sem_ir.clang_decls().Lookup(class_field->inst_id); + if (!clang_decl) { + return false; + } + + auto* cpp_field_decl = cast(clang_decl->decl()); + field_offsets.insert( + {cpp_field_decl, class_layout.FieldOffset(field_layout).bits()}); + } + + class_layout.AppendField(field_layout); + } + + return true; +} + +auto ReadOnlyASTSource::layoutRecordType( + const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment, + llvm::DenseMap& field_offsets, + llvm::DenseMap& base_offsets, + llvm::DenseMap& + vbase_offsets) -> bool { + auto carbon_class_info = GetAsCarbonOwnedClass(sem_ir_, record_decl); + if (!carbon_class_info) { + return false; + } + auto& [class_type_id, class_type] = *carbon_class_info; + + // Set the overall size and alignment. We round up the size to an integer + // number of bytes in order to avoid surprising Clang too much. + auto layout = + sem_ir_.types().GetCompleteTypeInfo(class_type_id).object_layout; + size = layout.size.bytes() * 8; + alignment = layout.alignment.bits(); + + // Fill in `field_offsets`. + CalculateCppFieldOffsets(sem_ir_, class_type.class_id, field_offsets); + + // Add offset for base class, if any. + if (const auto* class_decl = dyn_cast(record_decl); + class_decl && !class_decl->bases().empty()) { + CARBON_CHECK(class_decl->getNumBases() == 1, + "Carbon class with multiple bases"); + const auto& base = *class_decl->bases_begin(); + // TODO: If this class introduced a vptr, the base will be at an offset of + // `sizeof(void*)`, not 0. + base_offsets.insert( + {base.getType()->getAsCXXRecordDecl()->getCanonicalDecl(), + clang::CharUnits::Zero()}); + + // TODO: Support deriving from a C++ class with virtual bases. + CARBON_CHECK(class_decl->getNumVBases() == 0, + "Carbon class with multiple bases"); + static_cast(vbase_offsets); + } + + return true; +} + +} // namespace Carbon::SemIR diff --git a/toolchain/sem_ir/read_only_ast_source.h b/toolchain/sem_ir/read_only_ast_source.h new file mode 100644 index 000000000000..2a22a8b78836 --- /dev/null +++ b/toolchain/sem_ir/read_only_ast_source.h @@ -0,0 +1,41 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_SEM_IR_READ_ONLY_AST_SOURCE_H_ +#define CARBON_TOOLCHAIN_SEM_IR_READ_ONLY_AST_SOURCE_H_ + +#include "clang/Sema/ExternalSemaSource.h" +#include "toolchain/sem_ir/file.h" + +namespace Carbon::SemIR { + +class ReadOnlyASTSource : public clang::ExternalSemaSource { + public: + explicit ReadOnlyASTSource(const File& sem_ir) : sem_ir_(sem_ir) {} + + auto layoutRecordType( + const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment, + llvm::DenseMap& field_offsets, + llvm::DenseMap& + base_offsets, + llvm::DenseMap& + vbase_offsets) -> bool override; + + auto isA(const void* class_id) const -> bool override { + return class_id == &id || ExternalSemaSource::isA(class_id); + } + static auto classof(const ExternalASTSource* s) -> bool { + return s->isA(&id); + } + + private: + // For LLVM RTTI. + static char id; + + const File& sem_ir_; +}; + +} // namespace Carbon::SemIR + +#endif // CARBON_TOOLCHAIN_SEM_IR_READ_ONLY_AST_SOURCE_H_