@@ -37,3 +37,185 @@ EM_JS(int, __syscall_umask_js, (int mask), {
37
37
int __syscall_umask (int mask ) {
38
38
return __syscall_umask_js (mask );
39
39
}
40
+
41
+ #include <wasi/api.h>
42
+ #include <errno.h>
43
+ #undef errno
44
+
45
+ // Variant of EM_JS that does C preprocessor substitution on the body
46
+ #define EM_JS_MACROS (ret , func_name , args , body ...) \
47
+ EM_JS(ret, func_name, args, body)
48
+
49
+ EM_JS_MACROS (void , _emscripten_promising_main_js , (void ), {
50
+ // Define FS.createAsyncInputDevice(), This is quite similar to
51
+ // FS.createDevice() defined here:
52
+ // https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libfs.js?plain=1#L1642
53
+ // but instead of returning one byte at a time, the input() function should
54
+ // return a Uint8Array. This makes the handler code simpler, the
55
+ // `createAsyncInputDevice` simpler, and everything faster.
56
+ FS .createAsyncInputDevice = function (parent , name , input ) {
57
+ parent = typeof parent == 'string' ? parent : FS .getPath (parent );
58
+ var path = PATH .join2 (parent , name );
59
+ var mode = FS_getMode (true, false);
60
+ FS .createDevice .major || = 64 ;
61
+ var dev = FS .makedev (FS .createDevice .major ++ , 0 );
62
+ async function getDataBuf () {
63
+ var buf ;
64
+ try {
65
+ buf = await input ();
66
+ } catch (e ) {
67
+ throw new FS .ErrnoError (EIO );
68
+ }
69
+ if (!buf ?.byteLength ) {
70
+ throw new FS .ErrnoError (EAGAIN );
71
+ }
72
+ ops ._dataBuf = buf ;
73
+ }
74
+
75
+ var ops = {
76
+ _dataBuf : new Uint8Array (0 ),
77
+ open (stream ) {
78
+ stream .seekable = false;
79
+ },
80
+ async readAsync (stream , buffer , offset , length , pos /* ignored */ ) {
81
+ buffer = buffer .subarray (offset , offset + length );
82
+ if (!ops ._dataBuf .byteLength ) {
83
+ await getDataBuf ();
84
+ }
85
+ var toRead = Math .min (ops ._dataBuf .byteLength , buffer .byteLength );
86
+ buffer .subarray (0 , toRead ).set (ops ._dataBuf );
87
+ buffer = buffer .subarray (toRead );
88
+ ops ._dataBuf = ops ._dataBuf .subarray (toRead );
89
+ if (toRead ) {
90
+ stream .node .atime = Date .now ();
91
+ }
92
+ return toRead ;
93
+ },
94
+ };
95
+ FS .registerDevice (dev , ops );
96
+ return FS .mkdev (path , mode , dev );
97
+ };
98
+ if (!WebAssembly .promising ) {
99
+ // No stack switching support =(
100
+ return ;
101
+ }
102
+ const origResolveGlobalSymbol = resolveGlobalSymbol ;
103
+ if (!Module .onExit && process ?.exit ) {
104
+ Module .onExit = (code ) = > process .exit (code );
105
+ }
106
+ // * wrap the main symbol with WebAssembly.promising,
107
+ // * call exit_with_live_runtime() to prevent emscripten from shutting down
108
+ // the runtime before the promise resolves,
109
+ // * call onExit / process.exit ourselves, since exit_with_live_runtime()
110
+ // prevented Emscripten from calling it normally.
111
+ resolveGlobalSymbol = function (name , direct = false) {
112
+ const orig = origResolveGlobalSymbol (name , direct );
113
+ if (name == = "main" ) {
114
+ const main = WebAssembly .promising (orig .sym );
115
+ orig .sym = (...args ) = > {
116
+ (async () = > {
117
+ const ret = await main (...args );
118
+ process ?.exit ?.(ret );
119
+ })();
120
+ _emscripten_exit_with_live_runtime ();
121
+ };
122
+ }
123
+ return orig ;
124
+ };
125
+ })
126
+
127
+ __attribute__((constructor )) void _emscripten_promising_main (void ) {
128
+ _emscripten_promising_main_js ();
129
+ }
130
+
131
+
132
+ #define IOVEC_T_BUF_OFFSET 0
133
+ #define IOVEC_T_BUF_LEN_OFFSET 4
134
+ #define IOVEC_T_SIZE 8
135
+ _Static_assert (offsetof(__wasi_iovec_t , buf ) == IOVEC_T_BUF_OFFSET ,
136
+ "Unexpected __wasi_iovec_t layout" );
137
+ _Static_assert (offsetof(__wasi_iovec_t , buf_len ) == IOVEC_T_BUF_LEN_OFFSET ,
138
+ "Unexpected __wasi_iovec_t layout" );
139
+ _Static_assert (sizeof (__wasi_iovec_t ) == IOVEC_T_SIZE ,
140
+ "Unexpected __wasi_iovec_t layout" );
141
+
142
+ // If the stream has a readAsync handler, read to buffer defined in iovs, write
143
+ // number of bytes read to *nread, and return a promise that resolves to the
144
+ // errno. Otherwise, return null.
145
+ EM_JS_MACROS (__externref_t , __maybe_fd_read_async , (
146
+ __wasi_fd_t fd ,
147
+ const __wasi_iovec_t * iovs ,
148
+ size_t iovcnt ,
149
+ __wasi_size_t * nread
150
+ ), {
151
+ if (!WebAssembly .promising ) {
152
+ return null ;
153
+ }
154
+ var stream ;
155
+ try {
156
+ stream = SYSCALLS .getStreamFromFD (fd );
157
+ } catch (e ) {
158
+ // If the fd was already closed or never existed, getStreamFromFD()
159
+ // raises. We'll let fd_read_orig() handle setting errno.
160
+ return null ;
161
+ }
162
+ if (!stream .stream_ops .readAsync ) {
163
+ // Not an async device. Fall back to __wasi_fd_read_orig().
164
+ return null ;
165
+ }
166
+ return (async () = > {
167
+ // This is the same as libwasi.js fd_read() and doReadv() except we use
168
+ // readAsync and we await it.
169
+ // https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libwasi.js?plain=1#L331
170
+ // https://github.com/emscripten-core/emscripten/blob/4.0.11/src/lib/libwasi.js?plain=1#L197
171
+ try {
172
+ var ret = 0 ;
173
+ for (var i = 0 ; i < iovcnt ; i ++ ) {
174
+ var ptr = HEAP32 [(iovs + IOVEC_T_BUF_OFFSET )/4 ];
175
+ var len = HEAP32 [(iovs + IOVEC_T_BUF_LEN_OFFSET )/4 ];
176
+ iovs += IOVEC_T_SIZE ;
177
+ var curr = await stream .stream_ops .readAsync (stream , HEAP8 , ptr , len );
178
+ if (curr < 0 ) return -1 ;
179
+ ret += curr ;
180
+ if (curr < len ) break ; // nothing more to read
181
+ }
182
+ HEAP32 [nread /4 ] = ret ;
183
+ return 0 ;
184
+ } catch (e ) {
185
+ if (e .name != = 'ErrnoError' ) {
186
+ throw e ;
187
+ }
188
+ return e .errno ;
189
+ }
190
+ })();
191
+ };
192
+ );
193
+
194
+ // Bind original fd_read syscall to __wasi_fd_read_orig().
195
+ __wasi_errno_t __wasi_fd_read_orig (__wasi_fd_t fd , const __wasi_iovec_t * iovs ,
196
+ size_t iovs_len , __wasi_size_t * nread )
197
+ __attribute__((__import_module__ ("wasi_snapshot_preview1" ),
198
+ __import_name__ ("fd_read" ), __warn_unused_result__ ));
199
+
200
+ // Take a promise that resolves to __wasi_errno_t and suspend until it resolves,
201
+ // get the output.
202
+ EM_JS (__wasi_errno_t , __block_for_errno , (__externref_t p ), {
203
+ return p ;
204
+ }
205
+ if (WebAssembly .Suspending ) {
206
+ __block_for_errno = new WebAssembly .Suspending (__block_for_errno );
207
+ }
208
+ )
209
+
210
+ // Replacement for fd_read syscall. Call __maybe_fd_read_async. If it returned
211
+ // null, delegate back to __wasi_fd_read_orig. Otherwise, use __block_for_errno
212
+ // to get the result.
213
+ __wasi_errno_t __wasi_fd_read (__wasi_fd_t fd , const __wasi_iovec_t * iovs ,
214
+ size_t iovs_len , __wasi_size_t * nread ) {
215
+ __externref_t p = __maybe_fd_read_async (fd , iovs , iovs_len , nread );
216
+ if (__builtin_wasm_ref_is_null_extern (p )) {
217
+ return __wasi_fd_read_orig (fd , iovs , iovs_len , nread );
218
+ }
219
+ __wasi_errno_t res = __block_for_errno (p );
220
+ return res ;
221
+ }
0 commit comments