Compile to asm.js
listepo opened this issue · 17 comments
Feature suggestion
It would be nice to be able compile to asm.js
Isn't wasm the successor to asm.js? Is there more background to support asm.js instead of using wasm?
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.
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.
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.
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.
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:
- Executing AssemblyScript directly, and compiling to JavaScript with tsc, Deno, and Bun (and executing WASM directly with bun)
- Reading stdin in Static Hermes
- Compiling JavaScript to WASM with WASI support using Static Hermes
- Strip TypeScript types in Node.js, Deno, and Bun
- Compiling JavaScript to WASM using Bytecode Alliance's javy
- WASI.md (Static Hermes, Building with WASI-SDK)
- deno-wasi
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.
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)
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)...
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.
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]
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?
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.
@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?
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.
Since those functions are defined as TypeScript definitions, they must be capable of being compiled to JavaScript. Are the canonical definitions for load and store, etc. defined in /dist/asc.js?