mirror of
https://github.com/extism/extism.git
synced 2026-01-12 07:18:02 -05:00
Compare commits
16 Commits
test-java-
...
readme-lan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c5188cd9a | ||
|
|
92c9301f7a | ||
|
|
d73468a3ac | ||
|
|
ac7e1aeba3 | ||
|
|
668ef5c3c0 | ||
|
|
0170e79f90 | ||
|
|
a550c1b4fe | ||
|
|
c502e62510 | ||
|
|
6774b30de0 | ||
|
|
834d551990 | ||
|
|
a1f36c58d2 | ||
|
|
081c825cd8 | ||
|
|
dc3d54e260 | ||
|
|
cfb1317261 | ||
|
|
0a47a9afde | ||
|
|
30941efe09 |
8
.github/workflows/ci-java.yml
vendored
8
.github/workflows/ci-java.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
version: [11, 17]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
@@ -28,12 +29,9 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
java-version: '${{ matrix.version }}'
|
||||
- name: Test Java
|
||||
run: |
|
||||
cd java
|
||||
mvn --batch-mode -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn verify
|
||||
#- name: Examine logs
|
||||
# if: success() || failure()
|
||||
# run: |
|
||||
# cat /tmp/extism.log
|
||||
|
||||
|
||||
12
Makefile
12
Makefile
@@ -18,21 +18,19 @@ 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
|
||||
|
||||
install:
|
||||
install runtime/extism.h $(DEST)/include
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib
|
||||
mkdir -p $(DEST)/lib $(DEST)/include
|
||||
install runtime/extism.h $(DEST)/include/extism.h
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
uninstall:
|
||||
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
31
README.md
31
README.md
@@ -6,19 +6,26 @@
|
||||
|
||||
# [Extism](https://extism.org)
|
||||
|
||||
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [Go](https://extism.org/docs/integrate-into-your-codebase/go-host-sdk),
|
||||
[Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
|
||||
[Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
|
||||
[C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
|
||||
[OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk),
|
||||
[Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk),
|
||||
[PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk),
|
||||
[Elixir/Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk),
|
||||
[.NET](https://extism.org/docs/integrate-into-your-codebase/dotnet-host-sdk),
|
||||
[Java](https://extism.org/docs/integrate-into-your-codebase/java-host-sdk),
|
||||
[Zig](https://extism.org/docs/integrate-into-your-codebase/zig-host-sdk) & more (others coming soon).
|
||||
The universal plug-in system. Run WebAssembly extensions inside your app. Use idiomatic Host SDKs for [<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/go.svg" width="18" height="18" /> Go](https://extism.org/docs/integrate-into-your-codebase/go-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/ruby.svg" width="18" height="18" /> Ruby](https://extism.org/docs/integrate-into-your-codebase/ruby-host-sdk), [<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/python.svg" width="18" height="18" /> Python](https://extism.org/docs/integrate-into-your-codebase/python-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/nodedotjs.svg" width="18" height="18" /> Node](https://extism.org/docs/integrate-into-your-codebase/node-host-sdk), [<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/rust.svg" width="18" height="18" /> Rust](https://extism.org/docs/integrate-into-your-codebase/rust-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/c.svg" width="18" height="18" /> C](https://extism.org/docs/integrate-into-your-codebase/c-host-sdk), [<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/cplusplus.svg" width="18" height="18" /> C++](https://extism.org/docs/integrate-into-your-codebase/cpp-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/ocaml.svg" width="18" height="18" /> OCaml](https://extism.org/docs/integrate-into-your-codebase/ocaml-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/haskell.svg" width="18" height="18" /> Haskell](https://extism.org/docs/integrate-into-your-codebase/haskell-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/php.svg" width="18" height="18" /> PHP](https://extism.org/docs/integrate-into-your-codebase/php-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/elixir.svg" width="18" height="18" /> Elixir / <img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/erlang.svg" width="18" height="18" /> Erlang](https://extism.org/docs/integrate-into-your-codebase/elixir-or-erlang-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/dotnet.svg" width="18" height="18" /> .NET](https://extism.org/docs/integrate-into-your-codebase/dotnet-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/oracle.svg" width="18" height="18" /> Java](https://extism.org/docs/integrate-into-your-codebase/java-host-sdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/zig.svg" width="18" height="18" /> Zig](https://extism.org/docs/integrate-into-your-codebase/zig-host-sdk) & more (others coming soon).
|
||||
|
||||
Plug-in development kits (PDK) for plug-in authors supported in [Rust](https://github.com/extism/rust-pdk), [AssemblyScript](https://github.com/extism/assemblyscript-pdk), [Go](https://github.com/extism/go-pdk), [C/C++](https://github.com/extism/c-pdk), [Haskell](https://github.com/extism/haskell-pdk), and [Zig](https://github.com/extism/zig-pdk).
|
||||
Plug-in development kits (PDK) for plug-in authors supported in
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/rust.svg" width="18" height="18" /> Rust](https://github.com/extism/rust-pdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/assemblyscript.svg" width="18" height="18" /> AssemblyScript](https://github.com/extism/assemblyscript-pdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/go.svg" width="18" height="18" /> Go](https://github.com/extism/go-pdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/c.svg" width="18" height="18" />
|
||||
C / <img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/cplusplus.svg" width="18" height="18" /> C++](https://github.com/extism/c-pdk),
|
||||
[<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/haskell.svg" width="18" height="18" /> Haskell](https://github.com/extism/haskell-pdk),
|
||||
and [<img src="https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/zig.svg" width="18" height="18" /> Zig](https://github.com/extism/zig-pdk).
|
||||
|
||||
<p align="center">
|
||||
<img style="width: 70%;" src="https://user-images.githubusercontent.com/7517515/210286900-39b144fd-1b26-4dd0-b7a9-2b5755bc174d.png" alt="Extism embedded SDK language support"/>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
build:
|
||||
clang -o main main.c -lextism -L .
|
||||
clang -g -o main main.c -lextism -L .
|
||||
27
c/main.c
27
c/main.c
@@ -9,6 +9,21 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void hello_world(ExtismCurrentPlugin *plugin, const ExtismVal *inputs,
|
||||
uint64_t n_inputs, 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,18 @@ 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, (const ExtismFunction **)&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 +76,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
(package
|
||||
(name extism-manifest)
|
||||
(synopsis "Extism manifest bindings")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(description "Bindings to the Extism manifest format")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
(dune (>= 3.2))
|
||||
|
||||
@@ -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]
|
||||
@@ -50,7 +45,7 @@ fn context_reset(ctx: ResourceArc<ExtismContext>) {
|
||||
|
||||
#[rustler::nif]
|
||||
fn context_free(ctx: ResourceArc<ExtismContext>) {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let context = ctx.ctx.read().unwrap();
|
||||
std::mem::drop(context)
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -85,7 +80,7 @@ fn plugin_call(
|
||||
let mut plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let result = match plugin.call(name, input) {
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
Ok(result) => match str::from_utf8(&result) {
|
||||
Ok(result) => match str::from_utf8(result) {
|
||||
Ok(output) => Ok(output.to_string()),
|
||||
Err(_e) => Err(rustler::Error::Term(Box::new(
|
||||
"Could not read output from plugin",
|
||||
@@ -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)),
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Extism manifest bindings"
|
||||
description: "Bindings to Extism, the universal plugin system"
|
||||
description: "Bindings to the Extism manifest format"
|
||||
maintainer: ["Extism Authors <oss@extism.org>"]
|
||||
authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3-Clause"
|
||||
tags: ["topics" "wasm" "plugin"]
|
||||
homepage: "https://github.com/extism/extism"
|
||||
doc: "https://github.com/extism/extism"
|
||||
doc: "https://extism.org"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
|
||||
163
extism.go
163
extism.go
@@ -5,11 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#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 +22,71 @@ 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 cgo.Handle
|
||||
}
|
||||
|
||||
// Free a function
|
||||
func (f *Function) Free() {
|
||||
C.extism_function_free(f.pointer)
|
||||
f.pointer = nil
|
||||
f.userData.Delete()
|
||||
}
|
||||
|
||||
// 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 = cgo.NewHandle(userData)
|
||||
cname := C.CString(name)
|
||||
ptr := unsafe.Pointer(function.userData)
|
||||
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),
|
||||
ptr,
|
||||
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 +163,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 +205,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 +254,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 +341,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
|
||||
|
||||
@@ -7,7 +7,7 @@ authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3-Clause"
|
||||
tags: ["topics" "wasm" "plugin"]
|
||||
homepage: "https://github.com/extism/extism"
|
||||
doc: "https://github.com/extism/extism"
|
||||
doc: "https://extism.org"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
|
||||
25
go/main.go
25
go/main.go
@@ -4,10 +4,28 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/cgo"
|
||||
"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 uintptr) {
|
||||
fmt.Println("Hello from Go!")
|
||||
s := cgo.Handle(userData)
|
||||
fmt.Println(s.Value().(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 +40,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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Revision history for extism
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
## 0.2.0.0 -- 2023-01-16
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
|
||||
@@ -13,6 +13,9 @@ clean:
|
||||
cabal clean
|
||||
|
||||
publish: clean prepare
|
||||
cabal v2-haddock --haddock-for-hackage ./manifest/extism-manifest.cabal
|
||||
cabal v2-haddock --haddock-for-hackage
|
||||
cabal sdist ./manifest/extism-manifest.cabal
|
||||
cabal sdist
|
||||
# TODO: upload
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ toNullable (Just x) = NotNull x
|
||||
toNullable Nothing = Null
|
||||
fromNullable (NotNull x) = Just x
|
||||
fromNullable Null = Nothing
|
||||
fromNotNull (NotNull x) = x
|
||||
fromNotNull Null = error "Value is Null"
|
||||
mapNullable f Null = Null
|
||||
mapNullable f (NotNull x) = NotNull (f x)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<java.version>11</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependencies -->
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,27 @@ package org.extism.sdk.manifest;
|
||||
import java.util.Map;
|
||||
|
||||
// FIXME remove this and related stuff if not supported in java-sdk
|
||||
public record ManifestHttpRequest(String url, Map<String, String> header, String method) {
|
||||
}
|
||||
public class ManifestHttpRequest {
|
||||
|
||||
private final String url;
|
||||
private final Map<String, String> header;
|
||||
private final String method;
|
||||
|
||||
public ManifestHttpRequest(String url, Map<String, String> header, String method) {
|
||||
this.url = url;
|
||||
this.header = header;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String url() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Map<String, String> header() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public String method() {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
@@ -8,5 +8,11 @@ import com.google.gson.annotations.SerializedName;
|
||||
*
|
||||
* @param max Max number of pages.
|
||||
*/
|
||||
public record MemoryOptions(@SerializedName("max") Integer max) {
|
||||
public class MemoryOptions {
|
||||
@SerializedName("max")
|
||||
private final Integer max;
|
||||
|
||||
public MemoryOptions(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,37 @@ package org.extism.sdk.wasm;
|
||||
|
||||
/**
|
||||
* WASM Source represented by raw bytes.
|
||||
*
|
||||
* @param name
|
||||
* @param data the byte array representing the WASM code
|
||||
* @param hash
|
||||
*/
|
||||
public record ByteArrayWasmSource(String name, byte[] data, String hash) implements WasmSource {
|
||||
public class ByteArrayWasmSource implements WasmSource {
|
||||
|
||||
private final String name;
|
||||
private final byte[] data;
|
||||
private final String hash;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param name
|
||||
* @param data the byte array representing the WASM code
|
||||
* @param hash
|
||||
*/
|
||||
public ByteArrayWasmSource(String name, byte[] data, String hash) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] data() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,39 @@ package org.extism.sdk.wasm;
|
||||
|
||||
/**
|
||||
* WASM Source represented by a file referenced by a path.
|
||||
*
|
||||
* @param name
|
||||
* @param path
|
||||
* @param hash
|
||||
*/
|
||||
public record PathWasmSource(String name, String path, String hash) implements WasmSource {
|
||||
public class PathWasmSource implements WasmSource {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String path;
|
||||
|
||||
private final String hash;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param name
|
||||
* @param path
|
||||
* @param hash
|
||||
*/
|
||||
public PathWasmSource(String name, String path, String hash) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String path() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -250,16 +250,19 @@ impl Manifest {
|
||||
}
|
||||
|
||||
mod base64 {
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserializer, Serializer};
|
||||
|
||||
pub fn serialize<S: Serializer>(v: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
|
||||
let base64 = base64::encode(v);
|
||||
let base64 = general_purpose::STANDARD.encode(v);
|
||||
String::serialize(&base64, s)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
||||
let base64 = String::deserialize(d)?;
|
||||
base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
|
||||
general_purpose::STANDARD
|
||||
.decode(base64.as_bytes())
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
// ... where the context can be passed around to various functions etc.
|
||||
|
||||
3094
node/package-lock.json
generated
3094
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"
|
||||
@@ -36,7 +41,7 @@
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/node": "^18.11.4",
|
||||
"jest": "^29.2.2",
|
||||
"prettier": "2.8.2",
|
||||
"prettier": "2.8.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.23.18",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
15
ocaml/Makefile
Normal file
15
ocaml/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
VERSION?=0.0.0
|
||||
|
||||
build:
|
||||
dune build
|
||||
|
||||
test:
|
||||
dune test
|
||||
format:
|
||||
dune build @fmt --auto-promote
|
||||
|
||||
prepare:
|
||||
opam install .. --deps-only
|
||||
|
||||
publish:
|
||||
opam publish -v $(VERSION) -t $(VERSION) ..
|
||||
@@ -7,7 +7,7 @@ let main file func_name input =
|
||||
with_context @@ fun ctx ->
|
||||
let input = if String.equal input "-" then read_stdin () else input in
|
||||
let file = In_channel.with_open_bin file In_channel.input_all in
|
||||
let plugin = Plugin.make ctx file ~wasi:true |> Result.get_ok in
|
||||
let plugin = Plugin.create ctx file ~wasi:true |> Result.get_ok in
|
||||
let res = Plugin.call plugin ~name:func_name input |> Result.get_ok in
|
||||
print_endline res
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
62
ocaml/lib/current_plugin.ml
Normal file
62
ocaml/lib/current_plugin.ml
Normal file
@@ -0,0 +1,62 @@
|
||||
open Ctypes
|
||||
|
||||
type t = unit ptr
|
||||
type memory_block = { offs : Unsigned.UInt64.t; len : Unsigned.UInt64.t }
|
||||
|
||||
let memory ?(offs = Unsigned.UInt64.zero) t =
|
||||
Bindings.extism_current_plugin_memory t +@ Unsigned.UInt64.to_int offs
|
||||
|
||||
let find t offs =
|
||||
let len = Bindings.extism_current_plugin_memory_length t offs in
|
||||
if Unsigned.UInt64.(equal zero len) then None else Some { offs; len }
|
||||
|
||||
let alloc t len =
|
||||
let len = Unsigned.UInt64.of_int len in
|
||||
let offs = Bindings.extism_current_plugin_memory_alloc t len in
|
||||
{ offs; len }
|
||||
|
||||
let free t { offs; _ } = Bindings.extism_current_plugin_memory_free t offs
|
||||
|
||||
module Memory_block = struct
|
||||
let of_val t v =
|
||||
match Types.Val.to_i64 v with
|
||||
| None -> None
|
||||
| Some v ->
|
||||
let offs = Unsigned.UInt64.of_int64 v in
|
||||
find t offs
|
||||
|
||||
let of_val_exn t v =
|
||||
match of_val t v with
|
||||
| None -> invalid_arg "Memory_block.of_val_exn"
|
||||
| Some v -> v
|
||||
|
||||
let to_val { offs; len = _ } =
|
||||
Types.Val.of_i64 (Unsigned.UInt64.to_int64 offs)
|
||||
|
||||
let get_bigstring t { offs; len } : Bigstringaf.t =
|
||||
let p = memory t ~offs in
|
||||
bigarray_of_ptr array1
|
||||
(Unsigned.UInt64.to_int len)
|
||||
Bigarray.Char
|
||||
(coerce (ptr uint8_t) (ptr char) p)
|
||||
|
||||
let get_string t { offs; len } =
|
||||
let p = memory t ~offs in
|
||||
Ctypes.string_from_ptr
|
||||
(coerce (ptr uint8_t) (ptr char) p)
|
||||
~length:(Unsigned.UInt64.to_int len)
|
||||
|
||||
let set_bigstring t { offs; len } bs =
|
||||
let length = min (Unsigned.UInt64.to_int @@ len) (Bigstringaf.length bs) in
|
||||
let p = coerce (ptr uint8_t) (ptr char) @@ memory t ~offs in
|
||||
for i = 0 to length - 1 do
|
||||
p +@ i <-@ Bigstringaf.unsafe_get bs i
|
||||
done
|
||||
|
||||
let set_string t { offs; len } s =
|
||||
let length = min (Unsigned.UInt64.to_int @@ len) (String.length s) in
|
||||
let p = coerce (ptr uint8_t) (ptr char) @@ memory t ~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,185 @@
|
||||
(** 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 of_int : int -> t
|
||||
val to_int : t -> int
|
||||
end
|
||||
|
||||
(** [Val] represents low-level WebAssembly values *)
|
||||
module Val : sig
|
||||
type t
|
||||
(** Val *)
|
||||
|
||||
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 memory_block = { offs : Unsigned.UInt64.t; len : Unsigned.UInt64.t }
|
||||
(** Represents a block of guest memory *)
|
||||
|
||||
val memory : ?offs:Unsigned.UInt64.t -> t -> Unsigned.uint8 Ctypes.ptr
|
||||
(** Get pointer to entire plugin memory *)
|
||||
|
||||
val find : t -> Unsigned.UInt64.t -> memory_block option
|
||||
(** Find memory block *)
|
||||
|
||||
val alloc : t -> int -> memory_block
|
||||
(** Allocate a new block of memory *)
|
||||
|
||||
val free : t -> memory_block -> unit
|
||||
(** Free an allocated block of memory *)
|
||||
|
||||
(** Some helpter functions for reading/writing memory *)
|
||||
module Memory_block : sig
|
||||
val to_val : memory_block -> Val.t
|
||||
(** Convert memory block to [Val] *)
|
||||
|
||||
val of_val : t -> Val.t -> memory_block option
|
||||
(** Convert [Val] to memory block *)
|
||||
|
||||
val of_val_exn : t -> Val.t -> memory_block
|
||||
(** Convert [Val] to memory block, raises [Invalid_argument] if the value is not a pointer
|
||||
to a valid memory block *)
|
||||
|
||||
val get_string : t -> memory_block -> string
|
||||
(** Get a string from memory stored at the provided offset *)
|
||||
|
||||
val get_bigstring : t -> memory_block -> Bigstringaf.t
|
||||
(** Get a bigstring from memory stored at the provided offset *)
|
||||
|
||||
val set_string : t -> memory_block -> string -> unit
|
||||
(** Store a string into memory at the provided offset *)
|
||||
|
||||
val set_bigstring : t -> memory_block -> 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 create :
|
||||
string ->
|
||||
params:Val_type.t list ->
|
||||
results: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 ~params ~results ~user_data f] creates
|
||||
a new [Function] with the given [name], [params] specifies the argument types,
|
||||
[results] 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 +188,45 @@ val set_log_file :
|
||||
module Plugin : sig
|
||||
type t
|
||||
|
||||
(** Make a new plugin from raw WebAssembly or JSON encoded manifest *)
|
||||
val make :
|
||||
val create :
|
||||
?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 create name ~params ~results ~user_data f =
|
||||
let inputs = CArray.of_list Bindings.Extism_val_type.t params in
|
||||
let n_inputs = Unsigned.UInt64.of_int (CArray.length inputs) in
|
||||
let outputs = CArray.of_list Bindings.Extism_val_type.t results 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 create ?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 data = Manifest.json manifest in
|
||||
make ctx ?wasi data
|
||||
let of_manifest ?wasi ?functions ctx manifest =
|
||||
let data = Manifest.to_json manifest in
|
||||
create ctx ?wasi ?functions data
|
||||
|
||||
let%test "free plugin" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
let manifest = Manifest.(create [ Wasm.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
|
||||
@@ -70,18 +80,18 @@ let update plugin ?config ?(wasi = false) wasm =
|
||||
else Ok ()
|
||||
|
||||
let update_manifest plugin ?wasi manifest =
|
||||
let data = Manifest.json manifest in
|
||||
let data = Manifest.to_json manifest in
|
||||
update plugin ?wasi data
|
||||
|
||||
let%test "update plugin manifest and config" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
let manifest = Manifest.(create [ Wasm.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
|
||||
@@ -103,12 +113,12 @@ let call_bigstring (t : t) ~name input =
|
||||
call' Bindings.extism_plugin_call t ~name ptr len
|
||||
|
||||
let%test "call_bigstring" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
let manifest = Manifest.(create [ Wasm.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
|
||||
@@ -116,18 +126,41 @@ let call (t : t) ~name input =
|
||||
|> Result.map Bigstringaf.to_string
|
||||
|
||||
let%test "call" =
|
||||
let manifest = Manifest.v [ Manifest.file "test/code.wasm" ] in
|
||||
let manifest = Manifest.(create [ Wasm.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.create "hello_world" ~params:[ I64 ] ~results:[ I64 ]
|
||||
~user_data:"Hello again!"
|
||||
@@ fun plugin params results user_data ->
|
||||
let open Types.Val_array in
|
||||
let mem = Current_plugin.Memory_block.of_val_exn plugin params.$[0] in
|
||||
let s = Current_plugin.Memory_block.get_string plugin mem in
|
||||
let () = print_endline "Hello from OCaml!" in
|
||||
let () = print_endline user_data in
|
||||
let () = print_endline s in
|
||||
results.$[0] <- params.$[0]
|
||||
in
|
||||
let functions = [ hello_world ] in
|
||||
let manifest = Manifest.(create [ Wasm.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
|
||||
let manifest = Manifest.(create [ Wasm.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,22 +1,9 @@
|
||||
type memory = { max_pages : int option [@yojson.option] } [@@deriving yojson]
|
||||
|
||||
type wasm_file = {
|
||||
path : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
type base64 = string
|
||||
|
||||
let yojson_of_base64 x = `String (Base64.encode_exn x)
|
||||
let base64_of_yojson j = Yojson.Safe.Util.to_string j
|
||||
|
||||
type wasm_data = {
|
||||
data : base64;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
type memory_options = { max_pages : int option [@yojson.option] }
|
||||
[@@deriving yojson]
|
||||
|
||||
type dict = (string * string) list
|
||||
@@ -43,30 +30,51 @@ let yojson_of_config c =
|
||||
(fun (k, v) -> (k, match v with None -> `Null | Some v -> `String v))
|
||||
c)
|
||||
|
||||
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]
|
||||
module Wasm = struct
|
||||
type file = {
|
||||
path : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
type wasm = File of wasm_file | Data of wasm_data | Url of wasm_url
|
||||
type data = {
|
||||
data : base64;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
let yojson_of_wasm = function
|
||||
| File f -> yojson_of_wasm_file f
|
||||
| Data d -> yojson_of_wasm_data d
|
||||
| Url u -> yojson_of_wasm_url u
|
||||
type 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]
|
||||
|
||||
let wasm_of_yojson x =
|
||||
try File (wasm_file_of_yojson x)
|
||||
with _ -> (
|
||||
try Data (wasm_data_of_yojson x) with _ -> Url (wasm_url_of_yojson x))
|
||||
type t = File of file | Data of data | Url of url
|
||||
|
||||
let yojson_of_t = function
|
||||
| File f -> yojson_of_file f
|
||||
| Data d -> yojson_of_data d
|
||||
| Url u -> yojson_of_url u
|
||||
|
||||
let t_of_yojson x =
|
||||
try File (file_of_yojson x)
|
||||
with _ -> ( try Data (data_of_yojson x) with _ -> Url (url_of_yojson x))
|
||||
|
||||
let file ?name ?hash path = File { path; name; hash }
|
||||
let data ?name ?hash data = Data { data; name; hash }
|
||||
|
||||
let url ?headers ?name ?meth ?hash url =
|
||||
Url { headers; name; meth; hash; url }
|
||||
end
|
||||
|
||||
type t = {
|
||||
wasm : wasm list;
|
||||
memory : memory option; [@yojson.option]
|
||||
wasm : Wasm.t list;
|
||||
memory : memory_options option; [@yojson.option]
|
||||
config : config option; [@yojson.option]
|
||||
allowed_hosts : string list option; [@yojson.option]
|
||||
allowed_paths : dict option; [@yojson.option]
|
||||
@@ -74,12 +82,17 @@ type t = {
|
||||
}
|
||||
[@@deriving yojson]
|
||||
|
||||
let file ?name ?hash path = File { path; name; hash }
|
||||
let data ?name ?hash data = Data { data; name; hash }
|
||||
let url ?headers ?name ?meth ?hash url = Url { headers; name; meth; hash; url }
|
||||
|
||||
let v ?config ?memory ?allowed_hosts ?allowed_paths ?timeout_ms wasm =
|
||||
let create ?config ?memory ?allowed_hosts ?allowed_paths ?timeout_ms wasm =
|
||||
{ config; wasm; memory; allowed_hosts; allowed_paths; timeout_ms }
|
||||
|
||||
let json t = yojson_of_t t |> Yojson.Safe.to_string
|
||||
let to_json t = yojson_of_t t |> Yojson.Safe.to_string
|
||||
|
||||
let of_json s =
|
||||
let j = Yojson.Safe.from_string s in
|
||||
t_of_yojson j
|
||||
|
||||
let of_file filename =
|
||||
let j = Yojson.Safe.from_file filename in
|
||||
t_of_yojson j
|
||||
|
||||
let with_config t config = { t with config = Some config }
|
||||
|
||||
@@ -1,75 +1,87 @@
|
||||
type memory_options = { max_pages : int option } [@@deriving yojson]
|
||||
(** Memory options *)
|
||||
type memory = { max_pages : int option } [@@deriving yojson]
|
||||
|
||||
(** 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]
|
||||
module Wasm : sig
|
||||
type file = {
|
||||
path : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
[@@deriving yojson]
|
||||
(** WebAssembly file *)
|
||||
|
||||
(** WebAssembly module data *)
|
||||
type wasm_data = {
|
||||
data : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
} [@@deriving yojson]
|
||||
type data = {
|
||||
data : string;
|
||||
name : string option; [@yojson.option]
|
||||
hash : string option; [@yojson.option]
|
||||
}
|
||||
[@@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]
|
||||
type 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]
|
||||
(** 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]
|
||||
(** WebAssembly from a file, module data or URL *)
|
||||
type t = File of file | Data of data | Url of url [@@deriving yojson]
|
||||
|
||||
val file : ?name:string -> ?hash:string -> string -> t
|
||||
(** Create [wasm] from filename *)
|
||||
|
||||
val data : ?name:string -> ?hash:string -> string -> t
|
||||
(** Create [wasm] from WebAssembly module data *)
|
||||
|
||||
val url :
|
||||
?headers:(string * string) list ->
|
||||
?name:string ->
|
||||
?meth:string ->
|
||||
?hash:string ->
|
||||
string ->
|
||||
t
|
||||
(** Create [wasm] from URL *)
|
||||
end
|
||||
|
||||
(** Manifest type *)
|
||||
type t = {
|
||||
wasm : wasm list;
|
||||
memory : memory option;
|
||||
wasm : Wasm.t list;
|
||||
memory : memory_options option;
|
||||
config : config option;
|
||||
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 WebAssembly module data *)
|
||||
val data : ?name:string -> ?hash:string -> string -> wasm
|
||||
|
||||
(** Create [wasm] from URL *)
|
||||
val url :
|
||||
?headers:(string * string) list ->
|
||||
?name:string ->
|
||||
?meth:string ->
|
||||
?hash:string ->
|
||||
string ->
|
||||
wasm
|
||||
|
||||
(** Create new manifest *)
|
||||
val v :
|
||||
val create :
|
||||
?config:config ->
|
||||
?memory:memory ->
|
||||
?memory:memory_options ->
|
||||
?allowed_hosts:string list ->
|
||||
?allowed_paths:dict ->
|
||||
?timeout_ms:int ->
|
||||
wasm list ->
|
||||
Wasm.t list ->
|
||||
t
|
||||
(** Create new manifest *)
|
||||
|
||||
val to_json : t -> string
|
||||
(** Convert manifest to JSON *)
|
||||
val json : t -> string
|
||||
|
||||
(** Updates a manifest config *)
|
||||
val of_json : string -> t
|
||||
(** Read manifest from JSON string *)
|
||||
|
||||
val of_file : string -> t
|
||||
(** Read manifest from JSON file *)
|
||||
|
||||
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,8 @@
|
||||
fn main() {
|
||||
let fn_macro = "
|
||||
#define EXTISM_FUNCTION(N) extern void N(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)
|
||||
#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
|
||||
";
|
||||
if let Ok(bindings) = cbindgen::Builder::new()
|
||||
.with_crate(".")
|
||||
.with_language(cbindgen::Language::C)
|
||||
@@ -6,9 +10,14 @@ fn main() {
|
||||
.with_sys_include("stdint.h")
|
||||
.with_sys_include("stdbool.h")
|
||||
.with_pragma_once(true)
|
||||
.with_after_include(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");
|
||||
|
||||
162
runtime/extism.h
162
runtime/extism.h
@@ -3,35 +3,165 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define EXTISM_FUNCTION(N) extern void N(ExtismCurrentPlugin*, const ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, void*)
|
||||
#define EXTISM_GO_FUNCTION(N) extern void N(ExtismCurrentPlugin*, ExtismVal*, ExtismSize, ExtismVal*, ExtismSize, uintptr_t)
|
||||
|
||||
|
||||
/**
|
||||
* 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 +172,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 +201,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 +210,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 +220,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};
|
||||
@@ -22,7 +22,7 @@ pub(crate) use timer::{Timer, TimerAction};
|
||||
pub type Size = u64;
|
||||
pub type PluginIndex = i32;
|
||||
|
||||
pub(crate) use log::{debug, error, info, trace};
|
||||
pub(crate) use log::{debug, error, trace};
|
||||
|
||||
/// Converts any type implementing `std::fmt::Debug` into a suitable CString to use
|
||||
/// as an error message
|
||||
|
||||
@@ -173,7 +173,7 @@ impl PluginMemory {
|
||||
pages_needed = 1
|
||||
}
|
||||
|
||||
info!("Requesting {pages_needed} more pages");
|
||||
debug!("Requesting {pages_needed} more pages");
|
||||
// This will fail if we've already allocated the maximum amount of memory allowed
|
||||
self.memory.grow(&mut self.store, pages_needed)?;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ impl PluginMemory {
|
||||
length: n,
|
||||
};
|
||||
|
||||
info!(
|
||||
debug!(
|
||||
"Allocated new block: {} bytes at offset {}",
|
||||
mem.length, mem.offset
|
||||
);
|
||||
@@ -202,7 +202,7 @@ impl PluginMemory {
|
||||
|
||||
/// Free the block allocated at `offset`
|
||||
pub fn free(&mut self, offset: usize) {
|
||||
info!("Freeing block at {offset}");
|
||||
debug!("Freeing block at {offset}");
|
||||
if let Some(length) = self.live_blocks.remove(&offset) {
|
||||
self.free.push(MemoryBlock { offset, length });
|
||||
} else {
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
@@ -359,7 +356,7 @@ impl Runtime {
|
||||
&[Val::I32(0), Val::I32(0)],
|
||||
results.as_mut_slice(),
|
||||
)?;
|
||||
info!("Initialized Haskell language runtime");
|
||||
debug!("Initialized Haskell language runtime");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -373,7 +370,7 @@ impl Runtime {
|
||||
let mut results =
|
||||
vec![Val::null(); cleanup.ty(&plugin.memory.store).results().len()];
|
||||
cleanup.call(&mut plugin.memory.store, &[], results.as_mut_slice())?;
|
||||
info!("Cleaned up Haskell language runtime");
|
||||
debug!("Cleaned up Haskell language runtime");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,9 +328,10 @@ 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}");
|
||||
debug!("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