由 QuickJS 想到的

前几天,一个叫 Fabrice Bellard 的大牛程序员发布了一个新的 JS 引擎:QuickJS,引起了业界轰动。QuickJS 的主要特点是小,嵌入式,完整支持 ES2019 语法,和其他小型嵌入式 JS 引擎比起来,速度也很快。

登上大牛的主页(https://bellard.org),瞬间被惊呆了。原来之前耳熟能详的 FFMPEG、QEMU 都是出自他之手。还有一些之前闻所未闻的项目,据说这些都是大神的 Side Project。这些项目范围涉及之广让人瞠目,有涉及科学计算,提出最快速的计算圆周率的算法,并于 2009 年声称打破了圆周率计算的世界纪录,算出小数点后 2.7 万亿位,仅用一台普通个人计算机。有涉及编译器和虚拟机的,1996年,编写了一个简洁但是完整的 C 编译器和一个 Java 虚拟机 Harissa。发明的 TinyCC 是GNU/Linux 环境下最小的 ANSI C 语言编译器,是当前号称编译速度最快的 C 编译器。用 JavaScript 写的一个 PC 虚拟机 Jslinux。图形渲染相关 TinyGL,图片格式 BPG,等等……

他的个人主页也很特别,没有任何 CSS 样式,也没有一张图片,只有纯文本的项目罗列和介绍,甚至没有一点关于 About Me 的信息。然后,在 GitHub、Twitter、Facebook 等地方,都找不到这位神人的踪迹,不禁让人怀疑是不是一个真实存在的人,后来在 Hacker News 上有人证实了确实见过真人,更有人大声惊呼:还有 Fabrice 不能做的东西吗?看来他是一个深居简出,沉浸在代码世界而不受外界干扰的大神级程序员。

这让我更加有兴趣去拜读一下他的代码了。QuickJS 的主页介绍简单明了,代码直接是一个压缩包,名字里带着创建的日期:quickjs-2019-07-09.tar.xz。这大概是我在十几年前常见的代码共享的方式,而现在已经 2019 年了,大神也没有选择 GitHub 之类的开源协作的平台,甚至让我怀疑他开发过程中是不是有使用 git 之类的源代码管理工具。大神总是特立独行的,直接丢一个压缩包,大概是在说:这就是我所有代码了,有任何问题和疑问,直接给我发邮件吧,不接受任何社交媒体联系方式。

我隐约觉得我的关注重点应该还是他的代码,而不是他的人,也许他本人也是这么认为的。代码解压后,代码是用 C89 写的,不是 C++,甚至不是 C99。这大概是一种技术偏执,在 C 和 C++ 中选择 C 的神级程序员也不在少数,比如开发了 skynet 的云风,还有游戏引擎大牛 Andre Weissflog。在 Andre 的 twitter 上,他贴了一条自己的编程语言使用历程:

Z80 asm => 68k asm => pre-C89 => post-C89 => C++98 => C++11 => C99

经历过 C++11 的各种便利和绚丽,最后还是返璞归真的选择了 C99,或许也给我带来了一些深层的思考:什么才是程序的本质。和 Fabrice 不同,Andre 是一个社交型程序员,甚至使得我有一种感觉,他整天都在发 twitter,但发现他的代码居然一行也没有少写。

这样看来 Fabrice 选择 C89,也没有什么让人奇怪的地方了。代码解压后,自然是先编译一通。没有用 CMake,只有 MakeFile,嗯,一点问题也没有,大概我们都是被 CMake 惯坏了吧,离了它几乎都不懂得怎样编译代码了。大神总是以一种最原始最直接的方式,去组织和掌控着一切。在 linux 上,直接 make 编译,不用多长时间就顺利编过了。

据说 Fabrice 是从 2017 年开启的这个 Side Project,现在的我看他的代码,由如管中窥豹,只能看到一些粗浅的东西,更多东西还得花时间细细研读。但就 QuickJS 而言,在目前的版本里,它是没有 JIT 的,所以和 Google 的 V8 性能比起来,还是有很大差距的。它将一段 JS 代码编译生成一个可执行二进制的方式,是先将 JS 编译成字节码,然后将字节码嵌入到一段 c 的代码里,然后解释执行这段字节码。

比如,hello.js 代码:

console.log("Hello World");

经过 qjsc 编译后,会生成一个这样的 hello.c 代码:

/* File generated automatically by the QuickJS compiler. */

#include "quickjs-libc.h"

const uint32_t hello_size = 87;

const uint8_t hello[87] = {
 0x01, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48,
 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72,
 0x6c, 0x64, 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70,
 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c,
 0x6f, 0x2e, 0x6a, 0x73, 0x0d, 0x00, 0x02, 0x00,
 0x9e, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00,
 0x14, 0x01, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x38,
 0xc4, 0x00, 0x00, 0x00, 0x42, 0xc5, 0x00, 0x00,
 0x00, 0x04, 0xc6, 0x00, 0x00, 0x00, 0x27, 0x01,
 0x00, 0xd2, 0x2b, 0x8e, 0x03, 0x01, 0x00,
};

int main(int argc, char **argv)
{
  JSRuntime *rt;
  JSContext *ctx;
  rt = JS_NewRuntime();
  ctx = JS_NewContextRaw(rt);
  JS_AddIntrinsicBaseObjects(ctx);
  js_std_add_helpers(ctx, argc, argv);
  js_std_eval_binary(ctx, hello, hello_size, 0);
  js_std_loop(ctx);
  JS_FreeContext(ctx);
  JS_FreeRuntime(rt);
  return 0;
}

所有相关的 opcode 可以在 quickjs-opcode.h 里找到。最核心的 quickjs.c 有 47842 行。作为一个完整的 JS 引擎,代码已经是相当的精炼了,活生生的一本教科书。不过,现在的 QuickJS 在性能要求高的场景和 V8 还是没有可比性,希望这个东西不仅仅是大神闲暇之余的玩具,期待之后能带来更大的惊喜吧。

有人说, Fabrice 就是 10x Engineer,即工作效率相当于 10 个程序员。立马有人站出来反对,分明是 100x Engineer 嘛。这个世界就是这样,有些人比你聪明,比你有天赋,工作效率是你的 10 倍,同时还比你努力。当你在上班划水时,他在写代码,你在吃鸡落地成盒,他又调通了一个核心模块。

微信扫一扫交流

作者:CoderZh
微信关注:hacker-thinking (一个程序员的思考)
本文出处:https://blog.coderzh.com/2019/07/14/quickjs/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。