Porffor:用 JavaScript 写的 JavaScript AOT 编译器
Porffor:用 JavaScript 写的 JavaScript AOT 编译器 如果你写过 JavaScript,你可能习惯了它的动态类型、即时编译(JIT)和无处不在的运行时。但有没有想过,如果把 JavaScript 提前编译成机器码会发生什么? 这就是 Porffor 想要回答的问题。 Porffor 是一个实验性的 AOT(Ahead-of-Time)JavaScript/TypeScript 编译器,由开发者 Oliver Medhurst 从零构建。它能将 JS/TS 代码编译为 WebAssembly 和原生二进制文件。 听起来不太特别?让我们看看它的核心特点: 目前项目仍处于 pre-alpha 阶段,但已经通过了 61% 的 Test262 测试(ECMAScript 官方兼容性测试套件)。 传统 JavaScript 引擎使用解释器或多层 JIT 编译器。代码在运行时被解析、编译和优化。这意味着: Porffor 采用了不同的方式: 这种 AOT 方式让你在开发时编译,在生产环境直接运行已编译的代码——无需预热,最小开销。 为了实现这个目标,Porffor 包含三个自研的子引擎: Porffor 的 Wasm 输出比现有 JS→Wasm 项目小 10-30 倍,性能也快 10-30 倍(相比打包解释器的方案)。 这意味着: Porffor 生成的二进制文件比传统方案小 1000 倍(从 ~90MB 到 <100KB)。 这使得以下场景成为可能: 作为实验性项目,Porffor 目前还有一些限制: 让我们写一个素数计算器来看看 Porffor 的实际效果: 输出: 编译输出: 编译输出: 你可能会好奇,Porffor 生成的 C 代码长什么样?让我们对比一下手写版本和自动生成的版本。 这是 JavaScript 的灵活性带来的代价——Porffor 需要模拟整个 JS 运行时。 Porffor 使用独特的版本号格式: 版本号直接告诉你这个项目对 ECMAScript 标准的支持程度! Porffor 只使用广泛实现的 Wasm 提案,确保最大兼容性: 值得注意的是,Porffor 有意避免使用尚未广泛实现的提案(如 GC 提案)。 "Purple"(紫色)的威尔士语就是 "porffor"。 选择紫色的原因很简单: Porffor 是一个极具实验性的项目。它通过独特的架构设计,尝试解决传统 JS 引擎在以下方面的问题: 虽然目前仍处于早期阶段,JS 特性支持不完整,但其创新的架构为 JavaScript 的未来应用提供了新的可能性。 也许某一天,你真的可以用 JavaScript 写一个只有 100KB 的 CLI 工具,然后编译到任何平台上运行。那将会是怎样的体验?发音:/ˈpɔrfɔr/(威尔士语中"紫色"的意思)
什么是 Porffor?
它是如何工作的?
JavaScript/TypeScript
│
▼
WebAssembly / C 代码
│
▼
原生二进制文件三个自研子引擎
子引擎 作用 Asur 自研 Wasm 引擎,简单的解释器实现 Rhemyn 自研正则表达式引擎,将正则编译为 Wasm 字节码 2c Wasm → C 转译器,用于生成原生二进制 快速开始
安装
npm install -g porffor@latest基本用法
# 交互式 REPL
porf
# 直接运行 JS 文件
porf script.js
# 编译为 WebAssembly
porf wasm script.js out.wasm
# 编译为原生二进制
porf native script.js out
# 编译为 C 代码
porf c script.js out.c编译选项
--parser=acorn|@babel/parser|meriyah|hermes-parser|oxc-parser # 选择解析器
--parse-types # 解析 TypeScript
--opt-types # 使用类型注解优化
--valtype=i32|i64|f64 # 值类型(默认:f64)
-O0, -O1, -O2 # 优化级别谁需要 Porffor?
编译为 WebAssembly
编译为原生二进制
安全特性
eval,防止动态代码执行当然,它也有局限性
限制 说明 异步支持有限 Promise 和 await 支持有限作用域限制 不支持跨作用域变量(除参数和全局变量) 无动态执行 不支持 eval()、Function() 等(AOT 特性)JS 特性支持不完整 Test262 通过率约 61% 与其他 JS 引擎对比
架构差异
引擎 类型 编译策略 输出 Porffor AOT JS → Wasm/Native Wasm/二进制 V8 JIT 解释器 + 多层 JIT 机器码 QuickJS 字节码 JS → 字节码 字节码 性能对比
场景 Porffor JIT 引擎 字节码引擎 冷启动 最快 慢(需预热) 中等 峰值性能 中等 最快 慢 内存占用 低 高 中等 二进制大小 极小 N/A 小 什么时候选择什么?
Porffor 最适合:
├── 需要极小二进制体积的场景
├── 需要快速冷启动的场景(如 Serverless)
├── 需要安全沙箱执行的场景
└── 嵌入式/游戏机等非传统 JS 平台
V8/SpiderMonkey 最适合:
├── 通用 Web 应用
├── Node.js 服务端应用
└── 需要完整 JS 特性支持的场景
QuickJS/JerryScript 最适合:
├── 嵌入式设备
├── 资源受限环境
└── 不需要极致性能的场景动手试试
// 检查一个数是否为素数
function isPrime(n) {
if (n < 2) return 0;
if (n === 2) return 1;
if (n % 2 === 0) return 0;
const sqrtN = Math.sqrt(n);
for (let i = 3; i <= sqrtN; i += 2) {
if (n % i === 0) return 0;
}
return 1;
}
// 查找指定范围内的所有素数
function findPrimes(start, end) {
const primes = [];
let count = 0;
for (let i = start; i <= end; i++) {
if (isPrime(i)) {
primes[count] = i;
count++;
}
}
primes.length = count;
return primes;
}
// 主程序
function main() {
const START_NUM = 1;
const END_NUM = 100;
console.log('=== Porffor Prime Calculator ===');
console.log('Range:', START_NUM, 'to', END_NUM);
const primes = findPrimes(START_NUM, END_NUM);
console.log('Found', primes.length, 'primes');
let sum = 0;
for (let i = 0; i < primes.length; i++) {
sum += primes[i];
}
console.log('Sum:', sum);
console.log('Average:', sum / primes.length);
return 'Done!';
}
main();直接运行
porf prime.js=== Porffor Prime Calculator ===
Range: 1 to 100
Found 25 primes
Sum: 1060
Average: 42.4
Done!编译为 WebAssembly
porf wasm prime.js prime.wasmparsed: 5ms
generated wasm: 40ms
optimized: 7ms
assembled: 5ms
[108ms] compiled prime.js -> prime.wasm (36.5KB)编译为原生二进制
porf native prime.js primeparsed: 5ms
generated wasm: 38ms
optimized: 7ms
assembled: 4ms
compiled Wasm to C: 18ms
compiled C to native: 959ms
[1080ms] compiled prime.js -> prime (106.6KB)输出格式对比
格式 文件大小 编译时间 运行方式 源 JS 2.4KB - porf file.jsWasm 36KB ~100ms 需 Wasm 运行时 C 代码 356KB ~130ms 需 C 编译 Native 106KB ~1100ms 独立运行 生成的 C 代码是什么样的?
手写 C 版本(96 行,2.3KB)
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
bool isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
int sqrtN = (int)sqrt(n);
for (int i = 3; i <= sqrtN; i += 2) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int primes[100];
int primeCount = findPrimes(1, 100, primes);
printf("Found %d primes:\n", primeCount);
for (int i = 0; i < primeCount; i++) {
printf("%d%s", primes[i], i < primeCount - 1 ? ", " : "\n");
}
return 0;
}Porffor 生成的版本(12,880 行,353KB)
// generated by porffor 0.61.2
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
// Wasm 类型定义
typedef uint8_t u8;
typedef int32_t i32;
typedef double f64;
// JS 值结构体(数字或对象)
struct ReturnValue {
f64 value;
i32 type; // 类型标签
};
// Wasm 线性内存模拟
char* _memory;
u32 _memoryPages = 5;
// Wasm 指令模拟函数
i32 i32_load(i32 align, i32 offset, i32 pointer);
void f64_store(i32 align, i32 offset, i32 pointer, f64 value);
// JS 内置函数实现
struct ReturnValue __ecma262_ToString(...);
f64 __Math_sqrt(f64 l0);
void __Porffor_printString(...);
// ... 数百个内置函数
// 用户函数(从 JS 转换)
struct ReturnValue isPrime(...);
struct ReturnValue findPrimes(...);
int main() {
_memory = (char*)malloc(65536 * _memoryPages);
const struct ReturnValue _0 = _main(0, 0, 0, 0);
return 0;
}对比数据
指标 手写 C Porffor 生成 差异 源代码行数 96 行 12,880 行 134x 源文件大小 2.3KB 353KB 153x 二进制大小 33KB 104KB 3.15x 编译时间 ~10ms ~1080ms 108x 为什么 Porffor 生成的代码这么大?
原因 说明 Wasm 模拟层 需要模拟所有 Wasm 指令(load/store 等) JS 类型系统 JS 值可以是数字、字符串、对象,需要统一的 ReturnValue 结构内置函数库 实现 Math.*、console.log、Array.* 等数百个函数内存管理 Wasm 线性内存 + JS 对象内存的双重管理 字符串处理 JS 字符串是 UTF-16,需要复杂的转换逻辑 实际应用建议
场景 推荐方案 追求极致性能 手写 C / Rust 快速原型开发 Porffor(直接写 JS) 已有 JS 代码移植 Porffor(无需重写) 需要跨平台 Porffor(一次编译,多平台运行) 学习/研究 Porffor(了解 JS→Wasm→C 的转换过程) 版本号的秘密
0.61.2WebAssembly 提案支持
提案 状态 说明 Multi-value 必需 多返回值 Non-trapping float-to-int 必需 安全的浮点转整数 Bulk memory operations 可选 批量内存操作 Exception handling 可选 异常处理 Tail calls 可选(默认关闭) 尾调用优化 项目状态与资源
当前状态
官方资源
学习资源
为什么叫 Porffor?
总结
"Purple is pretty cool. And it apparently represents 'ambition', which is one word to describe this project." — Oliver Medhurst