Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save miklund/79c1f3eb129ea5689c03c41d17922c14 to your computer and use it in GitHub Desktop.
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
# Title: Creating a WebAssembly binary and running it in a browser
# Author: Mikael Lundin
# Date: 2016-06-19
// ////////
// 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);
///////////////////////////////////////////////////////////////////////////////
//
// 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];
}
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;
}
<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>
/ 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