Last active
January 20, 2022 11:18
-
-
Save tanoxyz/b2f84aeae28f8650ec4090d342d9219c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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