AssemblyScript / assemblyscript

A TypeScript-like language for WebAssembly.
https://www.assemblyscript.org
Apache License 2.0
16.82k stars 655 forks source link

`env` module isn't imported, using `moduleImports` and `--importMemory` together #2823

Open a-skua opened 7 months ago

a-skua commented 7 months ago

Bug description

moduleImpors(cases where @external or declare are used) together with the --importMemory flag, the glue code does not include the imports of the env module.

e.g. build assembly/index.ts with the following settings in asconfig.json.

{
  "targets": {
    "release": {
      "outFile": "build/release.wasm",
      "textFile": "build/release.wat",
      "sourceMap": true,
      "converge": false,
      "noAssert": true
    }
  },
  "options": {
    "bindings": "raw",
    "runtime": "minimal",
    "importMemory": true
  }
}
@external("app", "add")
declare function add(a: i32, b: i32): i32;

export function run(): void {
  add(1, 2);
}

Wasm expects imports for app.add and env.memory, but in the actual generated build/release.js, only the app module is imported, and the import for the env module is missing.

(module
 (type $0 (func (param i32 i32) (result i32)))
 (type $1 (func))
 (import "env" "memory" (memory $0 0))
 (import "app" "add" (func $assembly/index/add (param i32 i32) (result i32)))
 (table $0 1 1 funcref)
 (elem $0 (i32.const 1))
 (export "run" (func $assembly/index/run))
 (export "memory" (memory $0))
 (func $assembly/index/run
  i32.const 1
  i32.const 2
  call $assembly/index/add
  drop
 )
)
export async function instantiate(module, imports = {}) {
  const __module0 = imports.app;
  const adaptedImports = {
    app: __module0,
  };
  const { exports } = await WebAssembly.instantiate(module, adaptedImports);
  return exports;
}

While changing app.add to env.add can circumvent this issue, the desired outcome is to control the size of the memory from outside Wasm. So, the expectation is for code similar to the following to be generated.

export async function instantiate(module, imports = {}) {
  const __module0 = imports.app;
  const adaptedImports = {
    app: __module0,
+   env: Object.assign(Object.create(globalThis), imports.env || {}),
  };
  const { exports } = await WebAssembly.instantiate(module, adaptedImports);
  return exports;
}

Steps to reproduce

asconfig.json

{
  "targets": {
    "release": {
      "outFile": "build/release.wasm",
      "textFile": "build/release.wat",
      "sourceMap": true,
      "converge": false,
      "noAssert": true
    }
  },
  "options": {
    "bindings": "raw",
    "runtime": "minimal",
    "importMemory": true
  }
}

assembly/index.ts

@external("app", "add")
declare function add(a: i32, b: i32): i32;

export function run(): void {
  add(1, 2);
}

index.js

import { instantiate } from "./build/release.js";
const exports = await instantiate(await WebAssembly.compileStreaming(fetch(new URL("./build/release.wasm", import.meta.url))), {
  app: {
    add(a: number, b: number) {
      return a + b;
    },
  },
  env: {
    memory: new WebAssembly.Memory({
      initial: 1,
      maximum: 1,
    }),
  },
});

exports.run();

[!CAUTION]

error: Uncaught (in promise) TypeError: WebAssembly.instantiate(): Import #1 module="env": module is not an object or function
  const { exports } = await WebAssembly.instantiate(module, adaptedImports);
                                        ^

AssemblyScript version

v0.27.24