Code Monkey home page Code Monkey logo

wasp's Introduction

Web Assembly System Plugins (Wasp)

Wasp is a plugin system that leverages Web Assembly (Wasm) modules for Go. Wasp allows you to extend your applictaions by allowing dynamically loaded plugins that can be authored in any language that can compile to the Wasm format. Due to the limitations and sandboxed nature of Wasm not every capability of a language, as it was originally designed to run in the browser. For example it does not natively support the ability to make network connections via sockets, read and write files, etc. Support for these features is currently being worked on as part of the Wasi standard https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/, however until this standard is widely adopted capability across languages may vary.

To load and execute WebAssembly modules, Wasp uses the OSS Wasmer library.

In addition Wasm does not have the rich type system that Go has, currently Wasm only supports the types int32, int64, float32, float64. This means that any functions that are exposed by a Wasm module can only contain these types for data interchange. For example, the following Go function could be compiled to Wasm using TinyGo or the experimental Go Wasm target and could be called successfully by the interpreter.

//go:export sum
func sum(a, b int) int {
	//get("test")
	return a + b
}

But it is not possible to pass a string in and out of the function as string is not defined by Wasms type structure:

//go:export hello
func hello(name string) string {
	return "Hello " + s
}

To work round this limitation pointers can be used as shown in the rewritten example below. WasmString is not actually a Go string but an alias for a pointer to the location of a C string that is converted to a Go string with the helper function gostring, and exported using the cstring function.

//go:export hello
func hello(in WasmString) WasmString {
	// get the string from the memory pointer
	s := in.String()

	// Create a new empty WasmString
	out := WasmString(0)
	out.Copy("Hello " + s)

	return out
}

And the same example written in AssemblyScript:

export function hello(name: ArrayBuffer): ArrayBuffer {
  let inParam = String.UTF8.decode(name,true)

  return String.UTF8.encode("Hello " + inParam, true)
}

Wasp provides a data interchange ABI and helper functions for your Wasm modules that simplifies this process and automatically manages the process of copying Go string and []byte to the Wasm modules memory space. Note: the limitation on parameters only affects functions that interface with the plugin host internal functions and methods can use the full type system of the language used to author the Wasm module.

Current examples in the repo show how plugins can be written in:

  • Go (TinyGo)
  • C
  • Rust
  • Java (Kind of Works)
  • AssemblyScript

Basic Usage:

The following example shows how Wasp can be used to call the method hello that was exported from a Wasm module. First you create an instance of the engine and load the plugin.

// Create a logger 
log := hclog.Default()
log = log.Named("main")
engineLog = log.Named("engine")

// the wasp engine takes a wrapped logger that adapts your
// logger interface into wasps
wrappedLogger = logger.New(
  engineLog.Info,
  engineLog.Debug,
  engineLog.Error,
  engineLog.Trace
)


// Create the plugin engine 
e := engine.New(wrappedLogger)

// Register and compile the Wasm module to the plugin engine
err := e.RegisterPlugin("myplugin", "./plugins/go/module.wasm", nil, nil)
if err != nil {
	log.Error("Error loading plugin", "error", err)
	os.Exit(1)
}

// Get an instance of the plugin, an instance is an independently sand boxed environment
// that has it's own memory and filesystem.
i, err := e.GetInstance("myplugin", "")
if err != nil {
	log.Error("Error getting plugin instance", "error", err)
	os.Exit(1)
}

// Clean up any resources used by the instance
defer i.Remove()

Then you can use the CallFunction method on the instance to call the hello function exported from the Wasm module, Wasp automatically converts Go types into the simple types understood by the Wasm module. In the following example Wasp would take the input string "hello", allocate the required memory inside the Wasm module, copy the string data to this memory before calling the destination function with a pointer to this string. Responses work exactly the same way in reverse.

// Call the function hello that is exported by the module
var outString string
err = i.CallFunction("hello", &outString, 3, 2)
if err != nil {
	log.Error("Error calling function", "name", "hello", "error", err)
	os.Exit(1)
}
log.Info("Response from function", "name", "hello", "result", outString)

// Call the reverse function exported by the module
var outData []byte
err = e.CallFunction("reverse", &outData, []byte{1, 2, 3})
if err != nil {
	log.Error("Error calling function", "name", "reverse", "error", err)
	os.Exit(1)
}
log.Info("Response from function", "name", "reverse", "result", outData)

Callbacks

Callbacks can be defined to allow a local function to be called from the Wasm module. For example, if your application contains the Go function:

func callMe(in string) string {
	out := fmt.Sprintf("Hello %s", in)
	fmt.Println(out)

	return out
}

This can called by the Wasm module by defining an external import. The following example shows how to define an external function that is imported by the module. All functions exported from Wasp are placed into the plugin namespace, you use the annotation //go:wasm-module [namespace] to state which namespace the imported function is in. The annotation //export [function_name] defines the name of the function, you can then define the function signature.

//go:wasm-module plugin
//export call_me
func callMe(in WasmString) WasmString

To use an imported function you call it as you would any other function from your code. In the following example when the callback function is called by Wasp, the wasm module calls the imported function call_me that appends the string passed to the function to Hello . This is then returned back to Wasp.

//go:export callback
func callback() WasmString {
	// get the string from the memory pointer
	name := WasmString(0)
	name.Copy("Nic")

	s := callMe(name)
	//s := WasmString(0)
	//s.Copy("Hello")

	return s // WasmString(0)
}

To use callbacks they must first be registered, registration is done by creaing a Callbacks type that will contain all the callbacks for your plugin, You then use the AddCallback method, this takes three parameters, the module name that the imported function will be available at. The name of the function that it will be available to the Wasm module, and a Go function that will be executed. Wasp uses reflection to automatically manage the function parameters, it also automatically converts any string or []byte types into pointers that the Wasm module can decode.

cb := &engine.Callbacks{}
cb.AddCallback("plugin", "call_me", callMe)

Once you have created your callbacks they can be made available to the plugin by passing the collection to the RegisterPlugin function.

err := e.RegisterPlugin("test", *plugin, cb, nil)
2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Called callback function: out=["Hello Nic"]
2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Allocated memory in host: size=10 addr=131168
2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Called function: name=callback outputParam=0xc00009c500 inputParam=[] response=131168 time taken=160µs
2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Got string from memory: addr=131168 result="Hello Nic"
2021-04-12T17:44:41.957+0100 [INFO]  main: Response from function: name=callback result="Hello Nic"

Benchmarks:

Calling functions in Wasm modules will never be as fast as native Go functions as the Wasm function is running in a virtual environment. However the intention of Wasp is that it does not replace every function in your application but allows extension points. The following benchmarks only show a simple string calculation where most of the performance is lost through executing the plugin not the speed of the code executing in the plugin. For example, if this function was called in the context of a HTTP handler that makes a database query, adding 580965 nano seconds to a call that original took 200 milliseconds would only add 0.58 milliseconds to the total response. Wasm will always be slower than native code execution and the bulk of this duration is startup to create a new instance, calling multiple functions on the same instance has a dramatically reduced overhead. However depending on the context this may be an irrelivant and all benchmarks should be taken with a pinch of salt.

➜ go test -bench=. ./...
goos: linux
goarch: amd64
pkg: github.com/nicholasjackson/wasp/engine
cpu: AMD Ryzen 9 3950X 16-Core Processor            
BenchmarkIntFuncGoWASM-32                   6778                 370470 ns/op
BenchmarkStringFuncGoWASM-32                2262                 580965 ns/op
BenchmarkIntFuncNative-32               1000000000               0.2292 ns/op
BenchmarkStringFuncNative-32            80865258                  14.37 ns/op
PASS
ok      github.com/nicholasjackson/wasp/engine  7.420s
?       github.com/nicholasjackson/wasp/engine/logger   [no test files]
?       github.com/nicholasjackson/wasp/example [no test files]

wasp's People

Contributors

nicholasjackson avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.