仓颉 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,防止指针被移动 |
| 运行时 Runtime | 31 个 | 堆分配、除法检查、异常、线程与 GC 统计 |
| 原子操作 Atomic | 4 个 | 对象字段的并发读写 |
| 后端 Backend | 4 个 | 浮点状态、位置追踪 |
| 泛型 Generic | 17 个 | 泛型赋值、vtable/mtable 查找 |
| GC 快照 Snapshot | 4 个 | 写屏障 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.ref | Intrinsics.td#L437 | 向对象引用字段写值,带NoCapture语义 |
llvm.cj.gcwrite.struct | Intrinsics.td#L440 | 按偏移 + 大小写结构体字段区,标注NoAlias/WriteOnly |
llvm.cj.gcwrite.static.ref | Intrinsics.td#L444 | 写全局/静态引用变量 |
llvm.cj.gcwrite.static.struct | Intrinsics.td#L447 | 写全局结构体字段区 |
llvm.cj.gcwrite.generic | Intrinsics.td#L598 | 泛型对象写屏障 |
llvm.cj.gcwrite.generic.payload | Intrinsics.td#L637 | 写泛型 payload 区 |
llvm.cj.array_copy.ref | Intrinsics.td#L469 | 引用数组整体拷贝(逐元素屏障) |
llvm.cj.array_copy.struct | Intrinsics.td#L471 | 结构体数组拷贝 |
llvm.cj.array_copy.generic | Intrinsics.td#L635 | 泛型数组拷贝 |
读屏障(gcread 系列,6 个)
| Intrinsic | 定义行 | 作用 |
|---|---|---|
llvm.cj.gcread.ref | Intrinsics.td#L451 | 从对象引用字段读值 |
llvm.cj.gcread.weakref | Intrinsics.td#L454 | 读弱引用(可能返回空) |
llvm.cj.gcread.struct | Intrinsics.td#L458 | 按偏移 + 大小读结构体字段区 |
llvm.cj.gcread.static.ref | Intrinsics.td#L462 | 读全局引用变量 |
llvm.cj.gcread.static.struct | Intrinsics.td#L465 | 读全局结构体字段区 |
llvm.cj.gcread.generic | Intrinsics.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.store | Intrinsics.td#L560 | 参数:新值、基对象、字段指针、内存序 |
llvm.cj.atomic.load | Intrinsics.td#L562 | 返回基对象类型的旧值 |
llvm.cj.atomic.swap | Intrinsics.td#L564 | 原子交换并返回旧值 |
llvm.cj.atomic.compare_swap | Intrinsics.td#L566 | CAS:成功返回 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) |
| Profiling | start.cpu.profiling/stop.cpu.profiling(L547-L550) |
写屏障 GC 的快照四件套
支持可移动堆(snapshot GC)的四个 Intrinsic 定义了完整的重定位协议(Intrinsics.td#L1481-L1497):
llvm.cj.gc.statepoint—— 进入状态点,保存当前堆指针变量,返回 tokenllvm.cj.gc.result—— 从 token 取回 GC 结果llvm.cj.gc.relocate—— 按(token, 指针索引, 变量索引)重定位堆指针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
- 先读清单:打开 Intrinsics.td#L435-L638,每个分区注释(
Cangjie xxx Intrinsics)即分类目录,参数列表一目了然 - 再读封装:CJIntrinsics.h 的参数枚举把"第 2 个参数是什么"变成了具名常量,配合
getAtomicOrder()等访问器理解调用约定 - 最后看消费者:优化器如何使用它们?检索
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),仅供参考