Skip to content

Commit fdbd5ed

Browse files
Add RubyVM.instantiateComponent and RubyVM.instantiateModule
To provide higher-level instantiation API for Ruby VM, this commit introduces `RubyVM.instantiateComponent` and `RubyVM.instantiateModule` methods.
1 parent cdd6c03 commit fdbd5ed

File tree

6 files changed

+396
-166
lines changed

6 files changed

+396
-166
lines changed

bin/repl.mjs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import * as preview2Shim from "@bytecodealliance/preview2-shim"
2+
import * as nodeWasi from "wasi";
3+
import fs from "fs/promises";
4+
import path from "path";
5+
import { RubyVM } from "@ruby/wasm-wasi";
6+
import * as readline from 'node:readline/promises';
7+
import { stdin as input, stdout as output } from 'node:process';
8+
import { parseArgs } from "node:util"
9+
10+
async function instantiateComponent(pkgPath) {
11+
const componentJsPath = path.resolve(pkgPath, "dist/component/ruby.component.js");
12+
const { instantiate } = await import(componentJsPath);
13+
const getCoreModule = async (relativePath) => {
14+
const coreModulePath = path.resolve(pkgPath, "dist/component", relativePath);
15+
const buffer = await fs.readFile(coreModulePath);
16+
return WebAssembly.compile(buffer);
17+
}
18+
const { cli, filesystem } = preview2Shim;
19+
cli._setArgs(["ruby.wasm"].concat(process.argv.slice(2)));
20+
cli._setCwd("/")
21+
filesystem._setPreopens({})
22+
23+
const { vm } = await RubyVM.instantiateComponent({
24+
instantiate, getCoreModule, wasip2: preview2Shim,
25+
});
26+
return vm;
27+
}
28+
29+
async function instantiateModule(pkgPath) {
30+
const binaryPath = path.resolve(pkgPath, "dist/ruby.debug+stdlib.wasm");
31+
const binary = await fs.readFile(binaryPath);
32+
const rubyModule = await WebAssembly.compile(binary);
33+
const wasi = new nodeWasi.WASI({
34+
stdio: "inherit",
35+
args: ["ruby.wasm"].concat(process.argv.slice(2)),
36+
env: process.env,
37+
version: "preview1",
38+
});
39+
40+
const { vm } = await RubyVM.instantiateModule({
41+
module: rubyModule, wasip1: wasi
42+
})
43+
return vm;
44+
};
45+
46+
function parseOptions(args) {
47+
/** @type {import("util").ParseArgsConfig["options"]} */
48+
const options = {
49+
pkg: {
50+
type: "string",
51+
short: "p",
52+
default: (() => {
53+
const dirname = path.dirname(new URL(import.meta.url).pathname);
54+
return path.resolve(dirname, "../packages/npm-packages/ruby-head-wasm-wasi");
55+
})()
56+
},
57+
type: {
58+
type: "string",
59+
short: "t",
60+
default: "component",
61+
},
62+
help: {
63+
type: "boolean",
64+
short: "h",
65+
},
66+
}
67+
const { values } = parseArgs({ args, options });
68+
return values;
69+
}
70+
71+
function printUsage() {
72+
console.log("Usage: repl.mjs [--pkg <npm-package-path>] [--type <component|module>]");
73+
}
74+
75+
async function main() {
76+
const args = parseOptions(process.argv.slice(2));
77+
if (args["help"]) {
78+
printUsage();
79+
return;
80+
}
81+
const pkgPath = args["pkg"];
82+
const vm = await (async () => {
83+
switch (args["type"]) {
84+
case "component": {
85+
console.log(`Loading component from ${pkgPath}`);
86+
return await instantiateComponent(pkgPath);
87+
}
88+
case "module": {
89+
console.log(`Loading core module from ${pkgPath}`);
90+
return await instantiateModule(pkgPath);
91+
}
92+
default:
93+
throw new Error(`Unknown type: ${args["type"]}`);
94+
}
95+
})();
96+
const rl = readline.createInterface({ input, output });
97+
98+
vm.eval(`puts RUBY_DESCRIPTION`);
99+
100+
const printer = vm.eval(`
101+
class ReplPrinter
102+
def puts(*args)
103+
args.each do |arg|
104+
Kernel.puts(arg)
105+
end
106+
end
107+
end
108+
ReplPrinter.new
109+
`);
110+
while (true) {
111+
const line = await rl.question(`>> `);
112+
try {
113+
const result = vm.eval(line);
114+
printer.call("puts", result);
115+
} catch (e) {
116+
console.error(e);
117+
}
118+
}
119+
}
120+
121+
main().catch(console.error);

packages/npm-packages/ruby-wasm-wasi/src/browser.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,16 @@ export const DefaultRubyVM = async (
2525
new PreopenDirectory("/", new Map()),
2626
];
2727
const wasi = new WASI(args, env, fds, { debug: false });
28-
const vm = new RubyVM();
29-
30-
const imports = {
31-
wasi_snapshot_preview1: wasi.wasiImport,
32-
};
33-
vm.addToImports(imports);
3428
const printer = options.consolePrint ?? true ? consolePrinter() : undefined;
35-
printer?.addToImports(imports);
36-
37-
const instance = await WebAssembly.instantiate(rubyModule, imports);
38-
await vm.setInstance(instance);
39-
40-
printer?.setMemory(instance.exports.memory as WebAssembly.Memory);
41-
42-
wasi.initialize(instance as any);
43-
vm.initialize();
29+
const { vm, instance } = await RubyVM.instantiateModule({
30+
module: rubyModule, wasip1: wasi,
31+
addToImports: (imports) => {
32+
printer?.addToImports(imports);
33+
},
34+
setMemory: (memory) => {
35+
printer?.setMemory(memory);
36+
}
37+
});
4438

4539
return {
4640
vm,

packages/npm-packages/ruby-wasm-wasi/src/node.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,7 @@ export const DefaultRubyVM = async (
66
options: { env?: Record<string, string> | undefined } = {},
77
) => {
88
const wasi = new WASI({ env: options.env, version: "preview1", returnOnExit: true });
9-
const vm = new RubyVM();
10-
const imports = {
11-
wasi_snapshot_preview1: wasi.wasiImport,
12-
};
13-
14-
vm.addToImports(imports);
15-
16-
const instance = await WebAssembly.instantiate(rubyModule, imports);
17-
18-
await vm.setInstance(instance);
19-
20-
wasi.initialize(instance);
21-
vm.initialize();
9+
const { vm, instance } = await RubyVM.instantiateModule({ module: rubyModule, wasip1: wasi });
2210

2311
return {
2412
vm,

0 commit comments

Comments
 (0)