CheerpX: a WebAssembly-based x86 virtual machine in the browser, Yuri Iozzelli

CheerpX: a WebAssembly-based x86 virtual machine in the browser, Yuri Iozzelli — WebAssembly NA

WebAssembly NA

AI-assisted summary of CheerpX: a WebAssembly-based x86 virtual machine in the browser, Yuri Iozzelli — original source. Check the original for full context.

Source published:

Key ideas

  • Leaning Technologies introduced CheerpX, an execution engine written in C++ and compiled to WebAssembly that runs unmodified 32-bit x86 binaries in the browser using an interpreter and a dynamic JIT compiler. (4:18)
  • To bypass the impending end-of-life of Flash support without relying on reverse-engineered emulators like Ruffle or Lightspark, CheerpX executes the original Linux Adobe Flash Player shared library directly via an emulated Pepper API (PPAPI) host. (17:27)
  • Browser-level WebAssembly limitations, such as asynchronous compilation latency and tab crashes in Chrome when exceeding roughly 1,000 active Wasm modules, force CheerpX to batch multiple functions into single modules. (13:52)
  • In addition to Linux user-space syscall emulation, CheerpX supports full-system hardware emulation, demonstrated live by booting MS-DOS and running the classic game Prehistorik. (20:41)

Detailed notes

## Background Published on YouTube by the WebAssembly NA channel on February 18, 2020, this video records Yuri Iozzelli's presentation at a Wasm SF meetup regarding CheerpX, an x86 virtual machine built for the web browser. The video description highlights how CheerpX leverages dynamic Just-In-Time compilation into WebAssembly to execute native applications, spotlighting its flagship use case of running the unmodified Linux Adobe Flash Player without browser plugins.

## TL;DR Yuri Iozzelli introduces CheerpX, a client-side x86 virtualization engine that executes native 32-bit Linux binaries and operating systems inside web browsers using a two-tier architecture of an x86 interpreter paired with a runtime WebAssembly JIT compiler. He explains the technical hurdles of bridging native machine code into WebAssembly's structured execution model, detailing optimizations for x86 status flag elimination, module batching strategies to avoid browser tab crashes, and sandboxed storage and networking workarounds. The presentation highlights CheerpX's headline deployment—preserving legacy Adobe Flash content by running the original Linux Pepper shared library—alongside full-system emulation booting MS-DOS and retro games like Prehistorik. In the concluding Q&A, Iozzelli discusses upcoming WebAssembly standard proposals, socket virtualization trade-offs, and why emulating native binaries achieves higher fidelity than reverse-engineered clean-room runtimes like Ruffle.

## Atmosphere & style The presentation has the relaxed, highly technical vibe of an intimate developer meetup, balancing low-level systems engineering with pragmatic transparency about the web platform's current limits. Iozzelli speaks with straightforward, understated competence, freely acknowledging performance bottlenecks, browser crash thresholds, and experimental workarounds without excessive marketing spin. The audience engages closely throughout the Q&A, creating an energetic and collaborative exchange that moves fluidly from granular compiler internals and digital preservation to shared laughter over WebAssembly-to-Flash compilation cycles and the inevitable test of running Doom.

## Bulletin - [04:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=258s) Leaning Technologies introduced CheerpX, an execution engine written in C++ and compiled to WebAssembly that runs unmodified 32-bit x86 binaries in the browser using an interpreter and a dynamic JIT compiler. - [17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) To bypass the impending end-of-life of Flash support without relying on reverse-engineered emulators like Ruffle or Lightspark, CheerpX executes the original Linux Adobe Flash Player shared library directly via an emulated Pepper API (PPAPI) host. - [13:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=832s) Browser-level WebAssembly limitations, such as asynchronous compilation latency and tab crashes in Chrome when exceeding roughly 1,000 active Wasm modules, force CheerpX to batch multiple functions into single modules. - [20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) In addition to Linux user-space syscall emulation, CheerpX supports full-system hardware emulation, demonstrated live by booting MS-DOS and running the classic game Prehistorik.

## Key takeaways - [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) Executing arbitrary applications in the browser requires treating the browser as a full OS and hardware architecture with its own execution engines and system display APIs. - [04:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=258s) Unlike source recompilation tools (Cheerp/Emscripten) or bytecode engines (CheerpJ), CheerpX aims to run raw, unmodified 32-bit x86 Linux ELF binaries directly without source code. - [05:12](https://www.youtube.com/watch?v=7JUs4c99-mo&t=312s) CheerpX employs a two-tier execution model consisting of an x86 instruction interpreter for cold paths and profiling, alongside a JIT compiler that generates WebAssembly modules for hot basic blocks. - [05:12](https://www.youtube.com/watch?v=7JUs4c99-mo&t=312s) Complex x86 opcodes are decomposed into simpler primitives, requiring explicit emulation of EFLAGS calculations since WebAssembly does not generate CPU status flags automatically. - [07:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=438s) Because native binaries execute arbitrary, unstructured jumps whereas WebAssembly enforces structured control flow, CheerpX uses dynamically discovered Control Flow Graphs (CFGs) to group warm traces into callable WebAssembly functions. - [10:38](https://www.youtube.com/watch?v=7JUs4c99-mo&t=638s) The JIT compiler assumes guest native code is already optimized by compilers like GCC, focusing instead on virtualization overhead reduction such as dead-code elimination on intermediate EFLAGS calculations. - [13:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=832s) WebAssembly lacks synchronous native JIT execution; modules must be compiled asynchronously, forcing a delicate trade-off between function-level compilation granularity and browser engine memory limits. - [16:19](https://www.youtube.com/watch?v=7JUs4c99-mo&t=979s) Guest Linux binaries interact with an emulated sandbox environment that maps POSIX filesystem calls to IndexedDB and requires WebSocket relays or WebRTC to bridge the lack of raw networking sockets. - [17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) Running the authentic Linux Flash Player .so binary via a bridged PPAPI architecture guarantees full fidelity across complex Flash content where clean-room implementations historically struggle. - [20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) A unified core engine powers both application-level Linux system call translation and full-system hardware emulation capable of booting MS-DOS. - [10:38](https://www.youtube.com/watch?v=7JUs4c99-mo&t=638s) CheerpX currently relies on cooperative single-threaded time-slicing for concurrency, using experimental WebAssembly threads and SharedArrayBuffer strictly for timer interrupts and virtual time. - [32:33](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1953s) Building a generalized x86 virtual machine provides reusable web virtualization infrastructure for legacy software preservation that outlasts single-application targets like Flash.

## Detailed walkthrough ### [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) Introduction to Leaning Technologies and Running Code in the Browser

Leaning Technologies maintains two primary shipping products before expanding into virtualization. The first is Cheerp, a C++ compiler targeting WebAssembly and JavaScript. The second is CheerpJ, a solution designed to execute Java bytecode directly in WebAssembly and JavaScript. The speaker, Yuri Iozzelli, introduces their third project: CheerpX.

```mermaid flowchart TD accTitle: Browser Execution Layer Architecture accDescr: Diagram illustrating how an application requires both an execution engine and lower-level system capabilities to function inside the browser.

subgraph App["Application Layer"] Native["Native Application Logic"] end

subgraph Browser["Browser Environment (OS & Architecture)"] subgraph Engine["Execution Engine"] JS["JavaScript Engine"] Wasm["WebAssembly Runtime"] end subgraph SysCap["System Capabilities"] DOM["DOM / Canvas / WebGL (Display)"] APIs["System APIs (Audio, Network, Storage)"] end end

Native --> Engine Engine --> SysCap ```

To explain the vision of CheerpX, Iozzelli addresses what it fundamentally means to run any arbitrary application inside a web browser: “We just need to see the browser as just another architecture and operating system.” Under this mental model, executing software requires two core components: 1. An **execution engine** capable of executing computational logic (which, inside browser environments, consists of native WebAssembly and JavaScript). 2. Direct access to **system capabilities** through low-level libraries that present output to the user, because without graphics or audio output, an application “is just making computation without showing anything.”

[01:26](https://www.youtube.com/watch?v=7JUs4c99-mo&t=86s) Porting C++ and Java: Emscripten, Cheerp, and CheerpJ

Traditional browser porting workflows depend on source re-compilation or bytecode translation alongside platform emulation layers. For C++, compilers such as Emscripten and Cheerp take source code and emit WebAssembly and JavaScript. Developers must recreate OS-level APIs (such as POSIX interfaces or SDL graphics libraries) using JavaScript or browser-compatible C++.

To demonstrate this concept, Iozzelli showcases *BZFlag* (rendered in the talk as a multiplayer game ported with Cheerp), a 3D multiplayer tank game written in C++. Running it in the browser presented two main porting hurdles: - **Graphics API mismatches:** The game was originally authored using legacy OpenGL 1.x, which does not map 1:1 to WebGL, requiring an emulation translation layer. - **Networking limits:** The original game relied on raw UDP sockets for network synchronization. Because raw UDP is unavailable to client-side web scripts, the port substituted WebRTC data channels to transport packets. Because WebRTC was used, both the client and the dedicated game server could run concurrently inside browser tabs.

When tackling Java applications via CheerpJ, source code is unnecessary. CheerpJ operates on Java bytecode, allowing full binary compatibility across any language that targets the JVM (including Kotlin or Scala). CheerpJ preserves dynamic class loading, reflection, and run-time verification. On the library side, Leaning Technologies recompiled the OpenJDK runtime itself into WebAssembly and JavaScript. This enables legacy desktop UI frameworks like Swing and AWT to render inside the DOM without modification. Iozzelli presents *OpenAstexViewer*, a Java applet used for visualizing 3D molecular structures and protein models, executing smoothly inside modern browsers without requiring the deprecated NPAPI Java browser plugin.

[04:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=258s) Dropping Linux ELF Binaries Directly into the Browser

While runtime reimplementations like CheerpJ work well for specific ecosystems, they require building dedicated compatibility layers for every runtime environment. Iozzelli argues for a radical alternative: “What if we just run a Linux ELF binary and solve the problem?”

To illustrate this, Iozzelli builds a basic C test program on the command line using standard native development tools. He demonstrates compiling with two specific compiler flags: - `-static`: Links all libraries into a single standalone binary that can be loaded straight into the browser interface without managing separate dynamic library dependencies. - `-m32`: Generates a standard x86 32-bit binary, matching CheerpX's current architectural support.

After inspecting the output with the `file` command to confirm it is an authentic `ELF 32-bit LSB executable, Intel 80386`, he selects the binary via a browser file picker. The unmodified binary executes immediately within the browser tab.

```mermaid flowchart LR accTitle: CheerpX Two-Tier Execution Flow accDescr: Flow diagram showing how execution cycles between the interpreter and compiled WebAssembly modules.

Sched["Thread Scheduler"] --> Check{"Code Compiled?"} Check -- "No (Cold)" --> Interp["x86 Interpreter"] Check -- "Yes (Hot)" --> Wasm["Native WebAssembly JIT Blocks"]

Interp -->|"Profile & Discover CFG"| JIT["JIT Compiler"] JIT -->|"Emit Wasm Module"| Wasm

Wasm -->|"Timer / Syscall / Page Fault"| Interp Interp -->|"Resume Scheduling"| Sched ```

[05:12](https://www.youtube.com/watch?v=7JUs4c99-mo&t=312s) Inside CheerpX: The Two-Tier Architecture and Opcode Decomposition

CheerpX is an execution engine written in C++ and compiled to WebAssembly. It operates on arbitrary 32-bit x86 binaries using two distinct execution tiers: - **An x86 Interpreter:** Interprets code one instruction at a time, handling infrequently executed ("cold") code paths and profiling runtime behavior. - **A Just-In-Time (JIT) Compiler:** Takes frequently executed ("hot") basic blocks and compiles them directly into WebAssembly modules at runtime.

CheerpX supports two execution targets: user-space Linux binary execution (intercepting Linux system calls) and full-system virtualization capable of booting an unmodified operating system.

Execution is managed by a thread scheduler. When a target program counter points to already-compiled WebAssembly, the runtime bypasses the interpreter to execute native WebAssembly directly at near-native speeds. If an event occurs—such as a timer interrupt, page fault, or system call (`syscall`)—control falls back to the interpreter, which handles the transition and resumes the scheduling loop.

Handling the x86 instruction set requires managing a large, variable-length instruction space. CheerpX breaks down complex x86 opcodes into simpler micro-operations (primitives). For example, an `ADD` instruction is broken into: 1. Loading source operands from registers or immediate values. 2. Executing the mathematical addition. 3. Manually computing and updating the x86 `EFLAGS` register. Hardware x86 CPUs automatically calculate flags (such as Carry, Zero, Sign, and Overflow) after every arithmetic operation, but WebAssembly instructions do not generate CPU flags automatically. 4. Writing back the computed result to the destination register.

Similarly, a comparison instruction (`CMP`) is broken down as a subtraction primitive where the arithmetic difference is discarded and only the resulting status flags are computed and preserved.

[07:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=438s) Control Flow Graph Discovery and JIT Compilation Strategy

The interpreter executes instructions sequentially while constructing metadata structures needed by the JIT compiler, specifically building a runtime Control Flow Graph (CFG). This profiling stage tracks indirect branches: because indirect jumps in native code typically target a consistent set of destinations, the interpreter records these targets to help optimize downstream compilation.

The CFG is composed of traces (equivalent to basic blocks in compiler terminology): unbroken linear sequences of instructions terminating in a control transfer, such as a jump, conditional branch, or function call.

```mermaid flowchart TD accTitle: Dynamic Trace Promotion in CheerpX accDescr: Diagram illustrating how execution traces transition from cold interpretation to hot WebAssembly function generation.

subgraph Memory["x86 Binary Memory"] T1["Trace 1 (Cold / Interpreter)"] T2["Trace 2 (Warm)"] T3["Trace 3 (Hot Entry Point)"] T4["Trace 4 (Warm)"] end

T1 --> T3 T3 --> T2 T3 --> T4

subgraph JIT_Module["Compiled WebAssembly Function"] T3_Compiled["Trace 3 Logic"] T2_Compiled["Trace 2 Logic"] T4_Compiled["Trace 4 Logic"] end

T3 -.->|"Promote Hot Root + Reachable Warm Traces"| JIT_Module ```

Generating WebAssembly from native machine code poses an architectural mismatch: - Native binaries can branch arbitrarily anywhere in memory using absolute addresses, mid-function jumps, or unaligned entry points. - WebAssembly enforces structured control flow and execution units scoped inside strict functions (`func`).

While assembly language contains `CALL` and `RET` mnemonics, binaries are not required to follow structured calling conventions: “There are the call and ret instructions, so you can say that a function is something that is a target of a call and it ends when you get the ret instruction, but the code is actually not required to do that at all.” Binaries can execute a `RET` without an associated `CALL`, jump across arbitrary stack offsets, or execute self-modifying code.

To bridge this gap, CheerpX uses its dynamically collected CFG data to infer functional boundaries: - Traces begin cold within the interpreter. - As execution counts rise, traces are promoted to "warm" and eventually "hot." - When a trace becomes hot, it serves as the root entry point for a new WebAssembly function. - CheerpX gathers the hot root trace and all contiguous, reachable traces that are at least warm, bundling them into a single WebAssembly function module. - If uncompiled paths subsequently become warm later in execution, CheerpX invalidates or updates the compiled WebAssembly function with the expanded trace set.

[10:38](https://www.youtube.com/watch?v=7JUs4c99-mo&t=638s) JIT Optimizations, Flags Redundancy, and Syscall Interception

When compiling traces into WebAssembly, CheerpX applies custom optimization passes. The JIT operates under the core assumption that the underlying application code is already well-optimized by native ahead-of-time compilers like GCC or Clang: “The code is already let's say optimal, but we want to optimize all the overhead of the virtualization.”

One of the largest virtualization overheads in x86-to-WebAssembly translation is computing the CPU status flags (`EFLAGS`). On real hardware, flags are updated for free alongside arithmetic; in WebAssembly, computing flags requires explicit bitwise calculations and state writes. CheerpX runs dead-code elimination across flag calculations: - If an `ADD` instruction is immediately followed by a `SUB` instruction, both instructions would normally write their results to the flag status register. - If no conditional jump or branching instruction reads the flags between the two instructions, the flags computation for the `ADD` is completely redundant. - CheerpX eliminates the intermediate flag generation primitives, avoiding unnecessary bit shifting and register updates. - It also performs common subexpression elimination, such as removing redundant loads when the contents of an x86 register (like `EBX`) are already cached in local WebAssembly variables.

``` Original Assembly: ADD EAX, EBX; Computes sum AND updates EFLAGS (unobserved) SUB EAX, ECX; Computes difference AND updates EFLAGS (observed by JNZ) JNZ target

CheerpX Optimization: [Load EAX, EBX] [Wasm i32.add] <-- Omit EFLAGS generation entirely [Load ECX] [Wasm i32.sub + Calc Flags] <-- Retain flag calculations only here [Branch on Flags] ```

Beyond arithmetic optimization, CheerpX handles concurrency and platform integration: - **Threading and Multiprocessing:** True parallel Web Workers are an intended future enhancement. CheerpX handles multi-threading on the browser's single main thread through cooperative time-slicing, round-robin scheduling across logical threads, and managing distinct address spaces for separate Linux processes. - **System Calls (Syscalls):** CheerpX intercepts standard Linux software interrupts and system calls (`int 0x80` / `sysenter`). Calls such as `open`, `read`, `write`, `close`, timing calls, and virtual memory mappings (`mmap`) are routed to native web browser APIs, local storage, or network backends.

Iozzelli concludes by comparing CheerpX's design to native dynamic binary translators: “What I explained so far is not so different than what software like QEMU does natively for example, right? Just generate some code.” However, running this architecture inside browser sandboxes encounters WebAssembly runtime limitations not present in native environments.

[13:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=832s) WebAssembly JIT Realities and Browser Limitations

Native virtual machines generate machine code and jump straight into execution. In WebAssembly, dynamic code execution is far more cumbersome. A runtime must wrap generated WebAssembly bytecode inside a module, compile that module asynchronously, wait for the compilation promise to resolve, and only then invoke the functions exported from that module. This creates an architectural dilemma regarding compilation granularity. While the ideal model would compile a single function per module for near-instant execution, browser implementation limits make this impossible.

```mermaid flowchart TD accTitle: WebAssembly JIT Module Batching Trade-off accDescr: The trade-off between compiling one function per module versus batching functions to avoid browser crashes.

subgraph Ideal ["Ideal Model: Function-Level Compilation"] I1["Compile 1 function"] --> I2["Wrap in 1 Wasm module"] I2 --> I3["Near-zero compilation latency"] I3 --> I4["1,000+ active modules"] I4 --> I5["Browser tab crashes (Chrome)"] end

subgraph Batch ["Current CheerpX Batching Model"] B1["Group multiple hot functions"] --> B2["Wrap batch into 1 Wasm module"] B2 --> B3["Compilation latency & interpreter overhead"] B3 --> B4["Avoids browser crashes"] B4 --> B5["Risk of memory leaks via shared function tables"] end ```

Yuri highlights the instability encountered in production browsers: “for example Chrome, after we have like 1,000 modules at the same time, just the browser tab crashes.” Because of this crash threshold, CheerpX batches multiple functions into a single module. However, bundling too many functions introduces compilation latency, forcing the virtual machine to spend excessive time running in the slower interpreter while awaiting the compiled code. Additional browser-level bugs complicate the implementation, such as memory leaks caused by sharing function tables across modules.

- Module compilation in WebAssembly is fundamentally asynchronous, introducing mandatory wait latency before code execution. - WebAssembly tail calls are critical for indirect branching; dispatching indirect branches using standard function calls blows the call stack. - Tail calls were supported in Chrome at the time of the presentation, while Firefox support remained pending. - CheerpX uses experimental WebAssembly threads and `SharedArrayBuffer`—not to execute guest threads directly, but to schedule hardware interrupts and keep virtual time.

---

[16:19](https://www.youtube.com/watch?v=7JUs4c99-mo&t=979s) Sandboxing Constraints: Filesystems and Networking

Running arbitrary x86 Linux binaries inside the browser forces the execution layer to adapt to web security models. A native program cannot access the underlying host machine's local storage directly because the browser enforces strict sandboxing. To resolve this, CheerpX provides an emulated virtual filesystem backed by IndexedDB.

```mermaid flowchart LR accTitle: CheerpX Browser Sandboxing and Emulation Layer accDescr: Mapping guest x86 filesystem and network requests through browser-compatible sandboxed equivalents.

subgraph GuestApp ["x86 Guest Binary"] G_FS["POSIX Filesystem Calls"] G_NET["Raw UDP / Sockets"] G_HTTP["HTTP Requests"] end

subgraph Emulation ["CheerpX Emulation Layer"] E_FS["IndexedDB Virtual FS"] E_WS["WebSocket Tunnel"] E_Fetch["Browser Fetch / XHR"] end

subgraph HostEnv ["Host / Remote"] H_Store[("Browser Storage")] H_Proxy["Proxy Relay Server"] H_Net["Remote UDP Destination"] H_Web["Cross-Origin HTTP APIs"] end

G_FS --> E_FS --> H_Store G_NET --> E_WS --> H_Proxy --> H_Net G_HTTP --> E_Fetch --> H_Web ```

Networking introduces similar architectural barriers. When an application attempts to open an arbitrary transport protocol, such as a UDP socket, the browser cannot service the request because raw networking APIs are not exposed to client-side scripts. CheerpX handles network transport using WebSockets, though Yuri explains that “WebSockets are not real sockets, so you still need some kind of proxy server that relays the WebSocket traffic.” Standard HTTP requests execute through normal web APIs, although they remain vulnerable to standard cross-origin (CORS) restrictions.

---

[17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) The Primary Use Case: Adobe Flash Player Without Plugins

The flagship use case driving CheerpX development is preserving Adobe Flash applications without native NPAPI/PPAPI browser plugins. With official Flash support terminated at the end of 2020, existing preservation attempts have focused on clean-room reimplementations of the Flash runtime, such as the Rust-based Ruffle project or older efforts like Gnash and Lightspark. Yuri remains skeptical that reverse-engineered interpreters can ever achieve full fidelity. He notes that the founder of Leaning Technologies, who was the original author of Lightspark, concluded that running the original binary is the only path to 100% compatibility: “he's convinced that this is the only way to have everything working for real, and yeah, because we just run the original and nothing can go wrong, right?”

Because the Linux Flash Player was distributed as a shared library (`.so`) using Google's Pepper API (PPAPI) rather than a standalone executable, running it requires a split-host architecture:

```mermaid flowchart TD accTitle: Flash PPAPI Host Architecture in CheerpX accDescr: Architecture showing how CheerpX hosts the native Linux Flash shared library and bridges the Pepper API into JavaScript.

subgraph InVM ["CheerpX Virtual Machine (x86)"] F_SO["Linux Flash Player (.so)"] H_x86["x86 Pepper Host Helper"] F_SO <-->|"PPAPI"| H_x86 end

subgraph BrowserJS ["Browser JavaScript Environment"] H_JS["JavaScript Pepper Host Runtime"] WebAPIs["Web APIs / Canvas / Audio"] H_JS <--> WebAPIs end

H_x86 <-->|"Bridge RPC / Memory Sync"| H_JS ```

- Deployment on web pages is unobtrusive: site owners include a single script that detects all `<object>` tags and replaces them with HTML5 `<canvas>` elements managed by the CheerpX runtime. - Yuri demonstrates a live Flash game running within the browser. The execution exhibits minor stuttering and input latency, leading him to observe: “it's not super fast yet, but it works, so it's kind of incredible if you ask me that it's actually running.”

---

[20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) Full-System Emulation and Commercial Roadmaps

Beyond syscall-level emulation for Linux binaries, CheerpX includes a full-system emulation mode. Instead of intercepting Linux system calls, this engine emulates underlying motherboard and CPU hardware, enabling unmodified operating systems to boot on top. The system-level mode shares the exact same core execution architecture—the identical interpreter and WebAssembly JIT compiler—used by the application-level sandbox.

Yuri demonstrates this capability by booting MS-DOS inside the browser and launching the classic DOS platform game *Prehistorik*, which runs at smooth, playable speeds.

```mermaid flowchart TD accTitle: Unified Execution Engine Modes in CheerpX accDescr: Diagram illustrating how the single execution engine supports both user-mode syscall emulation and hardware-level emulation.

subgraph CoreEngine ["Unified Core Execution Engine"] JIT["WebAssembly JIT Compiler"] Interp["x86 Interpreter"] end

subgraph SyscallMode ["Application Syscall Emulation"] Flash["Adobe Flash Player (Linux.so)"] SyscallInterp["Linux Syscall Translation"] Flash --> SyscallInterp --> CoreEngine end

subgraph SysMode ["Full Hardware Emulation"] DOS["MS-DOS / Prehistorik"] HW["PC Hardware Emulation (Real Mode)"] DOS --> HW --> CoreEngine end ```

- The hardware engine is currently limited to real-mode operation, with protected-mode x86 support under active development. - The company has executed a commercial agreement with an online Flash game distributor to deploy CheerpX across production gaming portals. - Enterprise demand centers on legacy business applications built with Adobe Flex and Adobe Spark. - Leaning Technologies runs an early-adopter program for enterprise clients, targeting commercially acceptable performance by summer 2020.

---

[23:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1409s) Q&A: Wasm Working Group, Chrome Extensions, and CheerpJ GC

Following the presentation, audience members probe into Wasm standardisation, extension deployment, and memory management internals:

- **WebAssembly Community Group (CG) JIT Proposal:** An attendee notes that dynamically emitting Wasm bytes, instantiating modules, and fetching exported functions is awkward, mentioning that the WebAssembly Community Group is actively discussing first-class JIT capabilities. Yuri confirms he is attending the W3C meeting the following day to share CheerpX's architectural challenges with the working group. - **Automated Web Archives and Extension Packaging:** In response to whether CheerpX could run across the Internet Archive automatically, Yuri explains that while many sites still trigger runtime crashes, automated execution is theoretically straightforward. He points to Leaning Technologies' sister product, CheerpJ: “for our Java product we actually have a Chrome extension that is called CheerpJ Applet Runner, and you can use that extension to inject our script.” - **Firefox Extension Limitations:** When asked why the CheerpJ Applet Runner is only available on the Chrome Web Store, Yuri explains that the extension must inject execution shims extremely early in the document lifecycle: “there are a lot of workarounds that we put into the extension because we need to inject our stuff very, very early, and the APIs for extensions are not really letting us do all the stuff that we want.” These race-condition workarounds rely on Chrome-specific behaviors, though Firefox support remains constrained primarily by engineering team bandwidth. - **CheerpJ Garbage Collection Model:** An attendee asks how CheerpJ executes garbage collection without the ability to inspect the WebAssembly call stack. Yuri clarifies that CheerpJ compiles Java bytecode directly into JavaScript and relies on the browser engine's native garbage collector for object lifetimes. Only native JVM methods compile to WebAssembly; Leaning Technologies does not bundle a custom GC implementation. CheerpX itself manages raw x86 memory directly via its emulated physical memory model.

[28:44](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1724s) Concrete Values and JIT Optimization Possibilities

An audience member asks about the specifics of the Just-In-Time (JIT) compiler. Noting that the engine compiles the front-end language into execution traces, they point out that during JIT execution, the engine runs on concrete runtime values. They wonder whether CheerpX uses those concrete values at runtime to perform specific optimizations that mutate or refine the trace graph, acknowledging that the question “might be a little bit too technical to be honest.”

Yuri Iozzelli explains that CheerpX currently does not leverage runtime concrete values to optimize trace generation, though he concedes it is a promising direction: “Now we are not we are not doing that but that's actually a cool thing to explore yeah yeah you're right that could be done but yeah we are not doing that yet.”

[29:53](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1793s) Networking Challenges: Raw Sockets, WebSockets, and WebRTC

Another attendee asks how networking behaves when running native Linux or DOS binaries inside the browser—specifically whether an application can open a raw socket to execute a network service such as a web server. Yuri clarifies that network support is not yet implemented, largely due to the browser sandbox boundary. Because a native application expects to open raw sockets directly, CheerpX must translate those calls into browser-native networking primitives, which leaves two main alternatives:

```mermaid flowchart TD accTitle: Networking Bridging Strategies in CheerpX accDescr: Diagram illustrating trade-offs between WebSocket relay architecture and direct WebRTC peer connections.

subgraph Relayed ["WebSocket Strategy"] AppA["Browser Guest App"] -->|"WebSocket"| Relay["Relay Server"] Relay -->|"Native TCP/UDP"| NativeApp["Native Desktop App"] end

subgraph Direct ["WebRTC Strategy"] AppB["Browser Guest App A"] <-->|"WebRTC DataChannel"| AppC["Browser Guest App B"] end ```

If CheerpX uses WebRTC, the guest is confined to communicating only with endpoints running an identical WebRTC back-end, precluding direct communication with arbitrary native applications running outside the browser without specialized software. If CheerpX maps calls to WebSockets, it can employ an external relay server that opens normal sockets to interface between the browser guest and external native systems. However, for browser-to-browser use cases like multiplayer Flash games, WebRTC might be the cleaner choice. Yuri summarizes the situation by stating: “We still have to think about that and maybe the best thing is just case by case.”

The questioner then asks the room whether anyone from the Chrome team has an update on Project Fugu's raw socket and UDP capabilities. They observe that while Chrome Apps previously supported raw sockets, general web platform support remains limited, and Yuri agrees that while Chrome Apps could open sockets, “those are not anywhere everywhere but maybe one day.”

[32:33](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1953s) Emulating x86 vs. Re-implementing the Flash Virtual Machine

An audience member asks why Leaning Technologies chose to build a full x86 virtualization layer rather than writing a dedicated Flash interpreter, given that their headline application is preserving Flash content. Yuri details the long, fraught history of open-source Flash reimplementations, pointing out previous efforts like Gnash and Lightspark, as well as modern projects like Ruffle. Because the Flash specification is massive and filled with edge cases, reverse-engineering it has historically led to broken compatibility:

> “Problem is that Flash is very complex and it's very difficult to get it right for example Gnash has been around for a lot of time and you couldn't run everything and also Lightspark was even beyond Flash in compatibility but also that one has its limitation so I don't know how Ruffle is going but yeah we think that it will never be exactly the same as the original Flash Player.”

Beyond fidelity, an x86 virtual machine provides an architectural investment that outlives Flash. Although Adobe Flash was scheduled for complete deprecation by browser vendors within the year, CheerpX's underlying engine remains entirely generic:

> “Right now we have a concrete application that is saving Flash this year because it's going away this year but we can reuse all these stuff to make other things too so we think that this is a better use of our time instead of implementing Flash yet another time and maybe not getting it right.”

In response to a follow-up asking whether CheerpX re-implements the Windows API or runs a full Windows OS underneath, Yuri clarifies their current setup: they run the original Linux Flash player binary directly within CheerpX's Linux emulation environment.

[34:56](https://www.youtube.com/watch?v=7JUs4c99-mo&t=2096s) DOS Emulation, Internet Archive Preservation, and Closing Banter

The discussion shifts to running older DOS software and whether users can boot arbitrary disk images with DOS games. Yuri indicates that basic DOS emulation works, though hardware compatibility remains incomplete. CheerpX's primary development focus is centered on the shared core components—specifically the JIT compiler and x86 execution pipelines—rather than emulating every specialized video mode and legacy peripheral device.

An attendee working as a contractor for the Internet Archive notes their interest in CheerpX, mentioning that the Internet Archive hosts a massive collection of MS-DOS games playable in the browser and that they want to connect after the talk to discuss technical overlap.

The session ends with lighthearted questions from the audience: - An attendee asks the mandatory benchmark question: “Can you play Doom on it?” Yuri laughs and admits, “We didn't try actually but it's the ultimate test.” - Another audience member brings up an ironic full-circle experiment seen at the WebAssembly Summit, where a speaker demonstrated compiling WebAssembly code back into Flash files. The attendee suggests pairing that compiler with CheerpX to convert WebAssembly to Flash, and then using CheerpX to execute that Flash binary back inside WebAssembly in the browser, closing the session to laughter.

## Notable quotes > [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) We just need to see the browser as just another architecture and operating system. — Yuri Iozzelli

> [16:19](https://www.youtube.com/watch?v=7JUs4c99-mo&t=979s) WebSockets are not real sockets, so you still need some kind of proxy server that relays the WebSocket traffic. — Yuri Iozzelli

## Mentioned Leaning Technologies, Cheerp, CheerpJ, CheerpX, Yuri Iozzelli, WebAssembly, JavaScript, Emscripten, BZFlag, OpenGL, WebGL, WebRTC, OpenJDK, OpenAstexViewer, Swing, AWT, NPAPI, PPAPI, Linux, ELF, Intel 80386, GCC, Clang, QEMU, Google Chrome, Mozilla Firefox, IndexedDB, WebSocket, Adobe Flash Player, Ruffle, Gnash, Lightspark, MS-DOS, Prehistorik, Adobe Flex, Adobe Spark, WebAssembly Community Group, CheerpJ Applet Runner, Project Fugu, Internet Archive, Doom, WebAssembly Summit

## Video description Original event: https://www.meetup.com/wasmsf/events/268096535/

WebAssembly is a new web technology that promises to revolutionize the web ecosystem. In this talk, I will explore the very limits of what is possible to do with it, by describing CheerpX: A WebAssembly-based x86 virtual machine in the browser.

Similarly to modern native virtual machines, CheerpX features a Just-In-Time compiler capable of producing optimized WebAssembly code at runtime. This has the potential of speeding up the execution considerably and unlocking the possibility of running real-world native applications in the browser.

One such application that we are currently working on at Leaning Technologies to showcase the CheerpX potential is the Adobe Flash Player for Linux: This allows running unmodified Flash content on modern browsers without plugins.

[Source](https://www.youtube.com/watch?v=7JUs4c99-mo)

中文简报

CheerpX:浏览器中基于 WebAssembly 的 x86 虚拟机,Yuri Iozzelli Yuri Iozzelli 介绍了 CheerpX,这是一款客户端 x86 虚拟化引擎,它通过 x86 解释器与运行时 WebAssembly JIT 编译器相结合的双层架构,在 Web 浏览器内执行原生 32 位 Linux 二进制文件及操作系统。他解释了将原生机器码接入 WebAssembly 结构化执行模型所面临的技术难题,详细阐述了针对 x86 状态标志消除的优化、避免浏览器标签页崩溃的模块批处理策略,以及沙盒存储和网络融通方案。演讲重点介绍了 CheerpX 的核心应用场景——通过运行原始 Linux Pepper 共享库来保留陈旧的 Adobe Flash 内容,同时展示了启动 MS-DOS 并运行《史前大冒险》(Prehistorik)等怀旧游戏的全系统仿真。在最后的问答环节中,Iozzelli 探讨了即将推出的 WebAssembly 标准提案、套接字虚拟化的权衡取舍,以及为何模拟原生二进制文件能比 Ruffle 等逆向工程的纯净室运行时实现更高的保真度。 Leaning Technologies 介绍了 CheerpX,这是一个用 C++ 编写并编译为 WebAssembly 的执行引擎,它使用解释器和动态 JIT 编译器在浏览器中运行未经修改的 32 位 x86 二进制文件。 (4:18) 为了在不依赖 Ruffle 或 Lightspark 等逆向工程模拟器的情况下应对 Flash 即将终止支持的问题,CheerpX 通过模拟的 Pepper API (PPAPI) 宿主直接执行原始 Linux Adobe Flash Player 共享库。 (17:27) 浏览器级别的 WebAssembly 限制(如异步编译延迟,以及在 Chrome 中超过大约 1,000 个活动 Wasm 模块时引发的标签页崩溃)促使 CheerpX 将多个函数分批打包到单个模块中。 (13:52) 除了 Linux 用户空间系统调用模拟外,CheerpX 还支持全系统硬件仿真,并通过现场启动 MS-DOS 及运行经典游戏《史前大冒险》(Prehistorik)进行了演示。 (20:41)

Reading summary

Yuri Iozzelli of Leaning Technologies presents CheerpX, an execution engine that runs unmodified 32-bit x86 binaries inside the browser by treating the web environment as a target operating system and architecture. Rather than recompiling source code or re-implementing runtimes, CheerpX pairs an x86 interpreter with a WebAssembly Just-In-Time (JIT) compiler to execute legacy binaries and operating systems directly within browser sandboxes. Two-Tier Execution and Opcode Decomposition (5:12) CheerpX relies on a two-tier architecture written in C++ and compiled to WebAssembly. The engine routes cold, infrequently executed code paths through an x86 interpreter that processes instructions sequentially while profiling runtime behavior. When traces become frequently executed, an internal JIT compiler emits native WebAssembly modules that execute at near-native speed. A cooperative thread scheduler manages transitions: when an interrupt, page fault, or system call occurs, execution falls back from compiled WebAssembly blocks to the interpreter loop. Because x86 opcodes are variable-length and complex, CheerpX decomposes them into simpler micro-operations. A major source of virtualization overhead is the x86 EFLAGS register, which physical hardware updates automatically after every arithmetic operation, but WebAssembly does not generate. To avoid expensive manual flag calculations, CheerpX performs dead-code elimination: if an arithmetic instruction is followed by another instruction that overwrites status flags without an intervening conditional branch reading them, the engine eliminates intermediate flag generation primitives entirely. Bridging Control Flow and Browser JIT Limits (7:18) Translating native x86 machine code into WebAssembly requires reconciling conflicting control-flow paradigms. Native binaries frequently execute unaligned branches, mid-function jumps, and returns without structured calling conventions, whereas WebAssembly enforces strictly scoped, structured functions. CheerpX resolves this by using interpreter profiling to build a dynamic Control Flow Graph (CFG) out of linear execution traces, promoting hot entry traces alongside contiguous warm traces into combined WebAssembly function modules. However, dynamic compilation inside browsers faces structural hurdles absent in native virtualizers like QEMU. WebAssembly requires bytecode to be wrapped into a module, compiled asynchronously, and instantiated before execution. While compiling one function per module would minimize latency, Iozzelli reports that browsers like Chrome crash after instantiating roughly 1,000 active modules. To prevent crashes, CheerpX batches multiple functions into single modules, trading compilation latency and interpreter overhead against browser tab stability, while relying on WebAssembly tail calls to dispatch indirect jumps without blowing the call stack. Sandboxing Syscalls, Storage, and Network Constraints (16:19) To execute Linux ELF binaries within browser security constraints, CheerpX intercepts software interrupts and Linux system calls (such as int 0x80 and sysenter). File and memory operations, including open, read, write, and mmap, are redirected to an emulated POSIX virtual filesystem backed by the browser's IndexedDB storage. Networking presents a similar architectural sandbox barrier because web applications cannot open raw TCP or UDP sockets. CheerpX bridges network operations through WebSockets or browser HTTP APIs. Because WebSockets cannot connect directly to native transport sockets without an external proxy relay server, Iozzelli notes that the runtime must evaluate networking mechanisms on a case-by-case basis, considering WebRTC data channels for browser-to-browser communication while awaiting broader platform socket standards. Binary Emulation for Flash and Full-System DOS (17:27) The primary commercial driver for CheerpX is preserving Adobe Flash content following its scheduled deprecation at the end of 2020. Iozzelli argues that clean-room Flash runtime reimplementations like Gnash, Lightspark, and Ruffle struggle to achieve complete fidelity due to the vast surface area of undocumented edge cases. Leaning Technologies addresses this by running the original Linux Flash Player Pepper API (PPAPI) shared library (.so) inside CheerpX, using a split-host RPC architecture to bridge Pepper calls directly into browser HTML5 Canvas and audio APIs. The same core interpreter and WebAssembly JIT compiler power an alternate full-system emulation mode. Rather than intercepting Linux system calls, this mode emulates motherboard and CPU hardware components to boot unmodified operating systems. Iozzelli demonstrates this capability by booting MS-DOS and executing the game Prehistorik directly within the browser, though initial hardware support was restricted to real-mode operation. CheerpX proves the feasibility of running arbitrary x86 binaries in the browser without rewriting application logic, but its real-world performance remains bounded by WebAssembly module instantiation latency, single-threaded cooperative scheduling, and browser sandbox constraints on raw networking. While initial commercial deployment targeted enterprise Flash replacement and real-mode DOS preservation, broader general-purpose adoption hinges on expanding protected-mode virtualization and advancing WebAssembly standards for first-class dynamic JIT compilation.

精炼阅读摘要

Leaning Technologies 的 Yuri Iozzelli 提出了 CheerpX,这是一种在浏览器中运行未修改 32 位 x86 程序的 WebAssembly 虚拟化引擎,它将浏览器视作底层操作系统与硬件架构,直接执行原生 Linux ELF 二进制文件与完整操作系统。该方案主张放弃繁琐的源码重新编译或逆向重写运行时,依托解释器与动态 WebAssembly JIT 编译的双层架构,为 Adobe Flash 保留等遗留软件提供高兼容性的浏览器内运行环境。 双层执行架构与控制流图即时编译 (5:12) CheerpX 采用 C++ 编写并编译为 WebAssembly,专为执行 32 位 x86 指令设计。其执行流程分为两层:底层的 x86 解释器负责按指令顺序逐条执行低频“冷代码”并记录运行时剖析数据;JIT 编译器则将高频“热代码”基本块(Trace)直接编译为原生 WebAssembly 模块以接近原生的速度运行。当遇到定时器中断、缺页中断或系统调用(syscall)时,系统平滑退回解释器处理,随后再交由线程调度器循环。x86 指令集长度可变且复杂,CheerpX 将其拆解为底层微操作原语,例如执行 ADD 指令时,依次执行加载操作数、进行算术相加、手动计算并更新 x86 EFLAGS 标志寄存器,最后写回结果。 由于原生机器码存在任意内存跳转和非结构化控制流,与 WebAssembly 严格的结构化函数存在结构性冲突,CheerpX 在解释执行时动态构建运行时控制流图(CFG),监控间接分支目标。随着代码执行次数增加,冷代码晋升为热代码并作为入口根节点,与其相连的可达温代码被打包进同一个 WebAssembly 函数模块;若未编译路径后续变热,系统将使旧模块失效并重新生成。此外,JIT 针对虚拟化开销进行死代码消除优化:若两次连续算术运算之间没有条件分支读取状态,系统会自动剔除中间无用的 EFLAGS 标志位计算,显著减少冗余的位运算与状态写入。 浏览器沙盒限制与模块批处理权衡 (13:52) 相比原生虚拟机可以直接生成机器码并跳转执行,WebAssembly 的动态代码生成必须先包装为模块、进行异步编译并等待 Promise 解析后才能调用导出函数。这种机制带来了编译粒度的两难权衡:理论上“单函数单模块”延迟最低,但实际测试中 Chrome 在激活模块达到约 1,000 个时会直接崩溃,且跨模块共享函数表容易引发内存泄漏;因此 CheerpX 必须将多个热函数批量打包编译,但这又增加了即时编译延迟,迫使虚拟机在等待期间长时间停留在较慢的解释器中。 运行原生 x86 程序的底层环境同样受到浏览器沙盒的严格制约。x86 原生程序依赖的 POSIX 文件系统被 CheerpX 映射为基于 IndexedDB 的虚拟文件系统;而在网络方面,由于浏览器不向客户端脚本开放原始套接字(Raw Sockets)或 UDP API,CheerpX 无法直接发起底层传输。系统必须通过 WebSocket 搭配专用代理中继服务器来桥接外部 TCP/UDP 通信,或者借助 WebRTC 数据通道实现浏览器之间的端对端直连,直接运行通用原生网络服务仍面临技术阻碍。 运行原版共享库以保留 Flash 内容 (17:27) 随着 2020 年底 Adobe Flash 正式淘汰,开源社区(如 Gnash、Lightspark、Ruffle)普遍尝试逆向重写 Flash 运行时,但面对极为庞大且充满边界特性的 Flash 规范,重写方案难以达成完全兼容。Iozzelli 认为,保障完全兼容的唯一可靠途径是直接运行官方原版二进制文件。由于 Linux 版 Flash Player 并非独立可执行文件,而是基于 Google Pepper API(PPAPI)构建的共享库(.so),CheerpX 构建了一套虚拟机内外的混合宿主架构。 在该架构中,CheerpX 虚拟机内部运行原版 Linux Flash .so 及 x86 Pepper 宿主辅助模块,外部浏览器环境中则运行基于 JavaScript 的 Pepper 宿主运行时,两者通过远程过程调用与内存同步进行通信,并将渲染输出定向到 DOM 中的 HTML5 canvas 元素。网站部署时只需引入单份脚本即可自动替换 object 标签。尽管现场演示中的 Flash 游戏仍存在轻微卡顿与输入延迟,但验证了完整保留遗留系统的可行性。商业应用方面,团队已与 Flash 游戏分发平台签约,并面向依赖 Adobe Flex 与 Spark 的企业客户开展早期采用者计划。 全系统硬件仿真与通用虚拟机潜力 (20:41) 除了拦截 Linux 系统调用的用户态执行模式,CheerpX 还具备全系统仿真模式。该模式绕过系统调用翻译层,直接模拟底层主板和 CPU 硬件环境,允许未经修改的操作系统直接引导运行。全系统模式与用户态模式共享完全相同的解释器与 WebAssembly JIT 核心引擎。Iozzelli 现场演示了在浏览器中直接启动 MS-DOS 并以流畅速度游玩经典游戏《Prehistorik》。 当前全系统模式主要支持实模式运行,保护模式仍在持续开发中。针对观众关于为何不专门针对 Flash 编写轻量解释器的提问,Iozzelli 强调,构建通用的 x86 虚拟化层具有超越单一软件生命周期的长远价值:Flash 虽然即将退役,但通用 x86 虚拟化引擎能够沉淀为通用技术底座,不仅可直接应用于 Internet Archive 等数字遗产保护平台的海量历史 DOS 游戏与旧版原生软件,还能承载未来各类遗留二进制系统的迁移需求。 CheerpX 证明了利用 WebAssembly 将浏览器转化为通用虚拟化平台的工程可行性,通过直接执行原生 x86 32 位二进制代码,规避了重写复杂运行时的高昂成本与兼容性缺陷。然而,该架构目前仍受制于浏览器端异步编译带来的延迟与模块崩溃阈值,且缺乏多线程并行执行及对宿主原始套接字的直接访问支持;其商业化落地与全系统保护模式的成熟度仍有待进一步验证。

Detailed notes — zh

## Background 该视频由 YouTube 频道 WebAssembly NA 于 2020 年 2 月 18 日发布,记录了 Yuri Iozzelli 在旧金山 WebAssembly 线下聚会上的演讲。视频简介指出,演讲探讨了 WebAssembly 的能力边界,详细介绍了通过 WebAssembly 构建的浏览器内 x86 虚拟机 CheerpX,以及如何利用其 JIT 编译器在无插件的现代浏览器中直接运行未经修改的原生 Linux 版 Adobe Flash Player。 ## TL;DR Leaning Technologies 的 Yuri Iozzelli 介绍了基于 WebAssembly 的 x86 虚拟化引擎 CheerpX。该系统采用“解释器 + 动态 JIT 编译器”的双层执行架构,将庞杂的 x86 指令解构为细粒度原语,并在软件层重构控制流图以动态组装合法的 WebAssembly 函数,同时通过消除冗余标志位来削减虚拟化开销。项目重点攻克了在浏览器沙箱下运行 Linux 版 Adobe Flash Player 动态库以及 16 位 DOS 全系统模拟的场景,旨在绕过第三方逆向工程的高昂成本,直接在 Web 端无损执行原生 ELF 二进制程序。 ## Atmosphere & style 整场演讲充满硬核黑客的技术探索感与底层架构解析的严谨度。现场氛围专业而开放,面对浏览器标签页崩溃、模块数量上限及缺少硬件标志位等底层壁垒,讲者兼具坦诚的极客自嘲与务实的工程解题思路。台下参会者包括 WebAssembly 社区组成员与互联网档案馆开发者,从硬核内存模型讨论延伸到“能否运行 Doom”和“Wasm 转 Flash 再转 Wasm”的套娃玩笑,极具硅谷技术聚会特有的灵动与幽默。 ## Bulletin - [04:28](https://www.youtube.com/watch?v=7JUs4c99-mo&t=268s) Leaning Technologies 推出 CheerpX 虚拟化系统,基于 WebAssembly 在现代浏览器中直接运行未经修改的 Linux 32 位 x86 ELF 二进制文件,无需重写应用源码。 - [07:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=438s) CheerpX 采用解释器与 JIT 编译器的双层执行架构,通过动态分析控制流图(CFG)圈定热点基本块,在运行时动态拼合合法的 WebAssembly 函数。 - [17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) 首要落地应用是免插件运行 Linux 原生 Adobe Flash Player,通过模拟 Chromium PPAPI 与 Web API 桥接,规避了第三方反向工程实现 Flash 引擎的兼容性短板。 - [20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) 除了 Linux 用户态系统调用模拟,CheerpX 还具备向下虚拟化硬件的全系统模式,现场成功演示了在纯 16 位 DOS 环境下流畅运行经典游戏《史前文明》。 ## Key takeaways - [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) 将浏览器视作硬件与操作系统构架:现代网页只需具备执行引擎(JS/Wasm)与系统能力通道,即可承载通用桌面应用与游戏服务。 - [04:28](https://www.youtube.com/watch?v=7JUs4c99-mo&t=268s) 告别逐个语言开发独立运行时:CheerpX 直接从底层机器指令切入,支持执行标准 32 位 Linux ELF 可执行文件。 - [05:47](https://www.youtube.com/watch?v=7JUs4c99-mo&t=347s) 双层协同调度回路:调度器优先调用 JIT 产物,未编译代码走单步解释,遇中断、定时器或系统调用时退回解释器重新接管。 - [05:47](https://www.youtube.com/watch?v=7JUs4c99-mo&t=347s) x86 操作码拆解为基础原语:针对 WebAssembly 缺乏状态寄存器的限制,系统在软件层面显式模拟 EFLAGS 标志位的计算与回写。 - [07:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=438s) 动态发现 WebAssembly 函数边界:利用解释器统计基本块热度并追踪控制流,动态圈定连通的基本块打包编译为合法 Wasm 函数。 - [10:38](https://www.youtube.com/watch?v=7JUs4c99-mo&t=638s) 专注于虚拟化开销的 JIT 优化:假定输入二进制已被上游编译器充分优化,CheerpX 重点通过死标志消除等手段剔除未消费的标志位计算。 - [12:07](https://www.youtube.com/watch?v=7JUs4c99-mo&t=727s) 当前并发与宿主能力映射:底层依赖主线程时间片轮转模拟多任务,并将文件 I/O、内存分页等系统调用直接对接浏览器 Web API。 - [13:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=832s) 浏览器环境下的 Wasm JIT 瓶颈:Chrome 在加载约 1000 个动态模块后可能崩溃,引擎被迫采取批量打包函数编译的折中策略以权衡延迟与稳定性。 - [15:22](https://www.youtube.com/watch?v=7JUs4c99-mo&t=922s) 高度依赖前沿 Wasm 特性与沙箱绕行:系统依赖尾调用优化避免间接跳转导致栈溢出,并通过 IndexedDB 虚拟化沙箱文件系统。 - [17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) 以原生 Linux 二进制绕过 Flash 重构难题:相比 Gnash、Lightspark 和 Ruffle 等逆向方案,直接虚拟化官方播放器可确保 100% 行为兼容。 - [20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) 全硬件模拟与用户态模拟共享核心:底层复用同一套 JIT 编译器与解释器,全系统模式已实现引导 DOS 系统及流畅运行经典游戏。 - [21:42](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1302s) Wasm 社区组高度关注原生 JIT 支持:社区成员认为 CheerpX 是目前最健壮的 Wasm JIT 系统,为推进官方 JIT 提案提供了关键实践经验。 - [29:53](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1793s) 网络通信层面临沙箱阻碍:虚拟机内原生套接字无法直接映射到裸 UDP/TCP 端口,需在 WebRTC 点对点通信与 WebSocket 外部中继服务器间权衡取舍。 ## Detailed walkthrough ### [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) 将浏览器视为运行架构:从 Cheerp、CheerpJ 到 CheerpX Leaning Technologies 团队此前开发了两款核心产品:Cheerp 是一款将 C++ 编译为 WebAssembly 与 JavaScript 的编译器;CheerpJ 则是一套在浏览器中将 Java 字节码运行于 WebAssembly 与 JavaScript 上的解决方案。Yuri Iozzelli 登台介绍该公司的第三款重磅产品:CheerpX。在深入技术细节之前,他首先重塑了在浏览器中运行应用程序的核心认知模型。在他看来,开发者只需把浏览器当成另一种硬件架构与操作系统即可。 要在浏览器这一“操作系统”上运行任何应用程序,本质上只需要两大核心组件:执行引擎与底层系统能力访问接口。执行引擎负责实际执行代码,在浏览器环境中,这通常体现为原生运行的 JavaScript 与 WebAssembly;而系统能力则是让程序调取诸如渲染图形、屏幕绘制等底层库的通道,否则程序只是一味地进行纯计算,无法向外界展示任何实际产出。 在常规模式下,诸如 Emscripten 以及 Leaning Technologies 自研的 Cheerp,做法均是将 C++ 源码编译为 WebAssembly 和 JavaScript,并通过 JavaScript(或结合底层 C++ 代码)重新实现类似 POSIX API 或 SDL 的图形接口。为了说明这一点,Yuri 展示了一个通过 Cheerp 移植的 C++ 多人联网游戏示例——《Teeworlds》。为了让该游戏在浏览器内运行,他们重写了基于早期 OpenGL 1.0 的渲染管线以对接 WebGL(因为两者之间并非一对一简单映射),并使用 WebRTC 来模拟游戏原有的 UDP 网络通信。这一设计的妙处在于,通过 WebRTC 可以在纯浏览器内部同时运行客户端与服务端。 对于 Java 应用,团队则借助 CheerpJ 绕开源码,直接将 Java 字节码转译为 JavaScript。得益于 JVM 语言最终均生成统一字节码的特性,CheerpJ 天然支持 Kotlin、Scala 等全部 JVM 语言,并完整支持反射、动态类加载等高级特性。在系统库层面,他们将 OpenJDK 整体编译为 WebAssembly 和 JavaScript,从而支持包括 Swing、AWT 在内未经修改的复杂桌面程序直接在无插件的浏览器中运行,现场还演示了展示 3D 蛋白质分子结构的 Java Applet 程序《OpenAstexViewer》。 ```mermaid flowchart TD accTitle: 浏览器内应用执行的技术演变 accDescr: 对比传统源码/字节码转译与CheerpX直接运行ELF二进制的区别 subgraph SourceToWeb["源码/字节码编译路径"] A["C/C++ 源码"] -->|"Cheerp / Emscripten"| B["WebAssembly / JS"] C["Java 字节码"] -->|"CheerpJ"| B B --> D["浏览器 API (WebGL, WebRTC 等)"] end subgraph BinaryExec["CheerpX 架构"] E["Linux x86 ELF 二进制"] --> F["CheerpX 虚拟机"] F --> G["双层引擎 (解释器 + JIT)"] G --> H["浏览器原生执行"] end ``` - Yuri Iozzelli 概括了将浏览器作为目标平台的哲学理念:“We just need to see the browser as just another architecture and operating system.”(我们只需把浏览器看作另一种架构和操作系统。) - 《Teeworlds》移植案例中,UDP 网络完全通过 WebRTC 在浏览器内部进行端对端模拟,无需独立的外部原生服务器。 - CheerpJ 运行环境涵盖了完整的 OpenJDK 编译产物,使得无原生 Java 插件(Java Plugin)支持的现代浏览器能够即时启动传统 Java Applet。 --- ### [04:28](https://www.youtube.com/watch?v=7JUs4c99-mo&t=268s) CheerpX 的突破:直接执行未经修改的 x86 ELF 二进制 尽管 Cheerp 和 CheerpJ 能够分别解决 C++ 和 Java 程序的上游编译,但正如 Yuri 指出的那样,开发团队不可能针对市面上的每一种编程语言生态都单独定制开发一套专用运行时。为了兑现“在浏览器中运行任何程序”的承诺,Leaning Technologies 选择从硬件指令与操作系统底层的源头切入:直接在浏览器中执行 Linux 平台下的 x86 ELF 二进制文件。 为了验证这一概念,Yuri 现场演示了一个非常基础的 C 语言程序编译过程。他使用 gcc 编译该程序时附加了两个标志:`-static`(静态链接,将所有依赖合并为单个文件,便于直接拖放到网页中)和 `-m32`(生成 32 位机器码,目前 CheerpX 优先支持 32 位系统)。通过 file 命令确认该文件为标准的 32 位 Linux ELF 可执行程序后,他将其直接拖拽至运行着 CheerpX 的浏览器页面上,程序瞬间无损执行。 CheerpX 本身是一套采用 C++ 编写并最终编译为 WebAssembly 运行的通用、鲁棒且高扩展性的虚拟化系统。该系统设计支持两种截然不同的使用模式:既可以作为轻量级用户态运行时直接运行单个 Linux 应用程序,也可以支持启动包含完整操作系统的完整系统级仿真(Full System)。 - CheerpX 核心代码库由 C++ 编写,并通过编译器输出为纯粹的 WebAssembly 模块在浏览器中承载。 - 系统内部采用双层执行引擎(Two-tier Execution Engine):由底层解释器(Interpreter)与即时编译器(JIT Compiler)协同工作。 - 现场演示的 ELF 文件直接运行于无插件现代浏览器环境,完全依赖 CheerpX 提供的虚拟化支撑。 --- ### [05:47](https://www.youtube.com/watch?v=7JUs4c99-mo&t=347s) 双层执行架构与 x86 操作码拆解 CheerpX 内部构建了一套基于线程调度的执行回路。整体流程由负责主调度的循环(Flight Scheduler)驱动:调度器首先尝试调用 JIT 编译生成的代码;若当前目标机器码尚未编译,则退回至解释器中逐条解释执行。一旦在执行过程中触发特定中断事件——例如定时器到期、页面错误(Page Fault)或是系统调用(Syscall)——虚拟机会立即跳出当前编译代码,交回解释器重新接管上下文并重启调度循环。 这种设计背后的指导原则十分明确:对于程序中那些极少触发执行的冷代码(Cold Code),虚拟机保持由解释器单步运行以规避过度的编译开销;而一旦某段逻辑被频繁高频调用,JIT 编译器便会介入将其转化为高效的 WebAssembly 模块。 然而,x86 指令集的一大挑战是指令规模极其庞大且语义复杂。为了让 WebAssembly 能够正确且高效地模拟这些指令,CheerpX 将庞杂的 x86 操作码(Opcode)拆解为基础的原语操作(Primitives)。例如对于一条基础的 `ADD` 指令,引擎会将其解构成数个子步骤: 1. 从寄存器或立即数中加载操作数; 2. 执行实际的算术加法; 3. 手工计算并更新硬件状态标志位(EFLAGS); 4. 将最终结果写回目标存储位置。 这种手工更新标志位的操作是由 WebAssembly 本身的硬件语义限制决定的。在真实的 x86 物理 CPU 上,每一条算术计算指令都会在硬件级别由电路自动计算并写回标志位;但 WebAssembly 指令集中完全缺失对应的硬件标志状态,因此每一个诸如溢出(OF)、零标志(ZF)、符号(SF)等状态,都必须由虚拟机通过额外的指令逻辑手动算齐。类似地,对于 `CMP`(比较)指令,引擎将其还原为算术减法,但在拆解原语时会直接丢弃减法结果,仅保留标志位的计算与写回。 ```mermaid flowchart TD accTitle: CheerpX 执行引擎循环流程 accDescr: 解释器与JIT编译代码的协同执行回路 A["线程调度器 (Thread / Flight Scheduler)"] --> B{"代码是否已编译?"} B -- 是 --> C["执行原生 WebAssembly 代码"] B -- 否 --> D["调用解释器 (逐条单步执行)"] C --> E{"触发事件 (系统调用 / 中断 / 定时器)?"} E -- 是 --> D E -- 否 --> A D --> F["更新控制流图与执行频次计数"] F --> A ``` - x86 指令在虚拟机中不直接进行一对一翻译,而是被拆解成独立的底层细粒度原语(加载、运算、算标志、写回)。 - WebAssembly 缺失原生状态寄存器,导致所有硬件标志位逻辑必须在软件层面模拟:“Because in the hardware every arithmetic operation automatically computed flags and we don't have that in WebAssembly so we need to manually compute flags and then store the result.”(因为在硬件中每个算术操作都会自动计算标志,而在 WebAssembly 中我们没有这个机制,所以我们必须手动计算标志然后存储结果。) --- ### [07:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=438s) 控制流图重构与动态发现“函数” 在解释器单步执行 x86 指令的同时,它不仅负责维系状态机的演进,还会默默收集剖析数据,构建程序运行时的辅助数据结构。其中最核心的结构就是程序的控制流图(Control Flow Graph, CFG)。随着程序的推进,解释器逐步探明代码的生长拓扑,并重点追踪间接跳转(Indirect Jumps)的实际落点。由于在编译后程序的绝大多数执行周期中,间接跳转往往指向固定位置,捕捉这些跳转特征为后续优化提供了关键先验信息。 控制流图的基础构成单元是轨迹(Trace,在常规编译器领域等同于基本块 Basic Block)。一个基本块是一组单进单出的线性指令序列,其末尾必定挂载一个跳转、分支判断或调用(Call)指令。由于原始 x86 二进制在初始状态下只是一整块未知的无结构内存,虚拟机对其内在逻辑一无所知,只能通过解释器的实际触达来绘制这幅跳转图谱。 当某些基本块被反复执行足够多次并判定为“热”(Hot)时,JIT 编译器便会尝试将其编译为 WebAssembly。然而此时面临着一个极为棘手的底层架构冲突:WebAssembly 的最小执行单元必须被封装在合法的“函数”(Function)内,它无法脱离函数去无规则跳转或执行任意的代码片段。 在原生二进制代码中,根本不存在高级语言中结构清晰的“函数”实体。虽然指令集提供了 `call` 和 `ret`,表面上看 `call` 的目标地址至 `ret` 返回处可视为一个函数范围,但现实中的汇编代码完全不受此约束。开发者或优化编译器完全可以让 `ret` 孤立存在、在没有 `call` 的情况下随意跳转,甚至采用自修改代码(Self-modifying Code)。 针对这一脱节,CheerpX 创新性地采用“动态边界发现”策略: 1. 解释器监控所有轨迹块的执行频率,轨迹初始为冷(Cold),随调用增多变为温(Warm); 2. 一旦某一条轨迹的命中计数达到阈值升级为热(Hot),CheerpX 便将其锁定为待生成 WebAssembly 函数的入口点(Entry Point); 3. 引擎自该热点出发沿控制流图遍历,将所有由此可达且状态至少处于“温”的基本块一并圈定; 4. 将这整张局部的基本块网络直接打包编译为一个独立的 WebAssembly 函数并替换挂载回去; 5. 如果后续代码中有新的轨迹转温或转热,引擎可以在后续周期内重新发现并动态增量更新该 WebAssembly 函数。 - Yuri 指出原生代码的自由度使得高级函数概念荡然无存:“A function is something that is a target of a call and it ends when you get the ret instruction. But the code is actually not required to do that at all, it can do whatever it wants.”(函数通常是 call 指令的目标并在遇到 ret 指令时结束。但原生代码实际上完全不需要遵守这一点,它可以随心所欲执行任何操作。) - 动态发现机制确保了即使遭遇极端优化的乱序汇编或混淆代码,CheerpX 依然能拼合出在语义上合法合规的 WebAssembly 函数结构。 --- ### [10:38](https://www.youtube.com/watch?v=7JUs4c99-mo&t=638s) JIT 优化与冗余标志位消除 在即时编译阶段,CheerpX 会挂载一系列代码优化通道(Optimization Passes)。Yuri 指出,CheerpX 的优化核心假设与传统原生编译器大相径庭:系统假定输入的 x86 二进制代码本身已经由 GCC、Clang 或 Rust 等上游编译器进行了充分的架构层优化,指令排布在逻辑层面已经逼近最优。因此,CheerpX 的 JIT 编译器坚决不做冗余的算法级二次优化,其所有优化算力全神贯注于**消除虚拟化本身所带来的固有开销**。 在各类虚拟化开销中,最耗费算力且最主要的优化点正是前述的“标志位更新计算”。在绝大多数 x86 指令流中,连续发生的算术操作所生成的标志位并不会真正被后续代码消费。 现场以一组典型的指令组合为例展开剖析:假设一段程序中出现了一条 `ADD` 指令,紧接着跟了一条 `SUB`(减法)指令。在展开为原语后,`ADD` 会生成计算并保存标志位的操作,其后紧随 `SUB` 计算并重新写入新标志位的操作。如果在这两者之间没有任何诸如条件跳转(如 `JZ`、`JNZ`)或读取标志的操作,那么前一个 `ADD` 指令所花费的一切标志位计算与内存存储都是彻底无用的。 优化编译器在进行数据流活性分析后,一旦判定标志位寄存器在被下一次覆写前处于“未消费”状态,就会强行剔除该 `ADD` 指令展开序列中的标志位计算原语。此外,寄存器值的冗余重载(例如前面指令已将 EBX 载入,后续原语无须重复读取物理状态)也会被系统化剪枝,从而生成极度精简的 WebAssembly 机器码。 - Yuri 总结虚拟化优化的核心着眼点:“Our core assumption is that the code is already optimized, right?... But we want to optimize all the overhead of the virtualization.”(我们的核心假设是代码本身已经被优化过了……我们真正想要优化的是虚拟化本身所产生的一切开销。) - 死标志消除(Dead Flag Elimination)与冗余加载消除使得 JIT 生成的 WebAssembly 执行效率相较于单步解释产生数量级层面的提升。 --- ### [12:07](https://www.youtube.com/watch?v=7JUs4c99-mo&t=727s) 多线程调度、系统调用拦截与宿主环境限制 在多任务并发支持方面,CheerpX 现阶段对多线程的支持采用协作式/时间片轮转方案(Time Slicing)。当前所有的虚拟机逻辑均挂载在浏览器主进程的单一真实执行线程(Main Thread)中。为了模拟并发,虚拟机为每个逻辑线程分配一段离散的时间片,借助简单的轮询算法(Round-robin)或其他调度策略在各个虚拟线程上下文之间进行分时切换。 除了常规线程,CheerpX 也原生支持 Linux 进程模型(Processes)。在虚拟机内部,进程的本质与线程相同,唯一的区别在于各个进程拥有相互隔离、非共享的虚拟地址空间。Yuri 透露,团队未来的演进目标是在底层将虚拟线程拆分绑定到多个独立的 Web Worker 上,以此在浏览器中解锁真正的硬件多核心并行能力,但这在当时尚未完全落地。 对于纯计算之外的系统交互,CheerpX 将底层系统库的对接抽象为标准的 Linux 系统调用(System Calls)。执行引擎在运行中时刻捕获应用程序触发的系统调用中断,并利用浏览器的原生 Web API 将其就地映射实现。已经获得完整模拟的接口涵盖: - 文件系统调用:如 `open`、`read`、`write`、`close`、`seek` 等; - 时间控制相关调用; - 内存分页管理:模拟操作系统的内存映射(`mmap`)等机制。 在分享的第一部分尾声,Yuri 坦言当前的 WebAssembly 规范与各浏览器的底层实现依然对虚拟化技术构成了多维度的约束。尽管 CheerpX 的核心理念在宏观上与 QEMU 这类老牌原生仿真软件的动态翻译机制异曲同工,但在受限的浏览器沙箱与 WebAssembly 指令标准之内,将其平稳铺开并维持高吞吐,仍然逼近了当前 Web 技术的物理极限。 - CheerpX 模拟的多任务系统在底层暂时由单主线程承载,通过时间片切分运行。 - Linux 进程在 CheerpX 内具备独立的虚拟地址空间划分。 - 系统调用拦截全面覆盖 I/O、内存分页(Paging / `mmap`)与时钟抽象,由浏览器环境的宿主能力提供最终落地支持。 ### [13:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=832s) WebAssembly JIT 编译的权衡与浏览器限制 在原生 JIT 编译器中,程序可以直接生成机器码并跳转执行,但 WebAssembly 根本无法做到这一点。在 WebAssembly 的体系下,开发者不能凭空生成一段字节码并直接跳转进去运行,而是必须将生成的函数打包放进一个模块(module)中,随后调用浏览器 API 异步编译该模块。由于编译是一个异步过程,系统必须等待一段时间才能拿到模块实例,进而调用该模块内导出的函数。 这种机制带来了一个极其棘手的权衡问题。从理想状态来看,最优雅的方案是“每个模块只放一个函数”:一旦 JIT 引擎决定编译某个热点函数,就立即把它包装成独立模块并编译,这样单次编译时间极短、速度极快。然而现实中的浏览器施加了严苛的技术限制。Yuri Iozzelli 指出:“Chrome after we have like 1000 modules at the same time just the browser tab crashes.”(Chrome 在同时加载约 1000 个模块后,整个浏览器标签页就会直接崩溃)。为了避开崩溃,CheerpX 必须采用批处理策略(batching),把若干个函数合并到一个模块中统一编译。 这种折中带来了新的挑战:如果每个模块打包的函数过多,编译延迟就会显著上升;而如果在等待编译期间让执行引擎在解释器里停留过久,又会严重拖慢整体性能。因此团队必须在批处理大小与延迟之间小心翼翼地寻找平衡。此外,浏览器当前的 WebAssembly 实现还存在底层缺陷,例如在跨模块共享函数表(function tables)时,甚至会触发浏览器本身的内存泄漏问题。不过 Yuri 乐观地认为,随着浏览器底层引擎的持续演进,这些粗糙的边缘问题未来大概率无需他们亲自动手修补就能自然解决。 ### [15:22](https://www.youtube.com/watch?v=7JUs4c99-mo&t=922s) 依赖的 WebAssembly 实验性特性与沙箱限制 CheerpX 高度依赖若干当时仍处于实验阶段的 WebAssembly 新规范,其中最核心的是线程(threads)以及共享内存缓冲区(`SharedArrayBuffer`)。虽然虚拟机在设计上并未用多线程去并发跑客户机的通用计算指令,但多线程对后台定时中断调度(schedule interrupts)和时钟管理而言是必不可少的底层支撑。此外,CheerpX 还深度依赖尾调用优化(tail calls)。如果用常规的 WebAssembly 函数调用去模拟 x86 的间接跳转(indirect branch),调用栈会因无休止的嵌套而迅速耗尽并爆栈,而尾调用优化能彻底消除栈溢出风险,使间接跳转得以完美运转。在当时,尾调用仅在 Chrome 中实验性支持,Firefox 尚在实现中。 除了底层指令集扩展,将原生应用搬入浏览器还必须面对严格的沙箱隔离环境,开发者必须调低某些不切实际的预期。例如,原生程序不能指望像在宿主机上那样任意读写物理文件系统,为此 CheerpX 基于浏览器的 IndexedDB 虚拟构建了一套沙箱化文件系统。网络层面的限制则更加棘手:如果原生应用试图在底层直接打开任意的本地网络端口(例如裸 UDP 端口),浏览器根本没有开放对应的底层 API。CheerpX 只能通过 WebSocket 进行中继绕过,但 WebSocket 并不是真正的原生套接字,必须在远端搭建专门的代理服务器(proxy server)来转发流量;即便是标准 HTTP 请求,也经常会撞上浏览器的跨域资源共享(CORS)限制。 ### [17:27](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1047s) 核心应用场景:无需插件运行 Adobe Flash Player 既然技术难度如此之高,CheerpX 的首要落地场景究竟是什么?Yuri Iozzelli 直言,当下最核心的使命就是在无需任何浏览器插件的前提下,直接在现代浏览器中原生运行 Linux 版 Adobe Flash Player。2020 年正值 Adobe 宣布正式终结 Flash 支持的节点,但互联网上依然留存着海量的 Flash 遗留应用与课件。社区中虽有诸如 Ruffle 等开源重新实现方案,但在 Leaning Technologies 团队看来,通过反向工程重新写一个播放器根本无法做到真正 100% 的功能对齐。Yuri 提到,早在 Ruffle 之前就存在 Gnash 和 Lightspark 等老牌开源项目,而他的老板正是 Lightspark 的原始作者: > “And actually my boss is the original author of Lightspark and he's convinced that this is the only way to have everything working for real and yeah because we just run the original and nothing can go wrong right?” ```mermaid flowchart TD accTitle: CheerpX Flash Player 运行架构 accDescr: 展示网页、JavaScript 运行时、Pepper 宿主应用与 Flash 原生动态库之间的交互 subgraph BrowserEnv["现代浏览器页面"] ObjTag["原生 object 标签"] -->|"CheerpX 脚本替换"| Canvas["HTML5 Canvas 渲染目标"] JSRuntime["CheerpX JS 运行时<br/>(基于 Web API 实现 Pepper API)"] end subgraph CheerpXVM["CheerpX x86 虚拟化环境"] HostApp["x86 宿主应用程序<br/>(内部 Pepper 客户端适配)"] FlashSO["原始 Linux Flash Player<br/>(libpepflashplayer.so 共享库)"] HostApp <-->|"dlopen 加载与函数调用"| FlashSO end JSRuntime <-->|"双向通信 / 状态同步"| HostApp Canvas <--- JSRuntime ``` 在具体实现上,Flash 并非一个独立的执行文件,而是一个动态共享库(Shared Library),它必须依赖宿主应用程序加载并提供接口支持——具体而言,就是 Google Chrome 团队设计的 Pepper API(PPAPI)。为此,团队编写了一个编译为 x86 代码的专用宿主程序,与 Flash Player 一同置于虚拟化环境中运行。该宿主程序一部分运行在 x86 虚拟机内部加载 Flash 共享库,另一部分通过 JavaScript 暴露在虚拟机外部,利用原生 Web API 实现了整套 Pepper API 规范。对终端用户而言,接入极其傻瓜化:网页开发者只需在页面中引入一段 CheerpX 的脚本,该脚本会自动检索页面内所有的 `<object>` 标签并无缝替换为受 CheerpX 运行时控制的 `<canvas>` 画布。随后 Yuri 在现场打开了一个全屏运行的 Flash 动作游戏 Demo,尽管帧率尚有肉眼可见的卡顿,但游戏逻辑、音频以及交互完全能够正常运转。 ### [20:41](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1241s) 全系统硬件模拟模式与 DOS 游戏演示 除了以系统调用拦截为主的用户态模拟外,CheerpX 还具备开篇提及的第二种运行模式——全系统硬件模拟(Full System Emulation)。在该模式下,CheerpX 不再拦截 Linux 系统调用,而是向下真实模拟底层 x86 物理硬件外设,并在其上直接引导运行完整的操作系统。此时底层执行引擎依然保持一致,复用了相同的动态 JIT 编译器、解释器以及热点检测逻辑。 当时该全系统模拟模式已经能够成功引导并稳定运行纯 16 位 DOS 系统,尽管对 32 位保护模式(Protected Mode)的支持仍在攻关研发中。为了直观展现系统级模拟的能力,Yuri 在现场演示了经典的 DOS 动作冒险游戏《史前文明》(*Prehistorik*)。与略显沉重的 Flash 相比,这款 DOS 游戏的图形渲染流畅度、按键响应速度以及即时音效表现都极为优异,几乎达到了原生 DOSBox 的运行节奏,赢得了现场观众的一致赞叹。 - 全系统模式不拦截系统调用,而是向下虚拟化 CPU 寄存器、内存寻址和硬件中断。 - 无论是在用户态模拟还是全硬件模拟中,底层 JIT 编译器与解释器架构完全复用。 - 现场演示的《史前文明》(*Prehistorik*)运行在由 CheerpX 虚拟出的纯 DOS 环境中,运行帧率表现十分流畅。 ### [21:42](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1302s) 商业化展望、早期采用者计划与 WebAssembly CG 讨论 谈到 CheerpX 的发展蓝图,Yuri 坦言性能依然是首要攻关方向。Flash 演示中表现出的卡顿说明 JIT 引擎与桥接层还有很大的优化空间,但团队深信“解释器 + JIT 编译器”的分层架构具备极高的可扩展性与鲁棒性。商业落地方面,Leaning Technologies 已经与一家大型 Flash 游戏分发商达成了正式合作协议,计划将该技术全面部署到生产级游戏门户中;同时,企业级软件市场也展现出强烈的挽救遗留资产的需求,大量基于 Adobe Flex 和 Spark 框架开发的企业内网系统迫切需要脱离 NPAPI 插件生存。为此团队正式对外开放了早期采用者计划(Early Adopter Program),并预计在当年夏季(2020 年夏天)将性能打磨至令人满意的成熟水准。 在随后的讨论中,现场来自 WebAssembly 社区组(Wasm CG)的参会者给予了极高评价。该成员指出,业内虽然早就知道可以通过输出字节码、动态实例化模块并导出函数的方式在 Wasm 中“曲线实现”JIT,但此前从来没有人能把这套链路做到 CheerpX 如此强大、健壮的程度。Wasm 社区组目前正高度关注将 JIT 编译纳入官方提案,以期在 Wasm 标准中提供一等公民级别的 JIT 原生支持: > “This is definitely the most robust WebAssembly JITing system I have seen yet so I think your experience with that would be very useful to hear about in the CG and so hopefully we can get a proposal started in that direction.” Yuri 对此积极回应,表示次日将参加 Wasm 社区研讨会,非常渴望与标准委员会成员面对面深入交流 CheerpX 在动态编译与内存管理上踩过的各类技术坑。 ### [24:52](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1492s) 问答环节:Web 归档恢复、CheerpJ 垃圾回收机制与扩展限制 在观众问答环节,讨论聚焦于实际应用场景与底层内存模型。一位观众提问,互联网档案库(Internet Archive)中沉淀了极其庞大的历史 Flash 网站,能否利用 CheerpX 实现免修改自动唤醒?Yuri 确认这在原理上完全可行:只要在网页中动态注入 CheerpX 的脚本,就能自动拦截并激活旧内容。虽然当前版本面对复杂未知的 Flash 内容仍有一定概率让运行时崩溃,但整体兼容性正在快速爬坡。就如同 Leaning Technologies 之前针对 Java Applet 研发的 CheerpJ 一样,他们完全可以提供一个专属的 Chrome 浏览器扩展程序,在用户访问任何互联网档案馆页面时自动注入脚本,使旧应用原地满血复活。 随后另一位观众抛出了关于垃圾回收(Garbage Collection, GC)的硬核技术问题:鉴于当前的 WebAssembly 虚拟机规范根本无法让外部代码直接扫描和自省(inspect)Wasm 执行栈,CheerpX 及其姐妹产品 CheerpJ 究竟是如何妥善处理垃圾回收的?Yuri 澄清了其中的技术实现架构: - 在运行 Java 应用的 CheerpJ 中,Java 字节码主要被直接转译成了 JavaScript 代码,因此可以完全借道底层浏览器的 JavaScript 引擎垃圾回收器来自动管理生命周期。 - CheerpJ 中用到 WebAssembly 的部分主要是 Java 原生方法(Native Methods/JNI),Java 用户层代码目前并不处于纯 Wasm 内部,因此根本不需要虚拟机自研一套独立的内存栈扫描器。 - 团队正密切跟踪 WebAssembly GC 提案的进展,一旦 Wasm 原生 GC 落地,后续才会把完整的托管对象栈全面迁移至纯 WebAssembly 内部。 最后,有观众追问为什么当前的 CheerpJ Applet Runner 扩展程序只支持 Chrome 而没有上架 Firefox。Yuri 笑着解释称,这并非有任何跨浏览器壁垒,而是因为该扩展为了在页面生命周期极早期完成 DOM 与运行时注入,不得不依赖一系列 Chrome 特有的底层扩展 API 与 Workaround 补丁;受限于团队目前极度精简的研发人手,暂时还没有足够的工程资源去为 Firefox 专门调试适配,但其核心引擎本身是完全具备跨浏览器通用能力的。 ### [28:44](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1724s) JIT 追踪优化与基于实际执行值的特化探讨 在提问环节开头,一位听众探讨了 CheerpX 前端语言编译成追踪(traces)的机制。他指出,当 JIT 运行时,系统其实是在具体的运行时数值(concrete values)上执行程序;凭借这些在编译期未知但在运行期确定的具体值,理论上可以对追踪图(trace graph)进行特定的变异与优化。他询问 CheerpX 是否已经在 JIT 中应用了这类动态值优化。Yuri Iozzelli 承认这是一个非常技术化但十分有价值的方向,并坦诚回答:“No, we are not doing that, but that's actually a cool thing to explore.” 虽然当前版本尚未引入这种基于运行时具体值的特化优化,但这确实是后续值得深入探索的方向。 ### [29:53](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1793s) 网络栈与套接字映射:WebSocket 还是 WebRTC? 另一位听众提出,当在 CheerpX 中运行完整的 DOS 或 Linux 原生二进制文件时(例如在浏览器中运行一个原生的 Linux Web 服务器),虚拟机是否能够直接打开原始套接字(raw socket)。Yuri Iozzelli 解释说,CheerpX 目前尚未实现网络栈,其核心困难在于浏览器沙箱与原生网络的协议不匹配:Linux 应用程序尝试打开的是底层的原始 TCP/UDP 套接字,而浏览器环境只提供了有限的高层 API,主要是 WebSocket 和 WebRTC。系统必须在它们之间做出权衡与映射。 ```mermaid flowchart TD accTitle: 网络映射架构方案 accDescr: 展示原生套接字在浏览器环境下的两种中继与通信路径选择 subgraph NativeApp["虚拟机内应用"] App["Linux 原生程序 / Flash"] -->|"打开原始套接字"| Socket["POSIX Socket API"] end subgraph SolutionA["方案 A: 跨原生互通"] Socket -->|"映射"| WS["WebSocket 客户端"] WS -->|"中继通信"| Relay["外部中继服务器 Relay Server"] Relay -->|"常规套接字"| ExtNative["外部原生网络 / 客户端"] end subgraph SolutionB["方案 B: 纯浏览器对等"] Socket -->|"映射"| RTC["WebRTC DataChannel"] RTC -->|"P2P 互联"| BrowserPeer["其他浏览器实例 (如多人对战)"] end ``` Yuri 深入分析了这两种路线的取舍: - **WebRTC 方案**:虽然可以实现免中继的直接通信,但会将应用严格限制在运行相同 WebRTC 后端的浏览器实例之间。如果一个实例在浏览器中运行,另一个在别人的电脑上以原生方式运行,两者之间就完全无法直接通信。这种方式非常适合多人网页游戏等纯浏览器端互通的场景。 - **WebSocket 方案**:为了与现有的原生服务器或原生客户端互通,必须引入一个外部的中继服务器(relay server)。浏览器端通过 WebSocket 连接到中继服务器,再由中继服务器建立常规的原生套接字转发流量。 Yuri 总结道,目前尚无通用解法,对于应用场景(如 Flash 游戏还是通用网络程序)必须“case by case”逐案评估。现场听众随即追问 Chrome 团队推进的 Project Fugu(旨在为网页引入原生操作系统 API,包括原始连接、套接字与 UDP 支持)的最新进展。提问者提到虽然旧的 Chrome Apps 支持底层套接字,但 Fugu 中的 Raw Sockets 提案推进还很有限;Yuri 也确认除 Chrome Apps 等特定环境外,目前标准网页平台依然无法随意打开原始套接字。 ### [32:33](https://www.youtube.com/watch?v=7JUs4c99-mo&t=1953s) 为什么模拟 x86 而不是重新实现 Flash 虚拟机? 一位参会者对整个项目的技术选型提出了关键疑问:如果最终的核心目标之一是在现代浏览器中运行 Flash 文件(.swf),为什么不直接编写一个 Flash/ActionScript 虚拟机解释器,反而大费周章去实现一套复杂的 x86 架构解释与 JIT 引擎? Yuri Iozzelli 对此作出了详细剖析。历史上已经有多次尝试重新实现 Flash 运行时的开源项目,例如早期的 Gnash、后来的 Lightspark,以及当时新兴的基于 Rust 的 Ruffle: - **Flash 的极高复杂性**:Flash 经过数十年的演进,积累了极度庞杂且未完全公开的边界行为。像 Gnash 开发了多年也无法完全兼容所有内容,Lightspark 在兼容性上做出了巨大努力但同样存在局限。Yuri 直言对第三方逆向重写的兼容性持悲观态度:“We think that it will never be exactly the same as the original Flash Player.” - **通用性与开发效率**:2020 年正值 Adobe 计划在年底彻底停止支持并移除 Flash 播放器之际,Leaning Technologies 选择直接在 CheerpX 的 x86 虚拟机上运行 Adobe 官方未修改的原生 Linux 版 Flash Player 二进制文件,从而确保 100% 的原生兼容性。此外,一套通用的 x86 引擎不仅解决了 Flash 的燃眉之急,未来还能直接复用至运行其他各种原生程序,避免了再次陷入“又造一个 Flash 解释器却依然漏洞百出”的重复造轮子泥潭。 ### [34:24](https://www.youtube.com/watch?v=7JUs4c99-mo&t=2064s) DOS 模拟、硬件虚拟化与 Internet Archive 合作意向 针对系统底层的实现细节,有听众询问既然底层运行的是原生 Flash,系统是否在后台重新实现了类似 Windows 的环境。Yuri 明确纠正指出,他们使用的是原生 Linux 下的 Flash 播放器二进制文件,系统提供的是 Linux 系统调用层面的转换支持。 该听众紧接着询问了关于 DOS 硬件模拟的进展,比如是否可以直接挂载一个包含 MS-DOS 系统和游戏的磁盘镜像文件直接引导启动,以及对各种老旧显示模式(video modes)的支持程度。Yuri 回应道: - CheerpX 已经能够支持相当一部分 DOS 软件正常运行,但他无法承诺所有边缘的特殊硬件调用和显示模式都能完美工作。 - 公司的开发重心当前主要集中在更上层的 Linux 原生程序支持上,不过好在底层的核心模块——包括 JIT 编译器与 x86 指令执行引擎——是完全共享的基础设施。 提问者随即透露自己正在为 Internet Archive(互联网档案馆)从事合约开发,而互联网档案馆拥有全球最庞大的 DOS 历史游戏在线收藏库(目前基于浏览器的模拟器运行),表达了希望在会后与 Yuri 深入对接、探索 CheerpX 落地可能的强烈兴趣。 ### [36:18](https://www.youtube.com/watch?v=7JUs4c99-mo&t=2178s) 尾声:Doom 挑战与“WebAssembly 转 Flash”的套娃玩笑 在问答环节的最后,现场气氛转入轻松幽默。有听众抛出了黑客文化中经典的试金石问题:“Can you play Doom on it?”(能用来玩《毁灭战士》吗?)Yuri 笑着坦言他们还没有亲自尝试过在 CheerpX 上跑《Doom》,但认可这是检验任何虚拟机的终极测试。 随后,另一位参会者提到了之前 WebAssembly 峰会上的一场演讲——有人演示了将 WebAssembly 编译/转换回旧版 Flash 文件的离奇工具链。如果把那个工具和 CheerpX 串联起来,就意味着可以先把 WebAssembly 转换成 Flash 文件,再放到 CheerpX(一个基于 WebAssembly 的 x86 虚拟机)里跑 Linux 版 Flash Player,把 Flash 再解释成 WebAssembly 执行。全场在一片欢快的“终极套娃”笑声与掌声中结束了演讲。 ## Notable quotes > [00:29](https://www.youtube.com/watch?v=7JUs4c99-mo&t=29s) We just need to see the browser as just another architecture and operating system. — Yuri Iozzelli ## Mentioned CheerpX, Cheerp, CheerpJ, Yuri Iozzelli, Leaning Technologies, WebAssembly, WebAssembly CG, JavaScript, Emscripten, Teeworlds, WebGL, WebRTC, OpenJDK, OpenAstexViewer, Linux, ELF, x86, QEMU, Chrome, Firefox, IndexedDB, WebSocket, Adobe Flash Player, PPAPI, Ruffle, Gnash, Lightspark, Prehistorik, DOS, DOSBox, Adobe Flex, Spark, Internet Archive, Project Fugu, Doom ## Video description Original event: https://www.meetup.com/wasmsf/events/268096535/ WebAssembly is a new web technology that promises to revolutionize the web ecosystem. In this talk, I will explore the very limits of what is possible to do with it, by describing CheerpX: A WebAssembly-based x86 virtual machine in the browser. Similarly to modern native virtual machines, CheerpX features a Just-In-Time compiler capable of producing optimized WebAssembly code at runtime. This has the potential of speeding up the execution considerably and unlocking the possibility of running real-world native applications in the browser. One such application that we are currently working on at Leaning Technologies to showcase the CheerpX potential is the Adobe Flash Player for Linux: This allows running unmodified Flash content on modern browsers without plugins. [Source](https://www.youtube.com/watch?v=7JUs4c99-mo)

Source quotes

[0:29] Yuri Iozzelli: We just need to see the browser as just another architecture and operating system.

Source quotes

[0:29] Yuri Iozzelli: We just need to see the browser as just another architecture and operating system. [16:19] Yuri Iozzelli: WebSockets are not real sockets, so you still need some kind of proxy server that relays the WebSocket traffic.

Original transcript

[0:29] we have two main products if one is chip [0:32] is a C++ web assembly and JavaScript [0:34] compiler and the other strip J is a [0:37] solution to run Java and WebP assembly [0:39] in JavaScript and today I'm going to [0:41] talk about our third project that is two [0:43] things so first let me clarify what I [0:47] mean why running any application in the [0:49] browser we just need to see the browser [0:51] and just another architecture and [0:53] operating system so in order to run an [0:55] application in it we just need two main [0:58] pieces an execution engine so something [1:01] that can run our code and in the case of [1:04] the browser is JavaScript and web [1:06] assembly are our native code and also we [1:10] need to access system capabilities some [1:13] lower level libraries that actually show [1:15] something on the screen otherwise is [1:16] just making computation without showing [1:20] anything and so for example how do we [1:24] run application in the browser [1:26] I guess this most of you know about [1:28] Emscripten and chill feeds another [1:30] supress platform pilar so in this case [1:33] we take c++ source code and we compile [1:36] it to web assembly in Java stream and [1:38] then we also need to implement some [1:41] lower leverage API so for example it [1:43] causes API [1:44] SDL for showing graphics on the screen [1:46] and we need to implement those with [1:50] JavaScript if user script and I which is [1:53] actually you can use C++ also for the [1:55] double side but anyway you need to do [1:58] this kind of work and this is just a [2:01] demo to illustrate this this is a game I [2:04] parted with cheer it's a faulty words [2:07] it's a C++ multiplayer game and in this [2:10] case we need to implement the OpenGL in [2:15] this game runs with OpenGL one so there [2:18] are some work to do to make it work with [2:20] WebGL because it doesn't map really want [2:23] one also this game is multiplier so it [2:25] uses UDP for networking and you don't [2:29] have that in the browser but if you [2:30] emulate somehow in this case I use [2:34] WebRTC you can make it work and the nice [2:38] thing about this demo is that since I [2:40] use WebRTC both client and server [2:43] in the browser so you can just run [2:44] everything in your browser you can go to [2:47] that web page if you want to try it but [2:51] what if I want to run a java application [2:54] for example in this case you can you [2:56] chip tray and chip Jake compiled Java [2:59] bytecode not source code into JavaScript [3:02] and it supports in this way like all the [3:06] jvm languages because they are all [3:08] compiled to the same bytecode and we [3:11] also support all the Java features or [3:13] reflection will be tried in dynamic [3:15] reloading and for the library side of [3:17] things what we do is we compile the open [3:20] JDK into web assembly and JavaScript and [3:24] this way we can run unmodified [3:28] application for example and you swing or [3:30] a WT disconnects and I also have an [3:34] example of death if it runs [3:41] [Music] [3:46] yeah like demos right okay all right so [3:53] this is called open a stacks it's a Java [3:58] applet and it's visual lights 3d [4:03] molecules right protein and stuff like [4:05] that and yeah it was a job offer but I [4:08] don't have Java Java plug-in browser it [4:11] just runs out of the bots with cheap [4:15] Jade but yeah I promise you to run any [4:19] application so I cannot just implement [4:21] every single runtime what if we just run [4:25] a Linux elf binary and solve the problem [4:28] so let's try to do that so yeah I have a [4:34] very simple C program [4:37] I can compile it you can see I have a [4:43] couple of flags here a static just [4:45] because so we have one file that I can [4:48] drop in the browser later and m32 is [4:51] because we support or a 32-bit for now [4:54] so I can run this [4:56] and I can check that it's actually an [5:00] elf 32-bit executable and I can just [5:07] choose it from here and it runs the [5:11] browser [5:12] so what did I just do so no disrespect [5:18] exactly as you pack since generic robust [5:22] and scalable technology [5:23] so I'm binary code in the browser it's [5:25] written in C++ and compiled to [5:27] webassembly [5:28] and it features a two-tier execution [5:30] engine so there is an interpreter and a [5:33] JIT compiler and it also supports two [5:35] different runtime so you just saw an [5:38] Linux application running but we also [5:40] support running a full system with an [5:42] operating system on top so this is a [5:47] schematic of execution engine so we have [5:50] a fried schedule that calls including [5:52] the interpreter and if the code that we [5:55] want to run is compiled then that will [5:57] just skip and run the web assembly code [6:01] directly and if something happens for [6:03] example there is a timer if another [6:06] fault or a Cisco we can just go back to [6:08] the interpreter again and restart the [6:11] cycle and the idea is that code that the [6:15] code that is rarely executed can just go [6:18] in the interpreter and code that is [6:20] executed a lot is compiled to a person [6:23] and so accurately fix it as a pretty [6:30] large number of op codes and what we [6:32] actually do is we decompose these up [6:34] cots in primitives so this is an example [6:37] it is an add instruction and we [6:40] decompose it to this it's building [6:43] blocks or like loading the arguments [6:45] from immediate register or from an [6:48] immediate register then do the actual [6:50] add operation that includes completing [6:53] the flags because in the yard where [6:55] every automatic operation automatically [6:58] computed flags and we don't have that in [7:01] web assembly so we need to manually [7:02] compute flags and then store the result [7:05] and here's another example so this is a [7:08] comparison it was actually [7:10] subtraction and where we don't care [7:13] about the result but only deflects so [7:18] the interpreter like I said executes one [7:22] instruction at a time and while doing [7:24] that it also builds some auxiliary [7:27] structures useful for cheating the code [7:29] later so mainly the control flow graph [7:32] of the program so we discover well the [7:36] code grows while we run it and also we [7:44] we check where the indirect jumps go so [7:48] usually an indirect jump always go to [7:51] the same place so we know this kind of [7:53] information and we can use it later [7:55] and so this counter flow graph is made [7:58] of traces they're also called basic [8:01] blocks in other contexts and there is a [8:04] linear sequence of instructions which on [8:07] a branch or a color so a linear sequence [8:09] and then you go somewhere else so this [8:14] is just a visual explanation all of this [8:16] work imagine this is like the memory of [8:20] the x86 binary so this is just code we [8:24] don't really know what's in there at the [8:25] beginning and this code at some point [8:28] will jump some other places so this is [8:32] like imagine you can jump anywhere he [8:34] wants and we track that information and [8:37] we build this counter flow graph of [8:39] these traces and so when it tray when a [8:47] trace is often off so it's I've been [8:49] around enough times we decide to compile [8:53] it to AB assembly but there is a problem [8:54] here how do we run with assembly we just [8:58] run function so you cannot just run a [9:01] piece random piece of code to put your [9:03] code in a function and what is the [9:06] function in native code I mean there are [9:08] the call and write instructions so you [9:10] can say that a function is something [9:12] that is a target of a call and it ends [9:15] when you get the right instruction but [9:17] the code is actually not required to do [9:19] that at all it can do whatever he wants [9:21] you can have a right instruction without [9:23] a call or [9:24] any other thing you can add code that [9:25] says modify and so what we actually do [9:29] we just discover what a function could [9:35] be with the counter flow information [9:37] that we got in the interpreter so for [9:41] example here we have the counter flow [9:43] graph of the book of before and let's [9:46] say the code runs a bit and after some [9:50] time this funk this trace gets warm this [9:55] tears get poor and eventually one trace [9:58] will become hot and this is the entry [10:00] point of our web assembly function and [10:03] this trace and all the traces that are [10:06] reachable from that one that are at [10:08] least warm will become their own [10:12] webassembly function so we can plug that [10:14] one back in and it would be much faster [10:18] because we don't have to go to the [10:20] interpreter to run that and what if [10:24] another trace is becomes warm or hot in [10:28] the meantime where we can just after [10:31] sometimes you can decide to update our [10:33] function and I'll plug it again and yeah [10:38] so that's the basic idea of of it will [10:42] strum some optimization passes when we [10:45] call it compile the code and our core [10:49] assumption is that the code is already [10:52] optimized right because it has been [10:54] compiled by the the source language and [10:57] so the code is already let's say optimal [10:59] but we won't optimize all the overhead [11:02] of the visualization so for example a [11:05] very significant optimization that we [11:07] can do is avoid to compute the Flex [11:12] register that I told you before because [11:14] most of the time they are not really [11:16] needed so for example let me show you [11:19] what I mean let's say we have an add [11:22] instruction I immediately followed by a [11:24] sub instruction right so those are our [11:27] primitives one after the other and if we [11:30] look after the add + flags instruction [11:34] where the flags store and [11:37] then we have another flag store there [11:40] but nobody reads the flags and then in [11:43] the middle so it's useless to compute [11:46] them and we can know this and we can [11:49] just remove that operation and compile [11:51] more efficient code we can also you see [11:54] remove others things that are not useful [11:56] for so we already loaded value of PPX so [12:01] we don't need to do that again and stuff [12:03] like this yeah we also support [12:07] multi-threading and the way we do it is [12:11] there is actually one real thread in the [12:14] main thread of the of the browser [12:18] process and we do time slicing so we [12:22] allocate some times a lot for for a [12:24] logical try to run and we can just do [12:31] some round-robin or others scheduling [12:33] and we also support Linux processes [12:36] which they are just like threads but [12:38] with other space that is not shared and [12:41] eventually we want to support to run [12:45] real parallelism so having more than one [12:49] web work and running your code but it's [12:52] not implemented yet a system cost so for [12:58] ship X our low-level libraries are [13:01] actually system cons and we need to [13:05] catch the system call where they are [13:08] invoked and we implement them using the [13:13] browser API so we implement for example [13:16] file system related system calls like [13:19] open a secret right close also timing [13:22] related ones memory management's we can [13:25] map memory pages and several others yeah [13:32] there are some limitations that we are [13:35] eating on the current implementations of [13:39] webassembly [13:41] so for example what explained so far is [13:45] not so different than what [13:47] software like QA mode does natively for [13:50] example right just generate some code [13:52] that they jump into it and and but we [13:55] cannot do that inglis mb so we cannot [13:57] just generate some webassembly code and [13:59] jump into it [14:00] that's not how it works you need to put [14:03] it into a module then you compile the [14:05] module and this is in a synchronous [14:07] operation so you need to wait some time [14:08] and then you have your module and you [14:11] can call the functions inside the module [14:13] and there is a trade-off here because [14:17] ideally we would like one function per [14:20] module so as soon as we want to compile [14:22] one function we put it into a module and [14:24] we compile it so it would be super fast [14:26] because it's just one function but we [14:29] cannot do that unfortunately because [14:31] browsers currently have some limitation [14:34] for example chrome after we have like [14:37] 1000 modules at the same time just the [14:40] braddock tab crashes so yeah we need to [14:44] batch the functions so one module will [14:47] contain certain number of functions but [14:49] if there are too many the compilation [14:52] will take some time and there is latency [14:54] and we don't want to spend too much time [14:56] in the interpreter if we know that that [14:59] code is not so we need to carefully [15:01] balance this and there are other [15:03] problems so for example we found some [15:05] memory leaks modules if we share [15:10] function tables for example yeah so it's [15:14] still a bit rough but hopefully this [15:17] will improve without us doing anything [15:22] we also depend on some wasn't [15:25] experimental features so right now [15:28] mainly threads and shared re buffer so I [15:33] told you that we are not really using [15:35] threads or running the beautiful threads [15:37] but we do use them for example to [15:41] schedule interrupts at keep time and [15:44] other things also we use wasn't a calls [15:49] that is currently an experimental [15:51] feature [15:53] this is useful to implement indirect [15:55] branches without this if you implement [15:58] indirect branches with normal calls you [16:01] will end up blowing up the stack and [16:04] with this it's it works end and right [16:08] now their support this is supported in [16:10] Chrome still not in Firefox but it will [16:13] come eventually and yes so we can run [16:19] any application but we need to adopt [16:22] what our expectations are for example we [16:24] cannot expect the application to access [16:27] the file system of the computer is [16:30] running on because there is a sign box [16:32] in the browser so we provide a sandbox [16:34] file system with the index at DB and [16:39] also there are networking limitations so [16:42] if the application is just opening a [16:44] random network port like they say a new [16:48] DP port we cannot actually do that [16:50] because the browser doesn't expose that [16:54] so we can try to work around that with [16:57] WebSockets but what sockets are not real [16:59] socket so you still need some kind of [17:01] proxy server that relays the what sort [17:06] is traffic HTTP requests are mostly fine [17:10] but there are cross origins problems [17:14] that can happen so yeah it's still not [17:20] so immediate to have everything working [17:23] but it can be done yeah so you may want [17:27] there right it is all cool but what is [17:30] the use case of this right well the main [17:34] use case for now has been trying to run [17:38] the Adobe Flash Player in the in the [17:41] browser without the plug-in so as you [17:44] may know this year support for fresh [17:49] will will end but there are still a lot [17:51] of flash applications out there and [17:54] there is some effort to implement the [17:59] Flash Player so that is stuff like [18:01] raffle for example but [18:05] yeah we believe that we will never get [18:07] there when there is actual feature [18:10] parity so even before raffle there was [18:13] the ganache project were the lights part [18:16] project and actually my boss is the [18:20] original author of lights part and he's [18:22] convinced that this is the only way to [18:25] have everything working for real and [18:30] yeah because we just run the original [18:32] and nothing can go wrong right so this [18:39] is a little explanation of how it works [18:43] because flash is not an application it's [18:46] a sharp library so you need the most [18:49] application to load the shredder library [18:50] and provide an API it's the pepper API [18:55] that was designed by the chrome team [18:57] originally and so we need a most [19:02] application that is compiled to x86 and [19:05] lives alongside the flash player and but [19:10] off of it is actually outside and it [19:13] communicates with the one inside the VM [19:15] and the second part is JavaScript [19:19] implements the pepper API using the Web [19:24] API [19:26] so hopefully you how can you use this [19:30] you just include our script our script [19:33] will find all the objects tags in the [19:36] page and replace them with canvases [19:39] controlled by our runtime and everything [19:43] will work magically and if you don't [19:46] believe me I actually have an example [19:50] here so let me make this a little bit [19:54] bigger [20:03] so yeah this is flash game it's pretty [20:09] yeah one if you ask me but yeah so [20:16] yeah you see it's not super fast yet but [20:20] it works so it's kind of incredible if [20:25] you ask me that is actually running and [20:29] yeah [20:30] there you have it let's die so they told [20:41] you at the beginning we also another [20:44] mode that is the full system emulation [20:47] so in this case we don't emulate the [20:49] Siskel's but we mairead the hardware and [20:52] we run an operating system on top right [20:56] now we can run DOS we still cannot run [20:59] like real protected mode but we're [21:02] working on that and execution engine is [21:05] the same so the JIT the interpreter and [21:08] that's exactly the same and I yourself [21:12] Adam of death so be this dose and who [21:19] knows prehistoric it's a nice old style [21:24] game and it is actually pretty nice [21:28] speed I think it's so what we're [21:42] thinking about the future of this so as [21:45] you just saw that performance is [21:48] promising but we are still not there [21:50] especially for flash there's a little [21:55] lags but we think that our approach with [22:00] the two-tier engine is actually [22:02] scarborough robust and we can do more [22:04] optimization and we can improve on this [22:06] and we actually are ready of an [22:11] agreement with the [22:13] flash game distributor to deploy this on [22:17] actual websites and there is also [22:20] interest for enterprise software using [22:23] frameworks like like flex and sparked [22:26] that are flash frameworks for doing [22:31] enterprise applications and we have an [22:33] early adopter program so if if you worry [22:36] any reason you have flash in your [22:38] company you can contact us and we can [22:41] help you probably we expect to have [22:45] satisfying performance by the summer [22:47] hopefully and we think that this work is [22:51] very general so right now flash is our [22:54] focus but we think there may be other [22:57] cool application of this and we look [22:59] into that if we find something else and [23:04] yeah thank you if you have any questions [23:07] I like to answer and you can get in [23:13] touch with me there's my email and you [23:16] can learn more about our company that's [23:19] website and also we are irate so if you [23:22] are interested in this kind of [23:23] technology let us know if you have [23:29] questions thank you very much that was [23:34] super cool to see be kidding webassembly [23:41] story you know we all know it's possibly [23:44] you know you just flat out some bytes [23:47] for a module and then instantiate it and [23:49] then export a function to call it you [23:51] know but obviously there's a lot of room [23:55] to make that a lot better and I know [24:00] that a lot of people in the CG are very [24:04] interested in making a sort of get [24:08] proposal and and getting first-class [24:12] support for jitang very nice so this is [24:20] definitely the most robust web some [24:22] leading system I have seen yet so I [24:26] think your [24:26] with that would be very useful to hear [24:31] about in the CG and so hopefully we can [24:35] get a proposal started in that direction [24:37] yeah I will be in also tomorrow there so [24:40] if anybody wants to speak with me about [24:43] that I'm happy to do that perfect so [24:49] yeah there you go [24:52] so there's a lot of flash websites that [24:56] you can find on the web archives yeah is [24:59] there any way that we can use this to [25:01] maybe like automatically make those [25:04] sites work in chrome yeah so in [25:08] principle yes the only thing that's [25:10] needed to make this work is to inject [25:14] our script into a web page containing a [25:17] fresher and yeah ideally should work [25:20] right now not everything Wars and a lot [25:22] of things actually crush the runtime so [25:25] I show you cool demos that actually work [25:29] yeah everything is doable and you will [25:31] improve yeah so the original web site [25:34] has no idea that shape is days we can [25:38] still do something so for example for [25:40] our java product we actually have a [25:43] Chrome extension that is called chip J [25:46] applet Runner and you can use that [25:49] extension to inject our script if so if [25:52] even if the website isn't our script [25:59] there you can just use the extension and [26:01] eventually we will do something similar [26:03] cool [26:12] I'm curious how you handle garbage [26:15] collection given that you can't inspect [26:17] the wasum stack yes sorry I assumed a [26:25] lot of knowledge there [26:26] so most garbage collectors that I'm [26:28] familiar with will basically scan the [26:31] stack so I'm thinking mostly about the [26:33] Java on the chirp Jay so if that is a [26:39] problem or did you work around it [26:42] somehow or chip Jay the Java code is [26:46] compiled to JavaScript and we use the [26:48] JavaScript average collector the web [26:50] assembly parts of chip J are the native [26:53] methods that are compiled to AB assembly [26:55] while Java stuff right now is in [26:57] JavaScript hopefully when the GC will be [27:01] implemented for the same way we can move [27:03] also that part but right now we don't [27:06] ship our own garbage collector okay that [27:08] makes sense so basically the Java [27:11] garbage collector is utilizing the [27:13] JavaScript correct on it cool thank you [27:16] great great talk this is super cool [27:20] [Music] [27:21] before this meetup just a few weeks ago [27:24] I wanted to run a Java applet in my [27:26] browser and I discovered you and it's [27:28] glad to find your chirp jay extension I [27:34] just wanted to ask why is it only work [27:37] under as a Chrome extension right now [27:39] sorry what is the chirp Jay only work as [27:42] a Chrome extension if nothing that [27:45] prevents us for making a extension to [27:49] but so there are a lot of workarounds [27:53] that we put into the extension because [27:55] we need to inject our stuff very very [27:58] early and the API is for extensions are [28:04] not really letting us do all the stuff [28:08] that we want so there are some more [28:10] clams that are chrome specific and we [28:12] could do that for 552 but yeah you know [28:15] we have only so many people working on [28:18] this okay just [28:20] so I would love to do it for Firefox but [28:23] it's not there yet it's probably work [28:27] out of the box but I don't know thank [28:35] you for thank you for the talk um I was [28:38] wondering so you were talking about [28:40] traces that you analyzed the language [28:44] the front-end language and compile it [28:47] into traces which is nice and I was [28:51] wondering so when you are running the [28:53] cheat you're running on actual values [28:56] like that are with which you actually [29:00] execute the program basically and with [29:03] these values you could actually perform [29:06] some certain optimizations that modify [29:11] or mutate this trace graph are you using [29:16] these optimizations during the cheating [29:20] which kind of it might be a little bit [29:23] too technical to be honest but so during [29:27] execution of actual values you have [29:30] concrete values during execution yeah [29:32] but you do not know upon compilation [29:35] time yeah oh yeah now we are not we are [29:39] not doing that but that's actually a [29:41] cool thing to explore yeah yeah you're [29:44] right that could be done but yeah we are [29:47] not doing that yet okay thank you [29:53] so it's curious when you're doing your [29:55] full one where you're saying in run daus [29:58] or Linux binary are you able to open a [30:01] socket it's something I've been kind of [30:03] working on with waves and stuff at the [30:06] moment you really open like a raw socket [30:11] just like run a web server inside of if [30:14] I had a Linux binary that it's a web [30:17] server so yeah so we are still a network [30:23] is not implemented right now and one [30:26] reason is the problem I told you before [30:28] so the application is opening a row so [30:32] and we have to [30:33] somehow mapped it to what we have at our [30:35] disposal and that is either WebSocket so [30:38] we're about to see like I did that video [30:40] show everybody you have to decide for [30:43] example if you use WebRTC then we are [30:45] confining yourself to only communicate [30:48] with other applications that use the [30:51] same web RTC back-end so you cannot so [30:54] let's say that you have one application [30:55] one instance of application running [30:57] inside the browser and another one [31:00] running natively to another person's [31:02] computer they cannot communicate if we [31:04] implement with WebSockets and so the way [31:07] you could do that is you we could [31:10] implement it with WebSockets and have [31:13] some kind of relay server and the relay [31:15] server use normal sockets and in this [31:18] way you can communicate between the [31:20] native one and the browser one but if [31:23] you don't care about that maybe you just [31:25] want browser processes like flash maybe [31:27] you want multiplayer games in this case [31:30] web RTC make more sense maybe because [31:32] this case we don't need their privacy we [31:35] can just do like the t-bolts demo so we [31:38] still have to think about that and maybe [31:40] the best thing is just case by case okay [31:44] and question for any one on the chrome [31:48] team give me status on the fugu raw [31:51] connection stuff orbit ability yeah no [31:54] no no Chrome nice there's a there's a [31:57] project fugu which is Chrome's trying to [31:59] bring a lot of the chrome Chrome OS [32:02] api's to the native platform and one of [32:04] them that I actually been following is [32:05] the raw raw connection so be able to do [32:07] sockets and UDP I don't think it's very [32:10] far along last and in Chrome apps has it [32:16] but it's I think that also our Chrome [32:20] apps can open sockets but yeah those are [32:23] not anywhere everywhere but maybe one [32:26] day other questions [32:33] this way hey really cool talk you [32:39] mentioned that your goal is to run flash [32:43] files [32:46] why did you implement an x86 interpreter [32:50] rather than a slash interpreter that's a [32:54] good question but yeah I can mention we [32:59] could implement or implement the flesh [33:03] before machine let's say and there are [33:06] projects that did that back in the day [33:08] there was the Ganesh project and there [33:11] was a light-sport project now there is [33:13] the ruffle project problem is that trash [33:16] is very complex and it's very difficult [33:19] to get it right for example Dinesh has [33:21] been around for a lot of time and you [33:24] couldn't run everything and also light [33:26] spark was even beyond flashing [33:29] compatibility but also that one as its [33:31] limitation so I don't know our Apple is [33:36] going but yeah we we think that it will [33:42] never be exactly the same as the flesh [33:47] the original flash player and these [33:50] technologies also general so we right [33:52] now we have a concrete application that [33:54] is saving flash this year because it's [33:56] going away this year but we can reuse [33:59] all these stuff to make other things too [34:01] so we think that this is a better use of [34:03] our time instead of implementing flash [34:06] yet another time and maybe not getting [34:08] it right there you go it's follow up on [34:24] that this has been your running old [34:27] flash code and do you like reemployment [34:30] when Windows will be in the backend I [34:33] mean how does that work if you are using [34:37] like you know the original flash [34:41] code over an OS like Windows running [34:46] under your layers a Linux slash binary [34:53] okay yeah yeah and I guess my other [34:56] question was I'm also interested in old [34:59] dos emulation so I was wondering how far [35:01] along you are not like for example can I [35:05] just take like a disk image with ms-dos [35:07] in a game or something and just stick it [35:09] in there and it will just boot on it and [35:11] run it or yeah and how far along is the [35:14] hardware emulation it for the virtual [35:18] machine I mean you know like all the [35:20] video modes supported or like how far [35:22] along is yeah I'm not sure where we are [35:27] actually I think that most those stuff [35:29] works but yeah I'm not sure what's the [35:33] current state of things that side yeah [35:36] we cannot restore everything just I [35:38] don't know how far can we go right now [35:40] I'm not sure I'm I do something that is [35:44] a cool stuff but yeah right now we are [35:45] more focused on the other and hopefully [35:48] the shared path that is the jet engine [35:51] and x86 execution that is the same so [35:55] there is yeah I wanna talk to you later [35:59] because I do some contract work for [36:01] Internet Archive and we have the large [36:04] dust collection the games so I know that [36:07] runs in a browser size calculator [36:13] questions [36:18] can you play by doom [36:20] can you play Doom on it oh we didn't try [36:23] actually but it's the ultimate good any [36:32] last questions [36:33] I mean I'm gonna show if it's really a [36:39] question but have you seen at [36:40] webassembly Asami's there was a speaker [36:42] who converts rebus and a to flash [36:45] have you tried connecting that to your [36:51] assignments or was it speaker cool [36:53] webassembly to flash files so you can [36:59] probably converse slash back to [37:01] webassembly funner on [37:03] [Laughter] [37:06] [Music]