diff --git a/toolchain/lower/constant.cpp b/toolchain/lower/constant.cpp index 43694e15d320..06bb4c335136 100644 --- a/toolchain/lower/constant.cpp +++ b/toolchain/lower/constant.cpp @@ -215,9 +215,9 @@ static auto EmitAsConstant(ConstantContext& context, SemIR::Namespace inst) return context.GetUnusedConstant(inst.type_id); } -static auto EmitAsConstant(ConstantContext& /*context*/, +static auto EmitAsConstant(ConstantContext& context, SemIR::SpecificFunction inst) -> llvm::Constant* { - CARBON_FATAL("TODO: Add support: {0}", inst); + return context.GetUnusedConstant(inst.type_id); } static auto EmitAsConstant(ConstantContext& /*context*/, diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index f6c2320cf3b0..3c333b803e96 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -15,6 +15,7 @@ #include "toolchain/sem_ir/entry_point.h" #include "toolchain/sem_ir/file.h" #include "toolchain/sem_ir/function.h" +#include "toolchain/sem_ir/generic.h" #include "toolchain/sem_ir/ids.h" #include "toolchain/sem_ir/inst.h" #include "toolchain/sem_ir/typed_insts.h" @@ -60,6 +61,9 @@ auto FileContext::Run() -> std::unique_ptr { 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. for (auto inst_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)); } -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* { 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( - 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; } @@ -182,9 +203,6 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) // TODO: Consider tracking whether the function has been used, and only // 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 = SemIR::ReturnTypeInfo::ForFunction(sem_ir(), function, specific_id); 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()) { 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) { case SemIR::ValueRepr::Unknown: CARBON_FATAL("Incomplete parameter type lowering function declaration"); @@ -244,7 +263,7 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id) : llvm::Type::getVoidTy(llvm_context()); 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( function_return_type, param_types, /*isVarArg=*/false); diff --git a/toolchain/lower/file_context.h b/toolchain/lower/file_context.h index 644602ef4086..0c087d8f7337 100644 --- a/toolchain/lower/file_context.h +++ b/toolchain/lower/file_context.h @@ -47,6 +47,10 @@ class FileContext { 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. auto GetType(SemIR::TypeId type_id) -> llvm::Type* { // InvalidType should not be passed in. @@ -86,7 +90,9 @@ class FileContext { private: // Builds the declaration for the given function, which should then be cached // 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 // declaration with no definition, does nothing. @@ -130,18 +136,25 @@ class FileContext { // Maps callables to lowered functions. SemIR treats callables as the // 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 functions_; + // Maps specific callables to lowered functions. Vector indexes correspond to + // `SpecificId` indexes. We resize this directly to the correct size. + llvm::SmallVector specific_functions_; + // 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 types_; // Lowered version of the builtin type `type`. llvm::StructType* type_type_ = nullptr; // 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 constants_; // Maps global variables to their lowered variant. diff --git a/toolchain/lower/function_context.h b/toolchain/lower/function_context.h index 8bdd6d73bf49..b5213d8bea1d 100644 --- a/toolchain/lower/function_context.h +++ b/toolchain/lower/function_context.h @@ -72,6 +72,12 @@ class FunctionContext { 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. auto GetType(SemIR::TypeId type_id) -> llvm::Type* { return file_context_->GetType(type_id); diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index ab5ca0c259db..769e81fd57ed 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -308,7 +308,8 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id, return; } - auto* callee = context.GetFunction(callee_function.function_id); + auto* callee = context.GetOrCreateFunction( + callee_function.function_id, callee_function.resolved_specific_id); std::vector args; diff --git a/toolchain/lower/mangler.cpp b/toolchain/lower/mangler.cpp index 054541649cfa..3445332ec88e 100644 --- a/toolchain/lower/mangler.cpp +++ b/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); if (SemIR::IsEntryPoint(sem_ir(), function_id)) { + CARBON_CHECK(!specific_id.is_valid(), "entry point should not be generic"); return "main"; } std::string result; @@ -112,6 +112,12 @@ auto Mangler::Mangle(SemIR::FunctionId function_id) -> std::string { 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(); } diff --git a/toolchain/lower/mangler.h b/toolchain/lower/mangler.h index aef335174546..377b741a5be6 100644 --- a/toolchain/lower/mangler.h +++ b/toolchain/lower/mangler.h @@ -23,8 +23,9 @@ class Mangler { explicit Mangler(FileContext& file_context) : file_context_(file_context) {} // 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: // Mangle this qualified name with inner scope first, working outwards. This diff --git a/toolchain/lower/testdata/function/generic/call.carbon b/toolchain/lower/testdata/function/generic/call.carbon new file mode 100644 index 000000000000..2c843e594b16 --- /dev/null +++ b/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)