Skip to content

[CIR] Implement opportunistic VTable emission #1749

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3373,7 +3373,7 @@ void CIRGenModule::Release() {
assert(!MissingFeatures::emitModuleInitializers());
emitDeferred(getCodeGenOpts().ClangIRBuildDeferredThreshold);
assert(!MissingFeatures::emittedDeferredDecls());
assert(!MissingFeatures::emitVTablesOpportunistically());
emitVTablesOpportunistically();
assert(!MissingFeatures::applyGlobalValReplacements());
applyReplacements();
assert(!MissingFeatures::emitMultiVersionFunctions());
Expand Down Expand Up @@ -4013,8 +4013,6 @@ cir::GlobalOp CIRGenModule::createOrReplaceCXXRuntimeVariable(
}

bool CIRGenModule::shouldOpportunisticallyEmitVTables() {
if (codeGenOpts.OptimizationLevel != 0)
llvm_unreachable("NYI");
return codeGenOpts.OptimizationLevel > 0;
}

Expand Down Expand Up @@ -4245,6 +4243,25 @@ void CIRGenModule::addGlobalAnnotations(const ValueDecl *d,
func.setAnnotationsAttr(builder.getArrayAttr(annotations));
}

void CIRGenModule::emitVTablesOpportunistically() {
// Try to emit external vtables as available_externally if they have emitted
// all inlined virtual functions. It runs after EmitDeferred() and therefore
// is not allowed to create new references to things that need to be emitted
// lazily. Note that it also uses fact that we eagerly emitting RTTI.

assert(
(opportunisticVTables.empty() || shouldOpportunisticallyEmitVTables()) &&
"Only emit opportunistic vtables with optimizations");

for (const CXXRecordDecl *rd : opportunisticVTables) {
assert(getVTables().isVTableExternal(rd) &&
"This queue should only contain external vtables");
if (getCXXABI().canSpeculativelyEmitVTable(rd))
VTables.GenerateClassData(rd);
}
opportunisticVTables.clear();
}

void CIRGenModule::emitGlobalAnnotations() {
for (const auto &[mangledName, vd] : deferredAnnotations) {
mlir::Operation *gv = getGlobalValue(mangledName);
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,9 @@ class CIRGenModule : public CIRGenTypeCache {
/// A queue of (optional) vtables to consider emitting.
std::vector<const clang::CXXRecordDecl *> DeferredVTables;

/// A queue of (optional) vtables that may be emitted opportunistically.
std::vector<const clang::CXXRecordDecl *> opportunisticVTables;

mlir::Type getVTableComponentType();
CIRGenVTables &getVTables() { return VTables; }

Expand Down Expand Up @@ -783,6 +786,12 @@ class CIRGenModule : public CIRGenTypeCache {
/// Emit any needed decls for which code generation was deferred.
void emitDeferred(unsigned recursionLimit);

/// Try to emit external vtables as available_externally if they have emitted
/// all inlined virtual functions. It runs after EmitDeferred() and therefore
/// is not allowed to create new references to things that need to be emitted
/// lazily.
void emitVTablesOpportunistically();

/// Helper for `emitDeferred` to apply actual codegen.
void emitGlobalDecl(clang::GlobalDecl &D);

Expand Down
7 changes: 3 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenVTables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,10 @@ void CIRGenModule::emitDeferredVTables() {
#endif

for (const CXXRecordDecl *RD : DeferredVTables)
if (shouldEmitVTableAtEndOfTranslationUnit(*this, RD)) {
if (shouldEmitVTableAtEndOfTranslationUnit(*this, RD))
VTables.GenerateClassData(RD);
} else if (shouldOpportunisticallyEmitVTables()) {
llvm_unreachable("NYI");
}
else if (shouldOpportunisticallyEmitVTables())
opportunisticVTables.push_back(RD);

assert(savedSize == DeferredVTables.size() &&
"deferred extra vtables during vtable emission?");
Expand Down
4 changes: 1 addition & 3 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2257,6 +2257,7 @@ LogicalResult cir::GlobalOp::verify() {
case GlobalLinkageKind::CommonLinkage:
case GlobalLinkageKind::WeakAnyLinkage:
case GlobalLinkageKind::WeakODRLinkage:
case GlobalLinkageKind::AvailableExternallyLinkage:
// FIXME: mlir's concept of visibility gets tricky with LLVM ones,
// for instance, symbol declarations cannot be "public", so we
// have to mark them "private" to workaround the symbol verifier.
Expand All @@ -2265,9 +2266,6 @@ LogicalResult cir::GlobalOp::verify() {
<< stringifyGlobalLinkageKind(getLinkage())
<< "' linkage";
break;
default:
return emitError() << stringifyGlobalLinkageKind(getLinkage())
<< ": verifier not implemented\n";
}

// TODO: verify visibility for declarations?
Expand Down
25 changes: 25 additions & 0 deletions clang/test/CIR/CodeGen/vtable-available-externally.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %clang_cc1 %s -I%S -triple x86_64-unknown-linux-gnu -std=c++98 -O0 -disable-llvm-passes -emit-cir -o %t
// RUN: FileCheck -allow-deprecated-dag-overlap --check-prefix=CHECK %s < %t
// RUN: %clang_cc1 %s -I%S -triple x86_64-unknown-linux-gnu -std=c++98 -O2 -disable-llvm-passes -emit-cir -o %t.opt
// RUN: FileCheck -allow-deprecated-dag-overlap --check-prefix=CHECK-FORCE-EMIT %s < %t.opt

// CHECK: cir.global{{.*}} external @_ZTV1A
// CHECK-FORCE-EMIT: cir.global{{.*}} available_externally @_ZTV1A
struct A {
A();
virtual void f();
virtual ~A() { }
};

A::A() { }

void f(A* a) {
a->f();
};

// CHECK-LABEL: cir.func{{.*}} @_Z1gv
// CHECK: cir.call @_Z1fP1A
void g() {
A a;
f(&a);
}
Loading