{"id":17101698,"url":"https://github.com/nicholasjackson/wasp","last_synced_at":"2025-04-13T00:25:48.706Z","repository":{"id":57576587,"uuid":"356352896","full_name":"nicholasjackson/wasp","owner":"nicholasjackson","description":"Wasm System Plugins for Go","archived":false,"fork":false,"pushed_at":"2021-04-29T09:09:39.000Z","size":1797,"stargazers_count":38,"open_issues_count":9,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-26T18:21:26.456Z","etag":null,"topics":["go","wasi-standard","wasm-modules"],"latest_commit_sha":null,"homepage":"","language":"WebAssembly","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nicholasjackson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-09T17:39:41.000Z","updated_at":"2024-11-03T11:11:28.000Z","dependencies_parsed_at":"2022-08-29T01:00:38.068Z","dependency_job_id":null,"html_url":"https://github.com/nicholasjackson/wasp","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholasjackson%2Fwasp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholasjackson%2Fwasp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholasjackson%2Fwasp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholasjackson%2Fwasp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nicholasjackson","download_url":"https://codeload.github.com/nicholasjackson/wasp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248649720,"owners_count":21139538,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","wasi-standard","wasm-modules"],"created_at":"2024-10-14T15:26:26.178Z","updated_at":"2025-04-13T00:25:48.675Z","avatar_url":"https://github.com/nicholasjackson.png","language":"WebAssembly","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web Assembly System Plugins (Wasp)\n\n `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/](https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface), however until this standard is widely adopted capability across languages may vary.\n\nTo load and execute WebAssembly modules, Wasp uses the OSS [Wasmer](https://wasmer.io/) library.\n \nIn 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.\n\n```go\n//go:export sum\nfunc sum(a, b int) int {\n\t//get(\"test\")\n\treturn a + b\n}\n```\n\nBut it is not possible to pass a string in and out of the function as `string` is not defined by Wasms type structure:\n\n```go\n//go:export hello\nfunc hello(name string) string {\n\treturn \"Hello \" + s\n}\n```\n\nTo 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 \nlocation of a C string that is converted to a Go string with the helper function `gostring`, and exported using the `cstring` function.\n\n```go\n//go:export hello\nfunc hello(in WasmString) WasmString {\n\t// get the string from the memory pointer\n\ts := in.String()\n\n\t// Create a new empty WasmString\n\tout := WasmString(0)\n\tout.Copy(\"Hello \" + s)\n\n\treturn out\n}\n```\n\nAnd the same example written in AssemblyScript:\n\n```TypeScript\nexport function hello(name: ArrayBuffer): ArrayBuffer {\n  let inParam = String.UTF8.decode(name,true)\n\n  return String.UTF8.encode(\"Hello \" + inParam, true)\n}\n```\n\nWasp 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.\n\nCurrent examples in the repo show how plugins can be written in:\n* Go (TinyGo)\n* C\n* Rust\n* Java (Kind of Works)\n* AssemblyScript\n\n## Basic Usage:\n\nThe 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.\n\n```go\n\n// Create a logger \nlog := hclog.Default()\nlog = log.Named(\"main\")\nengineLog = log.Named(\"engine\")\n\n// the wasp engine takes a wrapped logger that adapts your\n// logger interface into wasps\nwrappedLogger = logger.New(\n  engineLog.Info,\n  engineLog.Debug,\n  engineLog.Error,\n  engineLog.Trace\n)\n\n\n// Create the plugin engine \ne := engine.New(wrappedLogger)\n\n// Register and compile the Wasm module to the plugin engine\nerr := e.RegisterPlugin(\"myplugin\", \"./plugins/go/module.wasm\", nil, nil)\nif err != nil {\n\tlog.Error(\"Error loading plugin\", \"error\", err)\n\tos.Exit(1)\n}\n\n// Get an instance of the plugin, an instance is an independently sand boxed environment\n// that has it's own memory and filesystem.\ni, err := e.GetInstance(\"myplugin\", \"\")\nif err != nil {\n\tlog.Error(\"Error getting plugin instance\", \"error\", err)\n\tos.Exit(1)\n}\n\n// Clean up any resources used by the instance\ndefer i.Remove()\n```\n\nThen 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. \n\n```go\n// Call the function hello that is exported by the module\nvar outString string\nerr = i.CallFunction(\"hello\", \u0026outString, 3, 2)\nif err != nil {\n\tlog.Error(\"Error calling function\", \"name\", \"hello\", \"error\", err)\n\tos.Exit(1)\n}\nlog.Info(\"Response from function\", \"name\", \"hello\", \"result\", outString)\n\n// Call the reverse function exported by the module\nvar outData []byte\nerr = e.CallFunction(\"reverse\", \u0026outData, []byte{1, 2, 3})\nif err != nil {\n\tlog.Error(\"Error calling function\", \"name\", \"reverse\", \"error\", err)\n\tos.Exit(1)\n}\nlog.Info(\"Response from function\", \"name\", \"reverse\", \"result\", outData)\n```\n\n## Callbacks\n\nCallbacks can be defined to allow a local function to be called from the Wasm module. For example, if your application contains the Go function:\n\n```go\nfunc callMe(in string) string {\n\tout := fmt.Sprintf(\"Hello %s\", in)\n\tfmt.Println(out)\n\n\treturn out\n}\n```\n\nThis can called by the Wasm module by defining an external import. The following example shows how to define an external function that is imported\nby the module. All functions exported from Wasp are placed into the `plugin` namespace, you use the annotation `//go:wasm-module [namespace]` to\nstate which namespace the imported function is in. The annotation `//export [function_name]` defines the name of the function, you can then define\nthe function signature.\n\n```go\n//go:wasm-module plugin\n//export call_me\nfunc callMe(in WasmString) WasmString\n```\n\nTo 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\nWasp, the wasm module calls the imported function `call_me` that appends the string passed to the function to `Hello `. This is then returned\nback to Wasp.\n\n```go\n//go:export callback\nfunc callback() WasmString {\n\t// get the string from the memory pointer\n\tname := WasmString(0)\n\tname.Copy(\"Nic\")\n\n\ts := callMe(name)\n\t//s := WasmString(0)\n\t//s.Copy(\"Hello\")\n\n\treturn s // WasmString(0)\n}\n\n```\n\nTo use callbacks they must first be registered, registration is done by creaing a Callbacks type that will contain all the callbacks for your plugin,\nYou then use the `AddCallback` method, this takes three parameters, the module name that the imported function will be available at. The name of the\nfunction 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.\n\n```go\ncb := \u0026engine.Callbacks{}\ncb.AddCallback(\"plugin\", \"call_me\", callMe)\n```\n\nOnce you have created your callbacks they can be made available to the plugin by passing the collection to the `RegisterPlugin` function.\n\n```go\nerr := e.RegisterPlugin(\"test\", *plugin, cb, nil)\n```\n\n```\n2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Called callback function: out=[\"Hello Nic\"]\n2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Allocated memory in host: size=10 addr=131168\n2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Called function: name=callback outputParam=0xc00009c500 inputParam=[] response=131168 time taken=160µs\n2021-04-12T17:44:41.957+0100 [DEBUG] main.engine: Got string from memory: addr=131168 result=\"Hello Nic\"\n2021-04-12T17:44:41.957+0100 [INFO]  main: Response from function: name=callback result=\"Hello Nic\"\n```\n\n## Benchmarks:\n\nCalling 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.\n\n```shell\n➜ go test -bench=. ./...\ngoos: linux\ngoarch: amd64\npkg: github.com/nicholasjackson/wasp/engine\ncpu: AMD Ryzen 9 3950X 16-Core Processor            \nBenchmarkIntFuncGoWASM-32                   6778                 370470 ns/op\nBenchmarkStringFuncGoWASM-32                2262                 580965 ns/op\nBenchmarkIntFuncNative-32               1000000000               0.2292 ns/op\nBenchmarkStringFuncNative-32            80865258                  14.37 ns/op\nPASS\nok      github.com/nicholasjackson/wasp/engine  7.420s\n?       github.com/nicholasjackson/wasp/engine/logger   [no test files]\n?       github.com/nicholasjackson/wasp/example [no test files]\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicholasjackson%2Fwasp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnicholasjackson%2Fwasp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicholasjackson%2Fwasp/lists"}