diff --git a/.github/workflows/ports_unix.yml b/.github/workflows/ports_unix.yml index 662121654e4f7..7895db4773d45 100644 --- a/.github/workflows/ports_unix.yml +++ b/.github/workflows/ports_unix.yml @@ -282,3 +282,17 @@ jobs: - name: Print failures if: failure() run: tests/run-tests.py --print-failures + + wasi: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install packages + run: source tools/ci.sh && ci_unix_wasi_setup + - name: Build + run: source tools/ci.sh && ci_unix_wasi_build + - name: Run main test suite + run: source tools/ci.sh && ci_unix_wasi_run_tests + - name: Print failures + if: failure() + run: tests/run-tests.py --print-failures diff --git a/ports/unix/Makefile b/ports/unix/Makefile index 3c54d156c31ba..f5edd8d990ff0 100644 --- a/ports/unix/Makefile +++ b/ports/unix/Makefile @@ -31,7 +31,11 @@ QSTR_DEFS += qstrdefsport.h QSTR_GLOBAL_DEPENDENCIES += $(VARIANT_DIR)/mpconfigvariant.h # OS name, for simple autoconfig +ifeq ($(VARIANT),wasi) +UNAME_S := wasi +else UNAME_S := $(shell uname -s) +endif # include py core make definitions include $(TOP)/py/py.mk @@ -107,7 +111,7 @@ CC = clang endif # Use clang syntax for map file LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip -else +else ifeq ($(UNAME_S),Linux) # Use gcc syntax for map file LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections endif @@ -303,6 +307,21 @@ $(BUILD)/lib/libffi/include/ffi.h: $(TOP)/lib/libffi/configure PREFIX = /usr/local BINDIR = $(DESTDIR)$(PREFIX)/bin +ifeq ($(VARIANT),wasi) +# LLVM still uses the older version of EH proposal. ("phase 3") +# Convert to the latest version of EH proposal with exnref. +$(BUILD)/$(PROG).spilled.exnref: $(BUILD)/$(PROG).spilled + $(WASM_OPT) --translate-to-exnref --enable-exception-handling \ + -o $(BUILD)/$(PROG).spilled.exnref $(BUILD)/$(PROG).spilled + +# Unlike emscripten, WASI doesn't provide a way to scan GC roots +# like WASM locals. Hopefully --spill-pointers can workaround it. +$(BUILD)/$(PROG).spilled: $(BUILD)/$(PROG) + $(WASM_OPT) --spill-pointers -o $(BUILD)/$(PROG).spilled $(BUILD)/$(PROG) + +all: $(BUILD)/$(PROG).spilled.exnref +endif + install: $(BUILD)/$(PROG) install -d $(BINDIR) install $(BUILD)/$(PROG) $(BINDIR)/$(PROG) diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 21ce75a351e98..44b3f74c8c5ee 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -155,7 +155,9 @@ typedef long mp_off_t; #define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (0) // Enable sys.executable. +#ifndef MICROPY_PY_SYS_EXECUTABLE #define MICROPY_PY_SYS_EXECUTABLE (1) +#endif #define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) diff --git a/ports/unix/unix_mphal.c b/ports/unix/unix_mphal.c index 5f5abc2e056f6..7af646888f505 100644 --- a/ports/unix/unix_mphal.c +++ b/ports/unix/unix_mphal.c @@ -43,7 +43,13 @@ #endif #endif -#ifndef _WIN32 +#if defined(_WIN32) || defined(__wasi__) +#define HAS_SIGNAL 0 +#else +#define HAS_SIGNAL 1 +#endif + +#if HAS_SIGNAL #include static void sighandler(int signum) { @@ -75,7 +81,7 @@ static void sighandler(int signum) { void mp_hal_set_interrupt_char(char c) { // configure terminal settings to (not) let ctrl-C through if (c == CHAR_CTRL_C) { - #ifndef _WIN32 + #if HAS_SIGNAL // enable signal handler struct sigaction sa; sa.sa_flags = 0; @@ -84,7 +90,7 @@ void mp_hal_set_interrupt_char(char c) { sigaction(SIGINT, &sa, NULL); #endif } else { - #ifndef _WIN32 + #if HAS_SIGNAL // disable signal handler struct sigaction sa; sa.sa_flags = 0; diff --git a/ports/unix/variants/mpconfigvariant_common.h b/ports/unix/variants/mpconfigvariant_common.h index 9eeed8797366c..24f9f4d4a59b2 100644 --- a/ports/unix/variants/mpconfigvariant_common.h +++ b/ports/unix/variants/mpconfigvariant_common.h @@ -95,7 +95,9 @@ #define MICROPY_PY_OS_INCLUDEFILE "ports/unix/modos.c" #define MICROPY_PY_OS_ERRNO (1) #define MICROPY_PY_OS_GETENV_PUTENV_UNSETENV (1) +#ifndef MICROPY_PY_OS_SYSTEM #define MICROPY_PY_OS_SYSTEM (1) +#endif #define MICROPY_PY_OS_URANDOM (1) // Enable the unix-specific "time" module. @@ -111,11 +113,15 @@ #endif // The "select" module is enabled by default, but disable select.select(). +#ifndef MICROPY_PY_SELECT_POSIX_OPTIMISATIONS #define MICROPY_PY_SELECT_POSIX_OPTIMISATIONS (1) +#endif #define MICROPY_PY_SELECT_SELECT (0) // Enable the "websocket" module. +#ifndef MICROPY_PY_WEBSOCKET #define MICROPY_PY_WEBSOCKET (1) +#endif // Enable the "machine" module, mostly for machine.mem*. #define MICROPY_PY_MACHINE (1) diff --git a/ports/unix/variants/wasi/mpconfigvariant.h b/ports/unix/variants/wasi/mpconfigvariant.h new file mode 100644 index 0000000000000..390926d5a27e9 --- /dev/null +++ b/ports/unix/variants/wasi/mpconfigvariant.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * 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. + */ + +// Set base feature level. +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) + +// WASI has different values for POLL constants. +#define MICROPY_PY_SELECT_POSIX_OPTIMISATIONS (0) + +// WASI doesn't have executable or process. +#define MICROPY_PY_OS_SYSTEM (0) +#define MICROPY_PY_SYS_EXECUTABLE (0) + +// Network support is limited in WASI. +// WebSocket isn't too useful without network. +#define MICROPY_PY_WEBSOCKET (0) + +// Enable extra Unix features. +#include "../mpconfigvariant_common.h" diff --git a/ports/unix/variants/wasi/mpconfigvariant.mk b/ports/unix/variants/wasi/mpconfigvariant.mk new file mode 100644 index 0000000000000..b4634ddb41707 --- /dev/null +++ b/ports/unix/variants/wasi/mpconfigvariant.mk @@ -0,0 +1,47 @@ +# prerequisites +# +# WASI_SDK: wasi-sdk 25.0 or later +# WASM_OPT: binaryen wasm-opt version_117 or later + +# Note: +# we specify the target "--target=wasm32-wasi" explicitly below for the case +# where $CLANG is built with a configuration different from wasi-sdk. +# ditto for "-B $(WASI_SDK)/bin/". + +WASI_SDK = /opt/wasi-sdk-25.0 +WASI_SYSROOT = $(WASI_SDK)/share/wasi-sysroot +WASM_OPT = wasm-opt +RESOURCE_DIR = $(shell $(WASI_SDK)/bin/clang --print-resource-dir) +CLANG = $(WASI_SDK)/bin/clang + +CC = $(CLANG) --sysroot $(WASI_SYSROOT) -resource-dir $(RESOURCE_DIR) +STRIP = $(WASI_SDK)/bin/strip +SIZE = $(WASI_SDK)/bin/size + +CFLAGS += --target=wasm32-wasi -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -mllvm -wasm-enable-sjlj +LDFLAGS += --target=wasm32-wasi -lwasi-emulated-process-clocks -lwasi-emulated-signal -lwasi-emulated-mman -lsetjmp -B $(WASI_SDK)/bin/ + +# WASI doesn't have FFI +MICROPY_PY_FFI = 0 + +# When threading is enabled, micropython GC uses signals, which is +# not available on WASI. +MICROPY_PY_THREAD = 0 + +# Disable for now because network support is limited in WASI. +MICROPY_PY_SOCKET = 0 +MICROPY_PY_SSL = 0 + +# ../../lib/berkeley-db-1.xx/PORT/include/db.h:40:10: +# fatal error: 'sys/cdefs.h' file not found +MICROPY_PY_BTREE = 0 + +# WASI doesn't have termios +MICROPY_PY_TERMIOS = 0 +MICROPY_USE_READLINE = 0 + +# The following things might just work as they are. +# Disabled for now because I'm not interested in them. +MICROPY_VFS_FAT = 0 +MICROPY_VFS_LFS1 = 0 +MICROPY_VFS_LFS2 = 0 diff --git a/tests/run-tests.py b/tests/run-tests.py index 628fde9d30a4c..68edf43b5f27d 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -447,8 +447,9 @@ def send_get(what): cmdlist = [os.path.abspath(MICROPYTHON), "-X", "emit=" + args.emit] if args.heapsize is not None: cmdlist.extend(["-X", "heapsize=" + args.heapsize]) - if sys.platform == "darwin": - cmdlist.extend(["-X", "realtime"]) + if os.getenv("MICROPY_MICROPYTHON_WASM") is None: + if sys.platform == "darwin": + cmdlist.extend(["-X", "realtime"]) cwd = os.path.dirname(test_file) diff --git a/tools/ci.sh b/tools/ci.sh index b0e59509add83..2c19bf4efe4cf 100755 --- a/tools/ci.sh +++ b/tools/ci.sh @@ -852,6 +852,36 @@ function ci_unix_qemu_riscv64_run_tests { (cd tests && MICROPY_MICROPYTHON=../ports/unix/build-coverage/micropython ./run-tests.py) } +function ci_unix_wasi_setup { + wget https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-linux.tar.gz + zcat binaryen-version_123-x86_64-linux.tar.gz | tar x + wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-linux.tar.gz + zcat wasi-sdk-25.0-x86_64-linux.tar.gz | tar x + wget https://github.com/yamt/toywasm/releases/download/v68.0.0/toywasm-v68.0.0-full-ubuntu-22.04-amd64.tgz + mkdir toywasm + zcat toywasm-v68.0.0-full-ubuntu-22.04-amd64.tgz | tar -C toywasm -x +} + +function ci_unix_wasi_build { + make ${MAKEOPTS} -C ports/unix VARIANT=wasi \ + WASI_SDK=$(pwd -P)/wasi-sdk-25.0-x86_64-linux \ + WASM_OPT=$(pwd -P)/binaryen-version_123/bin/wasm-opt \ + submodules + make ${MAKEOPTS} -C ports/unix VARIANT=wasi \ + WASI_SDK=$(pwd -P)/wasi-sdk-25.0-x86_64-linux \ + WASM_OPT=$(pwd -P)/binaryen-version_123/bin/wasm-opt +} + +function ci_unix_wasi_run_tests { + # Note: for simplicity, use absolute path where possible + # because wasi doesn't have the concept of "current directory" natively. + (cd tests && \ + MICROPY_MICROPYTHON=$(pwd -P)/../tools/run-with-toywasm.sh \ + TOYWASM=$(pwd -P)/../toywasm/bin/toywasm \ + MICROPY_MICROPYTHON_WASM=$(pwd -P)/../ports/unix/build-wasi/micropython.spilled.exnref \ + ./run-tests.py -d $(pwd -P)/basics) +} + ######################################################################################## # ports/windows diff --git a/tools/run-with-toywasm.sh b/tools/run-with-toywasm.sh new file mode 100755 index 0000000000000..e1da14e1c4fac --- /dev/null +++ b/tools/run-with-toywasm.sh @@ -0,0 +1,10 @@ +#! /bin/sh + +if [ ! -e "${MICROPY_MICROPYTHON_WASM}" ]; then + echo "MICROPY_MICROPYTHON_WASM is not set properly" >&2 + exit 1 +fi + +exec ${TOYWASM:-toywasm} --wasi --wasi-dir=/ \ +${MICROPY_MICROPYTHON_WASM} \ +-- "$@"