summaryrefslogtreecommitdiff
path: root/std/wasi
diff options
context:
space:
mode:
Diffstat (limited to 'std/wasi')
-rw-r--r--std/wasi/README.md42
-rw-r--r--std/wasi/snapshot_preview1.ts1344
-rw-r--r--std/wasi/snapshot_preview1_test.ts126
-rw-r--r--std/wasi/testdata/std_env_args.rs6
-rw-r--r--std/wasi/testdata/std_env_vars.rs6
-rw-r--r--std/wasi/testdata/std_io_stderr.rs7
-rw-r--r--std/wasi/testdata/std_io_stdin.rs9
-rw-r--r--std/wasi/testdata/std_io_stdout.rs7
-rw-r--r--std/wasi/testdata/std_process_exit.rs5
9 files changed, 1552 insertions, 0 deletions
diff --git a/std/wasi/README.md b/std/wasi/README.md
new file mode 100644
index 000000000..27a7fdbef
--- /dev/null
+++ b/std/wasi/README.md
@@ -0,0 +1,42 @@
+# wasi
+
+This module provides an implementation of the WebAssembly System Interface
+
+## Supported Syscalls
+
+## Usage
+
+```typescript
+import WASI from "https://deno.land/std/wasi/snapshot_preview1.ts";
+
+const wasi = new WASI({
+ args: Deno.args,
+ env: Deno.env,
+});
+
+const binary = Deno.readAll("path/to/your/module.wasm");
+const module = await WebAssembly.compile(binary);
+const instance = await WebAssembly.instantiate(module, {
+ wasi_snapshot_preview1: wasi.exports,
+});
+
+wasi.memory = module.exports.memory;
+
+if (module.exports._start) {
+ instance.exports._start();
+} else if (module.exports._initialize) {
+ instance.exports._initialize();
+} else {
+ throw new Error("No entry point found");
+}
+```
+
+## Testing
+
+The test suite for this module spawns rustc processes to compile various example
+Rust programs. You must have wasm targets enabled:
+
+```
+rustup target add wasm32-wasi
+rustup target add wasm32-unknown-unknown
+```
diff --git a/std/wasi/snapshot_preview1.ts b/std/wasi/snapshot_preview1.ts
new file mode 100644
index 000000000..a1ae5c8b9
--- /dev/null
+++ b/std/wasi/snapshot_preview1.ts
@@ -0,0 +1,1344 @@
+/* eslint-disable */
+
+import { resolve } from "../path/mod.ts";
+
+const CLOCKID_REALTIME = 0;
+const CLOCKID_MONOTONIC = 1;
+const CLOCKID_PROCESS_CPUTIME_ID = 2;
+const CLOCKID_THREAD_CPUTIME_ID = 3;
+
+const ERRNO_SUCCESS = 0;
+const ERRNO_2BIG = 1;
+const ERRNO_ACCES = 2;
+const ERRNO_ADDRINUSE = 3;
+const ERRNO_ADDRNOTAVAIL = 4;
+const ERRNO_AFNOSUPPORT = 5;
+const ERRNO_AGAIN = 6;
+const ERRNO_ALREADY = 7;
+const ERRNO_BADF = 8;
+const ERRNO_BADMSG = 9;
+const ERRNO_BUSY = 10;
+const ERRNO_CANCELED = 11;
+const ERRNO_CHILD = 12;
+const ERRNO_CONNABORTED = 13;
+const ERRNO_CONNREFUSED = 14;
+const ERRNO_CONNRESET = 15;
+const ERRNO_DEADLK = 16;
+const ERRNO_DESTADDRREQ = 17;
+const ERRNO_DOM = 18;
+const ERRNO_DQUOT = 19;
+const ERRNO_EXIST = 20;
+const ERRNO_FAULT = 21;
+const ERRNO_FBIG = 22;
+const ERRNO_HOSTUNREACH = 23;
+const ERRNO_IDRM = 24;
+const ERRNO_ILSEQ = 25;
+const ERRNO_INPROGRESS = 26;
+const ERRNO_INTR = 27;
+const ERRNO_INVAL = 28;
+const ERRNO_IO = 29;
+const ERRNO_ISCONN = 30;
+const ERRNO_ISDIR = 31;
+const ERRNO_LOOP = 32;
+const ERRNO_MFILE = 33;
+const ERRNO_MLINK = 34;
+const ERRNO_MSGSIZE = 35;
+const ERRNO_MULTIHOP = 36;
+const ERRNO_NAMETOOLONG = 37;
+const ERRNO_NETDOWN = 38;
+const ERRNO_NETRESET = 39;
+const ERRNO_NETUNREACH = 40;
+const ERRNO_NFILE = 41;
+const ERRNO_NOBUFS = 42;
+const ERRNO_NODEV = 43;
+const ERRNO_NOENT = 44;
+const ERRNO_NOEXEC = 45;
+const ERRNO_NOLCK = 46;
+const ERRNO_NOLINK = 47;
+const ERRNO_NOMEM = 48;
+const ERRNO_NOMSG = 49;
+const ERRNO_NOPROTOOPT = 50;
+const ERRNO_NOSPC = 51;
+const ERRNO_NOSYS = 52;
+const ERRNO_NOTCONN = 53;
+const ERRNO_NOTDIR = 54;
+const ERRNO_NOTEMPTY = 55;
+const ERRNO_NOTRECOVERABLE = 56;
+const ERRNO_NOTSOCK = 57;
+const ERRNO_NOTSUP = 58;
+const ERRNO_NOTTY = 59;
+const ERRNO_NXIO = 60;
+const ERRNO_OVERFLOW = 61;
+const ERRNO_OWNERDEAD = 62;
+const ERRNO_PERM = 63;
+const ERRNO_PIPE = 64;
+const ERRNO_PROTO = 65;
+const ERRNO_PROTONOSUPPORT = 66;
+const ERRNO_PROTOTYPE = 67;
+const ERRNO_RANGE = 68;
+const ERRNO_ROFS = 69;
+const ERRNO_SPIPE = 70;
+const ERRNO_SRCH = 71;
+const ERRNO_STALE = 72;
+const ERRNO_TIMEDOUT = 73;
+const ERRNO_TXTBSY = 74;
+const ERRNO_XDEV = 75;
+const ERRNO_NOTCAPABLE = 76;
+
+const RIGHTS_FD_DATASYNC = 0x0000000000000001n;
+const RIGHTS_FD_READ = 0x0000000000000002n;
+const RIGHTS_FD_SEEK = 0x0000000000000004n;
+const RIGHTS_FD_FDSTAT_SET_FLAGS = 0x0000000000000008n;
+const RIGHTS_FD_SYNC = 0x0000000000000010n;
+const RIGHTS_FD_TELL = 0x0000000000000020n;
+const RIGHTS_FD_WRITE = 0x0000000000000040n;
+const RIGHTS_FD_ADVISE = 0x0000000000000080n;
+const RIGHTS_FD_ALLOCATE = 0x0000000000000100n;
+const RIGHTS_PATH_CREATE_DIRECTORY = 0x0000000000000200n;
+const RIGHTS_PATH_CREATE_FILE = 0x0000000000000400n;
+const RIGHTS_PATH_LINK_SOURCE = 0x0000000000000800n;
+const RIGHTS_PATH_LINK_TARGET = 0x0000000000001000n;
+const RIGHTS_PATH_OPEN = 0x0000000000002000n;
+const RIGHTS_FD_READDIR = 0x0000000000004000n;
+const RIGHTS_PATH_READLINK = 0x0000000000008000n;
+const RIGHTS_PATH_RENAME_SOURCE = 0x0000000000010000n;
+const RIGHTS_PATH_RENAME_TARGET = 0x0000000000020000n;
+const RIGHTS_PATH_FILESTAT_GET = 0x0000000000040000n;
+const RIGHTS_PATH_FILESTAT_SET_SIZE = 0x0000000000080000n;
+const RIGHTS_PATH_FILESTAT_SET_TIMES = 0x0000000000100000n;
+const RIGHTS_FD_FILESTAT_GET = 0x0000000000200000n;
+const RIGHTS_FD_FILESTAT_SET_SIZE = 0x0000000000400000n;
+const RIGHTS_FD_FILESTAT_SET_TIMES = 0x0000000000800000n;
+const RIGHTS_PATH_SYMLINK = 0x0000000001000000n;
+const RIGHTS_PATH_REMOVE_DIRECTORY = 0x0000000002000000n;
+const RIGHTS_PATH_UNLINK_FILE = 0x0000000004000000n;
+const RIGHTS_POLL_FD_READWRITE = 0x0000000008000000n;
+const RIGHTS_SOCK_SHUTDOWN = 0x0000000010000000n;
+
+const WHENCE_SET = 0;
+const WHENCE_CUR = 1;
+const WHENCE_END = 2;
+
+const FILETYPE_UNKNOWN = 0;
+const FILETYPE_BLOCK_DEVICE = 1;
+const FILETYPE_CHARACTER_DEVICE = 2;
+const FILETYPE_DIRECTORY = 3;
+const FILETYPE_REGULAR_FILE = 4;
+const FILETYPE_SOCKET_DGRAM = 5;
+const FILETYPE_SOCKET_STREAM = 6;
+const FILETYPE_SYMBOLIC_LINK = 7;
+
+const ADVICE_NORMAL = 0;
+const ADVICE_SEQUENTIAL = 1;
+const ADVICE_RANDOM = 2;
+const ADVICE_WILLNEED = 3;
+const ADVICE_DONTNEED = 4;
+const ADVICE_NOREUSE = 5;
+
+const FDFLAGS_APPEND = 0x0001;
+const FDFLAGS_DSYNC = 0x0002;
+const FDFLAGS_NONBLOCK = 0x0004;
+const FDFLAGS_RSYNC = 0x0008;
+const FDFLAGS_SYNC = 0x0010;
+
+const FSTFLAGS_ATIM = 0x0001;
+const FSTFLAGS_ATIM_NOW = 0x0002;
+const FSTFLAGS_MTIM = 0x0004;
+const FSTFLAGS_MTIM_NOW = 0x0008;
+
+const LOOKUPFLAGS_SYMLINK_FOLLOW = 0x0001;
+
+const OFLAGS_CREAT = 0x0001;
+const OFLAGS_DIRECTORY = 0x0002;
+const OFLAGS_EXCL = 0x0004;
+const OFLAGS_TRUNC = 0x0008;
+
+const EVENTTYPE_CLOCK = 0;
+const EVENTTYPE_FD_READ = 1;
+const EVENTTYPE_FD_WRITE = 2;
+
+const EVENTRWFLAGS_FD_READWRITE_HANGUP = 1;
+const SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME = 1;
+
+const SIGNAL_NONE = 0;
+const SIGNAL_HUP = 1;
+const SIGNAL_INT = 2;
+const SIGNAL_QUIT = 3;
+const SIGNAL_ILL = 4;
+const SIGNAL_TRAP = 5;
+const SIGNAL_ABRT = 6;
+const SIGNAL_BUS = 7;
+const SIGNAL_FPE = 8;
+const SIGNAL_KILL = 9;
+const SIGNAL_USR1 = 10;
+const SIGNAL_SEGV = 11;
+const SIGNAL_USR2 = 12;
+const SIGNAL_PIPE = 13;
+const SIGNAL_ALRM = 14;
+const SIGNAL_TERM = 15;
+const SIGNAL_CHLD = 16;
+const SIGNAL_CONT = 17;
+const SIGNAL_STOP = 18;
+const SIGNAL_TSTP = 19;
+const SIGNAL_TTIN = 20;
+const SIGNAL_TTOU = 21;
+const SIGNAL_URG = 22;
+const SIGNAL_XCPU = 23;
+const SIGNAL_XFSZ = 24;
+const SIGNAL_VTALRM = 25;
+const SIGNAL_PROF = 26;
+const SIGNAL_WINCH = 27;
+const SIGNAL_POLL = 28;
+const SIGNAL_PWR = 29;
+const SIGNAL_SYS = 30;
+
+const RIFLAGS_RECV_PEEK = 0x0001;
+const RIFLAGS_RECV_WAITALL = 0x0002;
+
+const ROFLAGS_RECV_DATA_TRUNCATED = 0x0001;
+
+const SDFLAGS_RD = 0x0001;
+const SDFLAGS_WR = 0x0002;
+
+const PREOPENTYPE_DIR = 0;
+
+const clock_res_realtime = function (): bigint {
+ return BigInt(1e6);
+};
+
+const clock_res_monotonic = function (): bigint {
+ return BigInt(1e3);
+};
+
+const clock_res_process = clock_res_monotonic;
+const clock_res_thread = clock_res_monotonic;
+
+const clock_time_realtime = function (): bigint {
+ return BigInt(Date.now()) * BigInt(1e6);
+};
+
+const clock_time_monotonic = function (): bigint {
+ const t = performance.now();
+ const s = Math.trunc(t);
+ const ms = Math.floor((t - s) * 1e3);
+
+ return BigInt(s) * BigInt(1e9) + BigInt(ms) * BigInt(1e6);
+};
+
+const clock_time_process = clock_time_monotonic;
+const clock_time_thread = clock_time_monotonic;
+
+function errno(err: Error) {
+ switch (err.name) {
+ case "NotFound":
+ return ERRNO_NOENT;
+
+ case "PermissionDenied":
+ return ERRNO_ACCES;
+
+ case "ConnectionRefused":
+ return ERRNO_CONNREFUSED;
+
+ case "ConnectionReset":
+ return ERRNO_CONNRESET;
+
+ case "ConnectionAborted":
+ return ERRNO_CONNABORTED;
+
+ case "NotConnected":
+ return ERRNO_NOTCONN;
+
+ case "AddrInUse":
+ return ERRNO_ADDRINUSE;
+
+ case "AddrNotAvailable":
+ return ERRNO_ADDRNOTAVAIL;
+
+ case "BrokenPipe":
+ return ERRNO_PIPE;
+
+ case "InvalidData":
+ return ERRNO_INVAL;
+
+ case "TimedOut":
+ return ERRNO_TIMEDOUT;
+
+ case "Interrupted":
+ return ERRNO_INTR;
+
+ case "BadResource":
+ return ERRNO_BADF;
+
+ case "Busy":
+ return ERRNO_BUSY;
+
+ default:
+ return ERRNO_INVAL;
+ }
+}
+
+export interface ModuleOptions {
+ args?: string[];
+ env?: { [key: string]: string | undefined };
+ preopens?: { [key: string]: string };
+ memory?: WebAssembly.Memory;
+}
+
+export default class Module {
+ args: string[];
+ env: { [key: string]: string | undefined };
+ memory: WebAssembly.Memory;
+
+ // deno-lint-ignore no-explicit-any
+ fds: any[];
+
+ // deno-lint-ignore no-explicit-any
+ exports: { [key: string]: any };
+
+ constructor(options: ModuleOptions) {
+ this.args = options.args ? options.args : [];
+ this.env = options.env ? options.env : {};
+ this.memory = options.memory!;
+
+ this.fds = [
+ {
+ type: FILETYPE_CHARACTER_DEVICE,
+ handle: Deno.stdin,
+ },
+ {
+ type: FILETYPE_CHARACTER_DEVICE,
+ handle: Deno.stdout,
+ },
+ {
+ type: FILETYPE_CHARACTER_DEVICE,
+ handle: Deno.stderr,
+ },
+ ];
+
+ if (options.preopens) {
+ for (const [vpath, path] of Object.entries(options.preopens)) {
+ const info = Deno.statSync(path);
+ if (!info.isDirectory) {
+ throw new TypeError(`${path} is not a directory`);
+ }
+
+ const type = FILETYPE_DIRECTORY;
+
+ const entry = {
+ type,
+ path,
+ vpath,
+ };
+
+ this.fds.push(entry);
+ }
+ }
+
+ this.exports = {
+ args_get: (argv_ptr: number, argv_buf_ptr: number): number => {
+ const args = this.args;
+ const text = new TextEncoder();
+ const heap = new Uint8Array(this.memory.buffer);
+ const view = new DataView(this.memory.buffer);
+
+ for (let arg of args) {
+ view.setUint32(argv_ptr, argv_buf_ptr, true);
+ argv_ptr += 4;
+
+ const data = text.encode(`${arg}\0`);
+ heap.set(data, argv_buf_ptr);
+ argv_buf_ptr += data.length;
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ args_sizes_get: (argc_out: number, argv_buf_size_out: number): number => {
+ const args = this.args;
+ const text = new TextEncoder();
+ const view = new DataView(this.memory.buffer);
+
+ view.setUint32(argc_out, args.length, true);
+ view.setUint32(
+ argv_buf_size_out,
+ args.reduce(function (acc, arg) {
+ return acc + text.encode(`${arg}\0`).length;
+ }, 0),
+ true
+ );
+
+ return ERRNO_SUCCESS;
+ },
+
+ environ_get: (environ_ptr: number, environ_buf_ptr: number): number => {
+ const entries = Object.entries(this.env);
+ const text = new TextEncoder();
+ const heap = new Uint8Array(this.memory.buffer);
+ const view = new DataView(this.memory.buffer);
+
+ for (let [key, value] of entries) {
+ view.setUint32(environ_ptr, environ_buf_ptr, true);
+ environ_ptr += 4;
+
+ const data = text.encode(`${key}=${value}\0`);
+ heap.set(data, environ_buf_ptr);
+ environ_buf_ptr += data.length;
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ environ_sizes_get: (
+ environc_out: number,
+ environ_buf_size_out: number
+ ): number => {
+ const entries = Object.entries(this.env);
+ const text = new TextEncoder();
+ const view = new DataView(this.memory.buffer);
+
+ view.setUint32(environc_out, entries.length, true);
+ view.setUint32(
+ environ_buf_size_out,
+ entries.reduce(function (acc, [key, value]) {
+ return acc + text.encode(`${key}=${value}\0`).length;
+ }, 0),
+ true
+ );
+
+ return ERRNO_SUCCESS;
+ },
+
+ clock_res_get: (id: number, resolution_out: number): number => {
+ const view = new DataView(this.memory.buffer);
+
+ switch (id) {
+ case CLOCKID_REALTIME:
+ view.setBigUint64(resolution_out, clock_res_realtime(), true);
+ break;
+
+ case CLOCKID_MONOTONIC:
+ view.setBigUint64(resolution_out, clock_res_monotonic(), true);
+ break;
+
+ case CLOCKID_PROCESS_CPUTIME_ID:
+ view.setBigUint64(resolution_out, clock_res_process(), true);
+ break;
+
+ case CLOCKID_THREAD_CPUTIME_ID:
+ view.setBigUint64(resolution_out, clock_res_thread(), true);
+ break;
+
+ default:
+ return ERRNO_INVAL;
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ clock_time_get: (
+ id: number,
+ precision: bigint,
+ time_out: number
+ ): number => {
+ const view = new DataView(this.memory.buffer);
+
+ switch (id) {
+ case CLOCKID_REALTIME:
+ view.setBigUint64(time_out, clock_time_realtime(), true);
+ break;
+
+ case CLOCKID_MONOTONIC:
+ view.setBigUint64(time_out, clock_time_monotonic(), true);
+ break;
+
+ case CLOCKID_PROCESS_CPUTIME_ID:
+ view.setBigUint64(time_out, clock_time_process(), true);
+ break;
+
+ case CLOCKID_THREAD_CPUTIME_ID:
+ view.setBigUint64(time_out, clock_time_thread(), true);
+ break;
+
+ default:
+ return ERRNO_INVAL;
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_advise: (
+ fd: number,
+ offset: bigint,
+ len: bigint,
+ advice: number
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_allocate: (fd: number, offset: bigint, len: bigint): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_close: (fd: number): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ entry.handle.close();
+ delete this.fds[fd];
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_datasync: (fd: number): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_fdstat_get: (fd: number, stat_out: number): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+ view.setUint8(stat_out, entry.type);
+ view.setUint16(stat_out + 4, 0, true); // TODO
+ view.setBigUint64(stat_out + 8, 0n, true); // TODO
+ view.setBigUint64(stat_out + 16, 0n, true); // TODO
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_fdstat_set_flags: (fd: number, flags: number): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_fdstat_set_rights: (
+ fd: number,
+ fs_rights_base: bigint,
+ fs_rights_inheriting: bigint
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_filestat_get: (fd: number, buf_out: number): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_filestat_set_size: (fd: number, size: bigint): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_filestat_set_times: (
+ fd: number,
+ atim: bigint,
+ mtim: bigint,
+ fst_flags: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ if ((fst_flags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) {
+ atim = BigInt(Date.now() * 1e6);
+ }
+
+ if ((fst_flags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) {
+ mtim = BigInt(Date.now() * 1e6);
+ }
+
+ try {
+ Deno.utimeSync(entry.path, Number(atim), Number(mtim));
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_pread: (
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ offset: bigint,
+ nread_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const seek = entry.handle.seekSync(0, Deno.SeekMode.Current);
+ const view = new DataView(this.memory.buffer);
+
+ let nread = 0;
+ for (let i = 0; i < iovs_len; i++) {
+ const data_ptr = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data_len = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data = new Uint8Array(this.memory.buffer, data_ptr, data_len);
+ nread += entry.handle.readSync(data);
+ }
+
+ entry.handle.seekSync(seek, Deno.SeekMode.Start);
+ view.setUint32(nread_out, nread, true);
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_prestat_get: (fd: number, buf_out: number): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.vpath) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+ view.setUint8(buf_out, PREOPENTYPE_DIR);
+ view.setUint32(
+ buf_out + 4,
+ new TextEncoder().encode(entry.vpath).byteLength,
+ true
+ );
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_prestat_dir_name: (
+ fd: number,
+ path_ptr: number,
+ path_len: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.vpath) {
+ return ERRNO_BADF;
+ }
+
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ data.set(new TextEncoder().encode(entry.vpath));
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_pwrite: (
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ offset: bigint,
+ nwritten_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const seek = entry.handle.seekSync(0, Deno.SeekMode.Current);
+ const view = new DataView(this.memory.buffer);
+
+ let nwritten = 0;
+ for (let i = 0; i < iovs_len; i++) {
+ const data_ptr = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data_len = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data = new Uint8Array(this.memory.buffer, data_ptr, data_len);
+ nwritten += entry.handle.writeSync(data);
+ }
+
+ entry.handle.seekSync(seek, Deno.SeekMode.Start);
+ view.setUint32(nwritten_out, nwritten, true);
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_read: (
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ nread_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+
+ let nread = 0;
+ for (let i = 0; i < iovs_len; i++) {
+ const data_ptr = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data_len = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data = new Uint8Array(this.memory.buffer, data_ptr, data_len);
+ nread += entry.handle.readSync(data);
+ }
+
+ view.setUint32(nread_out, nread, true);
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_readdir: (
+ fd: number,
+ buf_ptr: number,
+ buf_len: number,
+ cookie: bigint,
+ bufused_out: number
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_renumber: (fd: number, to: number): number => {
+ if (!this.fds[fd]) {
+ return ERRNO_BADF;
+ }
+
+ if (!this.fds[to]) {
+ return ERRNO_BADF;
+ }
+
+ this.fds[to].handle.close();
+ this.fds[to] = this.fds[fd];
+ delete this.fds[fd];
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_seek: (
+ fd: number,
+ offset: bigint,
+ whence: number,
+ newoffset_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+
+ try {
+ // FIXME Deno does not support seeking with big integers
+
+ const newoffset = entry.handle.seekSync(Number(offset), whence);
+ view.setBigUint64(newoffset_out, BigInt(newoffset), true);
+ } catch (err) {
+ return ERRNO_INVAL;
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ fd_sync: (fd: number): number => {
+ return ERRNO_NOSYS;
+ },
+
+ fd_tell: (fd: number, offset_out: number): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+
+ try {
+ const offset = entry.handle.seekSync(0, Deno.SeekMode.Current);
+ view.setBigUint64(offset_out, offset, true);
+ } catch (err) {
+ return ERRNO_INVAL;
+ }
+
+ return ERRNO_NOSYS;
+ },
+
+ fd_write: (
+ fd: number,
+ iovs_ptr: number,
+ iovs_len: number,
+ nwritten_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ const view = new DataView(this.memory.buffer);
+
+ let nwritten = 0;
+ for (let i = 0; i < iovs_len; i++) {
+ const data_ptr = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ const data_len = view.getUint32(iovs_ptr, true);
+ iovs_ptr += 4;
+
+ nwritten += entry.handle.writeSync(
+ new Uint8Array(this.memory.buffer, data_ptr, data_len)
+ );
+ }
+
+ view.setUint32(nwritten_out, nwritten, true);
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_create_directory: (
+ fd: number,
+ path_ptr: number,
+ path_len: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ try {
+ Deno.mkdirSync(path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_filestat_get: (
+ fd: number,
+ flags: number,
+ path_ptr: number,
+ path_len: number,
+ buf_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ const view = new DataView(this.memory.buffer);
+
+ try {
+ const info = Deno.statSync(path);
+
+ view.setBigUint64(buf_out, BigInt(info.dev ? info.dev : 0), true);
+ buf_out += 8;
+
+ view.setBigUint64(buf_out, BigInt(info.ino ? info.ino : 0), true);
+ buf_out += 8;
+
+ switch (true) {
+ case info.isFile:
+ view.setUint8(buf_out, FILETYPE_REGULAR_FILE);
+ buf_out += 4;
+ break;
+
+ case info.isDirectory:
+ view.setUint8(buf_out, FILETYPE_DIRECTORY);
+ buf_out += 4;
+ break;
+
+ case info.isSymlink:
+ view.setUint8(buf_out, FILETYPE_SYMBOLIC_LINK);
+ buf_out += 4;
+ break;
+
+ default:
+ view.setUint8(buf_out, FILETYPE_UNKNOWN);
+ buf_out += 4;
+ break;
+ }
+
+ view.setUint32(buf_out, Number(info.nlink), true);
+ buf_out += 4;
+
+ view.setBigUint64(buf_out, BigInt(info.size), true);
+ buf_out += 8;
+
+ view.setBigUint64(
+ buf_out,
+ BigInt(info.atime ? info.atime.getTime() * 1e6 : 0),
+ true
+ );
+ buf_out += 8;
+
+ view.setBigUint64(
+ buf_out,
+ BigInt(info.mtime ? info.mtime.getTime() * 1e6 : 0),
+ true
+ );
+ buf_out += 8;
+
+ view.setBigUint64(
+ buf_out,
+ BigInt(info.birthtime ? info.birthtime.getTime() * 1e6 : 0),
+ true
+ );
+ buf_out += 8;
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_filestat_set_times: (
+ fd: number,
+ flags: number,
+ path_ptr: number,
+ path_len: number,
+ atim: bigint,
+ mtim: bigint,
+ fst_flags: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ if ((fst_flags & FSTFLAGS_ATIM_NOW) == FSTFLAGS_ATIM_NOW) {
+ atim = BigInt(Date.now()) * BigInt(1e6);
+ }
+
+ if ((fst_flags & FSTFLAGS_MTIM_NOW) == FSTFLAGS_MTIM_NOW) {
+ mtim = BigInt(Date.now()) * BigInt(1e6);
+ }
+
+ try {
+ Deno.utimeSync(path, Number(atim), Number(mtim));
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_link: (
+ old_fd: number,
+ old_flags: number,
+ old_path_ptr: number,
+ old_path_len: number,
+ new_fd: number,
+ new_path_ptr: number,
+ new_path_len: number
+ ): number => {
+ const old_entry = this.fds[old_fd];
+ const new_entry = this.fds[new_fd];
+ if (!old_entry || !new_entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!old_entry.path || !new_entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const old_data = new Uint8Array(
+ this.memory.buffer,
+ old_path_ptr,
+ old_path_len
+ );
+ const old_path = resolve(old_entry.path, text.decode(old_data));
+ const new_data = new Uint8Array(
+ this.memory.buffer,
+ new_path_ptr,
+ new_path_len
+ );
+ const new_path = resolve(new_entry.path, text.decode(new_data));
+
+ try {
+ Deno.linkSync(old_path, new_path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_open: (
+ fd: number,
+ dirflags: number,
+ path_ptr: number,
+ path_len: number,
+ oflags: number,
+ fs_rights_base: number | bigint,
+ fs_rights_inherting: number | bigint,
+ fdflags: number,
+ opened_fd_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ const options = {
+ read: false,
+ write: false,
+ append: false,
+ truncate: false,
+ create: false,
+ createNew: false,
+ };
+
+ if ((oflags & OFLAGS_CREAT) !== 0) {
+ options.create = true;
+ options.write = true;
+ }
+
+ if ((oflags & OFLAGS_DIRECTORY) !== 0) {
+ // TODO (caspervonb) review if we can
+ // emulate this; unix supports opening
+ // directories, windows does not.
+ }
+
+ if ((oflags & OFLAGS_EXCL) !== 0) {
+ options.createNew = true;
+ }
+
+ if ((oflags & OFLAGS_TRUNC) !== 0) {
+ options.truncate = true;
+ options.write = true;
+ }
+
+ if (
+ (BigInt(fs_rights_base) &
+ BigInt(RIGHTS_FD_READ | RIGHTS_FD_READDIR)) !=
+ 0n
+ ) {
+ options.read = true;
+ }
+
+ if (
+ (BigInt(fs_rights_base) &
+ BigInt(
+ RIGHTS_FD_DATASYNC |
+ RIGHTS_FD_WRITE |
+ RIGHTS_FD_ALLOCATE |
+ RIGHTS_FD_FILESTAT_SET_SIZE
+ )) !=
+ 0n
+ ) {
+ options.write = true;
+ }
+
+ if ((fdflags & FDFLAGS_APPEND) != 0) {
+ options.append = true;
+ }
+
+ if ((fdflags & FDFLAGS_DSYNC) != 0) {
+ // TODO (caspervonb) review if we can emulate this.
+ }
+
+ if ((fdflags & FDFLAGS_NONBLOCK) != 0) {
+ // TODO (caspervonb) review if we can emulate this.
+ }
+
+ if ((fdflags & FDFLAGS_RSYNC) != 0) {
+ // TODO (caspervonb) review if we can emulate this.
+ }
+
+ if ((fdflags & FDFLAGS_SYNC) != 0) {
+ // TODO (caspervonb) review if we can emulate this.
+ }
+
+ if (!options.read && !options.write && !options.truncate) {
+ options.read = true;
+ }
+
+ try {
+ const handle = Deno.openSync(path, options);
+ const opened_fd =
+ this.fds.push({
+ handle,
+ path,
+ }) - 1;
+
+ const view = new DataView(this.memory.buffer);
+ view.setUint32(opened_fd_out, opened_fd, true);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_readlink: (
+ fd: number,
+ path_ptr: number,
+ path_len: number,
+ buf_ptr: number,
+ buf_len: number,
+ bufused_out: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const view = new DataView(this.memory.buffer);
+ const heap = new Uint8Array(this.memory.buffer);
+
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, new TextDecoder().decode(data));
+
+ try {
+ const link = Deno.readLinkSync(path);
+ const data = new TextEncoder().encode(link);
+ heap.set(new Uint8Array(data, 0, buf_len), buf_ptr);
+
+ const bufused = Math.min(data.byteLength, buf_len);
+ view.setUint32(bufused_out, bufused, true);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_remove_directory: (
+ fd: number,
+ path_ptr: number,
+ path_len: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ try {
+ if (!Deno.statSync(path).isDirectory) {
+ return ERRNO_NOTDIR;
+ }
+
+ Deno.removeSync(path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_rename: (
+ fd: number,
+ old_path_ptr: number,
+ old_path_len: number,
+ new_fd: number,
+ new_path_ptr: number,
+ new_path_len: number
+ ): number => {
+ const old_entry = this.fds[fd];
+ const new_entry = this.fds[new_fd];
+ if (!old_entry || !new_entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!old_entry.path || !new_entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const old_data = new Uint8Array(
+ this.memory.buffer,
+ old_path_ptr,
+ old_path_len
+ );
+ const old_path = resolve(old_entry.path, text.decode(old_data));
+ const new_data = new Uint8Array(
+ this.memory.buffer,
+ new_path_ptr,
+ new_path_len
+ );
+ const new_path = resolve(new_entry.path, text.decode(new_data));
+
+ try {
+ Deno.renameSync(old_path, new_path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_symlink: (
+ old_path_ptr: number,
+ old_path_len: number,
+ fd: number,
+ new_path_ptr: number,
+ new_path_len: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const old_data = new Uint8Array(
+ this.memory.buffer,
+ old_path_ptr,
+ old_path_len
+ );
+ const old_path = text.decode(old_data);
+ const new_data = new Uint8Array(
+ this.memory.buffer,
+ new_path_ptr,
+ new_path_len
+ );
+ const new_path = resolve(entry.path, text.decode(new_data));
+
+ try {
+ Deno.symlinkSync(old_path, new_path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ path_unlink_file: (
+ fd: number,
+ path_ptr: number,
+ path_len: number
+ ): number => {
+ const entry = this.fds[fd];
+ if (!entry) {
+ return ERRNO_BADF;
+ }
+
+ if (!entry.path) {
+ return ERRNO_INVAL;
+ }
+
+ const text = new TextDecoder();
+ const data = new Uint8Array(this.memory.buffer, path_ptr, path_len);
+ const path = resolve(entry.path, text.decode(data));
+
+ try {
+ Deno.removeSync(path);
+ } catch (err) {
+ return errno(err);
+ }
+
+ return ERRNO_SUCCESS;
+ },
+
+ poll_oneoff: (
+ in_ptr: number,
+ out_ptr: number,
+ nsubscriptions: number,
+ nevents_out: number
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ proc_exit: (rval: number): never => {
+ Deno.exit(rval);
+ },
+
+ proc_raise: (sig: number): number => {
+ return ERRNO_NOSYS;
+ },
+
+ sched_yield: (): number => {
+ return ERRNO_SUCCESS;
+ },
+
+ random_get: (buf_ptr: number, buf_len: number): number => {
+ const buffer = new Uint8Array(this.memory.buffer, buf_ptr, buf_len);
+ crypto.getRandomValues(buffer);
+
+ return ERRNO_SUCCESS;
+ },
+
+ sock_recv: (
+ fd: number,
+ ri_data_ptr: number,
+ ri_data_len: number,
+ ri_flags: number,
+ ro_datalen_out: number,
+ ro_flags_out: number
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ sock_send: (
+ fd: number,
+ si_data_ptr: number,
+ si_data_len: number,
+ si_flags: number,
+ so_datalen_out: number
+ ): number => {
+ return ERRNO_NOSYS;
+ },
+
+ sock_shutdown: (fd: number, how: number): number => {
+ return ERRNO_NOSYS;
+ },
+ };
+ }
+}
diff --git a/std/wasi/snapshot_preview1_test.ts b/std/wasi/snapshot_preview1_test.ts
new file mode 100644
index 000000000..9d8892f9c
--- /dev/null
+++ b/std/wasi/snapshot_preview1_test.ts
@@ -0,0 +1,126 @@
+/* eslint-disable */
+
+import { assert, assertEquals } from "../testing/asserts.ts";
+import * as path from "../path/mod.ts";
+import WASI from "./snapshot_preview1.ts";
+
+if (import.meta.main) {
+ const options = JSON.parse(Deno.args[0]);
+ const binary = await Deno.readFile(Deno.args[1]);
+ const module = await WebAssembly.compile(binary);
+
+ const wasi = new WASI({
+ env: options.env,
+ args: options.args,
+ preopens: options.preopens,
+ });
+
+ const instance = new WebAssembly.Instance(module, {
+ wasi_snapshot_preview1: wasi.exports,
+ });
+
+ wasi.memory = instance.exports.memory;
+
+ instance.exports._start();
+} else {
+ const rootdir = path.dirname(path.fromFileUrl(import.meta.url));
+ const testdir = path.join(rootdir, "testdata");
+ const outdir = path.join(testdir, "snapshot_preview1");
+
+ for await (const entry of Deno.readDir(testdir)) {
+ if (!entry.name.endsWith(".rs")) {
+ continue;
+ }
+
+ const process = Deno.run({
+ cmd: [
+ "rustc",
+ "--target",
+ "wasm32-wasi",
+ "--out-dir",
+ outdir,
+ path.join(testdir, entry.name),
+ ],
+ stdout: "inherit",
+ stderr: "inherit",
+ });
+
+ const status = await process.status();
+ assert(status.success);
+
+ process.close();
+
+ // TODO(caspervonb) allow the prelude to span multiple lines
+ const source = await Deno.readTextFile(path.join(testdir, entry.name));
+ const prelude = source.match(/^\/\/\s*\{.*/);
+ if (prelude) {
+ const basename = entry.name.replace(/.rs$/, ".json");
+ await Deno.writeTextFile(
+ path.join(outdir, basename),
+ prelude[0].slice(2)
+ );
+ }
+ }
+
+ for await (const entry of Deno.readDir(outdir)) {
+ if (!entry.name.endsWith(".wasm")) {
+ continue;
+ }
+
+ Deno.test(entry.name, async function () {
+ const basename = entry.name.replace(/\.wasm$/, ".json");
+ const prelude = await Deno.readTextFile(path.resolve(outdir, basename));
+ const options = JSON.parse(prelude);
+
+ const process = await Deno.run({
+ cwd: testdir,
+ cmd: [
+ `${Deno.execPath()}`,
+ "run",
+ "--quiet",
+ "--unstable",
+ "--v8-flags=--experimental-wasm-bigint",
+ "--allow-all",
+ import.meta.url,
+ prelude,
+ path.resolve(outdir, entry.name),
+ ],
+ stdin: "piped",
+ stdout: "piped",
+ stderr: "piped",
+ });
+
+ if (options.stdin) {
+ const stdin = new TextEncoder().encode(options.stdin);
+ await Deno.writeAll(process.stdin, stdin);
+ }
+
+ process.stdin.close();
+
+ const stdout = await Deno.readAll(process.stdout);
+
+ if (options.stdout) {
+ assertEquals(new TextDecoder().decode(stdout), options.stdout);
+ } else {
+ await Deno.writeAll(Deno.stdout, stdout);
+ }
+
+ process.stdout.close();
+
+ const stderr = await Deno.readAll(process.stderr);
+
+ if (options.stderr) {
+ assertEquals(new TextDecoder().decode(stderr), options.stderr);
+ } else {
+ await Deno.writeAll(Deno.stderr, stderr);
+ }
+
+ process.stderr.close();
+
+ const status = await process.status();
+ assertEquals(status.code, options.exitCode ? +options.exitCode : 0);
+
+ process.close();
+ });
+ }
+}
diff --git a/std/wasi/testdata/std_env_args.rs b/std/wasi/testdata/std_env_args.rs
new file mode 100644
index 000000000..09151e549
--- /dev/null
+++ b/std/wasi/testdata/std_env_args.rs
@@ -0,0 +1,6 @@
+// { "args": ["one", "two", "three" ]}
+
+fn main() {
+ let args = std::env::args();
+ assert_eq!(args.len(), 3);
+}
diff --git a/std/wasi/testdata/std_env_vars.rs b/std/wasi/testdata/std_env_vars.rs
new file mode 100644
index 000000000..5c088a5fa
--- /dev/null
+++ b/std/wasi/testdata/std_env_vars.rs
@@ -0,0 +1,6 @@
+// { "env": { "one": "1", "two": "2", "three": "3" } }
+
+fn main() {
+ let vars = std::env::vars();
+ assert_eq!(vars.count(), 3);
+}
diff --git a/std/wasi/testdata/std_io_stderr.rs b/std/wasi/testdata/std_io_stderr.rs
new file mode 100644
index 000000000..8e1dee30e
--- /dev/null
+++ b/std/wasi/testdata/std_io_stderr.rs
@@ -0,0 +1,7 @@
+// { "stderr": "Hello, stderr!" }
+
+use std::io::Write;
+
+fn main() {
+ assert!(std::io::stderr().write_all(b"Hello, stderr!").is_ok())
+}
diff --git a/std/wasi/testdata/std_io_stdin.rs b/std/wasi/testdata/std_io_stdin.rs
new file mode 100644
index 000000000..170d575c5
--- /dev/null
+++ b/std/wasi/testdata/std_io_stdin.rs
@@ -0,0 +1,9 @@
+// { "stdin": "Hello, stdin!" }
+
+use std::io::Read;
+
+fn main() {
+ let mut buffer = String::new();
+ assert!(std::io::stdin().read_to_string(&mut buffer).is_ok());
+ assert_eq!(buffer, "Hello, stdin!")
+}
diff --git a/std/wasi/testdata/std_io_stdout.rs b/std/wasi/testdata/std_io_stdout.rs
new file mode 100644
index 000000000..3c9058a0d
--- /dev/null
+++ b/std/wasi/testdata/std_io_stdout.rs
@@ -0,0 +1,7 @@
+// { "stdout": "Hello, stdout!" }
+
+use std::io::Write;
+
+fn main() {
+ assert!(std::io::stdout().write_all(b"Hello, stdout!").is_ok())
+}
diff --git a/std/wasi/testdata/std_process_exit.rs b/std/wasi/testdata/std_process_exit.rs
new file mode 100644
index 000000000..0a0287c54
--- /dev/null
+++ b/std/wasi/testdata/std_process_exit.rs
@@ -0,0 +1,5 @@
+// { "exitCode": "120" }
+
+fn main() {
+ std::process::exit(120);
+}