Skip to content

Commit dfed743

Browse files
dwblaikiezygoloid
andauthored
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 <[email protected]>
1 parent 3c58fb7 commit dfed743

File tree

14 files changed

+317
-14
lines changed

14 files changed

+317
-14
lines changed

common/array_stack.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,17 @@ class ArrayStack {
6363
// Appends a value to the top array on the stack.
6464
auto AppendToTop(ValueT value) -> void {
6565
CARBON_CHECK(!array_offsets_.empty(),
66-
"Must call PushArray before PushValue.");
66+
"Must call PushArray before AppendToTop.");
6767
values_.push_back(value);
6868
}
6969

70+
// Prepends a value to the top array on the stack.
71+
auto PrependToTop(ValueT value) -> void {
72+
CARBON_CHECK(!array_offsets_.empty(),
73+
"Must call PushArray before PrependToTop.");
74+
values_.insert(values_.begin() + array_offsets_.back(), value);
75+
}
76+
7077
// Adds multiple values to the top array on the stack.
7178
auto AppendToTop(llvm::ArrayRef<ValueT> values) -> void {
7279
CARBON_CHECK(!array_offsets_.empty(),

toolchain/check/context.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,7 @@ class TypeCompleter {
931931
case SemIR::BuiltinInstKind::BoundMethodType:
932932
case SemIR::BuiltinInstKind::WitnessType:
933933
case SemIR::BuiltinInstKind::SpecificFunctionType:
934+
case SemIR::BuiltinInstKind::VtableType:
934935
return MakeCopyValueRepr(type_id);
935936

936937
case SemIR::BuiltinInstKind::StringType:

toolchain/check/handle_class.cpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,12 @@ auto HandleParseNode(Context& context, Parse::BaseDeclId node_id) -> bool {
542542
.index = SemIR::ElementIndex(
543543
context.args_type_info_stack().PeekCurrentBlockContents().size())});
544544

545+
if (base_info.type_id != SemIR::TypeId::Error) {
546+
auto base_class_info = context.classes().Get(
547+
context.types().GetAs<SemIR::ClassType>(base_info.type_id).class_id);
548+
class_info.is_dynamic |= base_class_info.is_dynamic;
549+
}
550+
545551
// Add a corresponding field to the object representation of the class.
546552
// TODO: Consider whether we want to use `partial T` here.
547553
// TODO: Should we diagnose if there are already any fields?
@@ -643,14 +649,36 @@ static auto CheckCompleteAdapterClassType(Context& context,
643649
// Checks that the specified finished class definition is valid and builds and
644650
// returns a corresponding complete type witness instruction.
645651
static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
646-
SemIR::ClassId class_id,
647-
SemIR::InstBlockId fields_id)
648-
-> SemIR::InstId {
652+
SemIR::ClassId class_id) -> SemIR::InstId {
649653
auto& class_info = context.classes().Get(class_id);
650654
if (class_info.adapt_id.is_valid()) {
655+
auto fields_id = context.args_type_info_stack().Pop();
656+
651657
return CheckCompleteAdapterClassType(context, node_id, class_id, fields_id);
652658
}
653659

660+
bool defining_vtable_ptr = class_info.is_dynamic;
661+
if (class_info.base_id.is_valid()) {
662+
auto base_info = context.insts().GetAs<SemIR::BaseDecl>(class_info.base_id);
663+
// TODO: If the base class is template dependent, we will need to decide
664+
// whether to add a vptr as part of instantiation.
665+
if (auto* base_class_info = TryGetAsClass(context, base_info.base_type_id);
666+
base_class_info && base_class_info->is_dynamic) {
667+
defining_vtable_ptr = false;
668+
}
669+
}
670+
671+
if (defining_vtable_ptr) {
672+
context.args_type_info_stack().AddFrontInstId(
673+
context.AddInstInNoBlock<SemIR::StructTypeField>(
674+
Parse::NodeId::Invalid,
675+
{.name_id = SemIR::NameId::Vptr,
676+
.field_type_id = context.GetPointerType(
677+
context.GetBuiltinType(SemIR::BuiltinInstKind::VtableType))}));
678+
}
679+
680+
auto fields_id = context.args_type_info_stack().Pop();
681+
654682
return context.AddInst<SemIR::CompleteTypeWitness>(
655683
node_id,
656684
{.type_id = context.GetBuiltinType(SemIR::BuiltinInstKind::WitnessType),
@@ -659,13 +687,12 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
659687

660688
auto HandleParseNode(Context& context, Parse::ClassDefinitionId node_id)
661689
-> bool {
662-
auto fields_id = context.args_type_info_stack().Pop();
663690
auto class_id =
664691
context.node_stack().Pop<Parse::NodeKind::ClassDefinitionStart>();
665692

666693
// The class type is now fully defined. Compute its object representation.
667694
auto complete_type_witness_id =
668-
CheckCompleteClassType(context, node_id, class_id, fields_id);
695+
CheckCompleteClassType(context, node_id, class_id);
669696
auto& class_info = context.classes().Get(class_id);
670697
class_info.complete_type_witness_id = complete_type_witness_id;
671698

toolchain/check/handle_function.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,15 @@ static auto BuildFunctionDecl(Context& context,
253253
.Case(KeywordModifierSet::Impl,
254254
SemIR::Function::VirtualModifier::Impl)
255255
.Default(SemIR::Function::VirtualModifier::None);
256+
if (virtual_modifier != SemIR::Function::VirtualModifier::None &&
257+
parent_scope_inst) {
258+
if (auto class_decl = parent_scope_inst->TryAs<SemIR::ClassDecl>()) {
259+
auto& class_info = context.classes().Get(class_decl->class_id);
260+
CARBON_CHECK(virtual_modifier != SemIR::Function::VirtualModifier::Impl ||
261+
class_info.is_dynamic);
262+
class_info.is_dynamic = true;
263+
}
264+
}
256265
if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Interface)) {
257266
// TODO: Once we are saving the modifiers for a function, add check that
258267
// the function may only be defined if it is marked `default` or `final`.

toolchain/check/inst_block_stack.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ class InstBlockStack {
5858
insts_stack_.AppendToTop(inst_id);
5959
}
6060

61+
// Adds the given instruction ID to the front of the block at the top of the
62+
// stack.
63+
auto AddFrontInstId(SemIR::InstId inst_id) -> void {
64+
CARBON_CHECK(!empty(), "no current block");
65+
insts_stack_.PrependToTop(inst_id);
66+
}
67+
6168
// Returns whether the current block is statically reachable.
6269
auto is_current_block_reachable() -> bool {
6370
return id_stack_.back() != SemIR::InstBlockId::Unreachable;

toolchain/check/testdata/basics/builtin_insts.carbon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
// CHECK:STDOUT: instSpecificFunctionType: {kind: BuiltinInst, arg0: SpecificFunctionType, type: typeTypeType}
4141
// CHECK:STDOUT: instNamespaceType: {kind: BuiltinInst, arg0: NamespaceType, type: typeTypeType}
4242
// CHECK:STDOUT: instWitnessType: {kind: BuiltinInst, arg0: WitnessType, type: typeTypeType}
43+
// CHECK:STDOUT: instVtableType: {kind: BuiltinInst, arg0: VtableType, type: typeTypeType}
4344
// CHECK:STDOUT: 'inst+0': {kind: Namespace, arg0: name_scope0, arg1: inst<invalid>, type: type(instNamespaceType)}
4445
// CHECK:STDOUT: constant_values:
4546
// CHECK:STDOUT: instTypeType: templateConstant(instTypeType)
@@ -53,6 +54,7 @@
5354
// CHECK:STDOUT: instSpecificFunctionType: templateConstant(instSpecificFunctionType)
5455
// CHECK:STDOUT: instNamespaceType: templateConstant(instNamespaceType)
5556
// CHECK:STDOUT: instWitnessType: templateConstant(instWitnessType)
57+
// CHECK:STDOUT: instVtableType: templateConstant(instVtableType)
5658
// CHECK:STDOUT: 'inst+0': templateConstant(inst+0)
5759
// CHECK:STDOUT: symbolic_constants: {}
5860
// CHECK:STDOUT: inst_blocks:

toolchain/check/testdata/class/fail_modifiers.carbon

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ fn AbstractWithDefinition.G() {
115115
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
116116
// CHECK:STDOUT: %G.type: type = fn_type @G [template]
117117
// CHECK:STDOUT: %G: %G.type = struct_value () [template]
118+
// CHECK:STDOUT: %.4: type = ptr_type <vtable> [template]
119+
// CHECK:STDOUT: %.5: type = struct_type {.<vptr>: %.4} [template]
120+
// CHECK:STDOUT: %.6: <witness> = complete_type_witness %.5 [template]
118121
// CHECK:STDOUT: }
119122
// CHECK:STDOUT:
120123
// CHECK:STDOUT: imports {
@@ -195,7 +198,7 @@ fn AbstractWithDefinition.G() {
195198
// CHECK:STDOUT: class @AbstractWithDefinition {
196199
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
197200
// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [template = constants.%G] {} {}
198-
// CHECK:STDOUT: %.loc92: <witness> = complete_type_witness %.1 [template = constants.%.2]
201+
// CHECK:STDOUT: %.loc92: <witness> = complete_type_witness %.5 [template = constants.%.6]
199202
// CHECK:STDOUT:
200203
// CHECK:STDOUT: !members:
201204
// CHECK:STDOUT: .Self = constants.%AbstractWithDefinition

toolchain/check/testdata/class/virtual_modifiers.carbon

Lines changed: 170 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// TIP: To dump output, run:
99
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/virtual_modifiers.carbon
1010

11+
// --- modifiers.carbon
12+
13+
package Modifiers;
1114

1215
base class Base {
1316
virtual fn H();
@@ -23,7 +26,32 @@ abstract class Abstract {
2326
impl fn L();
2427
}
2528

26-
// CHECK:STDOUT: --- virtual_modifiers.carbon
29+
// --- todo_fail_later_base.carbon
30+
31+
package FailLaterBase;
32+
33+
import Modifiers;
34+
35+
base class Derived {
36+
virtual fn F();
37+
extend base: Modifiers.Base;
38+
}
39+
40+
// --- fail_todo_init.carbon
41+
42+
package Init;
43+
44+
import Modifiers;
45+
46+
fn F() {
47+
// TODO: The vptr shouldn't be counted for programmer-facing behavior.
48+
// CHECK:STDERR: fail_todo_init.carbon:[[@LINE+3]]:27: error: cannot initialize class with 1 field(s) from struct with 0 field(s).
49+
// CHECK:STDERR: var v: Modifiers.Base = {};
50+
// CHECK:STDERR: ^~
51+
var v: Modifiers.Base = {};
52+
}
53+
54+
// CHECK:STDOUT: --- modifiers.carbon
2755
// CHECK:STDOUT:
2856
// CHECK:STDOUT: constants {
2957
// CHECK:STDOUT: %Base: type = class_type @Base [template]
@@ -32,8 +60,9 @@ abstract class Abstract {
3260
// CHECK:STDOUT: %H: %H.type = struct_value () [template]
3361
// CHECK:STDOUT: %I.type: type = fn_type @I [template]
3462
// CHECK:STDOUT: %I: %I.type = struct_value () [template]
35-
// CHECK:STDOUT: %.2: type = struct_type {} [template]
36-
// CHECK:STDOUT: %.3: <witness> = complete_type_witness %.2 [template]
63+
// CHECK:STDOUT: %.2: type = ptr_type <vtable> [template]
64+
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
65+
// CHECK:STDOUT: %.4: <witness> = complete_type_witness %.3 [template]
3766
// CHECK:STDOUT: %Abstract: type = class_type @Abstract [template]
3867
// CHECK:STDOUT: %J.type: type = fn_type @J [template]
3968
// CHECK:STDOUT: %J: %J.type = struct_value () [template]
@@ -70,7 +99,7 @@ abstract class Abstract {
7099
// CHECK:STDOUT: class @Base {
71100
// CHECK:STDOUT: %H.decl: %H.type = fn_decl @H [template = constants.%H] {} {}
72101
// CHECK:STDOUT: %I.decl: %I.type = fn_decl @I [template = constants.%I] {} {}
73-
// CHECK:STDOUT: %.loc16: <witness> = complete_type_witness %.2 [template = constants.%.3]
102+
// CHECK:STDOUT: %.loc8: <witness> = complete_type_witness %.3 [template = constants.%.4]
74103
// CHECK:STDOUT:
75104
// CHECK:STDOUT: !members:
76105
// CHECK:STDOUT: .Self = constants.%Base
@@ -82,7 +111,7 @@ abstract class Abstract {
82111
// CHECK:STDOUT: %J.decl: %J.type = fn_decl @J [template = constants.%J] {} {}
83112
// CHECK:STDOUT: %K.decl: %K.type = fn_decl @K [template = constants.%K] {} {}
84113
// CHECK:STDOUT: %L.decl: %L.type = fn_decl @L [template = constants.%L] {} {}
85-
// CHECK:STDOUT: %.loc24: <witness> = complete_type_witness %.2 [template = constants.%.3]
114+
// CHECK:STDOUT: %.loc16: <witness> = complete_type_witness %.3 [template = constants.%.4]
86115
// CHECK:STDOUT:
87116
// CHECK:STDOUT: !members:
88117
// CHECK:STDOUT: .Self = constants.%Abstract
@@ -101,3 +130,139 @@ abstract class Abstract {
101130
// CHECK:STDOUT:
102131
// CHECK:STDOUT: impl fn @L();
103132
// CHECK:STDOUT:
133+
// CHECK:STDOUT: --- todo_fail_later_base.carbon
134+
// CHECK:STDOUT:
135+
// CHECK:STDOUT: constants {
136+
// CHECK:STDOUT: %Derived: type = class_type @Derived [template]
137+
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
138+
// CHECK:STDOUT: %.1: type = tuple_type () [template]
139+
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
140+
// CHECK:STDOUT: %Base: type = class_type @Base [template]
141+
// CHECK:STDOUT: %.2: type = ptr_type <vtable> [template]
142+
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
143+
// CHECK:STDOUT: %.4: <witness> = complete_type_witness %.3 [template]
144+
// CHECK:STDOUT: %.5: type = ptr_type %.3 [template]
145+
// CHECK:STDOUT: %.6: type = unbound_element_type %Derived, %Base [template]
146+
// CHECK:STDOUT: %.7: type = struct_type {.<vptr>: %.2, .base: %Base} [template]
147+
// CHECK:STDOUT: %.8: <witness> = complete_type_witness %.7 [template]
148+
// CHECK:STDOUT: }
149+
// CHECK:STDOUT:
150+
// CHECK:STDOUT: imports {
151+
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
152+
// CHECK:STDOUT: import Core//prelude
153+
// CHECK:STDOUT: import Core//prelude/operators
154+
// CHECK:STDOUT: import Core//prelude/types
155+
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
156+
// CHECK:STDOUT: import Core//prelude/operators/as
157+
// CHECK:STDOUT: import Core//prelude/operators/bitwise
158+
// CHECK:STDOUT: import Core//prelude/operators/comparison
159+
// CHECK:STDOUT: import Core//prelude/types/bool
160+
// CHECK:STDOUT: }
161+
// CHECK:STDOUT: %Modifiers: <namespace> = namespace file.%Modifiers.import, [template] {
162+
// CHECK:STDOUT: .Base = %import_ref.1
163+
// CHECK:STDOUT: import Modifiers//default
164+
// CHECK:STDOUT: }
165+
// CHECK:STDOUT: %import_ref.1: type = import_ref Modifiers//default, inst+3, loaded [template = constants.%Base]
166+
// CHECK:STDOUT: %import_ref.2 = import_ref Modifiers//default, inst+4, unloaded
167+
// CHECK:STDOUT: %import_ref.3 = import_ref Modifiers//default, inst+5, unloaded
168+
// CHECK:STDOUT: %import_ref.4 = import_ref Modifiers//default, inst+9, unloaded
169+
// CHECK:STDOUT: }
170+
// CHECK:STDOUT:
171+
// CHECK:STDOUT: file {
172+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
173+
// CHECK:STDOUT: .Core = imports.%Core
174+
// CHECK:STDOUT: .Modifiers = imports.%Modifiers
175+
// CHECK:STDOUT: .Derived = %Derived.decl
176+
// CHECK:STDOUT: }
177+
// CHECK:STDOUT: %Core.import = import Core
178+
// CHECK:STDOUT: %Modifiers.import = import Modifiers
179+
// CHECK:STDOUT: %Derived.decl: type = class_decl @Derived [template = constants.%Derived] {} {}
180+
// CHECK:STDOUT: }
181+
// CHECK:STDOUT:
182+
// CHECK:STDOUT: class @Derived {
183+
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
184+
// CHECK:STDOUT: %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [template = imports.%Modifiers]
185+
// CHECK:STDOUT: %Base.ref: type = name_ref Base, imports.%import_ref.1 [template = constants.%Base]
186+
// CHECK:STDOUT: %.loc8: %.6 = base_decl %Base, element0 [template]
187+
// CHECK:STDOUT: %.loc9: <witness> = complete_type_witness %.7 [template = constants.%.8]
188+
// CHECK:STDOUT:
189+
// CHECK:STDOUT: !members:
190+
// CHECK:STDOUT: .Self = constants.%Derived
191+
// CHECK:STDOUT: .F = %F.decl
192+
// CHECK:STDOUT: .base = %.loc8
193+
// CHECK:STDOUT: extend name_scope4
194+
// CHECK:STDOUT: }
195+
// CHECK:STDOUT:
196+
// CHECK:STDOUT: class @Base {
197+
// CHECK:STDOUT: !members:
198+
// CHECK:STDOUT: .Self = imports.%import_ref.2
199+
// CHECK:STDOUT: .H = imports.%import_ref.3
200+
// CHECK:STDOUT: .I = imports.%import_ref.4
201+
// CHECK:STDOUT: }
202+
// CHECK:STDOUT:
203+
// CHECK:STDOUT: virtual fn @F();
204+
// CHECK:STDOUT:
205+
// CHECK:STDOUT: --- fail_todo_init.carbon
206+
// CHECK:STDOUT:
207+
// CHECK:STDOUT: constants {
208+
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
209+
// CHECK:STDOUT: %.1: type = tuple_type () [template]
210+
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
211+
// CHECK:STDOUT: %Base: type = class_type @Base [template]
212+
// CHECK:STDOUT: %.2: type = ptr_type <vtable> [template]
213+
// CHECK:STDOUT: %.3: type = struct_type {.<vptr>: %.2} [template]
214+
// CHECK:STDOUT: %.4: <witness> = complete_type_witness %.3 [template]
215+
// CHECK:STDOUT: %.5: type = ptr_type %.3 [template]
216+
// CHECK:STDOUT: %.6: type = struct_type {} [template]
217+
// CHECK:STDOUT: }
218+
// CHECK:STDOUT:
219+
// CHECK:STDOUT: imports {
220+
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
221+
// CHECK:STDOUT: import Core//prelude
222+
// CHECK:STDOUT: import Core//prelude/operators
223+
// CHECK:STDOUT: import Core//prelude/types
224+
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
225+
// CHECK:STDOUT: import Core//prelude/operators/as
226+
// CHECK:STDOUT: import Core//prelude/operators/bitwise
227+
// CHECK:STDOUT: import Core//prelude/operators/comparison
228+
// CHECK:STDOUT: import Core//prelude/types/bool
229+
// CHECK:STDOUT: }
230+
// CHECK:STDOUT: %Modifiers: <namespace> = namespace file.%Modifiers.import, [template] {
231+
// CHECK:STDOUT: .Base = %import_ref.1
232+
// CHECK:STDOUT: import Modifiers//default
233+
// CHECK:STDOUT: }
234+
// CHECK:STDOUT: %import_ref.1: type = import_ref Modifiers//default, inst+3, loaded [template = constants.%Base]
235+
// CHECK:STDOUT: %import_ref.2 = import_ref Modifiers//default, inst+4, unloaded
236+
// CHECK:STDOUT: %import_ref.3 = import_ref Modifiers//default, inst+5, unloaded
237+
// CHECK:STDOUT: %import_ref.4 = import_ref Modifiers//default, inst+9, unloaded
238+
// CHECK:STDOUT: }
239+
// CHECK:STDOUT:
240+
// CHECK:STDOUT: file {
241+
// CHECK:STDOUT: package: <namespace> = namespace [template] {
242+
// CHECK:STDOUT: .Core = imports.%Core
243+
// CHECK:STDOUT: .Modifiers = imports.%Modifiers
244+
// CHECK:STDOUT: .F = %F.decl
245+
// CHECK:STDOUT: }
246+
// CHECK:STDOUT: %Core.import = import Core
247+
// CHECK:STDOUT: %Modifiers.import = import Modifiers
248+
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
249+
// CHECK:STDOUT: }
250+
// CHECK:STDOUT:
251+
// CHECK:STDOUT: class @Base {
252+
// CHECK:STDOUT: !members:
253+
// CHECK:STDOUT: .Self = imports.%import_ref.2
254+
// CHECK:STDOUT: .H = imports.%import_ref.3
255+
// CHECK:STDOUT: .I = imports.%import_ref.4
256+
// CHECK:STDOUT: }
257+
// CHECK:STDOUT:
258+
// CHECK:STDOUT: fn @F() {
259+
// CHECK:STDOUT: !entry:
260+
// CHECK:STDOUT: %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [template = imports.%Modifiers]
261+
// CHECK:STDOUT: %Base.ref: type = name_ref Base, imports.%import_ref.1 [template = constants.%Base]
262+
// CHECK:STDOUT: %v.var: ref %Base = var v
263+
// CHECK:STDOUT: %v: ref %Base = bind_name v, %v.var
264+
// CHECK:STDOUT: %.loc11: %.6 = struct_literal ()
265+
// CHECK:STDOUT: assign %v.var, <error>
266+
// CHECK:STDOUT: return
267+
// CHECK:STDOUT: }
268+
// CHECK:STDOUT:

toolchain/lower/file_context.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,8 @@ static auto BuildTypeForInst(FileContext& context, SemIR::BuiltinInst inst)
440440
case SemIR::BuiltinInstKind::WitnessType:
441441
// Return an empty struct as a placeholder.
442442
return llvm::StructType::get(context.llvm_context());
443+
case SemIR::BuiltinInstKind::VtableType:
444+
return llvm::Type::getVoidTy(context.llvm_context());
443445
}
444446
}
445447

0 commit comments

Comments
 (0)