WebAssembly without Emscripten

栏目: IT技术 · 发布时间: 4年前

内容简介:How to get rid of Emscripten and build and compile WebAssembly applications with ease.For compiling C/C++ code to WebAssembly to run on the web, Emscripten is the recognized standard to do so. Emscripten started out as a proof-of-concept hack to translate

How to get rid of Emscripten and build and compile WebAssembly applications with ease.

WebAssembly without Emscripten

Introduction

For compiling C/C++ code to WebAssembly to run on the web, Emscripten is the recognized standard to do so. Emscripten started out as a proof-of-concept hack to translate compiled C to JavaScript. Later browser vendors recognized the usability and introduced asm.js to be better able to optimize the output of Emscripten at runtime. It was a hack on top of a hack and at that point Emscripten used its own hacked together version of Clang to build.

Finally the industry started to collaborate to work on WebAssembly and building it directly into LLVM. So we could get rid of the proof-of-concept that is Emscripten, right? No, somehow everyone involved thought we should keep it. That Python script collection that uses LLVM to build, then a bunch of JavaScript running in Node.JS to make some glue JavaScript, a Java tool to minify that output and finally a bunch of native tools to optimize along the way.

Emscripten is fine if what you’re doing itself is a proof-of-concept and you want to see a desktop application somehow running in a browser with zero or barely any code change. But if you’re seriously targeting the web as a platform, I suggest to look into getting rid of Emscripten to a streamlined, smaller and faster build process and runtime experience.

And it’s easy!

Explanation

For this post, I prepared 7 sample programs which you can check out here:

Demo (Click to run) Download Explanation
1 Pure C function Download
2 With C Standard Library Download
3 With C++ Download
4 WebGL rendering Download
5 Audio output Download
6 Loading a URL Download
7 Embedding WASM Download

You can also find the code to these on the GitHub repository .

Setup

For getting builds going, we need 4 things that all are easy to get and require minimal setup.

  • clang and wasm-ld from LLVM
  • libc/libcxx sources prepared for Wasm
  • wasm-opt for smaller output files
  • GNU Make for build automation

Getting LLVM

We need only clang and wasm-ld from LLVM 8.0.0 or newer which is available on the official LLVM releases page .

On Windows it’s much simpler to use 7zip to just extract clang.exe and wasm-ld.exe instead of installing the whole suite.

Getting System Libraries

The system libraries (libc/libcxx prepared for Wasm) are maintained in the Emscripten project .

Just download the GitHub archive and extract only the System directory from it.

Getting wasm-opt

The tool wasm-opt from Binaryen is needed for finalization of the output and it also provides a 15% size reduction of the generated .wasm files.

Binary releases are available on the Binaryen project page .

Feel free to extract only wasm-opt.exe and ignore the rest. This should be part of LLVM’s wasm-ld but sadly it is external.

Getting GNU Make

If you’re on Windows, GNU Make is a small 180 KB EXE file which you can get here . On Linux you can install the Make package and on MacOS it comes as part of Xcode.

Demos

Demo 1: Explaining the Basic Process

Check out the basic demohere.

Building

Thebasic makefile basically uses 3 commands to build the .wasm file.

  1. Run the clang compiler to compile the source file(s) to .o wasm object file(s)
  2. Run the ld linker to link the .o file(s) to a .wasm file
  3. Run wasm-opt to finalize the interface to support 64-bit types and further size optimizations

It also copies two files into the output directory for easy testing, explained below.

At the top of the file there are a few configurable variables:

LLVM_ROOT   = D:/dev/wasm/llvm
WASMOPT     = D:/dev/wasm/wasm-opt.exe

EXPORTS = square
SOURCES = main.c
BUILD   = RELEASE

The variables are:

  • LLVM_ROOT : Path to LLVM with clang and wasm-ld executables (see)
  • WASMOPT : Path to the wasm-opt tool (see)
  • EXPORTS : A list of functions that get exported from wasm to JavaScript
  • SOURCES : A list of source files included in the build
  • BUILD : If set to RELEASE the module gets built without debug information and with more optimizations

HTML frontend

Thebasic loader.html is the website setting up and loading the JavaScript file below.

It has provides a way to log lines of text onto the website and it defines some parameters and functions for the WebAssembly loading.

module: 'output.wasm',              //the .wasm file to fetch and instantiate
print: function(text) { ... },      //a function to output text
error: function(code, msg) { ... }, //called on a loading error and program crash
started: function() { ... },        //called after the module has been loaded, we call our sample function here

JavaScript layer

Thebasic loader.js is the JavaScript that loads the .wasm and provides an interface between it and WebAssembly.

For this basic dependency free build it is very small. It first loads the .wasm file through a fetch call (only works through a web server or locally in Firefox). Next it quickly goes through the .wasm file to figure out its memory requirements. This basic demo does not do any heap memory allocation/growing but the approach is the same for all demos. Then it sets up a JavaScript managed WebAssembly memory object with the calculated requirements. We set it up ourselves in JavaScript because the later demos want to interact with the memory. For example, passing strings from and to WebAsssembly. Finally the wasm module is instantiated and (if it were existing) global C++ constructors and main() is called. Then the html frontend is notified that the module has been loaded.

Demo 2: Using the C Standard Library

Check out the demo using the C Standard Libraryhere.

To use the C Standard Library in our program, we have to extend theMakefile and theJavaScript layer a bit.

At the top of theMakefile we add a variable SYSTEM_ROOT pointing to the path of the. Then the new 50 lines at the bottom add a build step that outputs an archive “System.bc” which contains libc, libcxx and a malloc implementation all in one file. This System.bc is only created once and to rebuild it one needs to delete it.

Then in theJavaScript layer we have new functions to interact with the now dynamic memory heap available to the wasm module. We have two functions to read and write UTF8 strings from JavaScript. We also fill out a list of functions given to the wasm module by the loader. Simple emulation of stdout text output and even a simple emulated file (with reading and seeking). We also pass a function called sbrk which is called when C wants to expand the size of the memory heap. Last there is a list of one line functions for things like assertion handling, time, and math.

After instantiating the module and passing over the functions we set up a string in the wasm memory which contains the first commandline argument so the main function receives a proper argc/argv combo.

Because the main() function in the code can now do the text printing on its own with our emulated stdout handling theloader html does nothing after the module has been loaded. There is one new line in the html which is payload: 'UGF5bG9hZCBGaWxl', which is a base64 encoded file that can be accessed with fopen inside the WebAssembly module.

Demo 3: Using C++ and the C++ Standard Library

Check out the demo using C++ featureshere.

Besides changing the source file from “main.c” to “main.cpp” theMakefile remains unchanged from the previous demo, because we already included all the C++ stuff into the combined “System.bc” archive.

The only change is in thesource code and there is no further change in theJavaScript layer or theloader html.

If you’re wondering why it’s still using printf and not std::cout, I have disabled streams and locale on purpose because it makes the output 180 KB instead 20 KB. Also I prefer the printf syntax.

Demo 4: WebGL Rendering

Check out the demo with WebGL renderinghere.

The only change to theMakefile is an exported function WAFNDraw , which will get called for every frame to render from JavaScript.

In theJavaScript layer we removed the stdout and file emulation (not needed in this demo) but added a rather big WebGL interface. This interface basically emulates OpenGL ES 2.0 so in the C/C++ side it can be programmed as a regular OpenGL 2.0 application (without fixed-function rendering). To keep the size of the JavaScript file reasonable, some variations of glUniform/glVertexAttrib/glGet and some uncommon functions are not implemented. If you need to add a missing function, you can reference the currently implemented functions or the complete implementation in the Emscripten project .

We also export two special functions to be called from C, WAJS_SetupCanvas to setup the WebGL rendering canvas and WAJS_GetTime to return the number of milliseconds since setup.

Thesource code implements a very basic OpenGL 2.0 application to draw a colored triangle with a simple vertex and fragment shader.

Demo 5: Audio Output

Check out the demo with Audio outputhere.

The only change to theMakefile is an exported function called WAFNAudio , which will get called for every block of audio needed from JavaScript.

In theJavaScript layer there’s a new function to be called from C called WAJS_StartAudio which starts up a stereo 44100 hz WebAudio output and whenever the audio output needs more data, WAFNAudio in C is called with a float buffer prepared from JavaScript to be filled by the WebAssembly function.

Thesource code implements a simple sine wave generator.

Demo 6: Loading Data from URL

Check out the demo with URL loadinghere.

The only change to theMakefile is an exported function called WAFNHTTP , which will get called with the response after the loading of a requested URL finishes.

In theJavaScript layer we have a function called WAJS_AsyncLoad which accepts a URL to be requested by the browser. The URL is relative to the loader HTML so it can be just a filename that is stored on the same web server in the same directory. Once the request completes (or if there is an error), the exported C function WAFNHTTP is called with the result.

Thesource code requests a TXT file and then prints the content once it is loaded.

Demo 7: Advanced Build Script with Embedding

Check out the resulthere.

This last demo combines all the previous one and adds some extra features to the build script.

It uses Python to embed the .wasm output file inside the JavaScript. Then the JavaScript loader with that embedded .wasm itself is directly embedded in the resulting html. This also has the advantage of working in all browsers when opening from a local file while the builds above only run in Firefox without loading via a web server.

At the top of theMakefile it now requires a variable PYTHON with the full path of the.

It also uses a minified JavaScript loader that has been created by pasting theoriginal file into an online script minifier tool .

The result is all contained within a single HTML file and is with some standard library usage, WebGL rendering and audio output only 50 KB.

Getting Python

If you already have Python (any version) on your system, you’re good to go.

Otherwise if you’re on Windows, there’s a simple portable ZIP of Python 3 here .

Remarks

If you are interested and are looking for more features that can be implemented this way, you can check out my game creation frameworkZillaLib. Its JavaScript loader is similar to the one in Demo 7, but has more features like keyboard/mouse/multi-touch input, fullscreen, pointer locking, window resize handling, web socket and storing of settings and other data in local storage. It also features integration with Visual Studio so building and running can be done directly from the IDE.

Ever since I moved from Emscripten to this approach I have not looked back. Faster and streamlined build, smaller output, and full control of the layer between WebAssembly and browser, all that by removing a ton of cruft and required tools and libraries and dependencies.

If you have questions, I can be reached on Twitter @B_Schelling .


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

算法之道

算法之道

邹恒明 / 机械工业出版社 / 2010-2 / 39.00元

《算法之道》追求的目标是算法背后的逻辑,是一本启示书,而不是一本包罗万象的算法大全。因此,《算法之道》甄选了那些最能够展现算法思想、战略和精华,并能够有效训练算法思维的内容。《算法之道》将算法的讨论分为五大部分:算法基础篇、算法设计篇、算法分析篇、经典算法篇、难解与无解篇。每一个部分分别讨论算法的一大方面:基础、设计、分析、经典和难解问题。 《算法之道》既可以作为大学本科或研究生的算法教材或......一起来看看 《算法之道》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具