Facebook's Static Hermes doesn't provide a way to read stdin
by default. Does hermes expose the capability to read standard input and write standard output? #1469.
CommonJS support was removed. ECMAScript Modules are not yet supported. We can't use
imported files to facilitate reading from standard input. There's no process.argv
to read arguments passed to the program.
There's static_h/examples/ffi/fopen.js which opens and reads a flat file.
Modifying that code to read from /dev/stdin
on Linux
// ...
let f = fopen("/dev/stdin", "r");
print(String(freadAll(f)).trim());
echo '4 5' | shermes -exec -typed fopen.ts
4 5
That works!
However, WASI doesn't support reading /dev/stdin
. Using JavaScript compiled to WASM with WASI-SDK using the above code wasmer
parses the "/dev/stdin"
reference and reads from stdin
. wasmtime
doesn't, and throws an error.
echo '4 5' | wasmtime out/fopen.wasm
Error: failed to run main module `out/fopen.wasm`
Caused by:
0: failed to invoke command default
1: error while executing at wasm backtrace:
0: 0x130e14 - <unknown>!<wasm function 4066>
1: 0xa5995 - <unknown>!<wasm function 2511>
2: 0xa5e29 - <unknown>!<wasm function 2520>
3: 0x5884 - <unknown>!<wasm function 33>
4: 0x2be4 - <unknown>!<wasm function 24>
5: 0x25bd - <unknown>!<wasm function 23>
6: 0xb9a8c - <unknown>!<wasm function 2934>
7: 0xb9984 - <unknown>!<wasm function 2933>
8: 0xb96a2 - <unknown>!<wasm function 2932>
9: 0xb95c6 - <unknown>!<wasm function 2931>
10: 0x35ac - <unknown>!<wasm function 25>
11: 0x130be0 - <unknown>!<wasm function 4044>
12: 0x2393 - <unknown>!<wasm function 21>
2: wasm trap: wasm `unreachable` instruction executed
Fortunately somebody left us some hints and breadcrumbs to follow to fix whatever is not working here with wasmtime
.
Error: failed to invoke command default. wasmer executes the same file #9922
WASI doesn't have
/dev
nor/dev/stdin
. Looks like Wasmer adds/dev/stdin
as non-standard extension: https://github.com/wasmerio/wasmer/blob/1d03406174b204ac403f61e2deb0a41601c8e41b/lib/virtual-fs/src/builder.rs#L106-L110 Instead of usingfopen("/dev/stdin")
you can use thestdin
global variable instead. This is of typeFILE *
just like the return value offopen
. You just have to make sure not tofclose
it.
The Hermes (Static) documentation is not exhaustive about FFI usage.
This signature expects a function. How do we define stdin
in the Static Hermes JavaScript context, even using FFI?
// stdio.h
const _fopen = $SHBuiltin.extern_c(
{ include: "stdio.h" },
function fopen(path: c_ptr, mode: c_ptr): c_ptr {
throw 0;
},
);
We have access to the C API with $SHBuiltin.extern_c
. In WASI-SDK /share/wasi-sysroot/include/wasm32-wasi/stdio.h
we can see what we can use to get the same result using wasmer
and wasmtime
.
// ...
extern FILE *const stdin;
extern FILE *const stdout;
extern FILE *const stderr;
#define stdin (stdin)
#define stdout (stdout)
#define stderr (stderr)
FILE *fopen(const char *__restrict, const char *__restrict);
FILE *freopen(const char *__restrict, const char *__restrict, FILE *__restrict);
int fclose(FILE *);
int remove(const char *);
int rename(const char *, const char *);
int feof(FILE *);
int ferror(FILE *);
int fflush(FILE *);
void clearerr(FILE *);
int fseek(FILE *, long, int);
long ftell(FILE *);
void rewind(FILE *);
int fgetpos(FILE *__restrict, fpos_t *__restrict);
int fsetpos(FILE *, const fpos_t *);
size_t fread(void *__restrict, size_t, size_t, FILE *__restrict);
size_t fwrite(const void *__restrict, size_t, size_t, FILE *__restrict);
int fgetc(FILE *);
int getc(FILE *);
int getchar(void);
int ungetc(int, FILE *);
// ...
Let's see what we can come up with to solve the issue we encountered.
In ISO/IEC 9899:TC3 Committee Draft September 7, 2007 WG14/N1256 there's
7.19.7.6 [The getchar function]
Synopsis
#include <stdio.h> int getchar(void);
Description
The getchar function is equivalent to getc with the argument stdin.
Returns
The getchar function returns the next character from the input stream pointed to by stdin. If the stream is at end-of-file, the end-of-file indicator for the stream is set and getchar returns EOF. If a read error occurs, the error indicator for the stream is set and getchar returns EOF.
No arguments. We don't have to (try to) define stdin
in Static Hermes context to use fgets
.
Let's test this option
const _getchar = $SHBuiltin.extern_c({
include: "stdio.h"
},
function getchar(): c_int {
throw 0;
},
);
function getchars(): string {
"use unsafe";
try {
let n: number = 50;
let c: string = String();
while (--n) {
let int = _getchar();
if (int === -1) {
break;
}
c += String.fromCodePoint(int);
print("int:", int);
print("string:", c);
}
return String(c).trim();
} catch (e) {
return String(e.message);
}
}
let stdin = getchars();
print(stdin);
echo '4 5' | shermes -exec -typed fopen-dev-stdin.ts
int: 52
string: 4
int: 32
string: 4
int: 53
string: 4 5
int: 10
string: 4 5
4 5
Works so far.
Test the code with wasmtime
, that errored when using /dev/stdin
./cross-compile-js.sh fopen.ts
usr/bin/cc /tmp/fopen.ts-556939.c -Os ...
../hermes/include/hermes/VM/static_h.h:334:2: warning: "JS exceptions are currenly broken with WASI" [-W#warnings]
334 | #warning "JS exceptions are currenly broken with WASI"
| ^
1 warning generated.
total 17M
-rwxrwxr-x 1 user user 69K Jan 7 00:10 fopen
-rw-rw-r-- 1 user user 44K Jan 7 00:10 fopen.c
-rw-rw-r-- 1 user user 12K Jan 7 00:10 fopen.o
-rw-rw-r-- 1 user user 2.9K Jan 7 00:10 fopen.ts
-rwxrwxr-x 1 user user 1.5M Jan 7 00:10 fopen.wasm
-rw-rw-r-- 1 user user 15M Jan 7 00:10 fopen.wat
Check wasmer
again
echo '4 5' | wasmer ./out/fopen.wasm
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]
Check native executable compiled by shermes
echo '7 8' | ./out/fopen
8 of 5039 (0-indexed, factorial 5040) => [0,1,2,4,5,3,6]
Check shermes
echo '12 2' | shermes -exec -typed ./out/fopen.ts
2 of 479001599 (0-indexed, factorial 479001600) => [0,1,2,3,4,5,6,7,8,10,9,11]
Check wasmtime
echo '9 32' | wasmtime ./out/fopen.wasm
32 of 362879 (0-indexed, factorial 362880) => [0,1,2,3,5,6,7,4,8]