Skip to content

Add loading plugins from binary #52

@brennanjl

Description

@brennanjl

A really great feature would be able to load a plugin from binary, instead of from filepath. This would be done by simply adding a method similar to Load on the generated plugin (and probably refactoring Load as well). Here's what it would look like in the example given in the README:

func (p *GreeterPlugin) Load(ctx context.Context, pluginPath string) (greeter, error) {
	b, err := os.ReadFile(pluginPath)
	if err != nil {
		return nil, err
	}

	return p.LoadBinary(ctx, b)
}

func (p *GreeterPlugin) LoadBinary(ctx context.Context, pluginBinary []byte) (greeter, error) {
	// Create a new runtime so that multiple modules will not conflict
	r, err := p.newRuntime(ctx)
	if err != nil {
		return nil, err
	}

	// Compile the WebAssembly module using the default configuration.
	code, err := r.CompileModule(ctx, pluginBinary)
	if err != nil {
		return 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, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
		} else if !ok {
			return nil, err
		}
	}

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

	sayhello := module.ExportedFunction("greeter_say_hello")
	if sayhello == nil {
		return nil, errors.New("greeter_say_hello 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 &greeterPlugin{
		runtime:  r,
		module:   module,
		malloc:   malloc,
		free:     free,
		sayhello: sayhello,
	}, nil
}

I'd be more than happy to submit a PR for this if you all would be ok with it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions