12 changes: 6 additions & 6 deletions llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -879,16 +879,17 @@ bool AsmPrinter::doFinalization(Module &M) {
bool IsThumb = (Arch == Triple::thumb || Arch == Triple::thumbeb);
MCInst TrapInst;
TM.getSubtargetImpl()->getInstrInfo()->getTrap(TrapInst);
unsigned LogAlignment = llvm::Log2_64(JITI->entryByteAlignment());

// Emit the right section for these functions.
OutStreamer.SwitchSection(OutContext.getObjectFileInfo()->getTextSection());
for (const auto &KV : JITI->getTables()) {
uint64_t Count = 0;
for (const auto &FunPair : KV.second) {
// Emit the function labels to make this be a function entry point.
MCSymbol *FunSym =
OutContext.GetOrCreateSymbol(FunPair.second->getName());
OutStreamer.EmitSymbolAttribute(FunSym, MCSA_Global);
// FIXME: JumpTableInstrInfo should store information about the required
// alignment of table entries and the size of the padding instruction.
EmitAlignment(3);
EmitAlignment(LogAlignment);
if (IsThumb)
OutStreamer.EmitThumbFunc(FunSym);
if (MAI->hasDotTypeDotSizeDirective())
Expand All @@ -910,10 +911,9 @@ bool AsmPrinter::doFinalization(Module &M) {
}

// Emit enough padding instructions to fill up to the next power of two.
// This assumes that the trap instruction takes 8 bytes or fewer.
uint64_t Remaining = NextPowerOf2(Count) - Count;
for (uint64_t C = 0; C < Remaining; ++C) {
EmitAlignment(3);
EmitAlignment(LogAlignment);
OutStreamer.EmitInstruction(TrapInst, getSubtargetInfo());
}

Expand Down
1 change: 1 addition & 0 deletions llvm/lib/CodeGen/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_llvm_library(LLVMCodeGen
ExecutionDepsFix.cpp
ExpandISelPseudos.cpp
ExpandPostRAPseudos.cpp
ForwardControlFlowIntegrity.cpp
GCMetadata.cpp
GCMetadataPrinter.cpp
GCStrategy.cpp
Expand Down
375 changes: 375 additions & 0 deletions llvm/lib/CodeGen/ForwardControlFlowIntegrity.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
//===-- ForwardControlFlowIntegrity.cpp: Forward-Edge CFI -----------------===//
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief A pass that instruments code with fast checks for indirect calls and
/// hooks for a function to check violations.
///
//===----------------------------------------------------------------------===//

#define DEBUG_TYPE "cfi"

#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Analysis/JumpInstrTableInfo.h"
#include "llvm/CodeGen/ForwardControlFlowIntegrity.h"
#include "llvm/CodeGen/JumpInstrTables.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/CallSite.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Verifier.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

STATISTIC(NumCFIIndirectCalls,
"Number of indirect call sites rewritten by the CFI pass");

char ForwardControlFlowIntegrity::ID = 0;
INITIALIZE_PASS_BEGIN(ForwardControlFlowIntegrity, "forward-cfi",
"Control-Flow Integrity", true, true)
INITIALIZE_PASS_DEPENDENCY(JumpInstrTableInfo);
INITIALIZE_PASS_DEPENDENCY(JumpInstrTables);
INITIALIZE_PASS_END(ForwardControlFlowIntegrity, "forward-cfi",
"Control-Flow Integrity", true, true)

ModulePass *llvm::createForwardControlFlowIntegrityPass() {
return new ForwardControlFlowIntegrity();
}

ModulePass *llvm::createForwardControlFlowIntegrityPass(
JumpTable::JumpTableType JTT, CFIntegrity CFIType, bool CFIEnforcing,
StringRef CFIFuncName) {
return new ForwardControlFlowIntegrity(JTT, CFIType, CFIEnforcing,
CFIFuncName);
}

// Checks to see if a given CallSite is making an indirect call, including
// cases where the indirect call is made through a bitcast.
static bool isIndirectCall(CallSite &CS) {
if (CS.getCalledFunction())
return false;

// Check the value to see if it is merely a bitcast of a function. In
// this case, it will translate to a direct function call in the resulting
// assembly, so we won't treat it as an indirect call here.
const Value *V = CS.getCalledValue();
if (const ConstantExpr *CE = dyn_cast<ConstantExpr>(V)) {
return !(CE->isCast() && isa<Function>(CE->getOperand(0)));
}

// Otherwise, since we know it's a call, it must be an indirect call
return true;
}

static const char cfi_failure_func_name[] = "__llvm_cfi_pointer_warning";
static const char cfi_func_name_prefix[] = "__llvm_cfi_function_";

ForwardControlFlowIntegrity::ForwardControlFlowIntegrity()
: ModulePass(ID), IndirectCalls(), JTType(JumpTable::Single),
CFIType(CFIntegrity::Sub), CFIEnforcing(false), CFIFuncName("") {
initializeForwardControlFlowIntegrityPass(*PassRegistry::getPassRegistry());
}

ForwardControlFlowIntegrity::ForwardControlFlowIntegrity(
JumpTable::JumpTableType JTT, CFIntegrity CFIType, bool CFIEnforcing,
std::string CFIFuncName)
: ModulePass(ID), IndirectCalls(), JTType(JTT), CFIType(CFIType),
CFIEnforcing(CFIEnforcing), CFIFuncName(CFIFuncName) {
initializeForwardControlFlowIntegrityPass(*PassRegistry::getPassRegistry());
}

ForwardControlFlowIntegrity::~ForwardControlFlowIntegrity() {}

void ForwardControlFlowIntegrity::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequired<JumpInstrTableInfo>();
AU.addRequired<JumpInstrTables>();
}

void ForwardControlFlowIntegrity::getIndirectCalls(Module &M) {
// To get the indirect calls, we iterate over all functions and iterate over
// the list of basic blocks in each. We extract a total list of indirect calls
// before modifying any of them, since our modifications will modify the list
// of basic blocks.
for (Function &F : M) {
for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
CallSite CS(&I);
if (!(CS && isIndirectCall(CS)))
continue;

Value *CalledValue = CS.getCalledValue();

// Don't rewrite this instruction if the indirect call is actually just
// inline assembly, since our transformation will generate an invalid
// module in that case.
if (isa<InlineAsm>(CalledValue))
continue;

IndirectCalls.push_back(&I);
}
}
}
}

void ForwardControlFlowIntegrity::updateIndirectCalls(Module &M,
CFITables &CFIT) {
Type *Int64Ty = Type::getInt64Ty(M.getContext());
for (Instruction *I : IndirectCalls) {
CallSite CS(I);
Value *CalledValue = CS.getCalledValue();

// Get the function type for this call and look it up in the tables.
Type *VTy = CalledValue->getType();
PointerType *PTy = dyn_cast<PointerType>(VTy);
Type *EltTy = PTy->getElementType();
FunctionType *FunTy = dyn_cast<FunctionType>(EltTy);
FunctionType *TransformedTy = JumpInstrTables::transformType(JTType, FunTy);
++NumCFIIndirectCalls;
Constant *JumpTableStart = nullptr;
Constant *JumpTableMask = nullptr;
Constant *JumpTableSize = nullptr;

// Some call sites have function types that don't correspond to any
// address-taken function in the module. This happens when function pointers
// are passed in from external code.
auto it = CFIT.find(TransformedTy);
if (it == CFIT.end()) {
// In this case, make sure that the function pointer will change by
// setting the mask and the start to be 0 so that the transformed
// function is 0.
JumpTableStart = ConstantInt::get(Int64Ty, 0);
JumpTableMask = ConstantInt::get(Int64Ty, 0);
JumpTableSize = ConstantInt::get(Int64Ty, 0);
} else {
JumpTableStart = it->second.StartValue;
JumpTableMask = it->second.MaskValue;
JumpTableSize = it->second.Size;
}

rewriteFunctionPointer(M, I, CalledValue, JumpTableStart, JumpTableMask,
JumpTableSize);
}

return;
}

bool ForwardControlFlowIntegrity::runOnModule(Module &M) {
JumpInstrTableInfo *JITI = &getAnalysis<JumpInstrTableInfo>();
Type *Int64Ty = Type::getInt64Ty(M.getContext());
Type *VoidPtrTy = Type::getInt8PtrTy(M.getContext());

// JumpInstrTableInfo stores information about the alignment of each entry.
// The alignment returned by JumpInstrTableInfo is alignment in bytes, not
// in the exponent.
ByteAlignment = JITI->entryByteAlignment();
LogByteAlignment = llvm::Log2_64(ByteAlignment);

// Set up tables for control-flow integrity based on information about the
// jump-instruction tables.
CFITables CFIT;
for (const auto &KV : JITI->getTables()) {
uint64_t Size = static_cast<uint64_t>(KV.second.size());
uint64_t TableSize = NextPowerOf2(Size);

int64_t MaskValue = ((TableSize << LogByteAlignment) - 1) & -ByteAlignment;
Constant *JumpTableMaskValue = ConstantInt::get(Int64Ty, MaskValue);
Constant *JumpTableSize = ConstantInt::get(Int64Ty, Size);

// The base of the table is defined to be the first jumptable function in
// the table.
Function *First = KV.second.begin()->second;
Constant *JumpTableStartValue = ConstantExpr::getBitCast(First, VoidPtrTy);
CFIT[KV.first].StartValue = JumpTableStartValue;
CFIT[KV.first].MaskValue = JumpTableMaskValue;
CFIT[KV.first].Size = JumpTableSize;
}

if (CFIT.empty())
return false;

getIndirectCalls(M);

if (!CFIEnforcing) {
addWarningFunction(M);
}

// Update the instructions with the check and the indirect jump through our
// table.
updateIndirectCalls(M, CFIT);

return true;
}

void ForwardControlFlowIntegrity::addWarningFunction(Module &M) {
PointerType *CharPtrTy = Type::getInt8PtrTy(M.getContext());

// Get the type of the Warning Function: void (i8*, i8*),
// where the first argument is the name of the function in which the violation
// occurs, and the second is the function pointer that violates CFI.
SmallVector<Type *, 2> WarningFunArgs;
WarningFunArgs.push_back(CharPtrTy);
WarningFunArgs.push_back(CharPtrTy);
FunctionType *WarningFunTy =
FunctionType::get(Type::getVoidTy(M.getContext()), WarningFunArgs, false);

if (!CFIFuncName.empty()) {
Constant *FailureFun = M.getOrInsertFunction(CFIFuncName, WarningFunTy);
if (!FailureFun)
report_fatal_error("Could not get or insert the function specified by"
" -cfi-func-name");
} else {
// The default warning function swallows the warning and lets the call
// continue, since there's no generic way for it to print out this
// information.
Function *WarningFun = M.getFunction(cfi_failure_func_name);
if (!WarningFun) {
WarningFun =
Function::Create(WarningFunTy, GlobalValue::LinkOnceAnyLinkage,
cfi_failure_func_name, &M);
}

BasicBlock *Entry =
BasicBlock::Create(M.getContext(), "entry", WarningFun, 0);
ReturnInst::Create(M.getContext(), Entry);
}
}

void ForwardControlFlowIntegrity::rewriteFunctionPointer(
Module &M, Instruction *I, Value *FunPtr, Constant *JumpTableStart,
Constant *JumpTableMask, Constant *JumpTableSize) {
IRBuilder<> TempBuilder(I);

Type *OrigFunType = FunPtr->getType();

BasicBlock *CurBB = cast<BasicBlock>(I->getParent());
Function *CurF = cast<Function>(CurBB->getParent());
Type *Int64Ty = Type::getInt64Ty(M.getContext());

Value *TI = TempBuilder.CreatePtrToInt(FunPtr, Int64Ty);
Value *TStartInt = TempBuilder.CreatePtrToInt(JumpTableStart, Int64Ty);

Value *NewFunPtr = nullptr;
Value *Check = nullptr;
switch (CFIType) {
case CFIntegrity::Sub: {
// This is the subtract, mask, and add version.
// Subtract from the base.
Value *Sub = TempBuilder.CreateSub(TI, TStartInt);

// Mask the difference to force this to be a table offset.
Value *And = TempBuilder.CreateAnd(Sub, JumpTableMask);

// Add it back to the base.
Value *Result = TempBuilder.CreateAdd(And, TStartInt);

// Convert it back into a function pointer that we can call.
NewFunPtr = TempBuilder.CreateIntToPtr(Result, OrigFunType);
break;
}
case CFIntegrity::Ror: {
// This is the subtract and rotate version.
// Rotate right by the alignment value. The optimizer should recognize
// this sequence as a rotation.

// This cast is safe, since unsigned is always a subset of uint64_t.
uint64_t LogByteAlignment64 = static_cast<uint64_t>(LogByteAlignment);
Constant *RightShift = ConstantInt::get(Int64Ty, LogByteAlignment64);
Constant *LeftShift = ConstantInt::get(Int64Ty, 64 - LogByteAlignment64);

// Subtract from the base.
Value *Sub = TempBuilder.CreateSub(TI, TStartInt);

// Create the equivalent of a rotate-right instruction.
Value *Shr = TempBuilder.CreateLShr(Sub, RightShift);
Value *Shl = TempBuilder.CreateShl(Sub, LeftShift);
Value *Or = TempBuilder.CreateOr(Shr, Shl);

// Perform unsigned comparison to check for inclusion in the table.
Check = TempBuilder.CreateICmpULT(Or, JumpTableSize);
NewFunPtr = FunPtr;
break;
}
case CFIntegrity::Add: {
// This is the mask and add version.
// Mask the function pointer to turn it into an offset into the table.
Value *And = TempBuilder.CreateAnd(TI, JumpTableMask);

// Then or this offset to the base and get the pointer value.
Value *Result = TempBuilder.CreateAdd(And, TStartInt);

// Convert it back into a function pointer that we can call.
NewFunPtr = TempBuilder.CreateIntToPtr(Result, OrigFunType);
break;
}
}

if (!CFIEnforcing) {
// If a check hasn't been added (in the rotation version), then check to see
// if it's the same as the original function. This check determines whether
// or not we call the CFI failure function.
if (!Check)
Check = TempBuilder.CreateICmpEQ(NewFunPtr, FunPtr);
BasicBlock *InvalidPtrBlock =
BasicBlock::Create(M.getContext(), "invalid.ptr", CurF, 0);
BasicBlock *ContinuationBB = CurBB->splitBasicBlock(I);

// Remove the unconditional branch that connects the two blocks.
TerminatorInst *TermInst = CurBB->getTerminator();
TermInst->eraseFromParent();

// Add a conditional branch that depends on the Check above.
BranchInst::Create(ContinuationBB, InvalidPtrBlock, Check, CurBB);

// Call the warning function for this pointer, then continue.
Instruction *BI = BranchInst::Create(ContinuationBB, InvalidPtrBlock);
insertWarning(M, InvalidPtrBlock, BI, FunPtr);
} else {
// Modify the instruction to call this value.
CallSite CS(I);
CS.setCalledFunction(NewFunPtr);
}
}

void ForwardControlFlowIntegrity::insertWarning(Module &M, BasicBlock *Block,
Instruction *I, Value *FunPtr) {
Function *ParentFun = cast<Function>(Block->getParent());

// Get the function to call right before the instruction.
Function *WarningFun = nullptr;
if (CFIFuncName.empty()) {
WarningFun = M.getFunction(cfi_failure_func_name);
} else {
WarningFun = M.getFunction(CFIFuncName);
}

assert(WarningFun && "Could not find the CFI failure function");

Type *VoidPtrTy = Type::getInt8PtrTy(M.getContext());

IRBuilder<> WarningInserter(I);
// Create a mergeable GlobalVariable containing the name of the function.
Value *ParentNameGV =
WarningInserter.CreateGlobalString(ParentFun->getName());
Value *ParentNamePtr = WarningInserter.CreateBitCast(ParentNameGV, VoidPtrTy);
Value *FunVoidPtr = WarningInserter.CreateBitCast(FunPtr, VoidPtrTy);
WarningInserter.CreateCall2(WarningFun, ParentNamePtr, FunVoidPtr);
}
13 changes: 7 additions & 6 deletions llvm/lib/CodeGen/JumpInstrTables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ void JumpInstrTables::getAnalysisUsage(AnalysisUsage &AU) const {

Function *JumpInstrTables::insertEntry(Module &M, Function *Target) {
FunctionType *OrigFunTy = Target->getFunctionType();
FunctionType *FunTy = transformType(OrigFunTy);
FunctionType *FunTy = transformType(JTType, OrigFunTy);

JumpMap::iterator it = Metadata.find(FunTy);
if (Metadata.end() == it) {
Expand Down Expand Up @@ -191,11 +191,12 @@ Function *JumpInstrTables::insertEntry(Module &M, Function *Target) {
}

bool JumpInstrTables::hasTable(FunctionType *FunTy) {
FunctionType *TransTy = transformType(FunTy);
FunctionType *TransTy = transformType(JTType, FunTy);
return Metadata.end() != Metadata.find(TransTy);
}

FunctionType *JumpInstrTables::transformType(FunctionType *FunTy) {
FunctionType *JumpInstrTables::transformType(JumpTable::JumpTableType JTT,
FunctionType *FunTy) {
// Returning nullptr forces all types into the same table, since all types map
// to the same type
Type *VoidPtrTy = Type::getInt8PtrTy(FunTy->getContext());
Expand All @@ -211,7 +212,7 @@ FunctionType *JumpInstrTables::transformType(FunctionType *FunTy) {
Type *Int32Ty = Type::getInt32Ty(FunTy->getContext());
FunctionType *VoidFnTy = FunctionType::get(
Type::getVoidTy(FunTy->getContext()), EmptyParams, false);
switch (JTType) {
switch (JTT) {
case JumpTable::Single:

return FunctionType::get(RetTy, EmptyParams, false);
Expand Down Expand Up @@ -253,10 +254,10 @@ FunctionType *JumpInstrTables::transformType(FunctionType *FunTy) {
bool JumpInstrTables::runOnModule(Module &M) {
JITI = &getAnalysis<JumpInstrTableInfo>();

// Get the set of jumptable-annotated functions.
// Get the set of jumptable-annotated functions that have their address taken.
DenseMap<Function *, Function *> Functions;
for (Function &F : M) {
if (F.hasFnAttribute(Attribute::JumpTable)) {
if (F.hasFnAttribute(Attribute::JumpTable) && F.hasAddressTaken()) {
assert(F.hasUnnamedAddr() &&
"Attribute 'jumptable' requires 'unnamed_addr'");
Functions[&F] = nullptr;
Expand Down
9 changes: 8 additions & 1 deletion llvm/lib/CodeGen/LLVMTargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@

#include "llvm/Target/TargetMachine.h"

#include "llvm/Analysis/JumpInstrTableInfo.h"
#include "llvm/Analysis/Passes.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/ForwardControlFlowIntegrity.h"
#include "llvm/CodeGen/JumpInstrTables.h"
#include "llvm/CodeGen/MachineFunctionAnalysis.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
Expand Down Expand Up @@ -143,8 +145,13 @@ bool LLVMTargetMachine::addPassesToEmitFile(PassManagerBase &PM,
AnalysisID StopAfter) {
// Passes to handle jumptable function annotations. These can't be handled at
// JIT time, so we don't add them directly to addPassesToGenerateCode.
PM.add(createJumpInstrTableInfoPass());
PM.add(createJumpInstrTableInfoPass(
getSubtargetImpl()->getInstrInfo()->getJumpInstrTableEntryBound()));
PM.add(createJumpInstrTablesPass(Options.JTType));
if (Options.FCFI)
PM.add(createForwardControlFlowIntegrityPass(
Options.JTType, Options.CFIType, Options.CFIEnforcing,
Options.getCFIFuncName()));

// Add common CodeGen passes.
MCContext *Context = addPassesToGenerateCode(this, PM, DisableVerify,
Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/CodeGen/TargetOptionsImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,10 @@ bool TargetOptions::HonorSignDependentRoundingFPMath() const {
StringRef TargetOptions::getTrapFunctionName() const {
return TrapFuncName;
}

/// getCFIFuncName - If this returns a non-empty string, then it is the name of
/// the function that gets called on CFI violations in CFI non-enforcing mode
/// (!TargetOptions::CFIEnforcing).
StringRef TargetOptions::getCFIFuncName() const {
return CFIFuncName;
}
23 changes: 0 additions & 23 deletions llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4489,29 +4489,6 @@ breakPartialRegDependency(MachineBasicBlock::iterator MI,
MI->addRegisterKilled(DReg, TRI, true);
}

void ARMBaseInstrInfo::getUnconditionalBranch(
MCInst &Branch, const MCSymbolRefExpr *BranchTarget) const {
if (Subtarget.isThumb())
Branch.setOpcode(ARM::tB);
else if (Subtarget.isThumb2())
Branch.setOpcode(ARM::t2B);
else
Branch.setOpcode(ARM::Bcc);

Branch.addOperand(MCOperand::CreateExpr(BranchTarget));
Branch.addOperand(MCOperand::CreateImm(ARMCC::AL));
Branch.addOperand(MCOperand::CreateReg(0));
}

void ARMBaseInstrInfo::getTrap(MCInst &MI) const {
if (Subtarget.isThumb())
MI.setOpcode(ARM::tTRAP);
else if (Subtarget.useNaClTrap())
MI.setOpcode(ARM::TRAPNaCl);
else
MI.setOpcode(ARM::TRAP);
}

bool ARMBaseInstrInfo::hasNOP() const {
return (Subtarget.getFeatureBits() & ARM::HasV6T2Ops) != 0;
}
Expand Down
6 changes: 0 additions & 6 deletions llvm/lib/Target/ARM/ARMBaseInstrInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,6 @@ class ARMBaseInstrInfo : public ARMGenInstrInfo {
void breakPartialRegDependency(MachineBasicBlock::iterator, unsigned,
const TargetRegisterInfo *TRI) const override;

void
getUnconditionalBranch(MCInst &Branch,
const MCSymbolRefExpr *BranchTarget) const override;

void getTrap(MCInst &MI) const override;

/// Get the number of addresses by LDM or VLDM or zero for unknown.
unsigned getNumLDMAddresses(const MachineInstr *MI) const;

Expand Down
16 changes: 16 additions & 0 deletions llvm/lib/Target/X86/X86InstrInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5477,16 +5477,32 @@ void X86InstrInfo::getNoopForMachoTarget(MCInst &NopInst) const {
NopInst.setOpcode(X86::NOOP);
}

// This code must remain in sync with getJumpInstrTableEntryBound in this class!
// In particular, getJumpInstrTableEntryBound must always return an upper bound
// on the encoding lengths of the instructions generated by
// getUnconditionalBranch and getTrap.
void X86InstrInfo::getUnconditionalBranch(
MCInst &Branch, const MCSymbolRefExpr *BranchTarget) const {
Branch.setOpcode(X86::JMP_4);
Branch.addOperand(MCOperand::CreateExpr(BranchTarget));
}

// This code must remain in sync with getJumpInstrTableEntryBound in this class!
// In particular, getJumpInstrTableEntryBound must always return an upper bound
// on the encoding lengths of the instructions generated by
// getUnconditionalBranch and getTrap.
void X86InstrInfo::getTrap(MCInst &MI) const {
MI.setOpcode(X86::TRAP);
}

// See getTrap and getUnconditionalBranch for conditions on the value returned
// by this function.
unsigned X86InstrInfo::getJumpInstrTableEntryBound() const {
// 5 bytes suffice: JMP_4 Symbol@PLT is uses 1 byte (E9) for the JMP_4 and 4
// bytes for the symbol offset. And TRAP is ud2, which is two bytes (0F 0B).
return 5;
}

bool X86InstrInfo::isHighLatencyDef(int opc) const {
switch (opc) {
default: return false;
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/X86/X86InstrInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ class X86InstrInfo final : public X86GenInstrInfo {

void getTrap(MCInst &MI) const override;

unsigned getJumpInstrTableEntryBound() const override;

bool isHighLatencyDef(int opc) const override;

bool hasHighOperandLatency(const InstrItineraryData *ItinData,
Expand Down
32 changes: 0 additions & 32 deletions llvm/test/CodeGen/ARM/jump_tables.ll

This file was deleted.

34 changes: 34 additions & 0 deletions llvm/test/CodeGen/X86/cfi_enforcing.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
; RUN: llc -mtriple=i386-unknown-linux-gnu -fcfi -cfi-enforcing <%s | FileCheck --check-prefix=X86 %s
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -fcfi -cfi-enforcing <%s | FileCheck --check-prefix=X86-64 %s

define void @indirect_fun() unnamed_addr jumptable {
ret void
}

define i32 @m(void ()* %fun) {
call void ()* %fun()
; CHECK: subl
; X86-64: andq $8,
; X86-64: leaq __llvm_jump_instr_table_0_1({{%[a-z0-9]+}}), [[REG:%[a-z0-9]+]]
; X86-64-NOT: callq __llvm_cfi_pointer_warning
; X86-64: callq *[[REG]]
; X86: andl $8,
; X86: leal __llvm_jump_instr_table_0_1({{%[a-z0-9]+}}), [[REG:%[a-z0-9]+]]
; X86-NOT: calll __llvm_cfi_pointer_warning
; X86: calll *[[REG]]
ret i32 0
}

define void ()* @get_fun() {
ret void ()* @indirect_fun
}

define i32 @main(i32 %argc, i8** %argv) {
%f = call void ()* ()* @get_fun()
%a = call i32 @m(void ()* %f)
ret i32 %a
}

; CHECK: .align 8
; CHECK: __llvm_jump_instr_table_0_1:
; CHECK: jmp indirect_fun@PLT
35 changes: 35 additions & 0 deletions llvm/test/CodeGen/X86/cfi_invoke.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
; RUN: llc <%s -fcfi -cfi-type=sub | FileCheck %s
; ModuleID = 'test.cc'
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

declare i32 @__gxx_personality_v0(...)

@_ZTIPKc = external constant i8*
@_ZTIi = external constant i8*

define void @f() unnamed_addr jumptable {
ret void
}

@a = global void ()* @f

; Make sure invoke gets targeted as well as regular calls
define void @_Z3foov(void ()* %f) uwtable ssp {
; CHECK-LABEL: _Z3foov:
entry:
invoke void %f()
to label %try.cont unwind label %lpad
; CHECK: callq __llvm_cfi_pointer_warning
; CHECK: callq *%rbx

lpad:
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*)
catch i8* bitcast (i8** @_ZTIi to i8*)
filter [1 x i8*] [i8* bitcast (i8** @_ZTIPKc to i8*)]
ret void

try.cont:
ret void
}

27 changes: 27 additions & 0 deletions llvm/test/CodeGen/X86/cfi_non_default_function.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
; RUN: llc -fcfi -cfi-func-name=cfi_new_failure <%s | FileCheck %s

target triple = "x86_64-unknown-linux-gnu"
define void @indirect_fun() unnamed_addr jumptable {
ret void
}

define i32 @m(void ()* %fun) {
; CHECK-LABEL: @m
call void ()* %fun()
; CHECK: callq cfi_new_failure
ret i32 0
}

define void ()* @get_fun() {
ret void ()* @indirect_fun
}

define i32 @main(i32 %argc, i8** %argv) {
%f = call void ()* ()* @get_fun()
%a = call i32 @m(void ()* %f)
ret i32 %a
}

; CHECK: .align 8
; CHECK: __llvm_jump_instr_table_0_1:
; CHECK: jmp indirect_fun@PLT
43 changes: 43 additions & 0 deletions llvm/test/CodeGen/X86/cfi_simple_indirect_call.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
; RUN: llc -fcfi -cfi-type=sub <%s | FileCheck --check-prefix=SUB %s
; RUN: llc -fcfi -cfi-type=add <%s | FileCheck --check-prefix=ADD %s
; RUN: llc -fcfi -cfi-type=ror <%s | FileCheck --check-prefix=ROR %s

target triple = "x86_64-unknown-linux-gnu"

define void @indirect_fun() unnamed_addr jumptable {
ret void
}

define i32 @m(void ()* %fun) {
call void ()* %fun()
; SUB: subl
; SUB: andq $8
; SUB-LABEL: leaq __llvm_jump_instr_table_0_1
; SUB-LABEL: callq __llvm_cfi_pointer_warning

; ROR: subq
; ROR: rolq $61
; ROR: testq
; ROR-LABEL: callq __llvm_cfi_pointer_warning

; ADD: andq $8
; ADD-LABEL: leaq __llvm_jump_instr_table_0_1
; ADD: cmpq
; ADD-LABEL: callq __llvm_cfi_pointer_warning
ret i32 0
}

define void ()* @get_fun() {
ret void ()* @indirect_fun
}

define i32 @main(i32 %argc, i8** %argv) {
%f = call void ()* ()* @get_fun()
%a = call i32 @m(void ()* %f)
ret i32 %a
}
; SUB: .text
; SUB: .align 8
; SUB-LABEL: .type __llvm_jump_instr_table_0_1,@function
; SUB-LABEL:__llvm_jump_instr_table_0_1:
; SUB-LABEL: jmp indirect_fun@PLT
1 change: 0 additions & 1 deletion llvm/test/CodeGen/X86/jump_table_alias.ll
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ define i32 @main(i32 %argc, i8** %argv) {
; There should only be one table, even though there are two GlobalAliases,
; because they both alias the same value.

; CHECK: .globl __llvm_jump_instr_table_0_1
; CHECK: .align 8, 0x90
; CHECK: .type __llvm_jump_instr_table_0_1,@function
; CHECK: __llvm_jump_instr_table_0_1:
Expand Down
29 changes: 29 additions & 0 deletions llvm/test/CodeGen/X86/jump_table_align.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
; RUN: llc -filetype=obj <%s -jump-table-type=single -o %t1
; RUN: llvm-objdump -triple=x86_64-unknown-linux-gnu -d %t1 | FileCheck %s
target triple = "x86_64-unknown-linux-gnu"
define i32 @f() unnamed_addr jumptable {
ret i32 0
}

define i32 @g(i8* %a) unnamed_addr jumptable {
ret i32 0
}

define void @h(void ()* %func) unnamed_addr jumptable {
ret void
}

define i32 @main() {
%g = alloca i32 (...)*, align 8
store i32 (...)* bitcast (i32 ()* @f to i32 (...)*), i32 (...)** %g, align 8
%1 = load i32 (...)** %g, align 8
%call = call i32 (...)* %1()
call void (void ()*)* @h(void ()* bitcast (void (void ()*)* @h to void ()*))
%a = call i32 (i32*)* bitcast (i32 (i8*)* @g to i32(i32*)*)(i32* null)
ret i32 %a
}

; Make sure that the padding from getJumpInstrTableEntryBound is right.
; CHECK: __llvm_jump_instr_table_0_1:
; CHECK-NEXT: e9 00 00 00 00 jmp 0
; CHECK-NEXT: 0f 1f 00 nopl (%rax)
9 changes: 3 additions & 6 deletions llvm/test/CodeGen/X86/jump_table_bitcast.ll
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,27 @@ define void @h(void ()* %func) unnamed_addr jumptable {
define i32 @main() {
%g = alloca i32 (...)*, align 8
store i32 (...)* bitcast (i32 ()* @f to i32 (...)*), i32 (...)** %g, align 8
; CHECK: movq $__llvm_jump_instr_table_0_[[ENTRY:1|2|3]], (%rsp)
; CHECK: movl $__llvm_jump_instr_table_0_[[ENTRY]], %ecx
; CHECK: movq $__llvm_jump_instr_table_0_[[ENTRY:1|2|3]],
; CHECK: movl $__llvm_jump_instr_table_0_[[ENTRY]],
%1 = load i32 (...)** %g, align 8
%call = call i32 (...)* %1()
call void (void ()*)* @h(void ()* bitcast (void (void ()*)* @h to void ()*))
; CHECK: movl $__llvm_jump_instr_table_0_{{1|2|3}}, %edi
; CHECK: movl $__llvm_jump_instr_table_0_{{1|2|3}},
; CHECK: callq h

%a = call i32 (i32*)* bitcast (i32 (i8*)* @g to i32(i32*)*)(i32* null)
; CHECK: callq g
ret i32 %a
}

; CHECK: .globl __llvm_jump_instr_table_0_1
; CHECK: .align 8, 0x90
; CHECK: .type __llvm_jump_instr_table_0_1,@function
; CHECK: __llvm_jump_instr_table_0_1:
; CHECK: jmp {{f|g|h}}@PLT
; CHECK: .globl __llvm_jump_instr_table_0_2
; CHECK: .align 8, 0x90
; CHECK: .type __llvm_jump_instr_table_0_2,@function
; CHECK: __llvm_jump_instr_table_0_2:
; CHECK: jmp {{f|g|h}}@PLT
; CHECK: .globl __llvm_jump_instr_table_0_3
; CHECK: .align 8, 0x90
; CHECK: .type __llvm_jump_instr_table_0_3,@function
; CHECK: __llvm_jump_instr_table_0_3:
Expand Down
45 changes: 14 additions & 31 deletions llvm/test/CodeGen/X86/jump_tables.ll
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ target triple = "x86_64-unknown-linux-gnu"

%struct.fun_struct = type { i32 (...)* }

@a = global [12 x i32 () *] [ i32 ()* bitcast (void ()* @indirect_fun to i32 ()*),
i32 ()* bitcast (void ()* @indirect_fun_match to i32 ()*),
i32 ()* bitcast (i32 ()* @indirect_fun_i32 to i32 ()*),
i32 ()* bitcast (i32 (i32)* @indirect_fun_i32_1 to i32 ()*),
i32 ()* bitcast (i32 (i32, i32)* @indirect_fun_i32_2 to i32 ()*),
i32 ()* bitcast (i32* (i32*, i32)* @indirect_fun_i32S_2 to i32 ()*),
i32 ()* bitcast (void (%struct.fun_struct)* @indirect_fun_struct to i32 ()*),
i32 ()* bitcast (void (i32 (...)*, i32)* @indirect_fun_fun to i32 ()*),
i32 ()* bitcast (i32 (i32 (...)*, i32)* @indirect_fun_fun_ret to i32 ()*),
i32 ()* bitcast (void ([19 x i8])* @indirect_fun_array to i32 ()*),
i32 ()* bitcast (void (<3 x i32>)* @indirect_fun_vec to i32 ()*),
i32 ()* bitcast (void (<4 x float>)* @indirect_fun_vec_2 to i32 ()*)
]

define void @indirect_fun() unnamed_addr jumptable {
ret void
}
Expand Down Expand Up @@ -74,62 +88,50 @@ define i32 @main(i32 %argc, i8** %argv) {
ret i32 %a
}

; SINGLE-DAG: .globl __llvm_jump_instr_table_0_1
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_1,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_1:
; SINGLE-DAG: jmp indirect_fun_array@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_2
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_2,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_2:
; SINGLE-DAG: jmp indirect_fun_i32_2@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_3
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_3,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_3:
; SINGLE-DAG: jmp indirect_fun_vec_2@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_4
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_4,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_4:
; SINGLE-DAG: jmp indirect_fun_i32S_2@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_5
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_5,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_5:
; SINGLE-DAG: jmp indirect_fun_struct@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_6
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_6,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_6:
; SINGLE-DAG: jmp indirect_fun_i32_1@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_7
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_7,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_7:
; SINGLE-DAG: jmp indirect_fun_i32@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_8
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_8,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_8:
; SINGLE-DAG: jmp indirect_fun_fun@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_9
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_9,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_9:
; SINGLE-DAG: jmp indirect_fun_fun_ret@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_10
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_10,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_10:
; SINGLE-DAG: jmp indirect_fun@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_11
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_11,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_11:
; SINGLE-DAG: jmp indirect_fun_match@PLT
; SINGLE-DAG: .globl __llvm_jump_instr_table_0_12
; SINGLE-DAG: .align 8, 0x90
; SINGLE-DAG: .type __llvm_jump_instr_table_0_12,@function
; SINGLE-DAG: __llvm_jump_instr_table_0_12:
Expand All @@ -144,82 +146,69 @@ define i32 @main(i32 %argc, i8** %argv) {
; SINGLE-DAG: ud2


; ARITY-DAG: .globl __llvm_jump_instr_table_2_1
; ARITY-DAG: .align 8, 0x90
; ARITY-DAG: .type __llvm_jump_instr_table_2_1,@function
; ARITY-DAG: __llvm_jump_instr_table_2_1:
; ARITY-DAG: jmp indirect_fun{{.*}}@PLT
; ARITY-DAG: .align 8, 0x90
; ARITY-DAG: ud2
; ARITY-DAG: .globl __llvm_jump_instr_table_0_1
; ARITY-DAG: .align 8, 0x90
; ARITY-DAG: .type __llvm_jump_instr_table_0_1,@function
; ARITY-DAG: __llvm_jump_instr_table_0_1:
; ARITY-DAG: jmp indirect_fun{{.*}}@PLT
; ARITY-DAG: .globl __llvm_jump_instr_table_1_1
; ARITY-DAG: .align 8, 0x90
; ARITY-DAG: .type __llvm_jump_instr_table_1_1,@function
; ARITY-DAG: __llvm_jump_instr_table_1_1:
; ARITY-DAG: jmp indirect_fun{{.*}}@PLT

; SIMPL-DAG: .globl __llvm_jump_instr_table_2_1
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: .type __llvm_jump_instr_table_2_1,@function
; SIMPL-DAG: __llvm_jump_instr_table_2_1:
; SIMPL-DAG: jmp indirect_fun{{.*}}@PLT
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: ud2
; SIMPL-DAG: .globl __llvm_jump_instr_table_0_1
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: .type __llvm_jump_instr_table_0_1,@function
; SIMPL-DAG: __llvm_jump_instr_table_0_1:
; SIMPL-DAG: jmp indirect_fun{{.*}}@PLT
; SIMPL-DAG: .globl __llvm_jump_instr_table_1_1
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: .type __llvm_jump_instr_table_1_1,@function
; SIMPL-DAG: __llvm_jump_instr_table_1_1:
; SIMPL-DAG: jmp indirect_fun{{.*}}@PLT
; SIMPL-DAG: .globl __llvm_jump_instr_table_3_1
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: .type __llvm_jump_instr_table_3_1,@function
; SIMPL-DAG: __llvm_jump_instr_table_3_1:
; SIMPL-DAG: jmp indirect_fun{{.*}}@PLT
; SIMPL-DAG: .globl __llvm_jump_instr_table_4_1
; SIMPL-DAG: .align 8, 0x90
; SIMPL-DAG: .type __llvm_jump_instr_table_4_1,@function
; SIMPL-DAG: __llvm_jump_instr_table_4_1:
; SIMPL-DAG: jmp indirect_fun{{.*}}@PLT


; FULL-DAG: .globl __llvm_jump_instr_table_10_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_10_1,@function
; FULL-DAG:__llvm_jump_instr_table_10_1:
; FULL-DAG: jmp indirect_fun_i32_1@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_9_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_9_1,@function
; FULL-DAG:__llvm_jump_instr_table_9_1:
; FULL-DAG: jmp indirect_fun_i32_2@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_7_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_7_1,@function
; FULL-DAG:__llvm_jump_instr_table_7_1:
; FULL-DAG: jmp indirect_fun_i32S_2@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_3_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_3_1,@function
; FULL-DAG:__llvm_jump_instr_table_3_1:
; FULL-DAG: jmp indirect_fun_vec_2@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_2_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_2_1,@function
; FULL-DAG:__llvm_jump_instr_table_2_1:
Expand All @@ -228,42 +217,36 @@ define i32 @main(i32 %argc, i8** %argv) {
; FULL-DAG: ud2
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_8_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_8_1,@function
; FULL-DAG:__llvm_jump_instr_table_8_1:
; FULL-DAG: jmp indirect_fun_i32@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_1_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_1_1,@function
; FULL-DAG:__llvm_jump_instr_table_1_1:
; FULL-DAG: jmp indirect_fun_array@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_0_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_0_1,@function
; FULL-DAG:__llvm_jump_instr_table_0_1:
; FULL-DAG: jmp indirect_fun_vec@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_6_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_6_1,@function
; FULL-DAG:__llvm_jump_instr_table_6_1:
; FULL-DAG: jmp indirect_fun_struct@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_5_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_5_1,@function
; FULL-DAG:__llvm_jump_instr_table_5_1:
; FULL-DAG: jmp indirect_fun_fun@PLT
; FULL-DAG: .align 8, 0x90
; FULL-DAG: ud2
; FULL-DAG: .globl __llvm_jump_instr_table_4_1
; FULL-DAG: .align 8, 0x90
; FULL-DAG: .type __llvm_jump_instr_table_4_1,@function
; FULL-DAG:__llvm_jump_instr_table_4_1:
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/LTO/jump-table-type.ll
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
; RUN: llvm-lto -o %t2 %t1 -jump-table-type=arity
; RUN: llvm-nm %t2 | FileCheck %s

; CHECK: T __llvm_jump_instr_table_0_1
; CHECK: T __llvm_jump_instr_table_1_1
; CHECK: t __llvm_jump_instr_table_0_1
; CHECK: t __llvm_jump_instr_table_1_1

target triple = "x86_64-unknown-linux-gnu"

Expand Down