mirror of
https://github.com/extism/extism.git
synced 2026-01-10 06:18:00 -05:00
feat: Add C API for host functions + support for C++, Python, Go, Node, OCaml (#195)
- New types:
- `ExtismValType` - Enum of WebAssembly types
- `ExtismValUnion` - A union of the possible WebAssembly types
- `ExtismVal` - A struct with `ExtismValType` and `ExtismValUnion`
- `ExtismFunction` - The host function wrapper type
- `ExtismFunctionType` - The type of the host function callback
- `ExtismCurrentPlugin` - Provides access to the currently running
plugin from inside a host function
- New functions:
- `extism_function_new` - Create a new `ExtismFunction`
- `extism_function_free` - Free an `ExtismFunction`
- `extism_current_plugin_memory`, `extism_current_plugin_memory_alloc`,
`extism_current_plugin_memory_free`,
`extism_current_plugin_memory_length` - Manage plugin memory from inside
a host functions
- Updated functions
- `extism_plugin_new` and `extsim_plugin_update` - now accept two extra
parameters for `ExtismFunction*` array and length of that array
## Notes
- Host functions take a user-data argument, which is owned by the
resulting `ExtismFunction` and will be cleaned up when
`extism_function_free` is called (if a cleanup function was passed in
with the user data)
- Host functions in every SDK require working with `ExtismVal` arguments
directly, this is pretty low-level for what is kind of a high-level
feature. We could work on adding some types to the SDKs that make
working with pointers to plugin data more accessible, maybe something
similar to how the Rust PDK handes input/output data.
- In each language the host functions more-or-less share a signature:
`(CurrentPlugin plugin, Val inputs[], Val outputs[], userData)`
- C, C++, OCaml and Go take a single userData argument but Python and
Node take a "rest" argument which allows passing any number of user-data
values
- Go requires the host function to be exported:
f9eb5ed839/go/main.go (L13-L26)
- Zig and Ruby should be relatively simple to add host functions to next
but I haven't really looked into Elixir, .NET or Java yet.
- Also closes #20
This commit is contained in:
7
Makefile
7
Makefile
@@ -18,15 +18,12 @@ else
|
||||
FEATURE_FLAGS=--features $(FEATURES)
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
lint:
|
||||
cargo clippy --release --no-deps --manifest-path runtime/Cargo.toml
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
debug:
|
||||
RUSTFLAGS=-g $(MAKE) build
|
||||
|
||||
|
||||
26
c/main.c
26
c/main.c
@@ -9,6 +9,21 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void hello_world(ExtismCurrentPlugin *plugin, const struct ExtismVal *inputs,
|
||||
uint64_t n_inputs, struct ExtismVal *outputs,
|
||||
uint64_t n_outputs, void *data) {
|
||||
puts("Hello from C!");
|
||||
puts(data);
|
||||
|
||||
ExtismSize ptr_offs = inputs[0].v.i64;
|
||||
|
||||
uint8_t *buf = extism_current_plugin_memory(plugin) + ptr_offs;
|
||||
uint64_t length = extism_current_plugin_memory_length(plugin, ptr_offs);
|
||||
fwrite(buf, length, 1, stdout);
|
||||
fputc('\n', stdout);
|
||||
outputs[0].v.i64 = inputs[0].v.i64;
|
||||
}
|
||||
|
||||
uint8_t *read_file(const char *filename, size_t *len) {
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
@@ -41,13 +56,17 @@ int main(int argc, char *argv[]) {
|
||||
ExtismContext *ctx = extism_context_new();
|
||||
|
||||
size_t len = 0;
|
||||
uint8_t *data = read_file("../wasm/code.wasm", &len);
|
||||
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
|
||||
uint8_t *data = read_file("../wasm/code-functions.wasm", &len);
|
||||
ExtismValType inputs[] = {I64};
|
||||
ExtismValType outputs[] = {I64};
|
||||
ExtismFunction *f = extism_function_new("hello_world", inputs, 1, outputs, 1,
|
||||
hello_world, "Hello, again!", NULL);
|
||||
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, &f, 1, true);
|
||||
free(data);
|
||||
if (plugin < 0) {
|
||||
puts(extism_error(ctx, -1));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(extism_plugin_call(ctx, plugin, "count_vowels", (uint8_t *)argv[1],
|
||||
strlen(argv[1])) == 0);
|
||||
ExtismSize out_len = extism_plugin_output_length(ctx, plugin);
|
||||
@@ -56,6 +75,7 @@ int main(int argc, char *argv[]) {
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
|
||||
extism_plugin_free(ctx, plugin);
|
||||
extism_function_free(f);
|
||||
extism_context_free(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
|
||||
|
||||
build-example:
|
||||
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
|
||||
$(CXX) -std=c++14 -o example -I. example.cpp $(FLAGS)
|
||||
|
||||
.PHONY: example
|
||||
example: build-example
|
||||
./example
|
||||
|
||||
build-test:
|
||||
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
$(CXX) -std=c++14 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
|
||||
.PHONY: test
|
||||
test: build-test
|
||||
|
||||
@@ -14,10 +14,27 @@ std::vector<uint8_t> read(const char *filename) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
auto wasm = read("../wasm/code.wasm");
|
||||
auto wasm = read("../wasm/code-functions.wasm");
|
||||
Context context = Context();
|
||||
std::string tmp = "Testing";
|
||||
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
// A lambda can be used as a host function
|
||||
auto hello_world = [&tmp](CurrentPlugin plugin,
|
||||
const std::vector<Val> &inputs,
|
||||
std::vector<Val> &outputs, void *user_data) {
|
||||
std::cout << "Hello from C++" << std::endl;
|
||||
std::cout << (const char *)user_data << std::endl;
|
||||
std::cout << tmp << std::endl;
|
||||
outputs[0].v = inputs[0].v;
|
||||
};
|
||||
|
||||
std::vector<Function> functions = {
|
||||
Function("hello_world", {ValType::I64}, {ValType::I64}, hello_world,
|
||||
(void *)"Hello again!",
|
||||
[](void *x) { std::cout << "Free user data" << std::endl; }),
|
||||
};
|
||||
|
||||
Plugin plugin = context.plugin(wasm, true, functions);
|
||||
|
||||
const char *input = argc > 1 ? argv[1] : "this is a test";
|
||||
ExtismSize length = strlen(input);
|
||||
|
||||
284
cpp/extism.hpp
284
cpp/extism.hpp
@@ -1,7 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -20,27 +22,62 @@ extern "C" {
|
||||
namespace extism {
|
||||
|
||||
typedef std::map<std::string, std::string> Config;
|
||||
class Wasm {
|
||||
|
||||
template <typename T> class ManifestKey {
|
||||
bool is_set = false;
|
||||
|
||||
public:
|
||||
std::string path;
|
||||
std::string url;
|
||||
T value;
|
||||
ManifestKey(T x, bool is_set = false) : is_set(is_set) { value = x; }
|
||||
|
||||
void set(T x) {
|
||||
value = x;
|
||||
is_set = true;
|
||||
}
|
||||
|
||||
bool empty() const { return is_set == false; }
|
||||
};
|
||||
|
||||
class Wasm {
|
||||
std::string _path;
|
||||
std::string _url;
|
||||
// TODO: add base64 encoded raw data
|
||||
std::string hash;
|
||||
ManifestKey<std::string> _hash =
|
||||
ManifestKey<std::string>(std::string(), false);
|
||||
|
||||
public:
|
||||
// Create Wasm pointing to a path
|
||||
static Wasm path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w._path = s;
|
||||
if (!hash.empty()) {
|
||||
w._hash.set(hash);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
// Create Wasm pointing to a URL
|
||||
static Wasm url(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w._url = s;
|
||||
if (!hash.empty()) {
|
||||
w._hash.set(hash);
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Json::Value json() const {
|
||||
Json::Value doc;
|
||||
|
||||
if (!this->path.empty()) {
|
||||
doc["path"] = this->path;
|
||||
if (!this->_path.empty()) {
|
||||
doc["path"] = this->_path;
|
||||
} else if (!this->_url.empty()) {
|
||||
doc["url"] = this->_url;
|
||||
}
|
||||
|
||||
if (!this->url.empty()) {
|
||||
doc["url"] = this->url;
|
||||
}
|
||||
|
||||
if (!this->hash.empty()) {
|
||||
doc["hash"] = this->hash;
|
||||
if (!this->_hash.empty()) {
|
||||
doc["hash"] = this->_hash.value;
|
||||
}
|
||||
|
||||
return doc;
|
||||
@@ -52,18 +89,23 @@ class Manifest {
|
||||
public:
|
||||
Config config;
|
||||
std::vector<Wasm> wasm;
|
||||
std::vector<std::string> allowed_hosts;
|
||||
std::map<std::string, std::string> allowed_paths;
|
||||
uint64_t timeout_ms;
|
||||
ManifestKey<std::vector<std::string>> allowed_hosts;
|
||||
ManifestKey<std::map<std::string, std::string>> allowed_paths;
|
||||
ManifestKey<uint64_t> timeout_ms;
|
||||
|
||||
Manifest() : timeout_ms(30000) {}
|
||||
// Empty manifest
|
||||
Manifest()
|
||||
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
|
||||
allowed_paths(std::map<std::string, std::string>(), false) {}
|
||||
|
||||
// Create manifest with a single Wasm from a path
|
||||
static Manifest path(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_path(s, hash);
|
||||
return m;
|
||||
}
|
||||
|
||||
// Create manifest with a single Wasm from a URL
|
||||
static Manifest url(std::string s, std::string hash = std::string()) {
|
||||
Manifest m;
|
||||
m.add_wasm_url(s, hash);
|
||||
@@ -92,7 +134,7 @@ public:
|
||||
if (!this->allowed_hosts.empty()) {
|
||||
Json::Value h;
|
||||
|
||||
for (auto s : this->allowed_hosts) {
|
||||
for (auto s : this->allowed_hosts.value) {
|
||||
h.append(s);
|
||||
}
|
||||
doc["allowed_hosts"] = h;
|
||||
@@ -100,54 +142,63 @@ public:
|
||||
|
||||
if (!this->allowed_paths.empty()) {
|
||||
Json::Value h;
|
||||
for (auto k : this->allowed_paths) {
|
||||
for (auto k : this->allowed_paths.value) {
|
||||
h[k.first] = k.second;
|
||||
}
|
||||
doc["allowed_paths"] = h;
|
||||
}
|
||||
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms);
|
||||
if (!this->timeout_ms.empty()) {
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
|
||||
}
|
||||
|
||||
Json::FastWriter writer;
|
||||
return writer.write(doc);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add Wasm from path
|
||||
void add_wasm_path(std::string s, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.path = s;
|
||||
w.hash = hash;
|
||||
Wasm w = Wasm::path(s, hash);
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
// Add Wasm from URL
|
||||
void add_wasm_url(std::string u, std::string hash = std::string()) {
|
||||
Wasm w;
|
||||
w.url = u;
|
||||
w.hash = hash;
|
||||
Wasm w = Wasm::url(u, hash);
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
void allow_host(std::string host) { this->allowed_hosts.push_back(host); }
|
||||
// Add host to allowed hosts
|
||||
void allow_host(std::string host) {
|
||||
if (this->allowed_hosts.empty()) {
|
||||
this->allowed_hosts.set(std::vector<std::string>{});
|
||||
}
|
||||
this->allowed_hosts.value.push_back(host);
|
||||
}
|
||||
|
||||
// Add path to allowed paths
|
||||
void allow_path(std::string src, std::string dest = std::string()) {
|
||||
if (this->allowed_paths.empty()) {
|
||||
this->allowed_paths.set(std::map<std::string, std::string>{});
|
||||
}
|
||||
|
||||
if (dest.empty()) {
|
||||
dest = src;
|
||||
}
|
||||
this->allowed_paths[src] = dest;
|
||||
this->allowed_paths.value[src] = dest;
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
void set_timeout_ms(uint64_t ms) { this->timeout_ms = ms; }
|
||||
|
||||
// Set config key/value
|
||||
void set_config(std::string k, std::string v) { this->config[k] = v; }
|
||||
};
|
||||
|
||||
class Error : public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
|
||||
class Error : public std::runtime_error {
|
||||
public:
|
||||
Error(std::string msg) : message(msg) {}
|
||||
const char *what() { return message.c_str(); }
|
||||
Error(std::string msg) : std::runtime_error(msg) {}
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
@@ -166,14 +217,102 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
typedef ExtismValType ValType;
|
||||
typedef ExtismValUnion ValUnion;
|
||||
typedef ExtismVal Val;
|
||||
|
||||
class CurrentPlugin {
|
||||
ExtismCurrentPlugin *pointer;
|
||||
|
||||
public:
|
||||
CurrentPlugin(ExtismCurrentPlugin *p) : pointer(p) {}
|
||||
|
||||
uint8_t *memory() { return extism_current_plugin_memory(this->pointer); }
|
||||
ExtismSize memory_length(uint64_t offs) {
|
||||
return extism_current_plugin_memory_length(this->pointer, offs);
|
||||
}
|
||||
|
||||
uint64_t alloc(ExtismSize size) {
|
||||
return extism_current_plugin_memory_alloc(this->pointer, size);
|
||||
}
|
||||
|
||||
void free(uint64_t offs) {
|
||||
extism_current_plugin_memory_free(this->pointer, offs);
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::function<void(CurrentPlugin, const std::vector<Val> &,
|
||||
std::vector<Val> &, void *user_data)>
|
||||
FunctionType;
|
||||
|
||||
struct UserData {
|
||||
FunctionType func;
|
||||
void *user_data = NULL;
|
||||
std::function<void(void *)> free_user_data;
|
||||
};
|
||||
|
||||
static void function_callback(ExtismCurrentPlugin *plugin,
|
||||
const ExtismVal *inputs, ExtismSize n_inputs,
|
||||
ExtismVal *outputs, ExtismSize n_outputs,
|
||||
void *user_data) {
|
||||
UserData *data = (UserData *)user_data;
|
||||
const std::vector<Val> inp(inputs, inputs + n_inputs);
|
||||
std::vector<Val> outp(outputs, outputs + n_outputs);
|
||||
data->func(CurrentPlugin(plugin), inp, outp, data->user_data);
|
||||
|
||||
for (ExtismSize i = 0; i < n_outputs; i++) {
|
||||
outputs[i] = outp[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void free_user_data(void *user_data) {
|
||||
UserData *data = (UserData *)user_data;
|
||||
if (data->user_data != NULL && data->free_user_data != NULL) {
|
||||
data->free_user_data(data->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
class Function {
|
||||
std::shared_ptr<ExtismFunction> func;
|
||||
std::string name;
|
||||
UserData user_data;
|
||||
|
||||
public:
|
||||
Function(std::string name, const std::vector<ValType> inputs,
|
||||
const std::vector<ValType> outputs, FunctionType f,
|
||||
void *user_data = NULL, std::function<void(void *)> free = nullptr)
|
||||
: name(name) {
|
||||
this->user_data.func = f;
|
||||
this->user_data.user_data = user_data;
|
||||
this->user_data.free_user_data = free;
|
||||
auto ptr = extism_function_new(
|
||||
this->name.c_str(), inputs.data(), inputs.size(), outputs.data(),
|
||||
outputs.size(), function_callback, &this->user_data, free_user_data);
|
||||
this->func = std::shared_ptr<ExtismFunction>(ptr, extism_function_free);
|
||||
}
|
||||
|
||||
Function(const Function &f) { this->func = f.func; }
|
||||
|
||||
ExtismFunction *get() { return this->func.get(); }
|
||||
};
|
||||
|
||||
class Plugin {
|
||||
std::shared_ptr<ExtismContext> context;
|
||||
ExtismPlugin plugin;
|
||||
std::vector<Function> functions;
|
||||
|
||||
public:
|
||||
// Create a new plugin
|
||||
Plugin(std::shared_ptr<ExtismContext> ctx, const uint8_t *wasm,
|
||||
ExtismSize length, bool with_wasi = false) {
|
||||
this->plugin = extism_plugin_new(ctx.get(), wasm, length, with_wasi);
|
||||
ExtismSize length, bool with_wasi = false,
|
||||
std::vector<Function> functions = std::vector<Function>())
|
||||
: functions(functions) {
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
this->plugin = extism_plugin_new(ctx.get(), wasm, length, ptrs.data(),
|
||||
ptrs.size(), with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin" : err);
|
||||
@@ -182,11 +321,18 @@ public:
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
// Create a new plugin from Manifest
|
||||
Plugin(std::shared_ptr<ExtismContext> ctx, const Manifest &manifest,
|
||||
bool with_wasi = false) {
|
||||
bool with_wasi = false, std::vector<Function> functions = {}) {
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
|
||||
auto buffer = manifest.json();
|
||||
this->plugin = extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), with_wasi);
|
||||
this->plugin =
|
||||
extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (this->plugin < 0) {
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin from manifest" : err);
|
||||
@@ -204,9 +350,15 @@ public:
|
||||
|
||||
ExtismContext *get_context() const { return this->context.get(); }
|
||||
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) {
|
||||
this->functions = functions;
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
|
||||
length, with_wasi);
|
||||
length, ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
@@ -214,11 +366,17 @@ public:
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
void update(const Manifest &manifest, bool with_wasi = false) {
|
||||
void update(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) {
|
||||
this->functions = functions;
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
auto buffer = manifest.json();
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin,
|
||||
(const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), with_wasi);
|
||||
bool b = extism_plugin_update(
|
||||
this->context.get(), this->plugin, (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
@@ -251,6 +409,7 @@ public:
|
||||
this->config(json.c_str(), json.size());
|
||||
}
|
||||
|
||||
// Call a plugin
|
||||
Buffer call(const std::string &func, const uint8_t *input,
|
||||
ExtismSize input_length) const {
|
||||
int32_t rc = extism_plugin_call(this->context.get(), this->plugin,
|
||||
@@ -271,15 +430,19 @@ public:
|
||||
return Buffer(ptr, length);
|
||||
}
|
||||
|
||||
// Call a plugin function with std::vector<uint8_t> input
|
||||
Buffer call(const std::string &func,
|
||||
const std::vector<uint8_t> &input) const {
|
||||
return this->call(func, input.data(), input.size());
|
||||
}
|
||||
|
||||
Buffer call(const std::string &func, const std::string &input) const {
|
||||
// Call a plugin function with string input
|
||||
Buffer call(const std::string &func,
|
||||
const std::string &input = std::string()) const {
|
||||
return this->call(func, (const uint8_t *)input.c_str(), input.size());
|
||||
}
|
||||
|
||||
// Returns true if the specified function exists
|
||||
bool function_exists(const std::string &func) const {
|
||||
return extism_plugin_function_exists(this->context.get(), this->plugin,
|
||||
func.c_str());
|
||||
@@ -290,38 +453,49 @@ class Context {
|
||||
public:
|
||||
std::shared_ptr<ExtismContext> pointer;
|
||||
|
||||
// Create a new context;
|
||||
Context() {
|
||||
this->pointer = std::shared_ptr<ExtismContext>(extism_context_new(),
|
||||
extism_context_free);
|
||||
}
|
||||
|
||||
Plugin plugin(const uint8_t *wasm, size_t length,
|
||||
bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, wasm, length, with_wasi);
|
||||
// Create plugin from uint8_t*
|
||||
Plugin plugin(const uint8_t *wasm, size_t length, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(this->pointer, wasm, length, with_wasi, functions);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) const {
|
||||
// Create plugin from std::string
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.size(),
|
||||
with_wasi);
|
||||
with_wasi, functions);
|
||||
}
|
||||
|
||||
Plugin plugin(const std::vector<uint8_t> &data,
|
||||
bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
|
||||
// Create plugin from uint8_t vector
|
||||
Plugin plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(this->pointer, data.data(), data.size(), with_wasi,
|
||||
functions);
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, manifest, with_wasi);
|
||||
// Create plugin from Manifest
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(this->pointer, manifest, with_wasi, functions);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Remove all plugins
|
||||
void reset() { extism_context_reset(this->pointer.get()); }
|
||||
};
|
||||
|
||||
// Set global log file for plugins
|
||||
inline bool set_log_file(const char *filename, const char *level) {
|
||||
return extism_log_file(filename, level);
|
||||
}
|
||||
|
||||
// Get libextism version
|
||||
inline std::string version() { return std::string(extism_version()); }
|
||||
} // namespace extism
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Context : IDisposable
|
||||
{
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, null, 0, withWasi);
|
||||
return new Plugin(this, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@ internal static class LibExtism
|
||||
/// <param name="context">Pointer to the context the plugin will be associated with.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmSize">The length of the `wasm` parameter.</param>
|
||||
/// <param name="functions">Array of host function pointers.</param>
|
||||
/// <param name="nFunctions">Number of host functions.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, bool withWasi);
|
||||
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, IntPtr *functions, int nFunctions, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
@@ -41,10 +43,12 @@ internal static class LibExtism
|
||||
/// <param name="plugin">Pointer to the plugin you want to update.</param>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="wasmLength">The length of the `wasm` parameter.</param>
|
||||
/// <param name="functions">Array of host function pointers.</param>
|
||||
/// <param name="nFunctions">Number of host functions.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, bool withWasi);
|
||||
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, IntPtr *functions, int nFunctions, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
|
||||
@@ -35,7 +35,7 @@ public class Plugin : IDisposable
|
||||
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, null, 0, withWasi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,7 @@ fn load(env: Env, _: Term) -> bool {
|
||||
}
|
||||
|
||||
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
|
||||
match extism_error {
|
||||
extism::Error::UnableToLoadPlugin(msg) => rustler::Error::Term(Box::new(msg)),
|
||||
extism::Error::Message(msg) => rustler::Error::Term(Box::new(msg)),
|
||||
extism::Error::Json(json_err) => rustler::Error::Term(Box::new(json_err.to_string())),
|
||||
extism::Error::Runtime(e) => rustler::Error::Term(Box::new(e.to_string())),
|
||||
}
|
||||
rustler::Error::Term(Box::new(extism_error.to_string()))
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
@@ -61,7 +56,7 @@ fn plugin_new_with_manifest(
|
||||
wasi: bool,
|
||||
) -> Result<i32, rustler::Error> {
|
||||
let context = &ctx.ctx.write().unwrap();
|
||||
let result = match Plugin::new(context, manifest_payload, wasi) {
|
||||
let result = match Plugin::new(context, manifest_payload, [], wasi) {
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
Ok(plugin) => {
|
||||
let plugin_id = plugin.as_i32();
|
||||
@@ -107,7 +102,7 @@ fn plugin_update_manifest(
|
||||
) -> Result<(), rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let result = match plugin.update(manifest_payload, wasi) {
|
||||
let result = match plugin.update(manifest_payload, [], wasi) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
};
|
||||
|
||||
160
extism.go
160
extism.go
@@ -9,7 +9,8 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: libextism.pc
|
||||
#cgo CFLAGS: -I/usr/local/include
|
||||
#cgo LDFLAGS: -L/usr/local/lib -lextism
|
||||
#include <extism.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
@@ -20,6 +21,69 @@ type Context struct {
|
||||
pointer *C.ExtismContext
|
||||
}
|
||||
|
||||
type ValType = C.ExtismValType
|
||||
|
||||
type Val = C.ExtismVal
|
||||
|
||||
type Size = C.ExtismSize
|
||||
|
||||
var (
|
||||
I32 ValType = C.I32
|
||||
I64 ValType = C.I64
|
||||
F32 ValType = C.F32
|
||||
F64 ValType = C.F64
|
||||
FuncRef ValType = C.FuncRef
|
||||
ExternRef ValType = C.ExternRef
|
||||
)
|
||||
|
||||
// Function is used to define host functions
|
||||
type Function struct {
|
||||
pointer *C.ExtismFunction
|
||||
userData interface{}
|
||||
}
|
||||
|
||||
// Free a function
|
||||
func (f *Function) Free() {
|
||||
C.extism_function_free(f.pointer)
|
||||
f.pointer = nil
|
||||
}
|
||||
|
||||
// NewFunction creates a new host function with the given name, input/outputs and optional user data, which can be an
|
||||
// arbitrary `interface{}`
|
||||
func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Pointer, userData interface{}) Function {
|
||||
var function Function
|
||||
function.userData = userData
|
||||
cname := C.CString(name)
|
||||
function.pointer = C.extism_function_new(
|
||||
cname,
|
||||
(*C.ExtismValType)(&inputs[0]),
|
||||
C.uint64_t(len(inputs)),
|
||||
(*C.ExtismValType)(&outputs[0]),
|
||||
C.uint64_t(len(outputs)),
|
||||
(*[0]byte)(f),
|
||||
unsafe.Pointer(&function.userData),
|
||||
nil,
|
||||
)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
return function
|
||||
}
|
||||
|
||||
type CurrentPlugin struct {
|
||||
pointer *C.ExtismCurrentPlugin
|
||||
}
|
||||
|
||||
func GetCurrentPlugin(ptr *C.ExtismCurrentPlugin) CurrentPlugin {
|
||||
return CurrentPlugin{
|
||||
pointer: ptr,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) Memory(offs uint) []byte {
|
||||
length := C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs))
|
||||
data := unsafe.Pointer(C.extism_current_plugin_memory(p.pointer))
|
||||
return unsafe.Slice((*byte)(unsafe.Add(data, offs)), C.int(length))
|
||||
}
|
||||
|
||||
// NewContext creates a new context, it should be freed using the `Free` method
|
||||
func NewContext() Context {
|
||||
p := C.extism_context_new()
|
||||
@@ -96,14 +160,32 @@ func ExtismVersion() string {
|
||||
return C.GoString(C.extism_version())
|
||||
}
|
||||
|
||||
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
|
||||
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
|
||||
ptr := makePointer(data)
|
||||
plugin := C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
plugin := C.int32_t(-1)
|
||||
|
||||
if len(functions) == 0 {
|
||||
plugin = C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
nil,
|
||||
0,
|
||||
C._Bool(wasi))
|
||||
} else {
|
||||
plugin = C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
&functionPointers[0],
|
||||
C.uint64_t(len(functions)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
}
|
||||
|
||||
if plugin < 0 {
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
@@ -120,18 +202,41 @@ func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
|
||||
return Plugin{id: int32(plugin), ctx: ctx}, nil
|
||||
}
|
||||
|
||||
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
|
||||
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
|
||||
ptr := makePointer(data)
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
|
||||
if b {
|
||||
return nil
|
||||
if len(functions) == 0 {
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
nil,
|
||||
0,
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
&functionPointers[0],
|
||||
C.uint64_t(len(functions)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
@@ -146,43 +251,43 @@ func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
|
||||
}
|
||||
|
||||
// PluginFromManifest creates a plugin from a `Manifest`
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(ctx, data, wasi)
|
||||
return register(ctx, data, functions, wasi)
|
||||
}
|
||||
|
||||
// Plugin creates a plugin from a WASM module
|
||||
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(ctx, wasm, wasi)
|
||||
return register(ctx, wasm, functions, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new WASM module
|
||||
func (p *Plugin) Update(module io.Reader, wasi bool) error {
|
||||
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.ctx, p.id, wasm, wasi)
|
||||
return update(p.ctx, p.id, wasm, functions, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new Manifest
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return update(p.ctx, p.id, data, wasi)
|
||||
return update(p.ctx, p.id, data, functions, wasi)
|
||||
}
|
||||
|
||||
// Set configuration values
|
||||
@@ -233,8 +338,7 @@ func (plugin Plugin) Call(functionName string, input []byte) ([]byte, error) {
|
||||
|
||||
if length > 0 {
|
||||
x := C.extism_plugin_output_data(plugin.ctx.pointer, C.int32_t(plugin.id))
|
||||
y := (*[]byte)(unsafe.Pointer(&x))
|
||||
return []byte((*y)[0:length]), nil
|
||||
return unsafe.Slice((*byte)(x), C.int(length)), nil
|
||||
}
|
||||
|
||||
return []byte{}, nil
|
||||
|
||||
24
go/main.go
24
go/main.go
@@ -4,10 +4,27 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/extism/extism"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <extism.h>
|
||||
EXTISM_GO_FUNCTION(hello_world);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
//export hello_world
|
||||
func hello_world(plugin *C.ExtismCurrentPlugin, inputs *C.ExtismVal, nInputs C.ExtismSize, outputs *C.ExtismVal, nOutputs C.ExtismSize, userData unsafe.Pointer) {
|
||||
fmt.Println("Hello from Go!")
|
||||
s := *(*interface{})(userData)
|
||||
fmt.Println(s.(string))
|
||||
inputSlice := unsafe.Slice(inputs, nInputs)
|
||||
outputSlice := unsafe.Slice(outputs, nOutputs)
|
||||
outputSlice[0] = inputSlice[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
version := extism.ExtismVersion()
|
||||
fmt.Println("Extism Version: ", version)
|
||||
@@ -22,9 +39,10 @@ func main() {
|
||||
} else {
|
||||
data = []byte("testing from go -> wasm shared memory...")
|
||||
}
|
||||
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
|
||||
plugin, err := ctx.PluginFromManifest(manifest, false)
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code-functions.wasm"}}}
|
||||
f := extism.NewFunction("hello_world", []extism.ValType{extism.I64}, []extism.ValType{extism.I64}, C.hello_world, "Hello again!")
|
||||
defer f.Free()
|
||||
plugin, err := ctx.PluginFromManifest(manifest, []extism.Function{f}, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -70,7 +70,7 @@ plugin c wasm useWasi =
|
||||
do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_new ctx (castPtr s) length wasi)
|
||||
extism_plugin_new ctx (castPtr s) length nullPtr 0 wasi )
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
@@ -92,7 +92,7 @@ update (Plugin (Context ctx) id) wasm useWasi =
|
||||
do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- unsafeUseAsCString wasm (\s ->
|
||||
extism_plugin_update ctx id (castPtr s) length wasi)
|
||||
extism_plugin_update ctx id (castPtr s) length nullPtr 0 wasi)
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
|
||||
@@ -9,18 +9,19 @@ import Data.Int
|
||||
import Data.Word
|
||||
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
newtype ExtismFunction = ExtismFunction () deriving Show
|
||||
|
||||
foreign import ccall unsafe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
|
||||
foreign import ccall unsafe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
|
||||
foreign import ccall unsafe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> CBool -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall unsafe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
|
||||
foreign import ccall unsafe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
|
||||
foreign import ccall unsafe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
foreign import ccall unsafe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
|
||||
foreign import ccall unsafe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
|
||||
foreign import ccall unsafe "extism.h extism_version" extism_version :: IO CString
|
||||
foreign import ccall safe "extism.h extism_context_new" extism_context_new :: IO (Ptr ExtismContext)
|
||||
foreign import ccall safe "extism.h &extism_context_free" extism_context_free :: FunPtr (Ptr ExtismContext -> IO ())
|
||||
foreign import ccall safe "extism.h extism_plugin_new" extism_plugin_new :: Ptr ExtismContext -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO Int32
|
||||
foreign import ccall safe "extism.h extism_plugin_update" extism_plugin_update :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Word64 -> Ptr (Ptr ExtismFunction) -> Word64 -> CBool -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_call" extism_plugin_call :: Ptr ExtismContext -> Int32 -> CString -> Ptr Word8 -> Word64 -> IO Int32
|
||||
foreign import ccall safe "extism.h extism_plugin_function_exists" extism_plugin_function_exists :: Ptr ExtismContext -> Int32 -> CString -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_error" extism_error :: Ptr ExtismContext -> Int32 -> IO CString
|
||||
foreign import ccall safe "extism.h extism_plugin_output_length" extism_plugin_output_length :: Ptr ExtismContext -> Int32 -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_plugin_output_data" extism_plugin_output_data :: Ptr ExtismContext -> Int32 -> IO (Ptr Word8)
|
||||
foreign import ccall safe "extism.h extism_log_file" extism_log_file :: CString -> CString -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_config" extism_plugin_config :: Ptr ExtismContext -> Int32 -> Ptr Word8 -> Int64 -> IO CBool
|
||||
foreign import ccall safe "extism.h extism_plugin_free" extism_plugin_free :: Ptr ExtismContext -> Int32 -> IO ()
|
||||
foreign import ccall safe "extism.h extism_context_reset" extism_context_reset :: Ptr ExtismContext -> IO ()
|
||||
foreign import ccall safe "extism.h extism_version" extism_version :: IO CString
|
||||
|
||||
@@ -56,10 +56,12 @@ public interface LibExtism extends Library {
|
||||
* @param contextPointer pointer to the {@link Context}.
|
||||
* @param wasm is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
* @param wasmSize the length of the `wasm` parameter
|
||||
* @param functions host functions
|
||||
* @param nFunctions the number of host functions
|
||||
* @param withWASI enables/disables WASI
|
||||
* @return id of the plugin or {@literal -1} in case of error
|
||||
*/
|
||||
int extism_plugin_new(long contextPointer, byte[] wasm, long wasmSize, boolean withWASI);
|
||||
int extism_plugin_new(Pointer contextPointer, byte[] wasm, long wasmSize, Pointer functions, int nFunctions, boolean withWASI);
|
||||
|
||||
/**
|
||||
* Returns the Extism version string
|
||||
@@ -117,10 +119,12 @@ public interface LibExtism extends Library {
|
||||
* @param pluginIndex
|
||||
* @param wasm
|
||||
* @param length
|
||||
* @param functions host functions
|
||||
* @param nFunctions the number of host functions
|
||||
* @param withWASI
|
||||
* @return {@literal true} if update was successful
|
||||
*/
|
||||
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, boolean withWASI);
|
||||
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, Pointer functions, int nFunctions, boolean withWASI);
|
||||
|
||||
/**
|
||||
* Remove a plugin from the registry and free associated memory.
|
||||
|
||||
@@ -36,7 +36,7 @@ public class Plugin implements AutoCloseable {
|
||||
Objects.requireNonNull(manifestBytes, "manifestBytes");
|
||||
|
||||
Pointer contextPointer = context.getPointer();
|
||||
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length, withWASI);
|
||||
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length, null, 0, withWASI);
|
||||
if (index == -1) {
|
||||
String error = context.error(this);
|
||||
throw new ExtismException(error);
|
||||
@@ -125,7 +125,7 @@ public class Plugin implements AutoCloseable {
|
||||
*/
|
||||
public boolean update(byte[] manifestBytes, boolean withWASI) {
|
||||
Objects.requireNonNull(manifestBytes, "manifestBytes");
|
||||
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length, withWASI);
|
||||
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length, null, 0, withWASI);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
10
libextism.pc
10
libextism.pc
@@ -1,10 +0,0 @@
|
||||
prefix=/usr/local
|
||||
exec_prefix=${prefix}
|
||||
includedir=${prefix}/include
|
||||
libdir=${exec_prefix}/lib
|
||||
|
||||
Name: extism
|
||||
Description: The Extism universal plug-in system.
|
||||
Version: 0.1.0
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lextism
|
||||
@@ -1,9 +1,33 @@
|
||||
const { withContext, Context } = require('./dist/index.js');
|
||||
const { readFileSync } = require('fs');
|
||||
const {
|
||||
withContext,
|
||||
Context,
|
||||
HostFunction,
|
||||
ValType,
|
||||
} = require("./dist/index.js");
|
||||
const { readFileSync } = require("fs");
|
||||
|
||||
function f(currentPlugin, inputs, outputs, userData) {
|
||||
let mem = currentPlugin.memory(inputs[0].v.i64);
|
||||
console.log(mem.length);
|
||||
console.log(mem.toString());
|
||||
console.log("Hello from Javascript!");
|
||||
console.log(userData);
|
||||
outputs[0] = inputs[0];
|
||||
}
|
||||
|
||||
let hello_world = new HostFunction(
|
||||
"hello_world",
|
||||
[ValType.I64],
|
||||
[ValType.I64],
|
||||
f,
|
||||
"Hello again!"
|
||||
);
|
||||
|
||||
let functions = [hello_world];
|
||||
|
||||
withContext(async function (context) {
|
||||
let wasm = readFileSync("../wasm/code.wasm");
|
||||
let p = context.plugin(wasm);
|
||||
let wasm = readFileSync("../wasm/code-functions.wasm");
|
||||
let p = context.plugin(wasm, true, functions);
|
||||
|
||||
if (!p.functionExists("count_vowels")) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
@@ -16,7 +40,7 @@ withContext(async function (context) {
|
||||
});
|
||||
|
||||
// or, use a context like this:
|
||||
let ctx = new Context();
|
||||
let wasm = readFileSync("../wasm/code.wasm");
|
||||
let p = ctx.plugin(wasm);
|
||||
// let ctx = new Context();
|
||||
// let wasm = readFileSync("../wasm/code.wasm");
|
||||
// let p = ctx.plugin(wasm, wasi = true);
|
||||
// ... where the context can be passed around to various functions etc.
|
||||
|
||||
3114
node/package-lock.json
generated
3114
node/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,12 +21,17 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"example": "node example.js",
|
||||
"example": "npm run build && node example.js",
|
||||
"build": "tsc",
|
||||
"test": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"ffi-napi": "^4.0.3"
|
||||
"@types/ref-array-di": "^1.2.5",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"ref-array-di": "^1.2.2",
|
||||
"ref-napi": "^3.0.3",
|
||||
"ref-struct-di": "^1.1.1",
|
||||
"ref-union-di": "^1.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -1,30 +1,96 @@
|
||||
import ffi from "ffi-napi";
|
||||
import ref from "ref-napi";
|
||||
import path from "path";
|
||||
|
||||
const context = "void*";
|
||||
var ArrayType = require("ref-array-di")(ref);
|
||||
var StructType = require("ref-struct-di")(ref);
|
||||
var UnionType = require("ref-union-di")(ref);
|
||||
|
||||
const opaque = ref.types.void;
|
||||
const context = ref.refType(opaque);
|
||||
|
||||
const function_t = ref.refType(opaque);
|
||||
const pluginIndex = ref.types.int32;
|
||||
|
||||
let ValTypeArray = ArrayType(ref.types.int);
|
||||
let PtrArray = new ArrayType("void*");
|
||||
|
||||
let ValUnion = new UnionType({
|
||||
i32: ref.types.uint32,
|
||||
i64: ref.types.uint64,
|
||||
f32: ref.types.float,
|
||||
f64: ref.types.double,
|
||||
});
|
||||
|
||||
/**
|
||||
* Val struct, low-level WebAssembly values
|
||||
*/
|
||||
let Val = new StructType({
|
||||
t: ref.types.int,
|
||||
v: ValUnion,
|
||||
});
|
||||
|
||||
/**
|
||||
* Array of `Val`
|
||||
*/
|
||||
let ValArray = ArrayType(Val);
|
||||
|
||||
const _functions = {
|
||||
extism_context_new: [context, []],
|
||||
extism_context_free: ["void", [context]],
|
||||
extism_plugin_new: ["int32", [context, "string", "uint64", "bool"]],
|
||||
extism_plugin_new: [
|
||||
pluginIndex,
|
||||
[context, "string", "uint64", PtrArray, "uint64", "bool"],
|
||||
],
|
||||
extism_plugin_update: [
|
||||
"bool",
|
||||
[context, "int32", "string", "uint64", "bool"],
|
||||
[context, pluginIndex, "string", "uint64", PtrArray, "uint64", "bool"],
|
||||
],
|
||||
extism_error: ["char*", [context, "int32"]],
|
||||
extism_error: ["string", [context, pluginIndex]],
|
||||
extism_plugin_call: [
|
||||
"int32",
|
||||
[context, "int32", "string", "string", "uint64"],
|
||||
[context, pluginIndex, "string", "string", "uint64"],
|
||||
],
|
||||
extism_plugin_output_length: ["uint64", [context, "int32"]],
|
||||
extism_plugin_output_data: ["uint8*", [context, "int32"]],
|
||||
extism_plugin_output_length: ["uint64", [context, pluginIndex]],
|
||||
extism_plugin_output_data: ["uint8*", [context, pluginIndex]],
|
||||
extism_log_file: ["bool", ["string", "char*"]],
|
||||
extism_plugin_function_exists: ["bool", [context, "int32", "string"]],
|
||||
extism_plugin_config: ["void", [context, "int32", "char*", "uint64"]],
|
||||
extism_plugin_free: ["void", [context, "int32"]],
|
||||
extism_plugin_function_exists: ["bool", [context, pluginIndex, "string"]],
|
||||
extism_plugin_config: ["void", [context, pluginIndex, "char*", "uint64"]],
|
||||
extism_plugin_free: ["void", [context, pluginIndex]],
|
||||
extism_context_reset: ["void", [context]],
|
||||
extism_version: ["char*", []],
|
||||
extism_version: ["string", []],
|
||||
extism_function_new: [
|
||||
function_t,
|
||||
[
|
||||
"string",
|
||||
ValTypeArray,
|
||||
"uint64",
|
||||
ValTypeArray,
|
||||
"uint64",
|
||||
"void*",
|
||||
"void*",
|
||||
"void*",
|
||||
],
|
||||
],
|
||||
extism_function_free: ["void", [function_t]],
|
||||
extism_current_plugin_memory: ["uint8*", ["void*"]],
|
||||
extism_current_plugin_memory_alloc: ["uint64", ["void*", "uint64"]],
|
||||
extism_current_plugin_memory_length: ["uint64", ["void*", "uint64"]],
|
||||
extism_current_plugin_memory_free: ["void", ["void*", "uint64"]],
|
||||
};
|
||||
|
||||
/**
|
||||
* An enumeration of all possible `Val` types
|
||||
*/
|
||||
export enum ValType {
|
||||
I32 = 0,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
FuncRef,
|
||||
ExternRef,
|
||||
}
|
||||
|
||||
interface LibExtism {
|
||||
extism_context_new: () => Buffer;
|
||||
extism_context_free: (ctx: Buffer) => void;
|
||||
@@ -32,6 +98,8 @@ interface LibExtism {
|
||||
ctx: Buffer,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
functions: Buffer,
|
||||
nfunctions: number,
|
||||
wasi: boolean
|
||||
) => number;
|
||||
extism_plugin_update: (
|
||||
@@ -39,9 +107,11 @@ interface LibExtism {
|
||||
plugin_id: number,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
functions: Buffer,
|
||||
nfunctions: number,
|
||||
wasi: boolean
|
||||
) => boolean;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => Buffer;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => string;
|
||||
extism_plugin_call: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
@@ -50,7 +120,7 @@ interface LibExtism {
|
||||
input_len: number
|
||||
) => number;
|
||||
extism_plugin_output_length: (ctx: Buffer, plugin_id: number) => number;
|
||||
extism_plugin_output_data: (ctx: Buffer, plugin_id: Number) => Uint8Array;
|
||||
extism_plugin_output_data: (ctx: Buffer, plugin_id: number) => Uint8Array;
|
||||
extism_log_file: (file: string, level: string) => boolean;
|
||||
extism_plugin_function_exists: (
|
||||
ctx: Buffer,
|
||||
@@ -65,7 +135,22 @@ interface LibExtism {
|
||||
) => void;
|
||||
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
|
||||
extism_context_reset: (ctx: Buffer) => void;
|
||||
extism_version: () => Buffer;
|
||||
extism_version: () => string;
|
||||
extism_function_new: (
|
||||
name: string,
|
||||
inputs: Buffer,
|
||||
nInputs: number,
|
||||
outputs: Buffer,
|
||||
nOutputs: number,
|
||||
f: Buffer,
|
||||
user_data: Buffer | null,
|
||||
free: Buffer | null
|
||||
) => Buffer;
|
||||
extism_function_free: (f: Buffer) => void;
|
||||
extism_current_plugin_memory: (p: Buffer) => Buffer;
|
||||
extism_current_plugin_memory_alloc: (p: Buffer, n: number) => number;
|
||||
extism_current_plugin_memory_length: (p: Buffer, n: number) => number;
|
||||
extism_current_plugin_memory_free: (p: Buffer, n: number) => void;
|
||||
}
|
||||
|
||||
function locate(paths: string[]): LibExtism {
|
||||
@@ -110,19 +195,19 @@ export function setLogFile(filename: string, level?: string) {
|
||||
* @returns The version string of the Extism runtime
|
||||
*/
|
||||
export function extismVersion(): string {
|
||||
return lib.extism_version().toString();
|
||||
return lib.extism_version();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const pluginRegistry = new FinalizationRegistry(({ id, pointer }) => {
|
||||
if (id && pointer) lib.extism_plugin_free(pointer, id);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const contextRegistry = new FinalizationRegistry((pointer) => {
|
||||
if (pointer) lib.extism_context_free(pointer);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const functionRegistry = new FinalizationRegistry((pointer) => {
|
||||
if (pointer) lib.extism_function_free(pointer);
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents a path or url to a WASM module
|
||||
*/
|
||||
@@ -161,7 +246,7 @@ export type ManifestWasm = ManifestWasmFile | ManifestWasmData;
|
||||
/**
|
||||
* The manifest which describes the {@link Plugin} code and
|
||||
* runtime constraints.
|
||||
*
|
||||
*
|
||||
* @see [Extism > Concepts > Manifest](https://extism.org/docs/concepts/manifest)
|
||||
*/
|
||||
export type Manifest = {
|
||||
@@ -214,7 +299,7 @@ export class Context {
|
||||
*/
|
||||
constructor() {
|
||||
this.pointer = lib.extism_context_new();
|
||||
contextRegistry.register(this, this.pointer, this);
|
||||
contextRegistry.register(this, this.pointer, this.pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,19 +310,24 @@ export class Context {
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
plugin(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
return new Plugin(this, manifest, wasi, config);
|
||||
plugin(
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig
|
||||
) {
|
||||
return new Plugin(this, manifest, wasi, functions, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the context. Should be called after the context is not needed to reclaim the memory.
|
||||
*/
|
||||
free() {
|
||||
contextRegistry.unregister(this.pointer);
|
||||
if (this.pointer) {
|
||||
contextRegistry.unregister(this);
|
||||
lib.extism_context_free(this.pointer);
|
||||
this.pointer = null;
|
||||
}
|
||||
this.pointer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,12 +358,156 @@ export async function withContext(f: (ctx: Context) => Promise<any>) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to the plugin that is currently running from inside a {@link HostFunction}
|
||||
*/
|
||||
export class CurrentPlugin {
|
||||
pointer: Buffer;
|
||||
|
||||
constructor(pointer: Buffer) {
|
||||
this.pointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access plugin's memory
|
||||
* @param offset - The offset in memory
|
||||
* @returns a pointer to the provided offset
|
||||
*/
|
||||
memory(offset: number): Buffer {
|
||||
let length = lib.extism_current_plugin_memory_length(this.pointer, offset);
|
||||
return Buffer.from(
|
||||
lib.extism_current_plugin_memory(this.pointer).buffer,
|
||||
offset,
|
||||
length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new memory block
|
||||
* @param n - The number of bytes to allocate
|
||||
* @returns the offset to the newly allocated block
|
||||
*/
|
||||
memoryAlloc(n: number): number {
|
||||
return lib.extism_current_plugin_memory_alloc(this.pointer, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a memory block
|
||||
* @param offset - The offset of the block to free
|
||||
*/
|
||||
memoryFree(offset: number) {
|
||||
return lib.extism_current_plugin_memory_free(this.pointer, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of a memory block
|
||||
* @param offset - The offset of the block
|
||||
* @returns the length of the block specified by `offset`
|
||||
*/
|
||||
memoryLength(offset: number): number {
|
||||
return lib.extism_current_plugin_memory_length(this.pointer, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for the host to define functions that can be called from WebAseembly
|
||||
*/
|
||||
export class HostFunction {
|
||||
callback: any;
|
||||
pointer: Buffer | null;
|
||||
name: string;
|
||||
userData: any[];
|
||||
inputs: typeof ValTypeArray;
|
||||
outputs: typeof ValTypeArray;
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
inputs: ValType[],
|
||||
outputs: ValType[],
|
||||
f: any,
|
||||
...userData: any
|
||||
) {
|
||||
this.userData = userData;
|
||||
this.callback = ffi.Callback(
|
||||
"void",
|
||||
[
|
||||
"void*",
|
||||
ref.refType(Val),
|
||||
"uint64",
|
||||
ref.refType(Val),
|
||||
"uint64",
|
||||
"void*",
|
||||
],
|
||||
(
|
||||
currentPlugin: Buffer,
|
||||
inputs: Buffer,
|
||||
nInputs: number,
|
||||
outputs: Buffer,
|
||||
nOutputs: number,
|
||||
user_data
|
||||
) => {
|
||||
let inputArr = [];
|
||||
let outputArr = [];
|
||||
|
||||
for (var i = 0; i < nInputs; i++) {
|
||||
inputArr.push(Val.get(inputs, i));
|
||||
}
|
||||
|
||||
for (var i = 0; i < nOutputs; i++) {
|
||||
outputArr.push(Val.get(outputs, i));
|
||||
}
|
||||
|
||||
f(
|
||||
new CurrentPlugin(currentPlugin),
|
||||
inputArr,
|
||||
outputArr,
|
||||
...this.userData
|
||||
);
|
||||
|
||||
for (var i = 0; i < nOutputs; i++) {
|
||||
Val.set(outputs, i, outputArr[i]);
|
||||
}
|
||||
}
|
||||
);
|
||||
this.name = name;
|
||||
this.inputs = new ValTypeArray(inputs);
|
||||
this.outputs = new ValTypeArray(outputs);
|
||||
this.pointer = lib.extism_function_new(
|
||||
this.name,
|
||||
this.inputs,
|
||||
this.inputs.length,
|
||||
this.outputs,
|
||||
this.outputs.length,
|
||||
this.callback,
|
||||
null,
|
||||
null
|
||||
);
|
||||
this.userData = userData;
|
||||
functionRegistry.register(this, this.pointer, this.pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a host function - this should be called to cleanup the associated resources
|
||||
*/
|
||||
free() {
|
||||
functionRegistry.unregister(this.pointer);
|
||||
if (this.pointer === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
lib.extism_function_free(this.pointer);
|
||||
this.pointer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Plugin represents an instance of your WASM program from the given manifest.
|
||||
*/
|
||||
export class Plugin {
|
||||
id: number;
|
||||
ctx: Context;
|
||||
functions: typeof PtrArray;
|
||||
token: { id: number; pointer: Buffer };
|
||||
|
||||
/**
|
||||
* Constructor for a plugin. @see {@link Context#plugin}.
|
||||
@@ -281,12 +515,14 @@ export class Plugin {
|
||||
* @param ctx - The context to manage this plugin
|
||||
* @param manifest - The {@link Manifest}
|
||||
* @param wasi - Set to true to enable WASI support
|
||||
* @param functions - An array of {@link HostFunction}
|
||||
* @param config - The plugin config
|
||||
*/
|
||||
constructor(
|
||||
ctx: Context,
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig
|
||||
) {
|
||||
let dataRaw: string | Buffer;
|
||||
@@ -298,10 +534,16 @@ export class Plugin {
|
||||
throw Error(`Unknown manifest type ${typeof manifest}`);
|
||||
}
|
||||
if (!ctx.pointer) throw Error("No Context set");
|
||||
this.functions = new PtrArray(functions.length);
|
||||
for (var i = 0; i < functions.length; i++) {
|
||||
this.functions[i] = functions[i].pointer;
|
||||
}
|
||||
let plugin = lib.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
dataRaw,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
Buffer.byteLength(dataRaw, "utf-8"),
|
||||
this.functions,
|
||||
functions.length,
|
||||
wasi
|
||||
);
|
||||
if (plugin < 0) {
|
||||
@@ -312,16 +554,17 @@ export class Plugin {
|
||||
throw `Unable to load plugin: ${err.toString()}`;
|
||||
}
|
||||
this.id = plugin;
|
||||
this.token = { id: this.id, pointer: ctx.pointer };
|
||||
this.ctx = ctx;
|
||||
pluginRegistry.register(
|
||||
this,
|
||||
{ id: this.id, pointer: this.ctx.pointer },
|
||||
this
|
||||
);
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
|
||||
lib.extism_plugin_config(
|
||||
ctx.pointer,
|
||||
this.id,
|
||||
s,
|
||||
Buffer.byteLength(s, "utf-8")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,9 +573,15 @@ export class Plugin {
|
||||
*
|
||||
* @param manifest - The new {@link Manifest} data
|
||||
* @param wasi - Set to true to enable WASI support
|
||||
* @param functions - An array of {@link HostFunction}
|
||||
* @param config - The new plugin config
|
||||
*/
|
||||
update(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
update(
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig
|
||||
) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
|
||||
dataRaw = manifest;
|
||||
@@ -342,11 +591,17 @@ export class Plugin {
|
||||
throw Error("Unknown manifest type type");
|
||||
}
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
this.functions = new PtrArray(functions.length);
|
||||
for (var i = 0; i < functions.length; i++) {
|
||||
this.functions[i] = functions[i].pointer;
|
||||
}
|
||||
const ok = lib.extism_plugin_update(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
dataRaw,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
Buffer.byteLength(dataRaw, "utf-8"),
|
||||
this.functions,
|
||||
functions.length,
|
||||
wasi
|
||||
);
|
||||
if (!ok) {
|
||||
@@ -359,7 +614,12 @@ export class Plugin {
|
||||
|
||||
if (config != null) {
|
||||
let s = JSON.stringify(config);
|
||||
lib.extism_plugin_config(this.ctx.pointer, this.id, s, Buffer.byteLength(s, 'utf-8'),);
|
||||
lib.extism_plugin_config(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
s,
|
||||
Buffer.byteLength(s, "utf-8")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +653,7 @@ export class Plugin {
|
||||
*
|
||||
* @param functionName - The name of the function
|
||||
* @param input - The input data
|
||||
* @returns A Buffer repreesentation of the output
|
||||
*@returns A Buffer repreesentation of the output
|
||||
*/
|
||||
async call(functionName: string, input: string | Buffer): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
@@ -403,7 +663,7 @@ export class Plugin {
|
||||
this.id,
|
||||
functionName,
|
||||
input.toString(),
|
||||
Buffer.byteLength(input, 'utf-8'),
|
||||
Buffer.byteLength(input, "utf-8")
|
||||
);
|
||||
if (rc !== 0) {
|
||||
var err = lib.extism_error(this.ctx.pointer, this.id);
|
||||
@@ -427,8 +687,7 @@ export class Plugin {
|
||||
* Free a plugin, this should be called when the plugin is no longer needed
|
||||
*/
|
||||
free() {
|
||||
if (this.ctx.pointer && this.id !== -1) {
|
||||
pluginRegistry.unregister(this);
|
||||
if (this.ctx.pointer && this.id >= 0) {
|
||||
lib.extism_plugin_free(this.ctx.pointer, this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
|
||||
@@ -40,13 +40,60 @@ let context = ptr void
|
||||
let extism_context_new = fn "extism_context_new" (void @-> returning context)
|
||||
let extism_context_free = fn "extism_context_free" (context @-> returning void)
|
||||
|
||||
module Extism_val_type = struct
|
||||
type t = I32 | I64 | F32 | F64 | FuncRef | ExternRef
|
||||
|
||||
let to_int = function
|
||||
| I32 -> 0
|
||||
| I64 -> 1
|
||||
| F32 -> 2
|
||||
| F64 -> 3
|
||||
| FuncRef -> 4
|
||||
| ExternRef -> 5
|
||||
|
||||
let of_int = function
|
||||
| 0 -> I32
|
||||
| 1 -> I64
|
||||
| 2 -> F32
|
||||
| 3 -> F64
|
||||
| 4 -> FuncRef
|
||||
| 5 -> ExternRef
|
||||
| n -> invalid_arg ("Extism_val_type.of_int: " ^ string_of_int n)
|
||||
|
||||
let t : t typ = view ~read:of_int ~write:to_int int
|
||||
end
|
||||
|
||||
module Extism_val_union = struct
|
||||
type t
|
||||
|
||||
let t : t union typ = union "ExtismValUnion"
|
||||
let i32 = field t "i32" int32_t
|
||||
let i64 = field t "i64" int64_t
|
||||
let f32 = field t "f32" float
|
||||
let f64 = field t "f64" double
|
||||
let () = seal t
|
||||
end
|
||||
|
||||
module Extism_val = struct
|
||||
type t
|
||||
|
||||
let t : t structure typ = structure "ExtismVal"
|
||||
let ty = field t "t" Extism_val_type.t
|
||||
let v = field t "v" Extism_val_union.t
|
||||
let () = seal t
|
||||
end
|
||||
|
||||
let extism_plugin_new =
|
||||
fn "extism_plugin_new"
|
||||
(context @-> string @-> uint64_t @-> bool @-> returning int32_t)
|
||||
(context @-> string @-> uint64_t
|
||||
@-> ptr (ptr void)
|
||||
@-> uint64_t @-> bool @-> returning int32_t)
|
||||
|
||||
let extism_plugin_update =
|
||||
fn "extism_plugin_update"
|
||||
(context @-> int32_t @-> string @-> uint64_t @-> bool @-> returning bool)
|
||||
(context @-> int32_t @-> string @-> uint64_t
|
||||
@-> ptr (ptr void)
|
||||
@-> uint64_t @-> bool @-> returning bool)
|
||||
|
||||
let extism_plugin_config =
|
||||
fn "extism_plugin_config"
|
||||
@@ -84,3 +131,35 @@ let extism_context_reset = fn "extism_context_reset" (context @-> returning void
|
||||
let extism_plugin_function_exists =
|
||||
fn "extism_plugin_function_exists"
|
||||
(context @-> int32_t @-> string @-> returning bool)
|
||||
|
||||
let extism_function_type =
|
||||
Foreign.funptr ~runtime_lock:true
|
||||
(ptr void @-> ptr Extism_val.t @-> uint64_t @-> ptr Extism_val.t
|
||||
@-> uint64_t @-> ptr void @-> returning void)
|
||||
|
||||
let extism_free_user_data =
|
||||
Foreign.funptr_opt ~runtime_lock:true (ptr void @-> returning void)
|
||||
|
||||
let extism_function_new =
|
||||
fn "extism_function_new"
|
||||
(string @-> ptr Extism_val_type.t @-> uint64_t @-> ptr Extism_val_type.t
|
||||
@-> uint64_t @-> extism_function_type @-> ptr void @-> extism_free_user_data
|
||||
@-> returning (ptr void))
|
||||
|
||||
let extism_function_free =
|
||||
fn "extism_function_free" (ptr void @-> returning void)
|
||||
|
||||
let extism_current_plugin_memory =
|
||||
fn "extism_current_plugin_memory" (ptr void @-> returning (ptr uint8_t))
|
||||
|
||||
let extism_current_plugin_memory_length =
|
||||
fn "extism_current_plugin_memory_length"
|
||||
(ptr void @-> uint64_t @-> returning uint64_t)
|
||||
|
||||
let extism_current_plugin_memory_alloc =
|
||||
fn "extism_current_plugin_memory_alloc"
|
||||
(ptr void @-> uint64_t @-> returning uint64_t)
|
||||
|
||||
let extism_current_plugin_memory_free =
|
||||
fn "extism_current_plugin_memory_free"
|
||||
(ptr void @-> uint64_t @-> returning void)
|
||||
|
||||
51
ocaml/lib/current_plugin.ml
Normal file
51
ocaml/lib/current_plugin.ml
Normal file
@@ -0,0 +1,51 @@
|
||||
open Ctypes
|
||||
|
||||
type t = unit ptr
|
||||
type offs = Unsigned.uint64
|
||||
type len = Unsigned.uint64
|
||||
|
||||
let memory t = Bindings.extism_current_plugin_memory t
|
||||
let length t offs = Bindings.extism_current_plugin_memory_length t offs
|
||||
let alloc t len = Bindings.extism_current_plugin_memory_alloc t len
|
||||
let free t offs = Bindings.extism_current_plugin_memory_free t offs
|
||||
|
||||
module Memory = struct
|
||||
let get_bigstring t offs : Bigstringaf.t =
|
||||
let length = length t offs in
|
||||
let p = memory t +@ Unsigned.UInt64.to_int offs in
|
||||
bigarray_of_ptr array1
|
||||
(Unsigned.UInt64.to_int length)
|
||||
Bigarray.Char
|
||||
(coerce (ptr uint8_t) (ptr char) p)
|
||||
|
||||
let get_string t offs =
|
||||
let length = length t offs in
|
||||
let p = memory t +@ Unsigned.UInt64.to_int offs in
|
||||
Ctypes.string_from_ptr
|
||||
(coerce (ptr uint8_t) (ptr char) p)
|
||||
~length:(Unsigned.UInt64.to_int length)
|
||||
|
||||
let set_bigstring t offs bs =
|
||||
let length =
|
||||
min (Unsigned.UInt64.to_int @@ length t offs) (Bigstringaf.length bs)
|
||||
in
|
||||
let p =
|
||||
coerce (ptr uint8_t) (ptr char)
|
||||
@@ (memory t +@ Unsigned.UInt64.to_int offs)
|
||||
in
|
||||
for i = 0 to length - 1 do
|
||||
p +@ i <-@ Bigstringaf.unsafe_get bs i
|
||||
done
|
||||
|
||||
let set_string t offs s =
|
||||
let length =
|
||||
min (Unsigned.UInt64.to_int @@ length t offs) (String.length s)
|
||||
in
|
||||
let p =
|
||||
coerce (ptr uint8_t) (ptr char)
|
||||
@@ (memory t +@ Unsigned.UInt64.to_int offs)
|
||||
in
|
||||
for i = 0 to length - 1 do
|
||||
p +@ i <-@ String.unsafe_get s i
|
||||
done
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
(name extism)
|
||||
(public_name extism)
|
||||
(inline_tests
|
||||
(deps test/code.wasm))
|
||||
(deps test/code.wasm test/code-functions.wasm))
|
||||
(libraries ctypes.foreign bigstringaf extism-manifest)
|
||||
(preprocess
|
||||
(pps ppx_yojson_conv ppx_inline_test)))
|
||||
|
||||
@@ -8,3 +8,4 @@ let () =
|
||||
| _ -> None)
|
||||
|
||||
let unwrap = function Ok x -> x | Error t -> raise (Error t)
|
||||
let throw e = raise (Error e)
|
||||
|
||||
@@ -2,6 +2,9 @@ module Manifest = Extism_manifest
|
||||
module Error = Error
|
||||
module Context = Context
|
||||
module Plugin = Plugin
|
||||
module Function = Function
|
||||
module Current_plugin = Current_plugin
|
||||
include Types
|
||||
|
||||
let with_context = Plugin.with_context
|
||||
let extism_version = Bindings.extism_version
|
||||
|
||||
@@ -1,36 +1,181 @@
|
||||
(** Extism bindings for OCaml *)
|
||||
|
||||
|
||||
(** Returns the libextism version, not the version of the OCaml library *)
|
||||
val extism_version : unit -> string
|
||||
(** Returns the libextism version, not the version of the OCaml library *)
|
||||
|
||||
module Manifest = Extism_manifest
|
||||
|
||||
module Error : sig
|
||||
type t = [`Msg of string]
|
||||
type t = [ `Msg of string ]
|
||||
|
||||
exception Error of t
|
||||
val unwrap: ('a, t) result -> 'a
|
||||
|
||||
val unwrap : ('a, t) result -> 'a
|
||||
val throw : t -> 'a
|
||||
end
|
||||
|
||||
(** [Val_type] enumerates every possible argument/result type *)
|
||||
module Val_type : sig
|
||||
type t = I32 | I64 | F32 | F64 | FuncRef | ExternRef (** Value type *)
|
||||
|
||||
val t : t Ctypes.typ
|
||||
val of_int : int -> t
|
||||
val to_int : t -> int
|
||||
end
|
||||
|
||||
(** [Val] represents low-level WebAssembly values *)
|
||||
module Val : sig
|
||||
type t
|
||||
(** Val *)
|
||||
|
||||
val t : t Ctypes.typ
|
||||
|
||||
val ty : t -> Val_type.t
|
||||
(** [ty v] returns the [Val_type.t] for the value [v] *)
|
||||
|
||||
val of_i32 : int32 -> t
|
||||
(** Create an i32 [Val] *)
|
||||
|
||||
val of_i64 : int64 -> t
|
||||
(** Create an i64 [Val] *)
|
||||
|
||||
val of_f32 : float -> t
|
||||
(** Create an f32 [Val] *)
|
||||
|
||||
val of_f64 : float -> t
|
||||
(** Create an f64 [Val] *)
|
||||
|
||||
val to_i32 : t -> int32 option
|
||||
(** Get an int32 from [Val] if the type matches *)
|
||||
|
||||
val to_i64 : t -> int64 option
|
||||
(** Get an int64 from [Val] if the type matches *)
|
||||
|
||||
val to_f32 : t -> float option
|
||||
(** Get a f32 from [Val] if the type matches *)
|
||||
|
||||
val to_f64 : t -> float option
|
||||
(** Get an f64 from [Val] if the type matches *)
|
||||
|
||||
val to_i32_exn : t -> int32
|
||||
(** Same as [to_i32] but raises an exception if the types don't match*)
|
||||
|
||||
val to_i64_exn : t -> int64
|
||||
(** Same as [to_i64] but raises an exception if the types don't match*)
|
||||
|
||||
val to_f32_exn : t -> float
|
||||
(** Same as [to_f32] but raises an exception if the types don't match*)
|
||||
|
||||
val to_f64_exn : t -> float
|
||||
(** Same as [to_f64] but raises an exception if the types don't match*)
|
||||
end
|
||||
|
||||
(** [Val_array] is used for input/output parameters for host functions *)
|
||||
module Val_array : sig
|
||||
type t = Val.t Ctypes.CArray.t
|
||||
(** [Val_array] type *)
|
||||
|
||||
val get : t -> int -> Val.t
|
||||
(** Get an index *)
|
||||
|
||||
val set : t -> int -> Val.t -> unit
|
||||
(** Set an index *)
|
||||
|
||||
val length : t -> int
|
||||
(** Get the number of items in a [Val_array]*)
|
||||
|
||||
val ( .$[] ) : t -> int -> Val.t
|
||||
(** Syntax for [get] *)
|
||||
|
||||
val ( .$[]<- ) : t -> int -> Val.t -> unit
|
||||
(** Syntax for [set] *)
|
||||
end
|
||||
|
||||
(** [Current_plugin] represents the plugin that is currently running, it should
|
||||
it should only be used from a host function *)
|
||||
module Current_plugin : sig
|
||||
type t
|
||||
(** Opaque type, wraps [ExtismCurrentPlugin] *)
|
||||
|
||||
type offs = Unsigned.uint64
|
||||
(** Memory offset type *)
|
||||
|
||||
type len = Unsigned.uint64
|
||||
(** Memory length type *)
|
||||
|
||||
val memory : t -> Unsigned.uint8 Ctypes.ptr
|
||||
(** Get pointer to entire plugin memory *)
|
||||
|
||||
val length : t -> offs -> len
|
||||
(** Get the length of an allocated block of memory *)
|
||||
|
||||
val alloc : t -> len -> offs
|
||||
(** Allocate a new block of memory *)
|
||||
|
||||
val free : t -> offs -> unit
|
||||
(** Free an allocated block of memory *)
|
||||
|
||||
(** Some helpter functions for reading/writing memory *)
|
||||
module Memory : sig
|
||||
val get_string : t -> offs -> string
|
||||
(** Get a string from memory stored at the provided offset *)
|
||||
|
||||
val get_bigstring : t -> offs -> Bigstringaf.t
|
||||
(** Get a bigstring from memory stored at the provided offset *)
|
||||
|
||||
val set_string : t -> offs -> string -> unit
|
||||
(** Store a string into memory at the provided offset *)
|
||||
|
||||
val set_bigstring : t -> offs -> Bigstringaf.t -> unit
|
||||
(** Store a bigstring into memory at the provided offset *)
|
||||
end
|
||||
end
|
||||
|
||||
(** [Function] is used to create new a new function, which can be called
|
||||
from a WebAssembly plugin *)
|
||||
module Function : sig
|
||||
type t
|
||||
(** Function type *)
|
||||
|
||||
val v :
|
||||
string ->
|
||||
Val_type.t list ->
|
||||
Val_type.t list ->
|
||||
user_data:'a ->
|
||||
(Current_plugin.t -> Val_array.t -> Val_array.t -> 'a -> unit) ->
|
||||
t
|
||||
(** Create a new function, [Function.v name args returns ~user_data f] creates
|
||||
a new [Function] with the given [name], [args] specifies the argument types,
|
||||
[returns] specifies the return types, [user_data] is used to pass arbitrary
|
||||
OCaml values into the function and [f] is the OCaml function that will be
|
||||
called.
|
||||
*)
|
||||
|
||||
val free : t -> unit
|
||||
(** Free a function *)
|
||||
|
||||
val free_all : t list -> unit
|
||||
(** Free a list of functions *)
|
||||
end
|
||||
|
||||
(** [Context] is used to group plugins *)
|
||||
module Context : sig
|
||||
(** Context type *)
|
||||
type t
|
||||
(** Context type *)
|
||||
|
||||
(** Create a new context *)
|
||||
val create : unit -> t
|
||||
(** Create a new context *)
|
||||
|
||||
val free : t -> unit
|
||||
(** Free a context. All plugins will be removed and the value should not be
|
||||
accessed after this call *)
|
||||
val free : t -> unit
|
||||
|
||||
(** Reset a context. All plugins will be removed *)
|
||||
val reset : t -> unit
|
||||
(** Reset a context. All plugins will be removed *)
|
||||
end
|
||||
|
||||
(** Execute a function with a fresh context and free it after *)
|
||||
val with_context : (Context.t -> 'a) -> 'a
|
||||
(** Execute a function with a fresh context and free it after *)
|
||||
|
||||
val set_log_file :
|
||||
?level:[ `Error | `Warn | `Info | `Debug | `Trace ] -> string -> bool
|
||||
@@ -39,40 +184,45 @@ val set_log_file :
|
||||
module Plugin : sig
|
||||
type t
|
||||
|
||||
(** Make a new plugin from raw WebAssembly or JSON encoded manifest *)
|
||||
val make :
|
||||
?config:Manifest.config ->
|
||||
?wasi:bool ->
|
||||
?functions:Function.t list ->
|
||||
Context.t ->
|
||||
string ->
|
||||
(t, Error.t) result
|
||||
(** Make a new plugin from raw WebAssembly or JSON encoded manifest *)
|
||||
|
||||
(** Make a new plugin from a [Manifest] *)
|
||||
val of_manifest :
|
||||
?wasi:bool -> Context.t -> Manifest.t -> (t, Error.t) result
|
||||
?wasi:bool ->
|
||||
?functions:Function.t list ->
|
||||
Context.t ->
|
||||
Manifest.t ->
|
||||
(t, Error.t) result
|
||||
(** Make a new plugin from a [Manifest] *)
|
||||
|
||||
(** Update a plugin from raw WebAssembly or JSON encoded manifest *)
|
||||
val update :
|
||||
t ->
|
||||
?config:(string * string option) list ->
|
||||
?wasi:bool ->
|
||||
?functions:Function.t list ->
|
||||
string ->
|
||||
(unit, [ `Msg of string ]) result
|
||||
(** Update a plugin from raw WebAssembly or JSON encoded manifest *)
|
||||
|
||||
val update_manifest : t -> ?wasi:bool -> Manifest.t -> (unit, Error.t) result
|
||||
(** Update a plugin from a [Manifest] *)
|
||||
val update_manifest :
|
||||
t -> ?wasi:bool -> Manifest.t -> (unit, Error.t) result
|
||||
|
||||
(** Call a function, uses [Bigstringaf.t] for input/output *)
|
||||
val call_bigstring :
|
||||
t -> name:string -> Bigstringaf.t -> (Bigstringaf.t, Error.t) result
|
||||
(** Call a function, uses [Bigstringaf.t] for input/output *)
|
||||
|
||||
(** Call a function, uses [string] for input/output *)
|
||||
val call : t -> name:string -> string -> (string, Error.t) result
|
||||
(** Call a function, uses [string] for input/output *)
|
||||
|
||||
(** Drop a plugin *)
|
||||
val free : t -> unit
|
||||
(** Drop a plugin *)
|
||||
|
||||
(** Check if a function is exported by a plugin *)
|
||||
val function_exists : t -> string -> bool
|
||||
(** Check if a function is exported by a plugin *)
|
||||
end
|
||||
|
||||
40
ocaml/lib/function.ml
Normal file
40
ocaml/lib/function.ml
Normal file
@@ -0,0 +1,40 @@
|
||||
open Ctypes
|
||||
|
||||
type t = {
|
||||
mutable pointer : unit ptr;
|
||||
mutable user_data : unit ptr;
|
||||
name : string;
|
||||
}
|
||||
|
||||
let free t =
|
||||
let () =
|
||||
if not (is_null t.user_data) then
|
||||
let () = Root.release t.user_data in
|
||||
t.user_data <- null
|
||||
in
|
||||
if not (is_null t.pointer) then
|
||||
let () = Bindings.extism_function_free t.pointer in
|
||||
t.pointer <- null
|
||||
|
||||
let free_all l = List.iter free l
|
||||
|
||||
let v name inputs outputs ~user_data f =
|
||||
let inputs = CArray.of_list Bindings.Extism_val_type.t inputs in
|
||||
let n_inputs = Unsigned.UInt64.of_int (CArray.length inputs) in
|
||||
let outputs = CArray.of_list Bindings.Extism_val_type.t outputs in
|
||||
let n_outputs = Unsigned.UInt64.of_int (CArray.length outputs) in
|
||||
let free' = Some Root.release in
|
||||
let user_data = Root.create user_data in
|
||||
let f current inputs n_inputs outputs n_outputs user_data =
|
||||
let user_data = Root.get user_data in
|
||||
let inputs = CArray.from_ptr inputs (Unsigned.UInt64.to_int n_inputs) in
|
||||
let outputs = CArray.from_ptr outputs (Unsigned.UInt64.to_int n_outputs) in
|
||||
f current inputs outputs user_data
|
||||
in
|
||||
let pointer =
|
||||
Bindings.extism_function_new name (CArray.start inputs) n_inputs
|
||||
(CArray.start outputs) n_outputs f user_data free'
|
||||
in
|
||||
let t = { pointer; user_data; name } in
|
||||
Gc.finalise free t;
|
||||
t
|
||||
@@ -1,6 +1,6 @@
|
||||
module Manifest = Extism_manifest
|
||||
|
||||
type t = { id : int32; ctx : Context.t }
|
||||
type t = { id : int32; ctx : Context.t; mutable functions : Function.t list }
|
||||
|
||||
let with_context f =
|
||||
let ctx = Context.create () in
|
||||
@@ -26,10 +26,15 @@ let free t =
|
||||
if not (Ctypes.is_null t.ctx.pointer) then
|
||||
Bindings.extism_plugin_free t.ctx.pointer t.id
|
||||
|
||||
let make ?config ?(wasi = false) ctx wasm =
|
||||
let make ?config ?(wasi = false) ?(functions = []) ctx wasm =
|
||||
let func_ptrs = List.map (fun x -> x.Function.pointer) functions in
|
||||
let arr = Ctypes.CArray.of_list Ctypes.(ptr void) func_ptrs in
|
||||
let n_funcs = Ctypes.CArray.length arr in
|
||||
let id =
|
||||
Bindings.extism_plugin_new ctx.Context.pointer wasm
|
||||
(Unsigned.UInt64.of_int (String.length wasm))
|
||||
(Ctypes.CArray.start arr)
|
||||
(Unsigned.UInt64.of_int n_funcs)
|
||||
wasi
|
||||
in
|
||||
if id < 0l then
|
||||
@@ -37,28 +42,33 @@ let make ?config ?(wasi = false) ctx wasm =
|
||||
| None -> Error (`Msg "extism_plugin_call failed")
|
||||
| Some msg -> Error (`Msg msg)
|
||||
else
|
||||
let t = { id; ctx } in
|
||||
let t = { id; ctx; functions } in
|
||||
if not (set_config t config) then Error (`Msg "call to set_config failed")
|
||||
else
|
||||
let () = Gc.finalise free t in
|
||||
Ok t
|
||||
|
||||
let of_manifest ?wasi ctx manifest =
|
||||
let of_manifest ?wasi ?functions ctx manifest =
|
||||
let data = Manifest.json manifest in
|
||||
make ctx ?wasi data
|
||||
make ctx ?wasi ?functions data
|
||||
|
||||
let%test "free plugin" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let plugin = of_manifest ctx manifest |> Error.unwrap in
|
||||
free plugin;
|
||||
true)
|
||||
|
||||
let update plugin ?config ?(wasi = false) wasm =
|
||||
let { id; ctx } = plugin in
|
||||
let update plugin ?config ?(wasi = false) ?(functions = []) wasm =
|
||||
let { id; ctx; _ } = plugin in
|
||||
let func_ptrs = List.map (fun x -> x.Function.pointer) functions in
|
||||
let arr = Ctypes.CArray.of_list Ctypes.(ptr void) func_ptrs in
|
||||
let n_funcs = Ctypes.CArray.length arr in
|
||||
let ok =
|
||||
Bindings.extism_plugin_update ctx.pointer id wasm
|
||||
(Unsigned.UInt64.of_int (String.length wasm))
|
||||
(Ctypes.CArray.start arr)
|
||||
(Unsigned.UInt64.of_int n_funcs)
|
||||
wasi
|
||||
in
|
||||
if not ok then
|
||||
@@ -77,11 +87,11 @@ let%test "update plugin manifest and config" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let config = [ ("a", Some "1") ] in
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let plugin = of_manifest ctx manifest |> Error.unwrap in
|
||||
let manifest = Manifest.with_config manifest config in
|
||||
update_manifest plugin manifest |> Result.is_ok)
|
||||
|
||||
let call' f { id; ctx } ~name input len =
|
||||
let call' f { id; ctx; _ } ~name input len =
|
||||
let rc = f ctx.pointer id name input len in
|
||||
if rc <> 0l then
|
||||
match Bindings.extism_error ctx.pointer id with
|
||||
@@ -105,10 +115,10 @@ let call_bigstring (t : t) ~name input =
|
||||
let%test "call_bigstring" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let plugin = of_manifest ctx manifest |> Error.unwrap in
|
||||
call_bigstring plugin ~name:"count_vowels"
|
||||
(Bigstringaf.of_string ~off:0 ~len:14 "this is a test")
|
||||
|> Result.get_ok |> Bigstringaf.to_string = "{\"count\": 4}")
|
||||
|> Error.unwrap |> Bigstringaf.to_string = "{\"count\": 4}")
|
||||
|
||||
let call (t : t) ~name input =
|
||||
let len = String.length input in
|
||||
@@ -118,16 +128,40 @@ let call (t : t) ~name input =
|
||||
let%test "call" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let plugin = of_manifest ctx manifest |> Error.unwrap in
|
||||
call plugin ~name:"count_vowels" "this is a test"
|
||||
|> Result.get_ok = "{\"count\": 4}")
|
||||
|> Error.unwrap = "{\"count\": 4}")
|
||||
|
||||
let function_exists { id; ctx } name =
|
||||
let%test "call_functions" =
|
||||
let open Types.Val_type in
|
||||
let hello_world =
|
||||
Function.v "hello_world" [ I64 ] [ I64 ] ~user_data:"Hello again!"
|
||||
@@ fun plugin inputs outputs user_data ->
|
||||
let open Types.Val_array in
|
||||
let s =
|
||||
Current_plugin.Memory.get_string plugin
|
||||
(Unsigned.UInt64.of_int64 @@ Types.Val.to_i64_exn inputs.$[0])
|
||||
in
|
||||
let () = print_endline "Hello from OCaml!" in
|
||||
let () = print_endline user_data in
|
||||
let () = print_endline s in
|
||||
outputs.$[0] <- inputs.$[0]
|
||||
in
|
||||
let functions = [ hello_world ] in
|
||||
let manifest = Manifest.v [ Manifest.file "test/code-functions.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin =
|
||||
of_manifest ctx manifest ~functions ~wasi:true |> Error.unwrap
|
||||
in
|
||||
call plugin ~name:"count_vowels" "this is a test"
|
||||
|> Error.unwrap = "{\"count\": 4}")
|
||||
|
||||
let function_exists { id; ctx; _ } name =
|
||||
Bindings.extism_plugin_function_exists ctx.pointer id name
|
||||
|
||||
let%test "function exists" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
with_context (fun ctx ->
|
||||
let plugin = of_manifest ctx manifest |> Result.get_ok in
|
||||
let plugin = of_manifest ctx manifest |> Error.unwrap in
|
||||
function_exists plugin "count_vowels"
|
||||
&& not (function_exists plugin "function_does_not_exist"))
|
||||
|
||||
BIN
ocaml/lib/test/code-functions.wasm
Executable file
BIN
ocaml/lib/test/code-functions.wasm
Executable file
Binary file not shown.
92
ocaml/lib/types.ml
Normal file
92
ocaml/lib/types.ml
Normal file
@@ -0,0 +1,92 @@
|
||||
open Ctypes
|
||||
module Val_type = Bindings.Extism_val_type
|
||||
|
||||
module Val = struct
|
||||
type t = (Bindings.Extism_val.t, [ `Struct ]) Ctypes.structured
|
||||
|
||||
let t = Bindings.Extism_val.t
|
||||
|
||||
let of_i32 (x : int32) : t =
|
||||
let u = Ctypes.make Bindings.Extism_val_union.t in
|
||||
u @. Bindings.Extism_val_union.i32 <-@ x;
|
||||
let t = Ctypes.make Bindings.Extism_val.t in
|
||||
t @. Bindings.Extism_val.ty <-@ Val_type.I32;
|
||||
t @. Bindings.Extism_val.v <-@ u;
|
||||
t
|
||||
|
||||
let of_i64 (x : int64) : t =
|
||||
let u = Ctypes.make Bindings.Extism_val_union.t in
|
||||
u @. Bindings.Extism_val_union.i64 <-@ x;
|
||||
let t = Ctypes.make Bindings.Extism_val.t in
|
||||
t @. Bindings.Extism_val.ty <-@ Val_type.I64;
|
||||
t @. Bindings.Extism_val.v <-@ u;
|
||||
t
|
||||
|
||||
let of_f32 (x : float) : t =
|
||||
let u = Ctypes.make Bindings.Extism_val_union.t in
|
||||
u @. Bindings.Extism_val_union.f32 <-@ x;
|
||||
let t = Ctypes.make Bindings.Extism_val.t in
|
||||
t @. Bindings.Extism_val.ty <-@ Val_type.F32;
|
||||
t @. Bindings.Extism_val.v <-@ u;
|
||||
t
|
||||
|
||||
let of_f64 (x : float) : t =
|
||||
let u = Ctypes.make Bindings.Extism_val_union.t in
|
||||
u @. Bindings.Extism_val_union.f64 <-@ x;
|
||||
let t = Ctypes.make Bindings.Extism_val.t in
|
||||
t @. Bindings.Extism_val.ty <-@ Val_type.F64;
|
||||
t @. Bindings.Extism_val.v <-@ u;
|
||||
t
|
||||
|
||||
let to_i32 t : int32 option =
|
||||
let ty = t @. Bindings.Extism_val.ty in
|
||||
let v = t @. Bindings.Extism_val.v in
|
||||
match !@ty with
|
||||
| Bindings.Extism_val_type.I32 ->
|
||||
Some !@(!@v @. Bindings.Extism_val_union.i32)
|
||||
| _ -> None
|
||||
|
||||
let to_i64 t : int64 option =
|
||||
let ty = t @. Bindings.Extism_val.ty in
|
||||
let v = t @. Bindings.Extism_val.v in
|
||||
match !@ty with
|
||||
| Bindings.Extism_val_type.I64 ->
|
||||
Some !@(!@v @. Bindings.Extism_val_union.i64)
|
||||
| _ -> None
|
||||
|
||||
let to_f32 t : float option =
|
||||
let ty = t @. Bindings.Extism_val.ty in
|
||||
let v = t @. Bindings.Extism_val.v in
|
||||
match !@ty with
|
||||
| Bindings.Extism_val_type.F32 ->
|
||||
Some !@(!@v @. Bindings.Extism_val_union.f32)
|
||||
| _ -> None
|
||||
|
||||
let to_f64 t : float option =
|
||||
let ty = t @. Bindings.Extism_val.ty in
|
||||
let v = t @. Bindings.Extism_val.v in
|
||||
match !@ty with
|
||||
| Bindings.Extism_val_type.F64 ->
|
||||
Some !@(!@v @. Bindings.Extism_val_union.f64)
|
||||
| _ -> None
|
||||
|
||||
let ty t = !@(t @. Bindings.Extism_val.ty)
|
||||
|
||||
let make_exn f x =
|
||||
match f x with Some x -> x | None -> Error.throw (`Msg "invalid type")
|
||||
|
||||
let to_i32_exn = make_exn to_i32
|
||||
let to_i64_exn = make_exn to_i64
|
||||
let to_f32_exn = make_exn to_f32
|
||||
let to_f64_exn = make_exn to_f64
|
||||
end
|
||||
|
||||
module Val_array = struct
|
||||
type t = Val.t Ctypes.CArray.t
|
||||
|
||||
let get t i = Ctypes.CArray.get t i
|
||||
let set t i x = Ctypes.CArray.set t i x
|
||||
let length t = Ctypes.CArray.length t
|
||||
let ( .$[] ) = get
|
||||
let ( .$[]<- ) = set
|
||||
end
|
||||
@@ -1,39 +1,42 @@
|
||||
(** Memory options *)
|
||||
type memory = { max_pages : int option } [@@deriving yojson]
|
||||
(** Memory options *)
|
||||
|
||||
(** Key/value dictionary *)
|
||||
type dict = (string * string) list [@@deriving yojson]
|
||||
(** Key/value dictionary *)
|
||||
|
||||
(** Key/value dictionary with optional values *)
|
||||
type config = (string * string option) list [@@deriving yojson]
|
||||
(** Key/value dictionary with optional values *)
|
||||
|
||||
(** WebAssembly file *)
|
||||
type wasm_file = {
|
||||
path : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
} [@@deriving yojson]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
(** WebAssembly file *)
|
||||
|
||||
(** WebAssembly module data *)
|
||||
type wasm_data = {
|
||||
data : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
} [@@deriving yojson]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
(** WebAssembly module data *)
|
||||
|
||||
(** WebAssembly URL *)
|
||||
type wasm_url = {
|
||||
url : string;
|
||||
headers : dict option; [@yojson.option]
|
||||
name : string option; [@yojson.option]
|
||||
meth : string option; [@yojson.option] [@key "method"]
|
||||
hash : string option; [@yojson.option]
|
||||
} [@@deriving yojson]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
(** WebAssembly URL *)
|
||||
|
||||
(** WebAssembly from a file, module data or URL *)
|
||||
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url [@@deriving yojson]
|
||||
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url
|
||||
[@@deriving yojson]
|
||||
|
||||
(** Manifest type *)
|
||||
type t = {
|
||||
wasm : wasm list;
|
||||
memory : memory option;
|
||||
@@ -41,15 +44,16 @@ type t = {
|
||||
allowed_hosts : string list option;
|
||||
allowed_paths : dict option;
|
||||
timeout_ms : int option;
|
||||
} [@@deriving yojson]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
(** Manifest type *)
|
||||
|
||||
(** Create [wasm] from filename *)
|
||||
val file : ?name:string -> ?hash:string -> string -> wasm
|
||||
(** Create [wasm] from filename *)
|
||||
|
||||
(** Create [wasm] from WebAssembly module data *)
|
||||
val data : ?name:string -> ?hash:string -> string -> wasm
|
||||
(** Create [wasm] from WebAssembly module data *)
|
||||
|
||||
(** Create [wasm] from URL *)
|
||||
val url :
|
||||
?headers:(string * string) list ->
|
||||
?name:string ->
|
||||
@@ -57,8 +61,8 @@ val url :
|
||||
?hash:string ->
|
||||
string ->
|
||||
wasm
|
||||
(** Create [wasm] from URL *)
|
||||
|
||||
(** Create new manifest *)
|
||||
val v :
|
||||
?config:config ->
|
||||
?memory:memory ->
|
||||
@@ -67,9 +71,10 @@ val v :
|
||||
?timeout_ms:int ->
|
||||
wasm list ->
|
||||
t
|
||||
(** Create new manifest *)
|
||||
|
||||
(** Convert manifest to JSON *)
|
||||
val json : t -> string
|
||||
(** Convert manifest to JSON *)
|
||||
|
||||
(** Updates a manifest config *)
|
||||
val with_config : t -> config -> t
|
||||
(** Updates a manifest config *)
|
||||
|
||||
@@ -29,7 +29,7 @@ class Plugin
|
||||
$data = string_to_bytes($data);
|
||||
}
|
||||
|
||||
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), (int)$wasi);
|
||||
$id = $this->lib->extism_plugin_new($ctx->pointer, $data, count($data), null, 0, (int)$wasi);
|
||||
if ($id < 0) {
|
||||
$err = $this->lib->extism_error($ctx->pointer, -1);
|
||||
throw new \Exception("Extism: unable to load plugin: " . $err);
|
||||
@@ -96,7 +96,7 @@ class Plugin
|
||||
$data = string_to_bytes($data);
|
||||
}
|
||||
|
||||
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), (int)$wasi);
|
||||
$ok = $this->lib->extism_plugin_update($this->context->pointer, $this->id, $data, count($data), null, 0, (int)$wasi);
|
||||
if (!$ok) {
|
||||
$err = $this->lib->extism_error($this->context->pointer, -1);
|
||||
throw new \Exception("Extism: unable to update plugin: " . $err);
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int32_t ExtismPlugin;
|
||||
|
||||
typedef uint64_t ExtismSize;
|
||||
|
||||
ExtismPlugin extism_plugin_register(const uint8_t *wasm, ExtismSize wasm_size, bool with_wasi);
|
||||
|
||||
bool extism_plugin_update(ExtismPlugin index,
|
||||
const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
bool with_wasi);
|
||||
|
||||
bool extism_plugin_config(ExtismPlugin plugin, const uint8_t *json, ExtismSize json_size);
|
||||
|
||||
bool extism_function_exists(ExtismPlugin plugin, const char *func_name);
|
||||
|
||||
int32_t extism_call(ExtismPlugin plugin_id,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
ExtismSize data_len);
|
||||
|
||||
const char *extism_error(ExtismPlugin plugin);
|
||||
|
||||
ExtismSize extism_output_length(ExtismPlugin plugin);
|
||||
|
||||
void extism_output_get(ExtismPlugin plugin, uint8_t *buf, ExtismSize len);
|
||||
|
||||
bool extism_log_file(const char *filename, const char *log_level);
|
||||
|
||||
const char *extism_version();
|
||||
@@ -3,21 +3,42 @@ import json
|
||||
import hashlib
|
||||
|
||||
sys.path.append(".")
|
||||
from extism import Context
|
||||
from extism import Context, Function, host_fn, ValType
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
data = sys.argv[1].encode()
|
||||
else:
|
||||
data = b"some data from python!"
|
||||
|
||||
|
||||
@host_fn
|
||||
def hello_world(plugin, input, output, context, a_string):
|
||||
mem = plugin.memory_at_offset(input[0])
|
||||
print("Hello from Python!")
|
||||
print(a_string)
|
||||
print(input)
|
||||
print(plugin.memory(mem)[:])
|
||||
output[0] = input[0]
|
||||
|
||||
|
||||
# a Context provides a scope for plugins to be managed within. creating multiple contexts
|
||||
# is expected and groups plugins based on source/tenant/lifetime etc.
|
||||
with Context() as context:
|
||||
wasm = open("../wasm/code.wasm", "rb").read()
|
||||
wasm = open("../wasm/code-functions.wasm", "rb").read()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
config = {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max": 5}}
|
||||
|
||||
plugin = context.plugin(config)
|
||||
functions = [
|
||||
Function(
|
||||
"hello_world",
|
||||
[ValType.I64],
|
||||
[ValType.I64],
|
||||
hello_world,
|
||||
context,
|
||||
"Hello again!",
|
||||
)
|
||||
]
|
||||
plugin = context.plugin(config, wasi=True, functions=functions)
|
||||
# Call `count_vowels`
|
||||
j = json.loads(plugin.call("count_vowels", data))
|
||||
print("Number of vowels:", j["count"])
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
from .extism import Error, Plugin, set_log_file, Context, extism_version
|
||||
from .extism import (
|
||||
Error,
|
||||
Plugin,
|
||||
set_log_file,
|
||||
Context,
|
||||
extism_version,
|
||||
host_fn,
|
||||
Function,
|
||||
ValType,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
from base64 import b64encode
|
||||
from cffi import FFI
|
||||
from typing import Union
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
@@ -124,6 +125,15 @@ def _wasm(plugin):
|
||||
return wasm
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(self, offs, length):
|
||||
self.offset = offs
|
||||
self.length = length
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
|
||||
class Context:
|
||||
"""
|
||||
Context is used to store and manage plugins. You need a context to create
|
||||
@@ -161,7 +171,9 @@ class Context:
|
||||
"""Remove all registered plugins"""
|
||||
_lib.extism_context_reset(self.pointer)
|
||||
|
||||
def plugin(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
|
||||
def plugin(
|
||||
self, manifest: Union[str, bytes, dict], wasi=False, config=None, functions=None
|
||||
):
|
||||
"""
|
||||
Register a new plugin from a WASM module or JSON encoded manifest
|
||||
|
||||
@@ -173,13 +185,40 @@ class Context:
|
||||
Set to `True` to enable WASI support
|
||||
config : dict
|
||||
The plugin config dictionary
|
||||
functions: list
|
||||
Additional host functions
|
||||
|
||||
Returns
|
||||
-------
|
||||
Plugin
|
||||
The created plugin
|
||||
"""
|
||||
return Plugin(self, manifest, wasi, config)
|
||||
return Plugin(self, manifest, wasi, config, functions)
|
||||
|
||||
|
||||
class Function:
|
||||
def __init__(self, name: str, args, returns, f, *user_data):
|
||||
self.pointer = None
|
||||
args = [a.value for a in args]
|
||||
returns = [r.value for r in returns]
|
||||
if len(user_data) > 0:
|
||||
self.user_data = _ffi.new_handle(user_data)
|
||||
else:
|
||||
self.user_data = _ffi.NULL
|
||||
self.pointer = _lib.extism_function_new(
|
||||
name.encode(),
|
||||
args,
|
||||
len(args),
|
||||
returns,
|
||||
len(returns),
|
||||
f,
|
||||
self.user_data,
|
||||
_ffi.NULL,
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
if self.pointer is not None:
|
||||
_lib.extism_function_free(self.pointer)
|
||||
|
||||
|
||||
class Plugin:
|
||||
@@ -190,7 +229,12 @@ class Plugin:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, context: Context, plugin: Union[str, bytes, dict], wasi=False, config=None
|
||||
self,
|
||||
context: Context,
|
||||
plugin: Union[str, bytes, dict],
|
||||
wasi=False,
|
||||
config=None,
|
||||
functions=None,
|
||||
):
|
||||
"""
|
||||
Construct a Plugin. Please use Context#plugin instead.
|
||||
@@ -199,7 +243,16 @@ class Plugin:
|
||||
wasm = _wasm(plugin)
|
||||
|
||||
# Register plugin
|
||||
self.plugin = _lib.extism_plugin_new(context.pointer, wasm, len(wasm), wasi)
|
||||
if functions is not None:
|
||||
functions = [f.pointer for f in functions]
|
||||
ptr = _ffi.new("ExtismFunction*[]", functions)
|
||||
self.plugin = _lib.extism_plugin_new(
|
||||
context.pointer, wasm, len(wasm), ptr, len(functions), wasi
|
||||
)
|
||||
else:
|
||||
self.plugin = _lib.extism_plugin_new(
|
||||
context.pointer, wasm, len(wasm), _ffi.NULL, 0, wasi
|
||||
)
|
||||
|
||||
self.ctx = context
|
||||
|
||||
@@ -213,7 +266,9 @@ class Plugin:
|
||||
s = json.dumps(config).encode()
|
||||
_lib.extism_plugin_config(self.ctx.pointer, self.plugin, s, len(s))
|
||||
|
||||
def update(self, manifest: Union[str, bytes, dict], wasi=False, config=None):
|
||||
def update(
|
||||
self, manifest: Union[str, bytes, dict], wasi=False, config=None, functions=None
|
||||
):
|
||||
"""
|
||||
Update a plugin with a new WASM module or manifest
|
||||
|
||||
@@ -227,9 +282,22 @@ class Plugin:
|
||||
The plugin config dictionary
|
||||
"""
|
||||
wasm = _wasm(manifest)
|
||||
ok = _lib.extism_plugin_update(
|
||||
self.ctx.pointer, self.plugin, wasm, len(wasm), wasi
|
||||
)
|
||||
if functions is not None:
|
||||
functions = [f.pointer for f in functions]
|
||||
ptr = _ffi.new("ExtismFunction*[]", functions)
|
||||
ok = _lib.extism_plugin_update(
|
||||
self.ctx.pointer,
|
||||
self.plugin,
|
||||
wasm,
|
||||
len(wasm),
|
||||
ptr,
|
||||
len(functions),
|
||||
wasi,
|
||||
)
|
||||
else:
|
||||
ok = _lib.extism_plugin_update(
|
||||
self.ctx.pointer, self.plugin, wasm, len(wasm), _ffi.NULL, 0, wasi
|
||||
)
|
||||
if not ok:
|
||||
error = _lib.extism_error(self.ctx.pointer, -1)
|
||||
if error != _ffi.NULL:
|
||||
@@ -310,3 +378,120 @@ class Plugin:
|
||||
|
||||
def __exit__(self, type, exc, traceback):
|
||||
self.__del__()
|
||||
|
||||
|
||||
def _convert_value(x):
|
||||
if x.t == 0:
|
||||
return Val(ValType.I32, x.v.i32)
|
||||
elif x.t == 1:
|
||||
return Val(ValType.I64, x.v.i64)
|
||||
elif x.t == 2:
|
||||
return Val(ValType.F32, x.v.f32)
|
||||
elif x.y == 3:
|
||||
return Val(ValType.F64, x.v.f64)
|
||||
return None
|
||||
|
||||
|
||||
def _convert_output(x, v):
|
||||
if v.t.value != x.t:
|
||||
raise Error(f"Output type mismatch, got {v.t} but expected {x.t}")
|
||||
|
||||
if v.t == ValType.I32:
|
||||
x.v.i32 = int(v.value)
|
||||
elif v.t == ValType.I64:
|
||||
x.v.i64 = int(v.value)
|
||||
elif x.t == ValType.F32:
|
||||
x.v.f32 = float(v.value)
|
||||
elif x.t == ValType.F64:
|
||||
x.v.f64 = float(v.value)
|
||||
else:
|
||||
raise Error("Unsupported return type: " + str(x.t))
|
||||
|
||||
|
||||
class ValType(Enum):
|
||||
I32 = 0
|
||||
I64 = 1
|
||||
F32 = 2
|
||||
F64 = 3
|
||||
FUNC_REF = 4
|
||||
EXTERN_REF = 5
|
||||
|
||||
|
||||
class Val:
|
||||
"""
|
||||
Low-level WebAssembly value
|
||||
"""
|
||||
|
||||
def __init__(self, t: ValType, v):
|
||||
self.t = t
|
||||
self.value = v
|
||||
|
||||
def __repr__(self):
|
||||
return f"Val({self.t}, {self.value})"
|
||||
|
||||
|
||||
class CurrentPlugin:
|
||||
"""
|
||||
Wraps the current plugin from inside a host function
|
||||
"""
|
||||
|
||||
def __init__(self, p):
|
||||
self.pointer = p
|
||||
|
||||
def memory(self, mem: Memory):
|
||||
"""Access a block of memory"""
|
||||
p = _lib.extism_current_plugin_memory(self.pointer)
|
||||
if p == 0:
|
||||
return None
|
||||
return _ffi.buffer(p + mem.offset, mem.length)
|
||||
|
||||
def alloc(self, n):
|
||||
"""Allocate a new block of memory of [n] bytes"""
|
||||
offs = _lib.extism_current_plugin_memory_alloc(self.pointer, n)
|
||||
return Memory(offs, n)
|
||||
|
||||
def free(self, mem):
|
||||
"""Free a block of memory"""
|
||||
return _lib.extism_current_plugin_memory_free(self.pointer, mem.offset)
|
||||
|
||||
def memory_at_offset(self, offs):
|
||||
"""Get a block of memory at the specified offset"""
|
||||
if isinstance(offs, Val):
|
||||
offs = offs.value
|
||||
len = _lib.extism_current_plugin_memory_length(self.pointer, offs)
|
||||
return Memory(offs, len)
|
||||
|
||||
|
||||
def host_fn(func):
|
||||
"""
|
||||
A decorator for creating host functions, this decorator wraps a function that takes the following parameters:
|
||||
- current plugin: `CurrentPlugin`
|
||||
- inputs: `List[Val]`
|
||||
- user_data: any number of values passed as user data
|
||||
|
||||
The function should return a list of `Val`
|
||||
"""
|
||||
|
||||
@_ffi.callback(
|
||||
"void(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)"
|
||||
)
|
||||
def handle_args(current, inputs, n_inputs, outputs, n_outputs, user_data):
|
||||
inp = []
|
||||
outp = []
|
||||
|
||||
for i in range(n_inputs):
|
||||
inp.append(_convert_value(inputs[i]))
|
||||
|
||||
for i in range(n_outputs):
|
||||
outp.append(_convert_value(outputs[i]))
|
||||
|
||||
if user_data == _ffi.NULL:
|
||||
output = func(CurrentPlugin(current), inp, outp)
|
||||
else:
|
||||
udata = _ffi.from_handle(user_data)
|
||||
func(CurrentPlugin(current), inp, outp, *udata)
|
||||
|
||||
for i in range(n_outputs):
|
||||
_convert_output(outputs[i], outp[i])
|
||||
|
||||
return handle_args
|
||||
|
||||
@@ -7,7 +7,6 @@ from os.path import join, dirname
|
||||
|
||||
|
||||
class TestExtism(unittest.TestCase):
|
||||
|
||||
def test_context_new(self):
|
||||
ctx = extism.Context()
|
||||
self.assertIsNotNone(ctx)
|
||||
@@ -20,8 +19,7 @@ class TestExtism(unittest.TestCase):
|
||||
self.assertEqual(j["count"], 4)
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test again"))
|
||||
self.assertEqual(j["count"], 7)
|
||||
j = json.loads(plugin.call("count_vowels",
|
||||
"this is a test thrice"))
|
||||
j = json.loads(plugin.call("count_vowels", "this is a test thrice"))
|
||||
self.assertEqual(j["count"], 6)
|
||||
j = json.loads(plugin.call("count_vowels", "🌎hello🌎world🌎"))
|
||||
self.assertEqual(j["count"], 3)
|
||||
@@ -44,8 +42,9 @@ class TestExtism(unittest.TestCase):
|
||||
def test_errors_on_unknown_function(self):
|
||||
with extism.Context() as ctx:
|
||||
plugin = ctx.plugin(self._manifest())
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: plugin.call("i_dont_exist", "someinput"))
|
||||
self.assertRaises(
|
||||
extism.Error, lambda: plugin.call("i_dont_exist", "someinput")
|
||||
)
|
||||
|
||||
def test_can_free_plugin(self):
|
||||
with extism.Context() as ctx:
|
||||
@@ -54,12 +53,13 @@ class TestExtism(unittest.TestCase):
|
||||
|
||||
def test_errors_on_bad_manifest(self):
|
||||
with extism.Context() as ctx:
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: ctx.plugin({"invalid_manifest": True}))
|
||||
self.assertRaises(
|
||||
extism.Error, lambda: ctx.plugin({"invalid_manifest": True})
|
||||
)
|
||||
plugin = ctx.plugin(self._manifest())
|
||||
self.assertRaises(
|
||||
extism.Error,
|
||||
lambda: plugin.update({"invalid_manifest": True}))
|
||||
extism.Error, lambda: plugin.update({"invalid_manifest": True})
|
||||
)
|
||||
|
||||
def test_extism_version(self):
|
||||
self.assertIsNotNone(extism.extism_version())
|
||||
@@ -68,36 +68,25 @@ class TestExtism(unittest.TestCase):
|
||||
with extism.Context() as ctx:
|
||||
plugin = ctx.plugin(self._loop_manifest())
|
||||
start = datetime.now()
|
||||
self.assertRaises(extism.Error,
|
||||
lambda: plugin.call("infinite_loop", b""))
|
||||
self.assertRaises(extism.Error, lambda: plugin.call("infinite_loop", b""))
|
||||
end = datetime.now()
|
||||
self.assertLess(end, start + timedelta(seconds=1.01),
|
||||
"plugin timeout exceeded 1000ms expectation")
|
||||
self.assertLess(
|
||||
end,
|
||||
start + timedelta(seconds=1.01),
|
||||
"plugin timeout exceeded 1000ms expectation",
|
||||
)
|
||||
|
||||
def _manifest(self):
|
||||
wasm = self._count_vowels_wasm()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
return {
|
||||
"wasm": [{
|
||||
"data": wasm,
|
||||
"hash": hash
|
||||
}],
|
||||
"memory": {
|
||||
"max_pages": 5
|
||||
}
|
||||
}
|
||||
return {"wasm": [{"data": wasm, "hash": hash}], "memory": {"max_pages": 5}}
|
||||
|
||||
def _loop_manifest(self):
|
||||
wasm = self._infinite_loop_wasm()
|
||||
hash = hashlib.sha256(wasm).hexdigest()
|
||||
return {
|
||||
"wasm": [{
|
||||
"data": wasm,
|
||||
"hash": hash
|
||||
}],
|
||||
"memory": {
|
||||
"max_pages": 5
|
||||
},
|
||||
"wasm": [{"data": wasm, "hash": hash}],
|
||||
"memory": {"max_pages": 5},
|
||||
"timeout_ms": 1000,
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ module Extism
|
||||
end
|
||||
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
||||
code.put_bytes(0, wasm)
|
||||
@plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, wasi)
|
||||
@plugin = C.extism_plugin_new(context.pointer, code, wasm.bytesize, nil, 0, wasi)
|
||||
if @plugin < 0
|
||||
err = C.extism_error(@context.pointer, -1)
|
||||
if err&.empty?
|
||||
@@ -161,7 +161,7 @@ module Extism
|
||||
end
|
||||
code = FFI::MemoryPointer.new(:char, wasm.bytesize)
|
||||
code.put_bytes(0, wasm)
|
||||
ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, wasi)
|
||||
ok = C.extism_plugin_update(@context.pointer, @plugin, code, wasm.bytesize, nil, 0, wasi)
|
||||
if !ok
|
||||
err = C.extism_error(@context.pointer, @plugin)
|
||||
if err&.empty?
|
||||
@@ -230,8 +230,8 @@ module Extism
|
||||
ffi_lib "extism"
|
||||
attach_function :extism_context_new, [], :pointer
|
||||
attach_function :extism_context_free, [:pointer], :void
|
||||
attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :bool], :int32
|
||||
attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :bool], :bool
|
||||
attach_function :extism_plugin_new, [:pointer, :pointer, :uint64, :pointer, :uint64, :bool], :int32
|
||||
attach_function :extism_plugin_update, [:pointer, :int32, :pointer, :uint64, :pointer, :uint64, :bool], :bool
|
||||
attach_function :extism_error, [:pointer, :int32], :string
|
||||
attach_function :extism_plugin_call, [:pointer, :int32, :string, :pointer, :uint64], :int32
|
||||
attach_function :extism_plugin_function_exists, [:pointer, :int32, :string], :bool
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
fn main() {
|
||||
let fn_macro = "#define EXTISM_FUNCTION(N) extern void N(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)";
|
||||
let go_fn_macro = "#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)";
|
||||
if let Ok(bindings) = cbindgen::Builder::new()
|
||||
.with_crate(".")
|
||||
.with_language(cbindgen::Language::C)
|
||||
@@ -6,9 +8,15 @@ fn main() {
|
||||
.with_sys_include("stdint.h")
|
||||
.with_sys_include("stdbool.h")
|
||||
.with_pragma_once(true)
|
||||
.with_after_include(fn_macro)
|
||||
.with_after_include(go_fn_macro)
|
||||
.rename_item("Size", "ExtismSize")
|
||||
.rename_item("PluginIndex", "ExtismPlugin")
|
||||
.rename_item("Context", "ExtismContext")
|
||||
.rename_item("ValType", "ExtismValType")
|
||||
.rename_item("ValUnion", "ExtismValUnion")
|
||||
.rename_item("Plugin", "ExtismCurrentPlugin")
|
||||
.with_style(cbindgen::Style::Type)
|
||||
.generate()
|
||||
{
|
||||
bindings.write_to_file("extism.h");
|
||||
|
||||
159
runtime/extism.h
159
runtime/extism.h
@@ -2,36 +2,163 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)
|
||||
|
||||
/**
|
||||
* A list of all possible value types in WebAssembly.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Signed 32 bit integer.
|
||||
*/
|
||||
I32,
|
||||
/**
|
||||
* Signed 64 bit integer.
|
||||
*/
|
||||
I64,
|
||||
/**
|
||||
* Floating point 32 bit integer.
|
||||
*/
|
||||
F32,
|
||||
/**
|
||||
* Floating point 64 bit integer.
|
||||
*/
|
||||
F64,
|
||||
/**
|
||||
* A 128 bit number.
|
||||
*/
|
||||
V128,
|
||||
/**
|
||||
* A reference to a Wasm function.
|
||||
*/
|
||||
FuncRef,
|
||||
/**
|
||||
* A reference to opaque data in the Wasm instance.
|
||||
*/
|
||||
ExternRef,
|
||||
} ExtismValType;
|
||||
|
||||
/**
|
||||
* A `Context` is used to store and manage plugins
|
||||
*/
|
||||
typedef struct ExtismContext ExtismContext;
|
||||
|
||||
typedef int32_t ExtismPlugin;
|
||||
/**
|
||||
* Wraps host functions
|
||||
*/
|
||||
typedef struct ExtismFunction ExtismFunction;
|
||||
|
||||
/**
|
||||
* Plugin contains everything needed to execute a WASM function
|
||||
*/
|
||||
typedef struct ExtismCurrentPlugin ExtismCurrentPlugin;
|
||||
|
||||
typedef uint64_t ExtismSize;
|
||||
|
||||
/**
|
||||
* A union type for host function argument/return values
|
||||
*/
|
||||
typedef union {
|
||||
int32_t i32;
|
||||
int64_t i64;
|
||||
float f32;
|
||||
double f64;
|
||||
} ExtismValUnion;
|
||||
|
||||
/**
|
||||
* `ExtismVal` holds the type and value of a function argument/return
|
||||
*/
|
||||
typedef struct {
|
||||
ExtismValType t;
|
||||
ExtismValUnion v;
|
||||
} ExtismVal;
|
||||
|
||||
/**
|
||||
* Host function signature
|
||||
*/
|
||||
typedef void (*ExtismFunctionType)(ExtismCurrentPlugin *plugin, const ExtismVal *inputs, ExtismSize n_inputs, ExtismVal *outputs, ExtismSize n_outputs, void *data);
|
||||
|
||||
typedef int32_t ExtismPlugin;
|
||||
|
||||
/**
|
||||
* Create a new context
|
||||
*/
|
||||
struct ExtismContext *extism_context_new(void);
|
||||
ExtismContext *extism_context_new(void);
|
||||
|
||||
/**
|
||||
* Free a context
|
||||
*/
|
||||
void extism_context_free(struct ExtismContext *ctx);
|
||||
void extism_context_free(ExtismContext *ctx);
|
||||
|
||||
/**
|
||||
* Create a new plugin
|
||||
* Returns a pointer to the memory of the currently running plugin
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
uint8_t *extism_current_plugin_memory(ExtismCurrentPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Allocate a memory block in the currently running plugin
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
uint64_t extism_current_plugin_memory_alloc(ExtismCurrentPlugin *plugin, ExtismSize n);
|
||||
|
||||
/**
|
||||
* Get the length of an allocated block
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, ExtismSize n);
|
||||
|
||||
/**
|
||||
* Free an allocated memory block
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, uint64_t ptr);
|
||||
|
||||
/**
|
||||
* Create a new host function
|
||||
*
|
||||
* Arguments
|
||||
* - `name`: function name, this should be valid UTF-8
|
||||
* - `inputs`: argument types
|
||||
* - `n_inputs`: number of argument types
|
||||
* - `outputs`: return types
|
||||
* - `n_outputs`: number of return types
|
||||
* - `func`: the function to call
|
||||
* - `user_data`: a pointer that will be passed to the function when it's called
|
||||
* this value should live as long as the function exists
|
||||
* - `free_user_data`: a callback to release the `user_data` value when the resulting
|
||||
* `ExtismFunction` is freed.
|
||||
*
|
||||
* Returns a new `ExtismFunction` or `null` if the `name` argument is invalid.
|
||||
*/
|
||||
ExtismFunction *extism_function_new(const char *name,
|
||||
const ExtismValType *inputs,
|
||||
ExtismSize n_inputs,
|
||||
const ExtismValType *outputs,
|
||||
ExtismSize n_outputs,
|
||||
ExtismFunctionType func,
|
||||
void *user_data,
|
||||
void (*free_user_data)(void *_));
|
||||
|
||||
/**
|
||||
* Free an `ExtismFunction`
|
||||
*/
|
||||
void extism_function_free(ExtismFunction *ptr);
|
||||
|
||||
/**
|
||||
* Create a new plugin with additional host functions
|
||||
*
|
||||
* `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
* `wasm_size`: the length of the `wasm` parameter
|
||||
* `functions`: an array of `ExtismFunction*`
|
||||
* `n_functions`: the number of functions provided
|
||||
* `with_wasi`: enables/disables WASI
|
||||
*/
|
||||
ExtismPlugin extism_plugin_new(struct ExtismContext *ctx,
|
||||
ExtismPlugin extism_plugin_new(ExtismContext *ctx,
|
||||
const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
const ExtismFunction **functions,
|
||||
ExtismSize n_functions,
|
||||
bool with_wasi);
|
||||
|
||||
/**
|
||||
@@ -42,26 +169,28 @@ ExtismPlugin extism_plugin_new(struct ExtismContext *ctx,
|
||||
*
|
||||
* Memory for this plugin will be reset upon update
|
||||
*/
|
||||
bool extism_plugin_update(struct ExtismContext *ctx,
|
||||
bool extism_plugin_update(ExtismContext *ctx,
|
||||
ExtismPlugin index,
|
||||
const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
const ExtismFunction **functions,
|
||||
ExtismSize nfunctions,
|
||||
bool with_wasi);
|
||||
|
||||
/**
|
||||
* Remove a plugin from the registry and free associated memory
|
||||
*/
|
||||
void extism_plugin_free(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
void extism_plugin_free(ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Remove all plugins from the registry
|
||||
*/
|
||||
void extism_context_reset(struct ExtismContext *ctx);
|
||||
void extism_context_reset(ExtismContext *ctx);
|
||||
|
||||
/**
|
||||
* Update plugin config values, this will merge with the existing values
|
||||
*/
|
||||
bool extism_plugin_config(struct ExtismContext *ctx,
|
||||
bool extism_plugin_config(ExtismContext *ctx,
|
||||
ExtismPlugin plugin,
|
||||
const uint8_t *json,
|
||||
ExtismSize json_size);
|
||||
@@ -69,9 +198,7 @@ bool extism_plugin_config(struct ExtismContext *ctx,
|
||||
/**
|
||||
* Returns true if `func_name` exists
|
||||
*/
|
||||
bool extism_plugin_function_exists(struct ExtismContext *ctx,
|
||||
ExtismPlugin plugin,
|
||||
const char *func_name);
|
||||
bool extism_plugin_function_exists(ExtismContext *ctx, ExtismPlugin plugin, const char *func_name);
|
||||
|
||||
/**
|
||||
* Call a function
|
||||
@@ -80,7 +207,7 @@ bool extism_plugin_function_exists(struct ExtismContext *ctx,
|
||||
* `data`: is the input data
|
||||
* `data_len`: is the length of `data`
|
||||
*/
|
||||
int32_t extism_plugin_call(struct ExtismContext *ctx,
|
||||
int32_t extism_plugin_call(ExtismContext *ctx,
|
||||
ExtismPlugin plugin_id,
|
||||
const char *func_name,
|
||||
const uint8_t *data,
|
||||
@@ -90,17 +217,17 @@ int32_t extism_plugin_call(struct ExtismContext *ctx,
|
||||
* Get the error associated with a `Context` or `Plugin`, if `plugin` is `-1` then the context
|
||||
* error will be returned
|
||||
*/
|
||||
const char *extism_error(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
const char *extism_error(ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the length of a plugin's output data
|
||||
*/
|
||||
ExtismSize extism_plugin_output_length(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
ExtismSize extism_plugin_output_length(ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Get the length of a plugin's output data
|
||||
*/
|
||||
const uint8_t *extism_plugin_output_data(struct ExtismContext *ctx, ExtismPlugin plugin);
|
||||
const uint8_t *extism_plugin_output_data(ExtismContext *ctx, ExtismPlugin plugin);
|
||||
|
||||
/**
|
||||
* Set log file and level
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
use crate::*;
|
||||
@@ -7,7 +8,7 @@ static mut TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
|
||||
/// A `Context` is used to store and manage plugins
|
||||
pub struct Context {
|
||||
/// Plugin registry
|
||||
pub plugins: BTreeMap<PluginIndex, Plugin>,
|
||||
pub plugins: BTreeMap<PluginIndex, UnsafeCell<Plugin>>,
|
||||
|
||||
/// Error message
|
||||
pub error: Option<std::ffi::CString>,
|
||||
@@ -90,29 +91,17 @@ impl Context {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
self.plugins.insert(id, plugin);
|
||||
self.plugins.insert(id, UnsafeCell::new(plugin));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn new_plugin(&mut self, data: impl AsRef<[u8]>, with_wasi: bool) -> PluginIndex {
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
self.set_error(e);
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
self.insert(plugin)
|
||||
}
|
||||
|
||||
pub fn new_plugin_with_functions(
|
||||
pub fn new_plugin<'a>(
|
||||
&mut self,
|
||||
data: impl AsRef<[u8]>,
|
||||
imports: impl IntoIterator<Item = Function>,
|
||||
imports: impl IntoIterator<Item = &'a Function>,
|
||||
with_wasi: bool,
|
||||
) -> PluginIndex {
|
||||
let plugin = match Plugin::new_with_functions(data, imports, with_wasi) {
|
||||
let plugin = match Plugin::new(data, imports, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
@@ -136,8 +125,11 @@ impl Context {
|
||||
}
|
||||
|
||||
/// Get a plugin from the context
|
||||
pub fn plugin(&mut self, id: PluginIndex) -> Option<&mut Plugin> {
|
||||
self.plugins.get_mut(&id)
|
||||
pub fn plugin(&mut self, id: PluginIndex) -> Option<*mut Plugin> {
|
||||
match self.plugins.get_mut(&id) {
|
||||
Some(x) => Some(x.get_mut()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plugin_exists(&mut self, id: PluginIndex) -> bool {
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::{Error, Internal};
|
||||
|
||||
/// A list of all possible value types in WebAssembly.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub enum ValType {
|
||||
// NB: the ordering here is intended to match the ordering in
|
||||
// `wasmtime_types::WasmType` to help improve codegen when converting.
|
||||
@@ -51,53 +52,146 @@ impl From<ValType> for wasmtime::ValType {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct Function(
|
||||
pub(crate) String,
|
||||
pub(crate) wasmtime::FuncType,
|
||||
pub(crate) Box<
|
||||
dyn Fn(
|
||||
wasmtime::Caller<Internal>,
|
||||
&[wasmtime::Val],
|
||||
&mut [wasmtime::Val],
|
||||
) -> Result<(), Error>
|
||||
+ Sync
|
||||
+ Send,
|
||||
>,
|
||||
);
|
||||
pub type Val = wasmtime::Val;
|
||||
|
||||
pub struct UserData {
|
||||
ptr: *mut std::ffi::c_void,
|
||||
free: Option<extern "C" fn(_: *mut std::ffi::c_void)>,
|
||||
is_any: bool,
|
||||
}
|
||||
|
||||
extern "C" fn free_any(ptr: *mut std::ffi::c_void) {
|
||||
let ptr = ptr as *mut dyn std::any::Any;
|
||||
unsafe { drop(Box::from_raw(ptr)) }
|
||||
}
|
||||
|
||||
impl UserData {
|
||||
pub fn new_pointer(
|
||||
ptr: *mut std::ffi::c_void,
|
||||
free: Option<extern "C" fn(_: *mut std::ffi::c_void)>,
|
||||
) -> Self {
|
||||
UserData {
|
||||
ptr,
|
||||
free,
|
||||
is_any: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new<T: std::any::Any>(x: T) -> Self {
|
||||
let ptr = Box::into_raw(Box::new(x)) as *mut _;
|
||||
UserData {
|
||||
ptr,
|
||||
free: Some(free_any),
|
||||
is_any: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_null(&self) -> bool {
|
||||
self.ptr.is_null()
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut std::ffi::c_void {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
pub(crate) fn make_copy(&self) -> Self {
|
||||
UserData {
|
||||
ptr: self.ptr,
|
||||
free: None,
|
||||
is_any: self.is_any,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any(&self) -> Option<&dyn std::any::Any> {
|
||||
if !self.is_any || self.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { Some(&*self.ptr) }
|
||||
}
|
||||
|
||||
pub fn any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
if !self.is_any || self.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { Some(&mut *self.ptr) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserData {
|
||||
fn default() -> Self {
|
||||
UserData {
|
||||
ptr: std::ptr::null_mut(),
|
||||
free: None,
|
||||
is_any: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for UserData {
|
||||
fn drop(&mut self) {
|
||||
if self.ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(free) = self.free {
|
||||
free(self.ptr);
|
||||
}
|
||||
|
||||
self.ptr = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for UserData {}
|
||||
unsafe impl Sync for UserData {}
|
||||
|
||||
type FunctionInner = dyn Fn(wasmtime::Caller<Internal>, &[wasmtime::Val], &mut [wasmtime::Val]) -> Result<(), Error>
|
||||
+ Sync
|
||||
+ Send;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Function {
|
||||
pub(crate) name: String,
|
||||
pub(crate) ty: wasmtime::FuncType,
|
||||
pub(crate) f: std::sync::Arc<FunctionInner>,
|
||||
pub(crate) _user_data: std::sync::Arc<UserData>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn new<F>(
|
||||
name: impl Into<String>,
|
||||
args: impl IntoIterator<Item = ValType>,
|
||||
returns: impl IntoIterator<Item = ValType>,
|
||||
user_data: Option<UserData>,
|
||||
f: F,
|
||||
) -> Function
|
||||
where
|
||||
F: 'static
|
||||
+ Fn(
|
||||
wasmtime::Caller<Internal>,
|
||||
&[wasmtime::Val],
|
||||
&mut [wasmtime::Val],
|
||||
) -> Result<(), Error>
|
||||
+ Fn(&mut crate::Plugin, &[Val], &mut [Val], UserData) -> Result<(), Error>
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
Function(
|
||||
name.into(),
|
||||
wasmtime::FuncType::new(
|
||||
let user_data = user_data.unwrap_or_default();
|
||||
let data = UserData::new_pointer(user_data.ptr, None);
|
||||
Function {
|
||||
name: name.into(),
|
||||
ty: wasmtime::FuncType::new(
|
||||
args.into_iter().map(wasmtime::ValType::from),
|
||||
returns.into_iter().map(wasmtime::ValType::from),
|
||||
),
|
||||
Box::new(f),
|
||||
)
|
||||
f: std::sync::Arc::new(move |mut caller, inp, outp| {
|
||||
f(caller.data_mut().plugin_mut(), inp, outp, data.make_copy())
|
||||
}),
|
||||
_user_data: std::sync::Arc::new(user_data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.0
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> &wasmtime::FuncType {
|
||||
&self.1
|
||||
&self.ty
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ pub mod sdk;
|
||||
mod timer;
|
||||
|
||||
pub use context::Context;
|
||||
pub use function::{Function, ValType};
|
||||
pub use function::{Function, UserData, Val, ValType};
|
||||
pub use manifest::Manifest;
|
||||
pub use memory::{MemoryBlock, PluginMemory};
|
||||
pub use memory::{MemoryBlock, PluginMemory, ToMemoryBlock};
|
||||
pub use plugin::{Internal, Plugin, Wasi};
|
||||
pub use plugin_ref::PluginRef;
|
||||
pub(crate) use timer::{Timer, TimerAction};
|
||||
|
||||
@@ -255,7 +255,7 @@ impl PluginMemory {
|
||||
}
|
||||
|
||||
/// Get memory as a mutable slice of bytes
|
||||
pub fn data_mut(&mut self) -> &[u8] {
|
||||
pub fn data_mut(&mut self) -> &mut [u8] {
|
||||
self.memory.data_mut(&mut self.store)
|
||||
}
|
||||
|
||||
|
||||
@@ -96,14 +96,9 @@ const EXPORT_MODULE_NAME: &str = "env";
|
||||
|
||||
impl Plugin {
|
||||
/// Create a new plugin from the given WASM code
|
||||
pub fn new(wasm: impl AsRef<[u8]>, with_wasi: bool) -> Result<Plugin, Error> {
|
||||
Self::new_with_functions(wasm, [], with_wasi)
|
||||
}
|
||||
|
||||
/// Create a new plugin from the given WASM code and imported functions
|
||||
pub fn new_with_functions(
|
||||
pub fn new<'a>(
|
||||
wasm: impl AsRef<[u8]>,
|
||||
imports: impl IntoIterator<Item = Function>,
|
||||
imports: impl IntoIterator<Item = &'a Function>,
|
||||
with_wasi: bool,
|
||||
) -> Result<Plugin, Error> {
|
||||
let engine = Engine::new(
|
||||
@@ -192,7 +187,9 @@ impl Plugin {
|
||||
|
||||
for f in &mut imports {
|
||||
let name = f.name().to_string();
|
||||
let func = Func::new(&mut memory.store, f.ty().clone(), f.2);
|
||||
let func = Func::new(&mut memory.store, f.ty().clone(), unsafe {
|
||||
&*std::sync::Arc::as_ptr(&f.f)
|
||||
});
|
||||
linker.define(EXPORT_MODULE_NAME, &name, func)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::*;
|
||||
// PluginRef is used to access a plugin from a context-scoped plugin registry
|
||||
pub struct PluginRef<'a> {
|
||||
pub id: PluginIndex,
|
||||
plugin: &'a mut Plugin,
|
||||
pub(crate) epoch_timer_tx: std::sync::mpsc::SyncSender<TimerAction>,
|
||||
plugin: *mut Plugin,
|
||||
_t: std::marker::PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> PluginRef<'a> {
|
||||
@@ -15,7 +16,7 @@ impl<'a> PluginRef<'a> {
|
||||
pub fn init(mut self, data: *const u8, data_len: usize) -> Self {
|
||||
trace!("PluginRef::init: {}", self.id,);
|
||||
self.as_mut().memory.reset();
|
||||
self.plugin.set_input(data, data_len);
|
||||
self.as_mut().set_input(data, data_len);
|
||||
|
||||
self
|
||||
}
|
||||
@@ -40,19 +41,23 @@ impl<'a> PluginRef<'a> {
|
||||
|
||||
// `unwrap` is okay here because we already checked with `ctx.plugin_exists` above
|
||||
let plugin = ctx.plugin(plugin_id).unwrap();
|
||||
if clear_error {
|
||||
trace!("Clearing plugin error: {plugin_id}");
|
||||
plugin.clear_error();
|
||||
}
|
||||
|
||||
// Reinstantiate plugin after calling _start because according to the WASI
|
||||
// applicate ABI _start should be called "at most once":
|
||||
// https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md
|
||||
if plugin.should_reinstantiate {
|
||||
plugin.should_reinstantiate = false;
|
||||
if let Err(e) = plugin.reinstantiate() {
|
||||
error!("Failed to reinstantiate: {e:?}");
|
||||
return plugin.error(format!("Failed to reinstantiate: {e:?}"), None);
|
||||
{
|
||||
let plugin = unsafe { &mut *plugin };
|
||||
if clear_error {
|
||||
trace!("Clearing plugin error: {plugin_id}");
|
||||
plugin.clear_error();
|
||||
}
|
||||
|
||||
// Reinstantiate plugin after calling _start because according to the WASI
|
||||
// applicate ABI _start should be called "at most once":
|
||||
// https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md
|
||||
if plugin.should_reinstantiate {
|
||||
plugin.should_reinstantiate = false;
|
||||
if let Err(e) = plugin.reinstantiate() {
|
||||
error!("Failed to reinstantiate: {e:?}");
|
||||
return plugin.error(format!("Failed to reinstantiate: {e:?}"), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,25 +65,26 @@ impl<'a> PluginRef<'a> {
|
||||
id: plugin_id,
|
||||
plugin,
|
||||
epoch_timer_tx,
|
||||
_t: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsRef<Plugin> for PluginRef<'a> {
|
||||
fn as_ref(&self) -> &Plugin {
|
||||
self.plugin
|
||||
unsafe { &*self.plugin }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsMut<Plugin> for PluginRef<'a> {
|
||||
fn as_mut(&mut self) -> &mut Plugin {
|
||||
self.plugin
|
||||
unsafe { &mut *self.plugin }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for PluginRef<'a> {
|
||||
fn drop(&mut self) {
|
||||
trace!("Dropping plugin {}", self.id);
|
||||
trace!("Dropping PluginRef {}", self.id);
|
||||
// Cleanup?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,74 @@ use std::str::FromStr;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A union type for host function argument/return values
|
||||
#[repr(C)]
|
||||
pub union ValUnion {
|
||||
i32: i32,
|
||||
i64: i64,
|
||||
f32: f32,
|
||||
f64: f64,
|
||||
// TODO: v128, ExternRef, FuncRef
|
||||
}
|
||||
|
||||
/// `ExtismVal` holds the type and value of a function argument/return
|
||||
#[repr(C)]
|
||||
pub struct ExtismVal {
|
||||
t: ValType,
|
||||
v: ValUnion,
|
||||
}
|
||||
|
||||
/// Wraps host functions
|
||||
pub struct ExtismFunction(Function);
|
||||
|
||||
impl From<Function> for ExtismFunction {
|
||||
fn from(x: Function) -> Self {
|
||||
ExtismFunction(x)
|
||||
}
|
||||
}
|
||||
|
||||
/// Host function signature
|
||||
pub type ExtismFunctionType = extern "C" fn(
|
||||
plugin: *mut Plugin,
|
||||
inputs: *const ExtismVal,
|
||||
n_inputs: Size,
|
||||
outputs: *mut ExtismVal,
|
||||
n_outputs: Size,
|
||||
data: *mut std::ffi::c_void,
|
||||
);
|
||||
|
||||
impl From<&wasmtime::Val> for ExtismVal {
|
||||
fn from(value: &wasmtime::Val) -> Self {
|
||||
match value.ty() {
|
||||
wasmtime::ValType::I32 => ExtismVal {
|
||||
t: ValType::I32,
|
||||
v: ValUnion {
|
||||
i32: value.unwrap_i32(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::I64 => ExtismVal {
|
||||
t: ValType::I64,
|
||||
v: ValUnion {
|
||||
i64: value.unwrap_i64(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::F32 => ExtismVal {
|
||||
t: ValType::F32,
|
||||
v: ValUnion {
|
||||
f32: value.unwrap_f32(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::F64 => ExtismVal {
|
||||
t: ValType::F64,
|
||||
v: ValUnion {
|
||||
f64: value.unwrap_f64(),
|
||||
},
|
||||
},
|
||||
t => todo!("{}", t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new context
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_context_new() -> *mut Context {
|
||||
@@ -22,22 +90,192 @@ pub unsafe extern "C" fn extism_context_free(ctx: *mut Context) {
|
||||
drop(Box::from_raw(ctx))
|
||||
}
|
||||
|
||||
/// Create a new plugin
|
||||
/// Returns a pointer to the memory of the currently running plugin
|
||||
/// NOTE: this should only be called from host functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_current_plugin_memory(plugin: *mut Plugin) -> *mut u8 {
|
||||
if plugin.is_null() {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
plugin.memory.data_mut().as_mut_ptr()
|
||||
}
|
||||
|
||||
/// Allocate a memory block in the currently running plugin
|
||||
/// NOTE: this should only be called from host functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_current_plugin_memory_alloc(plugin: *mut Plugin, n: Size) -> u64 {
|
||||
if plugin.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
|
||||
let mem = match plugin.memory.alloc(n as usize) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return plugin.error(e, 0),
|
||||
};
|
||||
|
||||
mem.offset as u64
|
||||
}
|
||||
|
||||
/// Get the length of an allocated block
|
||||
/// NOTE: this should only be called from host functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_current_plugin_memory_length(plugin: *mut Plugin, n: Size) -> Size {
|
||||
if plugin.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
|
||||
match plugin.memory.block_length(n as usize) {
|
||||
Some(x) => x as Size,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Free an allocated memory block
|
||||
/// NOTE: this should only be called from host functions.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_current_plugin_memory_free(plugin: *mut Plugin, ptr: u64) {
|
||||
if plugin.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let plugin = &mut *plugin;
|
||||
|
||||
plugin.memory.free(ptr as usize);
|
||||
}
|
||||
|
||||
/// Create a new host function
|
||||
///
|
||||
/// Arguments
|
||||
/// - `name`: function name, this should be valid UTF-8
|
||||
/// - `inputs`: argument types
|
||||
/// - `n_inputs`: number of argument types
|
||||
/// - `outputs`: return types
|
||||
/// - `n_outputs`: number of return types
|
||||
/// - `func`: the function to call
|
||||
/// - `user_data`: a pointer that will be passed to the function when it's called
|
||||
/// this value should live as long as the function exists
|
||||
/// - `free_user_data`: a callback to release the `user_data` value when the resulting
|
||||
/// `ExtismFunction` is freed.
|
||||
///
|
||||
/// Returns a new `ExtismFunction` or `null` if the `name` argument is invalid.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_function_new(
|
||||
name: *const std::ffi::c_char,
|
||||
inputs: *const ValType,
|
||||
n_inputs: Size,
|
||||
outputs: *const ValType,
|
||||
n_outputs: Size,
|
||||
func: ExtismFunctionType,
|
||||
user_data: *mut std::ffi::c_void,
|
||||
free_user_data: Option<extern "C" fn(_: *mut std::ffi::c_void)>,
|
||||
) -> *mut ExtismFunction {
|
||||
let name = match std::ffi::CStr::from_ptr(name).to_str() {
|
||||
Ok(x) => x.to_string(),
|
||||
Err(_) => {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let inputs = if inputs.is_null() || n_inputs == 0 {
|
||||
&[]
|
||||
} else {
|
||||
std::slice::from_raw_parts(inputs, n_inputs as usize)
|
||||
}
|
||||
.to_vec();
|
||||
|
||||
let output_types = if outputs.is_null() || n_outputs == 0 {
|
||||
&[]
|
||||
} else {
|
||||
std::slice::from_raw_parts(outputs, n_outputs as usize)
|
||||
}
|
||||
.to_vec();
|
||||
|
||||
let user_data = UserData::new_pointer(user_data, free_user_data);
|
||||
let f = Function::new(
|
||||
name,
|
||||
inputs,
|
||||
output_types.clone(),
|
||||
Some(user_data),
|
||||
move |plugin, inputs, outputs, user_data| {
|
||||
let inputs: Vec<_> = inputs.iter().map(ExtismVal::from).collect();
|
||||
let mut output_tmp: Vec<_> = output_types
|
||||
.iter()
|
||||
.map(|t| ExtismVal {
|
||||
t: t.clone(),
|
||||
v: ValUnion { i64: 0 },
|
||||
})
|
||||
.collect();
|
||||
|
||||
func(
|
||||
plugin,
|
||||
inputs.as_ptr(),
|
||||
inputs.len() as Size,
|
||||
output_tmp.as_mut_ptr(),
|
||||
output_tmp.len() as Size,
|
||||
user_data.as_ptr(),
|
||||
);
|
||||
|
||||
for (tmp, out) in output_tmp.iter().zip(outputs.iter_mut()) {
|
||||
match tmp.t {
|
||||
ValType::I32 => *out = Val::I32(tmp.v.i32),
|
||||
ValType::I64 => *out = Val::I64(tmp.v.i64),
|
||||
ValType::F32 => *out = Val::F32(tmp.v.f32 as u32),
|
||||
ValType::F64 => *out = Val::F64(tmp.v.f64 as u64),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
Box::into_raw(Box::new(ExtismFunction(f)))
|
||||
}
|
||||
|
||||
/// Free an `ExtismFunction`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_function_free(ptr: *mut ExtismFunction) {
|
||||
drop(Box::from_raw(ptr))
|
||||
}
|
||||
|
||||
/// Create a new plugin with additional host functions
|
||||
///
|
||||
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
/// `wasm_size`: the length of the `wasm` parameter
|
||||
/// `functions`: an array of `ExtismFunction*`
|
||||
/// `n_functions`: the number of functions provided
|
||||
/// `with_wasi`: enables/disables WASI
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_new(
|
||||
ctx: *mut Context,
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
functions: *mut *const ExtismFunction,
|
||||
n_functions: Size,
|
||||
with_wasi: bool,
|
||||
) -> PluginIndex {
|
||||
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
|
||||
let ctx = &mut *ctx;
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
ctx.new_plugin(data, with_wasi)
|
||||
let mut funcs = vec![];
|
||||
|
||||
if !functions.is_null() {
|
||||
for i in 0..n_functions {
|
||||
unsafe {
|
||||
let f = *functions.add(i as usize);
|
||||
if f.is_null() {
|
||||
continue;
|
||||
}
|
||||
let f = &*f;
|
||||
funcs.push(&f.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.new_plugin(data, funcs, with_wasi)
|
||||
}
|
||||
|
||||
/// Update a plugin, keeping the existing ID
|
||||
@@ -52,13 +290,31 @@ pub unsafe extern "C" fn extism_plugin_update(
|
||||
index: PluginIndex,
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
functions: *mut *const ExtismFunction,
|
||||
nfunctions: Size,
|
||||
with_wasi: bool,
|
||||
) -> bool {
|
||||
trace!("Call to extism_plugin_update with wasm pointer {:?}", wasm);
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let plugin = match Plugin::new(data, with_wasi) {
|
||||
|
||||
let mut funcs = vec![];
|
||||
|
||||
if !functions.is_null() {
|
||||
for i in 0..nfunctions {
|
||||
unsafe {
|
||||
let f = *functions.add(i as usize);
|
||||
if f.is_null() {
|
||||
continue;
|
||||
}
|
||||
let f = &*f;
|
||||
funcs.push(&f.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let plugin = match Plugin::new(data, funcs, with_wasi) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Error creating Plugin: {:?}", e);
|
||||
@@ -72,7 +328,8 @@ pub unsafe extern "C" fn extism_plugin_update(
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.plugins.insert(index, plugin);
|
||||
ctx.plugins
|
||||
.insert(index, std::cell::UnsafeCell::new(plugin));
|
||||
|
||||
info!("Plugin updated: {index}");
|
||||
true
|
||||
@@ -245,7 +502,7 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
}
|
||||
|
||||
// Call the function
|
||||
let mut results = vec![Val::null(); n_results];
|
||||
let mut results = vec![wasmtime::Val::null(); n_results];
|
||||
let res = func.call(
|
||||
&mut plugin_ref.as_mut().memory.store,
|
||||
&[],
|
||||
|
||||
@@ -13,4 +13,4 @@ extism-manifest = { version = "0.1.0", path = "../manifest" }
|
||||
extism-runtime = { version = "0.1.0", path = "../runtime"}
|
||||
serde_json = "1"
|
||||
log = "0.4"
|
||||
thiserror = "1"
|
||||
anyhow = "1"
|
||||
@@ -1,5 +1,7 @@
|
||||
pub use extism_manifest::{self as manifest, Manifest};
|
||||
pub use extism_runtime::{sdk as bindings, Function, ValType};
|
||||
pub use extism_runtime::{
|
||||
sdk as bindings, Function, MemoryBlock, Plugin as CurrentPlugin, UserData, Val, ValType,
|
||||
};
|
||||
|
||||
mod context;
|
||||
mod plugin;
|
||||
@@ -8,18 +10,7 @@ mod plugin_builder;
|
||||
pub use context::Context;
|
||||
pub use plugin::Plugin;
|
||||
pub use plugin_builder::PluginBuilder;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Unable to load plugin: {0}")]
|
||||
UnableToLoadPlugin(String),
|
||||
#[error("{0}")]
|
||||
Message(String),
|
||||
#[error("JSON: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error("Runtime: {0}")]
|
||||
Runtime(#[from] extism_runtime::Error),
|
||||
}
|
||||
pub type Error = anyhow::Error;
|
||||
|
||||
/// Gets the version of Extism
|
||||
pub fn extism_version() -> String {
|
||||
@@ -44,14 +35,32 @@ mod tests {
|
||||
use super::*;
|
||||
use std::time::Instant;
|
||||
|
||||
const WASM: &[u8] = include_bytes!("../../wasm/code.wasm");
|
||||
const WASM: &[u8] = include_bytes!("../../wasm/code-functions.wasm");
|
||||
|
||||
fn hello_world(
|
||||
_plugin: &mut CurrentPlugin,
|
||||
inputs: &[Val],
|
||||
outputs: &mut [Val],
|
||||
_user_data: UserData,
|
||||
) -> Result<(), Error> {
|
||||
outputs[0] = inputs[0].clone();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let wasm_start = Instant::now();
|
||||
set_log_file("test.log", Some(log::Level::Info));
|
||||
let context = Context::new();
|
||||
let mut plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
[ValType::I64],
|
||||
[ValType::I64],
|
||||
None,
|
||||
hello_world,
|
||||
);
|
||||
let functions = [&f];
|
||||
let mut plugin = Plugin::new(&context, WASM, functions, true).unwrap();
|
||||
println!("register loaded plugin: {:?}", wasm_start.elapsed());
|
||||
|
||||
let repeat = 1182;
|
||||
@@ -143,14 +152,32 @@ mod tests {
|
||||
use std::io::Write;
|
||||
std::thread::spawn(|| {
|
||||
let context = Context::new();
|
||||
let mut plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
[ValType::I64],
|
||||
[ValType::I64],
|
||||
None,
|
||||
hello_world,
|
||||
);
|
||||
let mut plugin = Plugin::new(&context, WASM, [&f], true).unwrap();
|
||||
let output = plugin.call("count_vowels", "this is a test").unwrap();
|
||||
std::io::stdout().write_all(output).unwrap();
|
||||
});
|
||||
|
||||
std::thread::spawn(|| {
|
||||
let f = Function::new(
|
||||
"hello_world",
|
||||
[ValType::I64],
|
||||
[ValType::I64],
|
||||
None,
|
||||
hello_world,
|
||||
);
|
||||
|
||||
let g = f.clone();
|
||||
std::thread::spawn(move || {
|
||||
let context = Context::new();
|
||||
let mut plugin = PluginBuilder::new_with_module(WASM)
|
||||
.with_function(&g)
|
||||
.with_wasi(true)
|
||||
.build(&context)
|
||||
.unwrap();
|
||||
let output = plugin.call("count_vowels", "this is a test aaa").unwrap();
|
||||
@@ -158,7 +185,7 @@ mod tests {
|
||||
});
|
||||
|
||||
let context = Context::new();
|
||||
let mut plugin = Plugin::new(&context, WASM, false).unwrap();
|
||||
let mut plugin = Plugin::new(&context, WASM, [&f], true).unwrap();
|
||||
let output = plugin.call("count_vowels", "abc123").unwrap();
|
||||
std::io::stdout().write_all(output).unwrap();
|
||||
}
|
||||
|
||||
@@ -23,32 +23,27 @@ impl<'a> Plugin<'a> {
|
||||
pub fn new_with_manifest(
|
||||
ctx: &'a Context,
|
||||
manifest: &Manifest,
|
||||
functions: impl IntoIterator<Item = &'a extism_runtime::Function>,
|
||||
wasi: bool,
|
||||
) -> Result<Plugin<'a>, Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
Self::new(ctx, data, wasi)
|
||||
}
|
||||
|
||||
/// Create a new plugin from the given manifest and import functions
|
||||
pub fn new_with_manifest_and_functions(
|
||||
ctx: &'a Context,
|
||||
manifest: &Manifest,
|
||||
imports: impl IntoIterator<Item = extism_runtime::Function>,
|
||||
wasi: bool,
|
||||
) -> Result<Plugin<'a>, Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
Self::new_with_functions(ctx, data, imports, wasi)
|
||||
Self::new(ctx, data, functions, wasi)
|
||||
}
|
||||
|
||||
/// Create a new plugin from a WASM module
|
||||
pub fn new(ctx: &'a Context, data: impl AsRef<[u8]>, wasi: bool) -> Result<Plugin, Error> {
|
||||
let plugin = ctx.lock().new_plugin(data, wasi);
|
||||
pub fn new(
|
||||
ctx: &'a Context,
|
||||
data: impl AsRef<[u8]>,
|
||||
functions: impl IntoIterator<Item = &'a Function>,
|
||||
wasi: bool,
|
||||
) -> Result<Plugin, Error> {
|
||||
let plugin = ctx.lock().new_plugin(data, functions, wasi);
|
||||
|
||||
if plugin < 0 {
|
||||
let err = unsafe { bindings::extism_error(&mut *ctx.lock(), -1) };
|
||||
let buf = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
let buf = buf.to_str().unwrap().to_string();
|
||||
return Err(Error::UnableToLoadPlugin(buf));
|
||||
let buf = buf.to_str().unwrap();
|
||||
return Err(Error::msg(buf));
|
||||
}
|
||||
|
||||
Ok(Plugin {
|
||||
@@ -57,36 +52,40 @@ impl<'a> Plugin<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new plugin from a WASM module with imported functions
|
||||
pub fn new_with_functions(
|
||||
ctx: &'a Context,
|
||||
data: impl AsRef<[u8]>,
|
||||
imports: impl IntoIterator<Item = extism_runtime::Function>,
|
||||
/// Update a plugin with the given manifest
|
||||
pub fn update_with_manifest(
|
||||
&mut self,
|
||||
manifest: &Manifest,
|
||||
functions: impl IntoIterator<Item = &'a Function>,
|
||||
wasi: bool,
|
||||
) -> Result<Plugin, Error> {
|
||||
let plugin = ctx.lock().new_plugin_with_functions(data, imports, wasi);
|
||||
|
||||
if plugin < 0 {
|
||||
let err = unsafe { bindings::extism_error(&mut *ctx.lock(), -1) };
|
||||
let buf = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
let buf = buf.to_str().unwrap().to_string();
|
||||
return Err(Error::UnableToLoadPlugin(buf));
|
||||
}
|
||||
|
||||
Ok(Plugin {
|
||||
id: plugin,
|
||||
context: ctx,
|
||||
})
|
||||
) -> Result<(), Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
self.update(data, functions, wasi)
|
||||
}
|
||||
|
||||
/// Update a plugin with the given WASM module
|
||||
pub fn update(&mut self, data: impl AsRef<[u8]>, wasi: bool) -> Result<(), Error> {
|
||||
pub fn update(
|
||||
&mut self,
|
||||
data: impl AsRef<[u8]>,
|
||||
functions: impl IntoIterator<Item = &'a Function>,
|
||||
wasi: bool,
|
||||
) -> Result<(), Error> {
|
||||
let functions = functions
|
||||
.into_iter()
|
||||
.map(|x| bindings::ExtismFunction::from(x.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
let mut functions = functions
|
||||
.into_iter()
|
||||
.map(|x| &x as *const _)
|
||||
.collect::<Vec<_>>();
|
||||
let b = unsafe {
|
||||
bindings::extism_plugin_update(
|
||||
&mut *self.context.lock(),
|
||||
self.id,
|
||||
data.as_ref().as_ptr(),
|
||||
data.as_ref().len() as u64,
|
||||
functions.as_mut_ptr(),
|
||||
functions.len() as u64,
|
||||
wasi,
|
||||
)
|
||||
};
|
||||
@@ -97,16 +96,10 @@ impl<'a> Plugin<'a> {
|
||||
let err = unsafe { bindings::extism_error(&mut *self.context.lock(), -1) };
|
||||
if !err.is_null() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
return Err(Error::Message(s.to_str().unwrap().to_string()));
|
||||
return Err(Error::msg(s.to_str().unwrap()));
|
||||
}
|
||||
|
||||
Err(Error::Message("extism_plugin_update failed".to_string()))
|
||||
}
|
||||
|
||||
/// Update a plugin with the given manifest
|
||||
pub fn update_manifest(&mut self, manifest: &Manifest, wasi: bool) -> Result<(), Error> {
|
||||
let data = serde_json::to_vec(manifest)?;
|
||||
self.update(data, wasi)
|
||||
Err(Error::msg("extism_plugin_update failed"))
|
||||
}
|
||||
|
||||
/// Set configuration values
|
||||
@@ -158,10 +151,10 @@ impl<'a> Plugin<'a> {
|
||||
let err = unsafe { bindings::extism_error(&mut *self.context.lock(), self.id) };
|
||||
if !err.is_null() {
|
||||
let s = unsafe { std::ffi::CStr::from_ptr(err) };
|
||||
return Err(Error::Message(s.to_str().unwrap().to_string()));
|
||||
return Err(Error::msg(s.to_str().unwrap()));
|
||||
}
|
||||
|
||||
return Err(Error::Message("extism_call failed".to_string()));
|
||||
return Err(Error::msg("extism_call failed"));
|
||||
}
|
||||
|
||||
let out_len =
|
||||
|
||||
@@ -6,13 +6,13 @@ enum Source {
|
||||
}
|
||||
|
||||
/// PluginBuilder is used to configure and create `Plugin` instances
|
||||
pub struct PluginBuilder {
|
||||
pub struct PluginBuilder<'a> {
|
||||
source: Source,
|
||||
wasi: bool,
|
||||
functions: Vec<Function>,
|
||||
functions: Vec<&'a Function>,
|
||||
}
|
||||
|
||||
impl PluginBuilder {
|
||||
impl<'a> PluginBuilder<'a> {
|
||||
/// Create a new `PluginBuilder` with the given WebAssembly module
|
||||
pub fn new_with_module(data: impl Into<Vec<u8>>) -> Self {
|
||||
PluginBuilder {
|
||||
@@ -38,23 +38,23 @@ impl PluginBuilder {
|
||||
}
|
||||
|
||||
/// Add a single host function
|
||||
pub fn with_function(mut self, f: Function) -> Self {
|
||||
pub fn with_function(mut self, f: &'a Function) -> Self {
|
||||
self.functions.push(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple host functions
|
||||
pub fn with_functions(mut self, f: impl IntoIterator<Item = Function>) -> Self {
|
||||
pub fn with_functions(mut self, f: impl IntoIterator<Item = &'a Function>) -> Self {
|
||||
self.functions.extend(f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self, context: &Context) -> Result<Plugin, Error> {
|
||||
pub fn build(self, context: &'a Context) -> Result<Plugin<'a>, Error> {
|
||||
match self.source {
|
||||
Source::Manifest(m) => {
|
||||
Plugin::new_with_manifest_and_functions(context, &m, self.functions, self.wasi)
|
||||
Plugin::new_with_manifest(context, &m, self.functions, self.wasi)
|
||||
}
|
||||
Source::Data(d) => Plugin::new_with_functions(context, &d, self.functions, self.wasi),
|
||||
Source::Data(d) => Plugin::new(context, &d, self.functions, self.wasi),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ class Visitor(c_ast.NodeVisitor):
|
||||
if hasattr(t.type, 'name'):
|
||||
type_name = t.type.name
|
||||
else:
|
||||
type_name = t.type.names[0]
|
||||
try:
|
||||
type_name = t.type.names[0]
|
||||
except:
|
||||
continue
|
||||
|
||||
const = hasattr(t.type, 'quals') and 'const' in t.type.quals
|
||||
t = Type(type_name, const=const, pointer=is_ptr)
|
||||
dest.append(Arg(name, t))
|
||||
|
||||
BIN
wasm/code-functions.wasm
Executable file
BIN
wasm/code-functions.wasm
Executable file
Binary file not shown.
@@ -16,7 +16,7 @@ pub const Plugin = struct {
|
||||
pub fn init(ctx: *Context, data: []const u8, wasi: bool) !Plugin {
|
||||
ctx.mutex.lock();
|
||||
defer ctx.mutex.unlock();
|
||||
const plugin = c.extism_plugin_new(ctx.ctx, toCstr(data), @as(u64, data.len), wasi);
|
||||
const plugin = c.extism_plugin_new(ctx.ctx, toCstr(data), @as(u64, data.len), null, 0, wasi);
|
||||
if (plugin < 0) {
|
||||
const err_c = c.extism_error(ctx.ctx, @as(i32, -1));
|
||||
const err = std.mem.span(err_c);
|
||||
@@ -76,7 +76,7 @@ pub const Plugin = struct {
|
||||
pub fn update(self: *Plugin, data: []const u8, wasi: bool) !void {
|
||||
self.ctx.mutex.lock();
|
||||
defer self.ctx.mutex.unlock();
|
||||
const res = c.extism_plugin_update(self.ctx.ctx, self.id, toCstr(data), @intCast(u64, data.len), wasi);
|
||||
const res = c.extism_plugin_update(self.ctx.ctx, self.id, toCstr(data), @intCast(u64, data.len), null, 0, wasi);
|
||||
if (res) return;
|
||||
const err_c = c.extism_error(self.ctx.ctx, @as(i32, -1));
|
||||
const err = std.mem.span(err_c);
|
||||
|
||||
Reference in New Issue
Block a user