Skip to content

Instantly share code, notes, and snippets.

@tanoxyz
Last active January 20, 2022 11:18
Show Gist options
  • Save tanoxyz/b2f84aeae28f8650ec4090d342d9219c to your computer and use it in GitHub Desktop.
Save tanoxyz/b2f84aeae28f8650ec4090d342d9219c to your computer and use it in GitHub Desktop.
# This is based on http://schellcode.github.io/webassembly-without-emscripten demos
clang --target=wasm32 -o a.wasm -nostdlib main.c
# Serve the index.html with a http server
<!doctype html>
<html lang="en-us">
<body style="background:#CCCCCC">
<canvas id="canvas" height="256" width="256"></canvas>
<script type="text/javascript">
var Module = {};
(async () => {
// Fetch the .wasm file and store its bytes into the byte array wasm_bytes
const response = await fetch("./a.wasm");
var wasm_bytes = await response.arrayBuffer();
wasm_bytes = new Uint8Array(wasm_bytes);
var env = {};
var JS = {};
// Starting from here, this code goes through the wasm file sections according the binary
// encoding description https://webassembly.org/docs/binary-encoding/ and
// https://github.com/WebAssembly/design/blob/main/BinaryEncoding.md
{
const UTF8_decoder = new TextDecoder("utf-8");
var cursor = 8;
const wasm_len = wasm_bytes.length;
// The wasm binary format uses variable lenght numbers, encoded using LEB128
// https://en.wikipedia.org/wiki/LEB128 this function decodes de current chunk and
// displaces the cursor
function Get_next_ULEB128() {
var result = 0;
var shift = 0;
while (1) {
const value = wasm_bytes[cursor];
result |= ((value & 0x7F) << shift);
cursor += 1;
if ((value & 0x80) == 0) break;
shift += 7;
}
return result;
}
while (cursor < wasm_len) {
const section_type = Get_next_ULEB128();
const section_len = Get_next_ULEB128();
// We only care about the section 2 (Imports section)
if (section_type !== 2) {
cursor += section_len;
continue;
}
const total_entries = Get_next_ULEB128();
for (var i = 0; i < total_entries; i+=1) {
// Get the module name
const module_str_len = Get_next_ULEB128();
const module_str = UTF8_decoder.decode(new Uint8Array(wasm_bytes.buffer, cursor, module_str_len));
cursor += module_str_len;
// Get the field
const field_str_len = Get_next_ULEB128();
const field_start = cursor;
const field_str = UTF8_decoder.decode(new Uint8Array(wasm_bytes.buffer, field_start, field_str_len));
cursor += field_str_len;
// Get the kind of module
var kind = wasm_bytes[cursor];
cursor += 1;
if (kind === 0) { // Function (we only care about the functions)
Get_next_ULEB128();
if (module_str === "JS") { // We only care about the JS modules
// The code is embebed in the field in this format:
// my_function|(int arg1, int argc)|{ ...the code ...}
// So:
const splited_field = field_str.split('|', 2); // We extract the different parts
function_name = splited_field[0];
function_args = splited_field[1];
function_code = field_str.substr(function_name.length + function_args.length + 2);
// strip C types out of params list (change '(float p1, unsigned int p2)' to 'p1,p2' (function pointers not supported)
function_args = function_args.replace(/\[.*?\]|^\(\s*(void|)\s*\)$/g, '').replace(/.*?(\w+)\s*[,\)]/g, ',$1').substr(1);
// Patch the binary puting white spaces on the field string in order to have the clean function name
wasm_bytes.fill(32, field_start + function_name.length, field_start + field_str_len)
// Append white spaces to the function name to get the same lenght as the full field string
function_name = function_name + (' '.repeat(field_str_len - function_name.length));
// Instantiate the javascript function
JS[function_name] = new Function(...(function_args ? function_args.split(',') : []).concat(function_code));
}
}
// Handle all the other kinds
else if (kind === 1) { // Table
Get_next_ULEB128(); // element_type
// Resizable limits
const flags = Get_next_ULEB128();
Get_next_ULEB128(); // initial lenght
if (flags === 1) {
Get_next_ULEB128(); // maximum
}
}
else if (kind === 2) { // Memory
// Resizable limits
const flags = Get_next_ULEB128();
Get_next_ULEB128(); // initial lenght
if (flags === 1) {
Get_next_ULEB128(); // maximum
}
}
else if (kind === 3) { // Global
Get_next_ULEB128(); // Value type
Get_next_ULEB128(); // Mutablility flag
}
}
}
}
// Instantiate the wasm module by passing the prepared env and JS objects containing import functions for the wasm module
var output = await WebAssembly.instantiate(wasm_bytes, {env:env, JS:JS});
Module.ASM = output.instance.exports;
if (Module.ASM._start) Module.ASM._start();
else if (Module.ASM.main) Module.ASM.main();
})();
</script>
</body>
</html>
// Macro to generate a JavaScript function that can be called from C
#define WA_JS(ret, name, args, ...) extern __attribute__((import_module("JS"), import_name(#name "|" #args "|" #__VA_ARGS__))) ret name args;
#define CELL_DIM 32
static unsigned char frame_buffer[256*256*4] = {0};
WA_JS(void, commit_buffer, (const unsigned char *data, int w, int h),
{
var buffer = new Uint8ClampedArray(Module.ASM.memory.buffer, data, w*h*4);
var ctx = document.getElementById('canvas').ctx = canvas.getContext('2d');
var image_data = new ImageData(buffer, w, h);
ctx.putImageData(image_data, 0, 0);
});
typedef void (*AppFrame) (void);
WA_JS(void, Set_animation_frame, (AppFrame app_frame), {
window.requestAnimationFrame( () => {
Module.ASM.WAFN_Run_app_frame(app_frame);
});
});
__attribute__((visibility("default"), export_name("WAFN_Run_app_frame"))) void
WAFN_Run_app_frame(AppFrame app_frame) {
app_frame();
}
void
App_frame(void) {
static int offset = 0;
for (int i = 0; i < 256; i+=1) {
for (int j = 0; j < 256; j+=1) {
int pixel = i * 256*4 + j * 4;
unsigned char col = 0x00;
if ((((i+offset) / CELL_DIM) & 1) == (((j+offset) / CELL_DIM) & 1)) {
col = 0xFF;
}
frame_buffer[pixel+0] = col;
frame_buffer[pixel+1] = col;
frame_buffer[pixel+2] = col;
frame_buffer[pixel+3] = 0xFF;
}
}
offset = (offset + 1) % CELL_DIM;
commit_buffer(frame_buffer, 256, 256);
Set_animation_frame(App_frame);
}
int
main() {
App_frame();
return 0;
}
__attribute__((visibility("default"), export_name("_start"))) int
_start() {
return main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment