Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -4213,6 +4213,17 @@ This is currently only supported for ELF binary formats. +.. _vfe_eligible: + +VFE Eligible +------------ + +``vfe_eligible @func`` + +A '``vfe_eligible``' constant represents a function which is functionally +equivalent to a given function, but is in an entry in a virtual table that is +eligible for Virtual Function Elimination. + .. _constantexprs: Constant Expressions Index: llvm/docs/TypeMetadata.rst =================================================================== --- llvm/docs/TypeMetadata.rst +++ llvm/docs/TypeMetadata.rst @@ -282,9 +282,16 @@ All virtual function calls which might use this vtable are in the current module. -In addition, all function pointer loads from a vtable marked with the -``!vcall_visibility`` metadata (with a non-zero value) must be done using the -:ref:`llvm.type.checked.load ` intrinsic, so that virtual -calls sites can be correlated with the vtables which they might load from. -Other parts of the vtable (RTTI, offset-to-top, ...) can still be accessed with -normal loads. +In addition, vtables marked with the ``!vcall_visibility`` metadata (with a +non-zero value) must follow these rules: + +1. All virtual function pointers in the vtable must be wrapped in a + ``vfe_eligible`` constant. Function pointers without a ``vfe_eligible`` + marker are not participating in removal of unused function pointers. See + `VFE Eligible `_. + +2. All virtual function pointer loads from a vtable must be done using the + :ref:`llvm.type.checked.load ` intrinsic, so that virtual + calls sites can be correlated with the vtables which they might load from. + Other parts of the vtable (RTTI, offset-to-top, ...) can still be accessed + with normal loads. Index: llvm/include/llvm/AsmParser/LLParser.h =================================================================== --- llvm/include/llvm/AsmParser/LLParser.h +++ llvm/include/llvm/AsmParser/LLParser.h @@ -62,12 +62,14 @@ APFloat APFloatVal{0.0}; Constant *ConstantVal; std::unique_ptr ConstantStructElts; + bool VFEEligible = false; ValID() = default; ValID(const ValID &RHS) : Kind(RHS.Kind), Loc(RHS.Loc), UIntVal(RHS.UIntVal), FTy(RHS.FTy), StrVal(RHS.StrVal), StrVal2(RHS.StrVal2), APSIntVal(RHS.APSIntVal), - APFloatVal(RHS.APFloatVal), ConstantVal(RHS.ConstantVal) { + APFloatVal(RHS.APFloatVal), ConstantVal(RHS.ConstantVal), + VFEEligible(RHS.VFEEligible) { assert(!RHS.ConstantStructElts); } Index: llvm/include/llvm/AsmParser/LLToken.h =================================================================== --- llvm/include/llvm/AsmParser/LLToken.h +++ llvm/include/llvm/AsmParser/LLToken.h @@ -370,6 +370,7 @@ kw_insertvalue, kw_blockaddress, kw_dso_local_equivalent, + kw_vfe_eligible, kw_freeze, Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -384,6 +384,7 @@ CST_CODE_INLINEASM = 28, // INLINEASM: [sideeffect|alignstack| // asmdialect|unwind, // asmstr,conststr] + CST_CODE_VFE_ELIGIBLE_VALUE = 29, // VFE_ELIGIBLE [ fty, f ] }; /// CastOpcodes - These are values used in the bitcode files to encode which Index: llvm/include/llvm/IR/Constants.h =================================================================== --- llvm/include/llvm/IR/Constants.h +++ llvm/include/llvm/IR/Constants.h @@ -926,6 +926,43 @@ DEFINE_TRANSPARENT_OPERAND_ACCESSORS(DSOLocalEquivalent, Value) +/// Wrapper for a function that marks it as "eligible for Virtual Function +/// Elimination" in a vtable, functionally represents the original function. +class VFEEligibleValue final : public Constant { + friend class Constant; + + VFEEligibleValue(GlobalValue *GV); + + void *operator new(size_t S) { return User::operator new(S, 1); } + + void destroyConstantImpl(); + Value *handleOperandChangeImpl(Value *From, Value *To); + +public: + void operator delete(void *Ptr) { User::operator delete(Ptr); } + + /// Return a VFEEligibleValue for the specified function. + static VFEEligibleValue *get(GlobalValue *GV); + + /// Transparently provide more efficient getOperand methods. + DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Value); + + GlobalValue *getGlobalValue() const { + return cast(Op<0>().get()); + } + + /// Methods for support type inquiry through isa, cast, and dyn_cast: + static bool classof(const Value *V) { + return V->getValueID() == VFEEligibleValueVal; + } +}; + +template <> +struct OperandTraits : public FixedNumOperandTraits { +}; + +DEFINE_TRANSPARENT_OPERAND_ACCESSORS(VFEEligibleValue, Value) + //===----------------------------------------------------------------------===// /// A constant value that is initialized with an expression using /// other constant values. Index: llvm/include/llvm/IR/Value.def =================================================================== --- llvm/include/llvm/IR/Value.def +++ llvm/include/llvm/IR/Value.def @@ -80,6 +80,7 @@ HANDLE_CONSTANT(BlockAddress) HANDLE_CONSTANT(ConstantExpr) HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(DSOLocalEquivalent) +HANDLE_CONSTANT_EXCLUDE_LLVM_C_API(VFEEligibleValue) // ConstantAggregate. HANDLE_CONSTANT(ConstantArray) Index: llvm/include/llvm/Transforms/IPO/GlobalDCE.h =================================================================== --- llvm/include/llvm/Transforms/IPO/GlobalDCE.h +++ llvm/include/llvm/Transforms/IPO/GlobalDCE.h @@ -47,6 +47,9 @@ DenseMap, 4>> TypeIdMap; + /// VTable -> set of VFE eligible vfuncs in that vtable. + DenseMap> EligibleMap; + // Global variables which are vtables, and which we have enough information // about to safely do dead virtual function elimination. SmallPtrSet VFESafeVTables; Index: llvm/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/lib/AsmParser/LLLexer.cpp +++ llvm/lib/AsmParser/LLLexer.cpp @@ -733,6 +733,7 @@ KEYWORD(x); KEYWORD(blockaddress); KEYWORD(dso_local_equivalent); + KEYWORD(vfe_eligible); // Metadata types. KEYWORD(distinct); Index: llvm/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/lib/AsmParser/LLParser.cpp +++ llvm/lib/AsmParser/LLParser.cpp @@ -3278,6 +3278,20 @@ return false; } + case lltok::kw_vfe_eligible: { + // ValID ::= 'vfe_eligible' @foo + Lex.Lex(); + + if (parseValID(ID, PFS)) + return true; + + if (ID.Kind != ValID::t_GlobalID && ID.Kind != ValID::t_GlobalName) + return error(ID.Loc, "expected global value name in vfe_eligible"); + + ID.VFEEligible = true; + return false; + } + case lltok::kw_trunc: case lltok::kw_zext: case lltok::kw_sext: @@ -5257,9 +5271,13 @@ } case ValID::t_GlobalName: V = getGlobalVal(ID.StrVal, Ty, ID.Loc); + if (V && ID.VFEEligible) + V = VFEEligibleValue::get(cast(V)); return V == nullptr; case ValID::t_GlobalID: V = getGlobalVal(ID.UIntVal, Ty, ID.Loc); + if (V && ID.VFEEligible) + V = VFEEligibleValue::get(cast(V)); return V == nullptr; case ValID::t_APSInt: if (!Ty->isIntegerTy()) Index: llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp +++ llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp @@ -219,6 +219,7 @@ STRINGIFY_CODE(CST_CODE, CE_SHUFVEC_EX) STRINGIFY_CODE(CST_CODE, CE_UNOP) STRINGIFY_CODE(CST_CODE, DSO_LOCAL_EQUIVALENT) + STRINGIFY_CODE(CST_CODE, VFE_ELIGIBLE_VALUE) case bitc::CST_CODE_BLOCKADDRESS: return "CST_CODE_BLOCKADDRESS"; STRINGIFY_CODE(CST_CODE, DATA) Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2939,6 +2939,19 @@ V = DSOLocalEquivalent::get(GV); break; } + case bitc::CST_CODE_VFE_ELIGIBLE_VALUE: { + if (Record.size() < 2) + return error("Invalid record"); + Type *GVTy = getTypeByID(Record[0]); + if (!GVTy) + return error("Invalid record"); + GlobalValue *GV = dyn_cast_or_null( + ValueList.getConstantFwdRef(Record[1], GVTy)); + if (!GV) + return error("Invalid record"); + V = VFEEligibleValue::get(GV); + break; + } } ValueList.assignValue(V, NextCstNo); Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -2655,6 +2655,10 @@ Code = bitc::CST_CODE_DSO_LOCAL_EQUIVALENT; Record.push_back(VE.getTypeID(Equiv->getGlobalValue()->getType())); Record.push_back(VE.getValueID(Equiv->getGlobalValue())); + } else if (const auto *Elig = dyn_cast(C)) { + Code = bitc::CST_CODE_VFE_ELIGIBLE_VALUE; + Record.push_back(VE.getTypeID(Elig->getGlobalValue()->getType())); + Record.push_back(VE.getValueID(Elig->getGlobalValue())); } else { #ifndef NDEBUG C->dump(); Index: llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp =================================================================== --- llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -2487,6 +2487,9 @@ if (const auto *Equiv = dyn_cast(CV)) return getObjFileLowering().lowerDSOLocalEquivalent(Equiv, TM); + if (const VFEEligibleValue *VE = dyn_cast(CV)) + return MCSymbolRefExpr::create(getSymbol(VE->getGlobalValue()), Ctx); + const ConstantExpr *CE = dyn_cast(CV); if (!CE) { llvm_unreachable("Unknown constant value to lower!"); Index: llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp =================================================================== --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -1615,6 +1615,9 @@ if (const auto *Equiv = dyn_cast(C)) return getValue(Equiv->getGlobalValue()); + if (const auto *VE = dyn_cast(C)) + return getValue(VE->getGlobalValue()); + VectorType *VecTy = cast(V->getType()); // Now that we know the number and type of the elements, get that number of Index: llvm/lib/IR/AsmWriter.cpp =================================================================== --- llvm/lib/IR/AsmWriter.cpp +++ llvm/lib/IR/AsmWriter.cpp @@ -1458,6 +1458,13 @@ return; } + if (const auto *VE = dyn_cast(CV)) { + Out << "vfe_eligible "; + WriteAsOperandInternal(Out, VE->getGlobalValue(), &TypePrinter, Machine, + Context); + return; + } + if (const ConstantArray *CA = dyn_cast(CV)) { Type *ETy = CA->getType()->getElementType(); Out << '['; Index: llvm/lib/IR/Constants.cpp =================================================================== --- llvm/lib/IR/Constants.cpp +++ llvm/lib/IR/Constants.cpp @@ -536,6 +536,9 @@ case Constant::DSOLocalEquivalentVal: delete static_cast(C); break; + case Constant::VFEEligibleValueVal: + delete static_cast(C); + break; case Constant::UndefValueVal: delete static_cast(C); break; @@ -1937,6 +1940,50 @@ return nullptr; } +VFEEligibleValue *VFEEligibleValue::get(GlobalValue *GV) { + VFEEligibleValue *&VE = GV->getContext().pImpl->VFEEligibleValues[GV]; + if (!VE) + VE = new VFEEligibleValue(GV); + + assert(VE->getGlobalValue() == GV && + "VFEEligibleValue does not match the expected global value"); + return VE; +} + +VFEEligibleValue::VFEEligibleValue(GlobalValue *GV) + : Constant(GV->getType(), Value::VFEEligibleValueVal, &Op<0>(), 1) { + setOperand(0, GV); +} + +/// Remove the constant from the constant table. +void VFEEligibleValue::destroyConstantImpl() { + const GlobalValue *GV = getGlobalValue(); + GV->getContext().pImpl->VFEEligibleValues.erase(GV); +} + +Value *VFEEligibleValue::handleOperandChangeImpl(Value *From, Value *To) { + assert(From == getGlobalValue() && "Changing value does not match operand."); + assert(isa(To) && + "Can only replace the operands with a global value"); + + if (cast(To)->isNullValue()) + return To; + + auto *GV = cast(To); + VFEEligibleValue *&NewVE = getContext().pImpl->VFEEligibleValues[GV]; + if (NewVE) + return llvm::ConstantExpr::getBitCast(NewVE, getType()); + + getContext().pImpl->VFEEligibleValues.erase(getGlobalValue()); + NewVE = this; + setOperand(0, GV); + + if (GV->getType() != getType()) + mutateType(GV->getType()); + + return nullptr; +} + //---- ConstantExpr::get() implementations. // Index: llvm/lib/IR/LLVMContextImpl.h =================================================================== --- llvm/lib/IR/LLVMContextImpl.h +++ llvm/lib/IR/LLVMContextImpl.h @@ -1430,6 +1430,8 @@ DenseMap DSOLocalEquivalents; + DenseMap VFEEligibleValues; + ConstantUniqueMap ExprConstants; ConstantUniqueMap InlineAsms; Index: llvm/lib/Transforms/IPO/GlobalDCE.cpp =================================================================== --- llvm/lib/Transforms/IPO/GlobalDCE.cpp +++ llvm/lib/Transforms/IPO/GlobalDCE.cpp @@ -130,8 +130,9 @@ // If this is a dep from a vtable to a virtual function, and we have // complete information about all virtual call sites which could call // though this vtable, then skip it, because the call site information will - // be more precise. - if (VFESafeVTables.count(GVU) && isa(&GV)) { + // be more precise. Only ignore deps that are are marked as vfe_eligible. + if (VFESafeVTables.count(GVU) && isa(&GV) && + EligibleMap[GVU].contains(&GV)) { LLVM_DEBUG(dbgs() << "Ignoring dep " << GVU->getName() << " -> " << GV.getName() << "\n"); continue; @@ -157,6 +158,22 @@ } } +static void findAllVFEEligibleValues(Constant *I, + SmallPtrSet &Results) { + for (Use &U : I->operands()) { + Constant *Op = cast(U); + if (auto *GV = dyn_cast(Op)) { + continue; + } + + if (auto *VE = dyn_cast(Op)) { + Results.insert(VE->getGlobalValue()); + } + + findAllVFEEligibleValues(Op, Results); + } +} + void GlobalDCEPass::ScanVTables(Module &M) { SmallVector Types; LLVM_DEBUG(dbgs() << "Building type info -> vtable map\n"); @@ -187,6 +204,10 @@ TypeIdMap[TypeID].insert(std::make_pair(&GV, Offset)); } + SmallPtrSet EligibleFuncs; + findAllVFEEligibleValues(&GV, EligibleFuncs); + EligibleMap[&GV] = EligibleFuncs; + // If the type corresponding to the vtable is private to this translation // unit, we know that we can see all virtual functions which might use it, // so VFE is safe. @@ -217,7 +238,11 @@ return; } - auto Callee = dyn_cast(Ptr->stripPointerCasts()); + GlobalValue *Callee = nullptr; + auto VE = dyn_cast(Ptr->stripPointerCasts()); + if (VE) Callee = VE->getGlobalValue(); + else Callee = dyn_cast(Ptr->stripPointerCasts()); + if (!Callee) { LLVM_DEBUG(dbgs() << "vtable entry is not function pointer!\n"); VFESafeVTables.erase(VTable); @@ -439,6 +464,7 @@ GVDependencies.clear(); ComdatMembers.clear(); TypeIdMap.clear(); + EligibleMap.clear(); VFESafeVTables.clear(); if (Changed) Index: llvm/lib/Transforms/Utils/ValueMapper.cpp =================================================================== --- llvm/lib/Transforms/Utils/ValueMapper.cpp +++ llvm/lib/Transforms/Utils/ValueMapper.cpp @@ -449,6 +449,12 @@ DSOLocalEquivalent::get(Func), NewTy); } + if (const auto *VE = dyn_cast(C)) { + auto *Val = mapValue(VE->getGlobalValue()); + GlobalValue *GV = cast(Val); + return getVM()[VE] = VFEEligibleValue::get(GV); + } + auto mapValueOrNull = [this](Value *V) { auto Mapped = mapValue(V); assert((Mapped || (Flags & RF_NullMapMissingGlobalValues)) && Index: llvm/test/Transforms/GlobalDCE/virtual-functions-base-call.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-base-call.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-base-call.ll @@ -25,11 +25,11 @@ %struct.A = type { i32 (...)** } %struct.B = type { %struct.A } -; CHECK: @_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*)] } -@_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 +; CHECK: @_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] } +@_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 -; CHECK: @_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* @_ZN1B3fooEv to i8*)] } -@_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !type !3, !type !4, !vcall_visibility !2 +; CHECK: @_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] } +@_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !type !3, !type !4, !vcall_visibility !2 ; CHECK: define internal i32 @_ZN1A3fooEv( define internal i32 @_ZN1A3fooEv(%struct.A* nocapture readnone %this) { Index: llvm/test/Transforms/GlobalDCE/virtual-functions-base-pointer-call.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-base-pointer-call.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-base-pointer-call.ll @@ -32,10 +32,10 @@ %struct.A = type { i32 (...)** } %struct.B = type { %struct.A } -; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A3fooEi to i8*), i8* null] } -@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A3fooEi to i8*), i8* bitcast (i32 (%struct.A*, float)* @_ZN1A3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 -; CHECK: @_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B3fooEi to i8*), i8* null] } -@_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B3fooEi to i8*), i8* bitcast (i32 (%struct.B*, float)* @_ZN1B3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !type !4, !type !5, !type !6, !vcall_visibility !3 +; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* vfe_eligible @_ZN1A3fooEi to i8*), i8* null] } +@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* vfe_eligible @_ZN1A3fooEi to i8*), i8* bitcast (i32 (%struct.A*, float)* vfe_eligible @_ZN1A3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 +; CHECK: @_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* vfe_eligible @_ZN1B3fooEi to i8*), i8* null] } +@_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* vfe_eligible @_ZN1B3fooEi to i8*), i8* bitcast (i32 (%struct.B*, float)* vfe_eligible @_ZN1B3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !type !4, !type !5, !type !6, !vcall_visibility !3 ; CHECK: define internal i32 @_ZN1A3fooEi( Index: llvm/test/Transforms/GlobalDCE/virtual-functions-derived-call.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-derived-call.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-derived-call.ll @@ -26,10 +26,10 @@ %struct.B = type { %struct.A } ; CHECK: @_ZTV1A = internal unnamed_addr constant { [3 x i8*] } zeroinitializer -@_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 +@_ZTV1A = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 -; CHECK: @_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* @_ZN1B3fooEv to i8*)] } -@_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !type !3, !type !4, !vcall_visibility !2 +; CHECK: @_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] } +@_ZTV1B = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !type !3, !type !4, !vcall_visibility !2 ; CHECK-NOT: define internal i32 @_ZN1A3fooEv( define internal i32 @_ZN1A3fooEv(%struct.A* nocapture readnone %this) { Index: llvm/test/Transforms/GlobalDCE/virtual-functions-derived-pointer-call.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-derived-pointer-call.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-derived-pointer-call.ll @@ -35,9 +35,9 @@ %struct.B = type { %struct.A } ; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } zeroinitializer -@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* @_ZN1A3fooEi to i8*), i8* bitcast (i32 (%struct.A*, float)* @_ZN1A3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 -; CHECK: @_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B3fooEi to i8*), i8* null] } -@_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* @_ZN1B3fooEi to i8*), i8* bitcast (i32 (%struct.B*, float)* @_ZN1B3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !type !4, !type !5, !type !6, !vcall_visibility !3 +@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*, i32)* vfe_eligible @_ZN1A3fooEi to i8*), i8* bitcast (i32 (%struct.A*, float)* vfe_eligible @_ZN1A3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 +; CHECK: @_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* vfe_eligible @_ZN1B3fooEi to i8*), i8* null] } +@_ZTV1B = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.B*, i32)* vfe_eligible @_ZN1B3fooEi to i8*), i8* bitcast (i32 (%struct.B*, float)* vfe_eligible @_ZN1B3barEf to i8*)] }, align 8, !type !0, !type !1, !type !2, !type !4, !type !5, !type !6, !vcall_visibility !3 ; CHECK-NOT: define internal i32 @_ZN1A3fooEi( Index: llvm/test/Transforms/GlobalDCE/virtual-functions-non-vfunc-entries.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/GlobalDCE/virtual-functions-non-vfunc-entries.ll @@ -0,0 +1,47 @@ +; RUN: opt < %s -globaldce -S | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" + +declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) + +; A vtable that contains a non-nfunc entry, @regular_non_virtual_func, which should not participate in VFE. +@vtable = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [ + i8* bitcast (void ()* vfe_eligible @vfunc1_live to i8*), + i8* bitcast (void ()* vfe_eligible @vfunc2_dead to i8*), + i8* bitcast (void ()* @regular_non_virtual_func to i8*) +]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} +!0 = !{i64 0, !"vfunc1.type"} +!1 = !{i64 8, !"vfunc2.type"} + +; CHECK: @vtable = internal unnamed_addr constant { [3 x i8*] } { [3 x i8*] [ +; CHECK-SAME: i8* bitcast (void ()* vfe_eligible @vfunc1_live to i8*), +; CHECK-SAME: i8* null, +; CHECK-SAME: i8* bitcast (void ()* @regular_non_virtual_func to i8*) +; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 + +; (1) vfunc1_live is referenced from @main, stays alive +define internal void @vfunc1_live() { + ; CHECK: define internal void @vfunc1_live( + ret void +} + +; (2) vfunc2_dead is never referenced, gets removed and vtable slot is null'd +define internal void @vfunc2_dead() { + ; CHECK-NOT: define internal void @vfunc2_dead( + ret void +} + +; (3) regular, non-virtual function that just happens to be referenced from the vtable data structure, should stay alive +define internal void @regular_non_virtual_func() { + ; CHECK: define internal void @regular_non_virtual_func( + ret void +} + +define void @main() { + %1 = ptrtoint { [3 x i8*] }* @vtable to i64 ; to keep @vtable alive + %2 = tail call { i8*, i1 } @llvm.type.checked.load(i8* null, i32 0, metadata !"vfunc1.type") + ret void +} + +!999 = !{i32 1, !"Virtual Function Elim", i32 1} +!llvm.module.flags = !{!999} Index: llvm/test/Transforms/GlobalDCE/virtual-functions-novfe.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-novfe.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-novfe.ll @@ -18,8 +18,8 @@ %struct.A = type { i32 (...)** } ; We should retain @_ZN1A3barEv in the vtable. -; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* @_ZN1A3barEv to i8*)] } -@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* @_ZN1A3barEv to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 +; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3barEv to i8*)] } +@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3barEv to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 ; A::foo is called, so must be retained. ; CHECK: define internal i32 @_ZN1A3fooEv( Index: llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-bad.ll @@ -5,11 +5,11 @@ declare { i8*, i1 } @llvm.type.checked.load(i8*, i32, metadata) @vtable = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [ - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc1 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc2 to i64), i64 ptrtoint ({ [3 x i32] }* @vtable to i64)) to i32), ; a "bad" relative pointer because it's base is not the @vtable symbol - i32 trunc (i64 sub (i64 ptrtoint (void ()* @weird_ref_1 to i64), i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @weird_ref_1 to i64), i64 ptrtoint (void ()* @weird_ref_2 to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 0, !"vfunc1.type"} !1 = !{i64 4, !"vfunc2.type"} Index: llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers-gep.ll @@ -8,15 +8,15 @@ @vtable = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ i32 42, i32 1337, - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2_dead to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc2_dead to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 8, !"vfunc1.type"} !1 = !{i64 12, !"vfunc2.type"} ; CHECK: @vtable = internal unnamed_addr constant { [4 x i32] } { [4 x i32] [ -; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), -; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32) +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc1_live to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32), +; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @vtable, i32 0, i32 0, i32 2) to i64)) to i32) ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 ; (1) vfunc1_live is referenced from @main, stays alive Index: llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-relative-pointers.ll @@ -6,15 +6,15 @@ ; A vtable with "relative pointers", slots don't contain pointers to implementations, but instead have an i32 offset from the vtable itself to the implementation. @vtable = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), - i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc2_dead to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc2_dead to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) ]}, align 8, !type !0, !type !1, !vcall_visibility !{i64 2} !0 = !{i64 0, !"vfunc1.type"} !1 = !{i64 4, !"vfunc2.type"} ; CHECK: @vtable = internal unnamed_addr constant { [2 x i32] } { [2 x i32] [ -; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), -; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) +; CHECK-SAME: i32 trunc (i64 sub (i64 ptrtoint (void ()* vfe_eligible @vfunc1_live to i64), i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32), +; CHECK-SAME: i32 trunc (i64 sub (i64 0, i64 ptrtoint ({ [2 x i32] }* @vtable to i64)) to i32) ; CHECK-SAME: ] }, align 8, !type !0, !type !1, !vcall_visibility !2 ; (1) vfunc1_live is referenced from @main, stays alive Index: llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-post-lto.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-post-lto.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-post-lto.ll @@ -8,7 +8,7 @@ %struct.A = type { i32 (...)** } -@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.A*)* @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 +@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 define internal void @_ZN1AC2Ev(%struct.A* %this) { entry: @@ -34,7 +34,7 @@ %struct.B = type { i32 (...)** } -@_ZTV1B = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.B*)* @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !3 +@_ZTV1B = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !3 define internal void @_ZN1BC2Ev(%struct.B* %this) { entry: @@ -60,7 +60,7 @@ %struct.C = type { i32 (...)** } -@_ZTV1C = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.C*)* @_ZN1C3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !4 +@_ZTV1C = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.C*)* vfe_eligible @_ZN1C3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !4 define internal void @_ZN1CC2Ev(%struct.C* %this) { entry: Index: llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-pre-lto.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-pre-lto.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions-visibility-pre-lto.ll @@ -8,7 +8,7 @@ %struct.A = type { i32 (...)** } -@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.A*)* @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 +@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 define internal void @_ZN1AC2Ev(%struct.A* %this) { entry: @@ -34,7 +34,7 @@ %struct.B = type { i32 (...)** } -@_ZTV1B = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.B*)* @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !3 +@_ZTV1B = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.B*)* vfe_eligible @_ZN1B3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !3 define internal void @_ZN1BC2Ev(%struct.B* %this) { entry: @@ -60,7 +60,7 @@ %struct.C = type { i32 (...)** } -@_ZTV1C = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.C*)* @_ZN1C3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !4 +@_ZTV1C = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (void (%struct.C*)* vfe_eligible @_ZN1C3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !4 define internal void @_ZN1CC2Ev(%struct.C* %this) { entry: Index: llvm/test/Transforms/GlobalDCE/virtual-functions.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/virtual-functions.ll +++ llvm/test/Transforms/GlobalDCE/virtual-functions.ll @@ -18,8 +18,8 @@ ; loaded. We replace it with null to keep the layout the same. Because it is at ; the end of the vtable we could potentially shrink the vtable, but don't ; currently do that. -; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*), i8* null] } -@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* @_ZN1A3barEv to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 +; CHECK: @_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*), i8* null] } +@_ZTV1A = internal unnamed_addr constant { [4 x i8*] } { [4 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*), i8* bitcast (i32 (%struct.A*)* vfe_eligible @_ZN1A3barEv to i8*)] }, align 8, !type !0, !type !1, !type !2, !vcall_visibility !3 ; A::foo is called, so must be retained. ; CHECK: define internal i32 @_ZN1A3fooEv( Index: llvm/test/Transforms/GlobalDCE/vtable-rtti.ll =================================================================== --- llvm/test/Transforms/GlobalDCE/vtable-rtti.ll +++ llvm/test/Transforms/GlobalDCE/vtable-rtti.ll @@ -8,9 +8,9 @@ %struct.A = type { i32 (...)** } -; CHECK: @_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* null] }, align 8, !type !0, !type !1, !vcall_visibility !2 +; CHECK: @_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* vfe_eligible @_ZTI1A to i8*), i8* null] }, align 8, !type !0, !type !1, !vcall_visibility !2 -@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast (void (%struct.A*)* @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 +@_ZTV1A = hidden unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* vfe_eligible @_ZTI1A to i8*), i8* bitcast (void (%struct.A*)* vfe_eligible @_ZN1A3fooEv to i8*)] }, align 8, !type !0, !type !1, !vcall_visibility !2 @_ZTS1A = hidden constant [3 x i8] c"1A\00", align 1 @_ZTI1A = hidden constant { i8*, i8* } { i8* bitcast (i8** getelementptr inbounds (i8*, i8** @_ZTVN10__cxxabiv117__class_type_infoE, i64 2) to i8*), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, align 8 Index: llvm/utils/emacs/llvm-mode.el =================================================================== --- llvm/utils/emacs/llvm-mode.el +++ llvm/utils/emacs/llvm-mode.el @@ -67,7 +67,7 @@ ;; Runtime preemption specifiers "dso_preemptable" "dso_local" "dso_local_equivalent" - "gc" "atomic" "volatile" "personality" "prologue" "section") 'symbols) . font-lock-keyword-face) + "gc" "atomic" "vfe_eligible" "volatile" "personality" "prologue" "section") 'symbols) . font-lock-keyword-face) ;; Arithmetic and Logical Operators `(,(regexp-opt '("add" "sub" "mul" "sdiv" "udiv" "urem" "srem" "and" "or" "xor" "setne" "seteq" "setlt" "setgt" "setle" "setge") 'symbols) . font-lock-keyword-face) Index: llvm/utils/vim/syntax/llvm.vim =================================================================== --- llvm/utils/vim/syntax/llvm.vim +++ llvm/utils/vim/syntax/llvm.vim @@ -191,6 +191,7 @@ \ uselistorder \ uselistorder_bb \ uwtable + \ vfe_eligible \ volatile \ weak \ weak_odr