diff --git a/common/array_stack.h b/common/array_stack.h index c39bccf1d8cf..4aee5ec8a88f 100644 --- a/common/array_stack.h +++ b/common/array_stack.h @@ -63,10 +63,17 @@ class ArrayStack { // Appends a value to the top array on the stack. auto AppendToTop(ValueT value) -> void { CARBON_CHECK(!array_offsets_.empty(), - "Must call PushArray before PushValue."); + "Must call PushArray before AppendToTop."); values_.push_back(value); } + // Prepends a value to the top array on the stack. + auto PrependToTop(ValueT value) -> void { + CARBON_CHECK(!array_offsets_.empty(), + "Must call PushArray before PrependToTop."); + values_.insert(values_.begin() + array_offsets_.back(), value); + } + // Adds multiple values to the top array on the stack. auto AppendToTop(llvm::ArrayRef values) -> void { CARBON_CHECK(!array_offsets_.empty(), diff --git a/toolchain/check/context.cpp b/toolchain/check/context.cpp index 82c4f82a2774..291be02761ca 100644 --- a/toolchain/check/context.cpp +++ b/toolchain/check/context.cpp @@ -931,6 +931,7 @@ class TypeCompleter { case SemIR::BuiltinInstKind::BoundMethodType: case SemIR::BuiltinInstKind::WitnessType: case SemIR::BuiltinInstKind::SpecificFunctionType: + case SemIR::BuiltinInstKind::VtableType: return MakeCopyValueRepr(type_id); case SemIR::BuiltinInstKind::StringType: diff --git a/toolchain/check/handle_class.cpp b/toolchain/check/handle_class.cpp index 8d4d63659476..02fe99f24dd6 100644 --- a/toolchain/check/handle_class.cpp +++ b/toolchain/check/handle_class.cpp @@ -542,6 +542,12 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool { .index = SemIR::ElementIndex( context.args_type_info_stack().PeekCurrentBlockContents().size())}); + if (base_info.type_id != SemIR::TypeId::Error) { + auto base_class_info = context.classes().Get( + context.types().GetAs(base_info.type_id).class_id); + class_info.is_dynamic |= base_class_info.is_dynamic; + } + // Add a corresponding field to the object representation of the class. // TODO: Consider whether we want to use `partial T` here. // TODO: Should we diagnose if there are already any fields? @@ -643,14 +649,36 @@ static auto CheckCompleteAdapterClassType(Context& context, // Checks that the specified finished class definition is valid and builds and // returns a corresponding complete type witness instruction. static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id, - SemIR::ClassId class_id, - SemIR::InstBlockId fields_id) - -> SemIR::InstId { + SemIR::ClassId class_id) -> SemIR::InstId { auto& class_info = context.classes().Get(class_id); if (class_info.adapt_id.is_valid()) { + auto fields_id = context.args_type_info_stack().Pop(); + return CheckCompleteAdapterClassType(context, node_id, class_id, fields_id); } + bool defining_vtable_ptr = class_info.is_dynamic; + if (class_info.base_id.is_valid()) { + auto base_info = context.insts().GetAs(class_info.base_id); + // TODO: If the base class is template dependent, we will need to decide + // whether to add a vptr as part of instantiation. + if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id); + base_class_info && base_class_info->is_dynamic) { + defining_vtable_ptr = false; + } + } + + if (defining_vtable_ptr) { + context.args_type_info_stack().AddFrontInstId( + context.AddInstInNoBlock( + Parse::NodeId::Invalid, + {.name_id = SemIR::NameId::Vptr, + .field_type_id = context.GetPointerType( + context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))})); + } + + auto fields_id = context.args_type_info_stack().Pop(); + return context.AddInst( node_id, {.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType), @@ -659,13 +687,12 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id, auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id) -> bool { - auto fields_id = context.args_type_info_stack().Pop(); auto class_id = context.node_stack().Pop(); // The class type is now fully defined. Compute its object representation. auto complete_type_witness_id = - CheckCompleteClassType(context, node_id, class_id, fields_id); + CheckCompleteClassType(context, node_id, class_id); auto& class_info = context.classes().Get(class_id); class_info.complete_type_witness_id = complete_type_witness_id; diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index c6122b27eb75..55e963a2ee9f 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -253,6 +253,15 @@ static auto BuildFunctionDecl(Context& context, .Case(KeywordModifierSet::Impl, SemIR::Function::VirtualModifier::Impl) .Default(SemIR::Function::VirtualModifier::None); + if (virtual_modifier != SemIR::Function::VirtualModifier::None && + parent_scope_inst) { + if (auto class_decl = parent_scope_inst->TryAs()) { + auto& class_info = context.classes().Get(class_decl->class_id); + CARBON_CHECK(virtual_modifier != SemIR::Function::VirtualModifier::Impl || + class_info.is_dynamic); + class_info.is_dynamic = true; + } + } if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Interface)) { // TODO: Once we are saving the modifiers for a function, add check that // the function may only be defined if it is marked `default` or `final`. diff --git a/toolchain/check/inst_block_stack.h b/toolchain/check/inst_block_stack.h index df718a099a92..fab55c51e4e6 100644 --- a/toolchain/check/inst_block_stack.h +++ b/toolchain/check/inst_block_stack.h @@ -58,6 +58,13 @@ class InstBlockStack { insts_stack_.AppendToTop(inst_id); } + // Adds the given instruction ID to the front of the block at the top of the + // stack. + auto AddFrontInstId(SemIR::InstId inst_id) -> void { + CARBON_CHECK(!empty(), "no current block"); + insts_stack_.PrependToTop(inst_id); + } + // Returns whether the current block is statically reachable. auto is_current_block_reachable() -> bool { return id_stack_.back() != SemIR::InstBlockId::Unreachable; diff --git a/toolchain/check/testdata/basics/builtin_insts.carbon b/toolchain/check/testdata/basics/builtin_insts.carbon index 57e3ac4876f5..9684e5de2ac5 100644 --- a/toolchain/check/testdata/basics/builtin_insts.carbon +++ b/toolchain/check/testdata/basics/builtin_insts.carbon @@ -40,6 +40,7 @@ // CHECK:STDOUT: instSpecificFunctionType: {kind: BuiltinInst, arg0: SpecificFunctionType, type: typeTypeType} // CHECK:STDOUT: instNamespaceType: {kind: BuiltinInst, arg0: NamespaceType, type: typeTypeType} // CHECK:STDOUT: instWitnessType: {kind: BuiltinInst, arg0: WitnessType, type: typeTypeType} +// CHECK:STDOUT: instVtableType: {kind: BuiltinInst, arg0: VtableType, type: typeTypeType} // CHECK:STDOUT: 'inst+0': {kind: Namespace, arg0: name_scope0, arg1: inst, type: type(instNamespaceType)} // CHECK:STDOUT: constant_values: // CHECK:STDOUT: instTypeType: templateConstant(instTypeType) @@ -53,6 +54,7 @@ // CHECK:STDOUT: instSpecificFunctionType: templateConstant(instSpecificFunctionType) // CHECK:STDOUT: instNamespaceType: templateConstant(instNamespaceType) // CHECK:STDOUT: instWitnessType: templateConstant(instWitnessType) +// CHECK:STDOUT: instVtableType: templateConstant(instVtableType) // CHECK:STDOUT: 'inst+0': templateConstant(inst+0) // CHECK:STDOUT: symbolic_constants: {} // CHECK:STDOUT: inst_blocks: diff --git a/toolchain/check/testdata/class/fail_modifiers.carbon b/toolchain/check/testdata/class/fail_modifiers.carbon index 7070dd7d4518..ad9b081eb757 100644 --- a/toolchain/check/testdata/class/fail_modifiers.carbon +++ b/toolchain/check/testdata/class/fail_modifiers.carbon @@ -115,6 +115,9 @@ fn AbstractWithDefinition.G() { // CHECK:STDOUT: %F: %F.type = struct_value () [template] // CHECK:STDOUT: %G.type: type = fn_type @G [template] // CHECK:STDOUT: %G: %G.type = struct_value () [template] +// CHECK:STDOUT: %.4: type = ptr_type [template] +// CHECK:STDOUT: %.5: type = struct_type {.: %.4} [template] +// CHECK:STDOUT: %.6: = complete_type_witness %.5 [template] // CHECK:STDOUT: } // CHECK:STDOUT: // CHECK:STDOUT: imports { @@ -195,7 +198,7 @@ fn AbstractWithDefinition.G() { // CHECK:STDOUT: class @AbstractWithDefinition { // CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} // CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] {} {} -// CHECK:STDOUT: %.loc92: = complete_type_witness %.1 [template = constants.%.2] +// CHECK:STDOUT: %.loc92: = complete_type_witness %.5 [template = constants.%.6] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%AbstractWithDefinition diff --git a/toolchain/check/testdata/class/virtual_modifiers.carbon b/toolchain/check/testdata/class/virtual_modifiers.carbon index 200e93dc412a..14ab62b715d3 100644 --- a/toolchain/check/testdata/class/virtual_modifiers.carbon +++ b/toolchain/check/testdata/class/virtual_modifiers.carbon @@ -8,6 +8,9 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/virtual_modifiers.carbon +// --- modifiers.carbon + +package Modifiers; base class Base { virtual fn H(); @@ -23,7 +26,32 @@ abstract class Abstract { impl fn L(); } -// CHECK:STDOUT: --- virtual_modifiers.carbon +// --- todo_fail_later_base.carbon + +package FailLaterBase; + +import Modifiers; + +base class Derived { + virtual fn F(); + extend base: Modifiers.Base; +} + +// --- fail_todo_init.carbon + +package Init; + +import Modifiers; + +fn F() { + // TODO: The vptr shouldn't be counted for programmer-facing behavior. + // CHECK:STDERR: fail_todo_init.carbon:[[@LINE+3]]:27: error: cannot initialize class with 1 field(s) from struct with 0 field(s). + // CHECK:STDERR: var v: Modifiers.Base = {}; + // CHECK:STDERR: ^~ + var v: Modifiers.Base = {}; +} + +// CHECK:STDOUT: --- modifiers.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { // CHECK:STDOUT: %Base: type = class_type @Base [template] @@ -32,8 +60,9 @@ abstract class Abstract { // CHECK:STDOUT: %H: %H.type = struct_value () [template] // CHECK:STDOUT: %I.type: type = fn_type @I [template] // CHECK:STDOUT: %I: %I.type = struct_value () [template] -// CHECK:STDOUT: %.2: type = struct_type {} [template] -// CHECK:STDOUT: %.3: = complete_type_witness %.2 [template] +// CHECK:STDOUT: %.2: type = ptr_type [template] +// CHECK:STDOUT: %.3: type = struct_type {.: %.2} [template] +// CHECK:STDOUT: %.4: = complete_type_witness %.3 [template] // CHECK:STDOUT: %Abstract: type = class_type @Abstract [template] // CHECK:STDOUT: %J.type: type = fn_type @J [template] // CHECK:STDOUT: %J: %J.type = struct_value () [template] @@ -70,7 +99,7 @@ abstract class Abstract { // CHECK:STDOUT: class @Base { // CHECK:STDOUT: %H.decl: %H.type = fn_decl @H [template = constants.%H] {} {} // CHECK:STDOUT: %I.decl: %I.type = fn_decl @I [template = constants.%I] {} {} -// CHECK:STDOUT: %.loc16: = complete_type_witness %.2 [template = constants.%.3] +// CHECK:STDOUT: %.loc8: = complete_type_witness %.3 [template = constants.%.4] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%Base @@ -82,7 +111,7 @@ abstract class Abstract { // CHECK:STDOUT: %J.decl: %J.type = fn_decl @J [template = constants.%J] {} {} // CHECK:STDOUT: %K.decl: %K.type = fn_decl @K [template = constants.%K] {} {} // CHECK:STDOUT: %L.decl: %L.type = fn_decl @L [template = constants.%L] {} {} -// CHECK:STDOUT: %.loc24: = complete_type_witness %.2 [template = constants.%.3] +// CHECK:STDOUT: %.loc16: = complete_type_witness %.3 [template = constants.%.4] // CHECK:STDOUT: // CHECK:STDOUT: !members: // CHECK:STDOUT: .Self = constants.%Abstract @@ -101,3 +130,139 @@ abstract class Abstract { // CHECK:STDOUT: // CHECK:STDOUT: impl fn @L(); // CHECK:STDOUT: +// CHECK:STDOUT: --- todo_fail_later_base.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %Derived: type = class_type @Derived [template] +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %Base: type = class_type @Base [template] +// CHECK:STDOUT: %.2: type = ptr_type [template] +// CHECK:STDOUT: %.3: type = struct_type {.: %.2} [template] +// CHECK:STDOUT: %.4: = complete_type_witness %.3 [template] +// CHECK:STDOUT: %.5: type = ptr_type %.3 [template] +// CHECK:STDOUT: %.6: type = unbound_element_type %Derived, %Base [template] +// CHECK:STDOUT: %.7: type = struct_type {.: %.2, .base: %Base} [template] +// CHECK:STDOUT: %.8: = complete_type_witness %.7 [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/operators +// CHECK:STDOUT: import Core//prelude/types +// CHECK:STDOUT: import Core//prelude/operators/arithmetic +// CHECK:STDOUT: import Core//prelude/operators/as +// CHECK:STDOUT: import Core//prelude/operators/bitwise +// CHECK:STDOUT: import Core//prelude/operators/comparison +// CHECK:STDOUT: import Core//prelude/types/bool +// CHECK:STDOUT: } +// CHECK:STDOUT: %Modifiers: = namespace file.%Modifiers.import, [template] { +// CHECK:STDOUT: .Base = %import_ref.1 +// CHECK:STDOUT: import Modifiers//default +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.1: type = import_ref Modifiers//default, inst+3, loaded [template = constants.%Base] +// CHECK:STDOUT: %import_ref.2 = import_ref Modifiers//default, inst+4, unloaded +// CHECK:STDOUT: %import_ref.3 = import_ref Modifiers//default, inst+5, unloaded +// CHECK:STDOUT: %import_ref.4 = import_ref Modifiers//default, inst+9, unloaded +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Modifiers = imports.%Modifiers +// CHECK:STDOUT: .Derived = %Derived.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Modifiers.import = import Modifiers +// CHECK:STDOUT: %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Derived { +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} +// CHECK:STDOUT: %Modifiers.ref: = name_ref Modifiers, imports.%Modifiers [template = imports.%Modifiers] +// CHECK:STDOUT: %Base.ref: type = name_ref Base, imports.%import_ref.1 [template = constants.%Base] +// CHECK:STDOUT: %.loc8: %.6 = base_decl %Base, element0 [template] +// CHECK:STDOUT: %.loc9: = complete_type_witness %.7 [template = constants.%.8] +// CHECK:STDOUT: +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = constants.%Derived +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: .base = %.loc8 +// CHECK:STDOUT: extend name_scope4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.2 +// CHECK:STDOUT: .H = imports.%import_ref.3 +// CHECK:STDOUT: .I = imports.%import_ref.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: virtual fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: --- fail_todo_init.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %F.type: type = fn_type @F [template] +// CHECK:STDOUT: %.1: type = tuple_type () [template] +// CHECK:STDOUT: %F: %F.type = struct_value () [template] +// CHECK:STDOUT: %Base: type = class_type @Base [template] +// CHECK:STDOUT: %.2: type = ptr_type [template] +// CHECK:STDOUT: %.3: type = struct_type {.: %.2} [template] +// CHECK:STDOUT: %.4: = complete_type_witness %.3 [template] +// CHECK:STDOUT: %.5: type = ptr_type %.3 [template] +// CHECK:STDOUT: %.6: type = struct_type {} [template] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Core: = namespace file.%Core.import, [template] { +// CHECK:STDOUT: import Core//prelude +// CHECK:STDOUT: import Core//prelude/operators +// CHECK:STDOUT: import Core//prelude/types +// CHECK:STDOUT: import Core//prelude/operators/arithmetic +// CHECK:STDOUT: import Core//prelude/operators/as +// CHECK:STDOUT: import Core//prelude/operators/bitwise +// CHECK:STDOUT: import Core//prelude/operators/comparison +// CHECK:STDOUT: import Core//prelude/types/bool +// CHECK:STDOUT: } +// CHECK:STDOUT: %Modifiers: = namespace file.%Modifiers.import, [template] { +// CHECK:STDOUT: .Base = %import_ref.1 +// CHECK:STDOUT: import Modifiers//default +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref.1: type = import_ref Modifiers//default, inst+3, loaded [template = constants.%Base] +// CHECK:STDOUT: %import_ref.2 = import_ref Modifiers//default, inst+4, unloaded +// CHECK:STDOUT: %import_ref.3 = import_ref Modifiers//default, inst+5, unloaded +// CHECK:STDOUT: %import_ref.4 = import_ref Modifiers//default, inst+9, unloaded +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: package: = namespace [template] { +// CHECK:STDOUT: .Core = imports.%Core +// CHECK:STDOUT: .Modifiers = imports.%Modifiers +// CHECK:STDOUT: .F = %F.decl +// CHECK:STDOUT: } +// CHECK:STDOUT: %Core.import = import Core +// CHECK:STDOUT: %Modifiers.import = import Modifiers +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: class @Base { +// CHECK:STDOUT: !members: +// CHECK:STDOUT: .Self = imports.%import_ref.2 +// CHECK:STDOUT: .H = imports.%import_ref.3 +// CHECK:STDOUT: .I = imports.%import_ref.4 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %Modifiers.ref: = name_ref Modifiers, imports.%Modifiers [template = imports.%Modifiers] +// CHECK:STDOUT: %Base.ref: type = name_ref Base, imports.%import_ref.1 [template = constants.%Base] +// CHECK:STDOUT: %v.var: ref %Base = var v +// CHECK:STDOUT: %v: ref %Base = bind_name v, %v.var +// CHECK:STDOUT: %.loc11: %.6 = struct_literal () +// CHECK:STDOUT: assign %v.var, +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index 7d9b36adab5b..38b5e7fa2d43 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -440,6 +440,8 @@ static auto BuildTypeForInst(FileContext& context, SemIR::BuiltinInst inst) case SemIR::BuiltinInstKind::WitnessType: // Return an empty struct as a placeholder. return llvm::StructType::get(context.llvm_context()); + case SemIR::BuiltinInstKind::VtableType: + return llvm::Type::getVoidTy(context.llvm_context()); } } diff --git a/toolchain/lower/testdata/class/virtual.carbon b/toolchain/lower/testdata/class/virtual.carbon new file mode 100644 index 000000000000..78a1d15f5540 --- /dev/null +++ b/toolchain/lower/testdata/class/virtual.carbon @@ -0,0 +1,69 @@ +// 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/class/virtual.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/virtual.carbon + +base class Base { +} +base class Intermediate { + extend base: Base; + virtual fn Fn(); +} +class Derived { + extend base: Intermediate; + impl fn Fn(); +} + +fn Create() { + var b: Base; + var i: Intermediate; + var d: Derived; +} + +fn Use(v: Intermediate*) { + v->Fn(); +} + +// CHECK:STDOUT: ; ModuleID = 'virtual.carbon' +// CHECK:STDOUT: source_filename = "virtual.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CFn.Intermediate.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @_CFn.Derived.Main() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CCreate.Main() !dbg !4 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: %b.var = alloca {}, align 8, !dbg !7 +// CHECK:STDOUT: %i.var = alloca { ptr, {} }, align 8, !dbg !8 +// CHECK:STDOUT: %d.var = alloca { { ptr, {} } }, align 8, !dbg !9 +// CHECK:STDOUT: ret void, !dbg !10 +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: define void @_CUse.Main(ptr %v) !dbg !11 { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: call void @_CFn.Intermediate.Main(), !dbg !12 +// CHECK:STDOUT: ret void, !dbg !13 +// CHECK:STDOUT: } +// 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: "virtual.carbon", directory: "") +// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Create", linkageName: "_CCreate.Main", scope: null, file: !3, line: 22, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !5 = !DISubroutineType(types: !6) +// CHECK:STDOUT: !6 = !{} +// CHECK:STDOUT: !7 = !DILocation(line: 23, column: 7, scope: !4) +// CHECK:STDOUT: !8 = !DILocation(line: 24, column: 7, scope: !4) +// CHECK:STDOUT: !9 = !DILocation(line: 25, column: 7, scope: !4) +// CHECK:STDOUT: !10 = !DILocation(line: 22, column: 1, scope: !4) +// CHECK:STDOUT: !11 = distinct !DISubprogram(name: "Use", linkageName: "_CUse.Main", scope: null, file: !3, line: 28, type: !5, spFlags: DISPFlagDefinition, unit: !2) +// CHECK:STDOUT: !12 = !DILocation(line: 29, column: 3, scope: !11) +// CHECK:STDOUT: !13 = !DILocation(line: 28, column: 1, scope: !11) diff --git a/toolchain/sem_ir/builtin_inst_kind.def b/toolchain/sem_ir/builtin_inst_kind.def index 57d20ffdd7eb..5e1efa5a7037 100644 --- a/toolchain/sem_ir/builtin_inst_kind.def +++ b/toolchain/sem_ir/builtin_inst_kind.def @@ -79,6 +79,9 @@ CARBON_SEM_IR_BUILTIN_INST_KIND(NamespaceType, "") // The type of witnesses. CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType, "") +// The type of virtual function tables +CARBON_SEM_IR_BUILTIN_INST_KIND(VtableType, "") + // Keep invalid last, so that we can use values as array indices without needing // an invalid entry. CARBON_SEM_IR_BUILTIN_INST_KIND_NAME(Invalid) diff --git a/toolchain/sem_ir/class.h b/toolchain/sem_ir/class.h index 121015a0eb11..12c66629bfcd 100644 --- a/toolchain/sem_ir/class.h +++ b/toolchain/sem_ir/class.h @@ -30,6 +30,9 @@ struct ClassFields { // TODO: The rules here are not yet decided. See #3384. InheritanceKind inheritance_kind; + // Whether this class or any base class has at least one virtual function. + bool is_dynamic = false; + // The following members are set at the `{` of the class definition. // The class scope. diff --git a/toolchain/sem_ir/ids.h b/toolchain/sem_ir/ids.h index 099dccbdd5ab..e1ab5064672d 100644 --- a/toolchain/sem_ir/ids.h +++ b/toolchain/sem_ir/ids.h @@ -563,6 +563,8 @@ struct NameId : public IdBase, public Printable { static const NameId PackageNamespace; // The name of `base`. static const NameId Base; + // The name of `vptr`. + static const NameId Vptr; // The number of non-index (<0) that exist, and will need storage in name // lookup. @@ -615,9 +617,10 @@ constexpr NameId NameId::PeriodSelf = NameId(InvalidIndex - 3); constexpr NameId NameId::ReturnSlot = NameId(InvalidIndex - 4); constexpr NameId NameId::PackageNamespace = NameId(InvalidIndex - 5); constexpr NameId NameId::Base = NameId(InvalidIndex - 6); -constexpr int NameId::NonIndexValueCount = 7; +constexpr NameId NameId::Vptr = NameId(InvalidIndex - 7); +constexpr int NameId::NonIndexValueCount = 8; // Enforce the link between SpecialValueCount and the last special value. -static_assert(NameId::NonIndexValueCount == -NameId::Base.index); +static_assert(NameId::NonIndexValueCount == -NameId::Vptr.index); // The ID of a name scope. struct NameScopeId : public IdBase, public Printable { diff --git a/toolchain/sem_ir/name.cpp b/toolchain/sem_ir/name.cpp index a2dba588c0fa..326d3837ad15 100644 --- a/toolchain/sem_ir/name.cpp +++ b/toolchain/sem_ir/name.cpp @@ -25,6 +25,8 @@ static auto GetSpecialName(NameId name_id, bool for_ir) -> llvm::StringRef { return "package"; case NameId::Base.index: return "base"; + case NameId::Vptr.index: + return for_ir ? "vptr" : ""; default: CARBON_FATAL("Unknown special name"); }