WebAssembly 小 Demo

意义

WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 — 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C/C++ 等语言提供一个编译目标,以便它们可以在Web上运行

WebAssembly 被设计为可以和 JavaScript 一起协同工作 — 通过使用 WebAssembly 的 JavaScript API,你可以把 WebAssembly 模块加载到一个 JavaScript 应用中并且在两者之间共享功能。这允许你在同一个应用中利用 WebAssembly 的性能和威力以及 JavaScript 的表达力和灵活性。

  • 高效: WebAssembly 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件能力以达到原生执行效率。
  • 安全: WebAssembly 运行在一个沙箱化的执行环境中,甚至可以在现有的 JavaScript 虚拟机中实现。在web环境中,WebAssembly 将会严格遵守同源策略以及浏览器安全策略。
  • 开放: WebAssembly 设计了一个非常规整的文本格式用来、调试、测试、实验、优化、学习、教学或者编写程序。可以以这种文本格式在web页面上查看wasm模块的源码
  • 标准: WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下。

关键概念

WebAssembly 如何在浏览器中运行,需要了解几个关键概念,这些概念都是一一映射到了WebAssembly的JavaScript API中。

  • 模块:表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。一个模块是无状态的,并且像一个二进制大对象(Blob)一样能够被缓存到IndexedDB 中或者在 windows 和 workers 之间(通过 postMessage() 函数)进行共享。一个模块能够像一个 ES2015 的模块一样声明导入和导出。
  • 内存(Memory):ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作(C/C++ 所必需)。
  • 表格(Table):带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用(为了安全和可移植性的原因)。当前 WebAssembly 版本中,只有函数是唯一合法的元素类型。
  • 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。一个实例就像一个已经被加载到一个拥有一组特定导入的特定的全局变量的ES2015模块。

Memory与Table区别:

WebAssembly Table 是一个可变大小的带类型(唯一合法的是函数类型)的引用数组,其中的引用可以被 JavaScript 和 WebAssembly 代码存取。然而,Memory 提供的是一个可变大小的带类型的原始字节数组。所以,把引用存储在 Memory 中是不安全。
在C/C++的原生实现中,函数指针是通过函数代码在进程的虚地址空间的原始地址表示的,并且由于前面提到的安全原因,它是不能被直接存储在线性内存(Memory)中的。取而代之的是,函数引用被存储在 Table 之中。它们的整数索引可以存储在线性内存(Memory)中并进行传递。

如何在应用中使用WebAssembly

  • 代码的二进制格式(工具可生成)
  • 加载运行该二进制代码的API

从C/C++移植为例

C/C++ 和 Javascript 区别:

  • C/C++ 是静态类型语言,而 Javascript 是动态类型语言
  • C/C++ 是手动内存管理,而 Javascript 依靠垃圾回收机制

在线 WASM 汇编程序: WasmFiddleWasmFiddle++WasmExplorer

Emscripten工具能够将一段C/C++代码,编译出:

Emscripten 是一个 LLVM(底层虚拟机) 生成 JavaScript 的编译器. 它采用 LLVM的字节码 (例如,使用 Clang 从 C/C++ 或者从其他语言生成的字节码) 并将其编译成可在 Web 上面运行的 JavaScript

在这里插入图片描述

  • 一个 .wasm 模块
  • 用来加载和运行该模块的 JavaScript ”胶水“代码
  • 一个用来展示代码运行结果的 HTML 文档

Emscripten生成的代码(其默认输出格式为 asm.js ,这是 JavaScript 的高度优化子集「变量一律都是静态类型,并且取消垃圾回收机制」)在许多情况下可以以接近原生的速度执行。

Asm.js 是一个规范,它定义了高度可优化的 JavaScript 严格子集。仅允许诸如 while、if、数字、顶级命名函数和其他简单构造之类的东西。它不允许对象、字符串、闭包以及基本上所有需要堆分配的内容。Asm.js 代码在许多方面都类似于C,但是它仍然是完全有效的 JavaScript,可以在所有当前引擎中运行。

在这里插入图片描述

  1. Emscripten 首先把C/C++提供给clang+LLVM——一个成熟的开源C/C++编译器工具链。
  2. Emscripten 将clang+LLVM编译的结果转换为一个.wasm二进制文件。
  3. 就自身而言,WebAssembly 当前不能直接的存取 DOM;它只能调用 JavaScript,并且只能传入整形和浮点型的原始数据类型作为参数。这就是说,为了使用任何 Web API,WebAssembly 需要调用到JavaScript,然后由JavaScript调用 Web API。因此,Emscripten 创建了 HTML 和 JavaScript 胶水代码以便完成这些功能。
    在这里插入图片描述
    在这里插入图片描述

一旦 JavaScript 引擎发现运行的是 asm.js,就知道这是经过优化的代码,可以跳过语法分析这一步,直接转成汇编语言。另外,浏览器还会调用 WebGL 通过 GPU 执行 asm.js。这些就是 asm.js 运行较快的原因。

加载和运行 WebAssembly 代码

当前还没有内置的方式让浏览器为你获取模块。当前唯一的方式就是创建一个包含你的 WebAssembly 模块二进制代码的 ArrayBuffer 并且使用 WebAssembly.instantiate() 编译它。

#include <stdio.h>

float add (float a, float b) {
  return a + b;
}

在这里插入图片描述

通过 WasmExplorer 工具(也可以使用上述的 Emscripten)编译后:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "add" (func $add))
 (func $add (; 0 ;) (param $0 f32) (param $1 f32) (result f32)
  (f32.add
   (get_local $0)
   (get_local $1)
  )
 )
)

示例:

<label for="num1"><input id="num1" type="text"></label>
<label for="num2"><input id="num2" type="text"></label>
<input id="compute" type="button" @click="add" value="计算"></input>
<label id="result"></label>
  
<script>
  const $ = (arg) => document.querySelector(arg)

  // 创建内存示例(initial:初始大小;maximum:最大大小)
  const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
  const importObj = {
    env: {
      abortStackOverflow: () => { throw new Error('overflow'); },
      table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc' }),
      tableBase: 0,
      memory: memory,
      memoryBase: 1024,
      STACKTOP: 0,
      STACK_MAX: memory.buffer.byteLength,
    }
  };

  fetch('./add.wasm').then(response =>
    // 1. 返回一个可以解析为带类型数组的promise                       
  	response.arrayBuffer()
  ).then(bytes =>
    // 2. 编译和实例化带类型数组     
 		WebAssembly.instantiate(bytes, importObj)
  ).then(wa => {
    $('#compute').addEventListener('click', () => {
      // 3. 使用通过 WebAssembly.Instance.exports 属性导出的特性
      let result = wa.instance.exports.add(+$('#num1').value, +$('#num2').value)
      $('#result').innerHTML = result
    })
  });
</script>

参考链接

  • https://developer.mozilla.org/zh-CN/docs/WebAssembly
  • https://developer.mozilla.org/en-US/docs/Games/Tools/asm.js
  • https://developer.mozilla.org/zh-CN/docs/WebAssembly/C_to_wasm
  • https://hacks.mozilla.org/2018/10/webassemblys-post-mvp-future/
  • http://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html
奋飛 CSDN认证博客专家 技术管理 前端工程化
乐观、勇气、专注、果断、好奇、公正、慎思、真诚、追求极致追求完美、诚信!独立撰写了多个前端专题模块,访问量达百万级。多次负责组织大数据可视化前端架构平台开发工作。对前端新技术、新潮流具有很强的敏锐力和洞察力!
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页