hamzaremmal/amy

View on GitHub
compiler/src/main/scala/amyc/backend/wasm/gen/NodeJSWrapper.scala

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package amyc.backend.wasm.gen

import amyc.backend.wasm.Modules.*

object NodeJSWrapper :

  def apply(moduleFile: String, module :Module): String =
    s"""function safe_require(module_name) {
       |  try {
       |    return require(module_name);
       |  } catch (e) {
       |    console.log('Error: nodejs module ' + module_name +
       |      ' must be installed (you might want to try: npm install ' + module_name + ')');
       |    process.exit(1);
       |  }
       |}
       |
       |// `Wasm` does **not** understand node buffers, but thankfully a node buffer
       |// is easy to convert to a native Uint8Array.
       |function toUint8Array(buf) {
       |  const u = new Uint8Array(buf.length);
       |  for (let i = 0; i < buf.length; ++i) {
       |    u[i] = buf[i];
       |  }
       |  return u;
       |}
       |// Loads a WebAssembly dynamic library, returns a promise.
       |// imports is an optional imports object
       |function loadWebAssembly(filename, imports) {
       |  // Fetch the file and compile it
       |  const buffer = toUint8Array(require('fs').readFileSync(filename))
       |  return WebAssembly.compile(buffer).then(module => {
       |    return new WebAssembly.Instance(module, imports)
       |  })
       |}
       |
       |const deasync = safe_require('deasync');
       |const rl = require('readline').createInterface({
       |  input: process.stdin,
       |  output: process.stdout
       |});
       |
       |// Newer versions of NodeJS don't seem to buffer line events, so we might lose inputs
       |// when stdin was redirected (e.g. in the automated tests).
       |let inputLines = [];
       |rl.on('line', function(answer) {
       |  inputLines.push(answer);
       |});
       |
       |function waitInput() {
       |  deasync.loopWhile(function(){return inputLines.length <= 0;});
       |  return inputLines.shift();
       |}
       |
       |const log = console.log;
       |const exit = () => process.exit(1);
       |
       |var memory = new WebAssembly.Memory({initial:100});
       |$importObject
       |
       |loadWebAssembly('$moduleFile', importObject).then(function(instance) {
       |""".stripMargin ++
      module.functions.filter(_.result.isEmpty).map { f =>
        s"  instance.exports.${f.name}();\n"
      }.mkString ++
      """  rl.close();
        |}).catch( function(error) {
        |  rl.close();
        |  process.exit(1)
        |})
        |""".stripMargin

  /** JavaScript object containing the values to be imported into the WASM instance.
    * Requires a waitInput(), log(line) and exit() function to be in scope.
    */
  lazy val importObject: String =
    s"""const importObject = {
       |  system: {
       |    mem: memory,
       |
       |    printInt: function(arg) {
       |      log(arg);
       |      0;
       |    },
       |
       |    printString: function(arg) {
       |      var bufView = new Uint8Array(memory.buffer);
       |      var i = arg;
       |      var result = "";
       |      while(bufView[i] != 0) {
       |       result += String.fromCharCode(bufView[i]);
       |       i = i + 1;
       |      }
       |      log(result);
       |      0;
       |    },
       |
       |    readInt: function() {
       |      var res = parseInt(waitInput());
       |      if (isNaN(res)) {
       |        log("Error: Could not parse int");
       |        exit();
       |      } else {
       |        return res;
       |      }
       |    },
       |
       |    // This function has a weird signature due to restrictions of the current WASM format:
       |    // It takes as argument the position that it needs to store the string to,
       |    // and returns the first position after the new string.
       |    readString0: function(memB) {
       |      var s = waitInput();
       |      var size = s.length;
       |      var padding = 4 - size % 4;
       |      var fullString = s + "\u0000".repeat(padding);
       |      var newMemB = memB + size + padding;
       |      var bufView8 = new Uint8Array(memory.buffer);
       |      for (var i = 0; i < fullString.length; i++) {
       |        bufView8[memB + i] = fullString.charCodeAt(i);
       |      }
       |      return newMemB;
       |    }
       |  }
       |};
       |""".stripMargin