使用pass实现插桩统计函数执行时间

2023/04/23 LLVM

本文基于llvm14.0.0

上一篇,已经介绍如何将自定义的 pass 集成到clang 中。

在项目中,我们经常要对App 的启动进行优化或者对一些页面进行曝光采集。这一篇就利用pass 来实现这一功能。今天来实现一个统计函数执行时间的pass

按照上一篇的步骤实现添加一个passFunctionCallTimePass

其具体实现如下: FunctionCallTime.h 中代码:


#ifndef _FunctionCallTime_H_
#define _FunctionCallTime_H_


// LLVM include
#include "llvm/Pass.h"

using namespace std;
using namespace llvm;

// Namespace
namespace llvm {
    Pass *createFunctionCallTimePass ();
    Pass *createFunctionCallTimePass (bool flag);
}
#endif

FunctionCallTime.cpp 中代码:

//
//  function_call_time.cpp
//  function-call-time
//
//  Created by king on 2020/12/18.
//

#include <iostream>

#include "llvm/ADT/Statistic.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
//#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/InitializePasses.h"
#include "llvm/Transforms/FunctionCallTime/FunctionCallTime.h"


using namespace llvm;



namespace {


struct FunctionCallTimePass : public FunctionPass {
    static char ID;
    bool flag;
    static StringRef InsertFuncNamePrefix;
    static StringRef BeginFuncName;
    static StringRef EndFuncName;
    
    FunctionCallTimePass()
    : FunctionPass(ID) {}
    
    FunctionCallTimePass(bool flag): FunctionPass(ID) {
        this->flag = flag;
        FunctionCallTimePass();
        
    }
    
    bool runOnFunction(Function &F) override {

        std::cout << "functionname: " << std::string(F.getName()) << std::endl;
        if (F.empty() || !flag) {
            return false;
        }
        // 1. 已经插入的不需要再次插入
        if (F.getName().startswith("_ly_fun_"))
            return false;



//         2. 记录开始时间
        Value* beginTime = nullptr;
        if (!insert_begin_inst(F, beginTime))
            return false;

        // 3. 方法结束时统计方法耗时,开始的时间记录作为参数
        insert_return_inst(F, beginTime);

        return true;
    }
    
    bool insertBeginInst(Function &F) {
        // 0.函数最开始的BasicBlock
        LLVMContext &context = F.getContext();
        BasicBlock &BB       = F.getEntryBlock();
        
        // 1. 获取要插入的函数
        FunctionCallee beginFun = F.getParent()->getOrInsertFunction(
                                                                     FunctionCallTimePass::BeginFuncName,
                                                                     FunctionType::get(Type::getVoidTy(context),
                                                                                       {Type::getInt8PtrTy(context)}, false));
        
        errs() << "function-call-time: " << BB.getParent()->getName() << " begin\n";
        // 2. 构造函数
        CallInst *inst = nullptr;
        IRBuilder<> builder(&BB);
        IRBuilder<> callBuilder(context);
        Value *name = builder.CreateGlobalStringPtr(BB.getParent()->getName());
        inst        = callBuilder.CreateCall(beginFun, {name});
        
        if (!inst) {
            llvm::errs() << "Create First CallInst Failed\n";
            return false;
        }
        
        // 3. 获取函数开始的第一条指令
        Instruction *beginInst = dyn_cast<Instruction>(BB.begin());
        
        // 4. 将inst插入
        inst->insertBefore(beginInst);
        
        return true;
    }
    
    void insertEndInst(Function &F) {
        LLVMContext &context = F.getContext();
        for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I) {
            
            //  函数结尾的BasicBlock
            BasicBlock &BB = *I;
            for (BasicBlock::iterator I = BB.begin(), E = BB.end(); I != E; ++I) {
                ReturnInst *IST = dyn_cast<ReturnInst>(I);
                if (!IST)
                    continue;
                
                // end_func 类型
                FunctionType *endFuncType = FunctionType::get(
                                                              Type::getVoidTy(context), {Type::getInt8PtrTy(context)}, false);
                
                // end_func
                FunctionCallee endFunc = BB.getModule()->getOrInsertFunction(
                                                                             FunctionCallTimePass::EndFuncName, endFuncType);
                
                // 构造end_func
                IRBuilder<> builder(&BB);
                IRBuilder<> callBuilder(context);
                Value *name     = builder.CreateGlobalStringPtr(BB.getParent()->getName());
                CallInst *endCI = callBuilder.CreateCall(endFunc, {name});
                
                // 插入end_func(struction)
                endCI->insertBefore(IST);
                
                errs() << "function-call-time: " << BB.getParent()->getName()
                << " end\n";
            }
        }
    }
    
    
    bool insert_begin_inst(Function &F, Value*& beginTime)
    {
        // 0.函数最开始的BasicBlock
        std::cout << "insert_begin_inst" << std::endl;
        LLVMContext &context = F.getContext();
        BasicBlock &bb = F.getEntryBlock();
        
        // 1. 获取要插入的函数
        FunctionCallee beginFun = F.getParent()->getOrInsertFunction("_ly_fun_b",FunctionType::get(Type::getInt64Ty(context), {}, false));
        
        // 2. 构造函数
        // Value *beginTime = nullptr;
        CallInst *inst = nullptr;
        IRBuilder<> builder(context);
        inst = builder.CreateCall(beginFun);
        
        if (!inst) {
            llvm::errs() << "Create First CallInst Failed\n";
            return false;
        }
        
        // 3. 获取函数开始的第一条指令
        Instruction *beginInst = dyn_cast<Instruction>(bb.begin());
        
        // 4. 将inst插入
        inst->insertBefore(beginInst);
        
        // 5.根据返回值记录开始时间
        beginTime = inst;
        
        return true;
    }
    
    void insert_return_inst(Function &F, Value* beginTime)
    {
        std::cout << "insert_return_inst: " << std::endl;
        LLVMContext &context = F.getContext();
        for (Function::iterator I = F.begin(), E = F.end(); I != E; ++I)
        {
            
            //  函数结尾的BasicBlock
            BasicBlock &BB = *I;
            for (BasicBlock::iterator I = BB.begin(), E = BB.end(); I != E; ++I)
            {
                ReturnInst *IST = dyn_cast<ReturnInst>(I);
                if (!IST)
                    continue;
                
                // end_func 类型
                FunctionType *endFuncType = FunctionType::get(
                                                              Type::getVoidTy(context),
                                                              {Type::getInt8PtrTy(context),Type::getInt64Ty(context)},
                                                              false
                                                              );
                
                // end_func
                FunctionCallee endFunc = BB.getModule()->getOrInsertFunction("_ly_fun_e", endFuncType);
                
                // 构造end_func
                IRBuilder<> builder(&BB);
                IRBuilder<> callBuilder(context);
                CallInst* endCI = callBuilder.CreateCall(endFunc,
                                                         {
                    builder.CreateGlobalStringPtr(BB.getParent()->getName()),
                    beginTime
                }
                                                         );
                
                // 插入end_func(struction)
                endCI->insertBefore(IST);
            }
        }
    }
    
    bool doFinalization(Module &M) override {
        return false;
    }
};
}  // namespace

char FunctionCallTimePass::ID                        = 0;
StringRef FunctionCallTimePass::InsertFuncNamePrefix = "_ly_fun";
StringRef FunctionCallTimePass::BeginFuncName        = "_ly_fun_b";
StringRef FunctionCallTimePass::EndFuncName          = "_ly_fun_e";
//// 注册给 opt
// opt -load LLVMFunctionCallTime.dylib -function-call-time xx.bc


INITIALIZE_PASS(FunctionCallTimePass, "fct", "Function Call Time Pass", false, false)

//static RegisterPass<FunctionCallTimePass>
//    X("function-call-time", "Function calls take time to collect", false,
//      false);

// 注册给 clang 通过 -Xclang -load -Xclang LLVMFunctionCallTime.dylib

//static void registerFunctionCallTimePass(const PassManagerBuilder &,legacy::PassManagerBase &PM) {
//    PM.add(new FunctionCallTimePass());
//}

//static RegisterStandardPasses Y(PassManagerBuilder::EP_EarlyAsPossible,
//                                [](const PassManagerBuilder &Builder,
//                                   llvm::legacy::PassManagerBase &PM) {
//                                    PM.add(new FunctionCallTimePass());
//                                });

//static RegisterStandardPasses Z(PassManagerBuilder::EP_EarlyAsPossible, registerFunctionCallTimePass);

namespace llvm {
Pass *createFunctionCallTimePass () {
    return new FunctionCallTimePass(true);
}
Pass *createFunctionCallTimePass (bool flag) {
    return new FunctionCallTimePass(flag);
}
}

PassManagerBuilder.cpp 中加入:

static cl::opt<bool> FunCallTime("fct", cl::init(false),
                           cl::desc("Enable basic block function call time"));

严格按照上一篇中介绍的去集成。

编译 clang

测试代码 main.c 中实现:

//
//  main.m
//  llvmdemo2
//
//  Created by heyonly on 2023/1/28.
//

#include <stdio.h>
#include <sys/time.h>


long _ly_fun_b(void)
{
  struct timeval star;
  gettimeofday(&star, NULL);
  long b = star.tv_sec * 1000000 + star.tv_usec;
  return b;
}

void _ly_fun_e(char *name, long b)
{
  struct timeval end;
  gettimeofday(&end, NULL);
  long e = end.tv_sec * 1000000 + end.tv_usec;
  long t = e - b;
  printf("%s %ld us\n",name, t);
}


int main(int argc, const char * argv[]) {
    printf("hello world!!\n");
    if (argc <= 1) {
        printf("无参数\n");
        return 0;
    }
    
    printf("argment count: %d\n",argc);
    
    return 0;
}

编译main.c

./clang -O3 -mllvm -fct -flegacy-pass-manager -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include -L/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib  /Users/heyonly/workspace/practise/llvmdemo2/llvmdemo2/main.c

编译完后测试:

$ ./a.out 
hello world!!
无参数
main 19 us
$ ./a.out 1
hello world!!
argment count: 2
main 21 us

完美。

下一篇将介绍如何使用的新的 passmanager 来管理pass

Search

    Post Directory