Skip to content

Commit bff0e59

Browse files
dwblaikiedanakj
andauthored
Rudimentary virtual function call interop support (#6050)
This is Itanium-specific for now (explicitly downcasting to the itanium vtable handling code in Clang) - though it doesn't look like it'd be a big stretch to either have conditional/two codepaths down Itanium and MSVC in Carbon, or maybe add a virtual function in clang to avoid needing to conditional+downcast in Carbon. Here's a working example: `dynamic_type.h`: ``` #ifndef TEST_H #define TEST_H struct A { virtual auto virt0() -> int; virtual auto virt1() -> int; }; auto GetVal() -> A* _Nonnull; #endif ``` `test.carbon`: ``` library "test"; import Cpp library "dynamic_type.h"; import Core library "io"; fn Run() { var a: Cpp.A* = Cpp.GetVal(); Core.Print(a->virt0()); Core.Print(a->virt1()); } ``` `dynamic_type.cpp`: ``` #include "dynamic_type.h" auto A::virt0() -> int { return 0; } auto A::virt1() -> int { return 1; } struct B: A { auto virt0() -> int override { return 7; } auto virt1() -> int override { return 42; } }; auto GetVal() -> A* _Nonnull { static B b; return &b; } ``` ``` $ ./bazel-bin/toolchain/carbon compile test.carbon $ clang++-tot -g dynamic_type.cpp test.o --output=a.out $ ./a.out 7 42 ``` (linking with `carbon link` failed because we aren't linking to the C++ runtime yet, it seems, so: `ld.lld: error: undefined symbol: vtable for __cxxabiv1::__class_type_info`) --------- Co-authored-by: Dana Jansens <[email protected]>
1 parent cac3578 commit bff0e59

File tree

4 files changed

+162
-62
lines changed

4 files changed

+162
-62
lines changed

toolchain/check/cpp/import.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/ASTContext.h"
1414
#include "clang/AST/RecordLayout.h"
1515
#include "clang/AST/UnresolvedSet.h"
16+
#include "clang/AST/VTableBuilder.h"
1617
#include "clang/Basic/FileManager.h"
1718
#include "clang/Frontend/ASTUnit.h"
1819
#include "clang/Frontend/CompilerInstance.h"
@@ -1623,6 +1624,21 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
16231624
AddPlaceholderInstInNoBlock(context, Parse::NodeId::None, function_decl);
16241625
context.imports().push_back(decl_id);
16251626

1627+
auto virtual_modifier = SemIR::Function::VirtualModifier::None;
1628+
int32_t virtual_index = -1;
1629+
if (auto* method_decl = dyn_cast<clang::CXXMethodDecl>(clang_decl)) {
1630+
if (method_decl->size_overridden_methods()) {
1631+
virtual_modifier = SemIR::Function::VirtualModifier::Override;
1632+
} else if (method_decl->isVirtual()) {
1633+
virtual_modifier = SemIR::Function::VirtualModifier::Virtual;
1634+
}
1635+
if (virtual_modifier != SemIR::Function::VirtualModifier::None) {
1636+
// TODO: Add support for Microsoft/non-Itanium vtables.
1637+
virtual_index = dyn_cast<clang::ItaniumVTableContext>(
1638+
context.ast_context().getVTableContext())
1639+
->getMethodVTableIndex(method_decl);
1640+
}
1641+
}
16261642
auto function_info = SemIR::Function{
16271643
{.name_id = GetFunctionName(context, clang_decl),
16281644
.parent_scope_id = GetParentNameScopeId(context, clang_decl),
@@ -1640,7 +1656,8 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
16401656
.definition_id = SemIR::InstId::None},
16411657
{.call_params_id = function_params_insts->call_params_id,
16421658
.return_slot_pattern_id = function_params_insts->return_slot_pattern_id,
1643-
.virtual_modifier = SemIR::FunctionFields::VirtualModifier::None,
1659+
.virtual_modifier = virtual_modifier,
1660+
.virtual_index = virtual_index,
16441661
.self_param_id = FindSelfPattern(
16451662
context, function_params_insts->implicit_param_patterns_id),
16461663
.clang_decl_id = context.sem_ir().clang_decls().Add(
@@ -1675,14 +1692,6 @@ auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
16751692
return SemIR::ErrorInst::InstId;
16761693
}
16771694

1678-
if (auto* method_decl = dyn_cast<clang::CXXMethodDecl>(clang_decl)) {
1679-
if (method_decl->isVirtual()) {
1680-
context.TODO(loc_id, "Unsupported: Virtual function");
1681-
MarkFailedDecl(context, clang_decl);
1682-
return SemIR::ErrorInst::InstId;
1683-
}
1684-
}
1685-
16861695
CARBON_CHECK(clang_decl->getFunctionType()->isFunctionProtoType(),
16871696
"Not Prototype function (non-C++ code)");
16881697

toolchain/check/testdata/interop/cpp/class/struct.carbon

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,14 @@ import Cpp library "dynamic.h";
169169
fn MyF(bar: Cpp.Bar*);
170170
//@dump-sem-ir-end
171171

172-
// --- fail_todo_call_dynamic.carbon
172+
// --- call_dynamic.carbon
173173

174174
library "[[@TEST_NAME]]";
175175

176176
import Cpp library "dynamic.h";
177177

178178
//@dump-sem-ir-begin
179179
fn MyF(bar: Cpp.Bar*) {
180-
// CHECK:STDERR: fail_todo_call_dynamic.carbon:[[@LINE+7]]:3: error: semantics TODO: `Unsupported: Virtual function` [SemanticsTodo]
181-
// CHECK:STDERR: bar->f();
182-
// CHECK:STDERR: ^~~~~~~~
183-
// CHECK:STDERR: fail_todo_call_dynamic.carbon:[[@LINE+4]]:3: note: in call to Cpp function here [InCallToCppFunction]
184-
// CHECK:STDERR: bar->f();
185-
// CHECK:STDERR: ^~~~~~~~
186-
// CHECK:STDERR:
187180
bar->f();
188181
}
189182
//@dump-sem-ir-end
@@ -473,16 +466,19 @@ fn MyF(bar: Cpp.Bar*);
473466
// CHECK:STDOUT:
474467
// CHECK:STDOUT: fn @MyF(%bar.param: %ptr);
475468
// CHECK:STDOUT:
476-
// CHECK:STDOUT: --- fail_todo_call_dynamic.carbon
469+
// CHECK:STDOUT: --- call_dynamic.carbon
477470
// CHECK:STDOUT:
478471
// CHECK:STDOUT: constants {
479472
// CHECK:STDOUT: %Bar: type = class_type @Bar [concrete]
480473
// CHECK:STDOUT: %ptr.f68: type = ptr_type %Bar [concrete]
481-
// CHECK:STDOUT: %pattern_type: type = pattern_type %ptr.f68 [concrete]
474+
// CHECK:STDOUT: %pattern_type.146: type = pattern_type %ptr.f68 [concrete]
482475
// CHECK:STDOUT: %MyF.type: type = fn_type @MyF [concrete]
476+
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
483477
// CHECK:STDOUT: %MyF: %MyF.type = struct_value () [concrete]
484-
// CHECK:STDOUT: %.2e2: type = cpp_overload_set_type @<null name> [concrete]
478+
// CHECK:STDOUT: %.2e2: type = cpp_overload_set_type @Bar.f [concrete]
485479
// CHECK:STDOUT: %empty_struct: %.2e2 = struct_value () [concrete]
480+
// CHECK:STDOUT: %Bar.f.type: type = fn_type @Bar.f [concrete]
481+
// CHECK:STDOUT: %Bar.f: %Bar.f.type = struct_value () [concrete]
486482
// CHECK:STDOUT: }
487483
// CHECK:STDOUT:
488484
// CHECK:STDOUT: imports {
@@ -491,13 +487,18 @@ fn MyF(bar: Cpp.Bar*);
491487
// CHECK:STDOUT: import Cpp//...
492488
// CHECK:STDOUT: }
493489
// CHECK:STDOUT: %Bar.decl: type = class_decl @Bar [concrete = constants.%Bar] {} {}
494-
// CHECK:STDOUT: %.6e0: %.2e2 = cpp_overload_set_value @<null name> [concrete = constants.%empty_struct]
490+
// CHECK:STDOUT: %.6e0: %.2e2 = cpp_overload_set_value @Bar.f [concrete = constants.%empty_struct]
491+
// CHECK:STDOUT: %Bar.f.decl: %Bar.f.type = fn_decl @Bar.f [concrete = constants.%Bar.f] {
492+
// CHECK:STDOUT: <elided>
493+
// CHECK:STDOUT: } {
494+
// CHECK:STDOUT: <elided>
495+
// CHECK:STDOUT: }
495496
// CHECK:STDOUT: }
496497
// CHECK:STDOUT:
497498
// CHECK:STDOUT: file {
498499
// CHECK:STDOUT: %MyF.decl: %MyF.type = fn_decl @MyF [concrete = constants.%MyF] {
499-
// CHECK:STDOUT: %bar.patt: %pattern_type = binding_pattern bar [concrete]
500-
// CHECK:STDOUT: %bar.param_patt: %pattern_type = value_param_pattern %bar.patt, call_param0 [concrete]
500+
// CHECK:STDOUT: %bar.patt: %pattern_type.146 = binding_pattern bar [concrete]
501+
// CHECK:STDOUT: %bar.param_patt: %pattern_type.146 = value_param_pattern %bar.patt, call_param0 [concrete]
501502
// CHECK:STDOUT: } {
502503
// CHECK:STDOUT: %bar.param: %ptr.f68 = value_param call_param0
503504
// CHECK:STDOUT: %.loc7: type = splice_block %ptr [concrete = constants.%ptr.f68] {
@@ -512,9 +513,11 @@ fn MyF(bar: Cpp.Bar*);
512513
// CHECK:STDOUT: fn @MyF(%bar.param: %ptr.f68) {
513514
// CHECK:STDOUT: !entry:
514515
// CHECK:STDOUT: %bar.ref: %ptr.f68 = name_ref bar, %bar
515-
// CHECK:STDOUT: %.loc15: ref %Bar = deref %bar.ref
516+
// CHECK:STDOUT: %.loc8: ref %Bar = deref %bar.ref
516517
// CHECK:STDOUT: %f.ref: %.2e2 = name_ref f, imports.%.6e0 [concrete = constants.%empty_struct]
517-
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %.loc15, %f.ref
518+
// CHECK:STDOUT: %bound_method: <bound method> = bound_method %.loc8, %f.ref
519+
// CHECK:STDOUT: %addr: %ptr.f68 = addr_of %.loc8
520+
// CHECK:STDOUT: %Bar.f.call: init %empty_tuple.type = call imports.%Bar.f.decl(%addr)
518521
// CHECK:STDOUT: return
519522
// CHECK:STDOUT: }
520523
// CHECK:STDOUT:

toolchain/lower/handle_call.cpp

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,60 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
476476
CARBON_FATAL("Unsupported builtin call.");
477477
}
478478

479+
static auto HandleVirtualCall(FunctionContext& context,
480+
llvm::ArrayRef<llvm::Value*> args,
481+
const SemIR::File* callee_file,
482+
const SemIR::Function& function,
483+
const SemIR::CalleeFunction& callee_function)
484+
-> llvm::CallInst* {
485+
CARBON_CHECK(!args.empty(),
486+
"Virtual functions must have at least one parameter");
487+
auto* ptr_type =
488+
llvm::PointerType::get(context.llvm_context(), /*AddressSpace=*/0);
489+
// The vtable pointer is always at the start of the object in the Carbon
490+
// ABI, so a pointer to the object is a pointer to the vtable pointer - load
491+
// that to get a pointer to the vtable.
492+
// TODO: Handle the case in C++ interop where the vtable pointer isn't at
493+
// the start of the object.
494+
// TODO: Use `context.LoadObject`.
495+
auto* vtable = context.builder().CreateLoad(ptr_type, args.front(), "vtable");
496+
auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
497+
auto* pointer_type =
498+
llvm::PointerType::get(context.llvm_context(), /* address space */ 0);
499+
auto function_type_info =
500+
context.GetFileContext(callee_file)
501+
.BuildFunctionTypeInfo(function,
502+
callee_function.resolved_specific_id);
503+
llvm::Value* virtual_fn;
504+
if (function.clang_decl_id.has_value()) {
505+
// Use absolute vtables for clang interop - the itanium vtable contains
506+
// function pointers.
507+
auto* virtual_function_pointer_address = context.builder().CreateGEP(
508+
pointer_type, vtable,
509+
{llvm::ConstantInt::get(
510+
i32_type, static_cast<uint64_t>(function.virtual_index))});
511+
virtual_fn = context.builder().CreateLoad(
512+
pointer_type, virtual_function_pointer_address, "memptr.virtualfn");
513+
} else {
514+
// For Carbon, use Relative VTables as pioneered by Fuchsia:
515+
// https://llvm.org/devmtg/2021-11/slides/2021-RelativeVTablesinC.pdf
516+
// In this case, the vtable contains an offset from the vtable itself to the
517+
// function in question. This avoids the use of link-time relocations in the
518+
// vtable (making object files smaller, improving link time) - at the cost
519+
// of extra instructions to resolve the offset at the call-site.
520+
// This uses the `llvm.load.relative` intrinsic (
521+
// https://llvm.org/docs/LangRef.html#llvm-load-relative-intrinsic ) that
522+
// essentially does the arithmetic in one-shot: ptr + *(ptr + offset)
523+
virtual_fn = context.builder().CreateCall(
524+
llvm::Intrinsic::getOrInsertDeclaration(
525+
&context.llvm_module(), llvm::Intrinsic::load_relative, {i32_type}),
526+
{vtable,
527+
llvm::ConstantInt::get(
528+
i32_type, static_cast<uint64_t>(function.virtual_index) * 4)});
529+
}
530+
return context.builder().CreateCall(function_type_info.type, virtual_fn,
531+
args);
532+
}
479533
auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
480534
SemIR::Call inst) -> void {
481535
llvm::ArrayRef<SemIR::InstId> arg_ids =
@@ -542,33 +596,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
542596
}
543597

544598
llvm::CallInst* call;
545-
if (function.virtual_index != -1) {
546-
CARBON_CHECK(!args.empty(),
547-
"Virtual functions must have at least one parameter");
548-
auto* ptr_type =
549-
llvm::PointerType::get(context.llvm_context(), /*AddressSpace=*/0);
550-
// The vtable pointer is always at the start of the object in the Carbon
551-
// ABI, so a pointer to the object is a pointer to the vtable pointer - load
552-
// that to get a pointer to the vtable.
553-
// TODO: Use `context.LoadObject`.
554-
auto* vtable =
555-
context.builder().CreateLoad(ptr_type, args.front(), "vtable");
556-
auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());
557-
auto function_type_info =
558-
context.GetFileContext(callee_file)
559-
.BuildFunctionTypeInfo(function,
560-
callee_function.resolved_specific_id);
561-
call = context.builder().CreateCall(
562-
function_type_info.type,
563-
context.builder().CreateCall(
564-
llvm::Intrinsic::getOrInsertDeclaration(
565-
&context.llvm_module(), llvm::Intrinsic::load_relative,
566-
{i32_type}),
567-
{vtable,
568-
llvm::ConstantInt::get(
569-
i32_type, static_cast<uint64_t>(function.virtual_index) * 4)}),
570-
args);
571-
} else {
599+
if (function.virtual_modifier == SemIR::Function::VirtualModifier::None) {
572600
auto* callee =
573601
context.GetFileContext(callee_file)
574602
.GetOrCreateFunction(callee_function.function_id,
@@ -590,6 +618,9 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
590618
CARBON_CHECK(callee->arg_size() == args.size(),
591619
"Argument count mismatch: {0}", describe_call());
592620
call = context.builder().CreateCall(callee, args);
621+
} else {
622+
call = HandleVirtualCall(context, args, callee_file, function,
623+
callee_function);
593624
}
594625

595626
context.SetLocal(inst_id, call);

toolchain/lower/testdata/interop/cpp/method.carbon

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@
1313

1414
// --- methods.h
1515

16-
struct A {
16+
struct Base {
17+
virtual void virt0();
18+
};
19+
20+
struct A: Base {
1721
int by_val() const { return n; }
1822
int by_ref() { return n; }
23+
virtual void virt1();
1924
int n;
2025
};
2126

@@ -39,6 +44,17 @@ fn UseVal(a: Cpp.A*) -> i32 {
3944
return a->by_ref();
4045
}
4146

47+
// --- call_virtual.carbon
48+
49+
library "[[@TEST_NAME]]";
50+
51+
import Cpp library "methods.h";
52+
53+
fn UseVal(a: Cpp.A*) {
54+
a->virt0();
55+
a->virt1();
56+
}
57+
4258
// --- thunk.h
4359

4460
struct NeedThunk {
@@ -62,7 +78,8 @@ fn Call(n: Cpp.NeedThunk) {
6278
// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
6379
// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
6480
// CHECK:STDOUT:
65-
// CHECK:STDOUT: %struct.A = type { i32 }
81+
// CHECK:STDOUT: %struct.A = type <{ %struct.Base, i32, [4 x i8] }>
82+
// CHECK:STDOUT: %struct.Base = type { ptr }
6683
// CHECK:STDOUT:
6784
// CHECK:STDOUT: $_ZNK1A6by_valEv = comdat any
6885
// CHECK:STDOUT:
@@ -73,13 +90,13 @@ fn Call(n: Cpp.NeedThunk) {
7390
// CHECK:STDOUT: }
7491
// CHECK:STDOUT:
7592
// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
76-
// CHECK:STDOUT: define linkonce_odr dso_local i32 @_ZNK1A6by_valEv(ptr nonnull align 4 dereferenceable(4) %this) #0 comdat align 2 {
93+
// CHECK:STDOUT: define linkonce_odr dso_local i32 @_ZNK1A6by_valEv(ptr nonnull align 8 dereferenceable(12) %this) #0 comdat align 2 {
7794
// CHECK:STDOUT: entry:
7895
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
7996
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8
8097
// CHECK:STDOUT: %this1 = load ptr, ptr %this.addr, align 8
81-
// CHECK:STDOUT: %n = getelementptr inbounds nuw %struct.A, ptr %this1, i32 0, i32 0
82-
// CHECK:STDOUT: %0 = load i32, ptr %n, align 4
98+
// CHECK:STDOUT: %n = getelementptr inbounds nuw %struct.A, ptr %this1, i32 0, i32 1
99+
// CHECK:STDOUT: %0 = load i32, ptr %n, align 8
83100
// CHECK:STDOUT: ret i32 %0
84101
// CHECK:STDOUT: }
85102
// CHECK:STDOUT:
@@ -89,7 +106,7 @@ fn Call(n: Cpp.NeedThunk) {
89106
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
90107
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8
91108
// CHECK:STDOUT: %0 = load ptr, ptr %this.addr, align 8
92-
// CHECK:STDOUT: %call = call i32 @_ZNK1A6by_valEv(ptr nonnull align 4 dereferenceable(4) %0)
109+
// CHECK:STDOUT: %call = call i32 @_ZNK1A6by_valEv(ptr nonnull align 8 dereferenceable(12) %0)
93110
// CHECK:STDOUT: ret i32 %call
94111
// CHECK:STDOUT: }
95112
// CHECK:STDOUT:
@@ -116,7 +133,8 @@ fn Call(n: Cpp.NeedThunk) {
116133
// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
117134
// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
118135
// CHECK:STDOUT:
119-
// CHECK:STDOUT: %struct.A = type { i32 }
136+
// CHECK:STDOUT: %struct.A = type <{ %struct.Base, i32, [4 x i8] }>
137+
// CHECK:STDOUT: %struct.Base = type { ptr }
120138
// CHECK:STDOUT:
121139
// CHECK:STDOUT: $_ZN1A6by_refEv = comdat any
122140
// CHECK:STDOUT:
@@ -127,13 +145,13 @@ fn Call(n: Cpp.NeedThunk) {
127145
// CHECK:STDOUT: }
128146
// CHECK:STDOUT:
129147
// CHECK:STDOUT: ; Function Attrs: mustprogress noinline nounwind optnone
130-
// CHECK:STDOUT: define linkonce_odr dso_local i32 @_ZN1A6by_refEv(ptr nonnull align 4 dereferenceable(4) %this) #0 comdat align 2 {
148+
// CHECK:STDOUT: define linkonce_odr dso_local i32 @_ZN1A6by_refEv(ptr nonnull align 8 dereferenceable(12) %this) #0 comdat align 2 {
131149
// CHECK:STDOUT: entry:
132150
// CHECK:STDOUT: %this.addr = alloca ptr, align 8
133151
// CHECK:STDOUT: store ptr %this, ptr %this.addr, align 8
134152
// CHECK:STDOUT: %this1 = load ptr, ptr %this.addr, align 8
135-
// CHECK:STDOUT: %n = getelementptr inbounds nuw %struct.A, ptr %this1, i32 0, i32 0
136-
// CHECK:STDOUT: %0 = load i32, ptr %n, align 4
153+
// CHECK:STDOUT: %n = getelementptr inbounds nuw %struct.A, ptr %this1, i32 0, i32 1
154+
// CHECK:STDOUT: %0 = load i32, ptr %n, align 8
137155
// CHECK:STDOUT: ret i32 %0
138156
// CHECK:STDOUT: }
139157
// CHECK:STDOUT:
@@ -154,6 +172,45 @@ fn Call(n: Cpp.NeedThunk) {
154172
// CHECK:STDOUT: !9 = !{}
155173
// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 10, scope: !7)
156174
// CHECK:STDOUT: !11 = !DILocation(line: 7, column: 3, scope: !7)
175+
// CHECK:STDOUT: ; ModuleID = 'call_virtual.carbon'
176+
// CHECK:STDOUT: source_filename = "call_virtual.carbon"
177+
// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
178+
// CHECK:STDOUT: target triple = "x86_64-unknown-linux-gnu"
179+
// CHECK:STDOUT:
180+
// CHECK:STDOUT: define void @_CUseVal.Main(ptr %a) !dbg !7 {
181+
// CHECK:STDOUT: entry:
182+
// CHECK:STDOUT: %.loc7_4.3.base = getelementptr inbounds nuw [16 x i8], ptr %a, i32 0, i32 0, !dbg !10
183+
// CHECK:STDOUT: %Base.virt0.call.vtable = load ptr, ptr %.loc7_4.3.base, align 8, !dbg !10
184+
// CHECK:STDOUT: %Base.virt0.call = getelementptr ptr, ptr %Base.virt0.call.vtable, i32 0, !dbg !10
185+
// CHECK:STDOUT: %Base.virt0.call.memptr.virtualfn = load ptr, ptr %Base.virt0.call, align 8, !dbg !10
186+
// CHECK:STDOUT: call void %Base.virt0.call.memptr.virtualfn(ptr %.loc7_4.3.base), !dbg !10
187+
// CHECK:STDOUT: %A.virt1.call.vtable = load ptr, ptr %a, align 8, !dbg !11
188+
// CHECK:STDOUT: %A.virt1.call = getelementptr ptr, ptr %A.virt1.call.vtable, i32 1, !dbg !11
189+
// CHECK:STDOUT: %A.virt1.call.memptr.virtualfn = load ptr, ptr %A.virt1.call, align 8, !dbg !11
190+
// CHECK:STDOUT: call void %A.virt1.call.memptr.virtualfn(ptr %a), !dbg !11
191+
// CHECK:STDOUT: ret void, !dbg !12
192+
// CHECK:STDOUT: }
193+
// CHECK:STDOUT:
194+
// CHECK:STDOUT: declare void @_ZN4Base5virt0Ev(ptr)
195+
// CHECK:STDOUT:
196+
// CHECK:STDOUT: declare void @_ZN1A5virt1Ev(ptr)
197+
// CHECK:STDOUT:
198+
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1, !2, !3, !4}
199+
// CHECK:STDOUT: !llvm.dbg.cu = !{!5}
200+
// CHECK:STDOUT:
201+
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
202+
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
203+
// CHECK:STDOUT: !2 = !{i32 1, !"wchar_size", i32 4}
204+
// CHECK:STDOUT: !3 = !{i32 8, !"PIC Level", i32 0}
205+
// CHECK:STDOUT: !4 = !{i32 7, !"PIE Level", i32 2}
206+
// CHECK:STDOUT: !5 = distinct !DICompileUnit(language: DW_LANG_C, file: !6, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
207+
// CHECK:STDOUT: !6 = !DIFile(filename: "call_virtual.carbon", directory: "")
208+
// CHECK:STDOUT: !7 = distinct !DISubprogram(name: "UseVal", linkageName: "_CUseVal.Main", scope: null, file: !6, line: 6, type: !8, spFlags: DISPFlagDefinition, unit: !5)
209+
// CHECK:STDOUT: !8 = !DISubroutineType(types: !9)
210+
// CHECK:STDOUT: !9 = !{}
211+
// CHECK:STDOUT: !10 = !DILocation(line: 7, column: 3, scope: !7)
212+
// CHECK:STDOUT: !11 = !DILocation(line: 8, column: 3, scope: !7)
213+
// CHECK:STDOUT: !12 = !DILocation(line: 6, column: 1, scope: !7)
157214
// CHECK:STDOUT: ; ModuleID = 'call_thunk.carbon'
158215
// CHECK:STDOUT: source_filename = "call_thunk.carbon"
159216
// CHECK:STDOUT: target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"

0 commit comments

Comments
 (0)