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

Error when calling js from wasm,error:WebAssembly Instantiation: Import #0 module="module" error: module is not an object or function #384

Closed
evanyuanvip opened this issue Dec 20, 2018 · 19 comments

Comments

@evanyuanvip
Copy link

evanyuanvip commented Dec 20, 2018

According to this #158, I tried it, but I was given an error. My code is:

declare namespace console {
   function logi(val: i32): void;
}
@external("env", "logf")
declare function logf(val: f64): void;
console.logi(123);
logf(1e10);
export function add(a: i32, b: i32): i32 {
  return a + b;
}
var importObject = {
	env: {
	    // import as @external("env", "logf")
	    logf(value) {
	      console.log("logf: " + value);
	    },
	    abort(msg, file, line, column) {
	      console.error("abort called at main.ts:" + line + ":" + column);
	    }
	},
	  console: {
	    // import as console.logi
	    logi(value) {
	      console.log("logi: " + value);
	    }
	}
}

fetch('dist/module.optimized.wasm')
    .then(res => res.arrayBuffer())
    .then(bytes => new WebAssembly.Instance(new WebAssembly.Module(bytes),importObject))
    .then(mod => {
    	console.log(mod.exports.add(503,100));
    });
(module
 (type $iv (func (param i32)))
 (type $Fv (func (param f64)))
 (type $iii (func (param i32 i32) (result i32)))
 (type $v (func))
 (import "module" "console.logi" (func $assembly/module/console.logi (param i32)))
 (import "env" "logf" (func $assembly/module/logf (param f64)))
 (memory $0 0)
 (table $0 1 anyfunc)
 (elem (i32.const 0) $null)
 (export "memory" (memory $0))
 (export "table" (table $0))
 (export "add" (func $assembly/module/add))
 (start $start)
 (func $assembly/module/add (; 2 ;) (type $iii) (param $0 i32) (param $1 i32) (result i32)
  get_local $0
  get_local $1
  i32.add
 )
 (func $start (; 3 ;) (type $v)
  i32.const 123
  call $assembly/module/console.logi
  f64.const 1e10
  call $assembly/module/logf
 )
 (func $null (; 4 ;) (type $v)
  nop
 )
)
"scripts": {
    "build": "npm run build:untouched && npm run build:optimized",
    "build:untouched": "asc assembly/module.ts -t dist/module.untouched.wat -b dist/module.untouched.wasm --validate --sourceMap --measure",
    "build:optimized": "asc assembly/module.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --validate --sourceMap --measure --optimize"
  }
@dcodeIO
Copy link
Member

dcodeIO commented Dec 20, 2018

According to your code, the JS side expects an import named module with a property console.logi. This changed recently to use the enclosing file name without extension as the default module name (here: module.ts -> module), so it might be that the documentation isn't up to date.

@evanyuanvip
Copy link
Author

According to your code, the JS side expects an import named module with a property console.logi. This changed recently to use the enclosing file name without extension as the default module name (here: module.ts -> module), so it might be that the documentation isn't up to date.

Can you give me a simple example?

@goldenratio
Copy link

goldenratio commented Jan 4, 2019

@evanyuanvip you need to include module in your importObject also env.memory and env.table

Example:

const importObj = {
  module: {},
  env: {
    memory: new WebAssembly.Memory({ initial: 256 }),
    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
  }
};

source: https://gist.github.com/kripken/59c67556dc03bb6d57052fedef1e61ab#file-hello_world-html-L24

Demo: https://github.com/goldenratio/wasm-helloworld

@evanyuanvip
Copy link
Author

@goldenratio Thank you !

@dcodeIO
Copy link
Member

dcodeIO commented Feb 7, 2019

Closing this issue for now as it hasn't received any replies recently. Feel free to reopen it if necessary!

@dcodeIO dcodeIO closed this as completed Feb 7, 2019
@torch2424
Copy link
Contributor

@dcodeIO So if you get the chance, I think this still has some weird behavior. I know have to do this in wasmboy: https://github.com/torch2424/wasmboy/blob/80441371e2da5ecec3d7e0b6fb4d53e08aa008ca/core/portable/importObject.js

As it would complain with Import #0 module="index" .... And I can't get namespaces working, even if I add them under index. I don't mind this, but I just wanted to let you know 😄

@dcodeIO
Copy link
Member

dcodeIO commented Feb 28, 2019

This works for me

declare namespace console {
  @external("console", "log")
  export function log(): void;
}

but it seems that @external on namespaces does nothing currently. That the issue here?

@torch2424
Copy link
Contributor

Ahh, I didn't try declaring both the namespace, and exporting the log. I'll try that once I get the chance 👍

@trusktr
Copy link
Member

trusktr commented May 27, 2019

I'm trying the example from #158, and I too get the error. I'm not sure how to fix it. I tried adding module: {} as mentioned above in #384 (comment), but the error remains the same.

So in JS I have

	await WebAssembly.instantiateStreaming(response, {
		module: {},
		console: {
			logi() {
				return 4
			},
		},
	})

then in AS I have

declare namespace console {
	function logi(): i32
}

// this is exported so it can be called on the JS side.
export function mul(a: i32, b: i32): i32 {
	return a * b + console.logi()
}

But it at runtime it says

Uncaught (in promise) TypeError: WebAssembly.instantiate(): Import #0 module="index" error: module is not an object or function

How do we make it work?

@trusktr
Copy link
Member

trusktr commented May 27, 2019

Okay, my file is called index.ts, so I changed module: {} to index: {}, and got passed that error.

But now I get

Uncaught (in promise) LinkError: WebAssembly.instantiate(): Import #0 module="index" function="console.logi" error: function import requires a callable

What did I do wrong?

@trusktr
Copy link
Member

trusktr commented May 27, 2019

The only way I can get it to work is by putting stuff in env, then using the form

@external("env", "getFour")
declare function getFour(): i32;

but I'd like to avoid the non-standard syntax (decorators only allowed on classes).

@trusktr
Copy link
Member

trusktr commented May 27, 2019

The reason I'm trying to use the namespace technique is that I wanted to write portable code, and decorators are only allowed on functions.

But I found a workaround: I put the imports with the @external decorators in a separate file. I have the following structure:

project
  |-- index.ts
  |-- imports.ts
  |-- mul.ts

And here's the AS files:

// index.ts
import {mul} from './mul'
export {mul}
// this file import things passed in from JS, and exports them for use by other
// AS modules.

@external("env", "getFour")
declare function getFour(): i32

export {getFour}
import {getFour} from './imports'

// this is exported so it can be called on the JS side.
export function mul(a: i32, b: i32): i32 {
	return a * b + getFour()
}

Then in JS supply getFour:

const {mul} = 	(await WebAssembly.instantiateStreaming(response, {
	env: {
		// this is called by `assert()`ions in the AssemblyScript.
		// Useful for debugging.
		abort(...args) {
			console.log(...args)
		},
		
		getFour() {
			return 4
		},
	},
})).instance.exports

mul(3,5) // it works

So, the mul code can import the getFour function, regardless of where it actually comes from.

Now we can at least have a better time writing portable code, because if we want to run this outside of AssemblyScript, then we can supply an alternative source for the ./imports module (f.e. using Webpack, or a CommonJS module that we place alongside the tsc output, etc). So at least the mul file is portable.

@dcodeIO
Copy link
Member

dcodeIO commented May 27, 2019

Internally, we usually have a file env.ts that we import from, containing the declares. This way, the module name will be env.

@trusktr
Copy link
Member

trusktr commented May 27, 2019

@dcodeIO Thanks, that's good to know. How do we get the declare namespace version working?

@dcodeIO
Copy link
Member

dcodeIO commented May 27, 2019

I think in

declare namespace console {
	function logi(): i32 // it's missing an export here
}

@torch2424
Copy link
Contributor

Just in case it helps someone, this is my current importObject, that is passed during Wasm Instantiation.

@anubhavgupta
Copy link

anubhavgupta commented Aug 29, 2020

This works:

// assemblyScript file
declare namespace console {
  function log(str: string): void;
} 
export function add(a: i32, b: i32): i32 {
  console.log("AnubhavGupta");
  return a + b;
}
// JS file
    await loader.instantiate(buffer,{
        index: {
            "console.log": (val) => console.log("log:", val)
        }
    });

@torch2424
Copy link
Contributor

Ah, so if anyone else comes across this, this is because imports are namespaced per their filename. So importing console in index.ts, means console needs to be inside an index object in the imports.

For example see the docs: https://www.assemblyscript.org/exports-and-imports.html#imports

And if you don't want this behavior, and want control on how things are declared in the import object, see the custom naming in the docs: https://www.assemblyscript.org/exports-and-imports.html#imports

For more general information on WebAssembly Imports in AssemblyScript, see: https://wasmbyexample.dev/examples/importing-javascript-functions-into-webassembly/importing-javascript-functions-into-webassembly.assemblyscript.en-us.html?programmingLanguage=assemblyscript

@trusktr
Copy link
Member

trusktr commented Feb 7, 2021

What if two AS files have the same name, but they are at different paths in the file system?

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

6 participants