Skip to content

Commit

Permalink
add new readme
Browse files Browse the repository at this point in the history
  • Loading branch information
neuronicnobody committed Oct 19, 2023
1 parent 9edca66 commit 6b27360
Showing 1 changed file with 201 additions and 1 deletion.
202 changes: 201 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,206 @@
# Extism Zig Host SDK

Zig SDK for the extism WebAssembly Plugin-System.
This repo contains the Zig code for integrating with the [Extism](https://extism.org/) runtime. Install this library into your host Zig application to run Extism plug-ins.

> **Note**: If you're unsure what Extism is or what an SDK is see our homepage: [https://extism.org](https://extism.org).
> **Note**: This is an early 1.0 release and is unstable until we hit 1.0. If you are looking to integrate now consider looking at the 0.x version in the [extism/extism](https://github.com/extism/extism/tree/main/zig) repo.
## Installation

### Install the Extism Runtime Dependency

For this library, you first need to install the Extism Runtime. You can [download the shared object directly from a release](https://github.com/extism/extism/releases) or use the [Extism CLI](https://github.com/extism/cli) to install it:

```bash
sudo extism lib install latest

#=> Fetching https://github.com/extism/extism/releases/download/v0.5.2/libextism-aarch64-apple-darwin-v0.5.2.tar.gz
#=> Copying libextism.dylib to /usr/local/lib/libextism.dylib
#=> Copying extism.h to /usr/local/include/extism.h
```

> **Note**: This library has breaking changes and targets 1.0 of the runtime. For the time being, install the runtime from our nightly development builds on git: `sudo extism lib install --version git`.
# within your Zig project directory:
mkdir -p libs
cd libs
git clone https://github.com/extism/extism.git

## Getting Started

This guide should walk you through some of the concepts in Extism and this Zig library.

### Creating A Plug-in

The primary concept in Extism is the [plug-in](https://extism.org/docs/concepts/plug-in). You can think of a plug-in as a code module stored in a `.wasm` file.

Since you may not have an Extism plug-in on hand to test, let's load a demo plug-in from the web:

```zig
// First require the library
const extism = @import("extism");
const wasm_url = extism.manifest.WasmUrl{ .url = "https://github.com/extism/plugins/releases/latest/download/count_vowels.wasm" };
const manifest = .{ .wasm = &[_]extism.manifest.Wasm{.{ .wasm_url= wasm_url }} };
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var plugin = try extism.Plugin.initFromManifest(
allocator,
manifest,
&[_]extism.Function{},
false,
);
defer plugin.deinit();
```

> **Note**: See [the Manifest docs](https://github.com/extism/zig-sdk/blob/main/src/manifest.zig#L32) as it has a rich schema and a lot of options.
### Calling A Plug-in's Exports

This plug-in was written in Rust and it does one thing, it counts vowels in a string. As such, it exposes one "export" function: `count_vowels`. We can call exports using [Extism::Plugin#call](https://github.com/extism/zig-sdk/blob/main/src/plugin.zig#L61):

```zig
try plugin.call("count_vowels", "Hello, World!");
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
```

All exports have a simple interface of bytes-in and bytes-out. This plug-in happens to take a string and return a JSON encoded string with a report of results.

### Plug-in State

Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:

```zig
try plugin.call("count_vowels", "Hello, World!");
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
try plugin.call("count_vowels", "Hello, World!");
# => {"count": 3, "total": 9, "vowels": "aeiouAEIOU"}
```

These variables will persist until this plug-in is freed or you initialize a new one.

### Configuration

Plug-ins may optionally take a configuration object. This is a static way to configure the plug-in. Our count-vowels plugin takes an optional configuration to change out which characters are considered vowels. Example:

```zig
try plugin.call("count_vowels", "Yellow, World!");
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
var config = std.json.ArrayHashMap([]const u8){};
defer config.deinit(allocator);
try config.map.put(allocator, "vowels", "aeiouyAEIOUY");
try plugin.setConfig(allocator, config);
try plugin.call("count_vowels", "Yellow, World!");
# => {"count": 4, "total": 4, "vowels": "aeiouAEIOUY"}
```

### Host Functions

Let's extend our count-vowels example a little bit: Instead of storing the `total` in an ephemeral plug-in var, let's store it in a persistent key-value store!

Wasm can't use our KV store on it's own. This is where [Host Functions](https://extism.org/docs/concepts/host-functions) come in.

[Host functions](https://extism.org/docs/concepts/host-functions) allow us to grant new capabilities to our plug-ins from our application. They are simply some zig methods you write which can be passed down and invoked from any language inside the plug-in.

Let's load the manifest like usual but load up this `count_vowels_kvstore` plug-in:

```zig
const wasm_url = extism.manifest.WasmUrl{ .url = "https://github.com/extism/plugins/releases/latest/download/count_vowels_kvstore.wasm" };
const manifest = .{ .wasm = &[_]extism.manifest.Wasm{.{ .wasm_url= wasm_url }} };
```

> *Note*: The source code for this is [here](https://github.com/extism/plugins/blob/main/count_vowels_kvstore/src/lib.rs) and is written in rust, but it could be written in any of our PDK languages.
Unlike our previous plug-in, this plug-in expects you to provide host functions that satisfy our its import interface for a KV store.

We want to expose two functions to our plugin, `kv_write(key: String, value: Bytes)` which writes a bytes value to a key and `kv_read(key: String) -> Bytes` which reads the bytes at the given `key`.

```zig
// pretend this is Redis or something
const HashMap = std.StringHashMap([]const u8);
var KV_STORE: HashMap = undefined;
export fn kv_read(plugin_ptr: ?*extism.c.ExtismCurrentPlugin, inputs: [*c]const extism.c.ExtismVal, n_inputs: u64, outputs: [*c]extism.c.ExtismVal, n_outputs: u64, user_data: ?*anyopaque) callconv(.C) void {
var curr_plugin = extism.CurrentPlugin.getCurrentPlugin(plugin_ptr orelse unreachable);
// retrieve the key from the plugin
var input_slice = inputs[0..n_inputs];
const key = curr_plugin.inputBytes(&input_slice[0]);
// Try to get the value from KV_STORE
const val = KV_STORE.get(key) orelse null;
// return the value to the plugin
var output_slice = outputs[0..n_outputs];
curr_plugin.returnBytes(&output_slice[0], val);
}
export fn kv_write(plugin_ptr: ?*extism.c.ExtismCurrentPlugin, inputs: [*c]const extism.c.ExtismVal, n_inputs: u64, outputs: [*c]extism.c.ExtismVal, n_outputs: u64, user_data: ?*anyopaque) callconv(.C) void {
var curr_plugin = extism.CurrentPlugin.getCurrentPlugin(plugin_ptr orelse unreachable);
// retrieve key and value from the plugin
var input_slice = inputs[0..n_inputs];
const key = curr_plugin.inputBytes(&input_slice[0]);
const val = curr_plugin.inputBytes(&input_slice[1]);
// write to the KV
_ = KV_STORE.put(key, val) catch unreachable;
}
```

Now we just need to create a new host environment and pass it in when loading the plug-in. Here our environment initializer takes no arguments, but you could imagine putting some customer specific instance variables in there:

```zig
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
KV_STORE = std.StringHashMap([]const u8).init(allocator);
defer KV_STORE.deinit();
var f_read = extism.Function.init(
"kv_read",
&[_]extism.c.ExtismValType{extism.c.I64},
&[_]extism.c.ExtismValType{extism.c.I64},
&kv_read,
@constCast(@as(*const anyopaque, @ptrCast("user data"))),
);
defer f_read.deinit();
var f_write = extism.Function.init(
"kv_write",
&[_]extism.c.ExtismValType{extism.c.I64, extism.c.I64},
&[_]extism.c.ExtismValType{},
&kv_write,
@constCast(@as(*const anyopaque, @ptrCast("user data"))),
);
defer f_write.deinit();
var plugin = try extism.Plugin.initFromManifest(
allocator,
manifest,
&[_]extism.Function{f_read, f_write},
false,
);
defer plugin.deinit();
```

Now we can invoke the event:

```zig
try plugin.call("count_vowels", "Hello, World!");
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
try plugin.call("count_vowels", "Hello, World!");
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
```

0 comments on commit 6b27360

Please sign in to comment.