Skip to content

Commit e8d2e37

Browse files
Inject stdout/stderr as character device by consolePrinter
Since the upstream `@bjorn3/browser_wasi_shim` intentionally offloads stdout/stderr handling to application, we need to do it in our side.
1 parent 253df40 commit e8d2e37

File tree

3 files changed

+64
-70
lines changed

3 files changed

+64
-70
lines changed

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

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Fd, File, OpenFile, WASI } from "@bjorn3/browser_wasi_shim";
1+
import { Fd, WASI } from "@bjorn3/browser_wasi_shim";
22
import { RubyVM } from "./index.js";
33
import { consolePrinter } from "./console.js";
44

@@ -18,31 +18,7 @@ export const DefaultRubyVM = async (
1818
([k, v]) => `${k}=${v}`,
1919
);
2020

21-
// WORKAROUND: `@bjorn3/browser_wasi_shim` does not set proper rights
22-
// and filetypes for stdout and stderr, but wasi-libc's fcntl(2) respects
23-
// rights and CRuby checks it. So we need to override `fd_fdstat_get` here.
24-
const FILETYPE_CHARACTER_DEVICE = 2;
25-
class Stdout extends OpenFile {
26-
fd_filestat_get() {
27-
const { ret, filestat } = super.fd_filestat_get();
28-
filestat.filetype = FILETYPE_CHARACTER_DEVICE;
29-
return { ret, filestat };
30-
}
31-
32-
fd_fdstat_get() {
33-
const { ret, fdstat } = super.fd_fdstat_get();
34-
const RIGHTS_FD_WRITE = BigInt(1);
35-
fdstat.fs_filetype = FILETYPE_CHARACTER_DEVICE;
36-
fdstat.fs_rights_base = RIGHTS_FD_WRITE;
37-
return { ret, fdstat };
38-
}
39-
}
40-
41-
const fds: Fd[] = [
42-
new OpenFile(new File([])), // stdin
43-
new Stdout(new File([])), // stdout
44-
new Stdout(new File([])), // stderr
45-
];
21+
const fds: Fd[] = [];
4622
const wasi = new WASI(args, env, fds, { debug: false });
4723
const vm = new RubyVM();
4824

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

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,35 +37,39 @@ export function consolePrinter(
3737
},
3838
) {
3939
let memory: WebAssembly.Memory | undefined = undefined;
40-
let view: DataView | undefined = undefined;
40+
let _view: DataView | undefined = undefined;
41+
42+
function getMemoryView(): DataView {
43+
if (typeof memory === "undefined") {
44+
throw new Error("Memory is not set");
45+
}
46+
if (_view === undefined || _view.buffer.byteLength === 0) {
47+
_view = new DataView(memory.buffer);
48+
}
49+
return _view;
50+
}
4151

4252
const decoder = new TextDecoder();
4353

4454
return {
4555
addToImports(imports: WebAssembly.Imports): void {
46-
const original = imports.wasi_snapshot_preview1.fd_write as (
47-
fd: number,
48-
iovs: number,
49-
iovsLen: number,
50-
nwritten: number,
51-
) => number;
52-
imports.wasi_snapshot_preview1.fd_write = (
53-
fd: number,
54-
iovs: number,
55-
iovsLen: number,
56-
nwritten: number,
57-
): number => {
56+
const wasiImport = imports.wasi_snapshot_preview1 as {
57+
fd_write: (
58+
fd: number,
59+
iovs: number,
60+
iovsLen: number,
61+
nwritten: number,
62+
) => number;
63+
fd_filestat_get: (fd: number, filestat: number) => number;
64+
fd_fdstat_get: (fd: number, fdstat: number) => number;
65+
};
66+
const original_fd_write = wasiImport.fd_write;
67+
wasiImport.fd_write = (fd, iovs, iovsLen, nwritten): number => {
5868
if (fd !== 1 && fd !== 2) {
59-
return original(fd, iovs, iovsLen, nwritten);
60-
}
61-
62-
if (typeof memory === "undefined" || typeof view === "undefined") {
63-
throw new Error("Memory is not set");
64-
}
65-
if (view.buffer.byteLength === 0) {
66-
view = new DataView(memory.buffer);
69+
return original_fd_write(fd, iovs, iovsLen, nwritten);
6770
}
6871

72+
const view = getMemoryView();
6973
const buffers = Array.from({ length: iovsLen }, (_, i) => {
7074
const ptr = iovs + i * 8;
7175
const buf = view.getUint32(ptr, true);
@@ -86,10 +90,44 @@ export function consolePrinter(
8690

8791
return 0;
8892
};
93+
94+
const original_fd_filestat_get = wasiImport.fd_filestat_get;
95+
wasiImport.fd_filestat_get = (fd, filestat): number => {
96+
if (fd !== 1 && fd !== 2) {
97+
return original_fd_filestat_get(fd, filestat);
98+
}
99+
100+
const view = getMemoryView();
101+
const result = original_fd_filestat_get(fd, filestat);
102+
if (result !== 0) {
103+
return result;
104+
}
105+
106+
const filetypePtr = filestat + 0;
107+
view.setUint8(filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
108+
109+
return 0;
110+
};
111+
112+
const original_fd_fdstat_get = wasiImport.fd_fdstat_get;
113+
wasiImport.fd_fdstat_get = (fd, fdstat): number => {
114+
if (fd !== 1 && fd !== 2) {
115+
return original_fd_fdstat_get(fd, fdstat);
116+
}
117+
118+
const view = getMemoryView();
119+
120+
const fs_filetypePtr = fdstat + 0;
121+
view.setUint8(fs_filetypePtr, 2); // FILETYPE_CHARACTER_DEVICE
122+
123+
const fs_rights_basePtr = fdstat + 8;
124+
view.setBigUint64(fs_rights_basePtr, BigInt(1)); // RIGHTS_FD_WRITE
125+
126+
return 0;
127+
};
89128
},
90129
setMemory(m: WebAssembly.Memory) {
91130
memory = m;
92-
view = new DataView(m.buffer);
93131
},
94132
};
95133
}

packages/npm-packages/ruby-wasm-wasi/tools/run-test-unit.mjs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -109,31 +109,11 @@ const instantiateWasmerWasi = async (rootTestFile) => {
109109
"RUBY_FIBER_MACHINE_STACK_SIZE": String(1024 * 1024 * 20),
110110
}).map(([key, value]) => `${key}=${value}`);
111111
112-
// WORKAROUND: `@bjorn3/browser_wasi_shim` does not set proper rights
113-
// for stdout and stderr, but wasi-libc's fcntl(2) respects rights and
114-
// CRuby checks it. So we need to override `fd_fdstat_get` here.
115-
const FILETYPE_CHARACTER_DEVICE = 2;
116-
class Stdout extends browserWasi.OpenFile {
117-
fd_filestat_get() {
118-
const { ret, filestat } = super.fd_filestat_get();
119-
filestat.filetype = FILETYPE_CHARACTER_DEVICE;
120-
return { ret, filestat };
121-
}
122-
123-
fd_fdstat_get() {
124-
const { ret, fdstat } = super.fd_fdstat_get();
125-
const RIGHTS_FD_WRITE = BigInt(1);
126-
const RIGHTS_FD_TELL = BigInt(1) << BigInt(5);
127-
fdstat.fs_filetype = FILETYPE_CHARACTER_DEVICE;
128-
fdstat.fs_rights_base = RIGHTS_FD_WRITE;
129-
return { ret, fdstat };
130-
}
131-
}
132112
133113
const fds = [
134114
new browserWasi.OpenFile(new browserWasi.File([])),
135-
new Stdout(new browserWasi.File([])),
136-
new Stdout(new browserWasi.File([])),
115+
new browserWasi.OpenFile(new browserWasi.File([])),
116+
new browserWasi.OpenFile(new browserWasi.File([])),
137117
new browserWasi.PreopenDirectory("/__root__", __root__.contents)
138118
]
139119

0 commit comments

Comments
 (0)