dcodeIO/webassembly

View on GitHub
cli/compiler.js

Summary

Maintainability
C
1 day
Test Coverage
var minimist = require("minimist"),
    chalk    = require("chalk"),
    path     = require("path"),
    tmp      = require("tmp"),
    util     = require("../cli/util");

var optimizeFinal = require("./config/optimize-final.json"),
    optimizeLink  = require("./config/optimize-link.json");

exports.main = (argv, callback) => {

    if (!callback)
        callback = util.defaultCallback;

    argv = minimist(argv, {
        alias: {
            out: "o",
            debug: "d",
            quiet: "q",

            optimize: "O",
            stack: "s",
            main: "m",
            define: "D",

            headers: "I",
            include: "i",
            link: "l",
            bare: "b"
        },
        string: [ "out", "stack", "main", "headers", "include", "link", "define" ],
        boolean: [ "debug", "quiet", "optimize", "bare", "internal" ]
    });

    var files = argv._;
    if (files.length !== 1) {
        util.printLogo("Compiler");
        process.stderr.write([
            chalk.bold.white("Compiles C code to a WebAssembly module."),
            "",
            "  -o, --out        Specifies the .wasm output file. Defaults to stdout.",
            "  -d, --debug      Prints debug information to stderr.",
            "  -q, --quiet      Suppresses informatory output.",
            "",
            chalk.gray.bold("  Module configuration:"),
            "",
            "  -O, --optimize   Optimizes the output file and removes dead code.",
            "  -s, --stack      Specifies the stack size. Defaults to 10000.",
            "  -m, --main       Executes the specified function on load.",
            "  -D, --define     Defines a macro.",
            "",
            chalk.gray.bold("  Includes and libraries:"),
            "",
            "  -I, --headers    Includes C headers from the specified directories.",
            "  -i, --include    Includes the specified source files.",
         // "  -l, --link       Links in the specified libraries after compilation.",
            "  -b, --bare       Does not link against the runtime library / libc.",
            "",
            "usage: " + chalk.bold.cyan("wa-compile") + " [options] program.c",
            ""
        ].join("\n"));
        process.nextTick(() => { callback(Error("usage")); });
        return 1;
    }

    var platform = util.checkPlatform(callback);
    if (!platform)
        return 3;

    if (!argv.quiet)
        util.printHeading("Compiling on " + platform + " ...");

    tmp.setGracefulCleanup();

    var temp = [
        tmp.fileSync({ prefix: "wa1_" }),
        tmp.fileSync({ prefix: "wa2_" })
    ];
    temp.index = 0;

    var file = path.normalize(files[0]),
        out  = argv.out && path.normalize(argv.out) || undefined;

    var defines = argv.define  && typeof argv.define  === "string" && [ argv.define  ] || argv.define  || [],
        headers = argv.headers && typeof argv.headers === "string" && [ argv.headers ] || argv.headers || [],
        include = argv.include && typeof argv.include === "string" && [ argv.include ] || argv.include || [],
        links   = argv.link    && typeof argv.link    === "string" && [ argv.link    ] || argv.link    || [];

    var includeArgs = [];

    includeArgs.push("-D", "WEBASSEMBLY");
    defines.forEach(def  => { includeArgs.push("-D", def); });
    if (argv.internal)
        includeArgs.push(
            "-isystem", path.join(util.basedir, "lib/musl-wasm32/include"),
            "-isystem", path.join(util.basedir, "lib/musl/include")
        );
    includeArgs.push(
        "-isystem", path.join(util.basedir, "include")
    );
    headers.forEach(file => { includeArgs.push("-I", file); });
    include.forEach(file => { includeArgs.push("-include", file); });

    var p =

    // Compile to LLVM bitcode

    util.run(path.join(util.bindir, "clang"), [
        file,
        argv.bare || links.length || !argv.optimize ? undefined : "-O",
        "-c",
        "--target=wasm32-unknown-unknown",
        "-emit-llvm",
        "-nostdinc",
        "-nostdlib",
        includeArgs,
        [ argv.debug && "-v" || undefined ],
        "-o", temp[temp.index %= temp.length].name
    ], argv);

    // Link with webassembly / libc

    if (!argv.bare) p = p.then(() =>

        util.run(path.join(util.bindir, "llvm-link"), [
            temp[temp.index++].name,
            path.join(util.basedir, "lib", "webassembly.bc"),
            "-only-needed",
            "-o", temp[temp.index %= temp.length].name
        ], argv));

    // Compile to assembly language (s)

    p = p.then(() =>

    util.run(path.join(util.bindir, "llc"), [
        temp[temp.index++].name,
        "-march=wasm32",
        "-filetype=asm",
        "-asm-verbose=false",
        "-thread-model=single",
        "-data-sections",
        "-function-sections",
        "-o", temp[temp.index %= temp.length].name
    ], argv)).then(() =>

    // Convert to a WebAssembly module

    util.run(path.join(util.bindir, "s2wasm"), [
        temp[temp.index++].name,
        "--import-memory" ,
        argv.bare ? undefined : [ "--allocate-stack", argv.stack || "10000" ],
        [ "--start", argv.main ],
        argv.debug ? "--debug" : undefined,
        "-o", temp[temp.index %= temp.length].name
    ], argv)).then(() =>

    // (Pre-)Optimize module

    util.run(path.join(util.bindir, "wasm-opt"), [
        temp[temp.index++].name,
        argv.optimize && (links.length || argv.bare ? optimizeLink : optimizeFinal) || [],
        argv.debug ? "--debug" : undefined,
        [ "-o", links.length ? temp[temp.index %= temp.length].name : out ]
    ], argv));

    // If applicable, link with other webassembly modules and perform final optimizations

    if (links.length)  { p = p.then(() =>

        util.run(path.join(util.bindir, "wasm-merge"), [
            links,
            temp[temp.index++].name,
            argv.optimize ? "-O" : undefined,
            argv.debug ? "-d" : undefined,
            [ "-o", argv.optimize ? temp[temp.index %= temp.length].name : out ]
        ], argv));

        if (argv.optimize) p = p.then(() =>

            util.run(path.join(util.bindir, "wasm-opt"), [
                temp[temp.index++].name,
                optimizeFinal,
                argv.debug ? "-d" : undefined,
                [ "-o", out ]
            ], argv));

    }

    p.then(finish, callback);

    function finish() {
        if (!argv.quiet)
            util.defaultSuccess();
        callback(null, out);
    }
};