Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why is wasm2js from AssemblyScript input hanging? #2901

Open
guest271314 opened this issue Jan 27, 2025 · 9 comments
Open

Why is wasm2js from AssemblyScript input hanging? #2901

guest271314 opened this issue Jan 27, 2025 · 9 comments
Labels

Comments

@guest271314
Copy link

Question

I've compiled Rust source code to WASM, then compiled that WASM to JavaScript with wasm2js, then used a WASI implementation from here https://gitlab.com/-/snippets/4782260 to execute the JavaScript output by wasm2js.

// ...
import WASI from "./wasi.js";

let wasi = new WASI();
var retasmFunc = asmFunc({
  "wasi_snapshot_preview1": wasi.exports,
});
export var memory = retasmFunc.memory;
export var _start = retasmFunc._start;
export var __main_void = retasmFunc.__main_void;

wasi.memory = memory;
_start();
 echo '3 4' | node permutations-rust.js
4 of 5 (0-indexed, factorial 6) => [2, 0, 1]

I've done something similar with Bytecode Alliance's Javy, and Facebook's Static Hermes.

Here either arguments or stdin is read and passed to the relevant function

if (process.argv.length > 1) {
  input = process.argv.at(-2);
  lex = process.argv.at(-1);
} else {
  let stdin = process.stdin;
  let buffer = new ArrayBuffer(64);
  let n: number = stdin.read(buffer);
  if (n > 0) {
    let data = String.UTF8.decode(buffer);
    input = data.slice(0, data.indexOf(" "));
    lex = data.slice(data.indexOf(" "), data.length);
  }
}

input = input.trim();
lex = lex.trim();

if (<i32> parseInt(input) < 2 || <i32> parseInt(lex) < 0) {
  process.stdout.write(`Expected n > 2, m >= 0, got ${input}, ${lex}`); // eval(input)
  process.exit(1);
}

array_nth_permutation(<i32> parseInt(input), <i32> parseInt(lex));
// ...
function asmFunc(imports) {
 var buffer = new ArrayBuffer(65536);
 var HEAP8 = new Int8Array(buffer);
 var HEAP16 = new Int16Array(buffer);
 var HEAP32 = new Int32Array(buffer);
 var HEAPU8 = new Uint8Array(buffer);
 var HEAPU16 = new Uint16Array(buffer);
 var HEAPU32 = new Uint32Array(buffer);
 var HEAPF32 = new Float32Array(buffer);
 var HEAPF64 = new Float64Array(buffer);
 var Math_imul = Math.imul;
 var Math_fround = Math.fround;
 var Math_abs = Math.abs;
 var Math_clz32 = Math.clz32;
 var Math_min = Math.min;
 var Math_max = Math.max;
 var Math_floor = Math.floor;
 var Math_ceil = Math.ceil;
 var Math_trunc = Math.trunc;
 var Math_sqrt = Math.sqrt;
 var wasi_snapshot_preview1 = imports.wasi_snapshot_preview1;
 var fimport$0 = wasi_snapshot_preview1.args_sizes_get;
 var fimport$1 = wasi_snapshot_preview1.fd_write;
 var fimport$2 = wasi_snapshot_preview1.proc_exit;
 var fimport$3 = wasi_snapshot_preview1.args_get;
 var fimport$4 = wasi_snapshot_preview1.fd_read;
 console.log("We get here...");
// ...
 function $131($0_1, $1_1) {
  console.log($0_1, $1_1);
// ...
 echo '7 8' | node --no-warnings module.js
We get here...
7 8

The resulting JavaScript from the AssemblyScript source to WASM then to JavaScript hangs. Why? And how to fix this?

@CountBleck
Copy link
Member

CountBleck commented Jan 27, 2025

I'm confused...what's the source code?

@guest271314
Copy link
Author

module.ts


export function array_nth_permutation(len: i32, n: i32): void { //Array<f64>
  let lex = n;
  let b: number[] = []; // copy of the set a.slice()
  for (let x: i32 = 0; x < len; x++) {
    b[x] = x;
  }
  // let len = a; // length of the set
  const res: number[] = []; // return value, undefined
  let i: i32 = 1;
  let f: i32 = 1;

  // compute f = factorial(len)
  for (; i <= len; i++) {
    f *= i;
  }

  let fac = f;
  // if the permutation number is within range
  if (n >= 0 && n < f) {
    // start with the empty set, loop for len elements
    // let result_len = 0;
    for (; len > 0; len--) {
      // determine the next element:
      // there are f/len subsets for each possible element,
      f /= len;
      // a simple division gives the leading element index
      i = (n - n % f) / f; // Math.floor(n / f);
      // alternately: i = (n - n % f) / f;
      // res[(result_len)++] = b[i];
      // for (let j = i; j < len; j++) {
      //   b[j] = b[j + 1]; // shift elements left
      // }
      res.push(b.splice(i, 1)[0]);
      // reduce n for the remaining subset:
      // compute the remainder of the above division
      n %= f;
      // extract the i-th element from b and push it at the end of res
    }

    let result: string = "[";
    for (let x: i32 = 0; x < res.length; x++) {
      let m: string = res[x].toString();
      let i: i32 = 0;
      do {
        result += m[i];
        i++;
      } while (m[i] !== ".");
      if (x < res.length -1) {
        result += ",";
      }
    }
    result += "]";
    process.stdout.write(
      `${lex} of ${fac - 1} (0-indexed, factorial ${fac}) => ${result}\n`,
    );

    process.exit(0);
  } else {
    if (n === 0) {
      process.stdout.write(`${n} = 0`);
    }
    process.stdout.write(`${n} >= 0 && ${n} < ${f}: ${n >= 0 && n < f}`);
    process.exit(1);
  }
}

let input: string = "0";
let lex: string = "0";

if (process.argv.length > 1) {
  input = process.argv.at(-2);
  lex = process.argv.at(-1);
} else {
  let stdin = process.stdin;
  let buffer = new ArrayBuffer(64);
  let n: number = stdin.read(buffer);
  if (n > 0) {
    let data = String.UTF8.decode(buffer);
    input = data.slice(0, data.indexOf(" "));
    lex = data.slice(data.indexOf(" "), data.length);
  }
}

input = input.trim();
lex = lex.trim();

if (<i32> parseInt(input) < 2 || <i32> parseInt(lex) < 0) {
  process.stdout.write(`Expected n > 2, m >= 0, got ${input}, ${lex}`); // eval(input)
  process.exit(1);
}

array_nth_permutation(<i32> parseInt(input), <i32> parseInt(lex));

Compiled with

node_modules/.bin/asc --enable simd --exportStart  --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json  module.ts -o module.wasm

@guest271314
Copy link
Author

Then using Binaryen's wasm2js to get the JavaScript output

node_modules/.bin/wasm2js module.wasm --enable-bulk-memory --enable-nontrapping-float-to-int  -o module.js

I do the same or similar with Bytecode Alliance's Javy, Rust, and Facebook's Static Hermes https://gist.github.com/guest271314/b10eac16be88350ffcd19387e22ad4d5. Pardon, this is the WASI implementation I'm using https://github.com/guest271314/deno-wasi/blob/runtime-agnostic-nodejs-api/wasi.js.

@guest271314
Copy link
Author

I think I figured out the issue in the source AssemblyScript code

I included this part in the algorithm specifically for AssemblyScript because AssemblyScript is representing integers as decimals

    let result: string = "[";
    for (let x: i32 = 0; x < res.length; x++) {
      let m: string = res[x].toString();
      let i: i32 = 0;
      do {
        result += m[i];
        i++;
      } while (m[i] !== ".");
      if (x < res.length -1) {
        result += ",";
      }
    }
    result += "]";

That is, when I replace that part with

    let result: string = `[${res}]`;
    /*
    "[";
    for (let x: i32 = 0; x < res.length; x++) {
      let m: string = res[x].toString();
      let i: i32 = 0;
      do {
        result += m[i];
        i++;
      } while (m[i] !== ".");
      if (x < res.length -1) {
        result += ",";
      }
    }
    result += "]";
    */

and run with wasmtime this is the result, where I'm expecting integers reflecting indexes, not decimals

echo '4 5' | wasmtime module.wasm
5 of 23 (0-indexed, factorial 24) => [0.0,3.0,2.0,1.0]

However, if I use that input WASM to wasm2js I get zeros - with the decimal

import WASI from "./wasi.js";
import fs from "node:fs";
let wasi = new WASI({
  args:[, '4', '5']
});
var memasmFunc = new ArrayBuffer(0);
var retasmFunc = asmFunc({
  "wasi_snapshot_preview1": {
    memory: { buffer: memasmFunc },
    ...wasi.exports,
  }
});

export var array_nth_permutation = retasmFunc.array_nth_permutation;
export var memory = retasmFunc.memory;
export var _start = retasmFunc._start;

wasi.memory = memory;
_start();
node --no-warnings module.js
5 of 23 (0-indexed, factorial 24) => [0.0,.0,.0,.0]

@guest271314
Copy link
Author

How do we get integers instead of decimals in AssemblyScript?

@CountBleck
Copy link
Member

CountBleck commented Jan 29, 2025

const res: i32[] = [];, not const res: number[] = [];.

Also, you can do i = n / f; instead of i = (n - n % f) / f;

@guest271314
Copy link
Author

That's it. Also requires res.push(<i32>b.splice(i, 1)[0]);

@CountBleck
Copy link
Member

Ah, you can have b be an i32[] too.

Anyhow, in AS, number is an alias to f64.

@guest271314
Copy link
Author

Alright, I am able to compile the source to JavaScript using tsc in the tsd directory with a couple // @ts-ignores for AssemblyScript's read() and String.UTF8.decode(), and creating node in types directory and copying @types/node into that folder. This is how the AssemblyScript code looks now.

Works with wasmtime and JavaScript runtimes with WASI support.

import "assemblyscript/std/portable.js";
import process from "node:process";
// array_nth_permutation
export function array_nth_permutation(len: i32, n: i32): void { //Array<f64>
  let lex = n; // length of the set
  let b: i32[] = []; // copy of the set a.slice()
  for (let x: i32 = 0; x < len; x++) {
    b.push(x);
  }
  const res: i32[] = []; // return value, undefined
  let i: i32 = 1;
  let f: i32 = 1;

  // compute f = factorial(len)
  for (; i <= len; i++) {
    f *= i;
  }

  let fac = f;
  // if the permutation number is within range
  if (n >= 0 && n < f) {
    // start with the empty set, loop for len elements
    // let result_len = 0;
    for (; len > 0; len--) {
      // determine the next element:
      // there are f/len subsets for each possible element,
      f /= len;
      // a simple division gives the leading element index
      i = (n - n % f) / f; // Math.floor(n / f);
      // alternately: i = (n - n % f) / f;
      // res[(result_len)++] = b[i];
      // for (let j = i; j < len; j++) {
      //   b[j] = b[j + 1]; // shift elements left
      // }
      res.push(<i32>b.splice(i, 1)[0]);
      // reduce n for the remaining subset:
      // compute the remainder of the above division
      n %= f;
      // extract the i-th element from b and push it at the end of res
    }

    let result: string = `[${res}]`;
    /*
    "[";
    for (let x: i32 = 0; x < res.length; x++) {
      let m: string = res[x].toString();
      let i: i32 = 0;
      do {
        result += m[i];
        i++;
      } while (m[i] !== ".");
      if (x < res.length -1) {
        result += ",";
      }
    }
    result += "]";
    */
    process.stdout.write(
      `${lex} of ${fac - 1} (0-indexed, factorial ${fac}) => ${result}\n`,
    );

    process.exit(0);
  } else {
    if (n === 0) {
      process.stdout.write(`${n} = 0`);
    }
    process.stdout.write(`${n} >= 0 && ${n} < ${f}: ${n >= 0 && n < f}`);
    process.exit(1);
  }
}

let input: string = "0";
let lex: string = "0";

if (process.argv.length > 1) {
  input = process.argv.at(-2);
  lex = process.argv.at(-1);
} else {
  let stdin = process.stdin;
  let buffer = new ArrayBuffer(64);
  // @ts-ignore
  let n: number = stdin.read(buffer);
  if (n > 0) {
    // @ts-ignore
    let data = String.UTF8.decode(buffer);
    input = data.slice(0, data.indexOf(" "));
    lex = data.slice(data.indexOf(" "), data.length);
  }
}

input = input.trim();
lex = lex.trim();

if (<i32> parseInt(input) < 2 || <i32> parseInt(lex) < 0) {
  process.stdout.write(`Expected n > 2, m >= 0, got ${input}, ${lex}`); // eval(input)
  process.exit(1);
}

array_nth_permutation(<i32> parseInt(input), <i32> parseInt(lex));
wasmtime module.wasm 12 2
2 of 479001599 (0-indexed, factorial 479001600) => [0,1,2,3,4,5,6,7,8,10,9,11]

and compile to JavaScript using Binaryen's wasm2js, that has an issue

// ...
 return {
  "array_nth_permutation": $126, 
  "memory": Object.create(Object.prototype, {
   "grow": {
    "value": __wasm_memory_grow
   }, 
   "buffer": {
    "get": function () {
     return buffer;
    }
    
   }
  }), 
  "_start": $96
 };
}

import WASI from "./wasi.js";
import fs from "node:fs";
let wasi = new WASI();
var memasmFunc = new ArrayBuffer(0);
var retasmFunc = asmFunc({
  "wasi_snapshot_preview1": {
    memory: { buffer: memasmFunc },
    ...wasi.exports,
  }
});

export var array_nth_permutation = retasmFunc.array_nth_permutation;
export var memory = retasmFunc.memory;
export var _start = retasmFunc._start;

wasi.memory = memory;
_start();
echo '12 2' | node --no-warnings module.js
2 of 49001599 (0-indexed, factorial 49001600) => [0,1,2,3,4,5,6,7,8,10,911]

See that 911? Should be 9,11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants