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

Closed
guest271314 opened this issue Jan 27, 2025 · 15 comments
Closed

Why is wasm2js from AssemblyScript input hanging? #2901

guest271314 opened this issue Jan 27, 2025 · 15 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.

@guest271314
Copy link
Author

I'm not sure the output is different using JavaScript compiled using wasm2js.

@guest271314
Copy link
Author

I do think there's a bug in AssemblyScript somewhere.

Consider the same algorithm using AssemblyScript

echo '13 2' | wasmtime module.wasm
Error: failed to run main module `module.wasm`

Caused by:
    0: failed to invoke command default
    1: error while executing at wasm backtrace:
           0: 0x3683 - <unknown>!<wasm function 131>
           1: 0x3b77 - <unknown>!<wasm function 132>
           2: 0x200d - <unknown>!<wasm function 101>
    2: wasm trap: integer divide by zero


compare Bytecode Alliance Javy output for factorial 13

echo '13 2' | wasmtime --preload javy_quickjs_provider_v3=../plugin.wasm ../nm_javy_permutations.wasm
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

@guest271314
Copy link
Author

@CountBleck Follow-up question. Let's say I'm not using WASI. How do I write the values to memory in WASM so I can then read those values from memory in JavaScript and/or using wasmtime or wasmer?

@CountBleck
Copy link
Member

The memory is exported by default under the name "memory". Use Wasmtime/Wasmer's APIs for accessing that export and manipulate it through there.

@guest271314
Copy link
Author

This is what I tried. Compiles though throws

export function array_nth_permutation(len: i64, n: i64): void { 
  let lex = n; 
  let b: i64[] = []; 
  for (let x: i64 = 0; x < len; x++) {
    b.push(x);
  }
  const res: i64[] = []; 
  let i: i64 = 1;
  let f: i64 = 1;

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

  let fac = f;
  // if the permutation number is within range
  if (n >= 0 && n < f) {
    for (; len > 0; len--) {
      f /= len;
      i = (n - n % f) / f; // Math.floor(n / f);
      res.push(<i64> b.splice(<i32> i, 1)[0]);
      n %= f;
    }
  }

  for (let i: i32 = 0; i < res.length; i++) {
    store<i64>(i, res[i]);
  }
}
node_modules/.bin/asc fac.ts -o fac.wasm
import { readFile } from "node:fs/promises";
import process from "node:process";

try {
  const embeddedModule = await compileModule("./fac.wasm");
  const result = await runWasm(embeddedModule);
} catch (e) {
  process.stdout.write(e.message, "utf8");
} finally {
  process.exit();
}

async function compileModule(wasmPath) {
  const bytes = await readFile(new URL(wasmPath, import.meta.url));
  return WebAssembly.compile(bytes);
}

async function runWasm(embeddedModule) {
 
  try {
    const memory = new WebAssembly.Memory({ initial: 1 }); 
    const instance = await WebAssembly.instantiate(
      embeddedModule,
      {memory: memory,
        env: {
          abort: (...args) => console.log(...args),
          
        },
      },
    );
    // Pass numbers to WASM
    instance.exports.array_nth_permutation(4, 5);
    // Read values from memory
    console.log(
     // new TextDecoder().decode(new Uint8Array(instance.exports.memory.buffer)),
    );

    return;
  } catch (e) {
    if (e instanceof WebAssembly.RuntimeError) {
      if (e) {
        throw new Error(e);
      }
    }
    throw e;
  }
}
bun fac.js
Invalid argument type in ToBigInt operation

@guest271314
Copy link
Author

@CountBleck You wrote about using store() over here #2869 (comment).

I'm curious how to make use of that AssemblyScript capability for the option to do something like exports.array_nth_permutation(4, 5), write the integers into memory in AssemblyScript, and then read the WebAssembly.Memory object data into a Uint8Array in JavaScript.

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