<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yangholmes</title>
    <description>The latest articles on DEV Community by Yangholmes (@yangholmes).</description>
    <link>https://dev.to/yangholmes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3135895%2F645bd628-8306-4b7f-adcc-2146a6f394fb.jpg</url>
      <title>DEV Community: Yangholmes</title>
      <link>https://dev.to/yangholmes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yangholmes"/>
    <language>en</language>
    <item>
      <title>EMSCRIPTEN 多线程编程笔记</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Fri, 19 Sep 2025 08:23:17 +0000</pubDate>
      <link>https://dev.to/yangholmes/emscripten-duo-xian-cheng-bian-cheng-bi-ji-5cdb</link>
      <guid>https://dev.to/yangholmes/emscripten-duo-xian-cheng-bian-cheng-bi-ji-5cdb</guid>
      <description>&lt;h2&gt;
  
  
  操作系统的多线程
&lt;/h2&gt;

&lt;p&gt;进程是操作系统分配资源的最小单位，每创建一个新的进程，会把父进程的资源复制一份到子进程。而线程是一种轻量级的进程，不独立拥有系统资源，操作系统内核是按照线程作为调度单位来调度资源。每一个进程是由一个或者多个线程组成的。&lt;/p&gt;

&lt;p&gt;进程中 Text、Data、BSS 和 Heap 部分线程之间共享，Stack 不共享，每个线程拥有自己独立的栈。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwcag6umsqdenarcv31f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwcag6umsqdenarcv31f.png" alt="进程和线程的内存资源" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Linux 系统中普遍使用 pthread 库开发多线程程序，pthread 符合 POSIX 标准，提供管理和操作线程的方法，包含在 &lt;code&gt;pthread.h&lt;/code&gt; 头文件中。同一个进程中，除了栈，所有线程共享同一份内存，同时因为线程的执行是并行的，所以不可避免地发生资源竞争的问题，即同一时间有多个线程试图获取或者修改同一个内存资源。当开发者小心翼翼地处理内存使用时，并行地读写内存可以带来效率提升，一旦不注意可能带来严重的问题。假设用 2 个线程执行如下代码，&lt;code&gt;counter&lt;/code&gt; 的结果可能远小于 2000 ：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;pthread 提供了锁来解决这个问题，最常见的锁是互斥锁和读写锁。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;互斥锁：同一时间只能有唯一一个线程访问，使用 &lt;code&gt;pthread_mutex_t&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;读写锁：同一时间只能有唯一一个线程写入，允许多个线程读取， 使用 &lt;code&gt;pthread_rwlock_t&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;这里不打算展开 Linux 多线程编程，超出了本篇讨论的重点。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  EMSCRIPTEN 的多线程
&lt;/h2&gt;

&lt;p&gt;浏览器是一个多线程应用，我们在《&lt;a href="https://dev.to/yangholmes/web-ying-yong-zha-gan-cpu-xing-neng-de-zheng-que-zi-shi-218n"&gt;web 应用榨干 CPU 性能的正确姿势&lt;/a&gt;》一文中介绍过这些线程，这里引用一张图：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3psq8x3j384qxosrchx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3psq8x3j384qxosrchx9.png" alt="浏览器线程" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这些线程由浏览器管理，开发者并不能干预，可以把这些线程看作是“不可编程”的多线程；浏览器像开发者提供了“可编程”的多线程，那就是 &lt;code&gt;Worker&lt;/code&gt; 。《&lt;a href="https://dev.to/yangholmes/web-ying-yong-zha-gan-cpu-xing-neng-de-zheng-que-zi-shi-218n"&gt;web 应用榨干 CPU 性能的正确姿势&lt;/a&gt;》介绍了在 JavaScript 中如何使用 &lt;code&gt;Worker&lt;/code&gt; 实现多线程编程，并介绍了线程之间 Transferable objects 数据传输方式。 Transferable objects 有点类似 互斥锁 ，数据从一个线程传输至另一个线程的时候，不进行数据拷贝，而是传递数据所在的内存&lt;strong&gt;所有权&lt;/strong&gt;，数据传输完成之后，只有接收线程可以访问这块数据，其他线程都无法访问；和互斥锁不同的是， Transferable objects 没有“解锁”方法，如果需要将数据“还给”发送线程，就按照 Transferable objects 的方式重新发送数据。Transferable objects 数据适用于 &lt;code&gt;ArrayBuffer&lt;/code&gt; 一类数据，没有“共享”的属性。如果想要在不同的线程之间“共享”内存，就像使用真正的内存那样，就需要使用 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Worker&lt;/code&gt; 和 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 正是 emscripten 多线程的实现基础，尽可能地实现 POSIX 标准的 pthread 功能。&lt;code&gt;Worker&lt;/code&gt; 实现了独立栈和共享 Text， &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 实现了共享堆，和文件系统类似，也是通过替换系统函数，移花接木。&lt;/p&gt;

&lt;h3&gt;
  
  
  SharedArrayBuffer
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 对象表示一块二进制内存缓冲区，和 &lt;code&gt;ArrayBuffer&lt;/code&gt; 类似，但 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 可以被共享同时不能被 transfer 。&lt;code&gt;new SharedArrayBuffer(length)&lt;/code&gt; 效果和 &lt;code&gt;calloc(nmemb, size)&lt;/code&gt; 非常类似，运行之后都可以获得值&lt;strong&gt;全为 0&lt;/strong&gt; 的内存，只不过 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 长度为 &lt;code&gt;length * 8 bit&lt;/code&gt; ， &lt;code&gt;calloc&lt;/code&gt; 长度为 &lt;code&gt;nmemb * size bit&lt;/code&gt; 。也就是说，&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 申请的内存是没有类型的，使用的时候需要根据实际情况构造成相应的 &lt;code&gt;TypedArray&lt;/code&gt; 类型。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sab&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 100&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// 0&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sab&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 可以在主线程和多个 &lt;code&gt;Worker&lt;/code&gt; 线程中创建、传输和修改，当多个线程同时使用同一块内存时，这块内存的修改传播到不同上下文需要花费一些时间，也就是说，修改生效不是立即的，和操作系统上多线程内存操作一样。使用上 1 节的例子，当 2 个线程执行如下代码后，&lt;code&gt;counter&lt;/code&gt; 的结果可能远小于 2000 ：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 在某一个线程创建共享内存&lt;/span&gt;
&lt;span class="nx"&gt;cosnt&lt;/span&gt; &lt;span class="nx"&gt;_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ShareArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 在 2 个线程中执行累加&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript 并不采用“锁”来控制内存读写，而是提供 &lt;code&gt;Atomics&lt;/code&gt; 对象来保证数据读写准确。&lt;code&gt;Atomics&lt;/code&gt; 的细节请参考&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics" rel="noopener noreferrer"&gt;文档&lt;/a&gt;，这里不赘述。如果想要让 &lt;code&gt;counter&lt;/code&gt; 的最终结果是 2000 ，只需要简单修改一下加法命令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Atomics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 原子加操作&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Atomics&lt;/code&gt; 可以用来实现锁功能， emscripten 就是这么做的。简单地讲，使用 &lt;code&gt;Atomics.wait&lt;/code&gt; 实现等待， &lt;code&gt;Atomics.compareExchange&lt;/code&gt; 实现加锁，&lt;code&gt;Atomics.store&lt;/code&gt; 实现解锁，&lt;code&gt;Atomics.notify&lt;/code&gt; 实现通知线程。&lt;/p&gt;

&lt;h3&gt;
  
  
  cross-origin isolated
&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 必须满足两个条件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;安全上下文，即 &lt;code&gt;https://&lt;/code&gt;、 &lt;code&gt;wss://&lt;/code&gt; 和 &lt;code&gt;localhost&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;cross-origin isolated ，即跨源隔离&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;安全上下文想必大家都知道是什么含义，这里简单解析一下 cross-origin isolated（跨源隔离）。跨源隔离是一种网页的状态，此时只能在同源 &lt;code&gt;document&lt;/code&gt; 共享上下文和使用 CORS 加载的资源（&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 的话是 COEP ）。同时，浏览器将把这个源的页面&lt;strong&gt;独立一个进程&lt;/strong&gt;来管理，意味着这个源的页面拥有独立的操作系统资源，崩溃报错不会轻易影响到其他页面。&lt;code&gt;SharedArrayBuffer&lt;/code&gt; 必须在 cross-origin isolated 状态下使用，否则会找不到这个构造函数。除了 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 外，cross-origin isolated 还具有其余两个特性：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Performance.now()&lt;/code&gt; 精度提高，提高到 5ms 甚至更高&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Performance.measureUserAgentSpecificMemory()&lt;/code&gt; 可用&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如何开启 cross-origin isolated ？在页面的响应头中添加 COEP 和 COOP ：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cross-origin isolated 会带来一些不便：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;非同源嵌入式资源无法直接加载，如 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;、 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;、 &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; 等，解决方法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在服务端设置正确的 &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; 响应头，并在标签中添加 &lt;code&gt;crossorigin&lt;/code&gt;属性，如 &lt;code&gt;&amp;lt;img src="***" crossorigin&amp;gt;&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;CORP&lt;/code&gt; ，服务端为资源设置 &lt;code&gt;Cross-Origin-Resource-Policy&lt;/code&gt; 响应头&lt;/li&gt;
&lt;li&gt;代理转发，把跨域资源处理称为同源资源&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; 必须显性标明跨域嵌入，否则无法加载&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;非同源 popup &lt;code&gt;window.opener&lt;/code&gt; 为 &lt;code&gt;null&lt;/code&gt; &lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;无法改写 &lt;code&gt;document.domain&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;是否开启多线程需要结合页面使用的资源情况来决定。&lt;/p&gt;

&lt;p&gt;如果不确定一个页面是否符合 cross-origin isolated ，可以读取 &lt;code&gt;window.crossOriginIsolated&lt;/code&gt; 嗅探，在 worker 中为 &lt;code&gt;self.crossOriginIsolated&lt;/code&gt; 。无法提前预判运行环境是否跨源隔离，通常需要分别准备一套单线程方案和一套多线程方案，通过嗅探决定使用哪一种。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crossOriginIsolated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myWorker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;worker-pthread.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SharedArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;myWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myWorker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;worker-single.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;myWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  主线程阻塞
&lt;/h3&gt;

&lt;p&gt;WebAssembly 在主线程唤起执行通常会导致主线程阻塞，进而引发 UI 卡死。一般的做法是把 WebAssembly 放到一个独立的线程去执行，这个在前面的文档中多次提及。在 emscripten 中，由于线程由编译器管理，根据当前硬件状况自动合理分配，如果此时手动再增加一个线程，可能会导致线程分配不合理。解决这个问题有两个方案：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;手动指定可用线程数。 &lt;code&gt;-sPTHREAD_POOL_SIZE=&amp;lt;expression&amp;gt;&lt;/code&gt; 参数用来指定可用线程数，接受一个数字或一个 JavaScript 表示式。一般地我们会选择不传这个参数或者传入 &lt;code&gt;navigator.hardwareConcurrency&lt;/code&gt; 。当开发者想要手动维护启动线程时，可以为启动线程保留一个线程数，设置为 &lt;code&gt;-sPTHREAD_POOL_SIZE="navigator.hardwareConcurrency-1"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;-sPROXY_TO_PTHREAD&lt;/code&gt; 参数。添加这个参数后，c 程序中的 &lt;code&gt;main()&lt;/code&gt; 函数会被替换成一个新的线程，在这个线程中运行原本的 &lt;code&gt;main()&lt;/code&gt; 函数。相当于是方法 1 的自动化版本。有时候我们开发的 WebAssembly 模块并没有 &lt;code&gt;main()&lt;/code&gt; 函数，此时可以参考使用方法 1 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这里建议使用 &lt;code&gt;-sPTHREAD_POOL_SIZE=&amp;lt;expression&amp;gt;&lt;/code&gt; 参数，无论是否手动分配启动线程。原因是当指定了 &lt;code&gt;-sPTHREAD_POOL_SIZE=&amp;lt;expression&amp;gt;&lt;/code&gt; 后，程序将提前创建好 workers ，当代码执行到 &lt;code&gt;pthread_create&lt;/code&gt; 可以直接使用 worker 而不是从实例化开始，可以提高效率，并获得跟原生 c 更接近的运行效果。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;-sPROXY_TO_PTHREAD&lt;/code&gt; 和 &lt;code&gt;--proxy-to-worker&lt;/code&gt; 很像，都是将 &lt;code&gt;main()&lt;/code&gt; 函数代理到 worker 中，带不一样的地方在于，&lt;code&gt;--proxy-to-worker&lt;/code&gt; 只是纯粹代理 &lt;code&gt;main()&lt;/code&gt; ，并不支持 &lt;code&gt;pthread&lt;/code&gt; 和 &lt;code&gt;SharedArrayBuffer&lt;/code&gt; 。&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webassembly</category>
      <category>emscripten</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Using SIMD in WebAssembly (Part 1)</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Wed, 10 Sep 2025 03:48:47 +0000</pubDate>
      <link>https://dev.to/yangholmes/using-simd-in-webassembly-part-1-52ec</link>
      <guid>https://dev.to/yangholmes/using-simd-in-webassembly-part-1-52ec</guid>
      <description>&lt;h2&gt;
  
  
  Overview of SIMD in WebAssembly
&lt;/h2&gt;

&lt;p&gt;SIMD in WebAssembly has the same meaning as in CPUs: &lt;strong&gt;Single Instruction Multiple Data&lt;/strong&gt;. SIMD instructions achieve parallel data processing by performing the same operation on multiple data elements simultaneously, enabling vectorized computation. Compute-intensive applications like audio/video processing, codecs, and image processing leverage SIMD for performance gains. SIMD implementation depends on CPU hardware, and different architectures support varying SIMD capabilities. WebAssembly's SIMD instruction set is relatively conservative, currently limited to fixed-length 128-bit (16-byte) instructions.&lt;/p&gt;

&lt;p&gt;Most mainstream virtual machines now support SIMD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome ≥ 91 (May 2021)&lt;/li&gt;
&lt;li&gt;Firefox ≥ 89 (June 2021)&lt;/li&gt;
&lt;li&gt;Safari ≥ 16.4 (March 2023)&lt;/li&gt;
&lt;li&gt;Node.js ≥ 16.4 (June 2021)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before using SIMD, check client support in your user base, then implement &lt;strong&gt;progressive enhancement&lt;/strong&gt; in your project. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create two versions of the same wasm module: one with SIMD instructions and one without&lt;/li&gt;
&lt;li&gt;Detect host support for SIMD using libraries like &lt;a href="https://github.com/GoogleChromeLabs/wasm-feature-detect" rel="noopener noreferrer"&gt;wasm-feature-detect&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Load the appropriate module based on detection results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://github.com/GoogleChromeLabs/wasm-feature-detect" rel="noopener noreferrer"&gt;wasm-feature-detect&lt;/a&gt; tests support for wasm features (including SIMD, 64-bit memory, multithreading) and is tree-shakable for web compatibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// loadWasmModule.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;simd&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wasm-feature-detect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simdUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;simd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simdUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SIMD Instruction Set
&lt;/h2&gt;

&lt;p&gt;SIMD instructions resemble scalar operations but process vectors. Key categories include arithmetic, load/store, logical operations, and lane manipulation. Summary of common instructions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction Format&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load/Store&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load offset=&amp;lt;n&amp;gt; align=&amp;lt;m&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Load 128-bit vector from memory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load offset=0 align=16 (i32.const 0))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load8_splat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Load 8-bit integer and splat to 16 lanes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load8_splat (i32.const 42))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.store offset=&amp;lt;n&amp;gt; align=&amp;lt;m&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Store 128-bit vector to memory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.store offset=16 align=16 (i32.const 32) (local.get $vec))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Constants&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.const &amp;lt;type&amp;gt; &amp;lt;values&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create constant vector&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.const i32x4 0 1 2 3)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integer Arithmetic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.add(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8-bit integer addition (16 lanes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.add (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.sub(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16-bit integer subtraction (8 lanes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.sub (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.add_saturate_s(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8-bit signed saturating addition&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.add_saturate_s (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Integer Comparison&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.eq(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8-bit integer equality (returns mask)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.eq (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.lt_s(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32-bit signed integer less-than&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.lt_s (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Floating Point&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f32x4.add(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32-bit float addition (4 lanes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f32x4.add (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.sqrt(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64-bit float square root (2 lanes)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.sqrt (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bitwise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.and(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bitwise AND&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.and (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.bitselect(a, b, mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bitwise selection by mask&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.bitselect (local.get $a) (local.get $b) (local.get $mask))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shifts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.shl(a, imm)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32-bit integer left shift (immediate)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.shl (local.get $a) (i32.const 2))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lane Operations&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.extract_lane_s(idx, a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extract signed 8-bit lane&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.extract_lane_s 3 (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.shuffle(mask, a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shuffle lanes from two vectors&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.shuffle 0 1 2 3 12 13 14 15... (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Conversion&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.trunc_sat_f32x4_s(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;f32 to i32 (saturated truncation)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.trunc_sat_f32x4_s (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Other&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.any_true(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check if any lane is non-zero&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.any_true (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f32x4.ceil(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32-bit float ceiling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f32x4.ceil (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Instruction set summarized with DeepSeek assistance. Please report any inaccuracies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Using SIMD Instructions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Example: Image color inversion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Non-SIMD implementation processes one pixel (4 bytes) per iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  (import "env" "log" (func $log (param i32)))

  (import "env" "memory" (memory 100))

  ;; invert RGB in place, skip Alpha
  (func $invert (param $start i32) (param $length i32)
    (local $end i32)   
    (local $i i32)    

    ;; Calculate end address = start + length * 4
    local.get $start
    (i32.mul (local.get $length) (i32.const 4))
    i32.add
    local.set $end

    local.get $start
    local.set $i

    (block $exit
      ;; Process R, G, B channels individually
      (loop $loop

        local.get $i
        local.get $end
        i32.ge_u
        br_if $exit


        ;; R
        local.get $i
        i32.const 255
        local.get $i
        i32.load8_u     
        i32.sub          
        i32.store8      

        ;; G
        local.get $i
        i32.const 1
        i32.add
        i32.const 255
        local.get $i
        i32.const 1
        i32.add
        i32.load8_u     
        i32.sub          
        i32.store8       

        ;; B
        local.get $i
        i32.const 2
        i32.add
        i32.const 255
        local.get $i
        i32.const 2
        i32.add
        i32.load8_u     
        i32.sub          
        i32.store8       

        ;; i = i + 4
        local.get $i
        i32.const 4
        i32.add
        local.set $i

        br $loop
      )
    )
  )

  (export "invert" (func $invert))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SIMD version processes 4 pixels (16 bytes) per iteration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  (import "env" "log" (func $log (param i32)))
  (import "env" "memory" (memory 100))

  (func $invert (param $start i32) (param $length i32)
    (local $end i32)        
    (local $i i32)          
    (local $chunk v128)     
    (local $mask v128)     
    (local $full255 v128)  

    ;; end = start + length * 4
    local.get $start
    local.get $length
    i32.const 4
    i32.mul

    i32.add
    i32.const 3
    i32.add
    local.set $end

    ;; i = start
    local.get $start
    local.set $i

    ;; Full 255 vector
    v128.const i8x16 255 255 255 255 255 255 255 255
                     255 255 255 255 255 255 255 255
    local.set $full255

    ;; Alpha channel mask (preserve positions 3,7,11,15)
    v128.const i8x16 0 0 0 255 0 0 0 255
                     0 0 0 255 0 0 0 255
    local.set $mask

    (block $exit
      (loop $loop
        ;; if (i &amp;gt;= end) break
        local.get $i
        local.get $end
        i32.ge_u
        br_if $exit

        ;; load 16 bytes (4 pixels)
        local.get $i
        v128.load
        local.set $chunk

        ;; tmp = 255 - chunk
        local.get $full255
        local.get $chunk
        i8x16.sub
        local.set $chunk

        ;; Preserve alpha channels
        local.get $i
        v128.load
        local.get $chunk
        local.get $mask
        v128.bitselect
        local.set $chunk

        ;; store back
        local.get $i
        local.get $chunk
        v128.store

        ;; i += 16
        local.get $i
        i32.const 16
        i32.add
        local.set $i

        br $loop
      )
    )
  )

  (export "invert" (func $invert))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The SIMD version processes 16 bytes per iteration (line 18-20). Since image data might not be multiples of 16 bytes, we add 3 to the end address for alignment. This could potentially overwrite memory if other data exists, but is acceptable in this isolated example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Comparison&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco95os03hng6cnnfqdfd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco95os03hng6cnnfqdfd.png" alt="Performance comparison" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Left: Original image (928×927 pixels)&lt;/li&gt;
&lt;li&gt;Middle: Non-SIMD result (processing time: ~2.9ms)&lt;/li&gt;
&lt;li&gt;Right: SIMD result (processing time: 0.5ms)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SIMD implementation shows ~6x speedup. Larger images yield greater benefits, but even smaller images like the classic Lenna test image show significant improvements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12afnhufe7kd0zlnhv6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12afnhufe7kd0zlnhv6b.png" alt="Lenna image comparison" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Part 2 will explore using SIMD in WebAssembly via C/C++ programs.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>wat</category>
    </item>
    <item>
      <title>在 WebAssembly 中使用 SIMD（二）</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Tue, 09 Sep 2025 10:23:19 +0000</pubDate>
      <link>https://dev.to/yangholmes/zai-webassembly-zhong-shi-yong-simder--25bo</link>
      <guid>https://dev.to/yangholmes/zai-webassembly-zhong-shi-yong-simder--25bo</guid>
      <description>&lt;p&gt;本篇讨论 C 程序 SIMD 的实现。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 emscripten 编译
&lt;/h2&gt;

&lt;p&gt;emscripten 支持 SIMD 指令编译，使用之前需要引入头文件 &lt;code&gt;#include &amp;lt;wasm_simd128.h&amp;gt;&lt;/code&gt; ，和 wat 一样，支持 128 位 SIMD 指令集，例如定义一个 32 位浮点数矢量：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_f32x4_make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果想要对一个已经成熟的 C 项目启用 SIMD 指令，是否需要深入源代码，在每个 C 代码的文件中增加 &lt;code&gt;#include &amp;lt;wasm_simd128.h&amp;gt;&lt;/code&gt; 并改写成 &lt;code&gt;v128_t&lt;/code&gt; 的样子呢？其实并不用， emscripten 支持自动嗅探并将“串行”代码转换成“并行”代码，只需要在编译的时候增加 &lt;code&gt;-msimd128&lt;/code&gt; 参数（这得益于 LLVM 的自动矢量化优化）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;使用 &lt;code&gt;-msimd128&lt;/code&gt; 参数需要搭配 &lt;code&gt;-O2&lt;/code&gt; 或者 &lt;code&gt;-O3&lt;/code&gt; 参数。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;上一篇使用 wat 实现了一个图片反色的函数，这里使用 C 语言实现一下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// invert-c.c&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;emscripten/emscripten.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;my_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 图片反色函数&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;invert_colors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pixel_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;total_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixel_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 遍历所有像素数据&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 跳过 Alpha 通道（索引3），只处理 RGB 通道&lt;/span&gt;
    &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// R 通道反色&lt;/span&gt;
    &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// G 通道反色&lt;/span&gt;
    &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// B 通道反色&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不启用 SIMD 编译，编译命令为：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emcc invert-c.c &lt;span class="nt"&gt;-o&lt;/span&gt; invert-c.wasm &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-O3&lt;/span&gt; &lt;span class="nt"&gt;-g3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sERROR_ON_UNDEFINED_SYMBOLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sEXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'["_invert_colors"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sIMPORTED_MEMORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sINITIAL_MEMORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6553600 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sALLOW_MEMORY_GROWTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sSTANDALONE_WASM&lt;/span&gt; &lt;span class="nt"&gt;--no-entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;启用 SIMD 编译，编译命令为：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;emcc invert-c.c &lt;span class="nt"&gt;-o&lt;/span&gt; invert-c-simd.wasm &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-msimd128&lt;/span&gt; &lt;span class="nt"&gt;-O3&lt;/span&gt; &lt;span class="nt"&gt;-g3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sERROR_ON_UNDEFINED_SYMBOLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sEXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'["_invert_colors"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sIMPORTED_MEMORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sINITIAL_MEMORY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6553600 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sALLOW_MEMORY_GROWTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-sSTANDALONE_WASM&lt;/span&gt; &lt;span class="nt"&gt;--no-entry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用相同的素材进行测试：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1na4q1qgtzgn8alswti.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs1na4q1qgtzgn8alswti.png" alt="cats" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;从左到右分别是原图、 wat 非 SIMD 、 wat SIMD 、 C 非 SIMD 、 C SIMD 处理后图片和耗时。可以看出，不启用 SIMD ，算法相同的情况下，通过 emscripten 编译的代码效率和 wat 相同，而启用 SIMD emscripten 编译的代码效率不如 wat 。为什么会有这种差别？使用 wabt &lt;code&gt;wasm2wat&lt;/code&gt; 命令看看 emscripten 编译出来的算法策略是什么样的（摘抄部分关键代码）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
i8x16.shuffle 0 4 8 12 16 20 24 28 0 0 0 0 0 0 0 0    ;; 使用重排取出 R 通道
...
i8x16.shuffle 0 0 0 0 0 0 0 0 0 4 8 12 16 20 24 28    ;; 处理 G B 通道
i8x16.shuffle 0 1 2 3 4 5 6 7 24 25 26 27 28 29 30 31
v128.not                                              ;; 反相
local.tee 2
;; 写回内存
v128.store8_lane offset=61 15
local.get 1
local.get 2
v128.store8_lane offset=57 14
local.get 1
local.get 2
v128.store8_lane offset=53 13
...

;; G 和 B 通道同理

...

;; 如果像素总数不是 16 的倍数，剩余部分使用非 SIMD 代码处理
i32.load8_u      ;; 取出第一个通道
i32.const -1
i32.xor          ;; 取反
i32.store8       ;; 写回内存
...              
;; 其余两个通道同理
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以看出两点不同&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;像素数量不满足 16 倍数的处理，笔者的处理方式是补全到等于或超过 16 的倍数，确保可以命中所有内存，不需要再运行非 SIMD 代码，性能会更好一些&lt;/li&gt;
&lt;li&gt;emscripten 采用重排的方式将每个像素的 RGBA 通道分别取出然后取反实现反色，最后写入的时候按照字节依次写入，笔者的实现方式是全部通道取反最后再将 Alpha 通道还原，原地读写，效率会更高一些&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;无论是手写的 SIMD 还是 emscripten 自动转换的 SIMD ，都要比非 SIMD 代码效率高。&lt;/p&gt;

&lt;h2&gt;
  
  
  shuffle 指令
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;i8x16.shuffle&lt;/code&gt; 是图形处理中非常常用的指令，可以用来一次性提取某个通道 128 位数据，方便后续运算。假设原始像素数据是这样排布的：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R0 G0 B0 A0 | R1 G1 B1 A1 | R2 G2 B2 A2 | R3 G3 B3 A3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接下来使用 &lt;code&gt;i8x16.shuffle&lt;/code&gt; 提取 R 通道：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i8x16.shuffle 0 4 8 12 0 0 0 0 0 0 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;得到&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[R0 R1 R2 R3 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;G 和 B 通道同理&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; G
i8x16.shuffle 1 5 9 13 0 0 0 0 0 0 0 0 

;; B
i8x16.shuffle 2 6 10 14 0 0 0 0 0 0 0 0 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;得到&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[G0 G1 G2 G3 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??]
[B0 B1 B2 B3 ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;??&lt;/code&gt; 部分无所谓是什么数据，可以填 0 ，不影响计算。&lt;/p&gt;

&lt;p&gt;最后就是 &lt;code&gt;255 - x&lt;/code&gt; 的计算，&lt;code&gt;uint&lt;/code&gt; 类型中，被全 1 数减相当于按位取反，这里直接使用 &lt;code&gt;v128.not&lt;/code&gt; 指令，会比减法指令更高效一些。&lt;/p&gt;

&lt;h2&gt;
  
  
  使用 emscripten SIMD 指令
&lt;/h2&gt;

&lt;p&gt;最后，笔者尝试使用 emscripten SIMD 指令重新编写代码。算法的思路和 wat 版本的一致，也是通过长度补齐的方式避免像素总数不是 16 的倍数：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// invert-c-simd.c&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;emscripten/emscripten.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;wasm_simd128.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;my_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;invert_colors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;pixel_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 每个像素包含 RGBA 4个字节&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;total_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixel_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// 创建常量向量：255&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;const_255&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_i8x16_splat&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// 创建掩码向量：每个像素的前3个字节为255（RGB），最后一个字节为0（Alpha）&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_v128_load&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="p"&gt;[]){&lt;/span&gt;
      &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// 处理完整的16字节块（4个像素）&lt;/span&gt;
  &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;simd_chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total_bytes&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;simd_chunks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 读取内存&lt;/span&gt;
    &lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_v128_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 反色计算：255 - value&lt;/span&gt;
    &lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;inverted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_i8x16_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const_255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 使用位选择保留Alpha通道不变&lt;/span&gt;
    &lt;span class="n"&gt;v128_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wasm_v128_bitselect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 写回内存&lt;/span&gt;
    &lt;span class="n"&gt;wasm_v128_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_data&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;查看结果：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno3lcfo5cpp7mpoe7s45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fno3lcfo5cpp7mpoe7s45.png" alt="simd 对比" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;左图依旧是原图，中间是 wat SIMD ，右图是 C SIMD 。可以看到，在相同的方案下，无论用什么语言来写，性能表现是一致的。&lt;/p&gt;

&lt;p&gt;但是两种语言依旧有不同。上一篇有提到，字符补全方案有一个缺点，可能会污染其他数据，如果模块中需要实现不止一个功能的时候，需要非常小心；使用 emscripten 可以通过一些手段规避这个风险，在静态检查的时候会发现这个问题，为代码预留内存空间。&lt;/p&gt;

&lt;p&gt;使用什么手段才能规避内存污染的风险呢？下期再讲。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>simd</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>在 WebAssembly 中使用 SIMD（一）</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Mon, 08 Sep 2025 08:21:26 +0000</pubDate>
      <link>https://dev.to/yangholmes/zai-webassembly-zhong-shi-yong-simd--1aj2</link>
      <guid>https://dev.to/yangholmes/zai-webassembly-zhong-shi-yong-simd--1aj2</guid>
      <description>&lt;h2&gt;
  
  
  WebAssembly 的 SIMD 概况
&lt;/h2&gt;

&lt;p&gt;WebAssembly 的 SIMD 和 CPU 的 SIMD 是一个意思，都是指 &lt;strong&gt;Single Instruction Multiple Data&lt;/strong&gt; (&lt;strong&gt;单指令多数据&lt;/strong&gt;) 。SIMD 指令通过同时对多个数据执行相同的操作来实现并行数据处理，进而获得矢量运算能力，计算密集型应用，例如音视频处理、编解码器、图像处理，都采用 SIMD 提升性能。SIMD 的实现依赖于 CPU ，不同的硬件条件支持的 SIMD  能力不同，所以 SIMD 指令集很大，并且在不同架构之间有所不同，当然 WebAssembly SIMD 指令集也包含其中。另一方面， WebAssembly 作为一个通用型平台，其支持的 SIMD 指令集相对比较保守，目前仅限于固定长度 16 字节（128 位）的指令集。&lt;/p&gt;

&lt;p&gt;目前主流的大部分虚拟机都支持 SIMD ：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chrome ≥ 91 (2021年5月)&lt;/li&gt;
&lt;li&gt;Firefox ≥ 89 (2021年6月)&lt;/li&gt;
&lt;li&gt;Safari ≥ 16.4 (2023年3月)&lt;/li&gt;
&lt;li&gt;Node.js ≥ 16.4 (2021年6月)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;使用之前先看看大部分用户使用的客户端是否支持，然后考虑在项目中增加测试代码&lt;strong&gt;渐进增强&lt;/strong&gt;。渐进增强的含义是，相同功能的 wasm 模块分别用非 SIMD 和 SIMD 指令编写，嗅探宿主对 SIMD 的支持情况，如果不支持则使用非 SIMD 模块，如果支持则使用 SIMD 模块。嗅探可以使用 &lt;a href="https://github.com/GoogleChromeLabs/wasm-feature-detect" rel="noopener noreferrer"&gt;wasm-feature-detect&lt;/a&gt; 库。这个库专门用于测试宿主对 wasm 特性支持程度，除了 SIMD 以外，这个库还可以检查诸如 64 位内存、多线程等新特性和实验特性，并且支持摇树（Tree-shakable），对 web 应用友好。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// loadWasmModule.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;simd&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wasm-feature-detect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;simdUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;simd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isSupported&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;simdUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SIMD 指令集
&lt;/h2&gt;

&lt;p&gt;SIMD 指令和单字节指令类似，也是算术运算、读取写入、逻辑运算这几类。使用时需要严格按照栈式指令操作，SIMD 指令汇总：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指令格式&lt;/th&gt;
&lt;th&gt;功能描述&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;读取和存储&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load offset=&amp;lt;n&amp;gt; align=&amp;lt;m&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;从内存加载 128 位向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load offset=0 align=16 (i32.const 0))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load8_splat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;加载 8 位整数并复制 16 次填充向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load8_splat (i32.const 42))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load16_splat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;加载 16 位整数并复制 8 次填充向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load16_splat (i32.const 1024))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load32_splat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;加载 32 位整数并复制 4 次填充向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load32_splat (i32.const 0x12345678))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.load64_splat&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;加载 64 位整数并复制 2 次填充向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.load64_splat (i32.const 0))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.store offset=&amp;lt;n&amp;gt; align=&amp;lt;m&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;存储 128 位向量到内存&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.store offset=16 align=16 (i32.const 32) (local.get $vec))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;创建常量&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.const &amp;lt;type&amp;gt; &amp;lt;values&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建常量向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.const i32x4 0 1 2 3)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.const &amp;lt;type&amp;gt; &amp;lt;values&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;创建浮点常量向量&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.const f32x4 1.0 2.0 3.0 4.0)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;整数算术运算&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.add(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8 位整数加法（16 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.add (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.sub(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 位整数减法（8 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.sub (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.mul(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位整数乘法（4 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.mul (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i64x2.add(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位整数加法（2 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i64x2.add (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.add_saturate_s(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8 位有符号饱和加法&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.add_saturate_s (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.sub_saturate_u(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 位无符号饱和减法&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.sub_saturate_u (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;整数比较运算&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.eq(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8 位整数相等比较（返回掩码）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.eq (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.lt_s(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位有符号整数小于比较&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.lt_s (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.gt_u(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 位无符号整数大于比较&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.gt_u (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;浮点运算&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f32x4.add(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位浮点加法（4 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f32x4.add (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.mul(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位浮点乘法（2 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.mul (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f32x4.min(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位浮点最小值（4 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f32x4.min (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.sqrt(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位浮点平方根（2 通道）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.sqrt (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;位运算&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.and(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按位与&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.and (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.or(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按位或&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.or (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.xor(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;按位异或&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.xor (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.bitselect(a, b, mask)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据掩码选择位&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.bitselect (local.get $a) (local.get $b) (local.get $mask))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;位移&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.shl(a, imm)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位整数左移（立即数）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.shl (local.get $a) (i32.const 2))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i64x2.shr_u(a, imm)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位无符号整数右移（立即数）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i64x2.shr_u (local.get $a) (i32.const 3))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.shl(a, imm)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 位整数左移（立即数）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.shl (local.get $a) (i32.const 4))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通道操作&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.extract_lane_s(idx, a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;提取 8 位有符号整数通道&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.extract_lane_s 3 (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.replace_lane(idx, a, value)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;替换 64 位浮点通道&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.replace_lane 1 (local.get $a) (f64.const 3.14))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.swizzle(a, s)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据索引向量重排通道&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.swizzle (local.get $a) (local.get $indices))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.shuffle(mask, a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;根据掩码混洗两个向量的通道&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.shuffle 0 1 2 3 12 13 14 15 8 9 10 11 4 5 6 7 (local.get $a) (local.get $b))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;类型转换&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i32x4.trunc_sat_f32x4_s(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位浮点转 32 位有符号整数（饱和截断）&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i32x4.trunc_sat_f32x4_s (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.convert_i32x4_s(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位有符号整数转 64 位浮点&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.convert_i32x4_s (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i16x8.extend_low_i8x16_s(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;将低 8 个 8 位有符号整数扩展为 16 位&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i16x8.extend_low_i8x16_s (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;其他&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;v128.any_true(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查向量中是否有任意通道非零&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(v128.any_true (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i8x16.all_true(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;检查所有 8 位通道是否全为非零&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(i8x16.all_true (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f32x4.ceil(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 位浮点向上取整&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f32x4.ceil (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f64x2.floor(a)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 位浮点向下取整&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(f64x2.floor (local.get $a))&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;指令集使用 deepseek 协助汇总，没有严格校对，如有错误请指出。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  使用 SIMD 指令
&lt;/h2&gt;

&lt;p&gt;举个例子，如果想要对一张图片进行反色处理，如果不使用 SIMD 指令集， wat 实现如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  (import "env" "log" (func $log (param i32)))
  ;; 导入内存
  (import "env" "memory" (memory 100))

  ;; 反色函数：原地转换 RGB 通道，跳过Alpha通道
  (func $invert (param $start i32) (param $length i32)
    (local $end i32)   ;; 结束地址
    (local $i i32)     ;; 当前字节索引

    ;; 计算结束地址 = start + length * 4
    local.get $start
    (i32.mul (local.get $length) (i32.const 4))
    i32.add
    local.set $end

    ;; 初始化循环变量 i = start
    local.get $start
    local.set $i

    (block $exit
      ;; 主循环（每次处理4个字节：R,G,B,A）
      (loop $loop

        ;; 检查是否到达结束地址
        local.get $i
        local.get $end
        i32.ge_u
        br_if $exit


        ;; 处理R通道（偏移0）
        local.get $i
        i32.const 255
        local.get $i
        i32.load8_u      ;; 加载原始R值
        i32.sub          ;; 计算255 - R
        i32.store8       ;; 存储反色后的R值

        ;; 处理G通道（偏移1）
        local.get $i
        i32.const 1
        i32.add
        i32.const 255
        local.get $i
        i32.const 1
        i32.add
        i32.load8_u      ;; 加载原始G值
        i32.sub          ;; 计算255 - G
        i32.store8       ;; 存储反色后的G值

        ;; 处理B通道（偏移2）
        local.get $i
        i32.const 2
        i32.add
        i32.const 255
        local.get $i
        i32.const 2
        i32.add
        i32.load8_u      ;; 加载原始B值
        i32.sub          ;; 计算255 - B
        i32.store8       ;; 存储反色后的B值

        ;; 跳过Alpha通道（偏移3），无需修改
        ;; 移动到下一个像素（i += 4）
        local.get $i
        i32.const 4
        i32.add
        local.set $i

        br $loop
      )
    )
  )

  ;; 导出函数
  (export "invert" (func $invert))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 SIMD 指令，每一步对 1 个像素点 1 个通道的操作会增强为对 4 个像素点4个通道的操作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  (import "env" "log" (func $log (param i32)))
  (import "env" "memory" (memory 100))

  (func $invert (param $start i32) (param $length i32)
    (local $end i32)        ;; 结束地址
    (local $i i32)          ;; 当前地址
    (local $chunk v128)     ;; 当前处理的16字节
    (local $mask v128)      ;; alpha 通道掩码
    (local $full255 v128)   ;; 全 255 掩码

    ;; end = start + length * 4
    local.get $start
    local.get $length
    i32.const 4
    i32.mul
    ;; 数据长度可能不是 4 的倍数，这里 +3 确保数据对齐
    i32.add
    i32.const 3
    i32.add
    local.set $end

    ;; i = start
    local.get $start
    local.set $i

    ;; 常量向量：全 255
    v128.const i8x16 255 255 255 255 255 255 255 255
                     255 255 255 255 255 255 255 255
    local.set $full255

    ;; 掩码：只保留 alpha 通道（第 3,7,11,15 个字节）
    v128.const i8x16 0 0 0 255 0 0 0 255
                     0 0 0 255 0 0 0 255
    local.set $mask

    (block $exit
      (loop $loop
        ;; if (i &amp;gt;= end) break
        local.get $i
        local.get $end
        i32.ge_u
        br_if $exit

        ;; load 16 bytes (4 pixels)
        local.get $i
        v128.load
        local.set $chunk

        ;; tmp = 255 - chunk
        local.get $full255
        local.get $chunk
        i8x16.sub
        local.set $chunk

        ;; 用 bitselect 保留 alpha 通道：
        local.get $i
        v128.load
        local.get $chunk
        local.get $mask
        v128.bitselect
        local.set $chunk

        ;; store back
        local.get $i
        local.get $chunk
        v128.store

        ;; i += 16
        local.get $i
        i32.const 16
        i32.add
        local.set $i

        br $loop
      )
    )
  )

  (export "invert" (func $invert))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意看第 18 到第 20 行，WebAssembly SIMD 指令一次处理 16 字节数据，对应 rgba 4 个通道的图片 4 个像素，每张图片的像素点数量有可能不是 4 的倍数，所以这里加上一个大于 3 的数字即可确保所有数据都可以被处理。但是也要注意，WebAssembly 没有内存守护，这么处理会污染内存，导致其他数据错误，此例功能单一且没有其他数据，这样操作性能最好。&lt;/p&gt;

&lt;p&gt;最后看性能对比：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco95os03hng6cnnfqdfd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fco95os03hng6cnnfqdfd.png" alt="性能对比" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上图最左边是素材原图，中间是没有使用 SIMD 指令的处理结果和用时，右边是使用 SIMD 指令的处理结果和用时。素材原图的尺寸为 928*927 ，除了中间的圆形图案以外，其余都是透明像素。可以看到，使用 SIMD 指令的方案性能要比不使用的快 6 倍左右。实际上，素材越大，效果越明显，不过笔者发现在处理更小的图片的场景中，也有显著的提升，比如经典的 lenna 图：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12afnhufe7kd0zlnhv6b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12afnhufe7kd0zlnhv6b.png" alt=" " width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  预告
&lt;/h2&gt;

&lt;p&gt;下一篇将讨论，C 程序如何在 WebAssembly 中使用 SIMD 。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>simd</category>
    </item>
    <item>
      <title>Using wabt</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Sat, 06 Sep 2025 09:22:15 +0000</pubDate>
      <link>https://dev.to/yangholmes/notes-on-using-wabt-1ag0</link>
      <guid>https://dev.to/yangholmes/notes-on-using-wabt-1ag0</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/WebAssembly/wabt" rel="noopener noreferrer"&gt;wabt&lt;/a&gt; is a WebAssembly binary toolkit that provides compilation, analysis, debugging, and validation tools for wasm-related code. This article briefly introduces the usage of common commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling wat Code
&lt;/h2&gt;

&lt;p&gt;Implementing the Fibonacci sequence in wat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; fib.wat
(module
  (import "env" "log" (func $log (param i32)))

  ;; Allocate one page of memory
  (memory (export "memory") 1)

  ;; Global variable: heap pointer (points to next available memory address)
  (global $heap_ptr (mut i32) (i32.const 0))

  ;; Allocate memory block
  ;; params: size (i32) - bytes to allocate
  ;; return: starting address (i32)
  (func $allocate (param $size i32) (result i32)
    (local $start i32)
    (local.set $start (global.get $heap_ptr))
    (global.set $heap_ptr
      (i32.add
        (global.get $heap_ptr)
        (local.get $size)
      )
    )
    (local.get $start)
  )

  ;; Fibonacci sequence
  ;; params: n (i32) - array length
  ;; return: array starting address (i32)
  (func (export "fib") (param $n i32) (result i32)
    (local $i i32)
    (local $arr_ptr i32)
    (local $prev i32)
    (local $curr i32)
    (local $next i32)

    ;; Allocate memory: n * sizeof(i32) = n * 4
    (local.set $arr_ptr
      (call $allocate
        (i32.mul
          (local.get $n)
          (i32.const 4)
        )
      )
    )

    ;; Handle edge cases
    (if (i32.le_s (local.get $n) (i32.const 0))
      (then (return (local.get $arr_ptr)))  ;; Return empty array address
    )

    ;; Initialize first two elements
    (i32.store (local.get $arr_ptr) (i32.const 0))
    (if (i32.gt_s (local.get $n) (i32.const 1))
      (then
        (i32.store
          (i32.add (local.get $arr_ptr) (i32.const 4))
          (i32.const 1)
        )
      )
    )

    ;; Iteratively calculate subsequent elements
    (local.set $prev (i32.const 0))
    (local.set $curr (i32.const 1))
    (local.set $i (i32.const 2))
    (loop $loop
      ;; Calculate next Fibonacci number
      (local.set $next (i32.add (local.get $prev) (local.get $curr)))
      (local.set $prev (local.get $curr))
      (local.set $curr (local.get $next))

      ;; Store in memory
      (i32.store
        (i32.add
          (local.get $arr_ptr)
          (i32.mul (local.get $i) (i32.const 4))
        )
        (local.get $next)
      )

      ;; Loop control
      (local.set $i (i32.add (local.get $i) (i32.const 1)))
      (br_if $loop (i32.lt_s (local.get $i) (local.get $n)))
    )

    ;; Return array starting address
    (local.get $arr_ptr)  
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile the code using wabt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wat2wasm ./fib.wat &lt;span class="nt"&gt;-o&lt;/span&gt; ./fib.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resulting wasm files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── fib.wasm
├── fib.wat
└── main.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;main.ts provides the host environment for calling wasm code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fibUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fib.wasm?url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateStreaming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fibUrl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Destructure exports to get fib function and memory&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Memory&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="c1"&gt;// Fibonacci sequence length&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Compute, storing result in memory, get result pointer&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Read result&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Vite as the build tool, which supports importing any resource type as a URL. Here we convert wasm to a resource URL and load it using &lt;code&gt;WebAssembly.instantiateStreaming&lt;/code&gt; and &lt;code&gt;fetch&lt;/code&gt;. Note that Vite also supports automatic wasm initialization using the &lt;code&gt;?init&lt;/code&gt; suffix (see Vite documentation for details).&lt;/p&gt;

&lt;p&gt;We use the &lt;code&gt;fib&lt;/code&gt; function to compute a Fibonacci sequence of length 10, with output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouxn7vsvxz4nvjq7pexx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouxn7vsvxz4nvjq7pexx.png" alt=" " width="598" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Results match expectations.&lt;/p&gt;

&lt;p&gt;Comparing wat code and wasm file sizes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqygcvuhh6mcb7xeli6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqygcvuhh6mcb7xeli6d.png" alt="wasm vs. wat" width="309" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After compilation, the wasm file is an order of magnitude smaller than the source. This efficiency comes from both wasm's compact format and LEB128 compression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analyzing wasm
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;wasm-objdump&lt;/code&gt; command is similar to the OS &lt;code&gt;objdump&lt;/code&gt;, used for analyzing wasm file information. Practical use case: A developer receives a wasm module and wants to quickly identify exported functions, their parameter counts/types, and return value types/lengths. Using the previous fib.wasm as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Export &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Export[2]:
 - memory[0] -&amp;gt; &lt;span class="s2"&gt;"memory"&lt;/span&gt;
 - func[2] &amp;lt;fib&amp;gt; -&amp;gt; &lt;span class="s2"&gt;"fib"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows fib.wasm has two exports: a memory and a &lt;code&gt;fib&lt;/code&gt; function (type 2). Next, examine function signatures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Function &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Function[2]:
 - func[1] &lt;span class="nv"&gt;sig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
 - func[2] &lt;span class="nv"&gt;sig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &amp;lt;fib&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function 2 uses type index 1 declared in the &lt;code&gt;Type&lt;/code&gt; section. Export the &lt;code&gt;Type&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Type &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Type[2]:
 - &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0] &lt;span class="o"&gt;(&lt;/span&gt;i32&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; nil
 - &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;1] &lt;span class="o"&gt;(&lt;/span&gt;i32&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; i32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've now obtained complete information about fib.wasm's exports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exported memory&lt;/li&gt;
&lt;li&gt;Exported function &lt;code&gt;fib&lt;/code&gt; with one &lt;code&gt;i32&lt;/code&gt; parameter and one &lt;code&gt;i32&lt;/code&gt; return value&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;wasm-objdump -x&lt;/code&gt; can be used alone to output all section information. For large files, it's better to output sections individually for easier analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formatting Code
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;wat-desugar&lt;/code&gt; command formats existing wat code to conform to certain specifications. For example, the original fib.wat source didn't strictly follow the "push operands -&amp;gt; execute instruction" pattern, often writing operands after instructions. While valid, this doesn't follow stack machine conventions. &lt;code&gt;wat-desugar&lt;/code&gt; helps standardize this code. Here's the formatted &lt;code&gt;$allocate&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func $allocate (param $size i32) (result i32)
    (local $start i32)
    global.get $heap_ptr
    local.set $start
    global.get $heap_ptr
    local.get $size
    i32.add
    global.set $heap_ptr
    local.get $start
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared to the original source, the formatted code is more compact and better follows stack-based calling conventions, though less readable. The most noticeable difference is the &lt;code&gt;i32.add&lt;/code&gt; operation: the original placed operands after the instruction, while the standardized version pushes operands to the stack before calling the add instruction.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The name "desugar" contrasts with "Syntactic Sugar". Syntax like &lt;code&gt;(i32.add (local.get 0) (local.get 1))&lt;/code&gt; is syntactic sugar - operands aren't pushed to the stack before the instruction call (more like a register machine). Compilers accept this non-stack-machine syntax because it's more intuitive. "Desugar" is like the Cantonese phrase "走糖" (less sugar), removing the syntactic sugar to reveal the most fundamental code that strictly follows the push-operands-then-execute pattern.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Disassembly
&lt;/h2&gt;

&lt;p&gt;wabt provides three disassembly commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wasm2wat&lt;/code&gt;: Disassembles wasm to wat code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wasm2c&lt;/code&gt;: Disassembles wasm to C source and header files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wasm-decompile&lt;/code&gt;: Disassembles wasm to readable C-style pseudocode&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, &lt;code&gt;wasm-decompile&lt;/code&gt; combined with &lt;code&gt;wasm2wat&lt;/code&gt; is most useful. Use &lt;code&gt;wasm-decompile&lt;/code&gt; to analyze functionality implementation. If minor module modifications are needed, use &lt;code&gt;wasm2wat&lt;/code&gt; to get a wat file, make changes, then recompile to wasm.&lt;/p&gt;

</description>
      <category>wabt</category>
      <category>webassembly</category>
      <category>javascript</category>
    </item>
    <item>
      <title>wabt 使用小记</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Thu, 04 Sep 2025 05:04:12 +0000</pubDate>
      <link>https://dev.to/yangholmes/wabt-shi-yong-xiao-ji-1bk4</link>
      <guid>https://dev.to/yangholmes/wabt-shi-yong-xiao-ji-1bk4</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/WebAssembly/wabt" rel="noopener noreferrer"&gt;wabt&lt;/a&gt; 是 WebAssembly 二进制格式工具集，提供 wasm 相关的代码编译、分析、调试和验证等功能。这篇简单介绍一下常用命令的用法。&lt;/p&gt;

&lt;h2&gt;
  
  
  编译 wat 代码
&lt;/h2&gt;

&lt;p&gt;用 wat 实现斐波那契数列：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; fib.wat
(module
  (import "env" "log" (func $log (param i32)))

  ;; 申请一页内存
  (memory (export "memory") 1)

  ;; 全局变量：堆指针（指向下一个可用内存地址）
  (global $heap_ptr (mut i32) (i32.const 0))

  ;; 分配内存块
  ;; params：size (i32) - 需要分配的字节数
  ;; return：起始地址 (i32)
  (func $allocate (param $size i32) (result i32)
    (local $start i32)
    (local.set $start (global.get $heap_ptr))
    (global.set $heap_ptr
      (i32.add
        (global.get $heap_ptr)
        (local.get $size)
      )
    )
    (local.get $start)
  )

  ;; 斐波那契数列
  ;; params：n (i32) - 数组长度
  ;; return：数组起始地址 (i32)
  (func (export "fib") (param $n i32) (result i32)
    (local $i i32)
    (local $arr_ptr i32)
    (local $prev i32)
    (local $curr i32)
    (local $next i32)

    ;; 分配内存：n * sizeof(i32) = n * 4
    (local.set $arr_ptr
      (call $allocate
        (i32.mul
          (local.get $n)
          (i32.const 4)
        )
      )
    )

    ;; 边界情况处理
    (if (i32.le_s (local.get $n) (i32.const 0))
      (then (return (local.get $arr_ptr)))  ;; 返回空数组地址
    )

    ;; 初始化前两个元素
    (i32.store (local.get $arr_ptr) (i32.const 0))
    (if (i32.gt_s (local.get $n) (i32.const 1))
      (then
        (i32.store
          (i32.add (local.get $arr_ptr) (i32.const 4))
          (i32.const 1)
        )
      )
    )

    ;; 迭代计算后续元素
    (local.set $prev (i32.const 0))
    (local.set $curr (i32.const 1))
    (local.set $i (i32.const 2))
    (loop $loop
      ;; 计算下一个斐波那契数
      (local.set $next (i32.add (local.get $prev) (local.get $curr)))
      (local.set $prev (local.get $curr))
      (local.set $curr (local.get $next))

      ;; 存储到内存
      (i32.store
        (i32.add
          (local.get $arr_ptr)
          (i32.mul (local.get $i) (i32.const 4))
        )
        (local.get $next)
      )

      ;; 循环控制
      (local.set $i (i32.add (local.get $i) (i32.const 1)))
      (br_if $loop (i32.lt_s (local.get $i) (local.get $n)))
    )

    ;; 返回数组起始地址
    (local.get $arr_ptr)  
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 wabt 编译代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wat2wasm ./fib.wat &lt;span class="nt"&gt;-o&lt;/span&gt; ./fib.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;得到 wasm 文件：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── fib.wasm
├── fib.wat
└── main.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;main.ts 提供了宿主调用 wasm 代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fibUrl&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fib.wasm?url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateStreaming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fibUrl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 解构导出对象，获得 fib 函数 和 内存&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Memory&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="c1"&gt;// 斐波那契数列长度&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// 计算，结果保存在内存中，获得保存结果的指针&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fib&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// 读取结果&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用 vite 作为构建工具，vite 支持将任何类型资源作为 url 导入，这里将 wasm 转化成资源地址，使用 &lt;code&gt;WebAssembly.instantiateStreaming&lt;/code&gt; 和 &lt;code&gt;fetch&lt;/code&gt; 加载。实际上 vite 也支持使用 &lt;code&gt;?init&lt;/code&gt; 后缀加载并自动初始化 wasm 文件，详细可以查阅 vite 文档。&lt;/p&gt;

&lt;p&gt;这里尝试使用 &lt;code&gt;fib&lt;/code&gt; 函数去计算长度为 10 的斐波那契数列，输出结果为：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouxn7vsvxz4nvjq7pexx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fouxn7vsvxz4nvjq7pexx.png" alt=" " width="598" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;符合预期。&lt;/p&gt;

&lt;p&gt;看一下 wat 代码和 wasm 文件的大小对比：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqygcvuhh6mcb7xeli6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqygcvuhh6mcb7xeli6d.png" alt=" " width="309" height="50"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;可以看出，经过编译后，wasm 文件比源码小了一个数量级。如此高效除了 wasm 格式紧凑以外， LEB128 压缩也贡献不少。&lt;/p&gt;

&lt;h2&gt;
  
  
  分析 wasm
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wasm-objdump&lt;/code&gt; 命令跟操作系统 &lt;code&gt;objdump&lt;/code&gt; 类似，用来分析 wasm 文件信息。有什么用？这里举一个例子：开发者拿到一个 wasm 模块，想要快速知道导出函数都有哪些，每个函数的入参数量和类型，返回值长度和类型，就可以使用 &lt;code&gt;wasm-objdump&lt;/code&gt; 工具分析。以上一节 fib.wasm 文件为例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Export &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;输出信息&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Export[2]:
 - memory[0] -&amp;gt; &lt;span class="s2"&gt;"memory"&lt;/span&gt;
 - func[2] &amp;lt;fib&amp;gt; -&amp;gt; &lt;span class="s2"&gt;"fib"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;说明 fib.wasm 有两个导出项，一个是内存，另一个是 &lt;code&gt;fib&lt;/code&gt; 函数，函数类型是 2 ，接下来再导出函数签名&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Function &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;得到：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Function[2]:
 - func[1] &lt;span class="nv"&gt;sig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
 - func[2] &lt;span class="nv"&gt;sig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &amp;lt;fib&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;函数 2 的类型在 &lt;code&gt;Type&lt;/code&gt; 段声明，且使用索引为 1 的类型，导出 &lt;code&gt;Type&lt;/code&gt; 段：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wasm-objdump ./fib.wasm &lt;span class="nt"&gt;-j&lt;/span&gt; Type &lt;span class="nt"&gt;-x&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Type&lt;/code&gt; 信息为：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fib.wasm:       file format wasm 0x1

Section Details:

Type[2]:
 - &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0] &lt;span class="o"&gt;(&lt;/span&gt;i32&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; nil
 - &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;1] &lt;span class="o"&gt;(&lt;/span&gt;i32&lt;span class="o"&gt;)&lt;/span&gt; -&amp;gt; i32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最终获取到了 fib.wasm 导出内容的全部信息：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;导出内存&lt;/li&gt;
&lt;li&gt;导出函数 &lt;code&gt;fib&lt;/code&gt; ，&lt;code&gt;fib&lt;/code&gt; 有一个 &lt;code&gt;i32&lt;/code&gt; 类型的入参，有一个 &lt;code&gt;i32&lt;/code&gt; 类型的返回值&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;wasm-objdump -x&lt;/code&gt; 参数可以单独直接使用，会输出 wasm 文件所有块信息，信息量小的文件可以直接全部输出，信息量大的文件建议分块输出，方便分析。&lt;/p&gt;

&lt;h2&gt;
  
  
  整理代码
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wat-desugar&lt;/code&gt; 命令可以用来整理现有的 wat 代码，以符合某些规范。比如上文的 fib.wat 源码没有严格按照 &lt;code&gt;操作数压栈 -&amp;gt; 执行指令&lt;/code&gt; 的流程编写，经常将操作数写在指令后面，尽管是合法的写法，但不符合栈式虚拟机的范式。使用 &lt;code&gt;wat-desugar&lt;/code&gt; 可以帮助我们规范这份代码。这里展示 &lt;code&gt;$allocate&lt;/code&gt; 函数整理后的代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func $allocate (param $size i32) (result i32)
    (local $start i32)
    global.get $heap_ptr
    local.set $start
    global.get $heap_ptr
    local.get $size
    i32.add
    global.set $heap_ptr
    local.get $start
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;结合上文源码可以看出来整理后的代码紧凑，更加符合栈式调用的范式，但是更不易读了。最明显的区别就是加法运算 &lt;code&gt;i32.add&lt;/code&gt; ，源码将操作数放在了指令的后面，而规范写法应该是先压栈再调用加法指令。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“desugar” 这个名称和 “Syntactic Sugar （语法糖）” 这个词语是相对的，形如 &lt;code&gt;(i32.add (local.get 0) (local.get 1))&lt;/code&gt; 这样的写法是一种语法糖，指令调用之前操作数并没有先压栈（有点像寄存器虚拟机），编译器接受这种不符合栈式虚拟机的语法，但这种写法仿佛更容易让人理解。“desugar” 类似广东人说的“&lt;strong&gt;走糖&lt;/strong&gt;”，将语法糖代码的糖分去掉，使用语法最原汁原味的代码，老老实实先压栈再计算。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  反汇编
&lt;/h2&gt;

&lt;p&gt;wabt 工具有 3 个反汇编命令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wasm2wat&lt;/code&gt; 将 wasm 反汇编成 wat 代码&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm2c&lt;/code&gt; 将 wasm 反汇编成 C 源码和头文件&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wasm-decompile&lt;/code&gt; 将 wasm 反汇编成容易阅读的 C 风格伪代码&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;笔者在开发过程中 &lt;code&gt;wasm-decompile&lt;/code&gt; 配合 &lt;code&gt;wasm2wat&lt;/code&gt; 使用比较多。 &lt;code&gt;wasm-decompile&lt;/code&gt; 用来分析功能的实现，如果需要对模块小修小改，使用 &lt;code&gt;wasm2wat&lt;/code&gt; 获得 wat 文件，修改后再用编译命令将 wat 编译成新的 wasm 即可。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>programming</category>
      <category>javascript</category>
      <category>wabt</category>
    </item>
    <item>
      <title>WebAssembly 基础（二）</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Tue, 02 Sep 2025 08:16:57 +0000</pubDate>
      <link>https://dev.to/yangholmes/webassembly-ji-chu-er--5chd</link>
      <guid>https://dev.to/yangholmes/webassembly-ji-chu-er--5chd</guid>
      <description>&lt;h2&gt;
  
  
  wasm 二进制格式结构
&lt;/h2&gt;

&lt;p&gt;和其他二进制格式一样，wasm 二进制格式也是以 &lt;strong&gt;魔术数&lt;/strong&gt;+&lt;strong&gt;版本号&lt;/strong&gt; 开头（ &lt;strong&gt;Magic Number&lt;/strong&gt; + &lt;strong&gt;Version&lt;/strong&gt; ），其他模块按照不同的类别聚合放在不同的&lt;strong&gt;段&lt;/strong&gt;（  &lt;strong&gt;Segment&lt;/strong&gt; ）中，&lt;strong&gt;严格按照顺序排列&lt;/strong&gt;，分配 ID 。wasm 一共有 12 个段，魔术数 和 版本号 没有分配 ID ，位于开头，其他段均有 ID ，范围是 1~11 ，ID 0 特殊，不需要按照顺序出现。二进制文件格式对人类阅读不友好，这里详细展开讨论，只介绍每个段的功能。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadcn7shhps4cxd1xbiyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fadcn7shhps4cxd1xbiyq.png" alt=" " width="800" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  0. 自定义段 Custom Section
&lt;/h3&gt;

&lt;p&gt;自定义段是可以用来存放任何数据，比如提供给编译器等工具使用，记录函数名等调试信息。这个段在模块中可有可无，wasm 不执行自定义段也不会出错。另外，虽然自定义段的 ID 是 0 ，但不必要出现在开头或者结尾，可以出现在任何一个非自定义段的前面或者后面，且可以存在多个自定义段。常见的内容有 sourceMap 链接、DWARF 调试信息。&lt;/p&gt;

&lt;h3&gt;
  
  
  1. 类型段 Type Section
&lt;/h3&gt;

&lt;p&gt;列出模块中所有函数原型（或者说函数签名、函数类型），即函数的参数和返回值。类似 C 的头文件。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 导入段 ​​Import Section
&lt;/h3&gt;

&lt;p&gt;列出模块所有导入项，包括函数、内存、表、全局变量。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 函数段 Function Section
&lt;/h3&gt;

&lt;p&gt;列出内部函数对应的签名索引。&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 表段 Table Section
&lt;/h3&gt;

&lt;p&gt;定义模块使用的表，如函数引用。 wasm v1.0 规定只有一张表，v2.0 表数量可以有多个。&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 内存段 Memory Section
&lt;/h3&gt;

&lt;p&gt;列出模块使用的线性内存，包括内存的初始页数、最大页数、内存数量。wasm v1.0 规定一个模块只能有一块内存，v2.0 内存数量可以有多个。&lt;/p&gt;

&lt;h3&gt;
  
  
  6. 全局段 Global Section
&lt;/h3&gt;

&lt;p&gt;定义全局变量及其初始值。&lt;/p&gt;

&lt;h3&gt;
  
  
  7. 导出段 ​​Export Section
&lt;/h3&gt;

&lt;p&gt;声明模块对外暴露的对象。&lt;/p&gt;

&lt;h3&gt;
  
  
  8. 起始段 ​​Start Section
&lt;/h3&gt;

&lt;p&gt;指定起始函数，类似于 C 的 &lt;code&gt;main&lt;/code&gt; 函数，在模块初始化时自动执行。&lt;/p&gt;

&lt;h3&gt;
  
  
  9. 元素段 Element Section
&lt;/h3&gt;

&lt;p&gt;表的初始化数据。&lt;/p&gt;

&lt;h3&gt;
  
  
  10. 代码段
&lt;/h3&gt;

&lt;p&gt;所有函数的二进制指令。函数段（ID 3）和代码段（ID 10）必须一一对应。&lt;/p&gt;

&lt;h3&gt;
  
  
  11. 数据段
&lt;/h3&gt;

&lt;p&gt;初始化线性内存。&lt;/p&gt;

&lt;p&gt;其中，类型段（1）、函数段（3）、代码段（10）是必需的，其他段可以省略，自定义段不参与代码执行。3 和 10 对应，4 和 9对应，5 和 11 对应。&lt;/p&gt;

&lt;p&gt;因为 wasm 具有&lt;strong&gt;严格的段顺序&lt;/strong&gt;，&lt;strong&gt;支持流式加载&lt;/strong&gt;，所以 wasm 可以一边加载，一边解析，一边验证，一边编译，初始化效率非常高。&lt;/p&gt;

&lt;p&gt;wasm 二进制格式采用小端方式（Little-Endian）编码，wasm 的魔术数为 &lt;code&gt;\0asm&lt;/code&gt; ，占 4 个字节，版本号也占 4 个字节，&lt;code&gt;\0asm&lt;/code&gt; 十六进制编码为 &lt;code&gt;0x6D736100&lt;/code&gt; ，版本为 1 十六进制编码为 &lt;code&gt;0x00000001&lt;/code&gt; 。在 wasm 文件中编码为&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 61 73 6D 01 00 00 00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;wasm 采用 &lt;a href="https://en.wikipedia.org/wiki/LEB128" rel="noopener noreferrer"&gt;LEB128&lt;/a&gt; 编码整数值，采用 &lt;a href="https://en.wikipedia.org/wiki/IEEE_754" rel="noopener noreferrer"&gt;IEEE 754&lt;/a&gt; 编码浮点数值。 LEB128 是一种变长码压缩，可以减少整型数的存储空间，压缩代码；IEEE 754 是常用的浮点数储存方法，这两个编码方式这里不展开。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyv9hjhgge9jqdwdv3kn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyv9hjhgge9jqdwdv3kn2.png" alt="wasm binary code" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  wat 语法
&lt;/h2&gt;

&lt;p&gt;wat 是 wasm 文本格式（ WebAssembly Text ），基于 &lt;strong&gt;S-Expression&lt;/strong&gt; 的一种嵌套括号结构，是 wasm 等效的文本形式，失去了 wasm 严格的段顺序，取而代之的是更容易阅读的表达式顺序。wat 和 wasm 之间可以使用 &lt;a href="https://github.com/WebAssembly/wabt" rel="noopener noreferrer"&gt;&lt;code&gt;wabt&lt;/code&gt;&lt;/a&gt; 工具转换。这里介绍一下 wat 核心语法结构。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;S 表示 symbolic ，符号的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. 模块定义 Module
&lt;/h3&gt;

&lt;p&gt;一个 wasm/wat 文件定义一个模块，以 &lt;code&gt;(module ...)&lt;/code&gt; 代码包裹：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  ;; 内部包含：函数、内存、表、全局变量等定义
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;wat 使用 &lt;code&gt;;;&lt;/code&gt; 注释&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. 函数 Functions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.1 函数签名
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func $name (param $a i32) (param $b f64) (result f64) ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;$name：函数名（可选，调试用）&lt;/li&gt;
&lt;li&gt;param：参数类型（如 i32, f64）&lt;/li&gt;
&lt;li&gt;result：返回值类型&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;wasm v1.0 规定函数只能有一个返回值，wasm v2.0 函数可以返回多个值，例如&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func $duplicate (param $x i32) (result i32 i32)
  local.get $x   ;; 第一个返回值
  local.get $x   ;; 第二个返回值
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在 JavaScript 中 &lt;code&gt;WebAssembly.Instance&lt;/code&gt; 只支持一个返回值，当有多个返回值时，会自动封装成&lt;strong&gt;数组&lt;/strong&gt;。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;duplicate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;     &lt;span class="c1"&gt;// [5, 5]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.2 局部变量
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func (param i32)
  (local.get 0)           ;; 获取第 0 个参数
  (local $var i32)        ;; 声明局部变量
  (local $a f64 $b i64)   ;; 声明多个同类型变量
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.3 函数体指令
&lt;/h4&gt;

&lt;p&gt;指令按​​栈模型​​顺序执行&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func $add (param $a i32) (param $b i32) (result i32)
  local.get $a    ;; 将 $a 压栈
  local.get $b    ;; 将 $b 压栈
  i32.add         ;; 弹出栈顶两个值，相加后压回结果
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. 类型 Types
&lt;/h3&gt;

&lt;p&gt;类型可以是 inline 的，也可以显式定义复用：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(type $AddSig (func (param i32 i32) (result i32)))
(func $add (type $AddSig) 
  local.get 0
  local.get 1
  i32.add
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. 内存 Memory
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.1 内存定义
&lt;/h4&gt;

&lt;p&gt;wasm 只有四种数据类型 &lt;code&gt;i32&lt;/code&gt; &lt;code&gt;i64&lt;/code&gt; &lt;code&gt;f32&lt;/code&gt; &lt;code&gt;f64&lt;/code&gt; ，当需要处理其他的数据类型时，需要使用内存。内存可以在 JavaScript 中定义，也可以在 wasm 内部定义：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 内部定义
(memory &amp;lt;min&amp;gt; &amp;lt;max&amp;gt;?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;内存可以具名，方便导出：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(module
  ;; 定义内存：初始 5 页 (5 * 64KiB)，最大 10 页
  (memory $mem 5 10)

  ;; 导出内存，方便外部访问
  (export "memory" (memory $mem))
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;内存可以从宿主导入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 导入外部定义
;; 从 JavaScript 导入一个内存对象，模块名 "js"，内存名 "mem"
(import "js" "mem" (memory 5 10))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JavaScript 定义：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// 5 页&lt;/span&gt;
  &lt;span class="na"&gt;maximum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;  &lt;span class="c1"&gt;// 最大 10 页&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateStreaming&lt;/span&gt;&lt;span class="p"&gt;(...,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;宿主导入的内存初始页数和最大页数要覆盖 wat 导入定义的初始页数和最大页数，也就是说，&lt;strong&gt;wat 导入声明初始页数 ​​≤​​ 宿主初始页数&lt;/strong&gt;，&lt;strong&gt;宿主最大页数 ≥​​ wat 导入声明最大页数&lt;/strong&gt;。否则模块初始化时，在验证阶段会报错。&lt;/p&gt;

&lt;h4&gt;
  
  
  4.2 读内存
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 内存读取
[类型].load[位数][_符号]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;符号后缀有两种， &lt;code&gt;_u&lt;/code&gt; 表示无符号， &lt;code&gt;_s&lt;/code&gt; 表示有符号。&lt;/p&gt;

&lt;p&gt;常用的指令有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;i32.load&lt;/code&gt; &lt;code&gt;i32.load8_s&lt;/code&gt; &lt;code&gt;i32.load8_u&lt;/code&gt; &lt;code&gt;i32.load16_s&lt;/code&gt; &lt;code&gt;i32.load16_u&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;i64.load&lt;/code&gt; &lt;code&gt;i64.load8_s&lt;/code&gt; &lt;code&gt;i64.load8_u&lt;/code&gt; &lt;code&gt;i64.load16_s&lt;/code&gt; &lt;code&gt;i64.load16_u&lt;/code&gt; &lt;code&gt;i64.load32_s&lt;/code&gt; &lt;code&gt;i64.load32_u&lt;/code&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;f32.load&lt;/code&gt; &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;f64.load&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;加载指令看起来比较复杂，比如 &lt;code&gt;i32.load8_s&lt;/code&gt; ，这条指令各部分的含义是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;i32：结果类型是 32 位整数&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;load：表示从内存中加载数据&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;8：表示加载的数据宽度是 8 位 (1 字节)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;_s：表示是有符号扩展（sign-extend）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;从内存中读取 8 位 (0..255) -&amp;gt; 这 8 位数字是有符号整型 (-128..127) -&amp;gt; 扩展成 32 位整数 (i32) -&amp;gt; 返回&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func (param $ptr i32) (result i32)
  local.get $ptr ;; 内存地址偏移量
  i32.load8_s    ;; 返回 -128..127 的无符号字节
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;读内存指令除了指定地址参数外，还可以指定对齐方式和地址偏移量，默认这两个参数可以省略。如果需要指定对齐方式和偏移，指令变成：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[类型].load[位数][_符号] align=N offset=M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这里 N 表示 2 的次幂，&lt;code&gt;align=3&lt;/code&gt; 表示按 2³ = 8 字节对齐。&lt;/p&gt;

&lt;p&gt;如果不指定对齐参数，那么将会使用&lt;code&gt;自然对齐&lt;/code&gt;，即访问内存的数据长度。如 &lt;code&gt;i32.load8_u&lt;/code&gt; 自然对齐为 8 位，一个字节，&lt;code&gt;align=0&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;在很多 CPU 架构上，内存访问对齐可以提升性能（按数据类型的自然边界访问更快），指令带上对齐信息，能帮助编译器做优化。一般地，对齐参数和读取的位数量保持一致可以获得更好的性能，如 &lt;code&gt;i32.load&lt;/code&gt; 自然对齐是 4 字节，即 &lt;code&gt;align=2&lt;/code&gt; ，&lt;code&gt;i64.load&lt;/code&gt; 自然对齐是 8 字节，&lt;code&gt;align=3&lt;/code&gt; 。如果指定了一个不合适的对齐方式参数，代码不会跑飞，编译器在编译的时候会去拼凑数据，使得字节数是对齐的，性能会变差。&lt;/p&gt;

&lt;p&gt;内存地址偏移量是一个无符号 32 位整型数，使用这个参数相当于扩展了 wasm 的寻址范围。在不使用偏移量时内存的寻址范围是 &lt;code&gt;2^32-1&lt;/code&gt; 大约为 4GB ；使用偏移量之后，寻址范围扩展到 &lt;code&gt;2^32-1 + 2^32-1 = 2^33 - 2&lt;/code&gt; 大约是 8GB 。有些情况下最大内存不超过 4GB （取决于宿主），如果尝试越界访问，会触发越界异常 trap 。&lt;/p&gt;

&lt;h4&gt;
  
  
  4.3 写内存
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 内存写入
[类型].store[位数]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;常用的指令有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;i32.store8&lt;/code&gt; &lt;code&gt;i32.store16&lt;/code&gt; &lt;code&gt;i32.store&lt;/code&gt;（默认 32 位）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;i64.store8&lt;/code&gt; &lt;code&gt;i64.store16&lt;/code&gt; &lt;code&gt;i64.store32&lt;/code&gt; &lt;code&gt;i64.store&lt;/code&gt; （默认 64 位）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;f32.store&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;f64.store&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 把字节 72 ('H') 写入位置 0
i32.const 0     ;; address
i32.const 72    ;; value
i32.store8      ;; 在 address 写一个字节
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;利用更宽的指令可以一次性写入更多数据，要注意 wasm 采用小端存储：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 写入字符 'h' 'e' 'l' 'l'
i32.const 0           ;; 内存地址
i32.const 0x6c6c6568  ;; memory bytes -&amp;gt; 68 65 6c 6c =&amp;gt; 'h','e','l','l'
i32.store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;写内存指令也可以提供&lt;strong&gt;对齐参数&lt;/strong&gt;和&lt;strong&gt;偏移量&lt;/strong&gt;，含义和读内存一致，这里就不赘述了。&lt;/p&gt;

&lt;h4 id="字符串直接写入"&gt;4.4 字符串直接写入&lt;/h4&gt;

&lt;p&gt;wasm 只支持 4 中数值数据类型，如何将字符串直接写入内存？字符串写入需要使用 &lt;code&gt;data&lt;/code&gt; 指令，以 UTF-8 字节写入内存：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 字符串写入默认内存
(data (i32.const &amp;lt;offset&amp;gt;) "bytes" ...)

;; 字符串写入具名内存
(data (memory $mem) (i32.const &amp;lt;offset&amp;gt;) "xxx\00")  ;; \00 表示终止符
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;举个例子，往内存中写入 “Hello World”：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(data (i32.const 0) "hello world\00")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5 其他内存控制指令
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;memory.size&lt;/code&gt; 返回当前内存页数（i32，单位 page）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;memory.grow&lt;/code&gt; 内存增长，参数为需要增长的页数，执行成功返回增长前的页数（失败返回 -1）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;memory.copy&lt;/code&gt; 参数为 dest src len ，把&lt;code&gt;源地址+长度&lt;/code&gt;的数据复制到&lt;code&gt;目标地址+长度&lt;/code&gt;内存中，支持重叠&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;memory.fill&lt;/code&gt; 参数为 dest value len ，把 len 个字节填充为 value&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="表_Tables"&gt;5. 表 Tables&lt;/h3&gt;

&lt;p&gt;表有两种类型，一种是函数引用 &lt;code&gt;funcref&lt;/code&gt; ，另一种是宿主对象引用（外部引用） &lt;code&gt;externref&lt;/code&gt; 。基本的语法为&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(table $tbl &amp;lt;min&amp;gt; &amp;lt;max&amp;gt;? &amp;lt;reftype&amp;gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;min&lt;/code&gt; 定义了表的最小尺寸，&lt;code&gt;max&lt;/code&gt; 定义表最大扩展尺寸。表的尺寸限定函数/引用的数量。表是 wasm 和宿主之间隐藏代码位置，间接调用/访问的方法。假如 wasm 要向 JavaScript 提供自增和自减两个函数，但不希望宿主直接获取函数的索引：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; xxx.wat
(module
  ;; 定义一个函数类型：参数类型 i32，返回类型 i32
  (type $t (func (param i32) (result i32)))

  ;; 定义两个函数
  (func $inc (type $t) (param $x i32) (result i32)
    local.get $x
    i32.const 1
    i32.add
  )

  (func $dec (type $t) (param $x i32) (result i32)
    local.get $x
    i32.const 1
    i32.sub
  )

  ;; 定义一个表，初始大小 2，最大 5，用来存放函数引用
  (table $tbl 2 5 funcref)

  ;; 初始化表内容：索引 0 存 $inc，索引 1 存 $dec
  (elem (i32.const 0) $inc $dec)

  ;; 使用 call_indirect 调用表中的函数
  ;; 参数：表索引、函数参数
  (func (export "call_from_table") (param $idx i32) (param $x i32) (result i32)
    local.get $x      ;; push 参数
    local.get $idx    ;; push 函数索引
    call_indirect (type $t) (table $tbl)
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;WebAssembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instantiateStreaming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;xxx.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 11 (自增)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_from_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 9  (自减)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其他指令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.get &amp;lt;tableidx&amp;gt;&lt;/code&gt; 从表里取出一个引用&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.set &amp;lt;tableidx&amp;gt;&lt;/code&gt; 向表中写入一个引用&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.size &amp;lt;tableidx&amp;gt;&lt;/code&gt; 取表当前大小&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.grow &amp;lt;tableidx&amp;gt;&lt;/code&gt; 扩展表容量&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.fill &amp;lt;tableidx&amp;gt;&lt;/code&gt; 批量填充某个引用&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table.copy &amp;lt;dst&amp;gt; &amp;lt;src&amp;gt;&lt;/code&gt; 从一张表拷贝到另一张表&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. 全局变量 Globals
&lt;/h3&gt;

&lt;p&gt;全局变量可以定位为常量或变量：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(global $counter (mut i32) (i32.const 0)) ;; 可变全局变量
(global $PI f64 (f64.const 3.14159))      ;; 常量
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. 导入和导出 Imports &amp;amp; Exports
&lt;/h3&gt;

&lt;h4&gt;
  
  
  7.1 导入
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 导入函数
(import "env" "log" (func $log (param i32)))
;; 导入内存
(import "js" "mem" (memory 1))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  7.2 导出
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 导出函数
(export "add" (func $add))
;; 导出内存
(export "shared_mem" (memory $mem))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. 控制流
&lt;/h3&gt;

&lt;p&gt;wasm 的控制流指令有四类：块/循环、条件、跳转、控制。&lt;/p&gt;

&lt;h4&gt;
  
  
  8.1 块/循环
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;block&lt;/code&gt; 和 &lt;code&gt;loop&lt;/code&gt; 。&lt;code&gt;block&lt;/code&gt; 创建具名的代码块，可以声明返回值，块结束时会把栈顶值作为块的返回值，配合 &lt;code&gt;br*&lt;/code&gt; 可以实现跳转。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(block $label (result &amp;lt;type&amp;gt;?) ... )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;loop&lt;/code&gt; 是创建循环结构的入口，同样可以声明返回值，循环结束时会把栈顶值作为块的返回值，需要配合 &lt;code&gt;br*&lt;/code&gt; 跳转到循环开头。如果循环块中没有 &lt;code&gt;br*&lt;/code&gt; ，&lt;code&gt;loop&lt;/code&gt; 并不会自动循环。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(loop $label (result &amp;lt;type&amp;gt;?) ... )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 等效于 for (let i = 0; i &amp;lt; 5; i++) {}
(local $i i32)
(loop $start
  (local.set $i (i32.add (local.get $i) 1))
  (br_if $start (i32.lt_s (local.get $i) (i32.const 5)))  ;; 循环5次
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  8.2 条件
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;if&lt;/code&gt; 用栈顶 i32 数值作为条件选择执行 &lt;code&gt;then&lt;/code&gt; 或者 &lt;code&gt;else&lt;/code&gt;，可以声明返回值，&lt;code&gt;then&lt;/code&gt; 和 &lt;code&gt;else&lt;/code&gt; 必须产生这个类型的返回。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(if (result &amp;lt;type&amp;gt;?)
  (then ...)
  (else ...)
  (end)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;例如编写一个输出参数绝对值加一的函数：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(func (param $x i32) (result i32)
  local.get $x
  i32.const 0
  i32.lt_s        ;; x &amp;lt; 0 ?
  if (result i32)
    ;; then: x 是负数 -&amp;gt; return -x + 1
    local.get $x
    i32.const -1
    i32.mul       ;; 取反 x = x * -1
    i32.const 1
    i32.add
  else
    ;; else: x &amp;gt;= 0 -&amp;gt; x + 1
    local.get $x
    i32.const 1
    i32.add
  end
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  8.3 跳转
&lt;/h4&gt;

&lt;p&gt;跳转指令有 3 个：无条件跳转 &lt;code&gt;br&lt;/code&gt; ，条件跳转 &lt;code&gt;br_if&lt;/code&gt; ，多路分支跳转 &lt;code&gt;br_table&lt;/code&gt; 。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; 无条件跳转
br &amp;lt;label&amp;gt;         
;; 消耗栈顶(i32)，若非零则跳转  
br_if &amp;lt;label&amp;gt;    
;; 栈顶(i32)作为索引，如果栈顶是 `[0, n]` 则跳转到对应的 `label` ，否则跳转到 `default`   
br_table &amp;lt;label_0&amp;gt; &amp;lt;label_1&amp;gt; ... &amp;lt;label_n&amp;gt; &amp;lt;default_label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;label&amp;gt;&lt;/code&gt; 可以是块的名称，也可以使 &lt;code&gt;i32&lt;/code&gt; 数字，使用数字时，表示跳出的层级，&lt;code&gt;0&lt;/code&gt; 表示跳出当前层级，&lt;code&gt;1&lt;/code&gt; 表示跳出父层级，以此类推。建议使用块名称。所谓跳转，并非跳转到定义块的首行，而是跳转到定义块的结尾。&lt;/p&gt;

&lt;p&gt;举几个例子：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; br
(block $outer
  (block $inner
    (br $outer)  ;; 直接跳出到 $outer
  )
  (unreachable)   ;; 此处往后，块内指令不会执行
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; br_if
(func (param $p i32) (result i32)
  (block $outer (result i32)
    (block $inner
      local.get $p
      i32.const 0
      i32.eq
      ;; 如果输入 p === 0 则跳到 $outer，带上 999 作为 $outer 的返回值
      i32.const 999
      br_if $outer
    )
    ;; 没有跳转则到这里，$outer 返回 123
    i32.const 123
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;;; br_table
(func (param $i i32) (result i32)
  (block $end (result i32)
    (block $default
      (block $case2
        (block $case1
          (block $case0
            local.get $i
            ;; 参数顺序： &amp;lt;labels...&amp;gt; &amp;lt;default&amp;gt;
            ;; idx === 0 -&amp;gt; branch to $case0
            br_table $case0 $case1 $case2 $default
          ) ;; end $case0
          ;; 当分支到 $case0 时会执行下面这句
          i32.const 10
          br $end
        ) ;; end $case1
        ;; 当分支到 $case1 时会执行下面这句
        i32.const 20
        br $end
      ) ;; end $case2
      ;; 当分支到 $case2 时会执行下面这句
      i32.const 30
      br $end
    ) ;; end $default
    ;; 当 br_table 命中 default（index 超范围）时，会跳到 $default 的结束处，
    ;; 然后继续执行这里的代码 —— 返回默认值 99
    i32.const 99
  ) ;; end $end
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果块定义了返回值，记得在跳转之前先把返回值压栈。要注意返回值和判断值的压栈顺序是，&lt;strong&gt;先压返回值，再压判断条件&lt;/strong&gt;，不压返回值或压栈顺序不对，类型检查会失败。&lt;/p&gt;

&lt;h4&gt;
  
  
  8.4 函数控制
&lt;/h4&gt;

&lt;p&gt;函数控制指令有两类，一类是调用函数 &lt;code&gt;call&lt;/code&gt; 和 &lt;code&gt;call_indirect&lt;/code&gt; ，另一类是终止函数 &lt;code&gt;return&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;call&lt;/code&gt; 是直接调用， &lt;code&gt;call_indirect&lt;/code&gt; 是使用表索引间接调用。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;call $funcname
call_indirect (type $t) (table $tbl?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;直接调用很简单，这里不举例了， &lt;code&gt;call_indirect&lt;/code&gt; 比较复杂，在 表 这一章节中有示例，可以倒回去阅读代码，这里解释参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type $t&lt;/code&gt; 表示当前调用的函数签名&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;table $tbl?&lt;/code&gt; 当前调用的表索引，可以不指定，默认使用 表0&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;调用函数在表中的索引和函数参数从栈获取，栈顶是索引，然后是参数。所以&lt;strong&gt;压栈的顺序是先压参数后压索引&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;return&lt;/code&gt; 是直接跳出函数，并返回值。返回值从栈顶取，所以在执行之前先压栈。&lt;/p&gt;

&lt;h4&gt;
  
  
  8.5 其他
&lt;/h4&gt;

&lt;p&gt;还有一些不太好分类的流控制指令：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;unreachable&lt;/code&gt; 立即触发 trap ，即运行时异常。用于标记不应触及的路径或产生错误&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;nop&lt;/code&gt; 空操作，不做任何事，对行为无影响，占用一个机器指令执行周期&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9. 数据段初始化内存
&lt;/h3&gt;

&lt;p&gt;详见 字符串直接写入。&lt;/p&gt;

&lt;h3&gt;
  
  
  10. 表初始化
&lt;/h3&gt;

&lt;p&gt;详见 表。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Getting Started with the Emscripten File System</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Wed, 27 Aug 2025 04:33:35 +0000</pubDate>
      <link>https://dev.to/yangholmes/getting-started-with-the-emscripten-file-system-57h7</link>
      <guid>https://dev.to/yangholmes/getting-started-with-the-emscripten-file-system-57h7</guid>
      <description>&lt;p&gt;Let's step beyond the scope of GDAL and explore the features of emscripten.&lt;/p&gt;

&lt;h2&gt;
  
  
  File System
&lt;/h2&gt;

&lt;p&gt;In computing, a &lt;strong&gt;File System&lt;/strong&gt; is a method for managing and accessing data in the form of &lt;strong&gt;files&lt;/strong&gt;. Data is stored on a variety of hardware devices, each with its own way of accessing data. The file system abstracts the complexities of data management and access into a unified interface, allowing users to manage and access stored data without understanding the underlying physical device parameters. Different operating systems have developed various file systems over time—for example, Linux supports ext, ext2, etc.; Windows supports NTFS, FAT32, etc.; and macOS supports HFS+, APFS, etc. These are not entirely compatible with each other.&lt;/p&gt;

&lt;p&gt;To enable software to run across different UNIX-like operating systems, the IEEE developed the POSIX standard. Linux largely adheres to the POSIX specification, including its file system conventions. Linux implements abstraction through the &lt;strong&gt;VFS (Virtual File System)&lt;/strong&gt; layer, a software layer in the kernel that provides a common interface for all types of file systems. Applications and system calls interact only with the VFS, which routes operations to specific file system drivers (e.g., ext4, NTFS).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why discuss Linux and POSIX? Two main reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Most algorithm libraries are compiled in operating systems that support POSIX-compliant file systems.&lt;/li&gt;
&lt;li&gt;Emscripten’s file system design is inspired by Linux’s POSIX compatibility.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How Applications Access the File System
&lt;/h2&gt;

&lt;p&gt;Operating systems provide library functions for file access to applications. In C/C++, this library is &lt;strong&gt;libc/libc++&lt;/strong&gt;. These library functions further encapsulate the details of file system operations, turning file operations into operations on file handles. This approach offers several advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reduces kernel system calls.&lt;/li&gt;
&lt;li&gt;Facilitates compatibility across different operating systems.&lt;/li&gt;
&lt;li&gt;Simplifies operations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc3mkaqn33ng2g7kzxqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc3mkaqn33ng2g7kzxqf.png" alt="File Reading Architecture" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To read a file in a C program, the process is: &lt;code&gt;Open File → Read Data → Close File&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// Open text file (read-only)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fopen failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;                    &lt;span class="c1"&gt;// Buffer to store each line&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Data is now in buffer; use as needed&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g., process the string, parse content, etc.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ferror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                     &lt;span class="c1"&gt;// Check for read errors&lt;/span&gt;
  &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"read error"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                          &lt;span class="c1"&gt;// Close the file&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;libc&lt;/strong&gt; (C Standard Library) includes 30 header files to date. &lt;code&gt;&amp;lt;stdio.h&amp;gt;&lt;/code&gt; contains core input/output functions, &lt;code&gt;&amp;lt;stdlib.h&amp;gt;&lt;/code&gt; includes functions for number conversion, memory allocation, process control, etc. Other commonly used headers include &lt;code&gt;&amp;lt;math.h&amp;gt;&lt;/code&gt; for math functions and &lt;code&gt;&amp;lt;assert.h&amp;gt;&lt;/code&gt; for assertions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How WebAssembly Reads and Writes Files
&lt;/h2&gt;

&lt;p&gt;In JavaScript, files are typically stored as &lt;code&gt;File&lt;/code&gt; objects, which inherit from &lt;code&gt;Blob&lt;/code&gt; and are essentially large chunks of binary data. If designing an algorithm from scratch, one might write files into memory and pass an &lt;code&gt;ArrayBuffer&lt;/code&gt; as a pointer when calling functions. However, mature libraries often rely on file system operations—using file paths to locate files and file handles to pass them—making it difficult to switch to pointer-based approaches.&lt;/p&gt;

&lt;p&gt;To address this, Emscripten provides a set of interfaces compatible with standard file operations. Inspired by POSIX, these interfaces closely resemble Linux’s file operations. For applications, the file system is transparent; they only know how to read and write files via libc interfaces, unaware of the underlying data storage mechanisms. During compilation, Emscripten performs a "bait-and-switch," replacing libc interfaces with syscalls and substituting the operating system’s VFS calls with Emscripten VFS calls, enabling WebAssembly file operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhof30ykao069mno071v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhof30ykao069mno071v.png" alt="Conversion Process" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Emscripten File System
&lt;/h2&gt;

&lt;p&gt;With the file operation interfaces in place, how is the data stored? Emscripten offers a flexible virtual file system architecture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx5km56td0iy16mu3uua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx5km56td0iy16mu3uua.png" alt="Emscripten File System Architecture" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MEMFS
&lt;/h3&gt;

&lt;p&gt;The Memory File System is Emscripten’s default file system, automatically mounted at the root directory &lt;code&gt;/&lt;/code&gt;. Data is stored in memory and is lost when the page is refreshed.&lt;/p&gt;

&lt;h3&gt;
  
  
  NODEFS / NODERAWFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;These file systems can only be used in a Node.js environment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;NODEFS&lt;/strong&gt; proxies the host’s file system into Emscripten’s virtual file system using Node.js’s synchronous file APIs, indirect read/write access to the local disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NODERAWFS&lt;/strong&gt; bypasses Emscripten’s proxying and directly uses Node.js’s file module. The key difference is that NODEFS requires calling &lt;code&gt;FS.mount()&lt;/code&gt; to mount the virtual file system and access files via virtual paths, while NODERAWFS uses absolute physical paths directly without mounting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipjck3yn7cys1626muhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipjck3yn7cys1626muhb.png" alt=" " width="800" height="1188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NODERAWFS is faster than NODEFS, but NODEFS uses file caching to reduce system calls. Use NODERAWFS for reading/writing large files from disk, and NODEFS for handling small, fragmented files.&lt;/p&gt;

&lt;h3&gt;
  
  
  IDBFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;IDBFS can only be used in browsers, including Web Workers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;IDBFS stores data in an &lt;code&gt;IndexedDB&lt;/code&gt; instance. &lt;strong&gt;IndexedDB provides an asynchronous interface, while POSIX standards are synchronous&lt;/strong&gt;—the two are incompatible. When using IDBFS, Emscripten first stores data in MEMFS and tracks file changes. The user must call &lt;code&gt;FS.syncfs()&lt;/code&gt; to flush changes to IndexedDB. If the user forgets to call &lt;code&gt;FS.syncfs()&lt;/code&gt; before closing or refreshing the page, changes in MEMFS will be lost. This can be mitigated by listening to &lt;code&gt;pagehide&lt;/code&gt; or &lt;code&gt;beforeunload&lt;/code&gt; events to force a sync.&lt;/p&gt;

&lt;p&gt;When mounting IDBFS, the &lt;code&gt;autoPersist: true&lt;/code&gt; parameter can be set to automatically save changes after each file modification. However, frequent file changes may impact performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  WORKERFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;WORKERFS can only be used within Workers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This file system provides read-only access to &lt;code&gt;File&lt;/code&gt; and &lt;code&gt;Blob&lt;/code&gt; objects inside a Worker without copying the entire file data into memory, making it suitable for handling large files.&lt;/p&gt;

&lt;h3&gt;
  
  
  PROXYFS
&lt;/h3&gt;

&lt;p&gt;PROXYFS enables file sharing between multiple WebAssembly modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Module 2 can use the path "/fs1" to access and modify Module 1's filesystem&lt;/span&gt;
&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fs1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROXYFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;module1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fs1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Virtual File System Analysis
&lt;/h2&gt;

&lt;p&gt;The core data structure of Emscripten’s file system is &lt;code&gt;FSNode&lt;/code&gt;, which mimics the inode structure in Linux file systems. The basic structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;{
  &lt;/span&gt;&lt;span class="nc"&gt;node_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// Node operations (e.g., lookup, create)&lt;/span&gt;
  &lt;span class="nx"&gt;stream_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// Stream operations (e.g., read, write, seek)&lt;/span&gt;
  &lt;span class="nx"&gt;mounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Mount information of the node&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rdev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Root node sets parent to itself&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Parent node (directory node)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Node name (file or directory name)&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// File type and permissions&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rdev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rdev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Major/minor device numbers (0 for non-device files)&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextInode&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Globally unique node ID&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// File content (ArrayBuffer) or list of directory entries&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// File size in bytes&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// File system mounted at this node&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Access, modification, and status change times&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During file system initialization, &lt;code&gt;FS.mount(MEMFS, {}, '/')&lt;/code&gt; is called to mount the memory file system at the root directory. Other file systems can be mounted as needed within MEMFS, e.g.,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORKERFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="c1"&gt;// Array of File objects or FileList&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Mount WORKERFS at /worker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other file operations, such as &lt;code&gt;mkdir&lt;/code&gt;, &lt;code&gt;rmdir&lt;/code&gt;, &lt;code&gt;chmod&lt;/code&gt;, and &lt;code&gt;link&lt;/code&gt;, are implemented in the &lt;code&gt;FS&lt;/code&gt; object and can be called directly. The file system is hierarchical; unless it is a mount point, child nodes inherit the file system type from their parent:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir() -&amp;gt; mknod() -&amp;gt; lookupPath() -&amp;gt; new FSNode()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Application calls to &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, and &lt;code&gt;close&lt;/code&gt; are ultimately directed to &lt;code&gt;FS.open&lt;/code&gt;, &lt;code&gt;FS.read&lt;/code&gt;, &lt;code&gt;FS.write&lt;/code&gt;, and &lt;code&gt;FS.close&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;mode&lt;/code&gt; field records the file type and permissions using the POSIX standard, represented as a 32-bit integer. The first 8 bits indicate the file type, and the remaining 24 bits represent permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware Devices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Everything is a file&lt;/strong&gt;. Like other Unix-like operating systems, Emscripten’s virtual file system can register hardware devices. For example, to simulate a serial communication device in the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generate a device number&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Register device operations&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO Simulate getting baud rate&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Create a device node&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the serial port &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; can be read from and written to in C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Development
&lt;/h2&gt;

&lt;p&gt;Currently, Emscripten’s virtual file system is implemented in JavaScript, which has a significant drawback: it does not support multithreading. Emscripten is developing a new file system, &lt;strong&gt;WASMFS&lt;/strong&gt;, though it is not yet complete. In the future, WASMFS will support multithreading and is expected to deliver significant performance improvements.&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
      <category>emscripten</category>
    </item>
    <item>
      <title>EMSCRIPTEN File System 入门</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Tue, 26 Aug 2025 06:07:13 +0000</pubDate>
      <link>https://dev.to/yangholmes/emscripten-file-system-ru-men-43bh</link>
      <guid>https://dev.to/yangholmes/emscripten-file-system-ru-men-43bh</guid>
      <description>&lt;p&gt;这篇我们跳出 GDAL 的范围，讨论一下 emscripten 的特性。&lt;/p&gt;

&lt;h2&gt;
  
  
  文件系统
&lt;/h2&gt;

&lt;p&gt;在计算机中，文件系统 File System 一种以&lt;strong&gt;文件&lt;/strong&gt;方式管理和访问数据的方式。数据存储在形形色色不同的硬件设备中，每种不同的设备访问数据的方式都不一样，文件系统将这些晦涩难懂的数据管理和访问抽象成统一的接口，用户就可以在不了解物理设备参数的情况下，通过一个个简单的文件管理和访问存储在上面的数据。不同的操作系统各自在不同时期发展出不同的文件系统，比如 Linux 支持 ext 、ext2 等，Windows 支持 NTFS 、FAT32 等，Mac OS 支持 HFS+ 、APFS 等，它们之间并不完全兼容。&lt;/p&gt;

&lt;p&gt;为了能在不同的类 UNIX 操作系统之间运行软件， IEEE 制订了 POSIX 标准，Linux 基本上遵循了 POSIX 规范，包括文件系统。Linux 通过 ​​VFS（Virtual File System）​​ 层实现了抽象，VFS 是内核中的一个软件层，它为所有不同类型的文件系统提供了一个通用的接口。应用程序和系统调用只与 VFS 交互，由 VFS 将操作路由到具体的文件系统驱动（如  ext4, ntfs）。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;为什么要介绍 Linux 和 POSIX ？原因有 2 ：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;绝大多数算法库都在兼容 POSIX 的文件系统的操作系统中编译&lt;/li&gt;
&lt;li&gt;emscripten 的文件系统受到了 Linux 兼容 POSIX 的启发&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  应用程序对文件系统的访问
&lt;/h2&gt;

&lt;p&gt;操作系统为应用程序提供文件访问的库函数，在 C/C++ 中，这个库是 libc/libc++ 。库函数进一步封装了文件系统的操作细节，操作文件变成了操作文件句柄，这样做的好处有：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;减少系统内核调用&lt;/li&gt;
&lt;li&gt;方便兼容不同的操作系统&lt;/li&gt;
&lt;li&gt;简化操作&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc3mkaqn33ng2g7kzxqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc3mkaqn33ng2g7kzxqf.png" alt="文件读取架构" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;假设要在 C 程序中读取一个文件，流程是 &lt;code&gt;打开文件 -&amp;gt; 读取数据 -&amp;gt; 关闭文件&lt;/code&gt; ：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"input.txt"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;FILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// 打开文本文件（只读）&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fopen failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;                    &lt;span class="c1"&gt;// 用于存储每一行数据&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 此处数据已经存放在 buffer 中，可在需要时使用&lt;/span&gt;
        &lt;span class="c1"&gt;// 例如：处理字符串、解析内容等&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ferror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                     &lt;span class="c1"&gt;// 检查读取过程中是否出错&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"read error"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                           &lt;span class="c1"&gt;// 关闭文件&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;libc 是 C standard library ，即 C 标准库。截至目前，它包含了 30 个头文件，其中 &lt;code&gt;&amp;lt;stdio.h&amp;gt;&lt;/code&gt; 包含核心的输入输出函数，&lt;code&gt;&amp;lt;stdlib.h&amp;gt;&lt;/code&gt; 包含数值转换、内存分配、过程控制等函数。常用的还有数学计算函数 &lt;code&gt;&amp;lt;math.h&amp;gt;&lt;/code&gt; ，断言 &lt;code&gt;&amp;lt;assert.h&amp;gt;&lt;/code&gt; 等。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  wasm 如何读写文件
&lt;/h2&gt;

&lt;p&gt;在 JavaScript 中，一般使用 &lt;code&gt;File&lt;/code&gt; 对象存储文件，&lt;code&gt;File&lt;/code&gt; 继承自 &lt;code&gt;Blob&lt;/code&gt; ，本质上大块的二进制数据。如果我们自己设计算法，一般会将文件写入 Memory 中，在调用函数的时候把 &lt;code&gt;ArrayBuffer&lt;/code&gt; 作为指针传递。成熟的库文件读写基于文件系统开发，使用文件路径寻找文件，使用文件句柄传递文件，很难改成指针。&lt;/p&gt;

&lt;p&gt;为此 emscripten 开发了一套接口用于兼容标准文件读写。由于是受 POSIX 启发，所以这套接口和 Linux 的读写接口非常相似。对于应用程序来说，文件系统是透明的，它只知道通过 libc 接口就可以读写文件，不知道数据在硬件设备上具体的存储机制，emscripten 在编译时使出一技偷梁换柱，把 libc 接口替换成 syscalls ，把原本操作系统的 VFS 调用替换成 emscripten VFS 调用，实现 wasm 文件读写。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhof30ykao069mno071v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhof30ykao069mno071v.png" alt="转换流程" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  emscripten 文件系统
&lt;/h2&gt;

&lt;p&gt;文件读写接口有了，文件给如何存储呢？ emscripten 提供了一套灵活的虚拟文件系统架构：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx5km56td0iy16mu3uua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx5km56td0iy16mu3uua.png" alt="Emscripten file system architecture" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  MEMFS
&lt;/h3&gt;

&lt;p&gt;内存文件系统是 emscripten 默认的文件系统，初始化时自动挂载在根目录 &lt;code&gt;/&lt;/code&gt; ，数据保存在内存中，页面刷新会丢失数据。&lt;/p&gt;

&lt;h3&gt;
  
  
  NODEFS / NODERAWFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;这两种文件系统只能在 Node.js 环境中使用&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NODEFS 文件系统将宿主的文件系统代理到 emscripten 虚拟文件系统中，使用 Node.js 同步文件 api ，可以间接读写本地磁盘的数据。&lt;/p&gt;

&lt;p&gt;NODERAWFS 文件系统不需要通过 emscripten 代理，直接调用 Node.js 文件模块。最显著的区别是，NODEFS 需要执行 &lt;code&gt;FS.mount()&lt;/code&gt; 挂载虚拟文件系统，通过虚拟路径读写文件；NODERAWFS 不需要挂载，直接使用绝对物理路径读写。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipjck3yn7cys1626muhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipjck3yn7cys1626muhb.png" alt=" " width="800" height="1188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;NODERAWFS 比 NODEFS 快，NODEFS 有文件缓存可以减少系统调用。当需要从磁盘读写大文件时，选 NODERAWFS ；当处理零碎小文件时，选 NODEFS 。&lt;/p&gt;

&lt;h3&gt;
  
  
  IDBFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;IDBFS 只能在浏览器中使用，包括 WebWorker&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;IDBFS 将数据存储在 &lt;code&gt;IndexedDB&lt;/code&gt; 实例中。&lt;strong&gt;IndexedDB 提供异步接口，POSIX 标准是同步接口&lt;/strong&gt;，两者无法兼容。使用 IDBFS 时，emscripten 先将数据存储在 MEMFS 中，并记录文件是否有修改，最后需要用户调用 &lt;code&gt;FS.syncfs()&lt;/code&gt; 函数一次性把变更写入 IndexedDB 中。如果用户忘记执行 &lt;code&gt;FS.syncfs()&lt;/code&gt; 便关闭页面或刷新页面，MEMFS 记录的文件将会丢失，可以通过监听 &lt;code&gt;pagehide&lt;/code&gt; 或 &lt;code&gt;beforeunload&lt;/code&gt; 事件强制刷盘。&lt;/p&gt;

&lt;p&gt;在挂载 IDBFS 的时候可以设置 &lt;code&gt;autoPersist: true&lt;/code&gt; 参数，这样每次有文件发生变化的时候都会保存。如果改动文件比较频繁，可能会造成性能浪费。&lt;/p&gt;

&lt;h3&gt;
  
  
  WORKERFS
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;WORKERFS 仅能在 Worker 中使用&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;该文件系统提供对 Worker 内部的 &lt;code&gt;File&lt;/code&gt; 和 &lt;code&gt;Blob&lt;/code&gt; 对象的只读访问，而无需将整个文件数据复制到内存中，可用于处理大文件。&lt;/p&gt;

&lt;h3&gt;
  
  
  PROXYFS
&lt;/h3&gt;

&lt;p&gt;PROXYFS 用于多个 wasm 模块之间文件共享。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Module 2 can use the path "/fs1" to access and modify Module 1's filesystem&lt;/span&gt;
&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fs1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;module2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROXYFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;module1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fs1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  虚拟文件系统解析
&lt;/h2&gt;

&lt;p&gt;emscripten 文件系统的核心数据是 FSNode ，模拟了 Linux 文件系统中的 inode 数据结构。基本数据结构为：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="err"&gt;{
  &lt;/span&gt;&lt;span class="nc"&gt;node_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;   &lt;span class="c1"&gt;// 节点操作（如 lookup , create 等）&lt;/span&gt;
  &lt;span class="nx"&gt;stream_ops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// 流操作（如 read , write , seek 等）&lt;/span&gt;
  &lt;span class="nx"&gt;mounted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 节点的挂载信息&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rde&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// root node sets parent to itself&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 父节点（目录节点）&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 节点名称（文件名或目录名）&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 文件类型和权限&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rdev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rdev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 设备文件的主/次设备号（非设备文件为 0）&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextInode&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 全局唯一的 node 编号&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// 文件内容（ ArrayBuffer ）或目录项列表&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// 文件大小（字节数）&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 指向挂载到此节点的文件系统&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;atime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// atime（访问时间）、 mtime（修改时间）和 ctime（状态改变时间）&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;初始化文件系统时，执行 &lt;code&gt;FS.mount(MEMFS, {}, '/')&lt;/code&gt; ，将内存文件系统挂载到根目录下，其他文件系统可以按需挂载到内存文件系统中，如&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;WORKERFS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="c1"&gt;// Array of File objects or FileList&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/worker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 挂载 WORKERFS 到 /worker 目录&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其他文件操作，如 &lt;code&gt;mkdir&lt;/code&gt; &lt;code&gt;rmdir&lt;/code&gt; &lt;code&gt;chmod&lt;/code&gt; &lt;code&gt;link&lt;/code&gt; 等函数均在 &lt;code&gt;FS&lt;/code&gt; 对象中实现，直接调用即可。文件系统具有继承性，除非是挂载点，子节点的文件系统类型继承自父节点:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mkdir() -&amp;gt; mknod() -&amp;gt; lookupPath() -&amp;gt; new FSNode()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;应用程序调用 &lt;code&gt;open&lt;/code&gt; &lt;code&gt;read&lt;/code&gt; &lt;code&gt;write&lt;/code&gt; &lt;code&gt;close&lt;/code&gt; 最终会被指向 &lt;code&gt;FS.open&lt;/code&gt; &lt;code&gt;FS.read&lt;/code&gt; &lt;code&gt;FS.write&lt;/code&gt; &lt;code&gt;FS.close&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mode&lt;/code&gt; 记录文件类型和权限，使用 POSIX 规范，使用 32 位证书表示，前 8 位表示文件类型，后 24 位表示权限。&lt;/p&gt;

&lt;h2&gt;
  
  
  硬件设备
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;万物皆文件&lt;/strong&gt;，和其他类 Unix 操作系统一样，emscripten 虚拟文件系统可以注册硬件设备。举一个简单的例子，假设我们想在浏览器中模拟串行通信设备：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 生成设备号&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 注册设备操作&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;ioctl&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO 模拟获取波特率&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 创建设备节点&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dev/ttyUSB0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接下来便可以在 C 中对 &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; 串行口进行读写了。&lt;/p&gt;

&lt;h2&gt;
  
  
  发展
&lt;/h2&gt;

&lt;p&gt;目前 emscripten 虚拟文件系统均基于 JavaScript 开发，有一个显著的缺点就是无法支持多线程。emscripten 正在开发新的文件系统 WASMFS ，目前还未完成，未来 WASMFS 会支持多线程，性能会有比较大的提高。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>emscripten</category>
      <category>javascript</category>
    </item>
    <item>
      <title>WebAssembly 基础（一）</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Thu, 21 Aug 2025 03:06:01 +0000</pubDate>
      <link>https://dev.to/yangholmes/webassembly-ji-chu--32kb</link>
      <guid>https://dev.to/yangholmes/webassembly-ji-chu--32kb</guid>
      <description>&lt;h2&gt;
  
  
  高级计算机语言的两种运行方式
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;预先编译 Ahead-of-Time Compilation ，简称 AOT&lt;/li&gt;
&lt;li&gt;即时编译 Just-in-Time Compilation ，简称 JIT&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;像 C/C++ 这类属于预先编译的计算机语言，而 JavaScript 这种属于即时编译语言。&lt;/p&gt;

&lt;h2&gt;
  
  
  现代编译器结构
&lt;/h2&gt;

&lt;p&gt;编译器的最终目的是将高级计算机语言编译成机器语言，由于用户使用的 CPU 五花八门，相同的高级语言代码编译出来的可执行文件都有可能是不同的。为了提高效率，编译器会按照编译流程模块化设计。一般由 &lt;strong&gt;前端(Front End)&lt;/strong&gt; 、&lt;strong&gt;中端(Middle End)&lt;/strong&gt; 和 &lt;strong&gt;后端(Back End)&lt;/strong&gt; 组成，每个节点都会产生中间表示(IR)传递给下一级。前端和中端的处理与硬件无关，最终在后端生成符合硬件参数的汇编代码。这种设计的好处在于，同一个编译器在不同的平台只需要开发不同的后端即可：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlaxeetcoumt3u9q4ozj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlaxeetcoumt3u9q4ozj.png" alt="编译器结构" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;举个例子，C 的编译过程：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbu6gh39qw1d2c4pn6jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjbu6gh39qw1d2c4pn6jr.png" alt="C 语言编译" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  WebAssembly 的编译流转过程
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngg9d01lyehgosmy6qhw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fngg9d01lyehgosmy6qhw.png" alt="WebAssembly 的编译流转过程" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;WASM 是编译器的目标代码，但从浏览器的角度来看，WASM 更像是中端产出的 IR 。最终要被 AOT/JIT 编译器编译成平台相关的机器码。&lt;/p&gt;

&lt;h2&gt;
  
  
  格式
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;二进制格式，文件后缀是 .wasm&lt;/li&gt;
&lt;li&gt;文本格式，文件后缀是 .wat&lt;/li&gt;
&lt;li&gt;内存格式&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;二进制格式是 wasm 模块的主要格式，文本格式是为了方便开发者调试和理解 wasm 。但其实 wat 的阅读性也很差，如同读汇编。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr1oiwhrpxablv76ef9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr1oiwhrpxablv76ef9a.png" alt="3种格式" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;wasm 模块必须安全可靠，所以在进入实例化之前，浏览器会先解码 wasm 为内存格式(in-memory) ，使用内存格式进行验证。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpwujfp0tiudyyhmhvqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxpwujfp0tiudyyhmhvqe.png" alt="语义阶段" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  wasm 结构
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxdarln3ak447aivnv2k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxdarln3ak447aivnv2k.png" alt="wasm 结构" width="800" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;流式(Streamable)加载 + 严格的段顺序，保证 wasm 可以一遍(One-Pass)完成代码的加载、解析、验证和编译。&lt;/p&gt;

</description>
      <category>webassembly</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Use GDAL in Web Applications (Part 3)</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Wed, 20 Aug 2025 07:10:57 +0000</pubDate>
      <link>https://dev.to/yangholmes/how-to-use-gdal-in-web-applications-part-3-1bc</link>
      <guid>https://dev.to/yangholmes/how-to-use-gdal-in-web-applications-part-3-1bc</guid>
      <description>&lt;p&gt;This article focuses on optimization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Necessity of Optimization
&lt;/h2&gt;

&lt;p&gt;The previous article introduced a complete &lt;a href="https://github.com/bugra9/gdal3.js/blob/master/Makefile" rel="noopener noreferrer"&gt;compilation script&lt;/a&gt; that successfully builds the WebAssembly version of GDAL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pq5z0b1xx6lz5ahztul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pq5z0b1xx6lz5ahztul.png" alt="GDAL Build Artifacts" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, the compilation results are &lt;strong&gt;not suitable for production environments&lt;/strong&gt; because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Excessive file sizes&lt;/strong&gt;: Core wasm file (27MB), glue code (272KB), data file (11MB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redundant glue code&lt;/strong&gt;: Contains Node.js and bash environment code, impossible to tree-shake&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug info in production&lt;/strong&gt;: Debug information is unnecessary in production environments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;File size is the most critical issue—total artifacts exceed 38MB, which is unacceptable for any web application.&lt;/p&gt;

&lt;p&gt;Additionally, the Makefile contains misconfigurations. Since emsdk silently ignores unsupported compilation options during build, these errors don't halt compilation. This article will also interpret the original author's intent and fix incorrect compilation parameters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Through compilation optimization, GDAL 3.x WebAssembly artifacts can be reduced but likely not enough. These techniques work well for GDAL 2.x and OpenCV 4.x. Deeper reasons relate to GDAL's source code and build mechanisms—beyond this series' scope.&lt;br&gt;&lt;br&gt;
&lt;em&gt;TODO: Add OpenCV optimization comparison&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Optimization Approaches
&lt;/h2&gt;

&lt;p&gt;For web applications, smaller resources are better. Classical frontend workflows use modern build tools and modular design to shrink JavaScript via lazy loading and tree-shaking. Non-JS resources are transformed by "loaders" into JS modules for optimization. However, these methods don't work for WebAssembly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Loader limitations&lt;/strong&gt;: Wasm files can be compressed but can't be used client-side without extra code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No tree-shaking&lt;/strong&gt;: Wasm is binary code; dead code elimination can't be done like with JS ASTs.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Could a &lt;code&gt;*.wasm&lt;/code&gt; loader exist? Tools like &lt;a href="https://vite.dev/guide/features.html#webassembly" rel="noopener noreferrer"&gt;vite&lt;/a&gt; support loading Wasm via &lt;code&gt;?init&lt;/code&gt;, but this doesn't suit glue code integration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thus, we optimize during the wasm compilation phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Separation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;WASM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;: Output combined &lt;code&gt;wasm.js&lt;/code&gt; (wasm embedded in JS)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt;: Separate wasm and JS output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt;: Output both formats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;wasm.js&lt;/code&gt; serves legacy browsers. &lt;code&gt;-sWASM=2&lt;/code&gt; outputs both, but if target browsers support wasm, &lt;code&gt;wasm.js&lt;/code&gt; is unnecessary. &lt;code&gt;wasm.js&lt;/code&gt; encodes wasm as base64, increasing file size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demand-Driven Compilation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"Compile only what you use"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  1. Library Functions
&lt;/h4&gt;

&lt;p&gt;Projects typically use only a small subset of a library. Dead code elimination is controlled by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORTED_FUNCTIONS&lt;/span&gt; &lt;span class="c"&gt;# List of exported functions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORT_ALL&lt;/span&gt; &lt;span class="c"&gt;# Export all functions
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Exported functions require a &lt;code&gt;_&lt;/code&gt; prefix. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;-sEXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"['_add']"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Emscripten Runtime Functions
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORTED_RUNTIME_METHODS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default is empty. Export only necessary methods. For virtual filesystems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;-sEXPORTED_RUNTIME_METHODS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"['FS']"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original &lt;a href="https://github.com/bugra9/gdal3.js/blob/master/Makefile" rel="noopener noreferrer"&gt;gdal3.js&lt;/a&gt; exports nearly all GDAL functions, a key reason for large artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debug Information
&lt;/h3&gt;

&lt;p&gt;emcc parameters resemble gcc's. Disable debug info in production using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;-gsource-map&lt;/span&gt;
&lt;span class="err"&gt;-source-map-base&lt;/span&gt;
&lt;span class="err"&gt;-O&amp;lt;level&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;-g&amp;lt;level&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. &lt;code&gt;-gsource-map&lt;/code&gt; and &lt;code&gt;-source-map-base&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Control sourcemap generation. If enabled, debuggers load &lt;code&gt;.map&lt;/code&gt; files from &lt;code&gt;&amp;lt;base-url&amp;gt;/&amp;lt;wasm-file-name&amp;gt;.map&lt;/code&gt;, with &lt;code&gt;&amp;lt;base-url&amp;gt;&lt;/code&gt; set by &lt;code&gt;-source-map-base&lt;/code&gt; (default: same as wasm path).&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;code&gt;-O&amp;lt;level&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Optimization levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-O0&lt;/code&gt;: No optimization, full debug info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O1&lt;/code&gt;: Basic optimizations, remove runtime asserts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O2&lt;/code&gt;: Dead code elimination (beyond &lt;code&gt;-O1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O3&lt;/code&gt;: Aggressive size reduction (beyond &lt;code&gt;-O2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Og&lt;/code&gt;: Similar to &lt;code&gt;-O1&lt;/code&gt;, more debug info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Os&lt;/code&gt;: Similar to &lt;code&gt;-O3&lt;/code&gt;, smaller output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Oz&lt;/code&gt;: Smaller than &lt;code&gt;-Os&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Default &lt;code&gt;-O0&lt;/code&gt; retains full debug info.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Higher optimization levels increase compilation time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  3. &lt;code&gt;-g&amp;lt;level&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Debug levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-g0&lt;/code&gt;: No debug info&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g1&lt;/code&gt;: Preserve whitespace in JS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g2&lt;/code&gt;: Preserve function names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g3&lt;/code&gt;: Full debug info (DWARF + LLVM metadata)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Omitting the number (e.g., &lt;code&gt;-g&lt;/code&gt;) defaults to &lt;code&gt;-g3&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Configuration
&lt;/h3&gt;

&lt;p&gt;By default, emscripten generates environment-detection code for multiple targets. For fixed environments, this is redundant. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;ENVIRONMENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node&lt;/code&gt;: Node.js&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web&lt;/code&gt;: Web browsers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webview&lt;/code&gt;: Same as &lt;code&gt;web&lt;/code&gt; (embedded webviews)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;worker&lt;/code&gt;: Web Worker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell&lt;/code&gt;: Command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For web apps, compile only &lt;code&gt;-sENVIRONMENT=worker&lt;/code&gt;. Also configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORT_ES6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set to &lt;code&gt;1&lt;/code&gt; to output ES Module-compliant glue code. Default output includes environment-sniffing CJS/IIFE, unusable with &lt;code&gt;import&lt;/code&gt;. Compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// -sEXPORT_ES6=1&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;moduleRtn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// -sEXPORT_ES6=0&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;moduleRtn&lt;/span&gt;&lt;span class="p"&gt;}})();&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;([],()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Filesystem
&lt;/h3&gt;

&lt;p&gt;Libraries like GDAL rely on OS filesystems. Emscripten emulates this in JS. Disable if unused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;FILESYSTEM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic if code references &lt;code&gt;stdio.h/fprintf&lt;/code&gt;. For pure computation, disable manually.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Access via &lt;code&gt;Module.FS&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Other Options
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Polyfill
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;POLYFILL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default &lt;code&gt;true&lt;/code&gt;. Disable if polyfills are handled elsewhere.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Use JS Math Library
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;JS_MATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;true&lt;/code&gt; to use browser's Math, avoiding libc compilation. May reduce precision. Recommended for precision-insensitive tasks.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Minimal Runtime
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;MINIMAL_RUNTIME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal output (no POSIX, no Module, no built-in XHR). May break functionality—not recommended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fixing gdal3.js Build Script Errors
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Invalid Debug Level
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/bugra9/gdal3.js/blob/v2.8.1/GDAL_EMCC_FLAGS.mk#L4" rel="noopener noreferrer"&gt;Line 4&lt;/a&gt; incorrectly uses &lt;code&gt;-g4&lt;/code&gt; (unsupported). &lt;/p&gt;

&lt;p&gt;Fix for &lt;code&gt;type=debug&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-O0&lt;/span&gt; &lt;span class="nt"&gt;-g3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Sourcemap Misconfiguration
&lt;/h4&gt;

&lt;p&gt;Same line: &lt;code&gt;--source-map-base&lt;/code&gt; without &lt;code&gt;-gsource-map&lt;/code&gt;. Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-gsource-map&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--source-map-base&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;BASE_URL&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optimizing gdal3.js Build Script
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Disable Debug in Production
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://github.com/bugra9/gdal3.js/blob/v2.8.1/GDAL_EMCC_FLAGS.mk#L6" rel="noopener noreferrer"&gt;Line 6&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-Oz&lt;/span&gt; &lt;span class="nt"&gt;-g0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Specify Environment
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;worker &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORT_ES6&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Reduce Exported Functions
&lt;/h4&gt;

&lt;p&gt;For the use case in Part 2 (only &lt;code&gt;GDALOpen&lt;/code&gt;, &lt;code&gt;GDALInfo&lt;/code&gt;, &lt;code&gt;GDALClose&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_malloc',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_free',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_CSLCount',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_GDALOpen',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_GDALClose',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'_GDALInfo'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal runtime methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORTED_RUNTIME_METHODS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'ccall',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'cwrap',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
'FS'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42xw0fgo2xmmjwkfn4ke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42xw0fgo2xmmjwkfn4ke.png" alt="Optimization Results" width="679" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wasm file&lt;/strong&gt;: Reduced by 6,177,075 bytes (22.44%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS file&lt;/strong&gt;: Reduced by 18,299 bytes (10.21%)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Future articles will cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Emscripten's virtual filesystem&lt;/li&gt;
&lt;li&gt;Purpose and optimization of &lt;code&gt;*.data&lt;/code&gt; files&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webassembly</category>
      <category>gdal</category>
      <category>javascript</category>
    </item>
    <item>
      <title>如何在 web 应用中使用 GDAL （三）</title>
      <dc:creator>Yangholmes</dc:creator>
      <pubDate>Mon, 18 Aug 2025 11:32:34 +0000</pubDate>
      <link>https://dev.to/yangholmes/ru-he-zai-web-ying-yong-zhong-shi-yong-gdal-san--42jd</link>
      <guid>https://dev.to/yangholmes/ru-he-zai-web-ying-yong-zhong-shi-yong-gdal-san--42jd</guid>
      <description>&lt;p&gt;这篇研究优化。&lt;/p&gt;

&lt;h2&gt;
  
  
  优化的必要性
&lt;/h2&gt;

&lt;p&gt;上上篇介绍了一个完整的&lt;a href="https://github.com/bugra9/gdal3.js/blob/master/Makefile" rel="noopener noreferrer"&gt;编译脚本&lt;/a&gt;，运行这个脚本可以顺利编译出 GDAL WebAssembly 版本的产物。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pq5z0b1xx6lz5ahztul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4pq5z0b1xx6lz5ahztul.png" alt="GDAL 编译产物" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;但是这个编译结果并不适合在生产环境中使用，原因有：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;文件太大，核心 wasm 文件 27MB ，胶水代码 272KB ，data 文件 11 MB&lt;/li&gt;
&lt;li&gt;胶水代码大量冗余，包含 Node 环境、bash 环境的代码，且无法进行 tree shaking&lt;/li&gt;
&lt;li&gt;生产环境无需输出调试信息&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其中文件过大是最致命的，构建产物全部加起来超过 38MB ，任何 web 应用都无法接受这么一个硕大文件依赖的模块。&lt;/p&gt;

&lt;p&gt;另外，这个 Makefile 包含许多错误配置，由于 emsdk 在编译时会抛弃不支持的编译配置，这些错误并没有中断编译。本篇还将尝试解读 gdal3.js 作者的意图，修正错误的编译参数。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;先叠甲&lt;/p&gt;

&lt;p&gt;通过编译过程优化，GDAL 3.x 版本的 WebAssembly 编译产物可以缩小，但很难做到足够小。本篇介绍的操作对于编译 GDAL 2.x 和 OpenCV 4.x 非常有效。个中原因，需要了解 GDAL 的源码和编译机制才能分晓，已超出本系列范围，先按下不表。&lt;/p&gt;

&lt;p&gt;TODO: 添加 OpenCV 的优化对比&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  如何优化
&lt;/h2&gt;

&lt;p&gt;对于 web 应用来说，资源越小越好。经典的 web 开发流程中，开发者使用现代化的前端构建工具和模块化设计，通过注入懒加载、Tree-Shaking 等方法把原本可能比较大的 JavaScript 文件缩小成比较小的文件，并且可以做到按需要加载；JavaScript 以外的文件，使用各种“加载器”转换成 JavaScript 代码的样子，也可以实现构建和优化。但这些手段在 WebAssembly 面前都不起作用了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;加载器的原理是将非 *.js 文件转换成 JavaScript 模块，例如将 *.png 文件转换为 base64 字符串或 url 字符串并导出为模块；转换的过程中可以缩小文件，依赖于这种文件的压缩算法，压缩后的文件必须保证在不增加代码的前提下能够在客户端中使用； *.wasm 文件可以压缩，但是目前还无法做到客户端中不增加代码就能使用&lt;/li&gt;
&lt;li&gt;JavaScript Tree-Shaking 是基于字符串代码抽象语法树去除死代码，*.wasm 是已经编译好的可执行文件，是二进制代码不是字符串，无法像 JavaScript 一样瘦身&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;能不能做一个 &lt;code&gt;*.wasm&lt;/code&gt; 文件的 loader 呢？有的，其实是有的，比如说 &lt;a href="https://vite.dev/guide/features.html#webassembly" rel="noopener noreferrer"&gt;vite&lt;/a&gt; 就可以使用 &lt;code&gt;?init&lt;/code&gt; 直接加载 WebAssembly 模块并初始化，省去写拉取初始化等代码的过程，但是这个写法并不适合结合胶水代码。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;既然如此，我们应该着眼于 wasm 编译阶段的优化。&lt;/p&gt;

&lt;h3&gt;
  
  
  代码分离
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;WASM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;有三个选项&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;0 - 生成 wasm.js ，wasm 代码和 js 代码合二为一&lt;/li&gt;
&lt;li&gt;1 - wasm 代码和 js 代码分开输出&lt;/li&gt;
&lt;li&gt;2 - 同时生成 wasm.js 和 wasm + js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;wasm.js 通常用于旧版本浏览器，&lt;code&gt;-sWASM=2&lt;/code&gt; 会同时输出 wasm.js 和 wasm + js ，浏览器加载独立的 wasm 失败后会自动加载 wasm.js 文件。wasm.js 将 wasm 编译成 base64 字符串保存在 js 文件中，通常会比 wasm + js 大一些。如果明确用户的浏览器版本支持 wasm ，没有必要使用 wasm.js 。&lt;/p&gt;

&lt;h3&gt;
  
  
  按需编译
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“用多少编多少”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  1. 库函数
&lt;/h4&gt;

&lt;p&gt;通常在项目中，我们只会用到算法库中占整个库很小比例的若干几个功能，没有必要编译无用的功能；编译 C/C++ 项目时，编译器通常会自动消除死代码，可以通过以下两个参数控制：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORTED_FUNCTIONS&lt;/span&gt;  &lt;span class="c"&gt;# 导出函数列表
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORT_ALL&lt;/span&gt;  &lt;span class="c"&gt;# 导出所有函数
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意，导出函数前面需要添加一个下划线 &lt;code&gt;_&lt;/code&gt; ，例如需要导出 &lt;code&gt;add&lt;/code&gt; 函数，可以这样编写&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;-sEXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"['_add']""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. emscripten 运行时函数
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORTED_RUNTIME_METHODS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;emscripten 运行时函数，默认值是空数组。我们应该按照实际需要添加导出函数，比如需要使用虚拟文件系统时，添加 &lt;code&gt;FS&lt;/code&gt; 到数组中&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;-sEXPORTED_RUNTIME_METHODS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"['FS']"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在 &lt;a href="https://github.com/bugra9/gdal3.js/blob/master/Makefile" rel="noopener noreferrer"&gt;gdal3.js&lt;/a&gt; 项目中，导出函数列表将几乎所有支持的 GDAL 功能都列进去了，这是编译产物巨大化的关键原因。&lt;/p&gt;

&lt;h3&gt;
  
  
  调试信息
&lt;/h3&gt;

&lt;p&gt;emcc 编译参数和 gcc 的大致相同，可以选择关闭生产环境中的调试信息来优化产物。和调试信息相关的配置有&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;-gsource-map&lt;/span&gt;
&lt;span class="err"&gt;-source-map-base&lt;/span&gt;
&lt;span class="err"&gt;-O&amp;lt;level&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;-g&amp;lt;level&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. &lt;code&gt;-gsource-map&lt;/code&gt; 和 &lt;code&gt;-source-map-base&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;-gsource-map&lt;/code&gt; 控制是否输出 sourcemap ，如果设置了输出 sourcemap ，调试器将会在 &lt;code&gt;&amp;lt;base-url&amp;gt;&lt;/code&gt; + &lt;code&gt;&amp;lt;wasm-file-name&amp;gt;&lt;/code&gt; + &lt;code&gt;.map&lt;/code&gt; 位置加载 .map 文件，&lt;code&gt;&amp;lt;base-url&amp;gt;&lt;/code&gt; 由参数 &lt;code&gt;-source-map-base&lt;/code&gt; 设置，默认为空，也就是和 wasm 文件同一个路径。&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;code&gt;-O&amp;lt;level&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;设置优化等级。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-O0&lt;/code&gt; - 完全不优化，保留所有调试信息&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O1&lt;/code&gt; - 基础优化，消除运行时断言&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O2&lt;/code&gt; - &lt;code&gt;-O1&lt;/code&gt; 基础上进一步优化，消除死代码&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-O3&lt;/code&gt; - &lt;code&gt;-O2&lt;/code&gt; 基础上再优化，减小输出文件&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Og&lt;/code&gt; - 和 &lt;code&gt;-O1&lt;/code&gt; 差不多，比 &lt;code&gt;-O1&lt;/code&gt; 保留更多调试信息&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Os&lt;/code&gt; - 和 &lt;code&gt;-O3&lt;/code&gt; 差不多，比 &lt;code&gt;-O3&lt;/code&gt; 输出文件更小&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-Oz&lt;/code&gt; - 和 &lt;code&gt;-Os&lt;/code&gt; 差不多，比 &lt;code&gt;-Os&lt;/code&gt; 输出文件更小&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;默认值是 &lt;code&gt;-O0&lt;/code&gt; ，保留所有调试信息。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，优化级别越高，编译的时间就会越长。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  3. &lt;code&gt;-g&amp;lt;level&amp;gt;&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;调试等级，一共有四个等级&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-g0&lt;/code&gt; - 不输出任何调试信息&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g1&lt;/code&gt; - 链接时，保留 JavaScript 中的空格&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g2&lt;/code&gt; - 链接时，保留编译代码中的函数名称&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g3&lt;/code&gt; - 编译为目标文件时，保留调试信息，包括 JS 空格、函数名称和 LLVM 调试信息（DWARF）（如果有的话）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果设置为 &lt;code&gt;-g&lt;/code&gt; 不带有任何数字，相当于 &lt;code&gt;-g3&lt;/code&gt; 。&lt;/p&gt;

&lt;h3&gt;
  
  
  运行环境设置
&lt;/h3&gt;

&lt;p&gt;默认情况下，emscripten 认为胶水代码会在不同的环境中执行，自动生成各种环境的嗅探代码和初始化代码。实际上，对于一个确定的应用而言，运行环境是固定的，没有必要产出环境嗅探。运行环境的参数是&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;ENVIRONMENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;emscripten 支持的值有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;node - Node.js&lt;/li&gt;
&lt;li&gt;web - 网页&lt;/li&gt;
&lt;li&gt;webview - 也是网页，特指插入 native 应用的网页，等同于 web&lt;/li&gt;
&lt;li&gt;worker - worker 环境&lt;/li&gt;
&lt;li&gt;shell - 命令行中&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果是 web 应用，只需要编译 &lt;code&gt;-sENVIRONMENT=worker&lt;/code&gt; 环境即可；同理，在 Node.js 环境中只需要编译 &lt;code&gt;-sENVIRONMENT=node&lt;/code&gt; 。&lt;/p&gt;

&lt;p&gt;还有一个与运行环境相关的配置项是&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;EXPORT_ES6&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;将这个配置设置为 &lt;code&gt;1&lt;/code&gt; ，便可以把胶水代码输出为符合 esmodule 规范的模块。默认情况下，胶水代码输出包含环境嗅探的 CommonJS 模块和 IIFE ，在现代前端项目中无法使用 &lt;code&gt;import&lt;/code&gt; 导入。对比一下两种输出的区别：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// -sEXPORT_ES6=1&lt;/span&gt;

&lt;span class="c1"&gt;// ... 最后一行&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;moduleRtn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// -sEXPORT_ES6=0&lt;/span&gt;

&lt;span class="c1"&gt;// ... 最后一行&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;moduleRtn&lt;/span&gt;&lt;span class="p"&gt;}})();&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;([],()&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;CModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  文件系统
&lt;/h3&gt;

&lt;p&gt;某些算法库，如 GDAL ，需要依赖操作系统的文件系统读写文件和输出输出，emscripten 实现了一套基于 JavaScript 的内存文件系统。如果项目中没有使用文件系统，可以不使用，配置文件系统的参数是&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;FILESYSTEM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;编译文件系统，如果代码引用了 stdio.h 和 fprintf ，会自动开启，如果代码是纯计算，可以手动关闭。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;emscripten 的文件系统随 Module 导出，可以通过 &lt;code&gt;Module.FS&lt;/code&gt; 访问&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  其他
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. polyfill
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;POLYFILL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;是否添加 polyfill 支持旧版浏览器，默认值是 &lt;code&gt;true&lt;/code&gt; 。一般需要支持旧版浏览器的项目，都会在统一的地方添加 polyfill ，无需 emscripten 再添加，建议关闭 polyfill 。&lt;/p&gt;

&lt;h4&gt;
  
  
  2. 使用 BOM 的 Math 库
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;JS_MATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以配置这个参数为 &lt;code&gt;true&lt;/code&gt; 使用 JavaScript Math 库，这样就可以避免编译 libc 。如果使用 JavaScript Math 库，计算的结果可能和 libc math 库精度不一致。建议在对精度不是很敏感的项目中可以开启。&lt;/p&gt;

&lt;h4&gt;
  
  
  3. 最小化输出
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="err"&gt;MINIMAL_RUNTIME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最小化输出，不带 POSIX 功能，不带 Module ，也不带 emscripten 内置的 XHR 等模块。尽管可以获得非常小的产物，但是可能代码无法运行，不建议使用。&lt;/p&gt;

&lt;h2&gt;
  
  
  实战
&lt;/h2&gt;

&lt;h3&gt;
  
  
  gdal3.js 编译脚本纠错
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. 调试等级错误
&lt;/h4&gt;

&lt;p&gt;FLAGS 文件&lt;a href="https://github.com/bugra9/gdal3.js/blob/v2.8.1/GDAL_EMCC_FLAGS.mk#L4" rel="noopener noreferrer"&gt;第 4 行&lt;/a&gt; 定义调试等级，emcc 支持参数 0~3 ，并不支持 &lt;code&gt;-g4&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这里明显可以看出来，当 &lt;code&gt;type&lt;/code&gt; 参数为 &lt;code&gt;debug&lt;/code&gt; 时，编译输出完整的调试信息。所以这里应该使用&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-O0&lt;/span&gt; &lt;span class="nt"&gt;-g3&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. sourcemap 设置错误
&lt;/h4&gt;

&lt;p&gt;还是在同一行，配置了 &lt;code&gt;--source-map-base&lt;/code&gt; ，但并未开启 &lt;code&gt;-gsource-map&lt;/code&gt; 。这里同样是当 &lt;code&gt;type&lt;/code&gt; 参数为 &lt;code&gt;debug&lt;/code&gt; 时，需要编译完整的 sourcemap 方便跟踪调试， &lt;code&gt;--source-map-base&lt;/code&gt; 建议从参数中读取，所以这里应该使用&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-gsource-map&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--source-map-base&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;BASE_URL&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  gdal3.js 编译脚本优化
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. 关闭所有调试信息
&lt;/h4&gt;

&lt;p&gt;FLAGS 文件&lt;a href="https://github.com/bugra9/gdal3.js/blob/v2.8.1/GDAL_EMCC_FLAGS.mk#L6" rel="noopener noreferrer"&gt;第 6 行&lt;/a&gt; 增加关闭调试的配置&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-Oz&lt;/span&gt; &lt;span class="nt"&gt;-g0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. 指定运行环境
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;worker &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORT_ES6&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. 减少导出函数
&lt;/h4&gt;

&lt;p&gt;例如上一篇，我们在代码中值用到了 &lt;code&gt;GDALOpen&lt;/code&gt; 、 &lt;code&gt;GDALInfo&lt;/code&gt; 和 &lt;code&gt;GDALClose&lt;/code&gt; ，那么我们就只导出这三个函数：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORTED_FUNCTIONS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_malloc',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_free',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_CSLCount',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_GDALOpen',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_GDALClose',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  '_GDALInfo'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;emscripten 提供的运行时函数也不需要太多，只导出用得上的：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;GDAL_EMCC_FLAGS&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;EXPORTED_RUNTIME_METHODS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  'ccall',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  'cwrap',&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  'FS'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  结果
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42xw0fgo2xmmjwkfn4ke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F42xw0fgo2xmmjwkfn4ke.png" alt="优化结果" width="679" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;wasm 文件减小了 6177075Bytes ，瘦身了 22.44% ； js 文件减小了 18299Bytes ，瘦身 10.21% 。&lt;/p&gt;

&lt;h2&gt;
  
  
  结语
&lt;/h2&gt;

&lt;p&gt;后面的篇章我们将会讨论：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;emscripten 的虚拟文件系统&lt;/li&gt;
&lt;li&gt;*.data 是什么，有什么用，如何优化 *.data&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webassembly</category>
      <category>gdal</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
