فهرست منبع

Initial, very rough lowering for calls to specific functions and specific function declarations. (#4399)

Generate declarations of specific functions on demand. Definitions are
not emitted yet, and I'm using a temporary, known-broken scheme for name
mangling.
Richard Smith 1 سال پیش
والد
کامیت
851ef2c517

+ 2 - 2
toolchain/lower/constant.cpp

@@ -215,9 +215,9 @@ static auto EmitAsConstant(ConstantContext& context, SemIR::Namespace inst)
   return context.GetUnusedConstant(inst.type_id);
   return context.GetUnusedConstant(inst.type_id);
 }
 }
 
 
-static auto EmitAsConstant(ConstantContext& /*context*/,
+static auto EmitAsConstant(ConstantContext& context,
                            SemIR::SpecificFunction inst) -> llvm::Constant* {
                            SemIR::SpecificFunction inst) -> llvm::Constant* {
-  CARBON_FATAL("TODO: Add support: {0}", inst);
+  return context.GetUnusedConstant(inst.type_id);
 }
 }
 
 
 static auto EmitAsConstant(ConstantContext& /*context*/,
 static auto EmitAsConstant(ConstantContext& /*context*/,

+ 32 - 13
toolchain/lower/file_context.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/file.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/function.h"
+#include "toolchain/sem_ir/generic.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/typed_insts.h"
 #include "toolchain/sem_ir/typed_insts.h"
@@ -60,6 +61,9 @@ auto FileContext::Run() -> std::unique_ptr<llvm::Module> {
     functions_[i] = BuildFunctionDecl(SemIR::FunctionId(i));
     functions_[i] = BuildFunctionDecl(SemIR::FunctionId(i));
   }
   }
 
 
+  // Specific functions are lowered when we emit a reference to them.
+  specific_functions_.resize(sem_ir_->specifics().size());
+
   // Lower global variable declarations.
   // Lower global variable declarations.
   for (auto inst_id :
   for (auto inst_id :
        sem_ir().inst_blocks().Get(sem_ir().top_inst_block_id())) {
        sem_ir().inst_blocks().Get(sem_ir().top_inst_block_id())) {
@@ -161,16 +165,33 @@ auto FileContext::GetGlobal(SemIR::InstId inst_id) -> llvm::Value* {
                sem_ir().insts().Get(inst_id));
                sem_ir().insts().Get(inst_id));
 }
 }
 
 
-auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id)
+auto FileContext::GetOrCreateFunction(SemIR::FunctionId function_id,
+                                      SemIR::SpecificId specific_id)
+    -> llvm::Function* {
+  // Non-generic functions are declared eagerly.
+  if (!specific_id.is_valid()) {
+    return GetFunction(function_id);
+  }
+
+  if (auto* result = specific_functions_[specific_id.index]) {
+    return result;
+  }
+
+  auto* result = BuildFunctionDecl(function_id, specific_id);
+  // TODO: Add this function to a list of specific functions whose definitions
+  // we need to emit.
+  specific_functions_[specific_id.index] = result;
+  return result;
+}
+
+auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
+                                    SemIR::SpecificId specific_id)
     -> llvm::Function* {
     -> llvm::Function* {
   const auto& function = sem_ir().functions().Get(function_id);
   const auto& function = sem_ir().functions().Get(function_id);
 
 
-  // Don't lower generic functions or associated functions.
-  // TODO: Associated functions have `Self` in scope so should be treated as
-  // generic functions.
-  if (function.generic_id.is_valid() ||
-      sem_ir().insts().Is<SemIR::InterfaceDecl>(
-          sem_ir().name_scopes().Get(function.parent_scope_id).inst_id)) {
+  // Don't lower generic functions. Note that associated functions in interfaces
+  // have `Self` in scope, so are implicitly generic functions.
+  if (function.generic_id.is_valid() && !specific_id.is_valid()) {
     return nullptr;
     return nullptr;
   }
   }
 
 
@@ -182,9 +203,6 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id)
   // TODO: Consider tracking whether the function has been used, and only
   // TODO: Consider tracking whether the function has been used, and only
   // lowering it if it's needed.
   // lowering it if it's needed.
 
 
-  // TODO: Pass in a specific ID for generic functions.
-  const auto specific_id = SemIR::SpecificId::Invalid;
-
   const auto return_info =
   const auto return_info =
       SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id);
       SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id);
   CARBON_CHECK(return_info.is_valid(), "Should not lower invalid functions.");
   CARBON_CHECK(return_info.is_valid(), "Should not lower invalid functions.");
@@ -218,8 +236,9 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id)
     if (!param_info.inst.runtime_index.is_valid()) {
     if (!param_info.inst.runtime_index.is_valid()) {
       continue;
       continue;
     }
     }
-    switch (auto value_rep =
-                SemIR::ValueRepr::ForType(sem_ir(), param_info.inst.type_id);
+    auto param_type_id = SemIR::GetTypeInSpecific(sem_ir(), specific_id,
+                                                  param_info.inst.type_id);
+    switch (auto value_rep = SemIR::ValueRepr::ForType(sem_ir(), param_type_id);
             value_rep.kind) {
             value_rep.kind) {
       case SemIR::ValueRepr::Unknown:
       case SemIR::ValueRepr::Unknown:
         CARBON_FATAL("Incomplete parameter type lowering function declaration");
         CARBON_FATAL("Incomplete parameter type lowering function declaration");
@@ -244,7 +263,7 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id)
           : llvm::Type::getVoidTy(llvm_context());
           : llvm::Type::getVoidTy(llvm_context());
 
 
   Mangler m(*this);
   Mangler m(*this);
-  std::string mangled_name = m.Mangle(function_id);
+  std::string mangled_name = m.Mangle(function_id, specific_id);
 
 
   llvm::FunctionType* function_type = llvm::FunctionType::get(
   llvm::FunctionType* function_type = llvm::FunctionType::get(
       function_return_type, param_types, /*isVarArg=*/false);
       function_return_type, param_types, /*isVarArg=*/false);

+ 17 - 4
toolchain/lower/file_context.h

@@ -47,6 +47,10 @@ class FileContext {
     return functions_[function_id.index];
     return functions_[function_id.index];
   }
   }
 
 
+  // Gets a or creates callable's function. Returns nullptr for a builtin.
+  auto GetOrCreateFunction(SemIR::FunctionId function_id,
+                           SemIR::SpecificId specific_id) -> llvm::Function*;
+
   // Returns a lowered type for the given type_id.
   // Returns a lowered type for the given type_id.
   auto GetType(SemIR::TypeId type_id) -> llvm::Type* {
   auto GetType(SemIR::TypeId type_id) -> llvm::Type* {
     // InvalidType should not be passed in.
     // InvalidType should not be passed in.
@@ -86,7 +90,9 @@ class FileContext {
  private:
  private:
   // Builds the declaration for the given function, which should then be cached
   // Builds the declaration for the given function, which should then be cached
   // by the caller.
   // by the caller.
-  auto BuildFunctionDecl(SemIR::FunctionId function_id) -> llvm::Function*;
+  auto BuildFunctionDecl(SemIR::FunctionId function_id,
+                         SemIR::SpecificId specific_id =
+                             SemIR::SpecificId::Invalid) -> llvm::Function*;
 
 
   // Builds the definition for the given function. If the function is only a
   // Builds the definition for the given function. If the function is only a
   // declaration with no definition, does nothing.
   // declaration with no definition, does nothing.
@@ -130,18 +136,25 @@ class FileContext {
 
 
   // Maps callables to lowered functions. SemIR treats callables as the
   // Maps callables to lowered functions. SemIR treats callables as the
   // canonical form of a function, so lowering needs to do the same.
   // canonical form of a function, so lowering needs to do the same.
-  // We resize this directly to the (often large) correct size.
+  // Vector indexes correspond to `FunctionId` indexes. We resize this directly
+  // to the correct size.
   llvm::SmallVector<llvm::Function*, 0> functions_;
   llvm::SmallVector<llvm::Function*, 0> functions_;
 
 
+  // Maps specific callables to lowered functions. Vector indexes correspond to
+  // `SpecificId` indexes. We resize this directly to the correct size.
+  llvm::SmallVector<llvm::Function*, 0> specific_functions_;
+
   // Provides lowered versions of types.
   // Provides lowered versions of types.
-  // We resize this directly to the (often large) correct size.
+  // Vector indexes correspond to `TypeId` indexes for non-symbolic types. We
+  // resize this directly to the (often large) correct size.
   llvm::SmallVector<llvm::Type*, 0> types_;
   llvm::SmallVector<llvm::Type*, 0> types_;
 
 
   // Lowered version of the builtin type `type`.
   // Lowered version of the builtin type `type`.
   llvm::StructType* type_type_ = nullptr;
   llvm::StructType* type_type_ = nullptr;
 
 
   // Maps constants to their lowered values.
   // Maps constants to their lowered values.
-  // We resize this directly to the (often large) correct size.
+  // Vector indexes correspond to `InstId` indexes for constant instructions. We
+  // resize this directly to the (often large) correct size.
   llvm::SmallVector<llvm::Constant*, 0> constants_;
   llvm::SmallVector<llvm::Constant*, 0> constants_;
 
 
   // Maps global variables to their lowered variant.
   // Maps global variables to their lowered variant.

+ 6 - 0
toolchain/lower/function_context.h

@@ -72,6 +72,12 @@ class FunctionContext {
     return file_context_->GetFunction(function_id);
     return file_context_->GetFunction(function_id);
   }
   }
 
 
+  // Gets or creates a callable's function.
+  auto GetOrCreateFunction(SemIR::FunctionId function_id,
+                           SemIR::SpecificId specific_id) -> llvm::Function* {
+    return file_context_->GetOrCreateFunction(function_id, specific_id);
+  }
+
   // Returns a lowered type for the given type_id.
   // Returns a lowered type for the given type_id.
   auto GetType(SemIR::TypeId type_id) -> llvm::Type* {
   auto GetType(SemIR::TypeId type_id) -> llvm::Type* {
     return file_context_->GetType(type_id);
     return file_context_->GetType(type_id);

+ 2 - 1
toolchain/lower/handle_call.cpp

@@ -308,7 +308,8 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
     return;
     return;
   }
   }
 
 
-  auto* callee = context.GetFunction(callee_function.function_id);
+  auto* callee = context.GetOrCreateFunction(
+      callee_function.function_id, callee_function.resolved_specific_id);
 
 
   std::vector<llvm::Value*> args;
   std::vector<llvm::Value*> args;
 
 

+ 9 - 3
toolchain/lower/mangler.cpp

@@ -97,11 +97,11 @@ auto Mangler::MangleInverseQualifiedNameScope(llvm::raw_ostream& os,
   }
   }
 }
 }
 
 
-auto Mangler::Mangle(SemIR::FunctionId function_id) -> std::string {
-  // FIXME: Add support for generic entities.
-
+auto Mangler::Mangle(SemIR::FunctionId function_id,
+                     SemIR::SpecificId specific_id) -> std::string {
   const auto& function = sem_ir().functions().Get(function_id);
   const auto& function = sem_ir().functions().Get(function_id);
   if (SemIR::IsEntryPoint(sem_ir(), function_id)) {
   if (SemIR::IsEntryPoint(sem_ir(), function_id)) {
+    CARBON_CHECK(!specific_id.is_valid(), "entry point should not be generic");
     return "main";
     return "main";
   }
   }
   std::string result;
   std::string result;
@@ -112,6 +112,12 @@ auto Mangler::Mangle(SemIR::FunctionId function_id) -> std::string {
 
 
   MangleInverseQualifiedNameScope(os, function.parent_scope_id);
   MangleInverseQualifiedNameScope(os, function.parent_scope_id);
 
 
+  // TODO: Add proper support for generic entities. The ID we emit here will not
+  // be consistent across object files.
+  if (specific_id.is_valid()) {
+    os << "." << specific_id.index;
+  }
+
   return os.str();
   return os.str();
 }
 }
 
 

+ 3 - 2
toolchain/lower/mangler.h

@@ -23,8 +23,9 @@ class Mangler {
   explicit Mangler(FileContext& file_context) : file_context_(file_context) {}
   explicit Mangler(FileContext& file_context) : file_context_(file_context) {}
 
 
   // Produce a deterministically unique mangled name for the function specified
   // Produce a deterministically unique mangled name for the function specified
-  // by `function_id`.
-  auto Mangle(SemIR::FunctionId function_id) -> std::string;
+  // by `function_id` and `specific_id`.
+  auto Mangle(SemIR::FunctionId function_id, SemIR::SpecificId specific_id)
+      -> std::string;
 
 
  private:
  private:
   // Mangle this qualified name with inner scope first, working outwards. This
   // Mangle this qualified name with inner scope first, working outwards. This

+ 89 - 0
toolchain/lower/testdata/function/generic/call.carbon

@@ -0,0 +1,89 @@
+// 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
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/function/generic/call.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/function/generic/call.carbon
+
+fn F[T:! type](x: T) {
+}
+
+class C {}
+class D {}
+
+fn G() {
+  var c: C = {};
+  var d: D = {};
+  var n: i32 = 0;
+
+  F(c);
+  F(d);
+  F(n);
+  F(i32);
+}
+
+// CHECK:STDOUT: ; ModuleID = 'call.carbon'
+// CHECK:STDOUT: source_filename = "call.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: %type = type {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: @struct.1.loc18_16 = internal constant {} zeroinitializer
+// CHECK:STDOUT: @struct.2.loc19_16 = internal constant {} zeroinitializer
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @_CG.Main() !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %c.var = alloca {}, align 8, !dbg !7
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %c.var, ptr align 1 @struct.1.loc18_16, i64 0, i1 false), !dbg !8
+// CHECK:STDOUT:   %d.var = alloca {}, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 1 %d.var, ptr align 1 @struct.2.loc19_16, i64 0, i1 false), !dbg !10
+// CHECK:STDOUT:   %n.var = alloca i32, align 4, !dbg !11
+// CHECK:STDOUT:   store i32 0, ptr %n.var, align 4, !dbg !12
+// CHECK:STDOUT:   call void @_CF.Main.1(ptr %c.var), !dbg !13
+// CHECK:STDOUT:   call void @_CF.Main.2(ptr %d.var), !dbg !14
+// CHECK:STDOUT:   %.loc24_5 = load i32, ptr %n.var, align 4, !dbg !15
+// CHECK:STDOUT:   call void @_CF.Main.3(i32 %.loc24_5), !dbg !16
+// CHECK:STDOUT:   call void @_CF.Main.4(%type zeroinitializer), !dbg !17
+// CHECK:STDOUT:   ret void, !dbg !18
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
+// CHECK:STDOUT: declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main.1(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main.2(ptr)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main.3(i32)
+// CHECK:STDOUT:
+// CHECK:STDOUT: declare void @_CF.Main.4(%type)
+// CHECK:STDOUT:
+// CHECK:STDOUT: ; uselistorder directives
+// CHECK:STDOUT: uselistorder ptr @llvm.memcpy.p0.p0.i64, { 1, 0 }
+// CHECK:STDOUT:
+// CHECK:STDOUT: attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
+// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
+// CHECK:STDOUT:
+// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
+// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
+// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+// CHECK:STDOUT: !3 = !DIFile(filename: "call.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "G", linkageName: "_CG.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 18, column: 7, scope: !4)
+// CHECK:STDOUT: !8 = !DILocation(line: 18, column: 3, scope: !4)
+// CHECK:STDOUT: !9 = !DILocation(line: 19, column: 7, scope: !4)
+// CHECK:STDOUT: !10 = !DILocation(line: 19, column: 3, scope: !4)
+// CHECK:STDOUT: !11 = !DILocation(line: 20, column: 7, scope: !4)
+// CHECK:STDOUT: !12 = !DILocation(line: 20, column: 3, scope: !4)
+// CHECK:STDOUT: !13 = !DILocation(line: 22, column: 3, scope: !4)
+// CHECK:STDOUT: !14 = !DILocation(line: 23, column: 3, scope: !4)
+// CHECK:STDOUT: !15 = !DILocation(line: 24, column: 5, scope: !4)
+// CHECK:STDOUT: !16 = !DILocation(line: 24, column: 3, scope: !4)
+// CHECK:STDOUT: !17 = !DILocation(line: 25, column: 3, scope: !4)
+// CHECK:STDOUT: !18 = !DILocation(line: 17, column: 1, scope: !4)