dcodeIO/webassembly

View on GitHub
src/index.js

Summary

Maintainability
B
5 hrs
Test Coverage
"use strict";

if (typeof window !== "undefined" && window)
    window.webassembly = exports;

// Common aliases
var getOwnPropertyNames = Object.getOwnPropertyNames;

/**
 * Describes a module instance as returned by {@link load}.
 * @interface IModule
 * @property {Object.<string,*>} exports Exports
 * @property {Object.<string,*>} imports Imports
 * @property {IMemory} memory Memory
 * @property {Object.<string,*>} env Environment
 */

/**
 * Describes a module memory instance.
 * @interface IMemory
 * @property {ArrayBuffer} buffer Underlying buffer
 * @property {number} initial=1 Specified initial amount of memory in 64k pages
 * @property {number} [maximum] If specified, maximum amount of memory in 64k pages
 * @property {Uint8Array} U8 Byte-level view
 * @property {Uint32Array} U32 Aligned unsigned 32-bit integer view
 * @property {Int32Array} S32 Aligned signed 32-bit integer view
 * @property {Float32Array} F32 Aligned 32-bit float view
 * @property {Float64Array} F64 Aligned 64-bit double view
 * @property {GetInt} getInt Reads a 32-bit signed integer starting at the specified memory offset (aligned to 4 bytes)
 * @property {GetUint} getUint Reads a 32-bit unsigned integer starting at the specified memory offset (aligned to 4 bytes)
 * @property {GetFloat} getFloat Reads a 32-bit float starting at the specified memory offset (aligned to 4 bytes)
 * @property {GetDouble} getDouble Reads a 64-bit double starting at the specified memory offset (aligned to 8 bytes)
 * @property {GetString} getString Reads the (zero-terminated, exclusive) string starting at the specified memory offset (aligned to 4 bytes)
 */

/**
 * Loads a WebAssembly.
 * @param {string} file File name
 * @param {LoadOptions} [options] Options
 * @returns {Promise.<IModule>} Promise resolving to the instantiated module
 */
function load(file, options) {

    /**
     * Options as used by {@link load}.
     * @interface LoadOptions
     * @property {number} [initialMemory=1] Initial memory in pages of 64k
     * @property {number} [maximumMemory] Maximum memory in pages of 64k
     * @property {Object.<string,*>} [imports] Imports
     */

    options || (options = {});

    var imports = options.imports || {};

    // Initialize memory

    var memory = imports.memory;
    if (!memory) {
        var opts = { initial: options.initialMemory || 1 };
        if (options.maximumMemory)
            opts.maximum = options.maximumMemory;
        memory = new WebAssembly.Memory(opts);
        memory.initial = options.initialMemory || 1;
        memory.maximum = options.maximumMemory;
    }

    var table = imports.table;
    if (!table)
        table = new WebAssembly.Table({ initial: 0, element: "anyfunc" });

    function grow() {
        var buf = memory.buffer;
        memory.U8  = new Uint8Array  (buf);
        memory.S32 = new Int32Array  (buf);
        memory.U32 = new Uint32Array (buf);
        memory.F32 = new Float32Array(buf);
        memory.F64 = new Float64Array(buf);
    }

    grow();

    // Add utilty to memory

    /**
     * Reads a 32-bit signed integer starting at the specified memory offset.
     * @typedef GetInt
     * @function
     * @param {number} ptr Memory offset
     * @returns {number} Signed 32-bit integer value
     */
    function getInt(ptr) {
        return memory.S32[ptr >> 2];
    }

    memory.getInt = getInt;

    /**
     * Reads a 32-bit unsigned integer starting at the specified memory offset.
     * @typedef GetUint
     * @function
     * @param {number} ptr Memory offset
     * @returns {number} Unsigned 32-bit integer value
     */
    function getUint(ptr) {
        return memory.U32[ptr >> 2];
    }

    memory.getUint = getUint;

    /**
     * Reads a 32-bit float starting at the specified memory offset.
     * @typedef GetFloat
     * @function
     * @param {number} ptr Memory offset
     * @returns {number} 32-bit float value
     */
    function getFloat(ptr) {
        return memory.F32[ptr >> 2];
    }

    memory.getFloat = getFloat;

    /**
     * Reads a 64-bit double starting at the specified memory offset.
     * @typedef GetDouble
     * @function
     * @param {number} ptr Memory offset
     * @returns {number} 64-bit float value
     */
    function getDouble(ptr) {
        return memory.F64[ptr >> 3];
    }

    memory.getDouble = getDouble;

    /**
     * Reads a (zero-terminated, exclusive) string starting at the specified memory offset.
     * @typedef GetString
     * @function
     * @param {number} ptr Memory offset
     * @returns {string} String value
     */
    function getString(ptr) {
        var start = (ptr >>>= 0);
        while (memory.U8[ptr++]);
        getString.bytes = ptr - start;
        return String.fromCharCode.apply(null, memory.U8.subarray(start, ptr - 1));
    }

    memory.getString = getString;

    // Initialize environment

    var env = {};

    env.memoryBase = imports.memoryBase || 0;
    env.memory = memory;
    env.tableBase = imports.tableBase || 0;
    env.table = table;

    // Add console to environment

    function sprintf(ptr, base) {
        var s = getString(ptr);
        return base
            ? s.replace(/%([dfisu]|lf)/g, ($0, $1) => {
                var val;
                return base +=
                    $1 === "u"  ? (val = getUint(base), 4)
                  : $1 === "f"  ? (val = getFloat(base), 4)
                  : $1 === "s"  ? (val = getString(getUint(base)), 4)
                  : $1 === "lf" ? (val = getDouble(base), 8)
                  :               (val = getInt(base), 4)
                  , val;
            })
            : s;
    }

    getOwnPropertyNames(console).forEach(key => {
        if (typeof console[key] === "function") // eslint-disable-line no-console
            env["console_" + key] = (ptr, base) => {
                console[key](sprintf(ptr, base)); // eslint-disable-line no-console
            };
    });

    // Add Math to environment

    getOwnPropertyNames(Math).forEach(key => {
        if (typeof Math[key] === "function")
            env["Math_" + key] = Math[key];
    });

    // Add imports to environment

    Object.keys(imports).forEach(key => env[key] = imports[key]);

    // Add default exit listeners if not explicitly imported

    if (!env._abort)
        env._abort = errno => { throw Error("abnormal abort in " + file + ": " + errno); };
    if (!env._exit)
        env._exit = code => { if (code) throw Error("abnormal exit in " + file + ": " + code); }

    // Finally, fetch the assembly and instantiate it

    env._grow = grow;

    return (typeof fetch === "function" && fetch || fetch_node)(file)
        .then(result => result.arrayBuffer())
        .then(buffer => WebAssembly.instantiate(buffer, { env: env }))
        .then(module => {
            var instance = module.instance;
            instance.imports = imports;
            instance.memory = memory;
            instance.env = env;
            return instance;
        });
}

exports.load = load;

// Internal fetch API polyfill for node that doesn't trigger webpack
var fs;
function fetch_node(file) {
    return new Promise((resolve, reject) => (fs || (fs = eval("equire".replace(/^/, "r"))("fs"))).readFile(file, (err, data) => err ? reject(err) : resolve({ arrayBuffer: () => data })));
}