mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 20:11:12 +01:00
Add vtable pointers to class layout (#4407)
A small step to virtual functions - adding vtable pointers to the
layout, but not initializing or otherwise using them at this stage.
A few open design questions I'd love feedback on:
* Is this the right/good enough SemIR representation for now? This patch
adds a `is_dynamic` attribute to `SemIR::Class` and populates/flags it
based on the flag of the base class, or if any virtual function is
declared in the class (or, at least that's my intent). Some other
options include:
* Each `Class` could store a `ClassId` (or `TypeId`?) of the (possibly
indirect, possibly self) base class that is the first one that is
dynamic/has a vtable pointer
* Could make the property narrower, like `has vtable pointer` and have
it `true` only on the type that introduces the vtable - then derived
classes would have to walk their base classes to check if they're the
one that needs to define the vtable pointer or not
* Should the vtable be the first element in the type? If there's a
non-dynamic base type, we could have a layout that's `{<non-dynamic base
type>, vtable ptr, <derived members>}`? Derived types would still be
able to uniquely identify where their vtable pointer is just fine... -
and the vtable pointer is, in a sense, a member of that intermediate
type, so it does seem a bit strange to force it to the front - but I
guess it's probably more efficient in some ways?
Open to any other suggestions/advice/thoughts on the direction, etc.
---------
Co-authored-by: Richard Smith <richard@metafoo.co.uk>
This commit is contained in:
co-authored by
Richard Smith
parent
3c58fb7ec5
commit
dfed743de2
@@ -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<ValueT> values) -> void {
|
||||
CARBON_CHECK(!array_offsets_.empty(),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<SemIR::ClassType>(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<SemIR::BaseDecl>(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<SemIR::StructTypeField>(
|
||||
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<SemIR::CompleteTypeWitness>(
|
||||
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<Parse::NodeKind::ClassDefinitionStart>();
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -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<SemIR::ClassDecl>()) {
|
||||
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`.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<invalid>, 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:
|
||||
|
||||
+4
-1
@@ -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 <vtable> [template]
|
||||
// CHECK:STDOUT: %.5: type = struct_type {.<vptr>: %.4} [template]
|
||||
// CHECK:STDOUT: %.6: <witness> = 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: <witness> = complete_type_witness %.1 [template = constants.%.2]
|
||||
// CHECK:STDOUT: %.loc92: <witness> = complete_type_witness %.5 [template = constants.%.6]
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: !members:
|
||||
// CHECK:STDOUT: .Self = constants.%AbstractWithDefinition
|
||||
|
||||
+170
-5
@@ -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: <witness> = complete_type_witness %.2 [template]
|
||||
// CHECK:STDOUT: %.2: type = ptr_type <vtable> [template]
|
||||
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
|
||||
// CHECK:STDOUT: %.4: <witness> = 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: <witness> = complete_type_witness %.2 [template = constants.%.3]
|
||||
// CHECK:STDOUT: %.loc8: <witness> = 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: <witness> = complete_type_witness %.2 [template = constants.%.3]
|
||||
// CHECK:STDOUT: %.loc16: <witness> = 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 <vtable> [template]
|
||||
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
|
||||
// CHECK:STDOUT: %.4: <witness> = 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 {.<vptr>: %.2, .base: %Base} [template]
|
||||
// CHECK:STDOUT: %.8: <witness> = complete_type_witness %.7 [template]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
// CHECK:STDOUT: %Core: <namespace> = 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> = 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> = 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: <namespace> = 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: <witness> = 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 <vtable> [template]
|
||||
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
|
||||
// CHECK:STDOUT: %.4: <witness> = 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> = 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> = 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> = 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: <namespace> = 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, <error>
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+69
@@ -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)
|
||||
@@ -79,6 +79,9 @@ CARBON_SEM_IR_BUILTIN_INST_KIND(NamespaceType, "<namespace>")
|
||||
// The type of witnesses.
|
||||
CARBON_SEM_IR_BUILTIN_INST_KIND(WitnessType, "<witness>")
|
||||
|
||||
// The type of virtual function tables
|
||||
CARBON_SEM_IR_BUILTIN_INST_KIND(VtableType, "<vtable>")
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -563,6 +563,8 @@ struct NameId : public IdBase, public Printable<NameId> {
|
||||
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<NameScopeId> {
|
||||
|
||||
@@ -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" : "<vptr>";
|
||||
default:
|
||||
CARBON_FATAL("Unknown special name");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user