Skip to content

Commit 6385c1d

Browse files
committed
[clang] Add experimental option to omit the RTTI component from the vtable when -fno-rtti is used
For programs that don't use RTTI, the rtti component is just replaced with a zero. This way, vtables that don't use RTTI can still cooperate with vtables that use RTTI since offset calculations on the ABI level would still work. However, if throughout your whole program you don't use RTTI at all (such as the embedded case), then this is just an unused pointer-sized component that's wasting space. This adds an experimental option for removing the RTTI component from the vtable. Some notes: - This is only allowed when RTTI is disabled, so we don't have to worry about things like `typeid` or `dynamic_cast`. - This is a "use at your own risk" since, similar to relative vtables, everything must be compiled with this since it's an ABI breakage. That is, a program compiled with this is not guaranteed to work with a program compiled without this, even if RTTI is disabled for both programs. Note that this is a completely different ABI flavor orthogonal to the relative-vtables ABI. That is, they can be enabled/disabled independently. Differential Revision: https://reviews.llvm.org/D152405
1 parent 4fed595 commit 6385c1d

File tree

11 files changed

+153
-2
lines changed

11 files changed

+153
-2
lines changed

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,9 @@ def err_cc1_round_trip_mismatch : Error<
674674
def err_cc1_unbounded_vscale_min : Error<
675675
"minimum vscale must be an unsigned integer greater than 0">;
676676

677+
def err_drv_using_omit_rtti_component_without_no_rtti : Error<
678+
"-fexperimental-omit-vtable-rtti call only be used with -fno-rtti">;
679+
677680
def err_drv_ssp_missing_offset_argument : Error<
678681
"'%0' is used without '-mstack-protector-guard-offset', and there is no default">;
679682

clang/include/clang/Basic/LangOptions.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,9 @@ LANGOPT(SpeculativeLoadHardening, 1, 0, "Speculative load hardening enabled")
450450
LANGOPT(RelativeCXXABIVTables, 1, 0,
451451
"Use an ABI-incompatible v-table layout that uses relative references")
452452

453+
LANGOPT(OmitVTableRTTI, 1, 0,
454+
"Use an ABI-incompatible v-table layout that omits the RTTI component")
455+
453456
LANGOPT(VScaleMin, 32, 0, "Minimum vscale value")
454457
LANGOPT(VScaleMax, 32, 0, "Maximum vscale value")
455458

clang/include/clang/Driver/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2671,6 +2671,12 @@ def fno_experimental_relative_cxx_abi_vtables :
26712671
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
26722672
HelpText<"Do not use the experimental C++ class ABI for classes with virtual tables">;
26732673

2674+
defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti",
2675+
LangOpts<"OmitVTableRTTI">, DefaultFalse,
2676+
PosFlag<SetTrue, [], [CC1Option], "Omit">,
2677+
NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
2678+
BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;
2679+
26742680
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
26752681
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
26762682
HelpText<"C++ ABI to use. This will override the target C++ ABI.">;

clang/lib/AST/VTableBuilder.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,11 @@ CharUnits VCallAndVBaseOffsetBuilder::getCurrentOffsetOffset() const {
665665
// vtable address point. (We subtract 3 to account for the information just
666666
// above the address point, the RTTI info, the offset to top, and the
667667
// vcall offset itself).
668-
int64_t OffsetIndex = -(int64_t)(3 + Components.size());
668+
size_t NumComponentsAboveAddrPoint = 3;
669+
if (Context.getLangOpts().OmitVTableRTTI)
670+
NumComponentsAboveAddrPoint--;
671+
int64_t OffsetIndex =
672+
-(int64_t)(NumComponentsAboveAddrPoint + Components.size());
669673

670674
// Under the relative ABI, the offset widths are 32-bit ints instead of
671675
// pointer widths.
@@ -1669,7 +1673,8 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables(
16691673
Components.push_back(VTableComponent::MakeOffsetToTop(OffsetToTop));
16701674

16711675
// Next, add the RTTI.
1672-
Components.push_back(VTableComponent::MakeRTTI(MostDerivedClass));
1676+
if (!Context.getLangOpts().OmitVTableRTTI)
1677+
Components.push_back(VTableComponent::MakeRTTI(MostDerivedClass));
16731678

16741679
uint64_t AddressPoint = Components.size();
16751680

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5526,6 +5526,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
55265526
Args.AddLastArg(CmdArgs, options::OPT_fexperimental_relative_cxx_abi_vtables,
55275527
options::OPT_fno_experimental_relative_cxx_abi_vtables);
55285528

5529+
Args.AddLastArg(CmdArgs, options::OPT_fexperimental_omit_vtable_rtti,
5530+
options::OPT_fno_experimental_omit_vtable_rtti);
5531+
55295532
// Handle segmented stacks.
55305533
Args.addOptInFlag(CmdArgs, options::OPT_fsplit_stack,
55315534
options::OPT_fno_split_stack);
@@ -6007,6 +6010,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
60076010
Args.AddLastArg(CmdArgs, options::OPT_fexperimental_relative_cxx_abi_vtables,
60086011
options::OPT_fno_experimental_relative_cxx_abi_vtables);
60096012

6013+
Args.AddLastArg(CmdArgs, options::OPT_fexperimental_omit_vtable_rtti,
6014+
options::OPT_fno_experimental_omit_vtable_rtti);
6015+
60106016
if (Arg *A = Args.getLastArg(options::OPT_ffuchsia_api_level_EQ))
60116017
A->render(Args, CmdArgs);
60126018

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4109,6 +4109,14 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
41094109
options::OPT_fno_experimental_relative_cxx_abi_vtables,
41104110
TargetCXXABI::usesRelativeVTables(T));
41114111

4112+
// RTTI is on by default.
4113+
bool HasRTTI = Args.hasFlag(options::OPT_frtti, options::OPT_fno_rtti, true);
4114+
Opts.OmitVTableRTTI =
4115+
Args.hasFlag(options::OPT_fexperimental_omit_vtable_rtti,
4116+
options::OPT_fno_experimental_omit_vtable_rtti, false);
4117+
if (Opts.OmitVTableRTTI && HasRTTI)
4118+
Diags.Report(diag::err_drv_using_omit_rtti_component_without_no_rtti);
4119+
41124120
for (const auto &A : Args.getAllArgValues(OPT_fmacro_prefix_map_EQ)) {
41134121
auto Split = StringRef(A).split('=');
41144122
Opts.MacroPrefixMap.insert(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// Check that -fexperimental-omit-vtable-rtti omits the RTTI component from
2+
/// the vtable.
3+
4+
// RUN: %clang_cc1 %s -triple=aarch64-unknown-linux-gnu -fno-rtti -fexperimental-omit-vtable-rtti -S -o - -emit-llvm | FileCheck -check-prefixes=POINTER,RTTI %s
5+
// RUN: %clang_cc1 %s -triple=aarch64-unknown-linux-gnu -fexperimental-relative-c++-abi-vtables -fno-rtti -fexperimental-omit-vtable-rtti -S -o - -emit-llvm | FileCheck -check-prefixes=RELATIVE,RTTI %s
6+
7+
/// Normally, the vtable would contain at least three components:
8+
/// - An offset to top
9+
/// - A pointer to the RTTI struct
10+
/// - A virtual function
11+
///
12+
/// Now vtables should have just two components.
13+
// POINTER: @_ZTV1A = unnamed_addr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr @_ZN1A3fooEv] }, align 8
14+
// RELATIVE: @_ZTV1A.local = private unnamed_addr constant { [2 x i32] } { [2 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1A3fooEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [2 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 1) to i64)) to i32)] }, align 4
15+
// RELATIVE: @_ZTV1A = unnamed_addr alias { [2 x i32] }, ptr @_ZTV1A.local
16+
17+
/// None of these supplementary symbols should be emitted with -fno-rtti, but
18+
/// as a sanity check lets make sure they're not emitted also.
19+
// RTTI-NOT: @_ZTVN10__cxxabiv117__class_type_infoE
20+
// RTTI-NOT: @_ZTS1A
21+
// RTTI-NOT: @_ZTI1A
22+
23+
class A {
24+
public:
25+
virtual void foo();
26+
};
27+
28+
void A::foo() {}
29+
30+
void A_foo(A *a) {
31+
a->foo();
32+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// Check that the offset to top calculation is adjusted to account for the
2+
/// omitted RTTI entry.
3+
4+
// RUN: %clang_cc1 %s -triple=aarch64-unknown-linux-gnu -fexperimental-omit-vtable-rtti -fno-rtti -S -o - -emit-llvm | FileCheck -check-prefixes=POINTER %s
5+
// RUN: %clang_cc1 %s -triple=aarch64-unknown-linux-gnu -fexperimental-relative-c++-abi-vtables -fexperimental-omit-vtable-rtti -fno-rtti -S -o - -emit-llvm | FileCheck -check-prefixes=RELATIVE %s
6+
7+
/// Some important things to check:
8+
/// - The n16 here represents the virtual thunk size. Normally this would be 24
9+
/// to represent 3 components (offset to top, RTTI component, vcall offset),
10+
/// but since one 8-byte component is removed, this is now 16.
11+
// POINTER-LABEL: @_ZTv0_n16_N7Derived1fEi(
12+
// POINTER-NEXT: entry:
13+
// POINTER: [[vtable:%.+]] = load ptr, ptr %this1, align 8
14+
15+
/// Same here - When getting the vbase offset, we subtract 2 pointer sizes
16+
/// instead of 3.
17+
// POINTER-NEXT: [[vbase_offset_ptr:%.+]] = getelementptr inbounds i8, ptr [[vtable]], i64 -16
18+
// POINTER-NEXT: [[vbase_offset:%.+]] = load i64, ptr [[vbase_offset_ptr]], align 8
19+
// POINTER-NEXT: [[adj_this:%.+]] = getelementptr inbounds i8, ptr %this1, i64 [[vbase_offset]]
20+
// POINTER: [[call:%.+]] = tail call noundef i32 @_ZN7Derived1fEi(ptr noundef{{[^,]*}} [[adj_this]], i32 noundef {{.*}})
21+
// POINTER: ret i32 [[call]]
22+
23+
/// For relative vtables, it's almost the same except the offset sizes are
24+
/// halved.
25+
// RELATIVE-LABEL: @_ZTv0_n8_N7Derived1fEi(
26+
// RELATIVE-NEXT: entry:
27+
// RELATIVE: [[vtable:%.+]] = load ptr, ptr %this1, align 8
28+
// RELATIVE-NEXT: [[vbase_offset_ptr:%.+]] = getelementptr inbounds i8, ptr [[vtable]], i64 -8
29+
// RELATIVE-NEXT: [[vbase_offset:%.+]] = load i32, ptr [[vbase_offset_ptr]], align 4
30+
// RELATIVE-NEXT: [[adj_this:%.+]] = getelementptr inbounds i8, ptr %this1, i32 [[vbase_offset]]
31+
// RELATIVE: [[call:%.+]] = tail call noundef i32 @_ZN7Derived1fEi(ptr noundef{{[^,]*}} [[adj_this]], i32 noundef {{.*}})
32+
// RELATIVE: ret i32 [[call]]
33+
34+
class Base {
35+
public:
36+
virtual int f(int x);
37+
38+
private:
39+
long x;
40+
};
41+
42+
class Derived : public virtual Base {
43+
public:
44+
virtual int f(int x);
45+
46+
private:
47+
long y;
48+
};
49+
50+
int Base::f(int x) { return x + 1; }
51+
int Derived::f(int x) { return x + 2; }
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// Ensure -fdump-vtable-layout omits the rtti component when passed -fexperimental-omit-vtable-rtti.
2+
3+
// RUN: %clang_cc1 %s -triple=aarch64-unknown-linux-gnu -fno-rtti -fexperimental-omit-vtable-rtti -emit-llvm-only -fdump-vtable-layouts | FileCheck %s
4+
5+
// CHECK: Vtable for 'A' (2 entries).
6+
// CHECK-NEXT: 0 | offset_to_top (0)
7+
// CHECK-NEXT: -- (A, 0) vtable address --
8+
// CHECK-NEXT: 1 | void A::foo()
9+
10+
class A {
11+
public:
12+
virtual void foo();
13+
};
14+
15+
void A::foo() {}
16+
17+
void A_foo(A *a) {
18+
a->foo();
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// RUN: %clangxx --target=aarch64-unknown-linux -fno-rtti -Xclang -fexperimental-omit-vtable-rtti -c %s -### 2>&1 | FileCheck %s --check-prefix=OMIT
2+
// RUN: %clangxx --target=aarch64-unknown-linux -fno-rtti -Xclang -fno-experimental-omit-vtable-rtti -c %s -### 2>&1 | FileCheck %s --check-prefix=NO-OMIT
3+
4+
// OMIT: "-fexperimental-omit-vtable-rtti"
5+
// NO-OMIT-NOT: "-fexperimental-omit-vtable-rtti"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Ensure that -fexperimental-omit-vtable-rtti is only allowed if rtti is
2+
/// disabled.
3+
4+
// RUN: not %clang -c -Xclang -fexperimental-omit-vtable-rtti %s 2>&1 | FileCheck -check-prefix=ERROR %s
5+
// RUN: not %clang -c -Xclang -fexperimental-omit-vtable-rtti -frtti %s 2>&1 | FileCheck -check-prefix=ERROR %s
6+
// RUN: not %clang -c -Xclang -fexperimental-omit-vtable-rtti -fno-rtti -frtti %s 2>&1 | FileCheck -check-prefix=ERROR %s
7+
8+
// RUN: %clang -c -Xclang -fexperimental-omit-vtable-rtti -fno-rtti %s 2>&1 | FileCheck -check-prefix=NO-ERROR %s --allow-empty
9+
// RUN: %clang -c -Xclang -fno-experimental-omit-vtable-rtti -frtti %s 2>&1 | FileCheck -check-prefix=NO-ERROR %s --allow-empty
10+
// RUN: %clang -c -Xclang -fexperimental-omit-vtable-rtti -Xclang -fno-experimental-omit-vtable-rtti -frtti %s 2>&1 | FileCheck -check-prefix=NO-ERROR %s --allow-empty
11+
12+
// ERROR: -fexperimental-omit-vtable-rtti call only be used with -fno-rtti
13+
// NO-ERROR-NOT: -fexperimental-omit-vtable-rtti call only be used with -fno-rtti

0 commit comments

Comments
 (0)