From 842582434c95acc6757637152547c26e04200d4f Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sat, 5 Jul 2025 23:41:59 +0800 Subject: [PATCH 1/4] Enable System Emulation in Web Browsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-space emulation has been supported and deployable in WebAssembly since #389, but system emulation was not yet available. With #508, system emulation was introduced, and later #551 added support for trap-and-emulate of guest Linux SDL syscalls, enabling offloading to the host’s SDL backend. This commit bridges these components together to enable full system emulation in the browser. xterm.js is leveraged as the frontend terminal, bridged with the backend VM shell through a custom buffer management mechanism. This mechanism provides a shell experience in the browser that closely resembles a real terminal. To handle terminal input without blocking the browser’s cooperative multitasking model, the original approach of mapping the read() system call to window.prompt() is avoided. Blocking behavior would freeze the browser’s event loop and degrade responsiveness. Instead, input from xterm.js is stored in a shared input buffer. The rv32emu periodically checks this buffer when handling external interrupts, and if input is available, it is read and consumed by the guest OS shell. The SDL graphic and sound backend are also supported. After booting the guest Linux system, users can run graphical applications such as doom-riscv, quake, or smolnes. These programs can be exited using Ctrl+C or their built-in exit funtionality. To reduce the size of the WebAssembly build and for the sake of the modularity, the project is now separated into user and system targets. As a result, two dedicated HTML files and corresponding preload JavaScript files are introduced: - user.html with user-pre.js - system.html with system-pre.js Navigation buttons are added to the index pages of both user and system demos, allowing users to switch easily between the two modes. Note that these navigation buttons are only functional when both user and system demos are deployed together, otherwise, the target pages may not be reachable. To improve usability, logic is implemented to disable and enable the "Run" button at appropriate times, preventing accidental re-execution when the process is already running. Additional improvements: - Ensure xterm.js uses \r\n instead of \n when logging to correctly move the cursor to the beginning of the line. - Add a new src/emsc.h header to store Emscripten-related variables and function declarations for better management. This implementation has been tested on the latest versions of Chrome, Firefox, and Safari. To serve user space emulation index page: $ make start-web CC=emcc ENABLE_SDL=1 -j8 To serve system emulation index page: $ make start-web CC=emcc ENABLE_SYSTEM=1 ENABLE_SDL=1 INITRD_SIZE=32 -j8 --- Makefile | 2 +- assets/wasm/html/system.html | 315 +++++++++++++++++++++ assets/wasm/html/{index.html => user.html} | 17 +- assets/wasm/js/pre.js | 9 - assets/wasm/js/system-pre.js | 40 +++ assets/wasm/js/user-pre.js | 10 + mk/wasm.mk | 42 ++- src/devices/uart.c | 43 ++- src/emsc.h | 18 ++ src/emulate.c | 26 +- src/log.c | 4 + src/main.c | 17 ++ src/riscv.c | 10 +- 13 files changed, 518 insertions(+), 35 deletions(-) create mode 100644 assets/wasm/html/system.html rename assets/wasm/html/{index.html => user.html} (98%) delete mode 100644 assets/wasm/js/pre.js create mode 100644 assets/wasm/js/system-pre.js create mode 100644 assets/wasm/js/user-pre.js create mode 100644 src/emsc.h diff --git a/Makefile b/Makefile index 41c8324bb..36e113414 100644 --- a/Makefile +++ b/Makefile @@ -286,8 +286,8 @@ $(OUT)/emulate.o: CFLAGS += -foptimize-sibling-calls -fomit-frame-pointer -fno-s include mk/external.mk include mk/artifact.mk -include mk/wasm.mk include mk/system.mk +include mk/wasm.mk all: config $(BUILD_DTB) $(BUILD_DTB2C) $(BIN) diff --git a/assets/wasm/html/system.html b/assets/wasm/html/system.html new file mode 100644 index 000000000..3c9710a0d --- /dev/null +++ b/assets/wasm/html/system.html @@ -0,0 +1,315 @@ + + + + + + Emscripten-Generated Code + + + + + + + +
+
Downloading...
+ + + Resize canvas + Lock/hide mouse + pointer     + + + + + + + + +
+

Experience graphics and sounds(remember to turn on your speaker) by running doom-riscv, quake, smolnes.

+
+ +
+ +
+ +
+ +
+ +
+ + + + + + diff --git a/assets/wasm/html/index.html b/assets/wasm/html/user.html similarity index 98% rename from assets/wasm/html/index.html rename to assets/wasm/html/user.html index 5beae7e63..e2fdf6951 100644 --- a/assets/wasm/html/index.html +++ b/assets/wasm/html/user.html @@ -121,6 +121,10 @@ + +
@@ -137,6 +141,13 @@ var spinnerElement = document.getElementById('spinner'); var runButton = document.getElementById("runButton"); runButton.addEventListener("click", runButtonClickHandler); + var toSysEmuButton = document.getElementById("toSysEmuButton"); + toSysEmuButton.addEventListener("click", toSysEmuButtonClickHandler); + + function toSysEmuButtonClickHandler() { + console.log("Navigate to system emulation"); + window.location.href = "./system" + } var elfDropdown = document.getElementById("elfDropdown"); for (var i = 0; i < elfFiles.length; i++) { @@ -163,11 +174,7 @@ element.scrollTop = element.scrollHeight; } Module._indirect_rv_halt(); - /* important to add some delay for waiting cancellation of main loop before next run */ - /* Otherwise, get error: only one main loop can be existed */ - setTimeout(() => { - Module['onRuntimeInitialized'](target_elf); - }, 1000); + Module['run_user'](target_elf); } var Module = { diff --git a/assets/wasm/js/pre.js b/assets/wasm/js/pre.js deleted file mode 100644 index c541967ea..000000000 --- a/assets/wasm/js/pre.js +++ /dev/null @@ -1,9 +0,0 @@ -Module['noInitialRun'] = true; -Module['onRuntimeInitialized'] = function(target_elf) { - if(target_elf === undefined){ - console.warn("target elf executable is undefined"); - return; - } - - callMain([target_elf]); -}; diff --git a/assets/wasm/js/system-pre.js b/assets/wasm/js/system-pre.js new file mode 100644 index 000000000..5fca6f083 --- /dev/null +++ b/assets/wasm/js/system-pre.js @@ -0,0 +1,40 @@ +Module["noInitialRun"] = true; + +Module["run_system"] = function (cli_param) { + callMain(cli_param.split(" ")); +}; + +// index.html's preRun needs to access this, thus declaring as global +let term; + +Module["onRuntimeInitialized"] = function () { + const input_buf_ptr = Module._get_input_buf(); + const input_buf_cap = Module._get_input_buf_cap(); + + term = new Terminal({ + cols: 120, + rows: 11, + }); + term.open(document.getElementById("terminal")); + + term.onKey(({ key, domEvent }) => { + + let heap = new Uint8Array( + Module.HEAPU8.buffer, + input_buf_ptr, + key.length, + ); + + for (let i = 0; i < key.length && i < input_buf_cap; i++) { + heap[i] = key.charCodeAt(i); + } + // Fill zero + for (let i = key.length; i < input_buf_cap; i++) { + heap[i] = 0; + } + + Module._set_input_buf_size(key.length); + + term.scrollToBottom(); + }); +}; diff --git a/assets/wasm/js/user-pre.js b/assets/wasm/js/user-pre.js new file mode 100644 index 000000000..f0925222f --- /dev/null +++ b/assets/wasm/js/user-pre.js @@ -0,0 +1,10 @@ +Module["noInitialRun"] = true; + +Module["run_user"] = function (target_elf) { + if (target_elf === undefined) { + console.warn("target elf executable is undefined"); + return; + } + + callMain([target_elf]); +}; diff --git a/mk/wasm.mk b/mk/wasm.mk index cf4d2c9e9..89cd04fc4 100644 --- a/mk/wasm.mk +++ b/mk/wasm.mk @@ -3,7 +3,7 @@ deps_emcc := ASSETS := assets/wasm WEB_HTML_RESOURCES := $(ASSETS)/html WEB_JS_RESOURCES := $(ASSETS)/js -EXPORTED_FUNCS := _main,_indirect_rv_halt +EXPORTED_FUNCS := _main,_indirect_rv_halt,_get_input_buf,_get_input_buf_cap,_set_input_buf_size DEMO_DIR := demo WEB_FILES := $(BIN).js \ $(BIN).wasm \ @@ -29,7 +29,19 @@ CFLAGS_emcc += -sINITIAL_MEMORY=2GB \ -s"EXPORTED_FUNCTIONS=$(EXPORTED_FUNCS)" \ -sSTACK_SIZE=4MB \ -sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency \ - --embed-file build/jit-bf.elf@/jit-bf.elf \ + --embed-file build/timidity@/etc/timidity \ + -DMEM_SIZE=0x20000000 \ + -DCYCLE_PER_STEP=2000000 \ + -O3 \ + -w + +ifeq ($(call has, SYSTEM), 1) +CFLAGS_emcc += --embed-file build/linux-image/Image@Image \ + --embed-file build/linux-image/rootfs.cpio@rootfs.cpio \ + --embed-file build/minimal.dtb@/minimal.dtb \ + --pre-js $(WEB_JS_RESOURCES)/system-pre.js +else +CFLAGS_emcc += --embed-file build/jit-bf.elf@/jit-bf.elf \ --embed-file build/coro.elf@/coro.elf \ --embed-file build/fibonacci.elf@/fibonacci.elf \ --embed-file build/hello.elf@/hello.elf \ @@ -40,12 +52,9 @@ CFLAGS_emcc += -sINITIAL_MEMORY=2GB \ --embed-file build/riscv32@/riscv32 \ --embed-file build/DOOM1.WAD@/DOOM1.WAD \ --embed-file build/id1/pak0.pak@/id1/pak0.pak \ - --embed-file build/timidity@/etc/timidity \ - -DMEM_SIZE=0x60000000 \ - -DCYCLE_PER_STEP=2000000 \ - --pre-js $(WEB_JS_RESOURCES)/pre.js \ - -O3 \ - -w + --pre-js $(WEB_JS_RESOURCES)/user-pre.js +endif + $(OUT)/elf_list.js: tools/gen-elf-list-js.py $(Q)tools/gen-elf-list-js.py > $@ @@ -132,11 +141,22 @@ define cp-web-file endef # WEB_FILES could be cleaned and recompiled, thus do not mix these two files into WEB_FILES -STATIC_WEB_FILES := $(WEB_HTML_RESOURCES)/index.html \ - $(WEB_JS_RESOURCES)/coi-serviceworker.min.js +STATIC_WEB_FILES := $(WEB_JS_RESOURCES)/coi-serviceworker.min.js +ifeq ($(call has, SYSTEM), 1) +STATIC_WEB_FILES += $(WEB_HTML_RESOURCES)/system.html +else +STATIC_WEB_FILES += $(WEB_HTML_RESOURCES)/user.html +endif + +start_web_deps := check-demo-dir-exist $(BIN) +ifeq ($(call has, SYSTEM), 1) +start_web_deps += $(BUILD_DTB) $(BUILD_DTB2C) +endif -start-web: check-demo-dir-exist $(BIN) +start-web: $(start_web_deps) + $(Q)rm -f $(DEMO_DIR)/*.html $(foreach T, $(WEB_FILES), $(call cp-web-file, $(T))) $(foreach T, $(STATIC_WEB_FILES), $(call cp-web-file, $(T))) + $(Q)mv $(DEMO_DIR)/*.html $(DEMO_DIR)/index.html $(Q)python3 -m http.server --bind $(DEMO_IP) $(DEMO_PORT) --directory $(DEMO_DIR) endif diff --git a/src/devices/uart.c b/src/devices/uart.c index f554165f9..407c178e1 100644 --- a/src/devices/uart.c +++ b/src/devices/uart.c @@ -5,12 +5,17 @@ #include #include +#include #include #include #include #include #include +#if defined(__EMSCRIPTEN__) +#include "emsc.h" +#endif + #include "uart.h" /* Emulate 8250 (plain, without loopback mode support) */ @@ -33,15 +38,42 @@ void u8250_update_interrupts(u8250_state_t *uart) uart->current_intr = ilog2(uart->pending_intrs); } +#if defined(__EMSCRIPTEN__) +#define INPUT_BUF_MAX_CAP 16 +static char input_buf[INPUT_BUF_MAX_CAP]; +static uint8_t input_buf_start = 0; +uint8_t input_buf_size = 0; + +char *get_input_buf() +{ + return input_buf; +} + +uint8_t get_input_buf_cap() +{ + return INPUT_BUF_MAX_CAP; +} + +void set_input_buf_size(uint8_t size) +{ + input_buf_size = size; +} +#endif + void u8250_check_ready(u8250_state_t *uart) { if (uart->in_ready) return; +#if defined(__EMSCRIPTEN__) + if (input_buf_size) + uart->in_ready = true; +#else struct pollfd pfd = {uart->in_fd, POLLIN, 0}; poll(&pfd, 1, 0); if (pfd.revents & POLLIN) uart->in_ready = true; +#endif } static void u8250_handle_out(u8250_state_t *uart, uint8_t value) @@ -57,12 +89,19 @@ static uint8_t u8250_handle_in(u8250_state_t *uart) if (!uart->in_ready) return value; +#if defined(__EMSCRIPTEN__) + value = (uint8_t) input_buf[input_buf_start]; + input_buf_start++; + if (--input_buf_size == 0) + input_buf_start = 0; +#else if (read(uart->in_fd, &value, 1) < 0) rv_log_error("Failed to read UART input: %s", strerror(errno)); +#endif uart->in_ready = false; - u8250_check_ready(uart); - if (value == 1) { /* start of heading (Ctrl-a) */ + if (value == 1) { /* start of heading (Ctrl-a) */ + u8250_check_ready(uart); if (getchar() == 120) { /* keyboard x */ rv_log_info("RISC-V emulator is destroyed"); exit(EXIT_SUCCESS); diff --git a/src/emsc.h b/src/emsc.h new file mode 100644 index 000000000..2ea925ff0 --- /dev/null +++ b/src/emsc.h @@ -0,0 +1,18 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#pragma once + +#include + +void indirect_rv_halt(); + +#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) +extern uint8_t input_buf_size; + +char *get_input_buf(); +uint8_t get_input_buf_cap(); +void set_input_buf_size(uint8_t size); +#endif diff --git a/src/emulate.c b/src/emulate.c index ae8835e9e..9bc4bb373 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -10,10 +10,6 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif - #if RV32_HAS(EXT_F) #include #include "softfp.h" @@ -23,6 +19,19 @@ extern struct target_ops gdbstub_ops; #endif +#if defined(__EMSCRIPTEN__) +#include "emsc.h" +#if RV32_HAS(SYSTEM) +EM_JS(void, enable_run_button, (), { + document.getElementById('runSysButton').disabled = false; +}); +#else +EM_JS(void, enable_run_button, (), { + document.getElementById('runButton').disabled = false; +}); +#endif +#endif + #include "decode.h" #include "mpool.h" #include "riscv.h" @@ -1009,6 +1018,9 @@ static void rv_check_interrupt(riscv_t *rv) if (peripheral_update_ctr-- == 0) { peripheral_update_ctr = 64; +#if defined(__EMSCRIPTEN__) + escape_seq: +#endif u8250_check_ready(PRIV(rv)->uart); if (PRIV(rv)->uart->in_ready) emu_update_uart_interrupts(rv); @@ -1031,6 +1043,11 @@ static void rv_check_interrupt(riscv_t *rv) break; case (SUPERVISOR_EXTERNAL_INTR & 0xf): SET_CAUSE_AND_TVAL_THEN_TRAP(rv, SUPERVISOR_EXTERNAL_INTR, 0); +#if defined(__EMSCRIPTEN__) + /* escape sequence has more than 1 byte */ + if (input_buf_size) + goto escape_seq; +#endif break; default: break; @@ -1174,6 +1191,7 @@ void rv_step(void *arg) emscripten_cancel_main_loop(); rv_delete(rv); /* clean up and reuse memory */ rv_log_info("RISC-V emulator is destroyed"); + enable_run_button(); } #endif } diff --git a/src/log.c b/src/log.c index ad2746c6e..f534245e1 100644 --- a/src/log.c +++ b/src/log.c @@ -52,7 +52,11 @@ static void stdout_callback(log_event_t *ev) ev->file, ev->line); #endif /* RV32_HAS(LOG_COLOR) */ vfprintf(ev->udata, ev->fmt, ev->ap); +#if defined(__EMSCRIPTEN__) + fprintf(ev->udata, "\r\n"); +#else fprintf(ev->udata, "\n"); +#endif fflush(ev->udata); } diff --git a/src/main.c b/src/main.c index 8e366519c..0a3ca86cf 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,19 @@ #include #include +#if defined(__EMSCRIPTEN__) +#include "emsc.h" +#if RV32_HAS(SYSTEM) +EM_JS(void, disable_run_button, (), { + document.getElementById('runSysButton').disabled = true; +}); +#else +EM_JS(void, disable_run_button, (), { + document.getElementById('runButton').disabled = true; +}); +#endif +#endif + #include "elf.h" #include "riscv.h" #include "utils.h" @@ -286,6 +299,10 @@ int main(int argc, char **args) } rv_log_info("RISC-V emulator is created and ready to run"); +#if defined(__EMSCRIPTEN__) + disable_run_button(); +#endif + rv_run(rv); /* dump registers as JSON */ diff --git a/src/riscv.c b/src/riscv.c index 2a1e59f72..66c302835 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -27,8 +27,8 @@ #define STDERR_FILENO FILENO(stderr) #endif -#ifdef __EMSCRIPTEN__ -#include +#if defined(__EMSCRIPTEN__) +#include "emsc.h" #endif #include "elf.h" @@ -234,7 +234,11 @@ static void map_file(char **ram_loc, const char *name) struct stat st; fstat(fd, &st); -#if HAVE_MMAP +/* EMSCRIPTEN: We don't currently support location hints for the address of the + * mapping */ +/* https://github.com/emscripten-core/emscripten/blob/52bc455316b2f44d3a94104776a335a5861ad73b/system/lib/libc/emscripten_mmap.c#L105 + */ +#if HAVE_MMAP && !defined(__EMSCRIPTEN__) /* remap to a memory region */ *ram_loc = mmap(*ram_loc, st.st_size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, fd, 0); From 67d97286423a22d569bc33dab56ef62efd0c0e23 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 6 Jul 2025 00:01:56 +0800 Subject: [PATCH 2/4] CI: Update for deploying user-wasm and system-wasm This commit updates the CI to deploy both user and system WebAssembly demos to the rv32emu-demo repository, resulting in the following file structure: . |-- coi-serviceworker.min.js |-- elf_list.js |-- index.html |-- rv32emu.js |-- rv32emu.wasm |-- rv32emu.worker.js |-- system |-- coi-serviceworker.min.js |-- index.html |-- rv32emu.js |-- rv32emu.wasm |-- rv32emu.worker.js The top-level files serve the user-space demo, while the system/ subdirectory hosts the system emulation demo. This structure allows both pages to coexist and be navigated independently in the same deployment. Improvements: - The release artifacts in the rv32emu-prebuilt repository include either a user-space executable (e.g., RISC-V ELF binaries) or a Linux image for system emulation. To distinguish between these two types of releases and trigger only the necessary deployment workflow, two separate dispatch event types are introduced: - deploy_user_wasm for user-space emulation WebAssembly deployment. - deploy_system_wasm for system emulation WebAssembly deployment. - Add needs and always() to ensure proper sequencing and execution of dependent jobs when both targets are deployed. - Change the source of the shareware Doom artifact: Downloading directly from the original site often results in 403 Forbidden errors on GitHub runners recently. The artifact is now hosted in our own repository (rv32emu-prebuilt) for more reliable access. Error: Resolving www.doomworld.com (www.doomworld.com)... 172.67.171.63, 104.21.29.17, 2606:4700:3037::ac43:ab3f, ... Connecting to www.doomworld.com (www.doomworld.com)|172.67.171.63|:443... connected. HTTP request sent, awaiting response... 403 Forbidden --- .github/workflows/deploy-wasm.yml | 132 +++++++++++++++++++++++++----- .github/workflows/main.yml | 16 +--- 2 files changed, 114 insertions(+), 34 deletions(-) diff --git a/.github/workflows/deploy-wasm.yml b/.github/workflows/deploy-wasm.yml index 682a71b92..5ee81973a 100644 --- a/.github/workflows/deploy-wasm.yml +++ b/.github/workflows/deploy-wasm.yml @@ -10,13 +10,107 @@ on: branches: - master repository_dispatch: # listening to rv32emu-prebuilt events - types: [deploy_wasm] + types: [deploy_user_wasm, deploy_system_wasm] jobs: - wasm-deploy: + wasm-system-deploy: if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' + github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm' + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: install-dependencies + run: | + sudo apt-get update -q=2 + sudo apt-get install -q=2 device-tree-compiler + - name: Verify if the JS or HTML files has been modified + id: changed-files + uses: tj-actions/changed-files@v46 + with: + files: | + assets/wasm/html/system.html + assets/wasm/js/system-pre.js + # Files below may have a potential performance impact (reference from benchmark.yml) + src/devices/*.c + src/system.c + src/riscv.c + src/decode.c + src/emulate.c + src/rv32_template.c + src/rv32_constopt.c + - name: install emcc + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + run: | + git clone https://github.com/emscripten-core/emsdk -b 3.1.51 + cd emsdk + ./emsdk install latest + ./emsdk activate latest + source ./emsdk_env.sh + echo "$PATH" >> $GITHUB_PATH + shell: bash + - name: fetch artifact + run: | + make artifact + # get from rv32emu-prebuilt + wget -O build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" + unzip -d build/ build/shareware_doom_iwad.zip + - name: build with emcc and move application files to /tmp + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + run: | + make CC=emcc ENABLE_SYSTEM=1 ENABLE_SDL=1 INITRD_SIZE=32 -j + mkdir /tmp/rv32emu-system-demo + mv assets/wasm/html/system.html /tmp/rv32emu-system-demo/index.html + mv assets/wasm/js/coi-serviceworker.min.js /tmp/rv32emu-system-demo + mv build/rv32emu.js /tmp/rv32emu-system-demo + mv build/rv32emu.wasm /tmp/rv32emu-system-demo + mv build/rv32emu.worker.js /tmp/rv32emu-system-demo + ls -al /tmp/rv32emu-system-demo + - name: Check out the rv32emu-system-demo repo + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + uses: actions/checkout@v4 + with: + persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. + repository: sysprog21/rv32emu-demo + - name: Create local changes + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + run: | + mkdir -p system + mv /tmp/rv32emu-system-demo/index.html ./system + mv /tmp/rv32emu-system-demo/coi-serviceworker.min.js ./system + mv /tmp/rv32emu-system-demo/rv32emu.js ./system + mv /tmp/rv32emu-system-demo/rv32emu.wasm ./system + mv /tmp/rv32emu-system-demo/rv32emu.worker.js ./system + - name: Commit files + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add system/ + git commit -m "Add changes to system emulation" + - name: Push changes + if: ${{ steps.changed-files.outputs.any_modified == 'true' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_system_wasm') }} + uses: ad-m/github-push-action@master + with: + repository: sysprog21/rv32emu-demo + github_token: ${{ secrets.RV32EMU_DEMO_TOKEN }} + branch: main + wasm-user-deploy: + needs: wasm-system-deploy # run jobs sequentially since two jobs operate on same reposity: rv32emu-demo + if: always() # ensures wasm-user-deploy runs regardless of the outcome or condition of wasm-system-deploy runs-on: ubuntu-latest steps: - name: Check out the repo @@ -26,8 +120,8 @@ jobs: uses: tj-actions/changed-files@v46 with: files: | - assets/wasm/html/index.html - assets/wasm/js/pre.js + assets/wasm/html/user.html + assets/wasm/js/user-pre.js build/*.elf tools/gen-elf-list-js.py # Files below may have a potential performance impact (reference from benchmark.yml) @@ -39,12 +133,10 @@ jobs: - name: install emcc if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} run: | - git clone https://github.com/emscripten-core/emsdk.git + git clone https://github.com/emscripten-core/emsdk -b 3.1.51 cd emsdk - git pull - git checkout 3.1.51 ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh @@ -53,21 +145,17 @@ jobs: - name: fetch artifact run: | make artifact - # Hack Cloudflare 403 Forbidden on GitHub Runner for Doom artifact download - wget --header="User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0" \ - --header="Referer: https://www.doomworld.com/" \ - --header="Accept-Language: en-US,en;q=0.9" \ - -O build/shareware_doom_iwad.zip \ - "https://www.doomworld.com/3ddownloads/ports/shareware_doom_iwad.zip" + # get from rv32emu-prebuilt + wget -O build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" unzip -d build/ build/shareware_doom_iwad.zip - name: build with emcc and move application files to /tmp if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} run: | make CC=emcc ENABLE_SDL=1 mkdir /tmp/rv32emu-demo - mv assets/wasm/html/index.html /tmp/rv32emu-demo + mv assets/wasm/html/user.html /tmp/rv32emu-demo/index.html mv assets/wasm/js/coi-serviceworker.min.js /tmp/rv32emu-demo mv build/elf_list.js /tmp/rv32emu-demo mv build/rv32emu.js /tmp/rv32emu-demo @@ -77,7 +165,7 @@ jobs: - name: Check out the rv32emu-demo repo if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} uses: actions/checkout@v4 with: persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal access token. @@ -85,7 +173,7 @@ jobs: - name: Create local changes if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} run: | mv /tmp/rv32emu-demo/index.html . mv /tmp/rv32emu-demo/coi-serviceworker.min.js . @@ -96,16 +184,16 @@ jobs: - name: Commit files if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} run: | git config --local user.email "github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git add --all - git commit -m "Add changes" + git commit -m "Add changes to user emulation" - name: Push changes if: ${{ steps.changed-files.outputs.any_modified == 'true' || github.event_name == 'workflow_dispatch' || - github.event_name == 'repository_dispatch' }} + (github.event_name == 'repository_dispatch' && github.event.action == 'deploy_user_wasm') }} uses: ad-m/github-push-action@master with: repository: sysprog21/rv32emu-demo diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 899fb4705..743c1447b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -80,12 +80,8 @@ jobs: make artifact make ENABLE_SYSTEM=1 artifact make ENABLE_ARCH_TEST=1 artifact - # Hack Cloudflare 403 Forbidden on GitHub Runner for Doom artifact download - wget --header="User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0" \ - --header="Referer: https://www.doomworld.com/" \ - --header="Accept-Language: en-US,en;q=0.9" \ - -O build/shareware_doom_iwad.zip \ - "https://www.doomworld.com/3ddownloads/ports/shareware_doom_iwad.zip" + # get from rv32emu-prebuilt + wget -O build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" unzip -d build/ build/shareware_doom_iwad.zip if: ${{ always() }} - name: default build using emcc @@ -394,12 +390,8 @@ jobs: | head -n 1 \ | sed -E 's/.*"tag_name": "([^"]+)".*/\1/') make LATEST_RELEASE=$LATEST_RELEASE ENABLE_ARCH_TEST=1 artifact - # Hack Cloudflare 403 Forbidden on GitHub Runner for Doom artifact download - wget --header="User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15" \ - --header="Referer: https://www.doomworld.com/" \ - --header="Accept-Language: en-US,en;q=0.9" \ - -O build/shareware_doom_iwad.zip \ - "https://www.doomworld.com/3ddownloads/ports/shareware_doom_iwad.zip" + # get from rv32emu-prebuilt + wget -O build/shareware_doom_iwad.zip "https://raw.githubusercontent.com/sysprog21/rv32emu-prebuilt/doom-artifact/shareware_doom_iwad.zip" unzip -d build/ build/shareware_doom_iwad.zip if: ${{ always() }} - name: default build using emcc From f4fb79c860733173a4cad49be9bc22fcdbf02da6 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 6 Jul 2025 00:59:51 +0800 Subject: [PATCH 3/4] Fetch all artifacts before generating ELF list Without this, the ELF list may be generated incorrectly because the build/riscv32 ELF executable has not been fetched yet. --- mk/wasm.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mk/wasm.mk b/mk/wasm.mk index 89cd04fc4..21c0d2d60 100644 --- a/mk/wasm.mk +++ b/mk/wasm.mk @@ -56,7 +56,7 @@ CFLAGS_emcc += --embed-file build/jit-bf.elf@/jit-bf.elf \ endif -$(OUT)/elf_list.js: tools/gen-elf-list-js.py +$(OUT)/elf_list.js: artifact tools/gen-elf-list-js.py $(Q)tools/gen-elf-list-js.py > $@ # used to download all dependencies of elf executable and bundle into single wasm From e022ac7135f6503f7209b0c974a0a11b2a0a4384 Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Sun, 6 Jul 2025 13:41:31 +0800 Subject: [PATCH 4/4] Update README To reflect the support for the system emulation in the web browsers. --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 43028be79..845f70503 100644 --- a/README.md +++ b/README.md @@ -378,16 +378,25 @@ $ source ~/emsdk/emsdk_env.sh Change the Emscripten SDK environment path if necessary. At this point, you can build and start a web server service to serve WebAssembly by running: +- user space emulation: ```shell -$ make CC=emcc start-web +$ make CC=emcc start-web -j8 +``` +- system emulation: +```shell +$ make CC=emcc start-web ENABLE_SYSTEM=1 INITRD_SIZE=32 -j8 ``` You would see the server's IP:PORT in your terminal. Copy and paste it to the browsers and you just access the index page of `rv32emu`. -You would see a dropdown menu which you can use to select the ELF executable. Select one and -click the Run button to run it. +You would see a dropdown menu which you can use to select the ELF executable for user space emulation, select one and +click the 'Run' button to run it. For system emulation, click the 'Run Linux' button to boot Linux. + +Alternatively, you may want to view a hosted `rv32emu` since building takes some time. +- [user space emulation demo page](https://sysprog21.github.io/rv32emu-demo/) +- [system emulation demo page](https://sysprog21.github.io/rv32emu-demo/system) -Alternatively, you may want to view a hosted `rv32emu` [demo page](https://sysprog21.github.io/rv32emu-demo/) since building takes some time. +Both pages can be easily switched using the navigation button. ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.