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

Allow using CompiledModule from previous call instead of loading from disk #50

Open
Ice3man543 opened this issue May 5, 2023 · 4 comments

Comments

@Ice3man543
Copy link

I have a list of web assembly modules written in go that provide extensibility to my program.

I don't want to compile the plug-in each time i use it though because the program is a long running multi goroutine application where 15ms+ for plugin loading each time would be very bad.

Ideally I'd like the ability to compile module beforehand and use it later by instantiating new instances per goroutine.

@Ice3man543
Copy link
Author

Ice3man543 commented May 5, 2023

Basically I'd like to do something like add these two new functions. -

  1. Open(ctx context.Context, pluginPath string) (wazero.Runtime, wazero.CompiledModule, error)
  2. LoadWithCompiled(ctx context.Context, r wazero.Runtime, code wazero.CompiledModule) (helperFunction, error)

This will be generated in the plugins_host.pb.go. Let me know if the approach looks good and I can make a PR for it.

An example implementation is provided below. Let me know if I am doing something wrong :)

Here we are creating a CompiledModule and instantiating it multiple times as described in wazero docs.

Compiled Module - a prepared and ready to be instantiated object created vi Compilation phrase. This can be used in instantiation multiple times to create multiple and isolated sandbox from a single Wasm binary.
Instantiate - In wazero, instantiate means allocating a Compiled Module and associating it with a unique name, resulting in a Module. This includes running any start functions. The result of instantiation is a module whose exported functions can be called.

func (p *HelperFunctionPlugin) Open(ctx context.Context, pluginPath string) (wazero.Runtime, wazero.CompiledModule, error) {
	b, err := os.ReadFile(pluginPath)
	if err != nil {
		return nil, nil, err
	}

	// Create a new runtime so that multiple modules will not conflict
	r, err := p.newRuntime(ctx)
	if err != nil {
		return nil, nil, err
	}

	// Compile the WebAssembly module using the default configuration.
	code, err := r.CompileModule(ctx, b)
	if err != nil {
		return nil, nil, err
	}

	// InstantiateModule runs the "_start" function, WASI's "main".
	module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
	if err != nil {
		// Note: Most compilers do not exit the module after running "_start",
		// unless there was an Error. This allows you to call exported functions.
		if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
			return nil, nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
		} else if !ok {
			return nil, nil, err
		}
	}
	defer module.Close(ctx)


	// Compare API versions with the loading plugin
	apiVersion := module.ExportedFunction("helper_function_api_version")
	if apiVersion == nil {
		return nil, nil, errors.New("helper_function_api_version is not exported")
	}
	results, err := apiVersion.Call(ctx)
	if err != nil {
		return nil, nil, err
	} else if len(results) != 1 {
		return nil, nil, errors.New("invalid helper_function_api_version signature")
	}
	if results[0] != HelperFunctionPluginAPIVersion {
		return nil, nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", HelperFunctionPluginAPIVersion, results[0])
	}

	info := module.ExportedFunction("helper_function_info")
	if info == nil {
		return nil, nil, errors.New("helper_function_info is not exported")
	}
	execute := module.ExportedFunction("helper_function_execute")
	if execute == nil {
		return nil, nil, errors.New("helper_function_execute is not exported")
	}
	return r, code, nil
}

func (p *HelperFunctionPlugin) LoadWithCompiled(ctx context.Context, r wazero.Runtime, code wazero.CompiledModule) (helperFunction, error) {
	// InstantiateModule runs the "_start" function, WASI's "main".
	module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
	if err != nil {
		// Note: Most compilers do not exit the module after running "_start",
		// unless there was an Error. This allows you to call exported functions.
		if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
			return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
		} else if !ok {
			return nil, err
		}
	}

	info := module.ExportedFunction("helper_function_info")
	if info == nil {
		return nil, errors.New("helper_function_info is not exported")
	}
	execute := module.ExportedFunction("helper_function_execute")
	if execute == nil {
		return nil, errors.New("helper_function_execute is not exported")
	}

	malloc := module.ExportedFunction("malloc")
	if malloc == nil {
		return nil, errors.New("malloc is not exported")
	}

	free := module.ExportedFunction("free")
	if free == nil {
		return nil, errors.New("free is not exported")
	}
	return &helperFunctionPlugin{
		runtime: r,
		module:  module,
		malloc:  malloc,
		free:    free,
		info:    info,
		execute: execute,
	}, nil
}

@codefromthecrypt
Copy link
Collaborator

@Ice3man543 by your description, I'm not sure if you have seen this or not. Can you comment first about it, because caching compilation is pretty involved, which is why we have this. In fact the recent ability to add RuntimeConfig to plugin-wasm was about sharing compilation https://pkg.go.dev/github.com/tetratelabs/wazero#example-package-CompileCache #32

@Ice3man543
Copy link
Author

@codefromthecrypt this makes a lot of sense. Thanks for the link, will look into it. BTW, the library is very neat. Kudos for your awesome work on it!

@codefromthecrypt
Copy link
Collaborator

@dmvolod gets credit for the recent phase of development, especially towards what you are asking about. Good luck!

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

Successfully merging a pull request may close this issue.

2 participants