打开 @wryjdhej 回答中微软研究院搞 Static TypeScript 的论文后,真让我吃了一惊,因为它恰好和我自己的业余玩票方向有不小的关系。我尝试认真解答一下。
首先,Static TypeScript (STS) 是什么呢?
STS 是用 TypeScript 写的编译器,能把 TS 的子集编译到 ARM 机器码。
难道编译出机器指令的编译器,可以不用底层语言写出来吗?其实编译也就是种「把一种结构化数据转成另一种」的工作,主流编程语言都可以完成。相关的科普介绍请移步我的这个回答: 程序语言都是怎么发明的?
既然是研究院而非微软本家出品,那么 STS 是个玩具项目吗?是,也不是。STS 已经落地到了微软的 MakeCode 项目里,主打面向教育的游戏编程,在发达国家已有百万级的师生使用。像下图中网页里给小朋友们写的,就是 STS:
我都给方老师想好广告词了——现在六年级小朋友都在学 TS,你还敢不学 TS 吗?快来报我的培训班吧!
那么,为什么不用 VSCode 这样自家非常成熟的 TS 编辑器,而要重新发明轮子呢?论文中给了一些这个领域里重要的需求点:
万恶的资本主义教育,居然给小朋友们发游戏机毒害他们!这些游戏机长什么样呢?是这样的:
理论上,只要把 JS 脚本拷进去解释执行,不就可以了吗?这有两个难点:
STS 就是解决这个问题的一种方案——有趣的是,这不是唯一的方案。我业余其实也在做些把 Web 技术栈移植到游戏机的工作。目测去年论文发表时,Fabrice Bellard 大神还没有推出他的 QuickJS 嵌入式 JS 引擎,因此论文里只提到了 DukTape 和 IoT.js 这些老轮子。新的 QuickJS 比这些嵌入式 JS 引擎都快(但也不是差上数量级的快),并完整支持了 ES2019。以此为契机,我近期也基于它做了些嵌入式设备上的引擎移植尝试,比如这两篇文章:
第一篇文章里我用的是顶配树莓派四代,性能算是非常过剩的,不必多提。第二篇文章中,我移植的对象是 32MB 内存的国产掌机 Miyoo,它跑起 QuickJS 的效果像这样:
当我把运行 JS 的目标定在 32MB 内存的机器时,微软考虑的是怎么在 32KB 内存的芯片上跑 JS……好吧虽然感觉他们的目标太苛刻了,但还是不得不服气,是在下输了。
所以,微软这帮人做了什么呢?MakeCode 是面向用户的产品,整套东西做得很接地气,分层设计看起来也很合理。主要包括这些:
下面就按论文里的方式,分几个维度介绍下 STS 吧。
首先,STS 自然是具备过人之处的,否则也不必写成论文了。STS 的独创能力主要包括以下这些:
STS 既然是 TS 的子集,那么肯定存在一些能力上的限制,主要包括这些:
STS 最底层的轮子分两部分:工具链(编译器、链接器等)和运行时。
对编译器来说,其职责在于产生 IR 中间表达。STS 编译器的 IR 能够转换到如下三种后端:
在 STS 中,ARM Thumb 和自定义的字节码,都是基于相同的汇编码生成的。这里的亮点在于,这一整条工具链,从 TypeScript 编译器,到 STS 代码生成器,再到汇编器和链接器,都是用 TypeScript 编写的,可以顺利地横跨浏览器和命令行环境来运行。很难想象,类似安卓 NDK 级别的传统嵌入式工具链,居然可以完全用 Web 技术栈来实现!
虽然 STS 能生成 ARM 机器码,但这是需要运行时才能执行的。运行时是用 C++ 预编译好的,带有一个基础的垃圾回收器,还默认 include 了些内置的 package。预编译好的运行时是个 ELF 格式的可执行文件,STS 会从运行时的二进制文件中提取函数符号的地址,稍微修改一下编译出的机器码,然后把文件连在一起,从而完成链接。这部分知识在《程序员的自我修养:链接、装载与库》里有详细介绍,可惜我还没啃完…
还有重要的一点,那就是如何实现 JS 中的对象呢?STS 直接利用了 C++ 对象来表达 JS 对象。C++ 里使用 Virtual Table 来实现 OO 中的多态。对每个 class 的实例,都有个装着函数指针的表,指向所有的虚方法。而指向这张表的指针则是所有相应对象中的一个成员变量。调用虚函数时,实际上就是通过查表的方式来找到方法并执行的。STS 中的类使用单继承与静态的内存布局。每个对象都与一张虚函数表和各类字段相关联。
论文还顺便介绍了一些其它用到的技术:
性能测试自然也是少不了的,请看下图:
在分析上面这些 micro benchmark 数据前,先做些基本的科普:
这些跑分数据的要点,也很容易解释清楚:
对于具体的性能指标,论文中有很多讨论,大家可以自己去看看设计者自己的评估,还是有不少干货的。
总体来看,语言 Runtime 的性能鄙视链是这样的:弱类型无 JIT (QuickJS) < 强类型无 JIT (STS) < 弱类型带 JIT (V8) < 强类型带 JIT (JVM)。那么如果更进一步,给 STS 加上 JIT 支持会怎么样呢?这就要问微软爸爸了。
STS 的方案充分挖掘出了硬件潜力,只要所有游戏引擎、框架、逻辑等上层代码均可控,那么几乎就能把运行 JS 系语言的硬件配置降低到最低。当然,STS 的设计目标还是和 QuickJS 不同的。后者我已经试过可以一行不改地运行起工业级的 React,而 STS 已经明确了不会支持 JS 了。对于 16KB 内存的极限情况来说,STS 确实是非常牛逼的方案。不过到了 32MB 的量级,差异就没有那么明显了。要知道索尼 PSP 的内存也就只有 32MB 呢。
那么,STS 会成为社区欢迎的新引擎吗?感觉还真不好说。毕竟一言不合就重写经典框架,不是前端社区的 传 统 艺 能 吗?
最后是我自己的几点感想:
勘误,这个项目是微软研究院在 Redmond 的组做的,最早我以为是 MSRA 的项目啦
个人觉得,完全有必要。
一个原因就是性能。
没记错的话,ie时代,js性能很差的。
后来webkit内核的浏览器取代了ie,统一了市场。原因就在于webkit内核优化了js的性能。各种预编译,缓存,js性能得到极大的提高。
但是据说,js性能优化已经到了极限。原因就在于js是一种动态类型的语言。一个变量的类型不确定,可以动态变化。但是,机器语言就是cpu认的底层指令是有类型的。所以无法继续提升js性能了。
所以,如果浏览器支持ts直接编译运行,那js性能可以直逼Java和c#。