网易首页 > 网易号 > 正文 申请入驻

MoonBit和Rust组合,解决美国用户WASM真实场景的实际痛点

0
分享至

前言

现在 MoonBit 因其众多的优势正在被全球的用户使用,不久前 MoonBit 收到美国客户的一封来信:

我们希望生成一些小适配器 WASM 组件作为我们工具的一部分。由于 WASM 是一种较低级的程序语言,直接生成 WASM 太痛苦了,并且生成大量 Rust 代码是有问题的,因为我们的 JS 用户在安装 Rust 工具链上有困难。由于 MoonBit 可以编译成 WASM,将这些适配器作为 MoonBit 代码生成,并从我们的 CLI 工具中通过 WASM 版的 MoonBit 编译器进行编译将是一个理想的解决方案。

在收到来信后,MoonBit 给美国客户提供了 WASM 版编译器的文件,以及一套使用 Rusty V8 实现用于运行WASM 版编译器的系统 API,帮助他们解决这个困扰良久的问题。

为什么选择使用 MoonBit?

生成 WASM 的可选项虽然还是有一些,如 GO、C#、Rust、C++ 和 C 等编程语言,但是 MoonBit 在这些中具有显著优势,生成 WASM 代码的质量更高,而且 Moo nBit 可以生成比 Rust 更紧凑的 WASM 文件,编译速度也更快 。

美国客户希望动态使用 MoonBit 编译器生成 WASM,但又不想每个平台都做一份,MoonBit 编译器本身可以编译成 wasm,配合 rusty_v8 便可跨平台运行,以下是详细的方案。

解决方案之使用 rusty_v8 运行

moonbit-compiler仓库里的wasm/js文件均使用 wasm_of_ocaml-6.0.1 构建, 最新版 wasm_of_ocaml 的API列表已经有所区别。以下统一将 wasm_of_ocaml 缩写为wasmoo。

虽然 wasmoo 官方没有提供 node以外的运行方式,但是只要实现了wasmoo的wasm加载脚本import object列表中的相应函数,使用其他wasm运行时也可以运行wasm版的MoonBit编译器。

rusty_v8 是一个为 Rust 语言提供的高质量绑定库,用于直接调用 Google 的 V8 JavaScript 引擎功能。

MoonBit 官方运行wasm的工具 moonrun 基于 rusty_v8 编写,故此处以 rusty_v8 为例演示如何实现wasmoo所需的外部API。

System API 概览

wasmoo所需的外部API一部分是js语言自带的,另一些则需要js运行时提供。要使用 rusty_v8 运行wasmoo编译出的wasm文件, 首先要做的事情是把API列表中需要js运行时实现的函数全部用rust实现并接入v8。

阅读wasmoo随wasm文件一同输出的js脚本可得以下API列表:

getenv : JSString -> JSString system : JSString -> Number log : JSString -> undefined is_file : JSString -> Number(1 | 0) is_directory : JSString -> Number(1 | 0) file_exists : JSString -> Number(1 | 0) // actually mains path_exists chmod : JSString, u32 as PermissionMode -> undefined truncate: JSString, u64 as Length -> undefined open : JSString as Path, i32 as Flags, Numberas PermissionMode -> i32 as FileDescriptor close : i32 as FileDescriptor -> undefined access : JSString as Path, i32 as Mode -> undefined write: i32 as FileDescriptor, UInt8Array as Buffer, i32 as Offset, i32 as Length, nullas Position -> Number read: i32 as FileDescriptor, UInt8Array as Buffer, i32 as Offset, i32 as Length, nullas Position -> Number fsync: i32 as FileDescriptor -> undefined file_size: i32 as FileDescriptor -> BigInt utimes: JSString as Path, F64 as AccessTime, F64 as ModifyTime -> undefined exit: i32 -> undefined isatty: i32 as FileDescriptor -> Number(1 | 0) getcwd: () -> JSString chdir: JSString -> undefined mkdir: JSString as Path, i32 as Mode -> undefined rmdir: JSString as Path -> undefined unlink: JSString as Path -> undefined readdir: JSString as Path -> Array
             
 stat : JSString as Path -> Object lstat : JSString as Path -> Object fstat: i32 as FileDescriptor -> Object fchmod: i32 as FileDescriptor, i32 as Mode -> undefined ftruncate: i32 as FileDescriptor, u64 as Length -> undefined rename: JSString as OldPath, JSString as NewPath -> undefined decode_utf8: Uint8Array -> JSString encode_into: JSString, Uint8Array -> Object { read, written }
      

以上是这些 API 的「 伪类型」标注 ,但相信对熟悉 JavaScript 与 Rust 的开发者来说不难看懂。实现这些API时比较棘手的工作包括:

  • 正确处理 wasmoo 常量到系统 API 选项的翻译

  • 在v8和Rust之间高效交换数据(多见于处理UInt8Array时)

如何实现 API 原文链接:

https://www.moonbitlang.com/blog/moonbit-wasm-compiler

将 API 导入 Js 环境

这一步骤通过init_wasmoo函数完成

pub fn init_wasmoo<'s>(     obj: v8::Local<'s, v8::Object>,     scope: &mut v8::HandleScope<'s>, ) -> v8::Local<'s, v8::Object> {     let getenv = v8::FunctionTemplate::new(scope, getenv);     let getenv = getenv.get_function(scope).unwrap();     let ident = v8::String::new(scope, "getenv").unwrap();     obj.set(scope, ident.into(), getenv.into());     let system = v8::FunctionTemplate::new(scope, system);     let system = system.get_function(scope).unwrap();     let ident = v8::String::new(scope, "system").unwrap();     obj.set(scope, ident.into(), system.into());     ...     obj }

完整实现代码请见:

https://github.com/moonbitlang/moonc_wasm/blob/main/src/wasmoo_extern.rs

启动 v8 并加载 wasm 文件

在实现了wasmoo所需的外部API后,下一步是以合适的方式启动v8并通过js脚本实例化wasm。这一步通过函数run_wasmoo实现。

fn run_wasmoo(module_name: &str, argv: Vec ) -> anyhow::Result<()> {     ... }

不过在此之前,还有两件事要做:加载 wasm 文件和初始化 v8.

加载 wasm 文件通过函数 load_wasm_file 实现,为了避免重复 IO(也为了方便打包),在函数 load_wasm_file 中使用 Rust 宏 include_bytes! 将 wasm 文件直接作为 bytes 嵌入源码。该函数在 init_wasmoo 中被添加到js环境。

fn load_wasm_file(     scope: &mut v8::HandleScope,     _args: v8::FunctionCallbackArguments,     mut ret: v8::ReturnValue, ) {     let contents = Vec::from(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/moonc/moonc.wasm")));     let len = contents.len();     let array_buffer = v8::ArrayBuffer::with_backing_store(         scope,         &v8::ArrayBuffer::new_backing_store_from_bytes(contents).make_shared(),     );     let uint8_array = v8::Uint8Array::new(scope, array_buffer, 0, len).unwrap();     ret.set(uint8_array.into()); }

初始化v8时,需要手动通过 flag 将栈空间开大一些。

fn initialize_v8() -> anyhow::Result<()> {     v8::V8::set_flags_from_string(&format!("--stack-size={}", 102400));     v8::V8::set_flags_from_string("--experimental-wasm-exnref");     v8::V8::set_flags_from_string("--experimental-wasm-imported-strings");     let platform = v8::new_default_platform(0, false).make_shared();     v8::V8::initialize_platform(platform);     v8::V8::initialize();     Ok(()) }

run_wasmoo里需要初始化文件描述符相关 API 需要的映射表

let context = v8::Context::new(scope, Default::default());     // setup file descriptor table     context.set_slot(wasmoo_extern::FdTable::new());     let scope = &mut v8::ContextScope::new(scope, context);

最后, run_wasmoo 还需要正确地处理传给 wasm 文件的命令行参数数组, 在此处所建立的 v8 环境中,命令行参数数组对应一个全局变量 process_argv 。

实际上wasmoo传递命令行参数数组的方式就是将它放在wasm的import list里。这一点在稍后解析wasm的引导脚本时即可看出。

// setup argvlet process_argv = v8::Array::new(scope, argv.len() as i32);     for (i, s) in argv.iter().enumerate() {         let s = v8::String::new(scope, s).unwrap();         process_argv.set_index(scope, i as u32, s.into());     }     let ident = v8::String::new(scope, "process_argv").unwrap();     global_proxy.set(scope, ident.into(), process_argv.into());

可以看出, run_wasmoo 的任务只是将需要运行时传递的参数塞到 js 脚本和 v8 环境中。加载 wasm 并最终执行任务的细节在脚本 js_glue_for_wasmoo.js 中:

https://github.com/moonbitlang/moonc_wasm/blob/main/src/moonc/js_glue_for_wasmoo.js

该脚本有一个非常复杂的导入列表

const bindings = {     ......     getcwd,     chdir,     mkdir,     rmdir,     unlink,     argv: () => process_argv,     readdir: (p) => read_dir(p),     stat: (p, l) => alloc_stat(stat(p), l),     lstat: (p, l) => alloc_stat(lstat(p), l),     fstat: (fd, l) => alloc_stat(fstat(fd), l),     ...... } const importObject = {     Math: math,     bindings,     js,     "wasm:js-string": string_ops,     "wasm:text-decoder": string_ops,     "wasm:text-encoder": string_ops,     env: {}, }; importObject.OCaml = {};

这个导入列表的复杂之处在于它里面的许多函数递归调用了从 wasm 中导出的函数(除此之外, importObject.OCaml 也需要用导出列表重新赋值。)

最终启动 wasm 需要将导出列表中的函数/对象引入 js 环境,然后调用从 wasm 中导出的 _initialize 函数。

let bytes = load_wasm_file(); let wasm_module = new WebAssembly.Module(bytes, options); let instance = new WebAssembly.Instance(wasm_module, importObject); Object.assign(     importObject.OCaml,     instance.exports,   ); var {     caml_callback,     caml_alloc_times,     caml_alloc_tm,     caml_alloc_stat,     caml_start_fiber,     caml_handle_uncaught_exception,     caml_buffer,     caml_extract_bytes,     string_get,     string_set,     _initialize,   } = instance.exports;   start_fiber = caml_start_fiber var buffer = caml_buffer?.buffer; var out_buffer = buffer && newUint8Array(buffer, 0, buffer.length); await _initialize();

完整代码请见:

https://github.com/moonbitlang/moonc_wasm

总结

文章探讨了编程语言工具链在多平台分发时面临的挑战,包括 CPU 架构差异、API 兼容性问题等,并指出容器化方案体积庞大、Nix/Guix 学习成本高的局限性。MoonBit 创新性地通过 wasm_of_ocaml 将 OCaml 编写的编译器工具链(moonc/moonfmt等)编译为 WASM,实现了高性能、跨平台和嵌入式支持。

核心方案‌ Rusty_v8 扩展‌:针对非 Node 环境,通过实现 WASM 所需的系统 API (如文件操作is_file、chmod等),利用 Rusty_v8 引擎自定义运行时,使工具链可嵌入其他系统。

技术亮点‌:WASM版本保持编译性能,同时支持灵活调用;通过抽象系统 API 层,解耦运行时依赖,增强可移植性.此方案为工具链分发提供了轻量化、低侵入性的新思路,尤其适合需要跨平台或嵌入第三方工具的场景。

尽管没有尝试过,但理论上来说只要提供一套行为上兼容度足够的API,wasm版 M oonBit 工具链也可以在其他环境里运行(例如使用虚拟文件系统的浏览器环境)。期待看到有人想出更有创意的使用方式!

原标题: Running WebAssembly-based MoonBit compiler

MoonBit 介绍:

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
刘虎再次“因言获罪”,预测最后结果还是不起诉!

刘虎再次“因言获罪”,预测最后结果还是不起诉!

法治应生
2026-02-03 01:25:03
白酒会被90后00后终结吗?这是我见过最简明易懂的答案!

白酒会被90后00后终结吗?这是我见过最简明易懂的答案!

夜深爱杂谈
2026-01-28 18:25:29
被双开的胡继勇:在四川工作十四年,当年的上级曾任中石油副总裁

被双开的胡继勇:在四川工作十四年,当年的上级曾任中石油副总裁

叹为观止易
2026-01-21 10:21:06
现货黄金突破4700美元/盎司

现货黄金突破4700美元/盎司

证券时报
2026-02-03 07:24:18
歌手于文文演唱会突然晕倒,被救护车紧急送医!吴克群救场:“她少唱的,我来帮她唱”

歌手于文文演唱会突然晕倒,被救护车紧急送医!吴克群救场:“她少唱的,我来帮她唱”

黄河新闻网吕梁频道
2026-02-02 09:12:32
郑爽张恒两家再起争执!34岁郑爽满头白发显沧桑,孩子哭闹场面扎心

郑爽张恒两家再起争执!34岁郑爽满头白发显沧桑,孩子哭闹场面扎心

八星人
2026-02-01 21:02:27
金价大幅回落 刚买的金饰能退吗?法律人士解读

金价大幅回落 刚买的金饰能退吗?法律人士解读

极目新闻
2026-02-03 07:58:07
中南行政委员会主席为林彪,副主席都有哪些人?其中两人后来官至正国级

中南行政委员会主席为林彪,副主席都有哪些人?其中两人后来官至正国级

寄史言志
2026-02-02 23:57:09
她才是下棋人!谢杏芳9年前设局,如今林丹在家沦为“编外人员”

她才是下棋人!谢杏芳9年前设局,如今林丹在家沦为“编外人员”

地球记
2026-01-08 22:42:11
震惊:李昊留洋梦碎居然被徐彬耽误了,青岛西海岸有苦说不出

震惊:李昊留洋梦碎居然被徐彬耽误了,青岛西海岸有苦说不出

姜大叔侃球
2026-02-03 11:54:21
理性!不要梭哈!

理性!不要梭哈!

一莎观察
2026-02-01 13:37:59
福建新“特大城市”曝光,厦门让位,南平成最大黑马!

福建新“特大城市”曝光,厦门让位,南平成最大黑马!

爱下厨的阿酾
2026-02-02 09:12:40
官方:本泽马将在利雅得新月身穿90号球衣

官方:本泽马将在利雅得新月身穿90号球衣

懂球帝
2026-02-03 07:35:06
1949年,毛主席给香港定下3条规矩!至今无人敢逾越

1949年,毛主席给香港定下3条规矩!至今无人敢逾越

鹤羽说个事
2026-01-29 16:32:40
4中4砍8+3!火箭超六出炉?完胜奥科吉+电风扇,难怪斯通不交易他

4中4砍8+3!火箭超六出炉?完胜奥科吉+电风扇,难怪斯通不交易他

熊哥爱篮球
2026-02-03 12:28:07
太顶了!明明什么都没露,却性感得要命!

太顶了!明明什么都没露,却性感得要命!

贵圈真乱
2025-12-20 12:02:06
画质还不如4K!8K电视集体团灭:全球仅剩一颗独苗

画质还不如4K!8K电视集体团灭:全球仅剩一颗独苗

快科技
2026-02-02 18:11:11
高铁站分别后儿子在母亲后面狂追,妈妈感动不已以为儿子舍不得自己,儿子回复:妈妈送错站口了

高铁站分别后儿子在母亲后面狂追,妈妈感动不已以为儿子舍不得自己,儿子回复:妈妈送错站口了

黄河新闻网吕梁频道
2026-02-02 11:55:53
火箭118-114险胜步行者 球员评价:2人满分,5人及格,3人低迷

火箭118-114险胜步行者 球员评价:2人满分,5人及格,3人低迷

篮球资讯达人
2026-02-03 10:49:48
具俊晔没想到,大S“隆重的”揭幕仪式,揭开了S家和他的真面目

具俊晔没想到,大S“隆重的”揭幕仪式,揭开了S家和他的真面目

林轻吟
2026-02-03 11:32:20
2026-02-03 13:11:00
开源中国 incentive-icons
开源中国
每天为开发者推送最新技术资讯
7578文章数 34499关注度
往期回顾 全部

科技要闻

1.25万亿美元!xAI员工赢麻了

头条要闻

男子投200万做租赁业务起步即爆单 每周收租金超百万

头条要闻

男子投200万做租赁业务起步即爆单 每周收租金超百万

体育要闻

“也许我的一小步,会成为中国足球的一大步”

娱乐要闻

小S致词:感谢具俊晔陪伴大S的最后3年

财经要闻

精神病医院骗保内幕调查:住院相当于坐牢

汽车要闻

问界M6官图首发 以年轻化设计叩击25-30万级市场

态度原创

亲子
家居
教育
游戏
本地

亲子要闻

高敏感儿童是有病吗? 用这样的运动处方:家长从此告别内耗与焦虑

家居要闻

极简木艺术 典雅自在

教育要闻

英语四六级听力总是跟不上,脑子一片空白,有什么方法能快速提高

T2财报即将公布!《GTA6》会再次宣布跳票吗?

本地新闻

云游中国|拨开云雾,巫山每帧都是航拍大片

无障碍浏览 进入关怀版