Last active
June 14, 2022 21:12
-
-
Save miklund/79c1f3eb129ea5689c03c41d17922c14 to your computer and use it in GitHub Desktop.
2016-06-19 Creating a WebAssembly binary and running it in a browser
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
# Title: Creating a WebAssembly binary and running it in a browser | |
# Author: Mikael Lundin | |
# Date: 2016-06-19 |
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
// //////// | |
// Preamble | |
// //////// | |
// first add the magic wasm number: 0x6d736100 | |
var wasm = uint32(0x6d736100) | |
// append the version of wasm, 11 in this case | |
wasm = wasm.concat(uint32(0x0b)) | |
// //////////// | |
// Type section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#type-section | |
// //////////// | |
// The type section declares all function signatures that will be used in the module. | |
var type = []; | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("type".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("type")); | |
// count: number of type entries to follow | |
type = type.concat(varuint32(1)); | |
// | |
// entries: here starts the first type entry | |
// | |
// form: 0x40 indicates a function type | |
type = type.concat(varuint7(0x40)); | |
// param_count: the number of parameters to the function | |
type = type.concat(varuint32(0x02)); | |
// param_types: the parameter types of the function | |
type = type.concat(value_type(1)); // i32 | |
type = type.concat(value_type(1)); // i32 | |
// return_count: the number of results from the function | |
type = type.concat(varuint1(1)); | |
// return_type: the result type of the function (if return_count is 1) | |
type = type.concat(value_type(1)); // i32 | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(type.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(type); | |
// ////////////// | |
// Import section | |
// ////////////// | |
// todo: implement this when imports are of import | |
// //////////////// | |
// Function section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#function-section | |
// //////////////// | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("function".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("function")); | |
var functions = []; | |
// count: count of signature indices to follow | |
functions = functions.concat(varuint32(1)); | |
// types: sequence of indices into the type section | |
functions = functions.concat(varuint32(0)); // index should be zero based | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(functions.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(functions); | |
// ///////////// | |
// Table section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#table-section | |
// ///////////// | |
// todo: implement when needed | |
// ////////////// | |
// Memory section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#memory-section | |
// ////////////// | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("memory".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("memory")); | |
var memory = []; | |
// initial: initial memory size in 64KiB pages | |
memory = memory.concat(varuint32(256, 1)); | |
// maximum: initial memory size in 64KiB pages | |
memory = memory.concat(varuint32(256, 1)); | |
// exported: 1 if the memory is visible outside the module | |
memory = memory.concat(uint8(1)); | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(memory.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(memory); | |
// ////////////// | |
// Export section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#export-section | |
// ////////////// | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("export".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("export")); | |
var exports = []; | |
// count: count of export entries to follow | |
exports = exports.concat(varuint32(1)); | |
// | |
// entries: repeated export entries as described below | |
// | |
// func_index: index into the function table | |
exports = exports.concat(varuint32(0)); | |
// function_len: function string length | |
exports = exports.concat(varuint32("add".length)); | |
// function_str: function string of function_len bytes | |
exports = exports.concat(stringToByteArray("add")); | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(exports.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(exports); | |
// ///////////// | |
// Start section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#start-section | |
// ///////////// | |
// todo: this module does not have a start function | |
// //////////// | |
// Code section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#code-section | |
// //////////// | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("code".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("code")); | |
var code = []; | |
// count: count of function bodies to follow | |
code = code.concat(varuint32(1)); | |
// | |
// bodies: sequence of function bodies | |
// | |
var body = []; | |
// local_count: number of local entries | |
body = body.concat(varuint32(0)); | |
// | |
// ast | |
// | |
// The program this ast is for | |
// | |
//(module | |
// (func $addTwo (param i32 i32) (result i32) | |
// (i32.add | |
// (get_local 0) | |
// (get_local 1))) | |
// (export "addTwo" $addTwo)) | |
// post-order encoding, right, left then op-code | |
// get_local: read a local variable or parameter | |
body = body.concat([0x14].concat(varuint32(0))); | |
// get_local: read a local variable or parameter | |
body = body.concat([0x14].concat(varuint32(1))); | |
// i32.add | |
body = body.concat([0x40]); | |
// body_size: size of function body to follow, in bytes | |
code = code.concat(varuint32(body.length, 4)); | |
// body | |
code = code.concat(body); | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(code.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(code); | |
// //////////// | |
// Data section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#data-section | |
// //////////// | |
// todo: no initialized data at this time to be loaded into linear memory | |
// //////////// | |
// Name section | |
// https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section | |
// //////////// | |
// id_len: section identifier string length | |
wasm = wasm.concat(varuint32("name".length)); | |
// id_str: section identifier string of id_len bytes | |
wasm = wasm.concat(stringToByteArray("name")); | |
var name = []; | |
// count: count of entries to follow | |
name = name.concat(varuint32(1)); | |
// | |
// entries: sequence of names | |
// | |
// fun_name_len: string length, in bytes | |
name = name.concat(varuint32("add".length)); | |
// fun_name_str: valid utf8 encoding | |
name = name.concat(stringToByteArray("add")); | |
// local_count: count of local names to follow | |
name = name.concat(varuint32(0)); | |
// payload_len: size of this section in bytes | |
wasm = wasm.concat(varuint32(name.length, 4)); | |
// payload_str: content of this section, of length payload_len | |
wasm = wasm.concat(name); | |
/////////////////////////////////////////////////////////////////////////////// |
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
// | |
// Data types | |
// | |
// a single-byte unsigned integer | |
function uint8(n) { | |
if (n < 0 || n > 255) { | |
throw new Error('uint8 is limited to [0, 255]') | |
} | |
return [n]; | |
} | |
// A four-byte little endian unsigned integer. | |
function uint32(n) { | |
if (n < 0 || n > 4294967295) { | |
throw new Error('uint32 is limited to [0, 4294967295]') | |
} | |
var v = [] | |
for (var i = 0; i < 4; i++) { | |
v[i] = n & (255) | |
n = n >> 8 | |
} | |
return v; | |
} | |
// A Signed LEB128 variable-length integer, limited to int32 values. | |
function varint32(n) { | |
if (n < -2147483648 || n > 2147483647) { | |
throw new Error('varint32 is limited to [-2147483648, 2147483647]') | |
} | |
return signedLEB128(n); | |
} | |
// A LEB128 variable-length integer, limited to the values 0 or 1. varuint1 values may contain leading zeros. | |
function varuint1(n) { | |
if (n < 0 || n > 1) { | |
throw new Error('varuint1 is limited to [0, 1]') | |
} | |
return unsignedLEB128(n); | |
} | |
// A LEB128 variable-length integer, limited to the values [0, 127]. varuint7 values may contain leading zeros. | |
function varuint7(n) { | |
if (n < 0 || n > 127) { | |
throw new Error('varuint7 is limited to [0, 127]'); | |
} | |
return unsignedLEB128(n); | |
} | |
// A LEB128 variable-length integer, limited to uint32 values. varuint32 values may contain leading zeros. | |
function varuint32(n, padding) { | |
if (n < 0 || n > 0xFFFFFFFF) { | |
throw new Error('varuint32 is limited to [0, 4294967295]') | |
} | |
return unsignedLEB128(n, padding); | |
} | |
// A Signed LEB128 variable-length integer, limited to int64 values. | |
function varint64(n) { | |
if (n < -9223372036854775808 || n > 9223372036854775807) { | |
throw new Error('varint64 is limited to [-9223372036854775808, 9223372036854775807]') | |
} | |
return signedLEB128(n); | |
} | |
// A single-byte unsigned integer indicating a value type. | |
function value_type(n) { | |
if (n < 1 || n > 4) { | |
throw new Error('value_type is limited to [1, 4] => [i32, i64, f32, f64]'); | |
} | |
return [n]; | |
} |
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
function signedLEB128 (value) { | |
var v = [], | |
// log2 is expensive, could be replace by a lookup table | |
size = Math.ceil(Math.log2(Math.abs(value))), | |
more = true, | |
isNegative = (value < 0), | |
b = 0; | |
while (more) { | |
// get 7 least significant bits | |
b = value & 127; | |
// left shift value 7 bits | |
value = value >> 7; | |
if (isNegative) { | |
// extend sign | |
value = value | (- (1 << (size - 7))); | |
} | |
// sign bit of byte is second high order bit | |
if ((value == 0 && ((b & 0x40) == 0)) || ((value == -1 && ((b & 0x40) == 0x40)))) { | |
// calculation is complete | |
more = false; | |
} | |
else { | |
b = b | 128; | |
} | |
v.push(b); | |
} | |
return v; | |
} | |
function unsignedLEB128 (value, padding) { | |
var v = [], | |
b = 0; | |
// no padding unless specified | |
padding = padding || 0; | |
do { | |
b = value & 127; | |
value = value >> 7; | |
if (value != 0 || padding > 0) { | |
b = b | 128; | |
} | |
v.push(b); | |
padding--; | |
} while (value != 0 || padding > -1); | |
return v; | |
} |
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
<html> | |
<head> | |
<style> | |
.hidden { | |
display: none; | |
} | |
</style> | |
<script> | |
var myModule; | |
function loadModule() { | |
// read binary file | |
var wasmFile = document.getElementById("wasmBinary").files[0]; | |
// create a reader object | |
var reader = new FileReader(); | |
// on success | |
reader.onload = function (e) { | |
// save the result ArrayBuffer | |
var wasmBinary = e.target.result; | |
// display success in the console | |
console.log('Successfully read file %d bytes', wasmBinary.byteLength); | |
// init the module | |
myModule = Wasm.instantiateModule(wasmBinary); | |
// display the section for testing the module | |
document.getElementById("add").className = ""; | |
return false; | |
}; | |
// on error | |
reader.onerror = function (e) { | |
// display error in the console | |
console.error('An error reading the file occured'); | |
}; | |
// read the whole file into an ArrayBuffer | |
reader.readAsArrayBuffer(wasmFile); | |
// do not refresh the page | |
return false; | |
} | |
function runModule() { | |
// get first argument | |
var arg1 = parseInt(document.getElementById("arg1").value); | |
// get second argument | |
var arg2 = parseInt(document.getElementById("arg2").value); | |
// caluclate the addition | |
var result = myModule.exports.add(arg1, arg2); | |
// display the result | |
document.getElementById("result").innerText = result; | |
} | |
</script> | |
<script src="polyfill-prototype-1/jslib/load-wasm.js"></script> | |
</head> | |
<body> | |
<h1>Upload a WebAssembly module</h1> | |
<input type="file" id="wasmBinary" name="wasmBinary" /> | |
<input type="submit" value="Load Module" onclick="loadModule()" /> | |
<div id="add" class="hidden" style="display:hidden"> | |
<h2>Module Loaded</h2> | |
<p>Test the functionality here</p> | |
<div> | |
<input id="arg1" name="arg1" size="7" /> | |
<span>+</span> | |
<input id="arg2" name="arg2" size="7" /> | |
<input type="submit" value="=" onclick="runModule()" /> | |
<span id="result"></span> | |
</div> | |
</div> | |
</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
/ output | |
console.log('Complete HEX to output') | |
var output = ""; | |
for (var i = 0; i < wasm.length; i++) { | |
output += wasm[i].toString(16) + " "; | |
} | |
console.log(output); | |
var buffer = Buffer.from(wasm); | |
var fd = fs.openSync('out.wasm', 'w'); | |
fs.write(fd, buffer, 0, buffer.length, 0, function (err) { | |
if(err) { | |
console.log(err) | |
} else { | |
console.log('The byte buffer (%d bytes) was saved to out.wasm', wasm.length) | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment