IR API(二)——使用LLVM IR调用C的函数和全局变量

本文探讨了LLVM IR如何调用C库函数与自定义函数,以及如何在IR中引用C全局变量,通过具体实例展示了如何在LLVM IR中声明并调用这些元素,以及在JIT执行时进行映射。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可以转载,请注明出处!

1、IR调用C的库函数

C中使用math.h中的库函数 double pow(double a, double b)

#include<stdio.h>
#include<math.h>

int main() {
	int a = 10;
	int b = 2;
	return pow(a, b);
}

我们要期待生成IR是下面这样:

  • declare double @pow(double, double)函数的声明。
; ModuleID = 'test'
source_filename = "test"

declare double @pow(double, double)

define i32 @main() {
entry:
  %0 = call double @pow(double 1.000000e+01, double 2.000000e+00)
  %1 = fptosi double %0 to i32
  ret i32 %1
}

使用C的api时,只需要在IR中声明一个同预期库函数名称一样的函数即可,如下面只需要声明一个pow函数,不需要给改函数定义,调用的时候只管调用就ok。

FunctionType *pow_type = TypeBuilder<double(double, double), false>::get(context);
Function *fun_pow = cast<Function>(module->getOrInsertFunction("pow", pow_type));

完整代码:

#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/IR/TypeBuilder.h"
#include <iostream>
#include <algorithm>
 
using namespace llvm;
using namespace std;

int main(){
    static LLVMContext MyGlobalContext;
    LLVMContext &context = MyGlobalContext;
    Module *module = new Module("test", context);
	IRBuilder<> builder(context);

	FunctionType *pow_type = TypeBuilder<double(double, double), false>::get(context);
	Function *fun_pow = cast<Function>(module->getOrInsertFunction("pow", pow_type));

	// 在IR中声明一个函数bar,我们会用IR定义这个函数
    FunctionType *bar_type = TypeBuilder<int(), false>::get(context);
    Function *bar = cast<Function>(module->getOrInsertFunction("main", bar_type));
	
	// 创建函数bar的代码块
    BasicBlock *entry = BasicBlock::Create(context, "entry", bar);
	builder.SetInsertPoint(entry);
	
	//构造实参列表
	Constant* a_value = ConstantFP::get(Type::getDoubleTy(context), 10.0);
	Constant* b_value = ConstantFP::get(Type::getDoubleTy(context), 2.0);
	
	std::vector<Value*> putsargs;
	putsargs.push_back(a_value);
	putsargs.push_back(b_value);
	ArrayRef<Value*>  argsRef(putsargs);
	
	// 调用函数pow
	Value* ret_pow = builder.CreateCall(fun_pow, argsRef);
	Value* ret = builder.CreateFPToSI(ret_pow, Type::getInt32Ty(context));
	builder.CreateRet(ret);
	//---------------------------------------------------------------------------------------
	module->dump();
	
	InitializeNativeTarget();
    InitializeNativeTargetAsmPrinter();
    InitializeNativeTargetAsmParser();
	
    ExecutionEngine *ee = EngineBuilder(std::unique_ptr<Module>(module)).setEngineKind(EngineKind::JIT).create();
	void *barAddr = ee->getPointerToFunction(bar);
	
	//运行机器指令
    typedef int (*FuncType)();
    FuncType barFunc = (FuncType)barAddr;
    ee->finalizeObject();
	std::cout << barFunc() << std::endl;
    
    delete module;
    return 0;
}

2、IR调用C中定义的函数和全局变量

C中定义的函数和全局变量:

int var_a = 20;
int var_b = 30;

int foo(int x, int y ) {
    return max(x, y);
}

目前我们要期待生成IR是下面这样:

  • declare i32 @foo(i32, i32)函数的声明。
  • @a= external global i32@b = external global i32全局变量的声明。
; ModuleID = 'test'
source_filename = "test"

@a = external global i32
@b = external global i32

declare i32 @foo(i32, i32)

define i32 @bar() {
entry:
  %0 = load i32, i32* @a
  %1 = load i32, i32* @b
  %2 = call i32 @foo(i32 %0, i32 %1)
  ret i32 %2
}

在IR中不给这两个声明做定义,因为在C中已经定义了,只需要在使用JIT执行时,将C中定义的int foo(int x, int y )函数和int var_a 全局值的地址分别映射到i32 @foo(i32, i32)@a上面即可:

ExecutionEngine *ee = EngineBuilder(std::unique_ptr<Module>(module)).setEngineKind(EngineKind::JIT).create();

// 将外部的C++代码中的全局变量映射到IR代码中,IR代码中只有声明
ee->addGlobalMapping(v1, &var_a);
ee->addGlobalMapping(v2, &var_b);
// 将外部的C++代码中的全局函数映射到IR代码中,IR代码中只有声明
ee->addGlobalMapping(foo_fun, (void *)foo);

完整代码:

#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/IR/TypeBuilder.h"
#include <iostream>
#include <algorithm>
 
using namespace llvm;
using namespace std;
 
int var_a = 20;
int var_b = 30;

int foo(int x, int y ) {
    return max(x, y);
}

int main(){
    static LLVMContext MyGlobalContext;
    LLVMContext &context = MyGlobalContext;
    Module *module = new Module("test", context);
    
	// 在IR代码中声明一个全局变量
    GlobalVariable *v1 = cast<GlobalVariable>(module->getOrInsertGlobal("a", Type::getInt32Ty(context)));
	GlobalVariable *v2 = cast<GlobalVariable>(module->getOrInsertGlobal("b", Type::getInt32Ty(context)));

	// 在IR中声明一个函数,注意我们并不会在IR中定义foo,我们会将这个IR中声明的函数映射到C++代码中的函数
    FunctionType *foo_type = TypeBuilder<int(int, int), false>::get(context);
    Function *foo_fun = cast<Function>(module->getOrInsertFunction("foo", foo_type));
    
	// 在IR中声明一个函数bar,我们会用IR定义这个函数
    FunctionType *bar_type = TypeBuilder<int(), false>::get(context);
    Function *bar = cast<Function>(module->getOrInsertFunction("bar", bar_type));
    
	// 创建函数bar的代码块
    BasicBlock *entry = BasicBlock::Create(context, "entry", bar);
    IRBuilder<> builder(entry);
    // 用一个局部变量获取全局变量v的值
    Value *v_IR1 = builder.CreateLoad(v1);
	Value *v_IR2 = builder.CreateLoad(v2);
	//构建参数
	std::vector<Value*> args;
	args.push_back(v_IR1);
	args.push_back(v_IR2);
	ArrayRef<Value*> argRef(args);
    // 调用函数foo
    Value *ret = builder.CreateCall(foo_fun, argRef);
    // 返回值
    builder.CreateRet(ret);
    
	module->dump();
	
	InitializeNativeTarget();
    InitializeNativeTargetAsmPrinter();
    InitializeNativeTargetAsmParser();
	
    ExecutionEngine *ee = EngineBuilder(std::unique_ptr<Module>(module)).setEngineKind(EngineKind::JIT).create();
	ee->addGlobalMapping(v1, &var_a);
	ee->addGlobalMapping(v2, &var_b);
	ee->addGlobalMapping(foo_fun, (void *)foo);
	void *barAddr = ee->getPointerToFunction(bar);
	
	//运行机器指令
    typedef int (*FuncType)();
    FuncType barFunc = (FuncType)barAddr;
    ee->finalizeObject();
	std::cout << barFunc() << std::endl;
    
    delete module;
    return 0;
}

//下面是执行的结果:
30
<think>好的,我现在需要回答用户关于使用LLVM API复制函数并删除原函数后是否会导致错误的问题。首先,我需要理解用户的问题场景。用户可能在处理LLVM的中间表示(IR),他们复制了一个函数,然后删除了原始函数,想知道这样做是否会引发错误。 首先,我应该回忆一下LLVM函数的管理机制。LLVM的Module类拥有所有的函数,每个函数都是Module的子对象。当复制一个函数时,可能需要使用Function::Create来基于原函数的类型创建一个新函数,然后复制其内容,比如基本块、指令等。用户可能通过CloneFunctionInto或者其他方法进行复制。 接下来,删除原函数。用户可能调用了eraseFromParent方法或者用其他方式从Module中移除原函数。这时候,问题在于其他部分是否仍然引用原函数。例如,如果有全局变量、其他函数或者元数据引用了原函数,删除它就会导致这些引用变成悬垂指针,从而在后续处理中引发错误,比如验证错误或者崩溃。 需要考虑到LLVMIR验证机制。LLVM在优化过程中会进行IR验证,确保IR的合法性。如果删除原函数后,存在仍然引用它的地方,验证就会失败,导致错误。例如,如果另一个函数调用指令仍然调用函数,而原函数已经被删除,这时候IR就不再合法,验证时会报错。 另外,用户是否正确地复制了函数的所有引用?例如,如果原函数被其他函数调用,复制后的新函数需要替换这些调用点。否则,删除原函数后,这些调用点就指向了不存在的函数,导致错误。 可能的步骤是,用户复制函数,替换所有对原函数的引用为新函数,然后再删除原函数。这样就不会有问题。但如果用户只是简单地复制函数并删除原函数,而没有处理引用,那么就会导致错误。 还需要考虑LLVMAPI使用细节。例如,当复制函数时,是否正确地处理了函数的属性、参数、基本块、指令以及使用变量常量。如果有遗漏,可能会导致新函数的行为不一致,或者在删除原函数后引发问题。 此外,函数的名称是否冲突?如果复制后的函数函数同名,删除原函数后,新函数可能继承了名称,但需要确保名称唯一性,否则可能影响后续处理。 总结可能的情况:如果用户在复制函数后没有正确更新所有对原函数的引用,直接删除原函数会导致IR中出现无效的引用,从而在验证或优化过程中报错。正确的做法是确保所有引用原函数的地方都被更新到新函数,然后再删除原函数。 现在需要将这些思考整理成结构化的回答,分步骤说明,包括关键点如引用更新、IR验证、名称处理等,并给出示例代码或建议的操作步骤,以及注意事项。</think>在 LLVM 中,复制函数后删除原始函数**可能导致错误**,具体取决于代码中是否存在对原函数的残留引用。以下是关键机制操作建议的分步说明: --- ### **1. 错误场景分析** 删除原函数后可能触发以下两类问题: | 错误类型 | 触发条件 | |-------------------------|--------------------------------------------------------------------------| | **悬垂引用** | 其他函数中的 `CallInst` 仍调用函数 | | **IR 验证失败** | 删除后未清理引用,LLVM 严格检查 IR 完整性(如 `verifyModule()` 抛出异常) | --- ### **2. 安全删除的条件** 必须确保以下两点后,才能安全删除原函数: 1. **所有调用点已迁移**:将原函数的 `CallInst`/`InvokeInst` 替换为新函数。 2. **无间接使用**:全局变量、别名(`GlobalAlias`)或元数据未引用原函数。 --- ### **3. 安全操作步骤** #### **步骤 1:复制函数** 通过 `CloneFunctionInto` 复制函数体(需手动创建新函数): ```cpp // 原函数:F Module &M = *F.getParent(); FunctionType *FTy = F.getFunctionType(); Function *NewF = Function::Create(FTy, F.getLinkage(), "new_name", M); // 复制内容(参数、基本块、指令等) ValueToValueMapTy VMap; SmallVector<ReturnInst*, 8> Returns; // 忽略返回指令 CloneFunctionInto(NewF, &F, VMap, /*ModuleLevelChanges=*/true, Returns); ``` #### **步骤 2:替换所有调用点** 遍历模块中所有调用函数的位置,替换为调用函数: ```cpp for (User *U : F.users()) { if (CallBase *CB = dyn_cast<CallBase>(U)) { if (CB->getCalledFunction() == &F) { // 确认调用的是原函数 CB->setCalledFunction(NewF); // 替换为新函数 } } } ``` #### **步骤 3:删除原函数** 确认无残留引用后安全删除: ```cpp F.eraseFromParent(); // 从 Module 中移除原函数 ``` --- ### **4. 验证 IR 合法性** 操作后需显式调用验证,确保 IR 正确: ```cpp if (verifyModule(M, &errs())) { // 处理验证错误(如未清理的引用) } ``` --- ### **5. 常见错误示例** #### **未替换调用点直接删除** ```cpp Function *NewF = cloneFunction(F); F.eraseFromParent(); // 错误!其他 CallInst 仍指向 F,触发 IR 验证失败 ``` #### **输出错误信息** ``` Invalid function pointer called! %call = call i32 @original_func() ; 原函数已删除,@original_func 不存在 ``` --- ### **6. 高级场景处理** | 场景 | 解决方案 | |-------------------------------|--------------------------------------------------------------------------| | **动态调用函数指针)** | 替换所有加载原函数地址的指令(如 `LoadInst` + `GlobalVariable`) | | **元数据引用** | 检查并更新 `NamedMDNode` 或指令元数据中的函数指针 | | **间接用户(如 BlockAddress)**| 更新 `BlockAddress` 中的函数引用 | --- ### **7. 完整代码示例** ```cpp void safelyReplaceAndDeleteFunction(Function &F) { Module &M = *F.getParent(); // 1. 复制函数 Function *NewF = Function::Create(F.getFunctionType(), F.getLinkage(), "new_name", M); ValueToValueMapTy VMap; SmallVector<ReturnInst*, 8> Returns; CloneFunctionInto(NewF, &F, VMap, /*ModuleLevelChanges=*/true, Returns); // 2. 替换所有调用点 std::vector<User*> Users(F.user_begin(), F.user_end()); // 避免迭代器失效 for (User *U : Users) { if (CallBase *CB = dyn_cast<CallBase>(U)) { CB->setCalledFunction(NewF); } } // 3. 验证并删除 if (!verifyModule(M, &errs())) { F.eraseFromParent(); } } ``` --- ### **8. 关键注意事项** - **线程安全**:若在优化过程中操作,需暂停相关 Pass 的执行。 - **调试信息**:若原函数关联了 `DISubprogram`,需同步更新调试元数据。 - **名称冲突**:确保新函数名称唯一(可通过 `getUniqueFunctionName()` 生成)。 --- 通过上述方法,可在保证 IR 完整性的前提下安全替换并删除函数
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值