You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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:新增StringLiteralExpr AST节点存储字符串字面量,新增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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:28:36