☰
仓颉 LLVM 专属 Intrinsic 全解析:GC Barrier 与原子操作的 30+ 内置函数清单
2026/10/9 3:42:09 网站建设 项目流程

仓颉 LLVM 专属 Intrinsic 全解析:GC Barrier 与原子操作的 30+ 内置函数清单

【免费下载链接】llvm-projectLLVM 项目是一个模块化、可复用的编译器及工具链技术的集合。此fork用于添加仓颉编译器的功能,并支持仓颉编译器项目。项目地址: https://gitcode.com/Cangjie/llvm-project

仓颉 LLVM(Cangjie llvm-project)是在标准 LLVM 基础上为仓颉语言定制的编译器基础设施。本文将完整梳理其中70+ 个int_cj_专属 Intrinsic(内置函数),重点覆盖 GC Barrier(写屏障)、GC 读操作与 4 大原子操作,帮助新手快速看懂仓颉编译器如何管理内存、数组与并发。

从上图可以看到,这套工具链中 Clang 与Cangjie(仓颉)并列作为表达式解析后端,LLVM 则承担反汇编与指令模拟——仓颉专属 Intrinsic 正是连接两者底层的关键桥梁。

什么是 Intrinsic?为什么仓颉必须扩展?

Intrinsic(内置函数)是 LLVM IR 中只声明、不实现的特殊函数:编译器在代码生成阶段把它们直接替换为特定指令或运行时调用。

仓颉是一门带自动内存管理(GC)+ 强类型 + 高并发特性的语言,标准 LLVM 的 Intrinsic 无法表达这些语义,因此仓颉团队扩展了一整套int_cj_*前缀的内置函数,分为六大类:

分类数量解决什么问题
GC Barrier(屏障)17 个写对象字段时通知 GC,防止指针被移动
运行时 Runtime31 个堆分配、除法检查、异常、线程与 GC 统计
原子操作 Atomic4 个对象字段的并发读写
后端 Backend4 个浮点状态、位置追踪
泛型 Generic17 个泛型赋值、vtable/mtable 查找
GC 快照 Snapshot4 个写屏障 GC 的堆指针重定位

5 分钟定位源码:Intrinsic 定义在哪?

所有仓颉 Intrinsic 的核心定义都在 TableGen 文件中,按分区注释组织,检索非常方便:

  • 总定义:llvm/include/llvm/IR/Intrinsics.td —— 从Cangjie GC Barrier Intrinsics分区开始
  • 快照 GC 分区:Intrinsics.td 第 1481-1497 行
  • 参数索引封装:CJIntrinsics.h —— 用GCReadRef、AtomicLoad等结构体为每个参数命名
  • GC 结构体元信息:CJStructTypeGCInfo.h
  • 配套的 GC 感知别名分析:CJAliasAnalysis.cpp

在 IR 中,这些函数的名字形如llvm.cj.gcwrite.ref、llvm.cj.atomic.load——也就是把 TableGen 定义名中的下划线换成点号并加上llvm.前缀。用opt -print-after-all观察 IR 输出时,这就是你搜索它们的关键词。

GC Barrier 内置函数清单:写屏障是核心

GC Barrier(屏障)是自动内存管理的心脏:向对象字段写入引用时,必须插入一条屏障指令,让 GC 有机会记录这次写入(或移动堆对象)。仓颉把屏障细化为ref / struct / static / generic四种粒度:

写屏障(gcwrite 系列,9 个)

Intrinsic(IR 名)定义行作用
llvm.cj.gcwrite.refIntrinsics.td#L437向对象引用字段写值,带NoCapture语义
llvm.cj.gcwrite.structIntrinsics.td#L440按偏移 + 大小写结构体字段区,标注NoAlias/WriteOnly
llvm.cj.gcwrite.static.refIntrinsics.td#L444写全局/静态引用变量
llvm.cj.gcwrite.static.structIntrinsics.td#L447写全局结构体字段区
llvm.cj.gcwrite.genericIntrinsics.td#L598泛型对象写屏障
llvm.cj.gcwrite.generic.payloadIntrinsics.td#L637写泛型 payload 区
llvm.cj.array_copy.refIntrinsics.td#L469引用数组整体拷贝(逐元素屏障)
llvm.cj.array_copy.structIntrinsics.td#L471结构体数组拷贝
llvm.cj.array_copy.genericIntrinsics.td#L635泛型数组拷贝

读屏障(gcread 系列,6 个)

Intrinsic定义行作用
llvm.cj.gcread.refIntrinsics.td#L451从对象引用字段读值
llvm.cj.gcread.weakrefIntrinsics.td#L454读弱引用(可能返回空)
llvm.cj.gcread.structIntrinsics.td#L458按偏移 + 大小读结构体字段区
llvm.cj.gcread.static.refIntrinsics.td#L462读全局引用变量
llvm.cj.gcread.static.structIntrinsics.td#L465读全局结构体字段区
llvm.cj.gcread.genericIntrinsics.td#L604泛型对象读屏障

其他屏障辅助(4 个)

  • llvm.cj.copy_struct.field(L473):跨对象拷贝单个结构体字段
  • llvm.cj.cross_access.barrier(L475):跨访问屏障,标记 i64 键
  • llvm.cj.get_exported.ref/llvm.cj.create_export.handle(L476-L480):导出/回收跨模块引用句柄
  • llvm.cj.get_lambda.addr(L481):获取闭包地址

💡新手技巧:CJIntrinsics.h 中为每个 Intrinsic 定义了参数枚举(如GCWriteRef的Val / BaseObj / FieldPtr),配合getBaseObj()、getValueArg()等辅助函数,是读懂屏障语义的最快入口。

原子操作 Intrinsic:4 个函数搞定对象字段并发

仓颉的原子操作直接作用于对象字段(而非裸指针),由基对象 + 字段偏移 + 内存序三元组定位,天然与 GC 兼容:

Intrinsic定义行内存模型
llvm.cj.atomic.storeIntrinsics.td#L560参数:新值、基对象、字段指针、内存序
llvm.cj.atomic.loadIntrinsics.td#L562返回基对象类型的旧值
llvm.cj.atomic.swapIntrinsics.td#L564原子交换并返回旧值
llvm.cj.atomic.compare_swapIntrinsics.td#L566CAS:成功返回 i1,区分成功/失败内存序

参数索引在 CJIntrinsics.h#L73-L87 中用AtomicLoad { Obj, Field, Order }等结构体明确命名——例如AtomicCompareSwap的六个参数依次为OldRef, NewRef, Obj, Field, SuccOrder, FailOrder,与 C++ 标准库compare_exchange_strong的语义一一对应。

运行时与 GC 快照 Intrinsic:堆、异常与 Profiling

内存分配与安全检查

  • llvm.cj.malloc.object/llvm.cj.malloc.array(L492-L497):堆上分配对象/数组,带Throws标志(可触发 GC 后重试)
  • llvm.cj.alloca.generic(L595):泛型栈上分配
  • llvm.cj.division_check.sdiv / udiv / srem / urem(L498-L509):带溢出/除零检查的除法与取余

异常、线程与 GC 统计(共 20 余个)

分组代表 Intrinsic
异常throw.exception、post_throw.exception、fill.in.stack.trace(L514-L526)
堆信息get.real.heap.size、get.max.heap.size、dump.heap.data(L527-L534)
线程get.thread.number、get.native.thread.number(L535-L540)
GC 指标get.gc.count、get.gc.time.us、invoke.gc、set.gc.threshold(L541-L554)
Profilingstart.cpu.profiling/stop.cpu.profiling(L547-L550)

写屏障 GC 的快照四件套

支持可移动堆(snapshot GC)的四个 Intrinsic 定义了完整的重定位协议(Intrinsics.td#L1481-L1497):

  1. llvm.cj.gc.statepoint—— 进入状态点,保存当前堆指针变量,返回 token
  2. llvm.cj.gc.result—— 从 token 取回 GC 结果
  3. llvm.cj.gc.relocate—— 按(token, 指针索引, 变量索引)重定位堆指针
  4. llvm.cj.stack.relocate—— 重定位栈上对象

这组设计与 LLVM 标准快照 GC 提案的思路一致,但把对象索引显式作为立即数参数(ImmArg),方便优化器做常量折叠。

泛型与类型系统 Intrinsic:17 个函数的分工

仓颉是值类型 + 引用类型混合语言,泛型代码在 IR 层面需要大量类型元数据支持(L576-L638):

  • 类型判定:is_subtype、is_typeinfo.equal、is_reference、is_tupletype_of—— 全部标注IntrSpeculatable,可安全提前执行
  • 元数据获取:get.type.info、get.field.offset、get.obj.klass—— 支撑运行时类型系统
  • 动态分发:get.vtable.func、get.mtable.func、get.method.outertype、vfe.info—— 虚表/接口表查找;其中vfe.info用显式参数携带 VFE 元数据,确保优化 pass 不丢失函数表偏移信息
  • 泛型赋值:assign.generic—— 按 TypeInfo 深拷贝泛型值,配合gcwrite.generic完成完整写入

上手路线图:3 步读懂仓颉 Intrinsic

  1. 先读清单:打开 Intrinsics.td#L435-L638,每个分区注释(Cangjie xxx Intrinsics)即分类目录,参数列表一目了然
  2. 再读封装:CJIntrinsics.h 的参数枚举把"第 2 个参数是什么"变成了具名常量,配合getAtomicOrder()等访问器理解调用约定
  3. 最后看消费者:优化器如何使用它们?检索isCJIntrinsic或在 llvm/lib/Analysis/ 中查看CJAliasAnalysis.cpp、CJFunctionVarLifeTime.cpp,即可理解屏障 Intrinsic 如何参与别名分析与活性分析

📌总结:这套 70+ 的int_cj_Intrinsic 家族,用GC Barrier保证了可移动堆的正确性,用原子对象字段操作简化了并发编程,用快照四件套预留了写屏障 GC 的演进空间——这正是仓颉 LLVM 相对标准 LLVM 最大的差异化所在。

【免费下载链接】llvm-projectLLVM 项目是一个模块化、可复用的编译器及工具链技术的集合。此fork用于添加仓颉编译器的功能,并支持仓颉编译器项目。项目地址: https://gitcode.com/Cangjie/llvm-project

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询