diff --git a/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h new file mode 100644 index 0000000000..b9333163e1 --- /dev/null +++ b/include/phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#ifndef PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H +#define PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/IR/Function.h" + +namespace psr { +class LLVMProjectIRDB; + +/// \brief Provides utilities to model calls through known external callback +/// brokers. +/// +/// The inserted model keeps the original broker call and adds a regular call to +/// the callback argument, so later analyses still see an ordinary call-site. +class ExternCallbackModel { +public: + static constexpr llvm::StringLiteral ModelPrefix = "__psrExternCallbackModel"; + + /// Rewrites all supported external callback call-sites in \p IRDB. + /// + /// \returns the number of rewritten call-sites. + static size_t rewriteCalls(LLVMProjectIRDB &IRDB); + + /// Returns true, if a function was generated by the ExternCallbackModel. + [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept; +}; +} // namespace psr + +#endif // PHASAR_PHASARLLVM_CONTROLFLOW_EXTERNCALLBACKMODEL_H diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h index 1503b1d318..e34c906723 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h @@ -24,6 +24,10 @@ class Resolver; /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -40,9 +44,24 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, + Soundness S = Soundness::Soundy); + /// Constructs a call-graph using the given CGResolver to resolve indirect /// calls. /// +/// This overload does not mutate the IRDB. Use +/// buildLLVMBasedCallGraphWithExternCallbackModels() if PhASAR should insert +/// its model functions first. +/// /// Uses a fixpoint iteration, if /// `CGResolver.mutatesHelperAnalysisInformation()` returns true and the /// soundness S is not Soundness::Unsound. @@ -59,6 +78,27 @@ buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); +/// May insert model functions for known external callback brokers before the +/// call-graph is built. +/// If CGResolver owns helper-analysis state that should include generated model +/// functions, rewrite the IRDB before constructing that state or use a CGType +/// overload. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S = Soundness::Soundy); + +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, @@ -70,6 +110,17 @@ buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, LLVMAliasInfoRef PT = nullptr, Soundness S = Soundness::Soundy); +/// Kept for compatibility with LLVMBasedICFG. See the constructor of +/// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, +/// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, +/// Soundness, bool) for more information. +[[nodiscard]] LLVMBasedCallGraph +buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, + LLVMAliasInfoRef PT = nullptr, + Soundness S = Soundness::Soundy); + /// Kept for compatibility with LLVMBasedICFG. See the constructor of /// LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *, CallGraphAnalysisType, /// llvm::ArrayRef, DIBasedTypeHierarchy *, LLVMAliasInfoRef, diff --git a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h index 49b47b98ae..e1aa638a55 100644 --- a/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h +++ b/include/phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h @@ -20,6 +20,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/ICFGBase.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" @@ -74,6 +75,9 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// \param IncludeGlobals Properly include global constructors/destructors /// into the ICFG, if true. Requires to generate artificial functions into the /// IRDB. True by default + /// + /// Note that LLVMBasedICFG may also insert model functions for known external + /// callback brokers, independent of IncludeGlobals. explicit LLVMBasedICFG(LLVMProjectIRDB *IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints = {}, DIBasedTypeHierarchy *TH = nullptr, @@ -132,7 +136,8 @@ class LLVMBasedICFG : public LLVMBasedCFG, public ICFGBase { /// Returns true, if a function was generated by phasar. [[nodiscard]] static bool isPhasarGenerated(const llvm::Function &F) noexcept { - return GlobalCtorsDtorsModel::isPhasarGenerated(F); + return GlobalCtorsDtorsModel::isPhasarGenerated(F) || + ExternCallbackModel::isPhasarGenerated(F); } using CFGBase::print; diff --git a/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp new file mode 100644 index 0000000000..096143621b --- /dev/null +++ b/lib/PhasarLLVM/ControlFlow/ExternCallbackModel.cpp @@ -0,0 +1,565 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" + +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" +#include "phasar/Utils/Logger.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Metadata.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" + +#include +#include +#include +#include + +namespace { + +enum class CallbackTypeKind { Metadata, PthreadCreate, OpenMP }; + +struct CallbackSpec { + int CalleeArg = -1; + llvm::SmallVector CallbackArgs; + bool PassVarArgs = false; + CallbackTypeKind TypeKind = CallbackTypeKind::Metadata; +}; + +using CallbackSpecs = llvm::SmallVector; + +struct RewriteCandidate { + llvm::CallBase *Call = nullptr; + llvm::Function *Broker = nullptr; + CallbackSpecs Specs; +}; + +std::optional getIntFromMetadata(const llvm::Metadata *MD) { + const auto *ConstMD = llvm::dyn_cast_or_null(MD); + if (!ConstMD) { + return std::nullopt; + } + + const auto *ConstInt = llvm::dyn_cast(ConstMD->getValue()); + if (!ConstInt) { + return std::nullopt; + } + + return ConstInt->getSExtValue(); +} + +std::optional getBoolFromMetadata(const llvm::Metadata *MD) { + auto Int = getIntFromMetadata(MD); + if (!Int) { + return std::nullopt; + } + + return *Int != 0; +} + +std::optional parseCallbackTuple(const llvm::MDNode &Tuple) { + if (Tuple.getNumOperands() < 2) { + return std::nullopt; + } + + auto CalleeArg = getIntFromMetadata(Tuple.getOperand(0).get()); + auto PassVarArgs = + getBoolFromMetadata(Tuple.getOperand(Tuple.getNumOperands() - 1).get()); + if (!CalleeArg || !PassVarArgs) { + return std::nullopt; + } + + CallbackSpec Spec; + Spec.CalleeArg = static_cast(*CalleeArg); + Spec.PassVarArgs = *PassVarArgs; + + for (unsigned Idx = 1; Idx + 1 < Tuple.getNumOperands(); ++Idx) { + auto CallbackArg = getIntFromMetadata(Tuple.getOperand(Idx).get()); + if (!CallbackArg) { + return std::nullopt; + } + Spec.CallbackArgs.push_back(static_cast(*CallbackArg)); + } + + return Spec; +} + +CallbackSpecs parseCallbackMetadata(const llvm::Function &Broker) { + CallbackSpecs Specs; + const auto *CallbackMD = Broker.getMetadata("callback"); + if (!CallbackMD) { + return Specs; + } + + for (const auto &Op : CallbackMD->operands()) { + const auto *Tuple = llvm::dyn_cast_or_null(Op.get()); + if (!Tuple) { + continue; + } + + if (auto Spec = parseCallbackTuple(*Tuple)) { + Specs.push_back(std::move(*Spec)); + } + } + + return Specs; +} + +CallbackSpecs getKnownCallbackSpecs(llvm::StringRef BrokerName) { + CallbackSpecs Specs; + + auto Known = llvm::StringSwitch(BrokerName) + .Case("pthread_create", 0) + .Case("__kmpc_fork_call", 1) + .Case("__kmpc_fork_teams", 1) + .Default(-1); + + switch (Known) { + case 0: + // int pthread_create(..., void *(*start_routine)(void *), void *arg) + Specs.push_back( + CallbackSpec{2, {3}, false, CallbackTypeKind::PthreadCreate}); + break; + case 1: + // void __kmpc_fork_call/__kmpc_fork_teams(ident_t *, i32 argc, + // microtask_t, ...) + // + // OpenMP passes gtid and bound pointers before the user payload. + Specs.push_back(CallbackSpec{2, {-1, -1}, true, CallbackTypeKind::OpenMP}); + break; + default: + break; + } + + return Specs; +} + +CallbackSpecs getCallbackSpecs(const llvm::Function &Broker) { + auto Specs = parseCallbackMetadata(Broker); + if (!Specs.empty()) { + return Specs; + } + + return getKnownCallbackSpecs(Broker.getName()); +} + +const llvm::Function *getDirectFunction(const llvm::Value *V) { + if (!V) { + return nullptr; + } + + return llvm::dyn_cast(V->stripPointerCastsAndAliases()); +} + +llvm::Type *getPointerType(llvm::LLVMContext &Ctx) { + return llvm::PointerType::get(Ctx, 0); +} + +llvm::Type *getCallbackArgType(const llvm::CallBase &BrokerCall, int ArgNo) { + if (ArgNo < 0) { + return getPointerType(BrokerCall.getContext()); + } + + if (static_cast(ArgNo) >= BrokerCall.arg_size()) { + return nullptr; + } + + return BrokerCall.getArgOperand(static_cast(ArgNo))->getType(); +} + +bool appendCallbackParamTypes(llvm::SmallVectorImpl &ParamTys, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + for (int ArgNo : Spec.CallbackArgs) { + auto *Ty = getCallbackArgType(BrokerCall, ArgNo); + if (!Ty) { + return false; + } + ParamTys.push_back(Ty); + } + + if (Spec.PassVarArgs) { + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + for (unsigned ArgNo = FirstVarArg; ArgNo < BrokerCall.arg_size(); ++ArgNo) { + ParamTys.push_back(BrokerCall.getArgOperand(ArgNo)->getType()); + } + } + + return true; +} + +llvm::FunctionType *inferFallbackCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + switch (Spec.TypeKind) { + case CallbackTypeKind::PthreadCreate: + return llvm::FunctionType::get(getPointerType(Broker.getContext()), + {getPointerType(Broker.getContext())}, + /*isVarArg*/ false); + case CallbackTypeKind::OpenMP: { + llvm::SmallVector ParamTys; + if (!appendCallbackParamTypes(ParamTys, BrokerCall, Spec)) { + return nullptr; + } + return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), + ParamTys, /*isVarArg*/ false); + } + case CallbackTypeKind::Metadata: { + llvm::SmallVector ParamTys; + if (!appendCallbackParamTypes(ParamTys, BrokerCall, Spec)) { + return nullptr; + } + return llvm::FunctionType::get(llvm::Type::getVoidTy(Broker.getContext()), + ParamTys, /*isVarArg*/ false); + } + } + llvm_unreachable("Unhandled callback type kind"); +} + +llvm::FunctionType *inferCallbackType(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + if (Spec.CalleeArg < 0 || + static_cast(Spec.CalleeArg) >= BrokerCall.arg_size()) { + return nullptr; + } + + const auto *CallbackFn = + getDirectFunction(BrokerCall.getArgOperand(Spec.CalleeArg)); + if (!CallbackFn) { + return inferFallbackCallbackType(Broker, BrokerCall, Spec); + } + + return CallbackFn->getFunctionType(); +} + +bool hasBrokerArg(const llvm::CallBase &BrokerCall, int ArgNo) noexcept { + return ArgNo >= 0 && static_cast(ArgNo) < BrokerCall.arg_size(); +} + +unsigned getCallbackArgCount(const llvm::CallBase &BrokerCall, + const CallbackSpec &Spec) { + unsigned Count = Spec.CallbackArgs.size(); + if (!Spec.PassVarArgs) { + return Count; + } + + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + if (FirstVarArg >= BrokerCall.arg_size()) { + return Count; + } + return Count + BrokerCall.arg_size() - FirstVarArg; +} + +bool hasExpectedCallbackArgCount(const llvm::FunctionType &CBTy, + unsigned ArgCount) noexcept { + if (CBTy.isVarArg()) { + return ArgCount >= CBTy.getNumParams(); + } + return ArgCount == CBTy.getNumParams(); +} + +bool validateSpec(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, const CallbackSpec &Spec) { + if (!hasBrokerArg(BrokerCall, Spec.CalleeArg)) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": callback callee " + << "argument " << Spec.CalleeArg + << " is unavailable in " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + + auto CalleeArg = static_cast(Spec.CalleeArg); + if (!BrokerCall.getArgOperand(CalleeArg)->getType()->isPointerTy()) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": callback callee " + << "argument " << Spec.CalleeArg + << " is not a pointer in " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + + bool HasMissingCallbackArgs = false; + for (int ArgNo : Spec.CallbackArgs) { + if (ArgNo < 0) { + continue; + } + + if (!hasBrokerArg(BrokerCall, ArgNo)) { + PHASAR_LOG_LEVEL(WARNING, "Cannot model extern callback broker " + << Broker.getName() + << ": callback argument " << ArgNo + << " is unavailable in " + << psr::llvmIRToString(&BrokerCall)); + HasMissingCallbackArgs = true; + } + } + if (HasMissingCallbackArgs) { + return false; + } + + auto *CBTy = inferCallbackType(Broker, BrokerCall, Spec); + if (!CBTy) { + PHASAR_LOG_LEVEL(WARNING, "Cannot infer callback function type for " + << psr::llvmIRToString(&BrokerCall)); + return false; + } + + unsigned ArgCount = getCallbackArgCount(BrokerCall, Spec); + if (!hasExpectedCallbackArgCount(*CBTy, ArgCount)) { + PHASAR_LOG_LEVEL( + WARNING, "Cannot model extern callback broker " + << Broker.getName() << ": expected " + << CBTy->getNumParams() << " callback argument(s), got " + << ArgCount << " in " << psr::llvmIRToString(&BrokerCall)); + return false; + } + + return true; +} + +bool validateSpecs(const llvm::Function &Broker, + const llvm::CallBase &BrokerCall, + llvm::ArrayRef Specs) { + for (const auto &Spec : Specs) { + if (!validateSpec(Broker, BrokerCall, Spec)) { + return false; + } + } + + return true; +} + +llvm::Value *adaptValue(llvm::IRBuilder<> &IRB, llvm::Value *Value, + llvm::Type *ExpectedTy) { + if (Value->getType() == ExpectedTy) { + return Value; + } + + if (Value->getType()->isIntegerTy() && ExpectedTy->isIntegerTy()) { + return IRB.CreateIntCast(Value, ExpectedTy, /*isSigned*/ false); + } + + if (Value->getType()->isPointerTy() && ExpectedTy->isIntegerTy()) { + return IRB.CreatePtrToInt(Value, ExpectedTy); + } + + if (Value->getType()->isIntegerTy() && ExpectedTy->isPointerTy()) { + return IRB.CreateIntToPtr(Value, ExpectedTy); + } + + PHASAR_LOG_LEVEL(WARNING, "Cannot adapt extern callback argument " + << psr::llvmIRToString(Value) << " to " + << psr::llvmTypeToString(ExpectedTy, true)); + return llvm::UndefValue::get(ExpectedTy); +} + +llvm::SmallVector +collectCallbackArgs(llvm::IRBuilder<> &IRB, llvm::Function &Model, + const llvm::CallBase &BrokerCall, llvm::FunctionType &CBTy, + const CallbackSpec &Spec) { + llvm::SmallVector Args; + + auto GetBrokerArg = [&Model](unsigned ArgNo) -> llvm::Argument * { + auto It = Model.arg_begin(); + std::advance(It, ArgNo); + return &*It; + }; + + for (int ArgNo : Spec.CallbackArgs) { + if (ArgNo < 0 || static_cast(ArgNo) >= BrokerCall.arg_size()) { + Args.push_back(nullptr); + continue; + } + + Args.push_back(GetBrokerArg(static_cast(ArgNo))); + } + + if (Spec.PassVarArgs) { + unsigned FirstVarArg = BrokerCall.getFunctionType()->getNumParams(); + for (unsigned ArgNo = FirstVarArg; ArgNo < BrokerCall.arg_size(); ++ArgNo) { + Args.push_back(GetBrokerArg(ArgNo)); + } + } + + for (unsigned Idx = 0, + End = std::min(Args.size(), CBTy.getNumParams()); + Idx < End; ++Idx) { + auto *ExpectedTy = CBTy.getParamType(Idx); + if (!Args[Idx]) { + Args[Idx] = llvm::UndefValue::get(ExpectedTy); + continue; + } + + Args[Idx] = adaptValue(IRB, Args[Idx], ExpectedTy); + } + + return Args; +} + +llvm::FunctionType *createModelType(llvm::CallBase &BrokerCall) { + llvm::SmallVector ParamTys; + ParamTys.reserve(BrokerCall.arg_size()); + for (auto &Arg : BrokerCall.args()) { + ParamTys.push_back(Arg->getType()); + } + + return llvm::FunctionType::get(BrokerCall.getType(), ParamTys, + /*isVarArg*/ false); +} + +std::string createModelName(const psr::LLVMProjectIRDB &IRDB, + const llvm::Function &Broker, + const llvm::CallBase &BrokerCall) { + std::string Name; + llvm::raw_string_ostream OS(Name); + OS << psr::ExternCallbackModel::ModelPrefix << '.' << Broker.getName() << '.' + << IRDB.getInstructionId(&BrokerCall); + return Name; +} + +llvm::Value *createOriginalBrokerCall(llvm::IRBuilder<> &IRB, + llvm::Function &Model, + llvm::CallBase &BrokerCall) { + llvm::SmallVector Args; + Args.reserve(BrokerCall.arg_size()); + for (auto &Arg : Model.args()) { + Args.push_back(&Arg); + } + + llvm::SmallVector OperandBundles; + BrokerCall.getOperandBundlesAsDefs(OperandBundles); + + auto *Call = + IRB.CreateCall(BrokerCall.getFunctionType(), + BrokerCall.getCalledOperand(), Args, OperandBundles); + Call->setCallingConv(BrokerCall.getCallingConv()); + Call->setAttributes(BrokerCall.getAttributes()); + Call->setDebugLoc(BrokerCall.getDebugLoc()); + Call->copyMetadata(BrokerCall); + return Call; +} + +void createCallbackCall(llvm::IRBuilder<> &IRB, llvm::Function &Model, + const llvm::Function &Broker, + llvm::CallBase &BrokerCall, const CallbackSpec &Spec) { + auto *CBTy = inferCallbackType(Broker, BrokerCall, Spec); + if (!CBTy) { + PHASAR_LOG_LEVEL(WARNING, "Cannot infer callback function type for " + << psr::llvmIRToString(&BrokerCall)); + return; + } + + auto CalleeArg = static_cast(Spec.CalleeArg); + llvm::Value *CallbackCallee = Model.getArg(CalleeArg); + if (const auto *DirectCallback = + getDirectFunction(BrokerCall.getArgOperand(CalleeArg))) { + CallbackCallee = const_cast(DirectCallback); + } + auto Args = collectCallbackArgs(IRB, Model, BrokerCall, *CBTy, Spec); + auto *Call = IRB.CreateCall(CBTy, CallbackCallee, Args); + Call->setDebugLoc(BrokerCall.getDebugLoc()); +} + +llvm::Function *createModel(psr::LLVMProjectIRDB &IRDB, llvm::Function &Broker, + llvm::CallBase &BrokerCall, + llvm::ArrayRef Specs) { + auto *ModelTy = createModelType(BrokerCall); + auto *Model = llvm::Function::Create( + ModelTy, llvm::GlobalValue::PrivateLinkage, + createModelName(IRDB, Broker, BrokerCall), IRDB.getModule()); + Model->setCallingConv(BrokerCall.getCallingConv()); + + auto *EntryBB = llvm::BasicBlock::Create(Model->getContext(), "entry", Model); + llvm::IRBuilder<> IRB(EntryBB); + + auto *BrokerRet = createOriginalBrokerCall(IRB, *Model, BrokerCall); + + for (const auto &Spec : Specs) { + createCallbackCall(IRB, *Model, Broker, BrokerCall, Spec); + } + + if (ModelTy->getReturnType()->isVoidTy()) { + IRB.CreateRetVoid(); + } else { + IRB.CreateRet(adaptValue(IRB, BrokerRet, ModelTy->getReturnType())); + } + + IRDB.insertFunction(Model); + return Model; +} + +bool shouldSkipFunction(const llvm::Function &F) { + return F.isDeclaration() || psr::ExternCallbackModel::isPhasarGenerated(F); +} + +} // namespace + +namespace psr { + +size_t ExternCallbackModel::rewriteCalls(LLVMProjectIRDB &IRDB) { + llvm::SmallVector BrokerCalls; + + for (auto &F : *IRDB.getModule()) { + if (shouldSkipFunction(F)) { + continue; + } + + for (auto &Inst : llvm::instructions(F)) { + auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto *Broker = llvm::dyn_cast( + Call->getCalledOperand()->stripPointerCastsAndAliases()); + if (!Broker) { + continue; + } + + auto Specs = getCallbackSpecs(*Broker); + if (Specs.empty() || !validateSpecs(*Broker, *Call, Specs)) { + continue; + } + + BrokerCalls.push_back({Call, Broker, std::move(Specs)}); + } + } + + for (auto &Candidate : BrokerCalls) { + auto *Model = + createModel(IRDB, *Candidate.Broker, *Candidate.Call, Candidate.Specs); + Candidate.Call->setCalledFunction(Model->getFunctionType(), Model); + Candidate.Call->setAttributes(llvm::AttributeList{}); + } + + if (!BrokerCalls.empty()) { + PHASAR_LOG_LEVEL_CAT(INFO, "LLVMBasedICFG", + "Rewritten " << BrokerCalls.size() + << " extern callback call-site(s)"); + } + + return BrokerCalls.size(); +} + +bool ExternCallbackModel::isPhasarGenerated(const llvm::Function &F) noexcept { + return F.hasName() && F.getName().starts_with(ModelPrefix); +} + +} // namespace psr diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp index 0f7f71e15a..6a8191401f 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.cpp @@ -2,6 +2,7 @@ #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/PhasarLLVM/ControlFlow/EntryFunctionUtils.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" @@ -257,32 +258,61 @@ auto psr::buildLLVMBasedCallGraph( return B.buildCallGraph(S); } +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S) + -> LLVMBasedCallGraph { + ExternCallbackModel::rewriteCalls(IRDB); + return buildLLVMBasedCallGraph(static_cast(IRDB), + CGResolver, EntryPoints, S); +} + auto psr::buildLLVMBasedCallGraph( LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints, - DIBasedTypeHierarchy &TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { + ExternCallbackModel::rewriteCalls(IRDB); + LLVMAliasInfo PTOwn; if (!PT && CGType == CallGraphAnalysisType::OTF) { PTOwn = std::make_unique(&IRDB); PT = PTOwn.asRef(); } - auto Res = Resolver::create(CGType, &IRDB, &VTP, &TH, PT); - return buildLLVMBasedCallGraph(IRDB, *Res, EntryPoints, S); + auto Res = Resolver::create(CGType, &IRDB, &VTP, TH, PT); + return buildLLVMBasedCallGraph(static_cast(IRDB), + *Res, EntryPoints, S); +} + +auto psr::buildLLVMBasedCallGraph( + LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy &TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + Soundness S) -> LLVMBasedCallGraph { + return buildLLVMBasedCallGraph(IRDB, CGType, EntryPoints, &TH, VTP, PT, S); } auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, CallGraphAnalysisType CGType, llvm::ArrayRef EntryPoints, - DIBasedTypeHierarchy &TH, + DIBasedTypeHierarchy *TH, LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, Soundness S) -> LLVMBasedCallGraph { auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraph(IRDB, CGType, EntryPointFns, TH, VTP, PT, S); } +auto psr::buildLLVMBasedCallGraph(LLVMProjectIRDB &IRDB, + CallGraphAnalysisType CGType, + llvm::ArrayRef EntryPoints, + DIBasedTypeHierarchy &TH, + LLVMVFTableProvider &VTP, LLVMAliasInfoRef PT, + Soundness S) -> LLVMBasedCallGraph { + return buildLLVMBasedCallGraph(IRDB, CGType, EntryPoints, &TH, VTP, PT, S); +} + auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, Resolver &CGResolver, llvm::ArrayRef EntryPoints, @@ -290,3 +320,12 @@ auto psr::buildLLVMBasedCallGraph(const LLVMProjectIRDB &IRDB, auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); return buildLLVMBasedCallGraph(IRDB, CGResolver, EntryPointFns, S); } + +auto psr::buildLLVMBasedCallGraphWithExternCallbackModels( + LLVMProjectIRDB &IRDB, Resolver &CGResolver, + llvm::ArrayRef EntryPoints, Soundness S) + -> LLVMBasedCallGraph { + auto EntryPointFns = getEntryFunctions(IRDB, EntryPoints); + return buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, CGResolver, + EntryPointFns, S); +} diff --git a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp index 7d6bd38fd3..9a26080c0b 100644 --- a/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp +++ b/lib/PhasarLLVM/ControlFlow/LLVMBasedICFG.cpp @@ -12,6 +12,7 @@ #include "phasar/ControlFlow/CallGraph.h" #include "phasar/ControlFlow/CallGraphAnalysisType.h" #include "phasar/ControlFlow/CallGraphData.h" +#include "phasar/PhasarLLVM/ControlFlow/GlobalCtorsDtorsModel.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCFG.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraph.h" #include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" @@ -19,7 +20,6 @@ #include "phasar/PhasarLLVM/ControlFlow/Resolver/Resolver.h" #include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" #include "phasar/PhasarLLVM/Pointer/LLVMAliasInfo.h" -#include "phasar/PhasarLLVM/Pointer/LLVMAliasSet.h" #include "phasar/PhasarLLVM/Utils/LLVMBasedContainerConfig.h" #include "phasar/PhasarLLVM/Utils/LLVMShorthands.h" #include "phasar/Utils/Soundness.h" @@ -39,9 +39,11 @@ void LLVMBasedICFG::initialize(LLVMProjectIRDB *IRDB, Resolver &CGResolver, Soundness S, bool IncludeGlobals) { if (IncludeGlobals) { auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, {EntryFun}, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, {EntryFun}, S); } else { - this->CG = buildLLVMBasedCallGraph(*IRDB, CGResolver, EntryPoints, S); + this->CG = buildLLVMBasedCallGraphWithExternCallbackModels( + *IRDB, CGResolver, EntryPoints, S); } } @@ -53,15 +55,14 @@ LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, : IRDB(IRDB), VTP(*IRDB) { assert(IRDB != nullptr); - LLVMAliasInfo PTOwn; - - if (!PT && CGType == CallGraphAnalysisType::OTF) { - PTOwn = std::make_unique(IRDB); - PT = PTOwn.asRef(); + if (IncludeGlobals) { + auto *EntryFun = GlobalCtorsDtorsModel::buildModel(*IRDB, EntryPoints); + this->CG = + buildLLVMBasedCallGraph(*IRDB, CGType, {EntryFun}, TH, VTP, PT, S); + } else { + this->CG = + buildLLVMBasedCallGraph(*IRDB, CGType, EntryPoints, TH, VTP, PT, S); } - - auto CGRes = Resolver::create(CGType, IRDB, &VTP, TH, PT); - initialize(IRDB, *CGRes, EntryPoints, S, IncludeGlobals); } LLVMBasedICFG::LLVMBasedICFG(LLVMProjectIRDB *IRDB, Resolver &CGResolver, diff --git a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp index f431e0a19b..fefd131b54 100644 --- a/lib/PhasarLLVM/Utils/LLVMShorthands.cpp +++ b/lib/PhasarLLVM/Utils/LLVMShorthands.cpp @@ -503,6 +503,24 @@ bool psr::isGuardVariable(const llvm::Value *V) { return false; } +static bool isGuardVariableLoadOperand(const llvm::Value *V) { + if (const auto *Load = llvm::dyn_cast(V)) { + return Load->isAtomic() && psr::isGuardVariable(Load->getPointerOperand()); + } + + if (const auto *Cast = llvm::dyn_cast(V)) { + return isGuardVariableLoadOperand(Cast->getOperand(0)); + } + + if (const auto *BinOp = llvm::dyn_cast(V); + BinOp && BinOp->getOpcode() == llvm::Instruction::And) { + return isGuardVariableLoadOperand(BinOp->getOperand(0)) || + isGuardVariableLoadOperand(BinOp->getOperand(1)); + } + + return false; +} + static bool isAllocationSiteOrSimilar(const llvm::Value *V) { if (const auto *Arg = llvm::dyn_cast(V)) { return Arg->hasStructRetAttr(); @@ -578,13 +596,11 @@ bool psr::isStaticVariableLazyInitializationBranch( if (auto *Cmp = llvm::dyn_cast(Condition); Cmp && llvm::ICmpInst::isEquality(Cmp->getPredicate())) { for (auto *Op : Cmp->operand_values()) { - if (auto *Load = llvm::dyn_cast(Op); - Load && Load->isAtomic()) { + if (isGuardVariableLoadOperand(Op)) { + return true; + } - if (isGuardVariable(Load->getPointerOperand())) { - return true; - } - } else if (auto *Call = llvm::dyn_cast(Op)) { + if (auto *Call = llvm::dyn_cast(Op)) { auto *CalledFunction = Call->getCalledFunction(); if (CalledFunction && CalledFunction->getName() == "__cxa_guard_acquire") { diff --git a/test/llvm_test_code/call_graphs/CMakeLists.txt b/test/llvm_test_code/call_graphs/CMakeLists.txt index 833ff39d2e..d18c0455c1 100644 --- a/test/llvm_test_code/call_graphs/CMakeLists.txt +++ b/test/llvm_test_code/call_graphs/CMakeLists.txt @@ -1,5 +1,16 @@ set(NoMem2regSources function_object_1.cpp + extern_callback_attribute.c + extern_callback_attribute_indirect.c + extern_callback_invalid_metadata_arg.c + extern_callback_kmpc_fork_call.c + extern_callback_kmpc_fork_teams.c + extern_callback_mismatched_callback_arity.c + extern_callback_non_pointer_callee_arg.c + extern_callback_pthread.c + extern_callback_pthread_indirect.c + extern_callback_pthread_multiple.c + extern_callback_too_few_broker_args.c function_pointer_1.c function_pointer_2.cpp function_pointer_3.cpp diff --git a/test/llvm_test_code/call_graphs/extern_callback_attribute.c b/test/llvm_test_code/call_graphs/extern_callback_attribute.c new file mode 100644 index 0000000000..5feae7595e --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_attribute.c @@ -0,0 +1,9 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value) { (void)value; } + +int main() { + callback_broker(sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c b/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c new file mode 100644 index 0000000000..e5dc36ee5a --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_attribute_indirect.c @@ -0,0 +1,10 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value) { (void)value; } + +int main() { + void (*cb)(int) = sink; + callback_broker(cb, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c b/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c new file mode 100644 index 0000000000..9ae0fa5acb --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_invalid_metadata_arg.c @@ -0,0 +1,9 @@ +extern void callback_broker(void (*callback)(int), int value, int extra) + __attribute__((callback(1, 3))); + +void sink(int value) { (void)value; } + +int main() { + ((void (*)(void (*)(int), int))callback_broker)(sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c new file mode 100644 index 0000000000..558dbff635 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_call.c @@ -0,0 +1,14 @@ +extern void __kmpc_fork_call(void *loc, int argc, + void (*microtask)(int *, int *, ...), ...); + +void microtask(int *gtid, int *bound, int *payload) { + (void)gtid; + (void)bound; + (void)payload; +} + +int main() { + int payload = 0; + __kmpc_fork_call(0, 1, (void (*)(int *, int *, ...))microtask, &payload); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c new file mode 100644 index 0000000000..d15819d2ea --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_kmpc_fork_teams.c @@ -0,0 +1,15 @@ +extern void __kmpc_fork_teams(void *loc, int argc, + void (*microtask)(int *, int *, ...), ...); + +void team_microtask(int *gtid, int *bound, int *payload) { + (void)gtid; + (void)bound; + (void)payload; +} + +int main() { + int payload = 0; + __kmpc_fork_teams(0, 1, (void (*)(int *, int *, ...))team_microtask, + &payload); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c b/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c new file mode 100644 index 0000000000..5f05dd4bb5 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_mismatched_callback_arity.c @@ -0,0 +1,12 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +void sink(int value, int extra) { + (void)value; + (void)extra; +} + +int main() { + callback_broker((void (*)(int))sink, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c b/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c new file mode 100644 index 0000000000..2dec7dc97a --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_non_pointer_callee_arg.c @@ -0,0 +1,7 @@ +extern void callback_broker(void (*callback)(int), int value) + __attribute__((callback(1, 2))); + +int main() { + ((void (*)(int, int))callback_broker)(1, 42); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread.c b/test/llvm_test_code/call_graphs/extern_callback_pthread.c new file mode 100644 index 0000000000..69198efdab --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread.c @@ -0,0 +1,10 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *worker(void *arg) { return arg; } + +int main() { + void *thread = 0; + void *payload = 0; + return pthread_create(&thread, 0, worker, payload); +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c b/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c new file mode 100644 index 0000000000..04f4d3e636 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread_indirect.c @@ -0,0 +1,11 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *worker(void *arg) { return arg; } + +int main() { + void *thread = 0; + void *payload = 0; + void *(*start)(void *) = worker; + return pthread_create(&thread, 0, start, payload); +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c b/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c new file mode 100644 index 0000000000..40fa0a0d46 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_pthread_multiple.c @@ -0,0 +1,15 @@ +extern int pthread_create(void **thread, const void *attr, + void *(*start_routine)(void *), void *arg); + +void *first_worker(void *arg) { return arg; } + +void *second_worker(void *arg) { return arg; } + +int main() { + void *first_thread = 0; + void *second_thread = 0; + void *payload = 0; + pthread_create(&first_thread, 0, first_worker, payload); + pthread_create(&second_thread, 0, second_worker, payload); + return 0; +} diff --git a/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c b/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c new file mode 100644 index 0000000000..6d731238c4 --- /dev/null +++ b/test/llvm_test_code/call_graphs/extern_callback_too_few_broker_args.c @@ -0,0 +1,3 @@ +extern int pthread_create(void **thread, const void *attr); + +int main() { return pthread_create(0, 0); } diff --git a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt index 680b7aa647..0ae5b52143 100644 --- a/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt +++ b/unittests/PhasarLLVM/ControlFlow/CMakeLists.txt @@ -8,6 +8,7 @@ set(ControlFlowSources LLVMBasedBackwardCFGTest.cpp LLVMBasedBackwardICFGTest.cpp LLVMBasedICFGExportTest.cpp + LLVMBasedICFGExternCallbackTest.cpp LLVMBasedICFGGlobCtorDtorTest.cpp LLVMBasedICFGSerializationTest.cpp LLVMVFTableProviderTest.cpp diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp new file mode 100644 index 0000000000..9ca4009e69 --- /dev/null +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGExternCallbackTest.cpp @@ -0,0 +1,285 @@ +/****************************************************************************** + * Copyright (c) 2026 Fabian Schiebel. + * All rights reserved. This program and the accompanying materials are made + * available under the terms of LICENSE.txt. + * + * Contributors: + * Fabian Schiebel and others + *****************************************************************************/ + +#include "phasar/Config/Configuration.h" +#include "phasar/ControlFlow/CallGraphAnalysisType.h" +#include "phasar/PhasarLLVM/ControlFlow/ExternCallbackModel.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedICFG.h" +#include "phasar/PhasarLLVM/ControlFlow/LLVMVFTableProvider.h" +#include "phasar/PhasarLLVM/ControlFlow/Resolver/RTAResolver.h" +#include "phasar/PhasarLLVM/DB/LLVMProjectIRDB.h" +#include "phasar/PhasarLLVM/TypeHierarchy/DIBasedTypeHierarchy.h" + +#include "llvm/IR/InstIterator.h" +#include "llvm/IR/InstrTypes.h" + +#include "TestConfig.h" +#include "gtest/gtest.h" + +using namespace psr; + +namespace { + +const llvm::Function *getGeneratedModel(const LLVMProjectIRDB &IRDB, + llvm::StringRef BrokerName) { + std::string Prefix = (ExternCallbackModel::ModelPrefix + llvm::Twine('.') + + BrokerName + llvm::Twine('.')) + .str(); + for (const auto *F : IRDB.getAllFunctions()) { + if (F->getName().starts_with(Prefix)) { + return F; + } + } + + return nullptr; +} + +llvm::SmallVector +getGeneratedModels(const LLVMProjectIRDB &IRDB, llvm::StringRef BrokerName) { + llvm::SmallVector Models; + std::string Prefix = (ExternCallbackModel::ModelPrefix + llvm::Twine('.') + + BrokerName + llvm::Twine('.')) + .str(); + for (const auto *F : IRDB.getAllFunctions()) { + if (F->getName().starts_with(Prefix)) { + Models.push_back(F); + } + } + + return Models; +} + +const llvm::CallBase *getCallbackCall(const llvm::Function &Model, + const LLVMBasedICFG &ICFG, + const llvm::Function &Callback) { + for (const auto &Inst : llvm::instructions(Model)) { + const auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto Callees = ICFG.getCalleesOfCallAt(Call); + if (Callees == llvm::ArrayRef{&Callback}) { + return Call; + } + } + + return nullptr; +} + +const llvm::CallBase *getCallbackCall(const llvm::Function &Model, + const LLVMBasedCallGraph &CG, + const llvm::Function &Callback) { + for (const auto &Inst : llvm::instructions(Model)) { + const auto *Call = llvm::dyn_cast(&Inst); + if (!Call) { + continue; + } + + auto Callees = CG.getCalleesOfCallAt(Call); + if (Callees == llvm::ArrayRef{&Callback}) { + return Call; + } + } + + return nullptr; +} + +void doExternCallbackTest(llvm::StringRef IRFile, llvm::StringRef BrokerName, + llvm::StringRef CallbackName, + bool ExpectIndirectCallbackCall = false, + unsigned ExpectedCallbackArgs = 1) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + DIBasedTypeHierarchy TH(IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, nullptr, + Soundness::Soundy, /*IncludeGlobals*/ false); + + const auto *Model = getGeneratedModel(IRDB, BrokerName); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition(CallbackName); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, ICFG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_EQ(ExpectIndirectCallbackCall, + ICFG.isIndirectFunctionCall(CallbackCall)); + EXPECT_EQ(ExpectedCallbackArgs, CallbackCall->arg_size()); + + auto Callees = ICFG.getCalleesOfCallAt(CallbackCall); + EXPECT_EQ(llvm::ArrayRef{Callback}, Callees); +} + +void doExternCallbackCallGraphBuilderTest(llvm::StringRef IRFile, + llvm::StringRef BrokerName, + llvm::StringRef CallbackName, + unsigned ExpectedCallbackArgs = 1) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + DIBasedTypeHierarchy TH(IRDB); + LLVMVFTableProvider VTP(IRDB); + RTAResolver Resolver(&IRDB, &VTP, &TH); + + auto CG = + buildLLVMBasedCallGraphWithExternCallbackModels(IRDB, Resolver, {"main"}); + + const auto *Model = getGeneratedModel(IRDB, BrokerName); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition(CallbackName); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, CG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_EQ(ExpectedCallbackArgs, CallbackCall->arg_size()); + + auto Callees = CG.getCalleesOfCallAt(CallbackCall); + EXPECT_EQ(llvm::ArrayRef{Callback}, Callees); +} + +unsigned countCallbackModels(llvm::ArrayRef Models, + const LLVMBasedICFG &ICFG, + const llvm::Function &Callback) { + unsigned Count = 0; + for (const auto *Model : Models) { + const auto *CallbackCall = getCallbackCall(*Model, ICFG, Callback); + if (!CallbackCall) { + continue; + } + + EXPECT_EQ(1U, CallbackCall->arg_size()); + ++Count; + } + + return Count; +} + +void expectNoRewrite(llvm::StringRef IRFile, llvm::StringRef BrokerName) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + IRFile); + ASSERT_TRUE(static_cast(IRDB)); + + EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_TRUE(getGeneratedModels(IRDB, BrokerName).empty()); +} + +} // namespace + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreate) { + doExternCallbackTest("call_graphs/extern_callback_pthread_c.ll", + "pthread_create", "worker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreateIndirect) { + doExternCallbackTest("call_graphs/extern_callback_pthread_indirect_c.ll", + "pthread_create", "worker", + /*ExpectIndirectCallbackCall*/ true); +} + +TEST(LLVMBasedICFGExternCallbackTest, PthreadCreateMultipleCallSites) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_multiple_c.ll"); + DIBasedTypeHierarchy TH(IRDB); + LLVMBasedICFG ICFG(&IRDB, CallGraphAnalysisType::OTF, {"main"}, &TH, nullptr, + Soundness::Soundy, /*IncludeGlobals*/ false); + + auto Models = getGeneratedModels(IRDB, "pthread_create"); + EXPECT_EQ(2U, Models.size()); + + const auto *FirstCallback = IRDB.getFunctionDefinition("first_worker"); + ASSERT_NE(nullptr, FirstCallback); + const auto *SecondCallback = IRDB.getFunctionDefinition("second_worker"); + ASSERT_NE(nullptr, SecondCallback); + + EXPECT_EQ(1U, countCallbackModels(Models, ICFG, *FirstCallback)); + EXPECT_EQ(1U, countCallbackModels(Models, ICFG, *SecondCallback)); +} + +TEST(LLVMBasedICFGExternCallbackTest, RewriteCallsIsIdempotent) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_multiple_c.ll"); + + EXPECT_EQ(2U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_EQ(0U, ExternCallbackModel::rewriteCalls(IRDB)); + EXPECT_EQ(2U, getGeneratedModels(IRDB, "pthread_create").size()); +} + +TEST(LLVMBasedICFGExternCallbackTest, KmpcForkCall) { + doExternCallbackTest("call_graphs/extern_callback_kmpc_fork_call_c.ll", + "__kmpc_fork_call", "microtask", + /*ExpectIndirectCallbackCall*/ false, + /*ExpectedCallbackArgs*/ 3); +} + +TEST(LLVMBasedICFGExternCallbackTest, KmpcForkTeams) { + doExternCallbackTest("call_graphs/extern_callback_kmpc_fork_teams_c.ll", + "__kmpc_fork_teams", "team_microtask", + /*ExpectIndirectCallbackCall*/ false, + /*ExpectedCallbackArgs*/ 3); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallbackAttribute) { + doExternCallbackTest("call_graphs/extern_callback_attribute_c.ll", + "callback_broker", "sink"); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallbackAttributeIndirect) { + doExternCallbackTest("call_graphs/extern_callback_attribute_indirect_c.ll", + "callback_broker", "sink", + /*ExpectIndirectCallbackCall*/ true); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderResolverOverload) { + doExternCallbackCallGraphBuilderTest( + "call_graphs/extern_callback_pthread_c.ll", "pthread_create", "worker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, CallGraphBuilderCGTypeOverloadIndirect) { + LLVMProjectIRDB IRDB(unittest::PathToLLTestFiles + + "call_graphs/extern_callback_pthread_indirect_c.ll"); + DIBasedTypeHierarchy TH(IRDB); + LLVMVFTableProvider VTP(IRDB); + + auto CG = buildLLVMBasedCallGraph(IRDB, CallGraphAnalysisType::OTF, {"main"}, + TH, VTP); + + const auto *Model = getGeneratedModel(IRDB, "pthread_create"); + ASSERT_NE(nullptr, Model); + + const auto *Callback = IRDB.getFunctionDefinition("worker"); + ASSERT_NE(nullptr, Callback); + + const auto *CallbackCall = getCallbackCall(*Model, CG, *Callback); + ASSERT_NE(nullptr, CallbackCall); + EXPECT_TRUE(CallbackCall->isIndirectCall()); +} + +TEST(LLVMBasedICFGExternCallbackTest, InvalidMetadataCallbackArg) { + expectNoRewrite("call_graphs/extern_callback_invalid_metadata_arg_c.ll", + "callback_broker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, TooFewBrokerArgs) { + expectNoRewrite("call_graphs/extern_callback_too_few_broker_args_c.ll", + "pthread_create"); +} + +TEST(LLVMBasedICFGExternCallbackTest, NonPointerCallbackCalleeArg) { + expectNoRewrite("call_graphs/extern_callback_non_pointer_callee_arg_c.ll", + "callback_broker"); +} + +TEST(LLVMBasedICFGExternCallbackTest, MismatchedCallbackArity) { + expectNoRewrite("call_graphs/extern_callback_mismatched_callback_arity_c.ll", + "callback_broker"); +} + +int main(int Argc, char **Argv) { + ::testing::InitGoogleTest(&Argc, Argv); + return RUN_ALL_TESTS(); +} diff --git a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp index e3278110f7..f2431a37f1 100644 --- a/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp +++ b/unittests/PhasarLLVM/ControlFlow/LLVMBasedICFGGlobCtorDtorTest.cpp @@ -25,7 +25,9 @@ #include "llvm/ADT/StringMap.h" #include "llvm/ADT/TinyPtrVector.h" #include "llvm/IR/Dominators.h" +#include "llvm/IR/InstIterator.h" #include "llvm/IR/InstrTypes.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Linker/Linker.h" #include "llvm/Support/ErrorHandling.h" @@ -79,6 +81,28 @@ class LLVMBasedICFGGlobCtorDtorTest : public ::testing::Test { } } } + + static const llvm::AllocaInst *findAlloca(const llvm::Function *F, + llvm::StringRef Name) { + for (const auto &Inst : llvm::instructions(F)) { + const auto *Alloca = llvm::dyn_cast(&Inst); + if (Alloca && Alloca->getName() == Name) { + return Alloca; + } + } + return nullptr; + } + + static const llvm::LoadInst *findLoadFrom(const llvm::Function *F, + const llvm::Value *Pointer) { + for (const auto &Inst : llvm::instructions(F)) { + const auto *Load = llvm::dyn_cast(&Inst); + if (Load && Load->getPointerOperand()->stripPointerCasts() == Pointer) { + return Load; + } + } + return nullptr; + } }; TEST_F(LLVMBasedICFGGlobCtorDtorTest, CtorTest) { @@ -335,10 +359,25 @@ TEST_F(LLVMBasedICFGGlobCtorDtorTest, LCATest4_1) { Solver.dumpResults(); - const auto *FooGet = IRDB.getInstruction(15); - const auto *LoadFoo = IRDB.getInstruction(14); - const auto *LoadX = IRDB.getInstruction(20); - const auto *End = IRDB.getInstruction(22); + const auto *GetFoo = IRDB.getFunctionDefinition("_Z6getFoov"); + ASSERT_NE(nullptr, GetFoo); + const auto *FooStorage = IRDB.getGlobalVariableDefinition("_ZZ6getFoovE3foo"); + ASSERT_NE(nullptr, FooStorage); + + const auto *LoadFoo = findLoadFrom(GetFoo, FooStorage); + ASSERT_NE(nullptr, LoadFoo); + const auto *FooGet = LoadFoo->getNextNode(); + ASSERT_NE(nullptr, FooGet); + + const auto *Main = IRDB.getFunctionDefinition("main"); + ASSERT_NE(nullptr, Main); + const auto *X = findAlloca(Main, "x"); + ASSERT_NE(nullptr, X); + + const auto *LoadX = findLoadFrom(Main, X); + ASSERT_NE(nullptr, LoadX); + const auto *End = Main->back().getTerminator(); + ASSERT_NE(nullptr, End); auto FooValueAfterGet = Solver.resultAt(FooGet, LoadFoo);