pcc created this revision. pcc added reviewers: rsmith, eugenis. pcc added subscribers: cfe-commits, krasin. pcc added a dependency: D22295: Introduce !splitpoint metadata and GlobalSplit pass..
This metadata can be used to annotate global variables with split points. Each split point specifies a byte offset within the global, and acts as a promise that no pointer derived from a constant-offset reference to the global will cross the boundary of a split point. The metadata allows the optimizer to split the virtual table at split points if possible and beneficial. A virtual table split is possible if it has local linkage. This guarantees that nothing outside of the module can reference the virtual table group directly. Typically, when LTO is used, the linker is able to internalize most virtual tables. A split is beneficial if either of the whole-program devirtualization or control flow integrity features are being used. In the former case, under virtual constant propagation we are able to place propagated constants directly in front of virtual tables of classes with multiple bases. In the latter case, we can arrange virtual tables with multiple bases in a more hierarchical order, which reduces the required amount of runtime data and simplifies the required checks. In one recent experiment, enabling vtable splitting reduced code size overhead of CFI in Chromium from 5% to 3.5%. Under the Itanium C++ ABI, Clang attaches split points at each boundary between virtual tables in a virtual table group, because the virtual tables are independent entities as far as a user of a virtual table within an object is concerned, provided that the dynamic type is unknown. A compiler cannot legally generate code to adjust a virtual table pointer to point to another virtual table in the virtual table group, as the primary virtual table will have an unknown number of virtual functions. Although a compiler could in principle adjust a virtual table pointer stored in an object to another virtual table in the virtual table group if the dynamic type is known, there is probably no benefit in doing so as the identity of the virtual function would also be known, so the compiler could just emit a direct call to the virtual function. According to an experiment carried out at godbolt.org [1], all major compilers (gcc, clang and icc) will either compile such code to a direct call or call a virtual function pointer loaded from the virtual table pointer stored in the correct base. An amendment to the Itanium ABI requiring that a conforming program may not adjust a virtual table pointer loaded from an object to another virtual table in the same virtual table group would seem to be all that would be required to guarantee that this scheme will be ABI compatible with future compilers, and I'd be happy to drive such an amendment. [1] https://godbolt.org/g/7eG8A1 Depends on D22295 http://reviews.llvm.org/D22296 Files: lib/CodeGen/CGVTables.cpp test/CodeGenCXX/splitpoint.cpp Index: test/CodeGenCXX/splitpoint.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/splitpoint.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -flto -triple x86_64-unknown-linux -fvisibility hidden -emit-llvm -o - %s | FileCheck %s + +// CHECK: @_ZTV1A = +// CHECK-NOT: splitpoint +struct A { + virtual void f(); +}; + +// CHECK: @_ZTV1B = +// CHECK-NOT: splitpoint +struct B { + virtual void g(); +}; + +// CHECK: @_ZTV1C = {{.*}}, !splitpoint [[SP:![0-9]+]] +// CHECK: [[SP]] = !{i64 24} +struct C : A, B { + virtual void f(); +}; + +void A::f() {} +void B::g() {} +void C::f() {} Index: lib/CodeGen/CGVTables.cpp =================================================================== --- lib/CodeGen/CGVTables.cpp +++ lib/CodeGen/CGVTables.cpp @@ -945,6 +945,25 @@ CharUnits PointerWidth = Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); + // Create split points. In the Itanium ABI, vtable groups may be split at + // boundaries between vtables in that group, which occurs at the transition + // from a non-function pointer to a function pointer. + uint64_t Offset = 0; + bool LastWasFunctionPointer = false; + for (const VTableComponent &Comp : VTLayout.vtable_components()) { + bool IsFunctionPointer = Comp.isFunctionPointerKind(); + if (LastWasFunctionPointer && !IsFunctionPointer) + VTable->addMetadata( + llvm::LLVMContext::MD_splitpoint, + *llvm::MDTuple::get( + getLLVMContext(), + {llvm::ConstantAsMetadata::get(llvm::ConstantInt::get( + Int64Ty, Offset * PointerWidth.getQuantity()))})); + + LastWasFunctionPointer = IsFunctionPointer; + ++Offset; + } + typedef std::pair<const CXXRecordDecl *, unsigned> BSEntry; std::vector<BSEntry> BitsetEntries; // Create a bit set entry for each address point.
Index: test/CodeGenCXX/splitpoint.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/splitpoint.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -flto -triple x86_64-unknown-linux -fvisibility hidden -emit-llvm -o - %s | FileCheck %s + +// CHECK: @_ZTV1A = +// CHECK-NOT: splitpoint +struct A { + virtual void f(); +}; + +// CHECK: @_ZTV1B = +// CHECK-NOT: splitpoint +struct B { + virtual void g(); +}; + +// CHECK: @_ZTV1C = {{.*}}, !splitpoint [[SP:![0-9]+]] +// CHECK: [[SP]] = !{i64 24} +struct C : A, B { + virtual void f(); +}; + +void A::f() {} +void B::g() {} +void C::f() {} Index: lib/CodeGen/CGVTables.cpp =================================================================== --- lib/CodeGen/CGVTables.cpp +++ lib/CodeGen/CGVTables.cpp @@ -945,6 +945,25 @@ CharUnits PointerWidth = Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); + // Create split points. In the Itanium ABI, vtable groups may be split at + // boundaries between vtables in that group, which occurs at the transition + // from a non-function pointer to a function pointer. + uint64_t Offset = 0; + bool LastWasFunctionPointer = false; + for (const VTableComponent &Comp : VTLayout.vtable_components()) { + bool IsFunctionPointer = Comp.isFunctionPointerKind(); + if (LastWasFunctionPointer && !IsFunctionPointer) + VTable->addMetadata( + llvm::LLVMContext::MD_splitpoint, + *llvm::MDTuple::get( + getLLVMContext(), + {llvm::ConstantAsMetadata::get(llvm::ConstantInt::get( + Int64Ty, Offset * PointerWidth.getQuantity()))})); + + LastWasFunctionPointer = IsFunctionPointer; + ++Offset; + } + typedef std::pair<const CXXRecordDecl *, unsigned> BSEntry; std::vector<BSEntry> BitsetEntries; // Create a bit set entry for each address point.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits