Skip to content

Enable System Emulation in Web Browsers #602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ChinYikMing
Copy link
Collaborator

@ChinYikMing ChinYikMing commented Jul 4, 2025

Demo Link

link. Press 'Run Linux' button to boot Linux. After booting, use it as usual. Experience graphics and sounds by running doom-riscv, quake, smolnes.

Demo Video

system-emu-wasm.mp4

Description

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
handles both standard ASCII input and escape sequences (such as arrow
keys), providing 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.

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%7C172.67.171.63%7C:443/)... connected.
    HTTP request sent, awaiting response... 403 Forbidden

Reproduce

  1. Clone:
$ git clone https://github.com/ChinYikMing/rv32emu.git -b sys-emu-wasm --depth 1 && cd rv32emu
  1. Source your emsdk's environment:
$ source ~/emsdk/emsdk_env.sh

3.1 To serve user space emulation index page:

$ make start-web CC=emcc ENABLE_SDL=1 -j8

3.2 To serve system emulation index page:

$ make start-web CC=emcc ENABLE_SYSTEM=1 ENABLE_SDL=1 INITRD_SIZE=32 -j8

Once the server is running, open your browser (Chrome, Firefox, or Safari) and navigate to http://127.0.0.1:8000/ to test it. The expected result should match link.

Summary by Bito

This pull request enhances system emulation in web browsers using WebAssembly, allowing users to run Linux and graphical applications like doom-riscv and quake. It introduces a new HTML interface for terminal interaction, improves memory management, and restructures the build process to separate user and system targets for better modularity.

@ChinYikMing ChinYikMing marked this pull request as draft July 4, 2025 01:08
@ChinYikMing
Copy link
Collaborator Author

Marked as draft since separating the deployment of user and system emulation for WebAssembly may require further discussion.

If we all agree to proceed with the separation,, a similar repo like rv32emu-demo (might be called rv32emu-demo-system) should be created.

@ChinYikMing
Copy link
Collaborator Author

The visualization of the running architecture:
image

Copy link
Contributor

@jserv jserv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Benchmark suite Current: 05924ce Previous: 1f72e2b Ratio
Dhrystone 1272 Average DMIPS over 10 runs 1273 Average DMIPS over 10 runs 1.00
Coremark 922.357 Average iterations/sec over 10 runs 903.084 Average iterations/sec over 10 runs 0.98

This comment was automatically generated by workflow using github-action-benchmark.

@jserv
Copy link
Contributor

jserv commented Jul 4, 2025

If we all agree to proceed with the separation,, a similar repo like rv32emu-demo (might be called rv32emu-demo-system) should be created.

Can the system emulation be part of rv32emu-demo?

@ChinYikMing
Copy link
Collaborator Author

If we all agree to proceed with the separation,, a similar repo like rv32emu-demo (might be called rv32emu-demo-system) should be created.

Can the system emulation be part of rv32emu-demo?

Yes, we can. Take this as an example: https://chinyikming.github.io/rv32emu-demo/ for user space emulation, and https://chinyikming.github.io/rv32emu-demo/system for system emulation.

src/emulate.c Outdated
@@ -1003,12 +1003,19 @@ static bool rv_has_plic_trap(riscv_t *rv)
(rv->csr_sip & rv->csr_sie));
}

#if defined(__EMSCRIPTEN__)
extern bool is_input_buf_avail;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more appropriate header where this extern declaration could be placed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a more appropriate header where this extern declaration could be placed?

Introduce src/emsc.h for the concern.

Copy link

Bito Automatic Review Skipped - Draft PR

Bito didn't auto-review because this pull request is in draft status.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change draft PR review settings here, or contact your Bito workspace admin at jserv.tw@gmail.com.

1 similar comment
Copy link

Bito Automatic Review Skipped - Draft PR

Bito didn't auto-review because this pull request is in draft status.
No action is needed if you didn't intend for the agent to review it. Otherwise, to manually trigger a review, type /review in a comment and save.
You can change draft PR review settings here, or contact your Bito workspace admin at jserv.tw@gmail.com.

@ChinYikMing
Copy link
Collaborator Author

ChinYikMing commented Jul 5, 2025

If we all agree to proceed with the separation,, a similar repo like rv32emu-demo (might be called rv32emu-demo-system) should be created.

Can the system emulation be part of rv32emu-demo?

Yes, we can. Take this as an example: https://chinyikming.github.io/rv32emu-demo/ for user space emulation, and https://chinyikming.github.io/rv32emu-demo/system for system emulation.

The index page of the demo links have been updated. Both user-space and system emulation can now be navigated back and forth using the newly added navigation buttons.

User-space emulation has been supported and deployable in WebAssembly
since sysprog21#389, but system emulation was not yet available. With sysprog21#508,
system emulation was introduced, and later sysprog21#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
handles both standard ASCII input and escape sequences (such as arrow
keys), providing 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
@ChinYikMing ChinYikMing marked this pull request as ready for review July 5, 2025 16:21
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
Without this, the ELF list may be generated incorrectly because the
build/riscv32 ELF executable has not been fetched yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants