codewars / runner

Issue tracker for Code Runner
34 stars 8 forks source link

Add WebAssembly (WebAssembly text format) #29

Open Voileexperiments opened 5 years ago

Voileexperiments commented 5 years ago

Adding wasm language shouldn't be very hard since Node can call wasm binaries trivally, which means the majority of Node environment can be reused.

As far as coding goes wat (WebAssembly text format) is the standard so we can code in wat, and then let it be converted to wasm to be run by Node environment.

Steps:

  1. Install WebAssembly toolkit https://github.com/webassembly/wabt (or https://github.com/mafintosh/webassembly-binary-toolkit for npm)
  2. Setup pipeline to pass solution.wat to wat2wasm to get wasm file
  3. Setup Node script to load compiled wasm file: https://webassembly.github.io/wabt/demo/wat2wasm/

And I think it's done?

Voileexperiments commented 5 years ago

WebAssembly logo (it's CC0 aka effectively public domain).

Voileexperiments commented 5 years ago

@kazk I'm trying to set up the wasm docker but it seems that the docker image linked to in this repo (codewars/base-runner) is completely outdated: it runs on trusty and Node 6 by default.

It's causing problems because trusty only has CMake 2.8 which causes error when building wabt because cmake_parse_arguments is only a built-in in CMake 3.5. Also Node 6 has no wasm support.

I checked against the current runner and it runs on xenial (uname -v gives #28~16.04.1-Ubuntu SMP Fri Jan 18 10:10:51 UTC 2019); xenial's cmake is 3.5.1 so it should be fine.

So how do I get access to the current base runner image and make wasm run off the xenial/Node 10 docker image?

kazk commented 5 years ago

? The new runner doesn't use any of those. The only code from this repository that's still used is the custom frameworks and some of the test output formatters.

So how do I get access to the current base runner image and make wasm run off the xenial/Node 10 docker image?

There's no common base image. If you're just trying to make a proof of concept, then just create anything that works. It doesn't even need to use Docker as long as it runs on Linux and shows how it can be used.

Voileexperiments commented 5 years ago

Okay then.

So as it turns out setting up WAT to JS toolchain is actually surprisingly simple:

  1. install Node
    curl https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add - \
    && apt-add-repository "deb https://deb.nodesource.com/node_11.x $(lsb_release -sc) main" \
    && apt-get update \
    && apt-get install -y nodejs
  2. install AssemblyScript's wabt and binaryen port
npm install -g binaryen wabt
  1. set NODE_PATH (to enable globally installed modules)
    export NODE_PATH=/usr/lib/node_modules:/runner/node_modules
  2. use it in Node like this:
    
    // prepare WAT file
    require('fs').writeFileSync('simple.wat', `(module
    (func (export "addTwo") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add))
    `);

// load WAT as module, validate then convert to binary var userCode = require('fs').readFileSync('simple.wat'); var wabt = require('wabt')(); var module = wabt.parseWat('simple.wat', userCode.toString()); try { module.validate(); } catch(e) { // validation failed throw e; } var wasm = module.toBinary({log: false});

// load the result binary like a normal wasm file var importObj = {}; WebAssembly.instantiate(wasm.buffer, importObj).then(res => { console.log(res.instance.exports.addTwo(1, 2)); // prints 3 });


(I added binaryen too in case someone wants to do other weird stuff with wasm.)

Things to investigate:
- `toBinary` also opens `canonicalize_lebs`, `relocatable` and `write_debug_names` flags from `wat2wasm`. Maybe open them to the test code?
- Needs more code snippets. I took the example from https://webassembly.github.io/wabt/demo/wat2wasm/, and there aren't lots of snippets around
- `wat2wasm` experimental features toggling is not documented but https://github.com/AssemblyScript/wabt.js/issues/2 shows a usage, I haven't put it in the above snippet
- I haven't tested error handling when parsing invalid wat files, it could be very shitty
kazk commented 5 years ago

Do we really need to generate binary format using JS API?

solution.wat:

(module
  (func $add (param i32 i32) (result i32)
    (i32.add
      (get_local 0)
      (get_local 1)))

(export "add" (func $add)))

Generate binary format using wat2wasm (should fail here on error):

wat2wasm solution.wat -o solution.wasm

Test:

const importWasm = require("./import-wasm"); // see below
const { assert } = require("chai");

// not ideal to import in a test case, but works
describe("add", () => {
  it("adds two numbers", async () => {
    const { add } = await importWasm("./solution.wasm");
    assert.strictEqual(add(1, 1), 2);
  });
});

Utility to import WebAssembly file.

// import-wasm.js
const readFile = require("util").promisify(require("fs").readFile);

module.exports = async function importWasm(file, env = defaultEnv()) {
  const bytes = await readFile(file);
  const module = await WebAssembly.compile(bytes);
  return new WebAssembly.Instance(module, { env }).exports;
};

const defaultEnv = () => ({
  memory: new WebAssembly.Memory({ initial: 256 }),
  table: new WebAssembly.Table({
    initial: 0,
    element: "anyfunc",
  }),
});

We can experiment more and decide.

Voileexperiments commented 5 years ago

Yes it can be done that way (it was my original approach), but wat2wasm comes from wabt set and they only provide instructions to build the binaries with CMake, so package manager will not help here. Alternatively it can be grabbed from the release, but that's kinda janky (and I'm not sure if bugs won't be introduced this way).

kazk commented 5 years ago

npm install -g webassembly-binary-toolkit after installing cmake should build it. I haven't tried building image for this yet, but it's been working fine on my machine running Arch Linux.

kazk commented 5 years ago

Do you know the difference between wat2wasm and wasm-as?

wat2wasm: translate from WebAssembly text format to the WebAssembly binary format wabt

wasm-as: Assembles WebAssembly in text format (currently S-Expression format) into binary format (going through Binaryen IR). binaryen

So what does "going through Binaryen IR" do?

Voileexperiments commented 5 years ago

Binaryen IR is a compiler, so it doesn't just translate wat to wasm, it also does code analysis and wasm-specific optimization (one use case is wasm => Binaryen IR => wasm). So it provides options akin to -O in typical compilers.