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

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-01 11:46:04
2比2!鲁能甩掉“恐韩症”,21岁天才进球,成球队亚冠最年轻球员

2比2!鲁能甩掉“恐韩症”,21岁天才进球,成球队亚冠最年轻球员

罗掌柜体育
2026-02-01 14:25:10
广东一初中生背影火了,网友怒赞!

广东一初中生背影火了,网友怒赞!

深圳晚报
2026-01-31 23:00:48
为什么世人都怀疑王莽是“穿越者”?看看他妻子穿的啥就知道了

为什么世人都怀疑王莽是“穿越者”?看看他妻子穿的啥就知道了

丞丞故事汇
2026-01-31 11:11:05
13岁女生确诊胃癌晚期,医生:她吸了13年爸爸的二手烟,肚子里布满了大大小小的肿瘤,没有任何治愈机会

13岁女生确诊胃癌晚期,医生:她吸了13年爸爸的二手烟,肚子里布满了大大小小的肿瘤,没有任何治愈机会

观威海
2026-01-31 10:06:12
惊!2026年立春不一般!2月4日这4类人必须躲春,做错白忙一场

惊!2026年立春不一般!2月4日这4类人必须躲春,做错白忙一场

老特有话说
2026-01-30 22:51:04
美伊正在谈,但川普这次是要能源和矿产,明面说的是以色列要的

美伊正在谈,但川普这次是要能源和矿产,明面说的是以色列要的

邵旭峰域
2026-02-01 13:24:13
“跳楼去世”真相大白仅1个月,朱之文近况曝光,蒋大为没说错

“跳楼去世”真相大白仅1个月,朱之文近况曝光,蒋大为没说错

可乐谈情感
2026-01-31 15:47:56
砸11亿!青岛山姆店终于要开了,山东人等太久!

砸11亿!青岛山姆店终于要开了,山东人等太久!

GA环球建筑
2026-02-01 18:15:27
一次,易中天问董宇辉:“你知道霍去病为什么死的那么早吗?

一次,易中天问董宇辉:“你知道霍去病为什么死的那么早吗?

忠于法纪
2026-01-29 09:18:52
女子称退150多元大衣时不慎寄走奔驰钥匙,“配一把6000元”!网店工作人员:若看到会给她

女子称退150多元大衣时不慎寄走奔驰钥匙,“配一把6000元”!网店工作人员:若看到会给她

极目新闻
2026-02-01 10:11:45
李亚鹏找到医院新址,直播眼里泛泪,房东心态崩了,只求过个好年

李亚鹏找到医院新址,直播眼里泛泪,房东心态崩了,只求过个好年

子芫伴你成长
2026-01-25 08:10:03
抢在美军开战前,俄交出武器,中方也先发制人,局面开始一边倒

抢在美军开战前,俄交出武器,中方也先发制人,局面开始一边倒

沧海旅行家
2026-01-31 16:30:03
“流氓有文化更可怕”,退休老干部频繁联系女幼师,聊天记录曝光

“流氓有文化更可怕”,退休老干部频繁联系女幼师,聊天记录曝光

妍妍教育日记
2026-01-27 19:58:28
说句扎心的大实话,咱们在南海的“牌”,一开始烂到家了。

说句扎心的大实话,咱们在南海的“牌”,一开始烂到家了。

南权先生
2026-01-27 15:44:44
具俊晔登上韩国电视台,对着大S墓碑跪地磕头,女主持几度泪奔!

具俊晔登上韩国电视台,对着大S墓碑跪地磕头,女主持几度泪奔!

古希腊掌管月桂的神
2026-01-31 10:17:21
上海交大:每次起床后大量喝水的人,用不了多久,身体或有7变化

上海交大:每次起床后大量喝水的人,用不了多久,身体或有7变化

读懂世界历史
2025-11-23 11:18:04
这个中国人的惊天叛逃,99.99%概率是死,但他却成了那0.01%

这个中国人的惊天叛逃,99.99%概率是死,但他却成了那0.01%

深度报
2026-01-31 16:59:47
Here we go!罗马诺:利物浦今夏总价7000万欧签雅凯达成协议

Here we go!罗马诺:利物浦今夏总价7000万欧签雅凯达成协议

懂球帝
2026-02-02 06:39:11
1岁幼童喂兔子被咬断手指,家长为找断指将兔子开膛破肚……医生提醒→

1岁幼童喂兔子被咬断手指,家长为找断指将兔子开膛破肚……医生提醒→

纵相新闻
2026-02-01 15:09:04
2026-02-02 11:44:49
开源中国 incentive-icons
开源中国
每天为开发者推送最新技术资讯
7577文章数 34499关注度
往期回顾 全部

科技要闻

元宝发10亿红包,阿里千问:我跟30亿

头条要闻

30岁男子如厕时猝死 妻子回忆事发前5天丈夫疼痛细节

头条要闻

30岁男子如厕时猝死 妻子回忆事发前5天丈夫疼痛细节

体育要闻

澳网男单决赛,属于阿尔卡拉斯的加冕仪式

娱乐要闻

周杰伦带王俊凯陈奕迅聚餐 畅聊音乐

财经要闻

国六货车被迫"换头" 每次收费超200元

汽车要闻

岚图汽车1月交付10515辆 同比增长31%

态度原创

旅游
房产
艺术
本地
公开课

旅游要闻

全国首个场景会展业态试验场“谷谷岛PaddyLand”亮相长沙

房产要闻

凤栖海棠,世界藏品丨绿城·凤鸣观棠品牌发布盛典首映

艺术要闻

马斯克花5万买的折叠屋,是预制住宅的未来吗?

本地新闻

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

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版