• wasm 系列之 WebAssembly 和 emscripten 暴力上手


    wasm 是什么?

    wasm 是 WebAssembly 的缩写。wasm 不是传统意义上的汇编语言,而是一种编译的中间字节码,可以在浏览器和其他 wasm runtime 上运行非 JavaScript 类型的语言,只要能被编译成 wasm,譬如 kotlin/wasm、Rust/wasm 等。

    这是一种高度优化的可执行格式,其运行速度几乎与本机代码一样快,同时具有可移植性和安全性。 Emscripten 通过与 LLVM、 Binaryen、 Closure Compiler 和其他工具的集成,自动为您完成大量优化工作。

    耳熟详闻的一个典型成功案例就是 Flutter 编译 Web 端应用,本质就是调用 Skia C++ 库,然后通过 wasm 技术提供了一个 CanvasKit 供 js 端使用。

    emscripten 是什么?

    Emscripten 是一个完整的 WebAssembly 开源编译器工具链。使用 Emscripten 可以将 C/C++ 代码或使用 LLVM 的任何其他语言编译为 WebAssembly,并在 Web、Node.js 或其他 Wasm 运行时上运行。

    实际上,任何可移植的 C/C++ 代码库都可以使用 Emscripten 编译成 WebAssembly,从需要渲染图形、播放声音以及加载和处理文件的高性能游戏,到 Qt 等应用程序框架。 Emscripten 已被用于将一长串现实世界代码库转换为 WebAssembly,其生成小而快速的代码!

    emscripten 环境准备

    首先我们需要用到 Emscripten。Emscripten 是一个编译器工具链,使用 LLVM 去编译出 wasm。

    先通过官网方式安装 Emscripten SDK,不同平台详情参见 https://emscripten.org/docs/getting_started/downloads.html

    # Get the emsdk repo
    git clone https://github.com/emscripten-core/emsdk.git
    
    # Enter that directory
    cd emsdk
    
    # Fetch the latest version of the emsdk (not needed the first time you clone)
    git pull
    
    # Download and install the latest SDK tools.
    ./emsdk install latest
    
    # Make the "latest" SDK "active" for the current user. (writes .emscripten file)
    ./emsdk activate latest
    
    # Activate PATH and other environment variables in the current terminal
    source ./emsdk_env.sh
    
    # On Windows, run emsdk.bat instead of ./emsdk, and emsdk_env.bat instead of source ./emsdk_env.sh.
    # On Windows, if you use the activate command, the step of emsdk_env.bat is optional. If you want to know more, see activate SDK version.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Windows 下执行完emsdk_env.bat后再执行emcc -v有可能提示找不到命令,这时候自己把打印的路径加入环境变量下就行。

    编写第一个 wasm 程序

    写 wasm 的最流行语言是 Rust 和 C/C++。
    C/C++ 的轮子比较丰富,比如 Skia(Canvas 底层调用的库)就是 C++ 写的。可惜的是 C/C++ 没有包管理工具。
    而当下最炙手可热的当属 Rust,我不得不说它真的很酷,有包管理工具,工具链也很完善。先选择使用 C/C++ 语言,下一篇再使用 Rust。

    在这里插入图片描述
    上图可以看到,执行完 emcc 对 c 源代码编译后生成了a.out.jsa.out.wasm两个产物文件。其中 js 文件是胶水代码,用来加载和执行 wasm,因为 wasm 不能直接作为入口文件使用,所以上图 node 命令实际执行的是胶水入口,然后胶水入口调用了 wasm 文件。

    我们可以通过 file 命令看下这三个文件类型,如图:

    在这里插入图片描述

    除过使用 nodejs 环境运行外,我们接下来尝试将上面 wasm 跑在浏览器中。

    先确保你本地有 nodejs 环境,并且通过npm -g install http-server安装了方便的 http-server,以便下面启动一个 http 服务器解决无法同源策略加载 wasm 文件的问题。

    新建一个 html 文件并引入 wasm 的胶水 js 代码,然后启动服务,如图:

    在这里插入图片描述

    接着在浏览器打开刚编写的网页可以在控制台看到我们前面在 c 语言中编写的 printf 代码输出,如下:

    在这里插入图片描述

    我们可以继续看下其网络情况,如下:

    在这里插入图片描述

    emscripten 用法

    上面我们快速使用 emscripten 的 emcc 命令,这里我们可以稍微再看下 emscripten 的其他用法,关于更多 emscripten 用法可以参见官方文档https://emscripten.org/docs/getting_started/Tutorial.html,这里不再赘述。

    Generating HTML

    我们可以将上面 emcc 编译命令换成emcc test.c -o test.html,然后可以一步到位生成网页,产物如下图:

    在这里插入图片描述

    接着使用浏览器直接打开 html,而不用我们自己再编写 html 引入 wasm 的胶水代码,如下:

    在这里插入图片描述

    可以看到我们 c 语言打印的 printf 输出已经出现在网页中了。上面网页其实有两部分,下部分是我们控制台输出的显示,上部分其实是一个 Canvas,我们通过下面例子就能在上面绘制彩色立方体的东西,如下:

    #include 
    #include 
    
    #ifdef __EMSCRIPTEN__
    #include 
    #endif
    
    int main(int argc, char** argv) {
      printf("hello, world!\n");
    
      SDL_Init(SDL_INIT_VIDEO);
      SDL_Surface *screen = SDL_SetVideoMode(256, 256, 32, SDL_SWSURFACE);
    
    #ifdef TEST_SDL_LOCK_OPTS
      EM_ASM("SDL.defaults.copyOnLock = false; SDL.defaults.discardOnLock = true; SDL.defaults.opaqueFrontBuffer = false;");
    #endif
    
      if (SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);
      for (int i = 0; i < 256; i++) {
        for (int j = 0; j < 256; j++) {
    #ifdef TEST_SDL_LOCK_OPTS
          // Alpha behaves like in the browser, so write proper opaque pixels.
          int alpha = 255;
    #else
          // To emulate native behavior with blitting to screen, alpha component is ignored. Test that it is so by outputting
          // data (and testing that it does get discarded)
          int alpha = (i+j) % 255;
    #endif
          *((Uint32*)screen->pixels + i * 256 + j) = SDL_MapRGBA(screen->format, i, j, 255-i, alpha);
        }
      }
      if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);
      SDL_Flip(screen);
    
      printf("you should see a smoothly-colored square - no sharp lines but the square borders!\n");
      printf("and here is some text that should be HTML-friendly: amp: |&| double-quote: |\"| quote: |'| less-than, greater-than, html-like tags: ||\nanother line.\n");
    
      SDL_Quit();
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    上面官方例子的代码位于https://github.com/emscripten-core/emscripten/blob/main/test/hello_world_sdl.cpp

    我们通过emcc hello_world_sdl.cpp -o hello.html编译然后浏览器打开产物网页,如下:

    在这里插入图片描述

    官方还有很多 Canvas 有趣例子,自己可以探索一下。

    Using files

    先放官方文档这句最重要的话:

    Your C/C++ code can access files using the normal libc stdio API (fopen, fclose, etc.)
    
    • 1

    JavaScript 通常在 web 浏览器的沙盒环境中运行,不直接访问本地文件系统。Emscripten模拟了一个虚拟的文件系统使得我们可以使用普通的 libc stdio API。我们要访问的文件应该预加载或嵌入到这个虚拟文件系统中。

    官方文件系统架构机制如下图:

    在这里插入图片描述

    我们来看一段名叫hello_world_file.cpp的 cpp 代码:

    #include 
    
    int main() {
      FILE *file = fopen("hello_world_file.txt", "rb");
      if (!file) {
        printf("cannot open file\n");
        return 1;
      }
      while (!feof(file)) {
        char c = fgetc(file);
        if (c != EOF) {
          putchar(c);
        }
      }
      fclose (file);
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    官方这个例子的源码位于https://github.com/emscripten-core/emscripten/blob/main/test/hello_world_file.cpp

    下面的命令用于指定要在运行编译后的代码之前先预加载到 Emscripten 的虚拟文件系统中的数据文件。这种方法很有用,因为浏览器只能从网络异步加载数据(Web Workers除外),而许多本机代码使用同步文件系统访问。

    emcc hello_world_file.cpp -o hello.html --preload-file hello_world_file.txt
    
    
    • 1
    • 2

    在这里插入图片描述

    运行效果如下:

    在这里插入图片描述

    Optimizing code

    与gcc和clang一样,Emscripten在默认情况下生成未优化的代码。你可以使用- 01命令行参数生成稍微优化的代码:

    emcc -O1 test/hello_world.cpp
    
    • 1

    还有其他-O2-O3-Og-Os-Oz等都和 clang 等一样的含义,不再赘述。

    总结

    到此我们算是入门了 wasm(WebAssembly) 和 emscripten,其他高级进阶请关注和作者链接等待更新,欢迎期待~

    在这里插入图片描述

  • 相关阅读:
    Spring framework day 03:Spring 整合 事务管理
    6-4应用层-电子邮件
    以 ZGC 为例,谈一谈 JVM 是如何实现 Reference 语义的
    [技术发展-27]:互联网平台的常见算法与新的治理办法
    机器视觉在艺术鉴赏和文物修复中的应用与挑战
    css画一条渐变的虚线
    视频智能分析平台LntonAIServer安防监控视频平台行人入侵检测算法核心特点及其应用价值
    java-net-php-python-ssm高校建党管理信息系统计算机毕业设计程序
    Lucene全文检索
    深入理解数据库原理
  • 原文地址:https://blog.csdn.net/yanbober/article/details/138046597