StaticScript 类与结构体编译原理:TypeScript class 如何变成 LLVM StructType?

StaticScript 类与结构体编译原理:TypeScript class 如何变成 LLVM StructType?

【免费下载链接】StaticScript :rocket: TypeScript compiler on top of TypeScript as frontend and LLVM as backend 【免费下载链接】StaticScript 项目地址: https://gitcode.com/gh_mirrors/sta/StaticScript

StaticScript 是一个以 TypeScript 为前端、LLVM 为后端的开源编译器,它的目标是把 TypeScript 代码直接编译成原生二进制。今天我们用最通俗的方式,拆解它最核心的一环:TypeScript class 如何一步步变成 LLVM StructType。无论你是编译器新手,还是好奇"类到底怎么在底层表示"的开发者,这篇文章都能帮你快速建立认知。

StaticScript 编译流程图:TypeScript 源码经过 LLVM 后端最终生成可执行文件

如上图所示,StaticScript 的整体流程是:.ts 源码 → LLVM-IR → Bytecode → 目标文件 → 与运行时库链接,最终得到可执行文件。而 class 到 StructType 的转换,就发生在"生成 LLVM-IR"这个阶段。

核心思路:class 本质上是"结构体 + 构造函数"

在 LLVM 的世界里,并没有"类"这个概念。TypeScript 的 class 到了 LLVM 眼里,就是一个结构体(Struct):类的每个属性,就是结构体的一个字段;类的实例化,就是调用一个返回结构体指针的函数。

这个思路是整个转换过程的地基,记住它,后面的步骤就都顺理成章了。⚡️

第一步:用 TypeScript 类型检查器收集类的属性

编译流程的入口在 src/backend/llvm/index.ts:当遍历到 ClassDeclaration 语法节点时,会被转交给专门的 ClassDeclarationGenerator(位于 src/backend/llvm/code-generation/class-statement.ts)。

它做的第一件事,是借助 TypeScript 编译器 API 拿到类的"体检报告":

  1. 通过 ctx.typeChecker.getTypeAtLocation(node) 获取 class 的类型对象;
  2. 再用 getPropertiesOfType(classType) 拿到该类型的所有属性(成员)列表。

这一步很聪明——它复用了 TypeScript 前端的类型分析能力,不需要自己重新解析属性,这正是"TypeScript 做前端"这句话的真正含义。

第二步:把 TS 类型映射成 LLVM 原生类型

拿到属性列表后,就要确定每个属性在 LLVM 中对应什么类型。这个映射工作由 NativeTypeResolver(源码在 src/backend/llvm/native-type-resolver.ts)完成,规则非常直观:

TypeScript 类型LLVM 类型说明
numberdouble(双精度浮点)数值默认按浮点处理
stringi8*(字符指针)字符串用指针表示
booleani1(1 位整数)布尔就是 0/1
int8 / int16 / int32 / int64 / int128对应位宽的整数类型显式整数类型
uint8 ... uint128无符号整数带符号位标记
float32 / float128对应浮点类型显式浮点类型
voidvoid常用于函数返回

有了这张"翻译表",每个 TypeScript 属性都能找到自己的 LLVM 原生类型。🧩

第三步:创建并填充 LLVM StructType

类型映射完成后,就该真正"造结构体"了。代码里只有短短几行,但信息量很大:

const struct = llvm.StructType.create(ctx.llvmContext, structName);
struct.setBody(properties.map((property) => {
    const nativeType = NativeTypeResolver.getType(
        ctx.typeChecker.getTypeOfSymbolAtLocation(property, node),
        ctx
    );
    return nativeType.getType();
}));

过程分两步:

  • StructType.create 先按类名创建一个"空"结构体类型(注意这里的名称就是 TypeScript class 的名字);
  • setBody 再把属性们映射好的 LLVM 类型按声明顺序填充进去,结构体就此定型。

也就是说,一个 class People { weight: number; length: number; } 最终会变成一个包含两个 double 字段的 LLVM 结构体,字段顺序与源码声明顺序完全一致。📦

第四步:把结构体注册进类的符号表

结构体建好后,不能就这么扔掉,后续 new 表达式还要用到它。所以最后一步是把它登记到作用域(Scope)的类表中:

ctx.scope.classes.set(structName, struct);

Scope(源码在 src/backend/llvm/scope.ts)里维护着一张 ClassesTable,本质是 Map<string, llvm.StructType>——类名到结构体类型的映射表。凡是编译过程中遇到过的 class,都能在这里查到对应的 LLVM StructType。

更有意思的是,Contextsrc/backend/llvm/context.ts)在初始化时就已经预注册了一批"内置类":Uint8ArrayInt32ArrayFloat64Array 等 TypedArray 全部以结构体形式预先登记,甚至数组本身也是用结构体表示的,形如 array<type>,内部包含元素类型、size(大小)和 capacity(容量)三个字段。

new 表达式如何找到这个 StructType?

实例化类时,NewExpressionGeneratorsrc/backend/llvm/code-generation/new-expression.ts)会登场。它通过 buildFromIdentifier 在类的符号表中查找类名,命中后得到一个 ClassReference——它并不代表某个具体值,而是携带了对应的 StructType 引用,再配合构造签名生成构造函数调用,最终包装成 ObjectReference 使用。

这里还有一个细节:Value 体系(src/backend/llvm/value.ts)里区分了 ClassReferenceObjectReferencePrimitive 等不同角色,类型信息随值一起流动,这也是编译器能够持续追踪"每个值是什么类型"的关键。

当前限制:这些 class 写法还不支持

作为教学价值极高的实验性编译器,StaticScript 的 class 支持目前是"精简版",了解这些限制能让你少踩坑:

  • 泛型类:遇到 typeParameters 会直接抛出 UnsupportedError;
  • 类方法:类里只允许属性声明,出现方法会被拒绝;
  • 继承:测试用例 tests/snapshots/general/class.ts 中带 extends 的代码目前处于注释状态,说明继承尚未实现;
  • 能用的:属性声明 + new 实例化 + 属性访问。

总结:一条清晰的四步流水线

回顾一下,TypeScript class 变成 LLVM StructType 的完整链路是:

  1. 🔍 TypeScript 类型检查器收集类的属性列表;
  2. 🧩 NativeTypeResolver 把 TS 类型翻译成 LLVM 原生类型;
  3. 🏗️ StructType.create + setBody 按序填充字段,造出结构体;
  4. 📋 把"类名 → 结构体"注册进 Scope.classes 符号表,供 new 表达式查用。

StaticScript 用最直白的方式演示了"面向对象语法如何降级为结构化数据"这一经典编译思路。如果你想亲手验证,克隆 https://gitcode.com/gh_mirrors/sta/StaticScript 仓库,然后从 src/backend/llvm/code-generation/class-statement.ts 读起,沿着上述四步逐行对照,相信你会收获满满。🚀

【免费下载链接】StaticScript :rocket: TypeScript compiler on top of TypeScript as frontend and LLVM as backend 【免费下载链接】StaticScript 项目地址: https://gitcode.com/gh_mirrors/sta/StaticScript

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

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

抵扣说明:

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

余额充值