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

wasm2js output from AssemblyScript input hangs #7245

Open
guest271314 opened this issue Jan 28, 2025 · 8 comments
Open

wasm2js output from AssemblyScript input hangs #7245

guest271314 opened this issue Jan 28, 2025 · 8 comments

Comments

@guest271314
Copy link

I compiled AssemblyScript to WASM, then compiled WASM to JavaScript with wasm2js. The WASM works as expected with wasmtime. The resulting JavaScript hangs when running with node, deno, and bun.

I've done the same process described above with Rust source code, Bytecode Alliance's Javy, and Facebook's Static Hermes; that is, source code to WASM, WASM to JavaScript using wasm2js, then running that JavaScript with WASI support with a JavaScript runtime.

This is the first time I have encountered code output by wasm2js hanging. I don't know if the issue is AssemblyScript source, wasm2js output, or the WASI (preview1) implementation I'm using.

The code

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));
node_modules/.bin/asc --enable simd --exportStart  --config ./node_modules/@assemblyscript/wasi-shim/asconfig.json  module.ts -o module.wasm
echo '4 5' | wasmtime module.wasm
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]

or

wasmtime module.wasm 4 5
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]

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

At the bottom of the JavaScript output by wasm2js

import WASI from "https://raw.githubusercontent.com/guest271314/deno-wasi/refs/heads/runtime-agnostic-nodejs-api/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();

This just hangs until ^C

echo '4 5' | deno -A module.js

@kripken
Copy link
Member

kripken commented Jan 28, 2025

To debug this, I would add some logging in the source, to see where the wasm2js build starts to behave differently.

If you can't figure it out that way, adding lower-level logging using binaryen is an option, like --log-execution.

If you want, attach the wasm file here for us to investigate.

@guest271314
Copy link
Author

Looks like the code hangs somewhere in $131.

deno -A module.js 4 5
asmFunc
array_nth_permutation 4 5
Do we get past label$1 while loop?
Do we get past label$3 while loop?
Before if $88_1
Inside if $88_1
 function $131($0_1, $1_1) {
  console.log("array_nth_permutation", $0_1, $1_1);
  $0_1 = $0_1 | 0;
  $1_1 = $1_1 | 0;
  // ...
  };
  console.log("Do we get past label$1 while loop?");
  // ...
  };
  console.log("Do we get past label$3 while loop?");
  $12_1 = $11_1;
  if (($1_1 | 0) >= (0 | 0)) {
   $88_1 = ($1_1 | 0) < ($11_1 | 0)
  } else {
   $88_1 = 0
  }
  console.log("Before if $88_1");
  if ($88_1) {
   console.log("Inside if $88_1");
   label$8 : while (1) {
    if (($0_1 | 0) > (0 | 0)) {
     $11_1 = ($11_1 | 0) / ($0_1 | 0) | 0;
     $10_1 = ($1_1 - (($1_1 | 0) % ($11_1 | 0) | 0) | 0 | 0) / ($11_1 | 0) | 0;
     $26_1 = $9_1;
     HEAP32[(global$31 + 4 | 0) >> 2] = $26_1;
     $26_1 = $5_1;
     HEAP32[(global$31 + 16 | 0) >> 2] = $26_1;
     $26_1 = $124($26_1 | 0, $10_1 | 0, 1 | 0) | 0;
     HEAP32[(global$31 + 12 | 0) >> 2] = $26_1;
     $126($9_1 | 0, +(+$125($26_1 | 0, 0 | 0))) | 0;
     $1_1 = ($1_1 | 0) % ($11_1 | 0) | 0;
     $0_1 = $0_1 - 1 | 0;
     continue label$8;
     console.log("Do we get past label$8 while loop?");
    }
    break label$8;
   };
  // ...
  }
  global$31 = global$31 + 60 | 0;
  console.log("End of $131");
 }

assemblyscript-wasm2js-hangs.tar.gz

@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]

@kripken
Copy link
Member

kripken commented Jan 29, 2025

I don't see the wasm file in the archive? With the wasm file and a way to run it I could compare wasm to wasm2js.

@guest271314
Copy link
Author

Thought I included the WASM...

I've updated the AssemblyScript code (see AssemblyScript/assemblyscript#2901 (comment)) to fix the part I initially filed this issue for, though the wasm2js still has a lingering issue.

I'm including the AssemblyScrip code, the WASM compiled using asc, and the wasm2js result (I fetched the lastest Binaryen yesterday, version 121).

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

The wasi-shim is from here https://github.com/AssemblyScript/wasi-shim.

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

With wasmtime I get the expected result

echo '12 2' | wasmtime module.wasm
2 of 479001599 (0-indexed, factorial 479001600) => [0,1,2,3,4,5,6,7,8,10,9,11]

With wasm2js JavaScript and WASI support using https://raw.githubusercontent.com/guest271314/deno-wasi/refs/heads/runtime-agnostic-nodejs-api/wasi.js, also included as wasi.js, in pertinent part

//import * as wasi_snapshot_preview1 from 'wasi_snapshot_preview1';
// ...
 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, like I get with wasmtime.

assemblyscript-wasm-wasm2js.tar.gz

@kripken
Copy link
Member

kripken commented Jan 30, 2025

I get this error with node 20:

$ echo '12 2' | node --no-warnings module.js
assemblyscript-wasm-wasm2js/module.js:4727
import WASI from "./wasi.js";
^^^^^^

SyntaxError: Cannot use import statement outside a module

@guest271314
Copy link
Author

That's Node.js clinging on to CommonJS. I'm running node nightly v24.0.0-nightly20250128532fff6b27. Even in version 20 you should be able to run standardized ECMAScript Modules with --experimental-default-type=module.

@guest271314
Copy link
Author

https://nodejs.org/en/blog/release/v20.10.0

2023-11-22, Version 20.10.0 'Iron' (LTS), @targos
Notable Changes
--experimental-default-type flag to flip module defaults
The new flag --experimental-default-type can be used to flip the default module system used by Node.js.

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

No branches or pull requests

2 participants