如何在LLVM IRBuilder中调用C++代码及实现自定义语言字符串功能?
嘿,这个场景我刚好折腾过!基于Kaleidoscope扩展字符串类型并绑定到llvm::SmallString,同时支持调用C方法,其实核心是搞定类型映射和**C函数/方法的IR声明与调用**,给你一步步拆解:
一、将自定义String类型映射到llvm::SmallString
首先得明确:我们不需要在IR里完全复刻SmallString的内部结构(毕竟它是LLVM的实现细节),只需要把自定义的String类型对应到LLVM IR中一个opaque结构体类型,然后绑定它的构造、方法等C++接口即可。
1. 扩展AST与解析器,支持字符串相关语法
首先得给你的语言添加字符串的解析能力:
- Lexer:添加识别双引号包裹字符串的逻辑,返回
tok_string类型的Token,同时保存字符串内容; - Parser:新增
StringLiteralExprAST节点存储字符串字面量,新增StringVarDecl节点处理String str("xxx");这类变量声明,还要扩展CallExpr支持成员方法调用(比如str.append(str1))。
2. 在LLVM IR中声明llvm::SmallString类型
在代码生成的初始化阶段,我们需要在LLVM模块中声明llvm::SmallString的类型(这里以SmallString<16>为例,你可以根据需求调整大小):
// 获取或创建SmallString的opaque结构体类型 llvm::StructType *SmallStringTy = llvm::StructType::getTypeByName(TheContext, "llvm::SmallString<16>"); if (!SmallStringTy) { // 作为opaque类型,不需要定义内部字段,只需要标识类型即可 SmallStringTy = llvm::StructType::create(TheContext, "llvm::SmallString<16>"); SmallStringTy->setBody({}, false); }
3. 处理String变量的构造(比如String str("Hello World");)
SmallString的构造函数是成员函数,我们需要先声明它的mangled名字(可以通过g++ -c -S -emit-llvm编译一个测试C++文件,从IR中提取函数名),然后在LLVM模块中注册:
// 声明SmallString<16>的构造函数:void llvm::SmallString<16>::SmallString(const char*) llvm::FunctionType *StrCtorTy = llvm::FunctionType::get( llvm::Type::getVoidTy(TheContext), {SmallStringTy->getPointerTo(), llvm::Type::getInt8PtrTy(TheContext)}, false ); llvm::FunctionCallee StrCtor = TheModule->getOrInsertFunction( "_ZN4llvm11SmallStringILi16EEC1EPKc", // 注意:这里的名字要和你的SmallString大小匹配 StrCtorTy );
然后在生成变量声明的IR时:
- 先为String变量分配栈空间;
- 把字符串字面量转换成LLVM的常量指针;
- 调用构造函数(成员函数的第一个参数是
this指针,也就是变量的栈地址):
// 处理String str("Hello World"); llvm::AllocaInst *StrAlloca = Builder.CreateAlloca(SmallStringTy, nullptr, "str.addr"); // 生成字符串字面量的常量指针 llvm::Constant *StrLiteral = llvm::ConstantDataArray::getString(TheContext, "Hello World"); llvm::Value *StrPtr = Builder.CreateBitCast(StrLiteral, llvm::Type::getInt8PtrTy(TheContext)); // 调用构造函数初始化 Builder.CreateCall(StrCtor, {StrAlloca, StrPtr});
4. 处理String的方法调用(比如str.append(str1);)
同样的逻辑,先声明append方法的mangled名字和函数类型:
// 声明append方法:llvm::SmallString<16>& llvm::SmallString<16>::append(const llvm::SmallString<16>&) llvm::FunctionType *AppendTy = llvm::FunctionType::get( SmallStringTy->getPointerTo(), // 返回值是SmallString&,对应IR中的指针 {SmallStringTy->getPointerTo(), SmallStringTy->getPointerTo()}, // this指针 + 参数引用 false ); llvm::FunctionCallee AppendFunc = TheModule->getOrInsertFunction( "_ZN4llvm11SmallStringILi16EE6appendERKS0_", // 同样要匹配SmallString的大小 AppendTy );
然后生成调用IR:
// 假设已经获取到str和str1的栈地址:StrAlloca、Str1Alloca llvm::Value *StrThis = Builder.CreateLoad(SmallStringTy, StrAlloca, "str.this"); llvm::Value *Str1Arg = Builder.CreateLoad(SmallStringTy, Str1Alloca, "str1.arg"); // 调用append方法 Builder.CreateCall(AppendFunc, {StrThis, Str1Arg});
二、调用C++代码的通用思路
其实上面的构造函数和方法调用都是调用C++代码的场景,通用步骤总结:
- 获取mangled名字:C函数/方法的名字会被 mangled,你可以用
g++ -c -S -emit-llvm编译一个包含目标函数的C文件,从生成的.ll文件中提取函数名; - 声明LLVM函数类型:根据C++函数的参数、返回值,对应到LLVM的类型(比如成员函数的第一个参数是
this指针,引用类型对应IR中的指针); - 生成调用IR:确保参数传递正确(尤其是
this指针、引用这类特殊类型)。
踩过的小坑
- 如果你调整了SmallString的大小(比如从16改成32),对应的mangled名字也会变化,一定要重新提取;
- 字符串字面量在LLVM中是常量数组,需要用
CreateBitCast转换成char*类型; - 如果你的语言支持变量作用域,记得在变量离开作用域时调用SmallString的析构函数(同样需要声明析构函数的mangled名字)。
内容的提问来源于stack exchange,提问作者ecwdw 23e3e23e
相关产品推荐
相关产品推荐

