Bläddra i källkod

Emit (relative) vtables (#5231)

One of the remaining three steps for proof-of-concept virtual function
lowering (the other two being: initializing vptrs to point to these
tables, and using the vptr+table at call sites).

Introduces a (placeholder?) mangling of vtables as ".$vtable" at the end
of the mangling of the class name.

Uses a scheme similar to clang's relative vtables - though those are
relative to the vtable slot, and this is relative to the start of the
vtable (seemed simpler? though I haven't looked at it in detail, perhaps
in lowering call sites I'll find the relative-to-vtable-slot is nicer,
easy enough to change).
David Blaikie 1 år sedan
förälder
incheckning
8847178242

+ 62 - 0
toolchain/lower/file_context.cpp

@@ -68,6 +68,10 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
     functions_[id.index] = BuildFunctionDecl(id);
   }
 
+  for (const auto& class_info : sem_ir_->classes().array_ref()) {
+    BuildVtable(class_info);
+  }
+
   // Specific functions are lowered when we emit a reference to them.
   specific_functions_.resize(sem_ir_->specifics().size());
 
@@ -703,4 +707,62 @@ auto FileContext::GetLocForDI(SemIR::InstId inst_id) -> LocForDI {
   }
 }
 
+auto FileContext::BuildVtable(const SemIR::Class& class_info) -> void {
+  // Bail out if this class is not dynamic (this will account for classes that
+  // are declared-and-not-defined (including extern declarations) as well).
+  if (!class_info.is_dynamic) {
+    return;
+  }
+
+  auto first_owning_decl_loc =
+      sem_ir().insts().GetLocId(class_info.first_owning_decl_id);
+  if (first_owning_decl_loc.is_import_ir_inst_id()) {
+    return;
+  }
+
+  auto canonical_vtable_id =
+      sem_ir().constant_values().GetConstantInstId(class_info.vtable_id);
+  if (canonical_vtable_id == SemIR::ErrorInst::SingletonInstId) {
+    return;
+  }
+  auto vtable_inst_block =
+      sem_ir().inst_blocks().Get(sem_ir()
+                                     .insts()
+                                     .GetAs<SemIR::Vtable>(canonical_vtable_id)
+                                     .virtual_functions_id);
+
+  auto* entry_type = llvm::IntegerType::getInt32Ty(llvm_context());
+  auto* table_type = llvm::ArrayType::get(entry_type, vtable_inst_block.size());
+
+  Mangler m(*this);
+  std::string mangled_name = m.MangleVTable(class_info);
+
+  auto* llvm_vtable = new llvm::GlobalVariable(
+      llvm_module(), table_type, /*isConstant=*/true,
+      llvm::GlobalValue::ExternalLinkage, nullptr, mangled_name);
+
+  auto* i32_type = llvm::IntegerType::getInt32Ty(llvm_context());
+  auto* i64_type = llvm::IntegerType::getInt64Ty(llvm_context());
+  auto* vtable_const_int =
+      llvm::ConstantExpr::getPtrToInt(llvm_vtable, i64_type);
+
+  llvm::SmallVector<llvm::Constant*> vfuncs;
+  vfuncs.reserve(vtable_inst_block.size());
+
+  for (auto fn_decl_id : vtable_inst_block) {
+    auto fn_decl = GetCalleeFunction(sem_ir(), fn_decl_id);
+    vfuncs.push_back(llvm::ConstantExpr::getTrunc(
+        llvm::ConstantExpr::getSub(
+            llvm::ConstantExpr::getPtrToInt(
+                GetOrCreateFunction(fn_decl.function_id,
+                                    SemIR::SpecificId::None),
+                i64_type),
+            vtable_const_int),
+        i32_type));
+  }
+
+  llvm_vtable->setInitializer(llvm::ConstantArray::get(table_type, vfuncs));
+  llvm_vtable->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global);
+}
+
 }  // namespace Carbon::Lower

+ 2 - 0
toolchain/lower/file_context.h

@@ -147,6 +147,8 @@ class FileContext {
   auto BuildGlobalVariableDecl(SemIR::VarStorage var_storage)
       -> llvm::GlobalVariable*;
 
+  auto BuildVtable(const SemIR::Class& class_info) -> void;
+
   // State for building the LLVM IR.
   llvm::LLVMContext* llvm_context_;
   std::unique_ptr<llvm::Module> llvm_module_;

+ 13 - 0
toolchain/lower/mangler.cpp

@@ -172,4 +172,17 @@ auto Mangler::MangleCppClang(const clang::NamedDecl* decl) -> std::string {
   return cpp_mangled_name.TakeStr();
 }
 
+auto Mangler::MangleVTable(const SemIR::Class& class_info) -> std::string {
+  RawStringOstream os;
+  os << "_C";
+
+  os << names().GetAsStringIfIdentifier(class_info.name_id);
+
+  MangleInverseQualifiedNameScope(os, class_info.parent_scope_id);
+
+  os << ".$vtable";
+
+  return os.TakeStr();
+}
+
 }  // namespace Carbon::Lower

+ 4 - 0
toolchain/lower/mangler.h

@@ -34,6 +34,10 @@ class Mangler {
   auto Mangle(SemIR::FunctionId function_id, SemIR::SpecificId specific_id)
       -> std::string;
 
+  // Produce a deterministically unique mangled name for the specified class's
+  // vtable.
+  auto MangleVTable(const SemIR::Class& class_info) -> std::string;
+
  private:
   // Mangle this qualified name with inner scope first, working outwards. This
   // may reduce the incidence of common prefixes in the name mangling. (i.e.:

+ 8 - 0
toolchain/lower/testdata/class/virtual.carbon

@@ -77,6 +77,9 @@ fn Use() {
 // CHECK:STDOUT: ; ModuleID = 'classes.carbon'
 // CHECK:STDOUT: source_filename = "classes.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @"_CIntermediate.Classes.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CFn.Intermediate.Classes to i64), i64 ptrtoint (ptr @"_CIntermediate.Classes.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @"_CDerived.Classes.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CFn.Derived.Classes to i64), i64 ptrtoint (ptr @"_CDerived.Classes.$vtable" to i64)) to i32)]
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CFn.Intermediate.Classes(ptr %self) !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void, !dbg !7
@@ -148,6 +151,8 @@ fn Use() {
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @"_CBase.MemberInit.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CFn.Base.MemberInit to i64), i64 ptrtoint (ptr @"_CBase.MemberInit.$vtable" to i64)) to i32)]
+// CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CFn.Base.MemberInit(ptr %self) !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   ret void, !dbg !7
@@ -206,6 +211,9 @@ fn Use() {
 // CHECK:STDOUT: ; ModuleID = 'member_brace_init.carbon'
 // CHECK:STDOUT: source_filename = "member_brace_init.carbon"
 // CHECK:STDOUT:
+// CHECK:STDOUT: @"_CBase.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CBase.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT: @"_CDerived.Main.$vtable" = unnamed_addr constant [1 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @_CF.Base.Main to i64), i64 ptrtoint (ptr @"_CDerived.Main.$vtable" to i64)) to i32)]
+// CHECK:STDOUT:
 // CHECK:STDOUT: declare void @_CF.Base.Main(ptr)
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CUse.Main() !dbg !4 {