---
title: "底层深入-WebGL应用逻辑运行时（JavaScript与WebAssembly）"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/webgl-logic-runtime-js-wasm/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/05-游戏图形与运行时/WebGL/底层深入-WebGL应用逻辑运行时（JavaScript与WebAssembly）.md"
content_hash: d1538cd47995f3df50dffd836872ed1c76978d1c1a37e1958e0ea211637437bf
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 底层深入-WebGL应用逻辑运行时（JavaScript与WebAssembly）

﻿# 底层深入-WebGL应用逻辑运行时（JavaScript与WebAssembly）

## 什么是 “JavaScript 运行时”？

> **运行时（runtime）= JS 引擎 + 事件循环 + 宿主 API**
> 引擎执行脚本；事件循环调度任务；宿主提供 “外界能力”（网络、文件、WebGL 等）。

**在 Web / 小程序里的两层结构**

```text
┌─────────────  宿主层  ──────────────┐
│  setTimeout / requestAnimationFrame│
│  wx.request / canvas / WebGL       │
└────────────────────────────────────┘
┌───────────  JS 引擎层  ────────────┐
│  解析(Parser) → 字节码(Interpreter)│
│  → JIT 编译(Hot code) → 机器码     │
│  GC / 隐式类型系统 / PromiseJobs   │
└────────────────────────────────────┘
```

* **Android 微信小游戏**：引擎是 **V8**；
* **iOS 微信小游戏**：引擎是 **JavaScriptCore (JSC)**；二者都 **没 DOM**、只暴露 `wx.*` API。
* 小游戏容器在 C++/Java 层维护事件循环，把宿主回调排进 **宏任务队列**，V8 / JSC 执行完一轮微任务后再取宏任务。

---

## JS 代码到机器码的流水线（以 V8 为例）

1. **解析**：生成 AST。
2. **Ignition** 把 AST 编译成 *字节码* 并立即解释执行。
3. **Profiling**：热点函数被标记为 *warm/hot*。
4. **Sparkplug / TurboFan** JIT 把热点字节码优化为本机指令；还能随时 **反优化**。

最终机器码直接跑在 ARM64 / x86-64 CPU 上；GC 维护堆对象与类型反馈。

---

## WebAssembly 与 JavaScript 的关系

* 它们“共用同一个（物理）运行时”，因为引擎、事件循环、宿主线程都完全重合；
* 它们各自拥有“独立的语言 runtime”，因为字节码格式、内存模型和首段编译器彼此不同。

### 差异部分

物理上共用同一 runtime，但逻辑上独立。

| 项          | JavaScript                            | WebAssembly                          |
| ---------- | ------------------------------------- | ------------------------------------ |
| **模块格式**   | `.js` 源文本/ESM                         | `.wasm` 二进制、`.wat` 文本                |
| **类型系统**   | 动态、可变对象                               | 静态, 固定位宽的 `i32/f64` 等                |
| **内存模型**   | GC 堆（对象可移动）                           | 线性内存 `WebAssembly.Memory`（连续字节）      |
| **首段编译器**  | _Ignition_ 解释器 / _Sparkplug_ baseline | _Liftoff_ baseline 编译器               |
| **互调桥**    | `WebAssembly.Instance.exports.fn()`   | `import "js" "log"` 直接调 JS 函数        |
| **加载 API** | `<script>`、`eval`                     | `WebAssembly.instantiateStreaming()` |

### 示例：同一个 `add` 函数的两种形态

```js
// JavaScript
export function add(a, b) { return a + b }
```

```wat
;; WebAssembly Text Format
(module
  (func $add (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add))
```

加载并互调：
```js
const { add } = await WebAssembly
      .instantiateStreaming(fetch('add.wasm'))
      .then(m => m.instance.exports);

console.log(add(40, 2));  // 42
```

### 编译流水线（以 V8 为例）

```text
JavaScript             WebAssembly
--------               ------------
source .js             binary .wasm
   │ parse               │ decode
   ▼                     ▼
 Ignition bytecode      Liftoff machine code   ← 首帧快速启动
   │ (解释执行 & 收集热度)  │
   ├───────┐             ├───────┐
   ▼       │             ▼       │
TurboFan JIT ───────────┴─► TurboFan JIT       ← 热点函数重编译
  (优化、内联、SIMD…)             (相同优化管线)
```

两条支流最后都汇入 **同一个机器码调度器**，在同一线程栈上运行。

### 共用的部分 = “引擎 + 事件循环 + 宿主 API”
两者 **共用同一个引擎内核与事件循环**：Wasm 模块只是 JS 引擎中的另一类 “代码对象”。当 Wasm 函数被调用时，执行栈可跨语言跳转。

| 层级                                   | 共享？ | 说明                                                               |
| ------------------------------------ | --- | ---------------------------------------------------------------- |
| **宿主事件循环**                           | ✅   | 浏览器/小游戏的宏任务队列只维护一份；JS 代码、Wasm 代码都要“让出栈”才能让 UI 与 I/O 事件继续跑。       |
| **引擎核心 (V8/JSC)**                    | ✅   | V8 官方定义自己是 “JavaScript **and WebAssembly** engine”；同一个堆、同一套线程模型。 |
| **优化 JIT (TurboFan / DFG)**          | ✅   | 优化器既能接收 JS 字节码也能接收 Wasm IR，再生成本机指令。                              |
| **Host APIs** (`wx.request`, WebGL…) | ✅   | 这部分暴露给 JS，全靠 JS 调用把参数塞进 Wasm 内存后再供 Wasm 代码使用。                    |

### 调用栈与内存的实际交错

```text
JS (app.js)
  └─ calls wasmAdd() ─► Wasm (add.wasm)
                         └─ calls console.log          (回到 JS)
```

- “JS→Wasm” 时，引擎生成 **TRAMPOLINE**：把 JS 数值装箱/拆箱成 `i32` 写进线性内存；
- “Wasm→JS” 时走 `import` 表，返回点仍在同一执行栈帧；
- GC 线程若移动 JS 对象，只需更新托管句柄；Wasm 线性内存地址永远不变，因而无需移动补丁。


## 在微信小游戏 / WebGL 场景中的落地

* **逻辑线程只有一个**：JS 执行、Wasm 执行、WebGL API 调用全在同一 V8/JSC 线程完成。
* **WebGL** 本质是宿主（GameRuntime）暴露的 C++ API：

  ```js
  const id = gl.createShader(gl.VERTEX_SHADER); // JS 调 => C++ => GPU
  ```
* **Unity WebGL**：IL2CPP 先把 C# 编译成 Wasm；运行时模板在 `Module.onRuntimeInitialized` 里用 JS 把渲染循环排进宿主事件循环。
* **同源崩溃上报**：`wx.onError`（JS）+ `Module.onAbort`（Wasm）监听到异常后，共用 `sendCrash()` API 把数据发往服务器——JS 和 Wasm 层面虽然触发点不同，但最终走的是同一个 JS 发送函数。
