diff --git a/.circleci/config.yml b/.circleci/config.yml index f04c31a946bba..938ca07b3757e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -494,7 +494,7 @@ jobs: name: get wasmtime command: | # use a pinned version due to https://github.com/bytecodealliance/wasmtime/issues/714 - export VERSION=v0.33.0 + export VERSION=v32.0.0 wget https://github.com/bytecodealliance/wasmtime/releases/download/$VERSION/wasmtime-$VERSION-x86_64-linux.tar.xz tar -xf wasmtime-$VERSION-x86_64-linux.tar.xz cp wasmtime-$VERSION-x86_64-linux/wasmtime ~/vms diff --git a/LICENSE b/LICENSE index 70cabe388d27d..c973e2ae71cb6 100644 --- a/LICENSE +++ b/LICENSE @@ -100,3 +100,31 @@ The third_party/ subdirectory contains code with other licenses. None of it is used by default, but certain options use it (e.g., the optional closure compiler flag will run closure compiler from third_party/). +Files in system/lib/standalone/ contain code derived from wasi-libc, in +accordance with the terms of the MIT license. wasi-libc's license follows: + + """ + Permission is hereby granted, free of charge, to any + person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without + limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software + is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice + shall be included in all copies or substantial portions + of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + """ diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index c5474f4847718..1c7c9e577bcb4 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -1536,6 +1536,9 @@ sigs = { lineColor__sig: 'ipiiiii', lineRGBA__sig: 'ipiiiiiiii', llvm_eh_typeid_for__sig: 'vp', + path_create_directory__sig: 'iipp', + path_filestat_get__sig: 'iiippp', + path_symlink__sig: 'ippipp', pixelRGBA__sig: 'ipiiiiii', proc_exit__sig: 'vi', random_get__sig: 'ipp', diff --git a/src/lib/libwasi.js b/src/lib/libwasi.js index 109592c11b9e2..e30ff08951ce7 100644 --- a/src/lib/libwasi.js +++ b/src/lib/libwasi.js @@ -608,6 +608,23 @@ var WasiLibrary = { randomFill(HEAPU8.subarray(buffer, buffer + size)); return 0; }, + +#if ALLOW_UNIMPLEMENTED_SYSCALLS + path_filestat_get__nothrow: true, + path_filestat_get: (fd, flags, path, path_len, buf) => { + return {{{ cDefs.ENOSYS }}}; + }, + + path_create_directory__nothrow: true, + path_create_directory: (fd, path, path_len) => { + return {{{ cDefs.ENOSYS }}}; + }, + + path_symlink__nothrow: true, + path_symlink: (old_path, old_path_len, fd, new_path, new_path_len) => { + return {{{ cDefs.ENOSYS }}}; + }, +#endif }; for (var x in WasiLibrary) { diff --git a/system/lib/standalone/paths.c b/system/lib/standalone/paths.c new file mode 100644 index 0000000000000..c1fd7ef61a41a --- /dev/null +++ b/system/lib/standalone/paths.c @@ -0,0 +1,254 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + * + * The preopen code is based on wasi-libc's `preopens.c` which is licensed + * under a MIT style license. This license can also be found in the LICENSE + * file. + */ + +#define _GNU_SOURCE +#include "paths.h" + +#include +#include +#include +#include +#include +#include +#include + +// A name and file descriptor pair. +typedef struct preopen { + // The path prefix associated with the file descriptor. + const char* prefix; + + // The file descriptor. + __wasi_fd_t fd; +} preopen; + +// A simple growable array of `preopen`. +static preopen* preopens; +static size_t num_preopens; + +// Are the `prefix_len` bytes pointed to by `prefix` a prefix of `path`? +static bool +prefix_matches(const char* prefix, size_t prefix_len, const char* path) { + // Allow an empty string as a prefix of any relative path. + if (path[0] != '/' && prefix_len == 0) { + return true; + } + + // Check whether any bytes of the prefix differ. + if (memcmp(path, prefix, prefix_len) != 0) { + return false; + } + + // Ignore trailing slashes in directory names. + size_t i = prefix_len; + while (i > 0 && prefix[i - 1] == '/') { + --i; + } + + // Match only complete path components. + char last = path[i]; + return last == '/' || last == '\0'; +} + +bool __paths_resolve_path(int* resolved_dirfd, const char** path_ptr) { + const char* path = *path_ptr; + + if (*resolved_dirfd != AT_FDCWD && path[0] != '/') { + return true; + } + + // Strip leading `/` characters, the prefixes we're mataching won't have + // them. + while (*path == '/') { + path++; + } + // Search through the preopens table. Iterate in reverse so that more + // recently added preopens take precedence over less recently addded ones. + size_t match_len = 0; + int fd = -1; + for (size_t i = num_preopens; i > 0; --i) { + const preopen* pre = &preopens[i - 1]; + const char* prefix = pre->prefix; + size_t len = strlen(prefix); + + // If we haven't had a match yet, or the candidate path is longer than + // our current best match's path, and the candidate path is a prefix of + // the requested path, take that as the new best path. + if ((fd == -1 || len > match_len) && prefix_matches(prefix, len, path)) { + fd = pre->fd; + match_len = len; + } + } + + if (fd == -1) { + return false; + } + + // The relative path is the substring after the portion that was matched. + const char* computed = path + match_len; + + // Omit leading slashes in the relative path. + while (*computed == '/') { + ++computed; + } + + // *at syscalls don't accept empty relative paths, so use "." instead. + if (*computed == '\0') { + computed = "."; + } + + *resolved_dirfd = fd; + *path_ptr = computed; + return true; +} + +#if defined(EMSCRIPTEN_PURE_WASI) + +static size_t preopen_capacity; + +#ifdef NDEBUG +#define assert_invariants() // assertions disabled +#else +static void assert_invariants(void) { + assert(num_preopens <= preopen_capacity); + assert(preopen_capacity == 0 || preopens != NULL); + assert(preopen_capacity == 0 || + preopen_capacity * sizeof(preopen) > preopen_capacity); + + for (size_t i = 0; i < num_preopens; ++i) { + const preopen* pre = &preopens[i]; + assert(pre->prefix != NULL); + assert(pre->fd != (__wasi_fd_t)-1); +#ifdef __wasm__ + assert((uintptr_t)pre->prefix < + (__uint128_t)__builtin_wasm_memory_size(0) * PAGESIZE); +#endif + } +} +#endif + +// Allocate space for more preopens. Returns 0 on success and -1 on failure. +static bool resize_preopens(void) { + size_t start_capacity = 4; + size_t old_capacity = preopen_capacity; + size_t new_capacity = old_capacity == 0 ? start_capacity : old_capacity * 2; + + preopen* old_preopens = preopens; + preopen* new_preopens = calloc(sizeof(preopen), new_capacity); + if (new_preopens == NULL) { + return false; + } + + memcpy(new_preopens, old_preopens, num_preopens * sizeof(preopen)); + preopens = new_preopens; + preopen_capacity = new_capacity; + free(old_preopens); + + assert_invariants(); + return true; +} + +// Normalize an absolute path. Removes leading `/` and leading `./`, so the +// first character is the start of a directory name. This works because our +// process always starts with a working directory of `/`. Additionally translate +// `.` to the empty string. +static const char* strip_prefixes(const char* path) { + while (1) { + if (path[0] == '/') { + path++; + } else if (path[0] == '.' && path[1] == '/') { + path += 2; + } else if (path[0] == '.' && path[1] == 0) { + path++; + } else { + break; + } + } + + return path; +} + +// Register the given preopened file descriptor under the given path. +// +// This function takes ownership of `prefix`. +static bool register_preopened_fd(__wasi_fd_t fd, const char* relprefix) { + // Check preconditions. + assert_invariants(); + assert(fd != AT_FDCWD); + assert(fd != -1); + assert(relprefix != NULL); + + if (num_preopens == preopen_capacity && !resize_preopens()) { + return false; + } + + char* prefix = strdup(strip_prefixes(relprefix)); + if (prefix == NULL) { + return false; + } + preopens[num_preopens++] = (preopen){ + prefix, + fd, + }; + + assert_invariants(); + return true; +} + +// Populate WASI preopens. +__attribute__((constructor(100))) // construct this before user code +static void _standalone_populate_preopens(void) { + // Skip stdin, stdout, and stderr, and count up until we reach an invalid + // file descriptor. + for (__wasi_fd_t fd = 3; fd != 0; ++fd) { + __wasi_prestat_t prestat; + __wasi_errno_t ret = __wasi_fd_prestat_get(fd, &prestat); + if (ret == __WASI_ERRNO_BADF) { + break; + } + if (ret != __WASI_ERRNO_SUCCESS) { + goto oserr; + } + switch (prestat.pr_type) { + case __WASI_PREOPENTYPE_DIR: { + char* prefix = malloc(prestat.u.dir.pr_name_len + 1); + if (prefix == NULL) { + goto software; + } + + // TODO: Remove the cast on `path` once the witx is updated with + // char8 support. + ret = __wasi_fd_prestat_dir_name( + fd, (uint8_t*)prefix, prestat.u.dir.pr_name_len); + if (ret != __WASI_ERRNO_SUCCESS) { + goto oserr; + } + prefix[prestat.u.dir.pr_name_len] = '\0'; + + if (!register_preopened_fd(fd, prefix)) { + goto software; + } + free(prefix); + + break; + } + default: + break; + } + } + + return; +oserr: + _Exit(EX_OSERR); +software: + _Exit(EX_SOFTWARE); +} + +#endif diff --git a/system/lib/standalone/paths.h b/system/lib/standalone/paths.h new file mode 100644 index 0000000000000..3e62d2fae1ec0 --- /dev/null +++ b/system/lib/standalone/paths.h @@ -0,0 +1,25 @@ +/* + * Copyright 2025 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#pragma once + +#include + +// +// Resolve a (dirfd, relative/absolute path) pair. +// +// Arguments: +// - `resolved_dirfd`: +// - as input: input dirfd, may be `AT_FDCWD` +// - as output: resolved dirfd (which always is a preopened fd) +// - `path_ptr`: +// - as input: pointer to a relative or absolute path +// - as output: a path relative to `resolved_dirfd` +// +// Returns: `true` if resolution was successful, `false` otherwise. +// +bool __paths_resolve_path(int* resolved_dirfd, const char** path_ptr); diff --git a/system/lib/standalone/standalone.c b/system/lib/standalone/standalone.c index 3a910ba662d62..e5b2a685c6827 100644 --- a/system/lib/standalone/standalone.c +++ b/system/lib/standalone/standalone.c @@ -12,10 +12,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -25,6 +27,7 @@ #include "lock.h" #include "emscripten_internal.h" +#include "paths.h" /* * WASI support code. These are compiled with the program, and call out @@ -45,6 +48,42 @@ _Static_assert(CLOCK_MONOTONIC == __WASI_CLOCKID_MONOTONIC, "must match"); _Static_assert(CLOCK_PROCESS_CPUTIME_ID == __WASI_CLOCKID_PROCESS_CPUTIME_ID, "must match"); _Static_assert(CLOCK_THREAD_CPUTIME_ID == __WASI_CLOCKID_THREAD_CPUTIME_ID, "must match"); +static void wasi_filestat_to_stat(const __wasi_filestat_t* in, + struct stat* out) { + *out = (struct stat){ + .st_dev = in->dev, + .st_ino = in->ino, + .st_nlink = in->nlink, + .st_size = in->size, + .st_atim = __wasi_timestamp_to_timespec(in->atim), + .st_mtim = __wasi_timestamp_to_timespec(in->mtim), + .st_ctim = __wasi_timestamp_to_timespec(in->ctim), + }; + + // Convert file type to legacy types encoded in st_mode. + switch (in->filetype) { + case __WASI_FILETYPE_BLOCK_DEVICE: + out->st_mode |= S_IFBLK; + break; + case __WASI_FILETYPE_CHARACTER_DEVICE: + out->st_mode |= S_IFCHR; + break; + case __WASI_FILETYPE_DIRECTORY: + out->st_mode |= S_IFDIR; + break; + case __WASI_FILETYPE_REGULAR_FILE: + out->st_mode |= S_IFREG; + break; + case __WASI_FILETYPE_SOCKET_DGRAM: + case __WASI_FILETYPE_SOCKET_STREAM: + out->st_mode |= S_IFSOCK; + break; + case __WASI_FILETYPE_SYMBOLIC_LINK: + out->st_mode |= S_IFLNK; + break; + } +} + // mmap support is nonexistent. TODO: emulate simple mmaps using // stdio + malloc, which is slow but may help some things? @@ -65,21 +104,106 @@ weak int _munmap_js( return -ENOSYS; } -// open(), etc. - we just support the standard streams, with no -// corner case error checking; everything else is not permitted. -// TODO: full file support for WASI, or an option for it -// open() weak int __syscall_openat(int dirfd, intptr_t path, int flags, ...) { - if (!strcmp((const char*)path, "/dev/stdin")) { + const char* resolved_path = (const char*)path; + + if (!strcmp(resolved_path, "/dev/stdin")) { return STDIN_FILENO; } - if (!strcmp((const char*)path, "/dev/stdout")) { + if (!strcmp(resolved_path, "/dev/stdout")) { return STDOUT_FILENO; } - if (!strcmp((const char*)path, "/dev/stderr")) { + if (!strcmp(resolved_path, "/dev/stderr")) { return STDERR_FILENO; } - return -EPERM; + + if (!__paths_resolve_path(&dirfd, &resolved_path)) { + return -ENOENT; + } + + // Compute rights corresponding with the access modes provided. + // Attempt to obtain all rights, except the ones that contradict the + // access mode provided to openat(). + __wasi_rights_t max = + ~(__WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_READ | + __WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_FD_ALLOCATE | + __WASI_RIGHTS_FD_READDIR | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE); + { + int accmode = flags & O_ACCMODE; + if (accmode == O_RDONLY || accmode == O_RDWR || accmode == O_WRONLY) { + if (accmode == O_RDONLY || accmode == O_RDWR) { + max |= __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR; + } + if (accmode == O_WRONLY || accmode == O_RDWR) { + max |= __WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_WRITE | + __WASI_RIGHTS_FD_ALLOCATE | + __WASI_RIGHTS_FD_FILESTAT_SET_SIZE; + } + } else if (accmode == O_EXEC || accmode == O_SEARCH) { + // Do nothing. + } else { + return -EINVAL; + } + } + + // Ensure that we can actually obtain the minimal rights needed. + __wasi_fdstat_t fsb_cur; + __wasi_errno_t error = __wasi_fd_fdstat_get(dirfd, &fsb_cur); + if (error != __WASI_ERRNO_SUCCESS) { + return -error; + } + + // Path lookup properties. + __wasi_lookupflags_t lookup_flags = 0; + if ((flags & O_NOFOLLOW) == 0) { + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + } + + // Open file with appropriate rights. + __wasi_fdflags_t fs_flags = 0; + if (flags & O_APPEND) { + fs_flags |= __WASI_FDFLAGS_APPEND; + } + if (flags & O_DSYNC) { + fs_flags |= __WASI_FDFLAGS_DSYNC; + } + if (flags & O_NONBLOCK) { + fs_flags |= __WASI_FDFLAGS_NONBLOCK; + } + if (flags & O_RSYNC) { + fs_flags |= __WASI_FDFLAGS_RSYNC; + } + if (flags & O_SYNC) { + fs_flags |= __WASI_FDFLAGS_SYNC; + } + + __wasi_oflags_t oflags = 0; + if (flags & O_CREAT) { + oflags |= __WASI_OFLAGS_CREAT; + } + if (flags & O_DIRECTORY) { + oflags |= __WASI_OFLAGS_DIRECTORY; + } + if (flags & O_EXCL) { + oflags |= __WASI_OFLAGS_EXCL; + } + if (flags & O_TRUNC) { + oflags |= __WASI_OFLAGS_TRUNC; + } + + __wasi_rights_t fs_rights_base = max & fsb_cur.fs_rights_inheriting; + __wasi_rights_t fs_rights_inheriting = fsb_cur.fs_rights_inheriting; + __wasi_fd_t newfd; + + error = __wasi_path_open(dirfd, lookup_flags, resolved_path, strlen(resolved_path), + oflags, + fs_rights_base, fs_rights_inheriting, fs_flags, + &newfd); + if (error != __WASI_ERRNO_SUCCESS) { + return -error; + } + + return newfd; } weak int __syscall_ioctl(int fd, int op, ...) { @@ -95,7 +219,7 @@ weak int __syscall_fstat64(int fd, intptr_t buf) { } weak int __syscall_stat64(intptr_t path, intptr_t buf) { - return -ENOSYS; + return __syscall_newfstatat(AT_FDCWD, path, buf, 0); } weak int __syscall_dup(int fd) { @@ -103,15 +227,66 @@ weak int __syscall_dup(int fd) { } weak int __syscall_mkdirat(int dirfd, intptr_t path, int mode) { - return -ENOSYS; + const char* resolved_path = (const char*)path; + + if (!__paths_resolve_path(&dirfd, &resolved_path)) { + return -ENOENT; + } + + __wasi_errno_t error = __wasi_path_create_directory(dirfd, resolved_path, strlen(resolved_path)); + if (error != __WASI_ERRNO_SUCCESS) { + return -error; + } + return 0; } weak int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags) { - return -ENOSYS; + // Convert flags to WASI. + __wasi_lookupflags_t lookup_flags = 0; + if ((flags & AT_SYMLINK_NOFOLLOW) == 0) { + lookup_flags |= __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + } + + const char* resolved_path = (const char*)path; + + if (!__paths_resolve_path(&dirfd, &resolved_path)) { + return -ENOENT; + } + + __wasi_filestat_t fsb_cur; + __wasi_errno_t error = __wasi_path_filestat_get( + dirfd, lookup_flags, resolved_path, strlen(resolved_path), &fsb_cur); + if (error != __WASI_ERRNO_SUCCESS) { + return -error; + } + + wasi_filestat_to_stat(&fsb_cur, (struct stat*)buf); + + return 0; } weak int __syscall_lstat64(intptr_t path, intptr_t buf) { - return -ENOSYS; + return __syscall_newfstatat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW); +} + +weak int __syscall_symlinkat(intptr_t target_arg, int newdirfd, intptr_t linkpath) { + const char* resolved_linkpath = (const char*)linkpath; + + if (!__paths_resolve_path(&newdirfd, &resolved_linkpath)) { + return -ENOENT; + } + + const char* target = (const char*)target_arg; + + __wasi_errno_t error = __wasi_path_symlink(target, + strlen(target), + newdirfd, + resolved_linkpath, + strlen(resolved_linkpath)); + if (error != __WASI_ERRNO_SUCCESS) { + return -error; + } + return 0; } // Emscripten additions diff --git a/test/common.py b/test/common.py index beb266d2d0021..81eeadff96920 100644 --- a/test/common.py +++ b/test/common.py @@ -614,7 +614,10 @@ def can_do_standalone(self, impure=False): # Impure means a test that cannot run in a wasm VM yet, as it is not 100% # standalone. We can still run them with the JS code though. -def also_with_standalone_wasm(impure=False): +# "exclude_engines" is a list of engine names on which the test cannot run. +# These are pattern that are matched against the basename of the engine +# executable. +def also_with_standalone_wasm(impure=False, exclude_engines=[]): # noqa: B006 def decorated(func): @wraps(func) def metafunc(self, standalone): @@ -633,11 +636,22 @@ def metafunc(self, standalone): # when it sees an i64 on the ffi. self.set_setting('WASM_BIGINT') self.emcc_args.append('-Wno-unused-command-line-argument') + wasm_engines = [] # if we are impure, disallow all wasm engines - if impure: - self.wasm_engines = [] - nodejs = self.require_node() - self.node_args += shared.node_bigint_flags(nodejs) + if not impure: + for engine in self.wasm_engines: + basename = os.path.basename(engine[0]) + if not any(pattern in basename for pattern in exclude_engines): + wasm_engines.append(engine) + if 'node' in exclude_engines: + self.js_engines = [] + if not wasm_engines: + self.skipTest('no WASM engines available for this test') + else: + nodejs = self.require_node() + self.node_args += shared.node_bigint_flags(nodejs) + # `self.require_node()` clears `self.wasm_engines`, so set them here. + self.wasm_engines = wasm_engines func(self) parameterize(metafunc, {'': (False,), @@ -1505,7 +1519,8 @@ def cleanup(line): def run_js(self, filename, engine=None, args=None, assert_returncode=0, interleaved_output=True, - input=None): + input=None, + cwd=None): # use files, as PIPE can get too full and hang us stdout_file = self.in_dir('stdout') stderr_file = None @@ -1529,6 +1544,7 @@ def run_js(self, filename, engine=None, args=None, jsrun.run_js(filename, engine, args, stdout=stdout, stderr=stderr, + cwd=cwd, assert_returncode=assert_returncode, input=input) except subprocess.TimeoutExpired as e: @@ -1957,7 +1973,7 @@ def _build_and_run(self, filename, expected_output, args=None, js_file = self.build(filename, **kwargs) self.assertExists(js_file) - engines = self.js_engines.copy() + engines = [(el, None) for el in self.js_engines.copy()] if len(engines) > 1 and not self.use_all_engines: engines = engines[:1] # In standalone mode, also add wasm vms as we should be able to run there too. @@ -1966,11 +1982,15 @@ def _build_and_run(self, filename, expected_output, args=None, # like with js engines, but for now as we bring it up, test in all of them if not self.wasm_engines: logger.warning('no wasm engine was found to run the standalone part of this test') - engines += self.wasm_engines + engines += [(engine, os.path.basename(engine[0])) for engine in self.wasm_engines] if len(engines) == 0: self.fail('No JS engine present to run this test with. Check %s and the paths therein.' % config.EM_CONFIG) - for engine in engines: + for engine, subdir in engines: + if subdir: + subdir = self.in_dir(subdir) + ensure_dir(subdir) js_output = self.run_js(js_file, engine, args, + cwd=subdir, assert_returncode=assert_returncode, interleaved_output=interleaved_output) js_output = js_output.replace('\r\n', '\n') diff --git a/test/jsrun.py b/test/jsrun.py index d7a9cf817922e..e08d7e2219038 100644 --- a/test/jsrun.py +++ b/test/jsrun.py @@ -40,10 +40,15 @@ def make_command(filename, engine, args=None): is_jsc = 'jsc' in jsengine or 'javascriptcore' in jsengine is_wasmer = 'wasmer' in jsengine is_wasmtime = 'wasmtime' in jsengine + is_toywasm = 'toywasm' in jsengine command_flags = [] if is_wasmer: command_flags += ['run'] - if is_wasmer or is_wasmtime: + elif is_wasmtime: + command_flags += ['--dir', '.', '--'] + elif is_toywasm: + command_flags += ['--wasi', '--wasi-dir', '.', '--'] + if is_wasmer or is_wasmtime or is_toywasm: # in a wasm runtime, run the wasm, not the js filename = shared.replace_suffix(filename, '.wasm') # Separates engine flags from script flags diff --git a/test/test_core.py b/test/test_core.py index fb1ed5663e2df..758ba2f63f37c 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -5582,6 +5582,7 @@ def test_fcntl(self): @crossplatform @also_with_nodefs_both + @also_with_standalone_wasm(exclude_engines=['node', 'wasmer']) def test_fcntl_open(self): nodefs = '-DNODEFS' in self.emcc_args or '-DNODERAWFS' in self.emcc_args if nodefs and WINDOWS: diff --git a/test/test_other.py b/test/test_other.py index ffc83109ff681..dd00f6e6c559e 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -6447,7 +6447,8 @@ def test_force_stdlibs(self): # See https://github.com/emscripten-core/emscripten/issues/22161 self.do_runf('hello_world.c', emcc_args=['-sWASM_BIGINT']) - @also_with_standalone_wasm() + # `wasmtime` hangs + @also_with_standalone_wasm(exclude_engines=['wasmtime']) def test_time(self): self.do_other_test('test_time.c') @@ -15404,7 +15405,8 @@ def test_standalone_whole_archive(self): def test_proxy_to_worker(self, args): self.do_runf('hello_world.c', emcc_args=['--proxy-to-worker'] + args) - @also_with_standalone_wasm() + # functions from `emscripten/console.h` only work with node + @also_with_standalone_wasm(impure=True) def test_console_out(self): self.do_other_test('test_console_out.c', regex=True) diff --git a/tools/building.py b/tools/building.py index 9d0de19a05bb2..840cf61f8929a 100644 --- a/tools/building.py +++ b/tools/building.py @@ -810,6 +810,9 @@ def metadce(js_file, wasm_file, debug_info, last): 'clock_time_get', 'path_open', 'random_get', + 'path_filestat_get', + 'path_create_directory', + 'path_symlink', } for item in graph: if 'import' in item and item['import'][1] in WASI_IMPORTS: diff --git a/tools/maint/gen_sig_info.py b/tools/maint/gen_sig_info.py index d8890da052749..a0e66c9af7a26 100755 --- a/tools/maint/gen_sig_info.py +++ b/tools/maint/gen_sig_info.py @@ -162,6 +162,9 @@ 'args_get', 'args_sizes_get', 'random_get', + 'path_filestat_get', + 'path_create_directory', + 'path_symlink', } diff --git a/tools/system_libs.py b/tools/system_libs.py index d82ab077bb3a2..6faaccf01de92 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -2217,6 +2217,7 @@ def get_files(self): path='system/lib/standalone', filenames=['standalone.c', 'standalone_wasm_stdio.c', + 'paths.c', '__main_void.c']) # It is more efficient to use JS methods for time, normally. files += files_in_path(