port lua to web environment using webassembly
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust, enabling deployment on the web for client and server applications.
Recently, all major browsers like Google Chrome, Safari, Microsoft Edge all support web assembly. Web assembly allows developers to compile C/C++ application into a browser supported format, meaning even a 3A game can be run on the browser platform as well.
Not only the portability, Web assembly also brings great performance improvement. It uses LLVM compiler to emit the assembly code. LLVM handles the C/C++ static analysis and optimization. Moreover, web assembly can be just-in-time compiled into machine code to achieve much higher performance.
For example, here is a Lua code interpreted by Lua virtual machine (5.3.5) that directly compiled from C to web assembly.
|
|
Click RUN to see the result.
run me, > < ~~~
Is this cool? This is a full-featured Lua virtual machine running in the browser environment. You can use all Lua build-in libraries. It is much faster than any other Lua JS implementation as well.
This article will demonstrate how to build a Lua web assembly target and inject it into the browser environment.
Web assembly uses Clang and LLVM as the compiler infrastructure. The default Clang and LLVM are not compatible with web assembly, you will need to build them from source. binaryen
is used to generate the final output from the assembly code. This is super tedious and the LLVM default does not support C standard library. You will need to remap the printf
or the fopen
function by yourself.
Fortunately, emscripten has wrapped all these tedious steps into a standalone package. We will use emscripten to compile Lua in this case.
First of all, we will need to obtain the emscripten package. It is super easy to do on macOS.
|
|
Then follow the instruction to set up emcc
.
Since we are going to host Lua in the browser environment. The default Lua host needs to be changed. Instead of executing a Lua file, we expose a function that allows Lua to execute the given script.
Modify lua.c
code to:
|
|
Note that luaL_dostring
is the Lua function that executes Lua code in protected mode. If an error occurs in the protected call, the error message will be pushed into the top of Lua stack.
Secondly, we need to modify the Makefile
:
|
|
EXPORTED_FUNCTIONS
is to export the function to the JS environment. Later we will create a JS wrap function to wrap the lua_main
function. Note that, LLVM will be appended a _
for each function created. So it should be _lua_main
instead of lua_main
.
Finally, we can build the Lua binary by calling:
|
|
It will generate lua.js
and lua.wasm
. We will use these two files in the browser environment.
Now we create an index.html
file:
|
|
First of all, we create a Module object. This object will be reused in the lua.js
file as well. So, the print
and printErr
functions are used to redirect the C stdout
and stderr
to the browser environment.
The onRuntimeInitialized
is the callback to be invoked when lua.wasm
is fully initialized. Here we wrap the lua_main function to a JS function lua_main
by using
|
|
When we call lua_main("print('hello world'");
, an alert, hello world
, will be shown to the user.
Of cause, the stdout can be redirected to an HTML element as well. Here is a simple Lua playground
Click RUN to see the result.
run me, > < ~~~
Have fun :)