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

Compile to asm.js #2869

Open
listepo opened this issue Sep 21, 2024 · 16 comments
Open

Compile to asm.js #2869

listepo opened this issue Sep 21, 2024 · 16 comments

Comments

@listepo
Copy link

listepo commented Sep 21, 2024

Feature suggestion

It would be nice to be able compile to asm.js

@HerrCai0907
Copy link
Member

Isn't wasm the successor to asm.js? Is there more background to support asm.js instead of using wasm?

@listepo
Copy link
Author

listepo commented Sep 25, 2024

Yes facebook/hermes#429 (comment)

@CountBleck
Copy link
Member

CountBleck commented Sep 29, 2024 via email

@guest271314
Copy link

This will emit JavaScript

bun build module.ts

This will cache the JavaScript representation of the file that Deno executes

deno -A module.ts

Node.js has built in strip types, and can execute AssemblyScript directly with --experimental-transform-types.

@CountBleck
Copy link
Member

CountBleck commented Feb 7, 2025

That's not the same thing as compiling to asm.js, because it prevents the usage of AS-specific features and may not have the same performance, depending on the code.

The author was specifically interested in Static Hermes, and your approach wouldn't be particularly useful (they're already able to execute JS).

Still, asm.js is becoming less and less useful by the day, so there's no sense in supporting it when wasm2js does so already.

@guest271314
Copy link

That's not the same thing as compiling to asm.js

Yes, it is.

There's no way you can tell the difference between the input or output of Static Hermes compiled to native executable, Static Hermes with -typed option compiled to WASM with Emscripten, that is, JavaScript, asm.js format, Static Hermes compiled to WASM using WASI-SDK, Bun executing WASM compiled from AssemblyScript, or the stripped or transformed types executing AssemblyScript directly with node, deno, bun, or compiling JavaScript to WASM using Bytecode Alliance Javy, and compiling each to JavaScript with wasm2js.

I've compiled the same algorithm using each of the above. You cannout blind test pick which is which.

@CountBleck
Copy link
Member

If you're able to do all of those, then there's no semantic difference (maybe a performance difference but idk).

However, if you use AS-specific features (load, store, etc.), you won't be able to run your code in Hermes, Bun, Deno, or Node directly without compiling it to Wasm with asc.

@guest271314
Copy link

However, if you use AS-specific features (load, store, etc.), you won't be able to run your code in Hermes, Bun, Deno, or Node directly without compiling it to Wasm with asc.

That's a technically incorrect assumption. deno, bun, and node can execute AssemblyScript directly. The types are stripped or "transformed" if you prefer. You lose nothing. The code becomes JavaScript.

printf '13 2' | bun run  module.ts
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]
printf '13 2' | bun module.ts
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]
printf '13 2' | node --experimental-transform-types --no-warnings module.ts
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

Bun also executes .wasm directly

printf '13 2' | bun module.wasm
13,  22 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

That's directly executing AssemblyScript source code.

//import "./assemblyscript/std/portable.js";
//import process from "node:process";
// array_nth_permutation
/*
declare abstract class Navigator {
  readonly userAgent: "AssemblyScript Version 0.27.32";
}

var navigator:Navigator;
*/
// @ts-ignore
//import process from "node:process";
//import { readSync } from "node:fs";
//process.stdin.readSync = readSync;
// @ts-ignore
// import { readSync } from "node:fs";
// import { readSync } from "node_modules/assemblyscript/std/types/node/fs.d.ts";
//import { wasi_process } from "wasi-shim/assembly/wasi_process.ts";
//process.stdout.write(`${wasi_process.read}`);
/*
echo '4 5' | node --no-warnings --experimental-transform-types --import ./preprocess-module.ts module.ts
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]

bun -r ./preprocess-module.ts module.ts 13 2
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

echo '13 2' | bun -r ./preprocess-module.ts module.ts
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]</pre>

node --no-warnings --experimental-transform-types --import ./preprocess-module.ts module.ts 13 2
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

*/
//import "./node_modules/@assemblyscript/wasi-shim/assembly/wasi_process.ts";
if (process.platform != "wasm" && 1 || 0) {
  // process.stdout.write(process.platform);

  //import("node:fs")readSync } from "node:fs";
  //process.stdin.readSync = readSync;
}
export function array_nth_permutation(len: i64, n: i64): void { //Array<f64>
  let lex = n; // length of the set
  let b: i64[] = []; // copy of the set a.slice()
  for (let x: i64 = 0; x < len; x++) {
    b.push(x);
  }
  const res: i64[] = []; // return value, undefined
  let i: i64 = 1;
  let f: i64 = 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);
      //process.stdout.write(`i: ${i} `);
      // 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(<i64> b.splice(<i32> i, 1)[0]);
      // reduce n for the remaining subset:
      // compute the remainder of the above division
      n %= f;
      //process.stdout.write(`n: ${n} `);
      // 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}) => [`,
    );

    for (let z: i64 = 0; z < res.length; z++) {
      process.stdout.write(`${res.at(<i32> z)}`);
      if (z < res.length - 1) {
        process.stdout.write(",");
      }
    }
    process.stdout.write("]\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";
let runtime: string = process.platform;
let bool: i32 = runtime === "wasm" ? 1 : 0;
// process.stdout.write(runtime);
/*
function require(mod: string): i32 {
  return 0;
}

function readSync(buffer: ArrayBuffer):i32 {
  // @ts-ignore
  return runtime === "wasm" ? stdin.read(buffer) : require("fs").readSync(0, buffer);
}
*/
if (bool > 0) {
  // process.stdout.write("AssemblyScript Version 0.27.32");
} else {
  // @ts-ignore
  // process.stdout.write(runtime);
}

if (process.argv.length >= 3) {
  input = process.argv.at(-2);
  lex = process.argv.at(-1);
} else {
  //let stdin = process.stdin;
  let buffer = new Uint8Array(64);
  // let view = new DataView(buffer);
  // @ts-ignore
  /*
error: Uncaught (in promise) TypeError: The "buffer" argument must be an instance of Buffer, TypedArray, or DataView. Received an instance of ArrayBuffer
  let n: i32 = process.stdin.readSync(0, buffer); // readSync(0, buffer);

ERROR TS2322: Type '~lib/dataview/DataView' is not assignable to type '~lib/arraybuffer/ArrayBuffer'.

  */
  let n: i32 = process.stdin.readSync(0, buffer); // readSync(0, buffer);
  if (n > 0) {
    let data: string = "";
    for (let i: i32 = 0; i < n; i++) {
      data += String.fromCodePoint(buffer[i]);
    }
    // @ts-ignore
    //let data = String.UTF8.decode(buffer);
    input = data.slice(0, data.indexOf(" "));
    lex = data.slice(data.indexOf(" "), data.length);
    //process.stdout.write(`${input}, ${lex}`);
  }
}

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));

That winds up looking something like this, depending on the runtime that strips or transforms the types

bun build --no-bundle module.ts

import"./preprocess-module.ts";
if (process.platform != "wasm" && 1 || 0) {
}
export function array_nth_permutation(len, n) {
  let lex = n;
  let b = [];
  for (let x = 0;x < len; x++) {
    b.push(x);
  }
  const res = [];
  let i = 1;
  let f = 1;
  for (;i <= len; i++) {
    f *= i;
  }
  let fac = f;
  if (n >= 0 && n < f) {
    for (;len > 0; len--) {
      f /= len;
      i = (n - n % f) / f;
      res.push(b.splice(i, 1)[0]);
      n %= f;
    }
    process.stdout.write(`${lex} of ${fac - 1} (0-indexed, factorial ${fac}) => [`);
    for (let z = 0;z < res.length; z++) {
      process.stdout.write(`${res.at(z)}`);
      if (z < res.length - 1) {
        process.stdout.write(",");
      }
    }
    process.stdout.write(`]
`);
    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 = "0";
let lex = "0";
let runtime = process.platform;
let bool = runtime === "wasm" ? 1 : 0;
if (bool > 0) {
} else {
}
if (process.argv.length >= 3) {
  input = process.argv.at(-2);
  lex = process.argv.at(-1);
} else {
  let buffer = new Uint8Array(64);
  let n = process.stdin.readSync(0, buffer);
  if (n > 0) {
    let data = "";
    for (let i = 0;i < n; i++) {
      data += String.fromCodePoint(buffer[i]);
    }
    input = data.slice(0, data.indexOf(" "));
    lex = data.slice(data.indexOf(" "), data.length);
  }
}
input = input.trim();
lex = lex.trim();
if (parseInt(input) < 2 || parseInt(lex) < 0) {
  process.stdout.write(`Expected n > 2, m >= 0, got ${input}, ${lex}`);
  process.exit(1);
}
array_nth_permutation(parseInt(input), parseInt(lex));

or this

cat preprocess-module.ts module.ts | deno -A - 15 5
5 of 1307674367999 (0-indexed, factorial 1307674368000) => [0,1,2,3,4,5,6,7,8,9,10,11,14,13,12]

cat "$HOME/.cache/cat "$HOME/.cache/deno/gen/file$PWD/\$deno\$stdin.mts.js"

import process from "node:process";
import { readSync } from "node:fs";
process.stdin.readSync = readSync;
/*
if (navigator.userAgent.includes("Deno")) {
  import("./module.ts")
}
*/ import "./preprocess-module.ts";
//import "./assemblyscript/std/portable.js";
//import process from "node:process";
// array_nth_permutation
/*
declare abstract class Navigator {
  readonly userAgent: "AssemblyScript Version 0.27.32";
}

var navigator:Navigator;
*/ // @ts-ignore
//import process from "node:process";
//import { readSync } from "node:fs";
//process.stdin.readSync = readSync;
// @ts-ignore
// import { readSync } from "node:fs";
// import { readSync } from "node_modules/assemblyscript/std/types/node/fs.d.ts";
//import { wasi_process } from "wasi-shim/assembly/wasi_process.ts";
//process.stdout.write(`${wasi_process.read}`);
/*
echo '4 5' | node --no-warnings --experimental-transform-types --import ./preprocess-module.ts module.ts
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]

bun -r ./preprocess-module.ts module.ts 13 2
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

echo '13 2' | bun -r ./preprocess-module.ts module.ts
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]</pre>

node --no-warnings --experimental-transform-types --import ./preprocess-module.ts module.ts 13 2
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

*/ //import "./node_modules/@assemblyscript/wasi-shim/assembly/wasi_process.ts";
if (process.platform != "wasm" && 1 || 0) {
// process.stdout.write(process.platform);
//import("node:fs")readSync } from "node:fs";
//process.stdin.readSync = readSync;
}
export function array_nth_permutation(len, n) {
  let lex = n; // length of the set
  let b = []; // copy of the set a.slice()
  for(let x = 0; x < len; x++){
    b.push(x);
  }
  const res = []; // return value, undefined
  let i = 1;
  let f = 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);
      //process.stdout.write(`i: ${i} `);
      // 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;
    //process.stdout.write(`n: ${n} `);
    // 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}) => [`);
    for(let z = 0; z < res.length; z++){
      process.stdout.write(`${res.at(z)}`);
      if (z < res.length - 1) {
        process.stdout.write(",");
      }
    }
    process.stdout.write("]\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 = "0";
// ...

or this

node --no-warnings node-strip-types.js module.ts transform
import "./preprocess-module.ts";
if (process.platform != "wasm" && 1 || 0) {}
export function array_nth_permutation(len, n) {
    let lex = n;
    let b = [];
    for(let x = 0; x < len; x++){
        b.push(x);
    }
    const res = [];
    let i = 1;
    let f = 1;
    for(; i <= len; i++){
        f *= i;
    }
    let fac = f;
    if (n >= 0 && n < f) {
        for(; len > 0; len--){
            f /= len;
            i = (n - n % f) / f;
            res.push(b.splice(i, 1)[0]);
            n %= f;
        }
        process.stdout.write(`${lex} of ${fac - 1} (0-indexed, factorial ${fac}) => [`);
        for(let z = 0; z < res.length; z++){
            process.stdout.write(`${res.at(z)}`);
            if (z < res.length - 1) {
                process.stdout.write(",");
            }
        }
        process.stdout.write("]\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 = "0";
let lex = "0";
let runtime = process.platform;
let bool = runtime === "wasm" ? 1 : 0;
if (bool > 0) {} else {}
if (process.argv.length >= 3) {
    input = process.argv.at(-2);
    lex = process.argv.at(-1);
} else {
    let buffer = new Uint8Array(64);
    let n = process.stdin.readSync(0, buffer);
    if (n > 0) {
        let data = "";
        for(let i = 0; i < n; i++){
            data += String.fromCodePoint(buffer[i]);
        }
        input = data.slice(0, data.indexOf(" "));
        lex = data.slice(data.indexOf(" "), data.length);
    }
}
input = input.trim();
lex = lex.trim();
if (parseInt(input) < 2 || parseInt(lex) < 0) {
    process.stdout.write(`Expected n > 2, m >= 0, got ${input}, ${lex}`);
    process.exit(1);
}
array_nth_permutation(parseInt(input), parseInt(lex));


//# sourceURL=module.ts

Using WASM compiled from JavaScript with Javy (depends on QuickJS Rust crate)

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

Static Hermes with typed option compiled to native executable, with clang or gcc, with emitted C, WASM with WASI-SDK, calling .wasm from JavaScript with Node.js, Deno, or Bun, or any runtime that support WebAssembly, using a WASI runtime agnostic implementation written in JavaScript that achieves the same result in Node.js, Deno, or Bun

./wasm-standalone-test.sh fopen.ts
../hermes/include/hermes/VM/static_h.h:334:2: warning: "JS exceptions are currenly broken with WASI" [-W#warnings]
  334 | #warning "JS exceptions are currenly broken with WASI"
      |  ^
1 warning generated.
total 17M
-rwxrwxr-x 1 user user  69K Jan 11 21:24 fopen
-rw-rw-r-- 1 user user  44K Jan 11 21:24 fopen.c
-rw-rw-r-- 1 user user  358 Jan 11 21:24 fopen.js
-rw-rw-r-- 1 user user  12K Jan 11 21:24 fopen.o
-rw-rw-r-- 1 user user 2.9K Jan 11 21:24 fopen.ts
-rwxrwxr-x 1 user user 1.5M Jan 11 21:24 fopen.wasm
-rw-rw-r-- 1 user user  15M Jan 11 21:24 fopen.wat
-rw-rw-r-- 1 user user  38K Jan 11 21:24 wasi.js
echo '13 2' | out/fopen
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]
echo '13 2' | wasmer ./out/fopen.wasm
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]
printf '13 2' | ./out/fopen
2 of 6227020799 (0-indexed, factorial 6227020800) => [0,1,2,3,4,5,6,7,8,9,11,10,12]

I don't think there is any way for you to accurately pick which JavaScript engine or WebAssembly engine, or native executable, nor AssemblyScript, nor TypeScript, nor JavaScript is which given the initial commitment to write the same algorithm in an agnostic manner.

I invite you to accurately discern based on any criteria you set out in writing, which runtime or native executable is executing the given algorithm with input as arguments or from stdin.

Sources:

@guest271314
Copy link

Re

If you're able to do all of those, then there's no semantic difference (maybe a performance difference but idk).

We have the technology, we can figure that out.

Just write out the candidates, the test parameters, and so forth. Bytecode Alliance Javy, AssemblyScript, Hermes and Static Hermes can be compiled to WASM - essentially using JavaScript source code. Each supports WASI.

Node.js is slowest running TypeScript directly TypeScript .ts file execution benchmarks for Deno, Bun, Node.js.

My interest is in using JavaScript as source code for cross-compilation. That requires a lot of testing in various runtimes, engines, and testing cross-compilation to native executable, WASM, and other programming languages.

I think TypeScript is sold in part as a "superset" of JavaScript, thus AssemblyScript must be capable of being compiled or tranpsiled or transformed or stipped of types if you prefer - to JavaScript - by any means necessary.

@CountBleck
Copy link
Member

That's a technically incorrect assumption.

I conceded that if you're able to use your AS code on those runtimes (i.e. your code is completely compatible with TypeScript/JavaScript concepts), then you'll observe no semantic difference.

However, if you have code like the following example, it won't work:

const LENGTH = 1024
const scratch = memory.data(LENGTH)

store<i32>(1, 1, scratch)
for (let i = 2; i < LENGTH; i++) {
  store<i32>(
    i,
    load<i32>(i - 1, scratch) + load<i32>(i - 2, scratch),
    scratch
  )
}

(The example isn't exactly representative of a real-world scenario, but here's something in the wild that won't work with your approach)

@CountBleck
Copy link
Member

My interest is in using JavaScript as source code for cross-compilation. That requires a lot of testing in various runtimes, engines, and testing cross-compilation to native executable, WASM, and other programming languages.

You might also be interested in Porffor...they definitely have JS spec compatibility as their primary goal.

thus AssemblyScript must be capable of being compiled or tranpsiled or transformed or stipped of types if you prefer - to JavaScript - by any means necessary.

AS is supposed to seem familiar to web devs; it doesn't provide strict JS/TS compatibility guarantees. If you want, you can try to polyfill AS-specific behavior, but it'll be difficult to deal with things like changetype<usize>(someArrayOrObject) (converting an object to its underlying pointer)...

@guest271314
Copy link

However, if you have code like the following example, it won't work:

const LENGTH = 1024
const scratch = memory.data(LENGTH)

store(1, 1, scratch)
for (let i = 2; i < LENGTH; i++) {
store(
i,
load(i - 1, scratch) + load(i - 2, scratch),
scratch
)
}

I think that particular example is based on how you have written memory.ts.

import { memcmp, memmove, memset } from "./util/memory";
import { E_NOTIMPLEMENTED } from "./util/error";

/** Memory manager interface. */
export namespace memory {

  /** Gets the size of the memory in pages. */
  // @ts-ignore: decorator
  @builtin
  export declare function size(): i32;

  /** Grows the memory by the given size in pages and returns the previous size in pages. */
  // @ts-ignore: decorator
  @unsafe @builtin
  export declare function grow(pages: i32): i32;

  /** Fills a section in memory with the specified byte value. */
  // @ts-ignore: decorator
  @unsafe @builtin
  export function fill(dst: usize, c: u8, n: usize): void {
    memset(dst, c, n); // fallback if "bulk-memory" isn't enabled
  }

  /** Copies a section of memory to another. Has move semantics. */
  // @ts-ignore: decorator
  @unsafe @builtin
  export function copy(dst: usize, src: usize, n: usize): void {
    memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled
  }

  export namespace atomic {

    // @ts-ignore: decorator
    @unsafe @builtin
    export declare function wait32(ptr: usize, expected: i32, timeout: i64): AtomicWaitResult;

    // @ts-ignore: decorator
    @unsafe @builtin
    export declare function wait64(ptr: usize, expected: i64, timeout: i64): AtomicWaitResult;
  }

  /** Initializes a memory segment. */
  // @ts-ignore: decorator
  @unsafe
  export function init(segmentIndex: u32, srcOffset: usize, dstOffset: usize, n: usize): void {
    throw new Error(E_NOTIMPLEMENTED);
  }

  /** Drops a memory segment. */
  // @ts-ignore: decorator
  @unsafe
  export function drop(segmentIndex: u32): void {
    throw new Error(E_NOTIMPLEMENTED);
  }

  /** Repeats a section of memory at a specific address. */
  // @ts-ignore: decorator
  @unsafe
  export function repeat(dst: usize, src: usize, srcLength: usize, count: usize): void {
    let index: usize = 0;
    let total = srcLength * count;
    while (index < total) {
      memory.copy(dst + index, src, srcLength);
      index += srcLength;
    }
  }

  /** Compares a section of memory to another. */
  // @ts-ignore: decorator
  @inline
  export function compare(vl: usize, vr: usize, n: usize): i32 {
    return memcmp(vl, vr, n);
  }

  /** Gets a pointer to a static chunk of memory of the given size. */
  // @ts-ignore: decorator
  @builtin
  export declare function data<T>(size: T, align?: i32): usize;
}

So we get this memory.ts.js

import { memcmp, memmove, memset } from "./util/memory";
import { E_NOTIMPLEMENTED } from "./util/error";
export var memory;
(function(memory) {
  function fill(dst, c, n) {
    memset(dst, c, n); // fallback if "bulk-memory" isn't enabled
  }
  memory.fill = fill;
  function copy(dst, src, n) {
    memmove(dst, src, n); // fallback if "bulk-memory" isn't enabled
  }
  memory.copy = copy;
  function init(segmentIndex, srcOffset, dstOffset, n) {
    throw new Error(E_NOTIMPLEMENTED);
  }
  memory.init = init;
  function drop(segmentIndex) {
    throw new Error(E_NOTIMPLEMENTED);
  }
  memory.drop = drop;
  function repeat(dst, src, srcLength, count) {
    let index = 0;
    let total = srcLength * count;
    while(index < total){
      memory.copy(dst + index, src, srcLength);
      index += srcLength;
    }
  }
  memory.repeat = repeat;
  function compare(vl, vr, n) {
    return memcmp(vl, vr, n);
  }
  memory.compare = compare;
})(memory || (memory = {}));
export var heap;
(function(heap) {
  function alloc(size) {
    return __alloc(size);
  }
  heap.alloc = alloc;
  function realloc(ptr, size) {
    return __realloc(ptr, size);
  }
  heap.realloc = realloc;
  function free(ptr) {
    __free(ptr);
  }
  heap.free = free;
  function reset() {
    if (isDefined(__reset)) {
      __reset();
    } else {
      throw new Error(E_NOTIMPLEMENTED);
    }
  }
  heap.reset = reset;
})(heap || (heap = {}));

when executing this

import { memory } from "./memory.ts";

console.log(memory);
deno run --unstable-sloppy-imports test.ts
{
  fill: [Function: fill],
  copy: [Function: copy],
  init: [Function: init],
  drop: [Function: drop],
  repeat: [Function: repeat],
  compare: [Function: compare]
}

I'm not opposed to using wasm2js. It works.

but it'll be difficult to deal with things like changetype(someArrayOrObject) (converting an object to its underlying pointer)...

Already did that, here to extend wasi_process.ts

@unmanaged
abstract class ReadableStream extends Stream {
  read(buffer: ArrayBuffer, offset: isize = 0): i32 {
    var end = <usize>buffer.byteLength;
    if (offset < 0 || <usize>offset > end) {
      throw new Error(E_INDEXOUTOFRANGE);
    }
    store<usize>(tempbuf, changetype<usize>(buffer) + offset);
    store<usize>(tempbuf, end - offset, sizeof<usize>());
    var err = fd_read(<u32>changetype<usize>(this), tempbuf, 1, tempbuf + 2 * sizeof<usize>());
    if (err) throw new Error(errnoToString(err));
    return <i32>load<isize>(tempbuf, 2 * sizeof<usize>());
  }
  readSync(_fd:i32, b: Uint8Array, offset: isize = 0): i32 {
    var buffer: ArrayBuffer = b.buffer;
    var end = <usize>buffer.byteLength;
    if (offset < 0 || <usize>offset > end) {
      throw new Error(E_INDEXOUTOFRANGE);
    }
    store<usize>(tempbuf, changetype<usize>(buffer) + offset);
    store<usize>(tempbuf, end - offset, sizeof<usize>());
    var err = fd_read(<u32>changetype<usize>(this), tempbuf, 1, tempbuf + 2 * sizeof<usize>());
    if (err) throw new Error(errnoToString(err));
    return <i32>load<isize>(tempbuf, 2 * sizeof<usize>());
  }
}

Either the AssemblyScript Portable documentation is true and correct

Other than that, portable code (JavaScript) does not have a concept of memory, so there are no load and store implementations in the portable standard library. Technically this can be polyfilled in various ways, but no default is provided since actual implementations are expected to be relatively specific (for instance: the portable compiler accesses Binaryen's memory).

or not. If true and correct that data<T>, store(), load(), etc. must be portable.

You might also be interested in Porffor...they definitely have JS spec compatibility as their primary goal.

Tried that code. Javy, Static Hermes do what they say they do.

@guest271314
Copy link

there should already be a Wasm-compiled version available after installing AS

There is. It works

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

The output at the end looks like this

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

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

Here is how I use that resulting JavaScript with WASI support

// Comment the generated wasi_snapshot_preview1 import
// We'll be using a custom JavaScript implementation of WASI below
// import * as wasi_snapshot_preview1 from 'wasi_snapshot_preview1';
// ...
 return {
  "array_nth_permutation": legalstub$135, 
  "memory": Object.create(Object.prototype, {
   "grow": {
    "value": __wasm_memory_grow
   }, 
   "buffer": {
    "get": function () {
     return buffer;
    }
    
   }
  }), 
  "_start": $104
 };
}

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();

Execute

echo '4 5' | node module-asc.js
5 of 23 (0-indexed, factorial 24) => [0,3,2,1]

@guest271314
Copy link

However, if you have code like the following example, it won't work:

Can't Atomics be used to implement those algorithms?

The question to me becomes why not write the AssemblyScript-specific functions and objects in source code in such a way that it can compile in it's entirety to both WASM and JavaScript?

@CountBleck
Copy link
Member

It's not the priority of many (perhaps most) users. AS is used because it's familiar, simple, and performant. Anyways, we're derailing this issue.

@guest271314
Copy link

@CountBleck So what's the verdict here? Use wasm2js and that's it?

The only AssemblyScript-specific "features" are load, store, which are written in TypeScript, and can certainly be compiled to JavaScript. All the rest can already be compiled to JavaScript by multiple JavaScript runtimes. So the to JavaScript part doesn't necessarily need another dependency to go to JavaScript.

The "performant" claim requires numbers. Otherwise it's just an empty claim. "performant" compared to what? Where are the numbers?

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

No branches or pull requests

4 participants