Skip to content

Commit

Permalink
Utils: Add utility pass to lower ifuncs
Browse files Browse the repository at this point in the history
Create a global constructor which will initialize a global table of
function pointers. For now, this is only used as a reduction technique
for llvm-reduce.

In the future this may be useful to support ifunc on systems where the
program loader doesn't natively support it.
  • Loading branch information
arsenm committed Jan 18, 2023
1 parent 76d3e1a commit e7cd42f
Show file tree
Hide file tree
Showing 12 changed files with 610 additions and 0 deletions.
28 changes: 28 additions & 0 deletions llvm/include/llvm/Transforms/Utils/LowerIFunc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===-- LowerIFunc.h --------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_TRANSFORMS_UTILS_LOWERIFUNC_H
#define LLVM_TRANSFORMS_UTILS_LOWERIFUNC_H

#include "llvm/IR/PassManager.h"

namespace llvm {

/// Pass to replace calls to ifuncs with indirect calls. This could be used to
/// support ifunc on systems where the program loader does not natively support
/// it. Constant initializer uses of ifuncs are not handled.
class LowerIFuncPass : public PassInfoMixin<LowerIFuncPass> {
public:
LowerIFuncPass() = default;

PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};

} // end namespace llvm

#endif // LLVM_TRANSFORMS_UTILS_LOWERIFUNC_H
16 changes: 16 additions & 0 deletions llvm/include/llvm/Transforms/Utils/ModuleUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/GlobalIFunc.h"
#include "llvm/Support/Alignment.h"
#include "llvm/Support/MemoryBufferRef.h"
#include <utility> // for std::pair
Expand Down Expand Up @@ -125,6 +126,21 @@ std::string getUniqueModuleId(Module *M);
void embedBufferInModule(Module &M, MemoryBufferRef Buf, StringRef SectionName,
Align Alignment = Align(1));

/// Lower all calls to ifuncs by replacing uses with indirect calls loaded out
/// of a global table initialized in a global constructor. This will introduce
/// one constructor function and adds it to llvm.global_ctors. The constructor
/// will call the resolver function once for each ifunc.
///
/// Leaves any unhandled constant initializer uses as-is.
///
/// If \p IFuncsToLower is empty, all ifuncs in the module will be lowered.
/// If \p IFuncsToLower is non-empty, only the selected ifuncs will be lowered.
///
/// The processed ifuncs without remaining users will be removed from the
/// module.
bool lowerGlobalIFuncUsersAsGlobalCtor(
Module &M, ArrayRef<GlobalIFunc *> IFuncsToLower = {});

class CallInst;
namespace VFABI {
/// Overwrite the Vector Function ABI variants attribute with the names provide
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@
#include "llvm/Transforms/Utils/LoopSimplify.h"
#include "llvm/Transforms/Utils/LoopVersioning.h"
#include "llvm/Transforms/Utils/LowerGlobalDtors.h"
#include "llvm/Transforms/Utils/LowerIFunc.h"
#include "llvm/Transforms/Utils/LowerInvoke.h"
#include "llvm/Transforms/Utils/LowerSwitch.h"
#include "llvm/Transforms/Utils/Mem2Reg.h"
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Passes/PassRegistry.def
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ MODULE_PASS("invalidate<all>", InvalidateAllAnalysesPass())
MODULE_PASS("iroutliner", IROutlinerPass())
MODULE_PASS("print-ir-similarity", IRSimilarityAnalysisPrinterPass(dbgs()))
MODULE_PASS("lower-global-dtors", LowerGlobalDtorsPass())
MODULE_PASS("lower-ifunc", LowerIFuncPass())
MODULE_PASS("lowertypetests", LowerTypeTestsPass())
MODULE_PASS("metarenamer", MetaRenamerPass())
MODULE_PASS("mergefunc", MergeFunctionsPass())
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Transforms/Utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ add_llvm_component_library(LLVMTransformUtils
LoopVersioning.cpp
LowerAtomic.cpp
LowerGlobalDtors.cpp
LowerIFunc.cpp
LowerInvoke.cpp
LowerMemIntrinsics.cpp
LowerSwitch.cpp
Expand Down
27 changes: 27 additions & 0 deletions llvm/lib/Transforms/Utils/LowerIFunc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===- LowerIFunc.cpp -----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements replacing calls to ifuncs by introducing indirect calls.
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/Utils/LowerIFunc.h"
#include "llvm/IR/Module.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"

using namespace llvm;

/// Replace all call users of ifuncs in the module.
PreservedAnalyses LowerIFuncPass::run(Module &M, ModuleAnalysisManager &AM) {
if (M.ifunc_empty())
return PreservedAnalyses::all();

lowerGlobalIFuncUsersAsGlobalCtor(M, {});
return PreservedAnalyses::none();
}
99 changes: 99 additions & 0 deletions llvm/lib/Transforms/Utils/ModuleUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,102 @@ void llvm::embedBufferInModule(Module &M, MemoryBufferRef Buf,

appendToCompilerUsed(M, GV);
}

bool llvm::lowerGlobalIFuncUsersAsGlobalCtor(
Module &M, ArrayRef<GlobalIFunc *> FilteredIFuncsToLower) {
SmallVector<GlobalIFunc *, 32> AllIFuncs;
ArrayRef<GlobalIFunc *> IFuncsToLower = FilteredIFuncsToLower;
if (FilteredIFuncsToLower.empty()) { // Default to lowering all ifuncs
for (GlobalIFunc &GI : M.ifuncs())
AllIFuncs.push_back(&GI);
IFuncsToLower = AllIFuncs;
}

bool UnhandledUsers = false;
LLVMContext &Ctx = M.getContext();
const DataLayout &DL = M.getDataLayout();

PointerType *TableEntryTy =
Ctx.supportsTypedPointers()
? PointerType::get(Type::getInt8Ty(Ctx), DL.getProgramAddressSpace())
: PointerType::get(Ctx, DL.getProgramAddressSpace());

ArrayType *FuncPtrTableTy =
ArrayType::get(TableEntryTy, IFuncsToLower.size());

Align PtrAlign = DL.getABITypeAlign(TableEntryTy);

// Create a global table of function pointers we'll initialize in a global
// constructor.
auto *FuncPtrTable = new GlobalVariable(
M, FuncPtrTableTy, false, GlobalValue::InternalLinkage,
PoisonValue::get(FuncPtrTableTy), "", nullptr,
GlobalVariable::NotThreadLocal, DL.getDefaultGlobalsAddressSpace());
FuncPtrTable->setAlignment(PtrAlign);

// Create a function to initialize the function pointer table.
Function *NewCtor = Function::Create(
FunctionType::get(Type::getVoidTy(Ctx), false), Function::InternalLinkage,
DL.getProgramAddressSpace(), "", &M);

BasicBlock *BB = BasicBlock::Create(Ctx, "", NewCtor);
IRBuilder<> InitBuilder(BB);

size_t TableIndex = 0;
for (GlobalIFunc *GI : IFuncsToLower) {
Function *ResolvedFunction = GI->getResolverFunction();

// We don't know what to pass to a resolver function taking arguments
//
// FIXME: Is this even valid? clang and gcc don't complain but this
// probably should be invalid IR. We could just pass through undef.
if (!std::empty(ResolvedFunction->getFunctionType()->params())) {
LLVM_DEBUG(dbgs() << "Not lowering ifunc resolver function "
<< ResolvedFunction->getName() << " with parameters\n");
UnhandledUsers = true;
continue;
}

// Initialize the function pointer table.
CallInst *ResolvedFunc = InitBuilder.CreateCall(ResolvedFunction);
Value *Casted = InitBuilder.CreatePointerCast(ResolvedFunc, TableEntryTy);
Constant *GEP = cast<Constant>(InitBuilder.CreateConstInBoundsGEP2_32(
FuncPtrTableTy, FuncPtrTable, 0, TableIndex++));
InitBuilder.CreateAlignedStore(Casted, GEP, PtrAlign);

// Update all users to load a pointer from the global table.
for (User *User : make_early_inc_range(GI->users())) {
Instruction *UserInst = dyn_cast<Instruction>(User);
if (!UserInst) {
// TODO: Should handle constantexpr casts in user instructions. Probably
// can't do much about constant initializers.
UnhandledUsers = true;
continue;
}

IRBuilder<> UseBuilder(UserInst);
LoadInst *ResolvedTarget =
UseBuilder.CreateAlignedLoad(TableEntryTy, GEP, PtrAlign);
Value *ResolvedCast =
UseBuilder.CreatePointerCast(ResolvedTarget, GI->getType());
UserInst->replaceUsesOfWith(GI, ResolvedCast);
}

// If we handled all users, erase the ifunc.
if (GI->use_empty())
GI->eraseFromParent();
}

InitBuilder.CreateRetVoid();

PointerType *ConstantDataTy = Ctx.supportsTypedPointers()
? PointerType::get(Type::getInt8Ty(Ctx), 0)
: PointerType::get(Ctx, 0);

// TODO: Is this the right priority? Probably should be before any other
// constructors?
const int Priority = 10;
appendToGlobalCtors(M, NewCtor, Priority,
ConstantPointerNull::get(ConstantDataTy));
return UnhandledUsers;
}
57 changes: 57 additions & 0 deletions llvm/test/Transforms/LowerIFunc/ifunc-alias.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals --include-generated-funcs
; RUN: opt -S -passes=lower-ifunc < %s | FileCheck %s

define ptr @resolver() {
ret ptr inttoptr (i64 333 to ptr)
}

@resolver_alias = alias ptr (), ptr @resolver
@ifunc_alias = alias ptr (), ptr @resolver_alias

@ifunc0_kept = ifunc float (i64), ptr @resolver_alias
@ifunc1_removed = ifunc float (i64), ptr @resolver_alias

@ifunc_def = ifunc float (i64), ptr @resolver
@alias_of_ifunc = alias float (i64), ptr @ifunc_def

define float @call_ifunc_aliasee(i64 %arg) {
%call = call float @ifunc1_removed(i64 %arg)
ret float %call
}

define float @call_alias_of_ifunc(i64 %arg) {
%call = call float @alias_of_ifunc(i64 %arg)
ret float %call
}
;.
; CHECK: @[[GLOB0:[0-9]+]] = internal global [3 x ptr] poison, align 8
; CHECK: @[[LLVM_GLOBAL_CTORS:[a-zA-Z0-9_$"\\.-]+]] = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 10, ptr @[[GLOB1:[0-9]+]], ptr null }]
; CHECK: @[[RESOLVER_ALIAS:[a-zA-Z0-9_$"\\.-]+]] = alias ptr (), ptr @resolver
; CHECK: @[[IFUNC_ALIAS:[a-zA-Z0-9_$"\\.-]+]] = alias ptr (), ptr @resolver_alias
; CHECK: @[[ALIAS_OF_IFUNC:[a-zA-Z0-9_$"\\.-]+]] = alias float (i64), ptr @ifunc_def
; CHECK: @[[IFUNC_DEF:[a-zA-Z0-9_$"\\.-]+]] = ifunc float (i64), ptr @resolver
;.
; CHECK-LABEL: define {{[^@]+}}@resolver(
; CHECK-NEXT: ret ptr inttoptr (i64 333 to ptr)
;
;
; CHECK-LABEL: define {{[^@]+}}@call_ifunc_aliasee(
; CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr getelementptr inbounds ([3 x ptr], ptr @[[GLOB0]], i32 0, i32 1), align 8
; CHECK-NEXT: [[CALL:%.*]] = call float [[TMP1]](i64 [[ARG:%.*]])
; CHECK-NEXT: ret float [[CALL]]
;
;
; CHECK-LABEL: define {{[^@]+}}@call_alias_of_ifunc(
; CHECK-NEXT: [[CALL:%.*]] = call float @alias_of_ifunc(i64 [[ARG:%.*]])
; CHECK-NEXT: ret float [[CALL]]
;
;
; CHECK-LABEL: define {{[^@]+}}@1(
; CHECK-NEXT: [[TMP1:%.*]] = call ptr @resolver()
; CHECK-NEXT: store ptr [[TMP1]], ptr @[[GLOB0]], align 8
; CHECK-NEXT: [[TMP2:%.*]] = call ptr @resolver()
; CHECK-NEXT: store ptr [[TMP2]], ptr getelementptr inbounds ([3 x ptr], ptr @[[GLOB0]], i32 0, i32 1), align 8
; CHECK-NEXT: [[TMP3:%.*]] = call ptr @resolver()
; CHECK-NEXT: store ptr [[TMP3]], ptr getelementptr inbounds ([3 x ptr], ptr @[[GLOB0]], i32 0, i32 2), align 8
; CHECK-NEXT: ret void
;
34 changes: 34 additions & 0 deletions llvm/test/Transforms/LowerIFunc/ifunc-nonsense-resolvers.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals --include-generated-funcs
; RUN: opt -S -passes=lower-ifunc < %s | FileCheck %s

@ifunc_with_arg = ifunc void (), ptr @resolver_with_arg

; Test a resolver with an argument (which probably should not be legal
; IR).
define ptr @resolver_with_arg(i64 %arg) {
%cast = inttoptr i64 %arg to ptr
ret ptr %cast
}

define void @call_with_arg() {
call void @ifunc_with_arg()
ret void
}
;.
; CHECK: @[[GLOB0:[0-9]+]] = internal global [1 x ptr] poison, align 8
; CHECK: @[[LLVM_GLOBAL_CTORS:[a-zA-Z0-9_$"\\.-]+]] = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 10, ptr @[[GLOB1:[0-9]+]], ptr null }]
; CHECK: @[[IFUNC_WITH_ARG:[a-zA-Z0-9_$"\\.-]+]] = ifunc void (), ptr @resolver_with_arg
;.
; CHECK-LABEL: define {{[^@]+}}@resolver_with_arg(
; CHECK-NEXT: [[CAST:%.*]] = inttoptr i64 [[ARG:%.*]] to ptr
; CHECK-NEXT: ret ptr [[CAST]]
;
;
; CHECK-LABEL: define {{[^@]+}}@call_with_arg(
; CHECK-NEXT: call void @ifunc_with_arg()
; CHECK-NEXT: ret void
;
;
; CHECK-LABEL: define {{[^@]+}}@1(
; CHECK-NEXT: ret void
;
Loading

0 comments on commit e7cd42f

Please sign in to comment.