mirror of
https://github.com/extism/extism.git
synced 2026-01-12 07:18:02 -05:00
Compare commits
1 Commits
release-0.
...
test-java-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce67aecf61 |
2
.github/workflows/browser-ci.yml
vendored
2
.github/workflows/browser-ci.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
|
||||
4
.github/workflows/ci-go.yml
vendored
4
.github/workflows/ci-go.yml
vendored
@@ -34,6 +34,6 @@ jobs:
|
||||
- name: Test Go Host SDK
|
||||
run: |
|
||||
go version
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
cd go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go | grep "Hello from Go!"
|
||||
LD_LIBRARY_PATH=/usr/local/lib go run main.go
|
||||
LD_LIBRARY_PATH=/usr/local/lib go test
|
||||
|
||||
9
.github/workflows/ci-java.yml
vendored
9
.github/workflows/ci-java.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
version: [11, 17]
|
||||
version: [8, 11, 17]
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
@@ -33,5 +33,10 @@ jobs:
|
||||
- name: Test Java
|
||||
run: |
|
||||
cd java
|
||||
cat pom.xml | sed 's/<java.version>17/<java.version>${{ matrix.version }}/' > updated.xml
|
||||
mv updated.xml pom.xml
|
||||
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
|
||||
|
||||
3
.github/workflows/ci-zig.yml
vendored
3
.github/workflows/ci-zig.yml
vendored
@@ -18,7 +18,6 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
zig_version: ["master"] # eventually use multiple versions once stable
|
||||
rust:
|
||||
- stable
|
||||
steps:
|
||||
@@ -27,8 +26,6 @@ jobs:
|
||||
- uses: ./.github/actions/extism
|
||||
- name: Setup Zig env
|
||||
uses: goto-bus-stop/setup-zig@v2
|
||||
with:
|
||||
version: ${{ matrix.zig_version }}
|
||||
|
||||
- name: Test Zig Host SDK
|
||||
run: |
|
||||
|
||||
3
.github/workflows/release-haskell.yaml
vendored
3
.github/workflows/release-haskell.yaml
vendored
@@ -1,7 +1,7 @@
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release Haskell SDK
|
||||
name: Release Rust SDK
|
||||
|
||||
jobs:
|
||||
release-sdks:
|
||||
@@ -10,6 +10,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: cachix/haskell-release-action@v1
|
||||
with:
|
||||
- hackage-token: "${{ secrets.HACKAGE_TOKEN }}"
|
||||
|
||||
14
.github/workflows/release-rust.yaml
vendored
14
.github/workflows/release-rust.yaml
vendored
@@ -19,19 +19,15 @@ jobs:
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- name: Release Rust Manifest Crate
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# order of crate publication matter: manifest, runtime, rust
|
||||
cargo publish --manifest-path manifest/Cargo.toml
|
||||
# allow for crates.io to update so dependant crates can locate extism-manifest
|
||||
sleep 5
|
||||
|
||||
- name: Release Rust Host SDK
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
|
||||
run: |
|
||||
# order of crate publication matter: manifest, runtime, rust
|
||||
|
||||
cargo publish --manifest-path manifest/Cargo.toml
|
||||
# allow for crates.io to update so dependant crates can locate extism-manifest
|
||||
sleep 5
|
||||
|
||||
cargo publish --manifest-path runtime/Cargo.toml --no-verify
|
||||
cargo publish --manifest-path rust/Cargo.toml
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -30,15 +30,9 @@ rust/test.log
|
||||
duniverse
|
||||
_build
|
||||
php/Extism.php
|
||||
python/docs
|
||||
dist-newstyle
|
||||
.stack-work
|
||||
vendor
|
||||
zig/zig-*
|
||||
zig/example-out/
|
||||
zig/*.log
|
||||
java/*.iml
|
||||
java/*.log
|
||||
java/.idea
|
||||
java/.DS_Store
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@ members = [
|
||||
"runtime",
|
||||
"rust",
|
||||
"libextism",
|
||||
"elixir/native/extism_nif"
|
||||
]
|
||||
exclude = ["kernel"]
|
||||
|
||||
14
Makefile
14
Makefile
@@ -18,23 +18,21 @@ else
|
||||
FEATURE_FLAGS=--features $(FEATURES)
|
||||
endif
|
||||
|
||||
build:
|
||||
cargo build --release $(FEATURE_FLAGS) --manifest-path libextism/Cargo.toml
|
||||
|
||||
.PHONY: kernel
|
||||
kernel:
|
||||
cd kernel && bash build.sh
|
||||
.PHONY: build
|
||||
|
||||
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:
|
||||
mkdir -p $(DEST)/lib $(DEST)/include
|
||||
install runtime/extism.h $(DEST)/include/extism.h
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib/libextism.$(SOEXT)
|
||||
install runtime/extism.h $(DEST)/include
|
||||
install target/release/libextism.$(SOEXT) $(DEST)/lib
|
||||
|
||||
uninstall:
|
||||
rm -f $(DEST)/include/extism.h $(DEST)/lib/libextism.$(SOEXT)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
### _Welcome!_
|
||||
|
||||
**Please note:** This project still under active development and APIs may change until we hit v1.0.
|
||||
|
||||
If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
|
||||
**Please note:** this project still under active development. It's usable, but expect some rough edges while work is underway. If you're interested in working on or building with Extism, please join our [Discord](https://discord.gg/cx3usBCWnc) and let us know - we are happy to help get you started.
|
||||
|
||||
[](https://discord.gg/cx3usBCWnc)
|
||||
|
||||
@@ -64,4 +62,5 @@ Extism is an open-source product from the team at:
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
_Reach out and tell us what you're building! We'd love to help._
|
||||
|
||||
@@ -103,11 +103,7 @@
|
||||
}
|
||||
|
||||
async loadFunctions(url) {
|
||||
let helloWorld = function(index){
|
||||
console.log("Hello, " + this.allocator.getString(index));
|
||||
return index;
|
||||
};
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] }, {"hello_world": helloWorld});
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": url } ] })
|
||||
let functions = Object.keys(await plugin.getExports())
|
||||
console.log("funcs ", functions)
|
||||
this.setState({functions})
|
||||
@@ -139,13 +135,7 @@
|
||||
|
||||
async handleOnRun(e) {
|
||||
e && e.preventDefault && e.preventDefault();
|
||||
let helloWorld = function(index){
|
||||
console.log("Hello, " + this.allocator.getString(index));
|
||||
return index;
|
||||
};
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] }, {
|
||||
"hello_world": helloWorld
|
||||
});
|
||||
let plugin = await this.extismContext.newPlugin({ "wasm": [ { "path": this.state.url } ] })
|
||||
let result = await plugin.call(this.state.func_name, this.state.input)
|
||||
let output = result
|
||||
this.setState({output})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'jsdom',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
|
||||
5145
browser/package-lock.json
generated
5145
browser/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@extism/runtime-browser",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.2",
|
||||
"description": "Extism runtime in the browser",
|
||||
"scripts": {
|
||||
"build": "node build.js && tsc --emitDeclarationOnly --outDir dist",
|
||||
@@ -23,9 +23,7 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.2",
|
||||
"esbuild": "^0.15.13",
|
||||
"esbuild-jest": "^0.5.0",
|
||||
"jest": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"tslint": "^6.1.3",
|
||||
@@ -34,6 +32,6 @@
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bjorn3/browser_wasi_shim": "^0.2.7"
|
||||
"@bjorn3/browser_wasi_shim": "^0.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Manifest, PluginConfig, ManifestWasmFile, ManifestWasmData } from './manifest';
|
||||
import { ExtismPlugin } from './plugin';
|
||||
import ExtismPlugin from './plugin';
|
||||
|
||||
/**
|
||||
* Can be a {@link Manifest} or just the raw bytes of the WASM module as an ArrayBuffer.
|
||||
@@ -20,7 +20,7 @@ export default class ExtismContext {
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
async newPlugin(manifest: ManifestData, functions: Record<string, any> = {}, config?: PluginConfig) {
|
||||
async newPlugin(manifest: ManifestData, config?: PluginConfig) {
|
||||
let moduleData: ArrayBuffer | null = null;
|
||||
if (manifest instanceof ArrayBuffer) {
|
||||
moduleData = manifest;
|
||||
@@ -40,6 +40,6 @@ export default class ExtismContext {
|
||||
throw Error(`Unsure how to interpret manifest ${manifest}`);
|
||||
}
|
||||
|
||||
return new ExtismPlugin(moduleData, functions, config);
|
||||
return new ExtismPlugin(moduleData, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,16 @@ function parse(bytes: Uint8Array): any {
|
||||
|
||||
describe('', () => {
|
||||
it('can load and call a plugin', async () => {
|
||||
// const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
|
||||
// const ctx = new ExtismContext();
|
||||
// const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
|
||||
// const functions = await plugin.getExports();
|
||||
// expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
|
||||
// let output = await plugin.call('count_vowels', 'this is a test');
|
||||
// expect(parse(output)).toEqual({ count: 4 });
|
||||
// output = await plugin.call('count_vowels', 'this is a test again');
|
||||
// expect(parse(output)).toEqual({ count: 7 });
|
||||
// output = await plugin.call('count_vowels', 'this is a test thrice');
|
||||
// expect(parse(output)).toEqual({ count: 6 });
|
||||
expect(true).toEqual(true);
|
||||
const data = fs.readFileSync(path.join(__dirname, '..', 'data', 'code.wasm'));
|
||||
const ctx = new ExtismContext();
|
||||
const plugin = await ctx.newPlugin({ wasm: [{ data: data }] });
|
||||
const functions = await plugin.getExports();
|
||||
expect(Object.keys(functions).filter((x) => !x.startsWith('__') && x !== 'memory')).toEqual(['count_vowels']);
|
||||
let output = await plugin.call('count_vowels', 'this is a test');
|
||||
expect(parse(output)).toEqual({ count: 4 });
|
||||
output = await plugin.call('count_vowels', 'this is a test again');
|
||||
expect(parse(output)).toEqual({ count: 7 });
|
||||
output = await plugin.call('count_vowels', 'this is a test thrice');
|
||||
expect(parse(output)).toEqual({ count: 6 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import ExtismContext from './context';
|
||||
import { ExtismFunction, ExtismPlugin } from './plugin';
|
||||
|
||||
export { ExtismContext, ExtismFunction, ExtismPlugin };
|
||||
export { ExtismContext };
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Allocator from './allocator';
|
||||
import { PluginConfig } from './manifest';
|
||||
import { WASI, Fd } from '@bjorn3/browser_wasi_shim';
|
||||
//@ts-ignore TODO add types to this library
|
||||
import { WASI, File } from "@bjorn3/browser_wasi_shim";
|
||||
|
||||
export type ExtismFunction = any;
|
||||
|
||||
export class ExtismPlugin {
|
||||
export default class ExtismPlugin {
|
||||
moduleData: ArrayBuffer;
|
||||
allocator: Allocator;
|
||||
config?: PluginConfig;
|
||||
@@ -12,16 +11,14 @@ export class ExtismPlugin {
|
||||
input: Uint8Array;
|
||||
output: Uint8Array;
|
||||
module?: WebAssembly.WebAssemblyInstantiatedSource;
|
||||
functions: Record<string, ExtismFunction>;
|
||||
|
||||
constructor(moduleData: ArrayBuffer, functions: Record<string, ExtismFunction> = {}, config?: PluginConfig) {
|
||||
constructor(moduleData: ArrayBuffer, config?: PluginConfig) {
|
||||
this.moduleData = moduleData;
|
||||
this.allocator = new Allocator(1024 * 1024);
|
||||
this.config = config;
|
||||
this.vars = {};
|
||||
this.input = new Uint8Array();
|
||||
this.output = new Uint8Array();
|
||||
this.functions = functions;
|
||||
}
|
||||
|
||||
async getExports(): Promise<WebAssembly.Exports> {
|
||||
@@ -68,31 +65,23 @@ export class ExtismPlugin {
|
||||
const environment = this.makeEnv();
|
||||
const args: Array<string> = [];
|
||||
const envVars: Array<string> = [];
|
||||
let fds: Fd[] = [
|
||||
// new XtermStdio(term), // stdin
|
||||
// new XtermStdio(term), // stdout
|
||||
// new XtermStdio(term), // stderr
|
||||
let fds = [
|
||||
new File([]), // stdin
|
||||
new File([]), // stdout
|
||||
new File([]), // stderr
|
||||
];
|
||||
let wasi = new WASI(args, envVars, fds);
|
||||
let env = {
|
||||
wasi_snapshot_preview1: wasi.wasiImport,
|
||||
env: environment,
|
||||
env: environment
|
||||
};
|
||||
this.module = await WebAssembly.instantiate(this.moduleData, env);
|
||||
// normally we would call wasi.start here but it doesn't respect when there is
|
||||
// no _start function
|
||||
//@ts-ignore
|
||||
wasi.inst = this.module.instance;
|
||||
if (this.module.instance.exports._start) {
|
||||
//@ts-ignore
|
||||
this.module.instance.exports._start();
|
||||
}
|
||||
return this.module;
|
||||
}
|
||||
|
||||
makeEnv(): any {
|
||||
const plugin = this;
|
||||
var env: any = {
|
||||
return {
|
||||
extism_alloc(n: bigint): bigint {
|
||||
return plugin.allocator.alloc(n);
|
||||
},
|
||||
@@ -191,13 +180,5 @@ export class ExtismPlugin {
|
||||
console.error(s);
|
||||
},
|
||||
};
|
||||
|
||||
for (const [name, func] of Object.entries(this.functions)) {
|
||||
env[name] = function () {
|
||||
return func.apply(plugin, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
build:
|
||||
clang -g -o main main.c -lextism -L .
|
||||
clang -o main main.c -lextism -L .
|
||||
27
c/main.c
27
c/main.c
@@ -9,21 +9,6 @@
|
||||
#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");
|
||||
@@ -56,18 +41,13 @@ int main(int argc, char *argv[]) {
|
||||
ExtismContext *ctx = extism_context_new();
|
||||
|
||||
size_t len = 0;
|
||||
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);
|
||||
uint8_t *data = read_file("../wasm/code.wasm", &len);
|
||||
ExtismPlugin plugin = extism_plugin_new(ctx, data, len, false);
|
||||
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);
|
||||
@@ -76,7 +56,6 @@ 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,16 +1,15 @@
|
||||
FLAGS=`pkg-config --cflags --libs jsoncpp gtest` -lextism -lpthread
|
||||
|
||||
build-example:
|
||||
$(CXX) -std=c++14 -o example -I. example.cpp $(FLAGS)
|
||||
$(CXX) -std=c++11 -o example -I. example.cpp $(FLAGS)
|
||||
|
||||
.PHONY: example
|
||||
example: build-example
|
||||
./example
|
||||
|
||||
build-test:
|
||||
$(CXX) -std=c++14 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
$(CXX) -std=c++11 -o test/test -I. test/test.cpp $(FLAGS)
|
||||
|
||||
.PHONY: test
|
||||
test: build-test
|
||||
cd test && ./test
|
||||
|
||||
|
||||
@@ -14,26 +14,10 @@ std::vector<uint8_t> read(const char *filename) {
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
auto wasm = read("../wasm/code-functions.wasm");
|
||||
std::string tmp = "Testing";
|
||||
auto wasm = read("../wasm/code.wasm");
|
||||
Context context = Context();
|
||||
|
||||
// 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(wasm, true, functions);
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
const char *input = argc > 1 ? argv[1] : "this is a test";
|
||||
ExtismSize length = strlen(input);
|
||||
|
||||
350
cpp/extism.hpp
350
cpp/extism.hpp
@@ -1,10 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -23,62 +20,27 @@ extern "C" {
|
||||
namespace extism {
|
||||
|
||||
typedef std::map<std::string, std::string> Config;
|
||||
|
||||
template <typename T> class ManifestKey {
|
||||
bool is_set = false;
|
||||
|
||||
public:
|
||||
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
|
||||
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;
|
||||
}
|
||||
std::string path;
|
||||
std::string url;
|
||||
// TODO: add base64 encoded raw data
|
||||
std::string hash;
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
Json::Value json() const {
|
||||
Json::Value doc;
|
||||
|
||||
if (!this->_path.empty()) {
|
||||
doc["path"] = this->_path;
|
||||
} else if (!this->_url.empty()) {
|
||||
doc["url"] = this->_url;
|
||||
if (!this->path.empty()) {
|
||||
doc["path"] = this->path;
|
||||
}
|
||||
|
||||
if (!this->_hash.empty()) {
|
||||
doc["hash"] = this->_hash.value;
|
||||
if (!this->url.empty()) {
|
||||
doc["url"] = this->url;
|
||||
}
|
||||
|
||||
if (!this->hash.empty()) {
|
||||
doc["hash"] = this->hash;
|
||||
}
|
||||
|
||||
return doc;
|
||||
@@ -90,23 +52,18 @@ class Manifest {
|
||||
public:
|
||||
Config config;
|
||||
std::vector<Wasm> wasm;
|
||||
ManifestKey<std::vector<std::string>> allowed_hosts;
|
||||
ManifestKey<std::map<std::string, std::string>> allowed_paths;
|
||||
ManifestKey<uint64_t> timeout_ms;
|
||||
std::vector<std::string> allowed_hosts;
|
||||
std::map<std::string, std::string> allowed_paths;
|
||||
uint64_t timeout_ms;
|
||||
|
||||
// Empty manifest
|
||||
Manifest()
|
||||
: timeout_ms(0, false), allowed_hosts(std::vector<std::string>(), false),
|
||||
allowed_paths(std::map<std::string, std::string>(), false) {}
|
||||
Manifest() : timeout_ms(30000) {}
|
||||
|
||||
// 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);
|
||||
@@ -135,7 +92,7 @@ public:
|
||||
if (!this->allowed_hosts.empty()) {
|
||||
Json::Value h;
|
||||
|
||||
for (auto s : this->allowed_hosts.value) {
|
||||
for (auto s : this->allowed_hosts) {
|
||||
h.append(s);
|
||||
}
|
||||
doc["allowed_hosts"] = h;
|
||||
@@ -143,63 +100,54 @@ public:
|
||||
|
||||
if (!this->allowed_paths.empty()) {
|
||||
Json::Value h;
|
||||
for (auto k : this->allowed_paths.value) {
|
||||
for (auto k : this->allowed_paths) {
|
||||
h[k.first] = k.second;
|
||||
}
|
||||
doc["allowed_paths"] = h;
|
||||
}
|
||||
|
||||
if (!this->timeout_ms.empty()) {
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms.value);
|
||||
}
|
||||
doc["timeout_ms"] = Json::Value(this->timeout_ms);
|
||||
|
||||
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 = Wasm::path(s, hash);
|
||||
Wasm w;
|
||||
w.path = s;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
// Add Wasm from URL
|
||||
void add_wasm_url(std::string u, std::string hash = std::string()) {
|
||||
Wasm w = Wasm::url(u, hash);
|
||||
Wasm w;
|
||||
w.url = u;
|
||||
w.hash = hash;
|
||||
this->wasm.push_back(w);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
void allow_host(std::string host) { this->allowed_hosts.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.value[src] = dest;
|
||||
this->allowed_paths[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::runtime_error {
|
||||
class Error : public std::exception {
|
||||
private:
|
||||
std::string message;
|
||||
|
||||
public:
|
||||
Error(std::string msg) : std::runtime_error(msg) {}
|
||||
Error(std::string msg) : message(msg) {}
|
||||
const char *what() { return message.c_str(); }
|
||||
};
|
||||
|
||||
class Buffer {
|
||||
@@ -218,141 +166,14 @@ 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);
|
||||
}
|
||||
|
||||
void returnString(Val &output, const std::string &s) {
|
||||
this->returnBytes(output, (const uint8_t *)s.c_str(), s.size());
|
||||
}
|
||||
|
||||
void returnBytes(Val &output, const uint8_t *bytes, size_t len) {
|
||||
auto offs = this->alloc(len);
|
||||
memcpy(this->memory() + offs, bytes, len);
|
||||
output.v.i64 = offs;
|
||||
}
|
||||
|
||||
uint8_t *inputBytes(Val &inp, size_t *length = nullptr) {
|
||||
if (inp.t != ValType::I64) {
|
||||
return nullptr;
|
||||
}
|
||||
if (length != nullptr) {
|
||||
*length = this->memory_length(inp.v.i64);
|
||||
}
|
||||
return this->memory() + inp.v.i64;
|
||||
}
|
||||
|
||||
std::string inputString(Val &inp) {
|
||||
size_t length = 0;
|
||||
char *buf = (char *)this->inputBytes(inp, &length);
|
||||
return std::string(buf, length);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void set_namespace(std::string s) {
|
||||
extism_function_set_namespace(this->func.get(), s.c_str());
|
||||
}
|
||||
|
||||
Function(const Function &f) { this->func = f.func; }
|
||||
|
||||
ExtismFunction *get() { return this->func.get(); }
|
||||
};
|
||||
|
||||
class CancelHandle {
|
||||
const ExtismCancelHandle *handle;
|
||||
|
||||
public:
|
||||
CancelHandle(const ExtismCancelHandle *x) : handle(x){};
|
||||
bool cancel() { return extism_plugin_cancel(this->handle); }
|
||||
};
|
||||
|
||||
class Plugin {
|
||||
std::shared_ptr<ExtismContext> context;
|
||||
ExtismPlugin plugin;
|
||||
std::vector<Function> functions;
|
||||
|
||||
public:
|
||||
// Create a new plugin
|
||||
Plugin(const uint8_t *wasm, ExtismSize length, bool with_wasi = false,
|
||||
std::vector<Function> functions = std::vector<Function>(),
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: 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);
|
||||
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);
|
||||
if (this->plugin < 0) {
|
||||
const char *err = extism_error(ctx.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to load plugin" : err);
|
||||
@@ -360,39 +181,12 @@ public:
|
||||
this->context = ctx;
|
||||
}
|
||||
|
||||
Plugin(const std::string &str, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi, functions,
|
||||
ctx) {}
|
||||
|
||||
Plugin(const std::vector<uint8_t> &data, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free))
|
||||
: Plugin(data.data(), data.size(), with_wasi, functions, ctx) {}
|
||||
|
||||
CancelHandle cancel_handle() {
|
||||
return CancelHandle(
|
||||
extism_plugin_cancel_handle(this->context.get(), this->id()));
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
// Create a new plugin from Manifest
|
||||
Plugin(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {},
|
||||
std::shared_ptr<ExtismContext> ctx = std::shared_ptr<ExtismContext>(
|
||||
extism_context_new(), extism_context_free)) {
|
||||
std::vector<const ExtismFunction *> ptrs;
|
||||
for (auto i : this->functions) {
|
||||
ptrs.push_back(i.get());
|
||||
}
|
||||
|
||||
Plugin(std::shared_ptr<ExtismContext> ctx, const Manifest &manifest,
|
||||
bool with_wasi = false) {
|
||||
auto buffer = manifest.json();
|
||||
this->plugin =
|
||||
extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
this->plugin = extism_plugin_new(ctx.get(), (const uint8_t *)buffer.c_str(),
|
||||
buffer.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);
|
||||
@@ -410,15 +204,9 @@ public:
|
||||
|
||||
ExtismContext *get_context() const { return this->context.get(); }
|
||||
|
||||
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());
|
||||
}
|
||||
void update(const uint8_t *wasm, size_t length, bool with_wasi = false) {
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin, wasm,
|
||||
length, ptrs.data(), ptrs.size(), with_wasi);
|
||||
length, with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
@@ -426,17 +214,11 @@ public:
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
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());
|
||||
}
|
||||
void update(const Manifest &manifest, bool with_wasi = false) {
|
||||
auto buffer = manifest.json();
|
||||
bool b = extism_plugin_update(
|
||||
this->context.get(), this->plugin, (const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), ptrs.data(), ptrs.size(), with_wasi);
|
||||
bool b = extism_plugin_update(this->context.get(), this->plugin,
|
||||
(const uint8_t *)buffer.c_str(),
|
||||
buffer.size(), with_wasi);
|
||||
if (!b) {
|
||||
const char *err = extism_error(this->context.get(), -1);
|
||||
throw Error(err == nullptr ? "Unable to update plugin" : err);
|
||||
@@ -469,7 +251,6 @@ 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,
|
||||
@@ -490,19 +271,15 @@ 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());
|
||||
}
|
||||
|
||||
// Call a plugin function with string input
|
||||
Buffer call(const std::string &func,
|
||||
const std::string &input = std::string()) const {
|
||||
Buffer call(const std::string &func, const std::string &input) 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());
|
||||
@@ -513,49 +290,38 @@ 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);
|
||||
}
|
||||
|
||||
// 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(wasm, length, with_wasi, functions, this->pointer);
|
||||
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 std::string
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin((const uint8_t *)str.c_str(), str.size(), with_wasi,
|
||||
functions, this->pointer);
|
||||
Plugin plugin(const std::string &str, bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, (const uint8_t *)str.c_str(), str.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(data.data(), data.size(), with_wasi, functions,
|
||||
this->pointer);
|
||||
Plugin plugin(const std::vector<uint8_t> &data,
|
||||
bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, data.data(), data.size(), with_wasi);
|
||||
}
|
||||
|
||||
#ifndef EXTISM_NO_JSON
|
||||
// Create plugin from Manifest
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false,
|
||||
std::vector<Function> functions = {}) const {
|
||||
return Plugin(manifest, with_wasi, functions, this->pointer);
|
||||
Plugin plugin(const Manifest &manifest, bool with_wasi = false) const {
|
||||
return Plugin(this->pointer, manifest, with_wasi);
|
||||
}
|
||||
#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
|
||||
|
||||
@@ -10,8 +10,6 @@ std::vector<uint8_t> read(const char *filename) {
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
const std::string code = "../../wasm/code.wasm";
|
||||
|
||||
namespace {
|
||||
using namespace extism;
|
||||
|
||||
@@ -21,24 +19,26 @@ TEST(Context, Basic) {
|
||||
}
|
||||
|
||||
TEST(Plugin, Manifest) {
|
||||
Manifest manifest = Manifest::path(code);
|
||||
Context context;
|
||||
Manifest manifest = Manifest::path("code.wasm");
|
||||
manifest.set_config("a", "1");
|
||||
|
||||
ASSERT_NO_THROW(Plugin plugin(manifest));
|
||||
Plugin plugin(manifest);
|
||||
ASSERT_NO_THROW(Plugin plugin = context.plugin(manifest));
|
||||
Plugin plugin = context.plugin(manifest);
|
||||
|
||||
Buffer buf = plugin.call("count_vowels", "this is a test");
|
||||
ASSERT_EQ((std::string)buf, "{\"count\": 4}");
|
||||
}
|
||||
|
||||
TEST(Plugin, BadManifest) {
|
||||
Context context;
|
||||
Manifest manifest;
|
||||
ASSERT_THROW(Plugin plugin(manifest), Error);
|
||||
ASSERT_THROW(Plugin plugin = context.plugin(manifest), Error);
|
||||
}
|
||||
|
||||
TEST(Plugin, Bytes) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
auto wasm = read("code.wasm");
|
||||
ASSERT_NO_THROW(Plugin plugin = context.plugin(wasm));
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
@@ -48,7 +48,7 @@ TEST(Plugin, Bytes) {
|
||||
|
||||
TEST(Plugin, UpdateConfig) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
Config config;
|
||||
@@ -58,33 +58,13 @@ TEST(Plugin, UpdateConfig) {
|
||||
|
||||
TEST(Plugin, FunctionExists) {
|
||||
Context context;
|
||||
auto wasm = read(code.c_str());
|
||||
auto wasm = read("code.wasm");
|
||||
Plugin plugin = context.plugin(wasm);
|
||||
|
||||
ASSERT_FALSE(plugin.function_exists("bad_function"));
|
||||
ASSERT_TRUE(plugin.function_exists("count_vowels"));
|
||||
}
|
||||
|
||||
TEST(Plugin, HostFunction) {
|
||||
auto wasm = read("../../wasm/code-functions.wasm");
|
||||
auto t = std::vector<ValType>{ValType::I64};
|
||||
Function hello_world =
|
||||
Function("hello_world", t, t,
|
||||
[](CurrentPlugin plugin, const std::vector<Val> ¶ms,
|
||||
std::vector<Val> &results, void *user_data) {
|
||||
auto offs = plugin.alloc(4);
|
||||
memcpy(plugin.memory() + offs, "test", 4);
|
||||
results[0].v.i64 = (int64_t)offs;
|
||||
});
|
||||
auto functions = std::vector<Function>{
|
||||
hello_world,
|
||||
};
|
||||
Plugin plugin(wasm, true, functions);
|
||||
auto buf = plugin.call("count_vowels", "aaa");
|
||||
ASSERT_EQ(buf.length, 4);
|
||||
ASSERT_EQ((std::string)buf, "test");
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<NoBuild>true</NoBuild>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<NoBuild>true</NoBuild>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.runtime.win-x64</PackageId>
|
||||
<Version>0.7.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Internal implementation package for Extism to work on Windows x64</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.runtime.win-x64</PackageId>
|
||||
<Version>0.2.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Internal implementation package for Extism to work on Windows x64</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
<PackageLicenseExpression>BSD-3-Clause</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes/win-x64.dll"
|
||||
CopyToOutputDirectory="Always"
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="runtimes/win-x64.dll"
|
||||
CopyToOutputDirectory="Always"
|
||||
Pack="true"
|
||||
PackagePath="runtimes\win-x64\native\extism.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,40 +1,11 @@
|
||||
using Extism.Sdk;
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
var context = new Context();
|
||||
|
||||
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
|
||||
|
||||
using var helloWorld = new HostFunction(
|
||||
"hello_world",
|
||||
"env",
|
||||
new[] { ExtismValType.I64 },
|
||||
new[] { ExtismValType.I64 },
|
||||
userData,
|
||||
HelloWorld);
|
||||
|
||||
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
|
||||
{
|
||||
Console.WriteLine("Hello from .NET!");
|
||||
|
||||
var text = Marshal.PtrToStringAnsi(data);
|
||||
Console.WriteLine(text);
|
||||
|
||||
var input = plugin.ReadString(new nint(inputs[0].v.i64));
|
||||
Console.WriteLine($"Input: {input}");
|
||||
|
||||
outputs[0].v.i64 = plugin.WriteString(input);
|
||||
}
|
||||
|
||||
var wasm = File.ReadAllBytes("./code-functions.wasm");
|
||||
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
|
||||
var wasm = await File.ReadAllBytesAsync("./code.wasm");
|
||||
using var plugin = context.CreatePlugin(wasm, withWasi: true);
|
||||
|
||||
var output = Encoding.UTF8.GetString(
|
||||
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))
|
||||
);
|
||||
|
||||
Console.WriteLine($"Output: {output}");
|
||||
|
||||
Console.WriteLine(output); // prints {"count": 3}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -7,10 +6,8 @@ namespace Extism.Sdk.Native;
|
||||
/// <summary>
|
||||
/// Represents an Extism context through which you can load <see cref="Plugin"/>s.
|
||||
/// </summary>
|
||||
public unsafe class Context : IDisposable
|
||||
public class Context : IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<int, Plugin> _plugins = new ConcurrentDictionary<int, Plugin>();
|
||||
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private int _disposed;
|
||||
@@ -20,63 +17,33 @@ public unsafe class Context : IDisposable
|
||||
/// </summary>
|
||||
public Context()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
NativeHandle = LibExtism.extism_context_new();
|
||||
}
|
||||
NativeHandle = LibExtism.extism_context_new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native pointer to the Extism Context.
|
||||
/// </summary>
|
||||
internal LibExtism.ExtismContext* NativeHandle { get; }
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads an Extism <see cref="Plugin"/>.
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
|
||||
public Plugin CreatePlugin(ReadOnlySpan<byte> wasm, bool withWasi)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
fixed (IntPtr* functionsPtr = functionHandles)
|
||||
{
|
||||
var index = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi);
|
||||
if (index == -1)
|
||||
{
|
||||
var errorMsg = GetError();
|
||||
if (errorMsg != null)
|
||||
{
|
||||
throw new ExtismException(errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ExtismException("Failed to create plugin.");
|
||||
}
|
||||
}
|
||||
|
||||
return _plugins[index] = new Plugin(this, functions, index);
|
||||
var plugin = LibExtism.extism_plugin_new(NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
return new Plugin(this, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a plugin by index.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of plugin.</param>
|
||||
/// <returns></returns>
|
||||
public Plugin GetPlugin(int index)
|
||||
{
|
||||
return _plugins[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from this <see cref="Context"/>'s registry.
|
||||
/// </summary>
|
||||
@@ -143,11 +110,6 @@ public unsafe class Context : IDisposable
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
foreach (var plugin in _plugins.Values)
|
||||
{
|
||||
plugin.Dispose();
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_context_free(NativeHandle);
|
||||
}
|
||||
@@ -189,3 +151,34 @@ public unsafe class Context : IDisposable
|
||||
return LibExtism.extism_log_file(logPath, logLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
Trace
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the current plugin. Can only be used within <see cref="HostFunction"/>s.
|
||||
/// </summary>
|
||||
public class CurrentPlugin
|
||||
{
|
||||
internal CurrentPlugin(nint nativeHandle)
|
||||
{
|
||||
NativeHandle = nativeHandle;
|
||||
}
|
||||
|
||||
internal nint NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the memory of the currently running plugin.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public nint GetMemory()
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string from a memory block using UTF8.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public string ReadString(nint pointer)
|
||||
{
|
||||
return ReadString(pointer, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string form a memory block.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <param name="encoding"></param>
|
||||
/// <returns></returns>
|
||||
public string ReadString(nint pointer, Encoding encoding)
|
||||
{
|
||||
var buffer = ReadBytes(pointer);
|
||||
|
||||
return encoding.GetString(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a span of bytes for a given block.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public unsafe Span<byte> ReadBytes(nint pointer)
|
||||
{
|
||||
var mem = GetMemory();
|
||||
var length = (int)BlockLength(pointer);
|
||||
var ptr = (byte*)mem + pointer;
|
||||
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string into the current plugin memory using UTF-8 encoding and returns the pointer of the block.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public nint WriteString(string value)
|
||||
=> WriteString(value, Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a string into the current plugin memory and returns the pointer of the block.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoding"></param>
|
||||
public nint WriteString(string value, Encoding encoding)
|
||||
{
|
||||
var bytes = encoding.GetBytes(value);
|
||||
var pointer = AllocateBlock(bytes.Length);
|
||||
WriteBytes(pointer, bytes);
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte array into a block of memory.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public unsafe void WriteBytes(nint pointer, Span<byte> bytes)
|
||||
{
|
||||
var length = BlockLength(pointer);
|
||||
if (length < bytes.Length)
|
||||
{
|
||||
throw new InvalidOperationException("Destination block length is less than source block length.");
|
||||
}
|
||||
|
||||
var mem = GetMemory();
|
||||
var ptr = (void*)(mem + pointer);
|
||||
var destination = new Span<byte>(ptr, bytes.Length);
|
||||
|
||||
bytes.CopyTo(destination);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees a block of memory belonging to the current plugin.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
public void FreeBlock(nint pointer)
|
||||
{
|
||||
LibExtism.extism_current_plugin_memory_free(NativeHandle, pointer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a memory block in the currently running plugin.
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
public nint AllocateBlock(long length)
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory_alloc(NativeHandle, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="pointer"></param>
|
||||
/// <returns></returns>
|
||||
public long BlockLength(nint pointer)
|
||||
{
|
||||
return LibExtism.extism_current_plugin_memory_length(NativeHandle, pointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ public class ExtismException : Exception
|
||||
/// with a specified error message and a reference to the inner exception
|
||||
/// that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="message">The message that describes the error .</param>
|
||||
/// <param name="innerException">
|
||||
/// The exception that is the cause of the current exception, or a null reference
|
||||
/// (Nothing in Visual Basic) if no inner exception is specified.
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Extism.Sdk</PackageId>
|
||||
<Version>0.6.0</Version>
|
||||
<Version>0.2.0</Version>
|
||||
<Authors>Extism Contributors</Authors>
|
||||
<Description>Extism SDK that allows hosting Extism plugins in .NET apps.</Description>
|
||||
<Tags>extism, wasm, plugin</Tags>
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Extism.Sdk
|
||||
{
|
||||
/// <summary>
|
||||
/// A host function signature.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Plugin Index</param>
|
||||
/// <param name="inputs">Input parameters</param>
|
||||
/// <param name="outputs">Output parameters, the host function can change this.</param>
|
||||
/// <param name="userData">A data passed in during Host Function creation.</param>
|
||||
public delegate void ExtismFunction(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, IntPtr userData);
|
||||
|
||||
/// <summary>
|
||||
/// A function provided by the host that plugins can call.
|
||||
/// </summary>
|
||||
public class HostFunction : IDisposable
|
||||
{
|
||||
private const int DisposedMarker = 1;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Registers a Host Function.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
|
||||
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
|
||||
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
|
||||
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
|
||||
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
|
||||
/// <param name="hostFunction"></param>
|
||||
public HostFunction(
|
||||
string functionName,
|
||||
Span<ExtismValType> inputTypes,
|
||||
Span<ExtismValType> outputTypes,
|
||||
IntPtr userData,
|
||||
ExtismFunction hostFunction) :
|
||||
this(functionName, "", inputTypes, outputTypes, userData, hostFunction)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a Host Function.
|
||||
/// </summary>
|
||||
/// <param name="functionName">The literal name of the function, how it would be called from a <see cref="Plugin"/>.</param>
|
||||
/// <param name="namespace">Function namespace.</param>
|
||||
/// <param name="inputTypes">The types of the input arguments/parameters the <see cref="Plugin"/> caller will provide.</param>
|
||||
/// <param name="outputTypes">The types of the output returned from the host function to the <see cref="Plugin"/>.</param>
|
||||
/// <param name="userData">An opaque pointer to an object from the host, accessible to the <see cref="Plugin"/>.
|
||||
/// NOTE: it is the shared responsibility of the host and <see cref="Plugin"/> to cast/dereference this value properly.</param>
|
||||
/// <param name="hostFunction"></param>
|
||||
unsafe public HostFunction(
|
||||
string functionName,
|
||||
string @namespace,
|
||||
Span<ExtismValType> inputTypes,
|
||||
Span<ExtismValType> outputTypes,
|
||||
IntPtr userData,
|
||||
ExtismFunction hostFunction)
|
||||
{
|
||||
fixed (ExtismValType* inputs = inputTypes)
|
||||
fixed (ExtismValType* outputs = outputTypes)
|
||||
{
|
||||
NativeHandle = LibExtism.extism_function_new(functionName, inputs, inputTypes.Length, outputs, outputTypes.Length, CallbackImpl, userData, IntPtr.Zero);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(@namespace))
|
||||
{
|
||||
LibExtism.extism_function_set_namespace(NativeHandle, @namespace);
|
||||
}
|
||||
|
||||
void CallbackImpl(
|
||||
nint plugin,
|
||||
ExtismVal* inputsPtr,
|
||||
uint n_inputs,
|
||||
ExtismVal* outputsPtr,
|
||||
uint n_outputs,
|
||||
IntPtr data)
|
||||
{
|
||||
var outputs = new Span<ExtismVal>(outputsPtr, (int)n_outputs);
|
||||
var inputs = new Span<ExtismVal>(inputsPtr, (int)n_inputs);
|
||||
|
||||
hostFunction(new CurrentPlugin(plugin), inputs, outputs, data);
|
||||
}
|
||||
}
|
||||
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Host Function.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _disposed, DisposedMarker) == DisposedMarker)
|
||||
{
|
||||
// Already disposed.
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throw an appropriate exception if the Host Function has been disposed.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
protected void CheckNotDisposed()
|
||||
{
|
||||
Interlocked.MemoryBarrier();
|
||||
if (_disposed == DisposedMarker)
|
||||
{
|
||||
ThrowDisposedException();
|
||||
}
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowDisposedException()
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(HostFunction));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Host Function.
|
||||
/// </summary>
|
||||
unsafe protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Free up any managed resources here
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_function_free(NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destructs the current Host Function and frees all resources used by it.
|
||||
/// </summary>
|
||||
~HostFunction()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,197 +2,24 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// A union type for host function argument/return values.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ExtismValUnion
|
||||
{
|
||||
/// <summary>
|
||||
/// Set this for 32 bit integers
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public int i32;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 64 bit integers
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public long i64;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 32 bit floats
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public float f32;
|
||||
|
||||
/// <summary>
|
||||
/// Set this for 64 bit floats
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public double f64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents Wasm data types that Extism can understand
|
||||
/// </summary>
|
||||
public enum ExtismValType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Signed 32 bit integer. Equivalent of <see cref="int"/> or <see cref="uint"/>
|
||||
/// </summary>
|
||||
I32,
|
||||
|
||||
/// <summary>
|
||||
/// Signed 64 bit integer. Equivalent of <see cref="long"/> or <see cref="ulong"/>
|
||||
/// </summary>
|
||||
I64,
|
||||
|
||||
/// <summary>
|
||||
/// Floating point 32 bit integer. Equivalent of <see cref="float"/>
|
||||
/// </summary>
|
||||
F32,
|
||||
|
||||
/// <summary>
|
||||
/// Floating point 64 bit integer. Equivalent of <see cref="double"/>
|
||||
/// </summary>
|
||||
F64,
|
||||
|
||||
/// <summary>
|
||||
/// A 128 bit number.
|
||||
/// </summary>
|
||||
V128,
|
||||
|
||||
/// <summary>
|
||||
/// A reference to opaque data in the Wasm instance.
|
||||
/// </summary>
|
||||
FuncRef,
|
||||
|
||||
/// <summary>
|
||||
/// A reference to opaque data in the Wasm instance.
|
||||
/// </summary>
|
||||
ExternRef
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// `ExtismVal` holds the type and value of a function argument/return
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ExtismVal
|
||||
{
|
||||
/// <summary>
|
||||
/// The type for the argument
|
||||
/// </summary>
|
||||
public ExtismValType t;
|
||||
|
||||
/// <summary>
|
||||
/// The value for the argument
|
||||
/// </summary>
|
||||
public ExtismValUnion v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Functions exposed by the native Extism library.
|
||||
/// </summary>
|
||||
internal static class LibExtism
|
||||
{
|
||||
/// <summary>
|
||||
/// A `Context` is used to store and manage plugins.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct ExtismContext { }
|
||||
|
||||
/// <summary>
|
||||
/// Host function signature
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="inputs"></param>
|
||||
/// <param name="n_inputs"></param>
|
||||
/// <param name="outputs"></param>
|
||||
/// <param name="n_outputs"></param>
|
||||
/// <param name="data"></param>
|
||||
unsafe internal delegate void InternalExtismFunction(nint plugin, ExtismVal* inputs, uint n_inputs, ExtismVal* outputs, uint n_outputs, IntPtr data);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pointer to the memory of the currently running plugin.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory")]
|
||||
internal static extern IntPtr extism_current_plugin_memory(nint plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Allocate a memory block in the currently running plugin
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_alloc")]
|
||||
internal static extern IntPtr extism_current_plugin_memory_alloc(nint plugin, long n);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="n"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_length")]
|
||||
internal static extern long extism_current_plugin_memory_length(nint plugin, long n);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of an allocated block.
|
||||
/// NOTE: this should only be called from host functions.
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
/// <param name="ptr"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_current_plugin_memory_free")]
|
||||
internal static extern void extism_current_plugin_memory_free(nint plugin, IntPtr ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new host function.
|
||||
/// </summary>
|
||||
/// <param name="name">function name, this should be valid UTF-8</param>
|
||||
/// <param name="inputs">argument types</param>
|
||||
/// <param name="nInputs">number of argument types</param>
|
||||
/// <param name="outputs">return types</param>
|
||||
/// <param name="nOutputs">number of return types</param>
|
||||
/// <param name="func">the function to call</param>
|
||||
/// <param name="userData">a pointer that will be passed to the function when it's called this value should live as long as the function exists</param>
|
||||
/// <param name="freeUserData">a callback to release the `user_data` value when the resulting `ExtismFunction` is freed.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_function_new")]
|
||||
unsafe internal static extern IntPtr extism_function_new(string name, ExtismValType* inputs, long nInputs, ExtismValType* outputs, long nOutputs, InternalExtismFunction func, IntPtr userData, IntPtr freeUserData);
|
||||
|
||||
/// <summary>
|
||||
/// Set the namespace of an <see cref="ExtismFunction"/>
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
/// <param name="namespace"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_function_set_namespace")]
|
||||
internal static extern void extism_function_set_namespace(IntPtr ptr, string @namespace);
|
||||
|
||||
/// <summary>
|
||||
/// Free an <see cref="ExtismFunction"/>
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
[DllImport("extism", EntryPoint = "extism_function_free")]
|
||||
internal static extern void extism_function_free(IntPtr ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new context.
|
||||
/// </summary>
|
||||
/// <returns>A pointer to the newly created context.</returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern ExtismContext* extism_context_new();
|
||||
public static extern IntPtr extism_context_new();
|
||||
|
||||
/// <summary>
|
||||
/// Remove a context from the registry and free associated memory.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_context_free(ExtismContext* context);
|
||||
public static extern void extism_context_free(IntPtr context);
|
||||
|
||||
/// <summary>
|
||||
/// Load a WASM plugin.
|
||||
@@ -200,12 +27,10 @@ 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 internal static extern int extism_plugin_new(ExtismContext* context, byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi);
|
||||
unsafe public static extern IntPtr extism_plugin_new(IntPtr context, byte* wasm, int wasmSize, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
@@ -215,13 +40,11 @@ internal static class LibExtism
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <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="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="wasmLength">The length of the `wasm` parameter.</param>
|
||||
/// <param name="withWasi">Enables/disables WASI.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_update(ExtismContext* context, int plugin, byte* wasm, long wasmSize, Span<IntPtr> functions, long nFunctions, bool withWasi);
|
||||
unsafe public static extern bool extism_plugin_update(IntPtr context, IntPtr plugin, byte* wasm, int wasmLength, bool withWasi);
|
||||
|
||||
/// <summary>
|
||||
/// Remove a plugin from the registry and free associated memory.
|
||||
@@ -229,14 +52,14 @@ internal static class LibExtism
|
||||
/// <param name="context">Pointer to the context the plugin is associated with.</param>
|
||||
/// <param name="plugin">Pointer to the plugin you want to free.</param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_plugin_free(ExtismContext* context, int plugin);
|
||||
public static extern void extism_plugin_free(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Remove all plugins from the registry.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern void extism_context_reset(ExtismContext* context);
|
||||
public static extern void extism_context_reset(IntPtr context);
|
||||
|
||||
/// <summary>
|
||||
/// Update plugin config values, this will merge with the existing values.
|
||||
@@ -247,7 +70,7 @@ internal static class LibExtism
|
||||
/// <param name="jsonLength">The length of the `json` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_config(ExtismContext* context, int plugin, byte* json, int jsonLength);
|
||||
unsafe public static extern bool extism_plugin_config(IntPtr context, IntPtr plugin, byte* json, int jsonLength);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if funcName exists.
|
||||
@@ -257,7 +80,7 @@ internal static class LibExtism
|
||||
/// <param name="funcName"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern bool extism_plugin_function_exists(ExtismContext* context, int plugin, string funcName);
|
||||
public static extern bool extism_plugin_function_exists(IntPtr context, IntPtr plugin, string funcName);
|
||||
|
||||
/// <summary>
|
||||
/// Call a function.
|
||||
@@ -269,7 +92,7 @@ internal static class LibExtism
|
||||
/// <param name="dataLen">The length of the `data` parameter.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern int extism_plugin_call(ExtismContext* context, int plugin, string funcName, byte* data, int dataLen);
|
||||
unsafe public static extern int extism_plugin_call(IntPtr context, IntPtr plugin, string funcName, byte* data, int dataLen);
|
||||
|
||||
/// <summary>
|
||||
/// Get the error associated with a Context or Plugin, if plugin is -1 then the context error will be returned.
|
||||
@@ -278,7 +101,7 @@ internal static class LibExtism
|
||||
/// <param name="plugin">A plugin pointer, or -1 for the context error.</param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_error(ExtismContext* context, nint plugin);
|
||||
public static extern IntPtr extism_error(IntPtr context, nint plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the length of a plugin's output data.
|
||||
@@ -287,7 +110,7 @@ internal static class LibExtism
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern long extism_plugin_output_length(ExtismContext* context, int plugin);
|
||||
public static extern long extism_plugin_output_length(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Get the plugin's output data.
|
||||
@@ -296,7 +119,7 @@ internal static class LibExtism
|
||||
/// <param name="plugin"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
unsafe internal static extern IntPtr extism_plugin_output_data(ExtismContext* context, int plugin);
|
||||
public static extern IntPtr extism_plugin_output_data(IntPtr context, IntPtr plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Set log file and level.
|
||||
@@ -305,43 +128,43 @@ internal static class LibExtism
|
||||
/// <param name="logLevel"></param>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism")]
|
||||
internal static extern bool extism_log_file(string filename, string logLevel);
|
||||
public static extern bool extism_log_file(string filename, string logLevel);
|
||||
|
||||
/// <summary>
|
||||
/// Get the Extism version string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DllImport("extism", EntryPoint = "extism_version")]
|
||||
internal static extern IntPtr extism_version();
|
||||
public static extern IntPtr extism_version();
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
internal static class LogLevels
|
||||
public static class LogLevels
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
internal const string Error = "Error";
|
||||
public const string Error = "Error";
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
internal const string Warn = "Warn";
|
||||
public const string Warn = "Warn";
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
internal const string Info = "Info";
|
||||
public const string Info = "Info";
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
internal const string Debug = "Debug";
|
||||
public const string Debug = "Debug";
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
internal const string Trace = "Trace";
|
||||
public const string Trace = "Trace";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
namespace Extism.Sdk.Native;
|
||||
|
||||
/// <summary>
|
||||
/// Extism Log Levels
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Designates very serious errors.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Designates hazardous situations.
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// Designates useful information.
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// Designates lower priority information.
|
||||
/// </summary>
|
||||
Debug,
|
||||
|
||||
/// <summary>
|
||||
/// Designates very low priority, often extremely verbose, information.
|
||||
/// </summary>
|
||||
Trace
|
||||
}
|
||||
@@ -11,32 +11,18 @@ public class Plugin : IDisposable
|
||||
private const int DisposedMarker = 1;
|
||||
|
||||
private readonly Context _context;
|
||||
private readonly HostFunction[] _functions;
|
||||
private int _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Create a and load a plug-in
|
||||
/// Using this constructor will give the plug-in it's own internal Context
|
||||
/// </summary>
|
||||
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
|
||||
/// <param name="functions">List of host functions expected by the plugin.</param>
|
||||
/// <param name="withWasi">Enable/Disable WASI.</param>
|
||||
public static Plugin Create(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
|
||||
var context = new Context();
|
||||
return context.CreatePlugin(wasm, functions, withWasi);
|
||||
}
|
||||
|
||||
internal Plugin(Context context, HostFunction[] functions, int index)
|
||||
internal Plugin(Context context, IntPtr handle)
|
||||
{
|
||||
_context = context;
|
||||
_functions = functions;
|
||||
Index = index;
|
||||
NativeHandle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pointer to the native Plugin struct.
|
||||
/// </summary>
|
||||
internal int Index { get; }
|
||||
internal IntPtr NativeHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Update a plugin, keeping the existing ID.
|
||||
@@ -47,10 +33,9 @@ public class Plugin : IDisposable
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var functions = _functions.Select(f => f.NativeHandle).ToArray();
|
||||
fixed (byte* wasmPtr = wasm)
|
||||
{
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, Index, wasmPtr, wasm.Length, functions, 0, withWasi);
|
||||
return LibExtism.extism_plugin_update(_context.NativeHandle, NativeHandle, wasmPtr, wasm.Length, withWasi);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,18 +49,18 @@ public class Plugin : IDisposable
|
||||
|
||||
fixed (byte* jsonPtr = json)
|
||||
{
|
||||
return LibExtism.extism_plugin_config(_context.NativeHandle, Index, jsonPtr, json.Length);
|
||||
return LibExtism.extism_plugin_config(_context.NativeHandle, NativeHandle, jsonPtr, json.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a specific function exists in the current plugin.
|
||||
/// </summary>
|
||||
unsafe public bool FunctionExists(string name)
|
||||
public bool FunctionExists(string name)
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, Index, name);
|
||||
return LibExtism.extism_plugin_function_exists(_context.NativeHandle, NativeHandle, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,20 +78,14 @@ public class Plugin : IDisposable
|
||||
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
int response = LibExtism.extism_plugin_call(_context.NativeHandle, Index, functionName, dataPtr, data.Length);
|
||||
if (response == 0)
|
||||
{
|
||||
int response = LibExtism.extism_plugin_call(_context.NativeHandle, NativeHandle, functionName, dataPtr, data.Length);
|
||||
if (response == 0) {
|
||||
return OutputData();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
var errorMsg = GetError();
|
||||
if (errorMsg != null)
|
||||
{
|
||||
if (errorMsg != null) {
|
||||
throw new ExtismException(errorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
throw new ExtismException("Call to Extism failed");
|
||||
}
|
||||
}
|
||||
@@ -117,11 +96,11 @@ public class Plugin : IDisposable
|
||||
/// Get the length of a plugin's output data.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal int OutputLength()
|
||||
internal int OutputLength()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, Index);
|
||||
return (int)LibExtism.extism_plugin_output_length(_context.NativeHandle, NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,7 +114,7 @@ public class Plugin : IDisposable
|
||||
|
||||
unsafe
|
||||
{
|
||||
var ptr = LibExtism.extism_plugin_output_data(_context.NativeHandle, Index).ToPointer();
|
||||
var ptr = LibExtism.extism_plugin_output_data(_context.NativeHandle, NativeHandle).ToPointer();
|
||||
return new Span<byte>(ptr, length);
|
||||
}
|
||||
}
|
||||
@@ -144,11 +123,11 @@ public class Plugin : IDisposable
|
||||
/// Get the error associated with the current plugin.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
unsafe internal string? GetError()
|
||||
internal string? GetError()
|
||||
{
|
||||
CheckNotDisposed();
|
||||
|
||||
var result = LibExtism.extism_error(_context.NativeHandle, Index);
|
||||
var result = LibExtism.extism_error(_context.NativeHandle, NativeHandle);
|
||||
return Marshal.PtrToStringUTF8(result);
|
||||
}
|
||||
|
||||
@@ -189,7 +168,7 @@ public class Plugin : IDisposable
|
||||
/// <summary>
|
||||
/// Frees all resources held by this Plugin.
|
||||
/// </summary>
|
||||
unsafe protected virtual void Dispose(bool disposing)
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
@@ -197,7 +176,7 @@ public class Plugin : IDisposable
|
||||
}
|
||||
|
||||
// Free up unmanaged resources
|
||||
LibExtism.extism_plugin_free(_context.NativeHandle, Index);
|
||||
LibExtism.extism_plugin_free(_context.NativeHandle, NativeHandle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,4 +186,4 @@ public class Plugin : IDisposable
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Extism.Sdk.Native;
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Xunit;
|
||||
@@ -10,17 +9,6 @@ namespace Extism.Sdk.Tests;
|
||||
|
||||
public class BasicTests
|
||||
{
|
||||
[Fact]
|
||||
public void CountHelloWorldVowelsWithoutContext()
|
||||
{
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
|
||||
using var plugin = Plugin.Create(wasm, Array.Empty<HostFunction>(), withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountHelloWorldVowels()
|
||||
{
|
||||
@@ -28,46 +16,9 @@ public class BasicTests
|
||||
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code.wasm"));
|
||||
using var plugin = context.CreatePlugin(wasm, Array.Empty<HostFunction>(), withWasi: true);
|
||||
using var plugin = context.CreatePlugin(wasm, withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CountVowelsHostFunctions()
|
||||
{
|
||||
using var context = new Context();
|
||||
|
||||
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
|
||||
|
||||
using var helloWorld = new HostFunction(
|
||||
"hello_world",
|
||||
"env",
|
||||
new[] { ExtismValType.I64 },
|
||||
new[] { ExtismValType.I64 },
|
||||
userData,
|
||||
HelloWorld);
|
||||
|
||||
var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
|
||||
var wasm = File.ReadAllBytes(Path.Combine(binDirectory, "code-functions.wasm"));
|
||||
using var plugin = context.CreatePlugin(wasm, new[] { helloWorld }, withWasi: true);
|
||||
|
||||
var response = plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World"));
|
||||
Assert.Equal("{\"count\": 3}", Encoding.UTF8.GetString(response));
|
||||
|
||||
void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> outputs, nint data)
|
||||
{
|
||||
Console.WriteLine("Hello from .NET!");
|
||||
|
||||
var text = Marshal.PtrToStringAnsi(data);
|
||||
Console.WriteLine(text);
|
||||
|
||||
var input = plugin.ReadString(new nint(inputs[0].v.i64));
|
||||
Console.WriteLine($"Input: {input}");
|
||||
|
||||
var output = new string(input); // clone the string
|
||||
outputs[0].v.i64 = plugin.WriteString(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,6 @@
|
||||
<None Include="..\..\..\wasm\code.wasm" Link="code.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\..\..\wasm\code-functions.wasm" Link="code-functions.wasm">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -35,8 +32,4 @@
|
||||
<ProjectReference Include="..\..\src\Extism.Sdk\Extism.Sdk.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
16
dune-project
16
dune-project
@@ -21,13 +21,12 @@
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ctypes (>= 0.18.0))
|
||||
(dune (>= 3.2))
|
||||
(ctypes-foreign (>= 0.18.0))
|
||||
(bigstringaf (>= 0.9.0))
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(extism-manifest (= :version))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(ppx_yojson_conv (>= 0.15.0))
|
||||
extism-manifest
|
||||
(ppx_inline_test (>= 0.15.0))
|
||||
(cmdliner (>= 1.1.1))
|
||||
)
|
||||
(tags
|
||||
@@ -36,12 +35,11 @@
|
||||
(package
|
||||
(name extism-manifest)
|
||||
(synopsis "Extism manifest bindings")
|
||||
(description "Bindings to the Extism manifest format")
|
||||
(description "Bindings to Extism, the universal plugin system")
|
||||
(depends
|
||||
(ocaml (>= 4.14.1))
|
||||
dune
|
||||
(ppx_yojson_conv (>= v0.15.0))
|
||||
(ppx_inline_test (>= v0.15.0))
|
||||
(dune (>= 3.2))
|
||||
(ppx_yojson_conv (>= 0.15.0))
|
||||
(base64 (>= 3.5.0))
|
||||
)
|
||||
(tags
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
defmodule Extism.CancelHandle do
|
||||
@moduledoc """
|
||||
A CancelHandle is a handle generated by a plugin that allows it to be cancelled from another
|
||||
thread while running.
|
||||
"""
|
||||
defstruct [
|
||||
# The actual NIF Resource. PluginIndex and the context
|
||||
handle: nil
|
||||
]
|
||||
|
||||
def wrap_resource(handle) do
|
||||
%__MODULE__{
|
||||
handle: handle
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Cancel plugin execution
|
||||
"""
|
||||
def cancel(handle) do
|
||||
Extism.Native.plugin_cancel(handle.handle)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Extim.CancelHandle do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
concat(["#Extism.CancelHandle<", to_doc(dict.handle, opts), ">"])
|
||||
end
|
||||
end
|
||||
@@ -16,8 +16,6 @@ defmodule Extism.Native do
|
||||
def plugin_has_function(_ctx, _plugin_id, _function_name), do: error()
|
||||
def plugin_free(_ctx, _plugin_id), do: error()
|
||||
def set_log_file(_filename, _level), do: error()
|
||||
def plugin_cancel_handle(_ctx, _plugin_id), do: error()
|
||||
def plugin_cancel(_handle), do: error()
|
||||
|
||||
defp error, do: :erlang.nif_error(:nif_not_loaded)
|
||||
end
|
||||
|
||||
@@ -15,25 +15,12 @@ defmodule Extism.Plugin do
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a new plugin
|
||||
"""
|
||||
def new(manifest, wasi \\ false, context \\ nil) do
|
||||
ctx = context || Extism.Context.new()
|
||||
{:ok, manifest_payload} = JSON.encode(manifest)
|
||||
|
||||
case Extism.Native.plugin_new_with_manifest(ctx.ptr, manifest_payload, wasi) do
|
||||
{:error, err} -> {:error, err}
|
||||
res -> {:ok, Extism.Plugin.wrap_resource(ctx, res)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Call a plugin's function by name
|
||||
|
||||
## Examples
|
||||
|
||||
iex> {:ok, plugin} = Extism.Plugin.new(manifest, false)
|
||||
iex> {:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
|
||||
iex> {:ok, output} = Extism.Plugin.call(plugin, "count_vowels", "this is a test")
|
||||
# {:ok, "{\"count\": 4}"}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ defmodule Extism.MixProject do
|
||||
def project do
|
||||
[
|
||||
app: :extism,
|
||||
version: "0.4.0",
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.12",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps(),
|
||||
@@ -23,7 +23,7 @@ defmodule Extism.MixProject do
|
||||
|
||||
defp deps do
|
||||
[
|
||||
{:rustler, "~> 0.29.1"},
|
||||
{:rustler, "~> 0.26.0"},
|
||||
{:json, "~> 1.4"},
|
||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
|
||||
]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
%{
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"},
|
||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||
"json": {:hex, :json, "1.4.1", "8648f04a9439765ad449bc56a3ff7d8b11dd44ff08ffcdefc4329f7c93843dfa", [:mix], [], "hexpm", "9abf218dbe4ea4fcb875e087d5f904ef263d012ee5ed21d46e9dbca63f053d16"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"},
|
||||
"rustler": {:hex, :rustler, "0.29.1", "880f20ae3027bd7945def6cea767f5257bc926f33ff50c0d5d5a5315883c084d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "109497d701861bfcd26eb8f5801fe327a8eef304f56a5b63ef61151ff44ac9b6"},
|
||||
"toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"},
|
||||
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
|
||||
"rustler": {:hex, :rustler, "0.26.0", "06a2773d453ee3e9109efda643cf2ae633dedea709e2455ac42b83637c9249bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "42961e9d2083d004d5a53e111ad1f0c347efd9a05cb2eb2ffa1d037cdc74db91"},
|
||||
"toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism_nif"
|
||||
version = "0.3.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Benjamin Eckel <bhelx@simst.im>"]
|
||||
|
||||
@@ -9,10 +9,7 @@ name = "extism_nif"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# need this to be here and be empty
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
rustler = "0.28.0"
|
||||
extism = "0.4.0"
|
||||
rustler = "0.26.0"
|
||||
extism = { version = "0.1.0", path = "../../../rust" }
|
||||
log = "0.4"
|
||||
|
||||
@@ -17,24 +17,22 @@ mod atoms {
|
||||
struct ExtismContext {
|
||||
ctx: RwLock<Context>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for ExtismContext {}
|
||||
unsafe impl Send for ExtismContext {}
|
||||
|
||||
struct ExtismCancelHandle {
|
||||
handle: RwLock<extism::CancelHandle>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for ExtismCancelHandle {}
|
||||
unsafe impl Send for ExtismCancelHandle {}
|
||||
|
||||
fn load(env: Env, _: Term) -> bool {
|
||||
rustler::resource!(ExtismContext, env);
|
||||
rustler::resource!(ExtismCancelHandle, env);
|
||||
true
|
||||
}
|
||||
|
||||
fn to_rustler_error(extism_error: extism::Error) -> rustler::Error {
|
||||
rustler::Error::Term(Box::new(extism_error.to_string()))
|
||||
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::nif]
|
||||
@@ -52,7 +50,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)
|
||||
}
|
||||
|
||||
@@ -62,8 +60,8 @@ fn plugin_new_with_manifest(
|
||||
manifest_payload: String,
|
||||
wasi: bool,
|
||||
) -> Result<i32, rustler::Error> {
|
||||
let context = ctx.ctx.write().unwrap();
|
||||
let result = match Plugin::new(&context, manifest_payload, [], wasi) {
|
||||
let context = &ctx.ctx.write().unwrap();
|
||||
let result = match Plugin::new(context, manifest_payload, wasi) {
|
||||
Err(e) => Err(to_rustler_error(e)),
|
||||
Ok(plugin) => {
|
||||
let plugin_id = plugin.as_i32();
|
||||
@@ -87,7 +85,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",
|
||||
@@ -109,7 +107,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)),
|
||||
};
|
||||
@@ -119,24 +117,6 @@ fn plugin_update_manifest(
|
||||
result
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_cancel_handle(
|
||||
ctx: ResourceArc<ExtismContext>,
|
||||
plugin_id: i32,
|
||||
) -> Result<ResourceArc<ExtismCancelHandle>, rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
let plugin = unsafe { Plugin::from_id(plugin_id, context) };
|
||||
let handle = plugin.cancel_handle();
|
||||
Ok(ResourceArc::new(ExtismCancelHandle {
|
||||
handle: RwLock::new(handle),
|
||||
}))
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_cancel(handle: ResourceArc<ExtismCancelHandle>) -> bool {
|
||||
handle.handle.read().unwrap().cancel()
|
||||
}
|
||||
|
||||
#[rustler::nif]
|
||||
fn plugin_free(ctx: ResourceArc<ExtismContext>, plugin_id: i32) -> Result<(), rustler::Error> {
|
||||
let context = &ctx.ctx.read().unwrap();
|
||||
@@ -190,8 +170,6 @@ rustler::init!(
|
||||
plugin_call,
|
||||
plugin_update_manifest,
|
||||
plugin_has_function,
|
||||
plugin_cancel_handle,
|
||||
plugin_cancel,
|
||||
plugin_free,
|
||||
set_log_file,
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "Extism manifest bindings"
|
||||
description: "Bindings to the Extism manifest format"
|
||||
description: "Bindings to Extism, the universal plugin system"
|
||||
maintainer: ["Extism Authors <oss@extism.org>"]
|
||||
authors: ["Extism Authors <oss@extism.org>"]
|
||||
license: "BSD-3-Clause"
|
||||
@@ -11,9 +11,8 @@ doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"dune" {>= "3.2"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"dune" {>= "3.2" & >= "3.2"}
|
||||
"ppx_yojson_conv" {>= "0.15.0"}
|
||||
"base64" {>= "3.5.0"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
|
||||
332
extism.go
332
extism.go
@@ -5,50 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/local/include
|
||||
#cgo LDFLAGS: -L/usr/local/lib -lextism
|
||||
#cgo pkg-config: libextism.pc
|
||||
#include <extism.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int64_t extism_val_i64(ExtismValUnion* x){
|
||||
return x->i64;
|
||||
}
|
||||
|
||||
int32_t extism_val_i32(ExtismValUnion* x){
|
||||
return x->i32;
|
||||
}
|
||||
|
||||
float extism_val_f32(ExtismValUnion* x){
|
||||
return x->f32;
|
||||
}
|
||||
|
||||
double extism_val_f64(ExtismValUnion* x){
|
||||
return x->f64;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i64(ExtismValUnion* x, int64_t i){
|
||||
x->i64 = i;
|
||||
}
|
||||
|
||||
|
||||
void extism_val_set_i32(ExtismValUnion* x, int32_t i){
|
||||
x->i32 = i;
|
||||
}
|
||||
|
||||
void extism_val_set_f32(ExtismValUnion* x, float f){
|
||||
x->f32 = f;
|
||||
}
|
||||
|
||||
void extism_val_set_f64(ExtismValUnion* x, double f){
|
||||
x->f64 = f;
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -57,106 +20,6 @@ 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
|
||||
V128 ValType = C.V128
|
||||
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)
|
||||
var inputsPtr *C.ExtismValType = nil
|
||||
if len(inputs) > 0 {
|
||||
inputsPtr = (*C.ExtismValType)(&inputs[0])
|
||||
}
|
||||
var outputsPtr *C.ExtismValType = nil
|
||||
if len(outputs) > 0 {
|
||||
outputsPtr = (*C.ExtismValType)(&outputs[0])
|
||||
}
|
||||
function.pointer = C.extism_function_new(
|
||||
cname,
|
||||
inputsPtr,
|
||||
C.uint64_t(len(inputs)),
|
||||
outputsPtr,
|
||||
C.uint64_t(len(outputs)),
|
||||
(*[0]byte)(f),
|
||||
ptr,
|
||||
nil,
|
||||
)
|
||||
C.free(unsafe.Pointer(cname))
|
||||
return function
|
||||
}
|
||||
|
||||
func (f *Function) SetNamespace(s string) {
|
||||
cstr := C.CString(s)
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
C.extism_function_set_namespace(f.pointer, cstr)
|
||||
}
|
||||
|
||||
func (f Function) WithNamespace(s string) Function {
|
||||
f.SetNamespace(s)
|
||||
return f
|
||||
}
|
||||
|
||||
type CurrentPlugin struct {
|
||||
pointer *C.ExtismCurrentPlugin
|
||||
}
|
||||
|
||||
func GetCurrentPlugin(ptr unsafe.Pointer) CurrentPlugin {
|
||||
return CurrentPlugin{
|
||||
pointer: (*C.ExtismCurrentPlugin)(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))
|
||||
}
|
||||
|
||||
// Alloc a new memory block of the given length, returning its offset
|
||||
func (p *CurrentPlugin) Alloc(n uint) uint {
|
||||
return uint(C.extism_current_plugin_memory_alloc(p.pointer, C.uint64_t(n)))
|
||||
}
|
||||
|
||||
// Free the memory block specified by the given offset
|
||||
func (p *CurrentPlugin) Free(offs uint) {
|
||||
C.extism_current_plugin_memory_free(p.pointer, C.uint64_t(offs))
|
||||
}
|
||||
|
||||
// Length returns the number of bytes allocated at the specified offset
|
||||
func (p *CurrentPlugin) Length(offs uint) uint {
|
||||
return uint(C.extism_current_plugin_memory_length(p.pointer, C.uint64_t(offs)))
|
||||
}
|
||||
|
||||
// NewContext creates a new context, it should be freed using the `Free` method
|
||||
func NewContext() Context {
|
||||
p := C.extism_context_new()
|
||||
@@ -173,9 +36,8 @@ func (ctx *Context) Free() {
|
||||
|
||||
// Plugin is used to call WASM functions
|
||||
type Plugin struct {
|
||||
ctx *Context
|
||||
id int32
|
||||
functions []Function
|
||||
ctx *Context
|
||||
id int32
|
||||
}
|
||||
|
||||
type WasmData struct {
|
||||
@@ -234,32 +96,14 @@ func ExtismVersion() string {
|
||||
return C.GoString(C.extism_version())
|
||||
}
|
||||
|
||||
func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugin, error) {
|
||||
func register(ctx *Context, data []byte, wasi bool) (Plugin, error) {
|
||||
ptr := makePointer(data)
|
||||
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),
|
||||
)
|
||||
}
|
||||
plugin := C.extism_plugin_new(
|
||||
ctx.pointer,
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
)
|
||||
|
||||
if plugin < 0 {
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
@@ -273,44 +117,21 @@ func register(ctx *Context, data []byte, functions []Function, wasi bool) (Plugi
|
||||
)
|
||||
}
|
||||
|
||||
return Plugin{id: int32(plugin), ctx: ctx, functions: functions}, nil
|
||||
return Plugin{id: int32(plugin), ctx: ctx}, nil
|
||||
}
|
||||
|
||||
func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi bool) error {
|
||||
func update(ctx *Context, plugin int32, data []byte, wasi bool) error {
|
||||
ptr := makePointer(data)
|
||||
functionPointers := []*C.ExtismFunction{}
|
||||
for _, f := range functions {
|
||||
functionPointers = append(functionPointers, f.pointer)
|
||||
}
|
||||
b := bool(C.extism_plugin_update(
|
||||
ctx.pointer,
|
||||
C.int32_t(plugin),
|
||||
(*C.uchar)(ptr),
|
||||
C.uint64_t(len(data)),
|
||||
C._Bool(wasi),
|
||||
))
|
||||
|
||||
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
|
||||
}
|
||||
if b {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := C.extism_error(ctx.pointer, C.int32_t(-1))
|
||||
@@ -324,57 +145,44 @@ func update(ctx *Context, plugin int32, data []byte, functions []Function, wasi
|
||||
)
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin in its own context
|
||||
func NewPlugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
ctx := NewContext()
|
||||
return ctx.Plugin(module, functions, wasi)
|
||||
}
|
||||
|
||||
// NewPlugin creates a plugin in its own context from a manifest
|
||||
func NewPluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
ctx := NewContext()
|
||||
return ctx.PluginFromManifest(manifest, functions, wasi)
|
||||
}
|
||||
|
||||
// PluginFromManifest creates a plugin from a `Manifest`
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, functions []Function, wasi bool) (Plugin, error) {
|
||||
func (ctx *Context) PluginFromManifest(manifest Manifest, wasi bool) (Plugin, error) {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(ctx, data, functions, wasi)
|
||||
return register(ctx, data, wasi)
|
||||
}
|
||||
|
||||
// Plugin creates a plugin from a WASM module
|
||||
func (ctx *Context) Plugin(module io.Reader, functions []Function, wasi bool) (Plugin, error) {
|
||||
func (ctx *Context) Plugin(module io.Reader, wasi bool) (Plugin, error) {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return Plugin{id: -1}, err
|
||||
}
|
||||
|
||||
return register(ctx, wasm, functions, wasi)
|
||||
return register(ctx, wasm, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new WASM module
|
||||
func (p *Plugin) Update(module io.Reader, functions []Function, wasi bool) error {
|
||||
func (p *Plugin) Update(module io.Reader, wasi bool) error {
|
||||
wasm, err := io.ReadAll(module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.functions = functions
|
||||
return update(p.ctx, p.id, wasm, functions, wasi)
|
||||
|
||||
return update(p.ctx, p.id, wasm, wasi)
|
||||
}
|
||||
|
||||
// Update a plugin with a new Manifest
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, functions []Function, wasi bool) error {
|
||||
func (p *Plugin) UpdateManifest(manifest Manifest, wasi bool) error {
|
||||
data, err := json.Marshal(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.functions = functions
|
||||
return update(p.ctx, p.id, data, functions, wasi)
|
||||
return update(p.ctx, p.id, data, wasi)
|
||||
}
|
||||
|
||||
// Set configuration values
|
||||
@@ -425,7 +233,8 @@ 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))
|
||||
return unsafe.Slice((*byte)(x), C.int(length)), nil
|
||||
y := (*[]byte)(unsafe.Pointer(&x))
|
||||
return []byte((*y)[0:length]), nil
|
||||
}
|
||||
|
||||
return []byte{}, nil
|
||||
@@ -444,80 +253,3 @@ func (plugin *Plugin) Free() {
|
||||
func (ctx Context) Reset() {
|
||||
C.extism_context_reset(ctx.pointer)
|
||||
}
|
||||
|
||||
// ValGetI64 returns an I64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI64(v unsafe.Pointer) int64 {
|
||||
return int64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetUInt returns a uint from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetUInt(v unsafe.Pointer) uint {
|
||||
return uint(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetI32 returns an int32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetI32(v unsafe.Pointer) int32 {
|
||||
return int32(C.extism_val_i32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float32 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF32(v unsafe.Pointer) float32 {
|
||||
return float32(C.extism_val_f32(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValGetF32 returns a float64 from an ExtismVal, it accepts a pointer to a C.ExtismVal
|
||||
func ValGetF64(v unsafe.Pointer) float64 {
|
||||
return float64(C.extism_val_i64(&(*Val)(v).v))
|
||||
}
|
||||
|
||||
// ValSetI64 stores an int64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI64(v unsafe.Pointer, i int64) {
|
||||
C.extism_val_set_i64(&(*Val)(v).v, C.int64_t(i))
|
||||
}
|
||||
|
||||
// ValSetI32 stores an int32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetI32(v unsafe.Pointer, i int32) {
|
||||
C.extism_val_set_i32(&(*Val)(v).v, C.int32_t(i))
|
||||
}
|
||||
|
||||
// ValSetF32 stores a float32 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF32(v unsafe.Pointer, i float32) {
|
||||
C.extism_val_set_f32(&(*Val)(v).v, C.float(i))
|
||||
}
|
||||
|
||||
// ValSetF64 stores a float64 in an ExtismVal, it accepts a pointer to a C.ExtismVal and the new value
|
||||
func ValSetF64(v unsafe.Pointer, f float64) {
|
||||
C.extism_val_set_f64(&(*Val)(v).v, C.double(f))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnBytes(v unsafe.Pointer, b []byte) {
|
||||
mem := p.Alloc(uint(len(b)))
|
||||
ptr := p.Memory(mem)
|
||||
copy(ptr, b)
|
||||
ValSetI64(v, int64(mem))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) ReturnString(v unsafe.Pointer, s string) {
|
||||
p.ReturnBytes(v, []byte(s))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputBytes(v unsafe.Pointer) []byte {
|
||||
return p.Memory(ValGetUInt(v))
|
||||
}
|
||||
|
||||
func (p *CurrentPlugin) InputString(v unsafe.Pointer) string {
|
||||
return string(p.InputBytes(v))
|
||||
}
|
||||
|
||||
type CancelHandle struct {
|
||||
pointer *C.ExtismCancelHandle
|
||||
}
|
||||
|
||||
func (p *Plugin) CancelHandle() CancelHandle {
|
||||
pointer := C.extism_plugin_cancel_handle(p.ctx.pointer, C.int(p.id))
|
||||
return CancelHandle{pointer}
|
||||
}
|
||||
|
||||
func (c *CancelHandle) Cancel() bool {
|
||||
return bool(C.extism_plugin_cancel(c.pointer))
|
||||
}
|
||||
|
||||
11
extism.opam
11
extism.opam
@@ -11,13 +11,12 @@ doc: "https://github.com/extism/extism"
|
||||
bug-reports: "https://github.com/extism/extism/issues"
|
||||
depends: [
|
||||
"ocaml" {>= "4.14.1"}
|
||||
"dune" {>= "3.2"}
|
||||
"ctypes" {>= "0.18.0"}
|
||||
"dune" {>= "3.2" & >= "3.2"}
|
||||
"ctypes-foreign" {>= "0.18.0"}
|
||||
"bigstringaf" {>= "0.9.0"}
|
||||
"ppx_yojson_conv" {>= "v0.15.0"}
|
||||
"extism-manifest" {= version}
|
||||
"ppx_inline_test" {>= "v0.15.0"}
|
||||
"ppx_yojson_conv" {>= "0.15.0"}
|
||||
"extism-manifest"
|
||||
"ppx_inline_test" {>= "0.15.0"}
|
||||
"cmdliner" {>= "1.1.1"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
@@ -36,5 +35,3 @@ build: [
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/extism/extism.git"
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
build-env: [EXTISM_TEST_NO_LIB = ""]
|
||||
post-messages: ["See https://extism.org/docs/install/ for information about installing libextism"]
|
||||
@@ -4,19 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func manifest(functions bool) Manifest {
|
||||
path := "./wasm/code.wasm"
|
||||
if functions {
|
||||
path = "./wasm/code-functions.wasm"
|
||||
}
|
||||
|
||||
func manifest() Manifest {
|
||||
return Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: path,
|
||||
Path: "./wasm/code.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -44,7 +38,7 @@ func TestCallPlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -64,7 +58,7 @@ func TestFreePlugin(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -84,7 +78,7 @@ func TestContextReset(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -104,7 +98,7 @@ func TestCanUpdateAManifest(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -113,7 +107,7 @@ func TestCanUpdateAManifest(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
plugin.UpdateManifest(manifest(false), []Function{}, false)
|
||||
plugin.UpdateManifest(manifest(), false)
|
||||
|
||||
// can still call the plugin
|
||||
if err := expectVowelCount(plugin, "this is a test", 4); err != nil {
|
||||
@@ -125,7 +119,7 @@ func TestFunctionExists(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -142,7 +136,7 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest(false), []Function{}, false)
|
||||
plugin, err := ctx.PluginFromManifest(manifest(), false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -152,33 +146,3 @@ func TestErrorsOnUnknownFunction(t *testing.T) {
|
||||
t.Fatal("Was expecting call to unknown function to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
manifest := Manifest{
|
||||
Wasm: []Wasm{
|
||||
WasmFile{
|
||||
Path: "./wasm/loop.wasm",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := NewContext()
|
||||
defer ctx.Free()
|
||||
|
||||
plugin, err := ctx.PluginFromManifest(manifest, []Function{}, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cancelHandle := plugin.CancelHandle()
|
||||
|
||||
go func(handle CancelHandle) {
|
||||
time.Sleep(time.Second * 1)
|
||||
handle.Cancel()
|
||||
}(cancelHandle)
|
||||
|
||||
_, err = plugin.Call("infinite_loop", []byte(""))
|
||||
if err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
34
go/main.go
34
go/main.go
@@ -4,38 +4,17 @@ 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 unsafe.Pointer, 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)
|
||||
|
||||
// Get memory pointed to by first element of input slice
|
||||
p := extism.GetCurrentPlugin(plugin)
|
||||
str := p.InputString(unsafe.Pointer(&inputSlice[0]))
|
||||
fmt.Println(str)
|
||||
|
||||
outputSlice[0] = inputSlice[0]
|
||||
}
|
||||
|
||||
func main() {
|
||||
version := extism.ExtismVersion()
|
||||
fmt.Println("Extism Version: ", version)
|
||||
|
||||
ctx := extism.NewContext()
|
||||
defer ctx.Free() // this will free the context and all associated plugins
|
||||
|
||||
// set some input data to provide to the plugin module
|
||||
var data []byte
|
||||
if len(os.Args) > 1 {
|
||||
@@ -43,10 +22,9 @@ func main() {
|
||||
} else {
|
||||
data = []byte("testing from go -> wasm shared memory...")
|
||||
}
|
||||
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 := extism.NewPluginFromManifest(manifest, []extism.Function{f}, true)
|
||||
|
||||
manifest := extism.Manifest{Wasm: []extism.Wasm{extism.WasmFile{Path: "../wasm/code.wasm"}}}
|
||||
plugin, err := ctx.PluginFromManifest(manifest, false)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Revision history for extism
|
||||
|
||||
## 0.2.0.0 -- 2023-01-16
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
module Main where
|
||||
|
||||
import Extism
|
||||
import Extism.CurrentPlugin
|
||||
import Extism.Manifest(manifest, wasmFile)
|
||||
|
||||
unwrap (Right x) = x
|
||||
unwrap (Left (ExtismError msg)) = do
|
||||
error msg
|
||||
|
||||
hello plugin params msg = do
|
||||
putStrLn "Hello from Haskell!"
|
||||
putStrLn msg
|
||||
offs <- allocBytes plugin (toByteString "{\"count\": 999}")
|
||||
return [toI64 offs]
|
||||
|
||||
main = do
|
||||
setLogFile "stdout" Error
|
||||
let m = manifest [wasmFile "../wasm/code-functions.wasm"]
|
||||
f <- hostFunction "hello_world" [I64] [I64] hello "Hello, again"
|
||||
plugin <- unwrap <$> createPluginFromManifest m [f] True
|
||||
res <- unwrap <$> call plugin "count_vowels" (toByteString "this is a test")
|
||||
putStrLn (fromByteString res)
|
||||
free plugin
|
||||
let m = manifest [wasmFile "../wasm/code.wasm"]
|
||||
context <- Extism.newContext
|
||||
plugin <- unwrap <$> Extism.pluginFromManifest context m False
|
||||
res <- unwrap <$> Extism.call plugin "count_vowels" (Extism.toByteString "this is a test")
|
||||
putStrLn (Extism.fromByteString res)
|
||||
Extism.free plugin
|
||||
|
||||
@@ -13,9 +13,6 @@ 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cabal-version: 3.0
|
||||
name: extism
|
||||
version: 0.3.0
|
||||
version: 0.0.1
|
||||
license: BSD-3-Clause
|
||||
maintainer: oss@extism.org
|
||||
author: Extism authors
|
||||
@@ -8,10 +8,10 @@ bug-reports: https://github.com/extism/extism
|
||||
synopsis: Extism bindings
|
||||
description: Bindings to Extism, the universal plugin system
|
||||
category: Plugins, WebAssembly
|
||||
extra-doc-files: CHANGELOG.md
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism Extism.CurrentPlugin
|
||||
exposed-modules: Extism
|
||||
reexported-modules: Extism.Manifest
|
||||
hs-source-dirs: src
|
||||
other-modules: Extism.Bindings
|
||||
@@ -19,10 +19,10 @@ library
|
||||
extra-libraries: extism
|
||||
extra-lib-dirs: /usr/local/lib
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
extism-manifest >= 0.0.0 && < 0.3.0
|
||||
base >= 4.16.1 && < 4.18.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
extism-manifest >= 0.0.0 && < 0.1.0
|
||||
|
||||
test-suite extism-example
|
||||
type: exitcode-stdio-1.0
|
||||
|
||||
@@ -16,16 +16,14 @@ isNull JSNull = True
|
||||
isNull _ = False
|
||||
filterNulls obj = [(a, b) | (a, b) <- obj, not (isNull b)]
|
||||
object x = makeObj $ filterNulls x
|
||||
objectWithNulls = makeObj
|
||||
nonNull = NotNull
|
||||
objectWithNulls x = makeObj x
|
||||
nonNull x = NotNull x
|
||||
null' = Null
|
||||
(.=) a b = (a, showJSON b)
|
||||
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)
|
||||
|
||||
@@ -40,7 +38,7 @@ find :: JSON a => String -> JSValue -> Nullable a
|
||||
find k obj = obj .? k
|
||||
|
||||
update :: JSON a => String -> a -> JSValue -> JSValue
|
||||
update k v (JSObject obj) = object $ fromJSObject obj ++ [k .= v]
|
||||
update k v (JSObject obj) = object $ (fromJSObject obj) ++ [k .= v]
|
||||
|
||||
instance JSON a => JSON (Nullable a) where
|
||||
showJSON (NotNull x) = showJSON x
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
cabal-version: 3.0
|
||||
name: extism-manifest
|
||||
version: 0.2.0
|
||||
version: 0.0.1
|
||||
license: BSD-3-Clause
|
||||
maintainer: oss@extism.org
|
||||
author: Extism authors
|
||||
@@ -8,14 +8,14 @@ bug-reports: https://github.com/extism/extism
|
||||
synopsis: Extism manifest bindings
|
||||
description: Bindings to Extism WebAssembly manifest
|
||||
category: Plugins, WebAssembly
|
||||
extra-doc-files: CHANGELOG.md
|
||||
extra-source-files: CHANGELOG.md
|
||||
|
||||
library
|
||||
exposed-modules: Extism.Manifest Extism.JSON
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
base >= 4.16.1 && < 4.18.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
base64-bytestring >= 1.2.1 && < 1.3,
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
module Extism (
|
||||
module Extism,
|
||||
module Extism.Manifest,
|
||||
ValType(..),
|
||||
Val(..)
|
||||
) where
|
||||
|
||||
module Extism (module Extism, module Extism.Manifest) where
|
||||
import Data.Int
|
||||
import Data.Word
|
||||
import Control.Monad (void)
|
||||
import Foreign.ForeignPtr
|
||||
import Foreign.C.String
|
||||
import Foreign.Ptr
|
||||
import Foreign.Marshal.Array
|
||||
import Foreign.Storable
|
||||
import Foreign.StablePtr
|
||||
import Foreign.Concurrent
|
||||
import Foreign.Marshal.Utils (copyBytes, moveBytes)
|
||||
import Data.ByteString as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import Data.ByteString.Unsafe (unsafeUseAsCString)
|
||||
@@ -27,17 +16,8 @@ import Extism.Bindings
|
||||
-- | Context for managing plugins
|
||||
newtype Context = Context (ForeignPtr ExtismContext)
|
||||
|
||||
-- | Host function
|
||||
data Function = Function (ForeignPtr ExtismFunction) (StablePtr ())
|
||||
|
||||
-- | Plugins can be used to call WASM function
|
||||
data Plugin = Plugin Context Int32 [Function]
|
||||
|
||||
-- | Cancellation handle for Plugins
|
||||
newtype CancelHandle = CancelHandle (Ptr ExtismCancelHandle)
|
||||
|
||||
-- | Access the plugin that is currently executing from inside a host function
|
||||
type CurrentPlugin = Ptr ExtismCurrentPlugin
|
||||
data Plugin = Plugin Context Int32
|
||||
|
||||
-- | Log level
|
||||
data LogLevel = Error | Warn | Info | Debug | Trace deriving (Show)
|
||||
@@ -71,94 +51,68 @@ reset (Context ctx) =
|
||||
newContext :: IO Context
|
||||
newContext = do
|
||||
ptr <- extism_context_new
|
||||
fptr <- Foreign.ForeignPtr.newForeignPtr extism_context_free ptr
|
||||
fptr <- newForeignPtr extism_context_free ptr
|
||||
return (Context fptr)
|
||||
|
||||
|
||||
-- | Execute a function with a new 'Context' that is destroyed when it returns
|
||||
withContext :: (Context -> IO a) -> IO a
|
||||
withContext f = do
|
||||
ctx <- newContext
|
||||
f ctx
|
||||
|
||||
-- | Execute a function with the provided 'Plugin' as a parameter, then frees the 'Plugin'
|
||||
-- | before returning the result.
|
||||
withPlugin :: (Plugin -> IO a) -> Plugin -> IO a
|
||||
withPlugin f plugin = do
|
||||
res <- f plugin
|
||||
free plugin
|
||||
return res
|
||||
|
||||
-- | Create a 'Plugin' from a WASM module, `useWasi` determines if WASI should
|
||||
-- | be linked
|
||||
plugin :: Context -> B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
|
||||
plugin c wasm functions useWasi =
|
||||
let nfunctions = fromIntegral (Prelude.length functions) in
|
||||
plugin :: Context -> B.ByteString -> Bool -> IO (Result Plugin)
|
||||
plugin c wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
let Context ctx = c in
|
||||
do
|
||||
funcs <- Prelude.mapM (\(Function ptr _) -> withForeignPtr ptr (\x -> do return x)) functions
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
p <- unsafeUseAsCString wasm (\s ->
|
||||
withArray funcs (\funcs ->
|
||||
extism_plugin_new ctx (castPtr s) length funcs nfunctions wasi ))
|
||||
extism_plugin_new ctx (castPtr s) length wasi)
|
||||
if p < 0 then do
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
else
|
||||
return $ Right (Plugin c p functions))
|
||||
|
||||
-- | Create a 'Plugin' with its own 'Context'
|
||||
createPlugin :: B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
|
||||
createPlugin c functions useWasi = do
|
||||
ctx <- newContext
|
||||
plugin ctx c functions useWasi
|
||||
return $ Right (Plugin c p))
|
||||
|
||||
-- | Create a 'Plugin' from a 'Manifest'
|
||||
pluginFromManifest :: Context -> Manifest -> [Function] -> Bool -> IO (Result Plugin)
|
||||
pluginFromManifest ctx manifest functions useWasi =
|
||||
pluginFromManifest :: Context -> Manifest -> Bool -> IO (Result Plugin)
|
||||
pluginFromManifest ctx manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
plugin ctx wasm functions useWasi
|
||||
|
||||
-- | Create a 'Plugin' with its own 'Context' from a 'Manifest'
|
||||
createPluginFromManifest :: Manifest -> [Function] -> Bool -> IO (Result Plugin)
|
||||
createPluginFromManifest manifest functions useWasi = do
|
||||
ctx <- newContext
|
||||
pluginFromManifest ctx manifest functions useWasi
|
||||
plugin ctx wasm useWasi
|
||||
|
||||
-- | Update a 'Plugin' with a new WASM module
|
||||
update :: Plugin -> B.ByteString -> [Function] -> Bool -> IO (Result Plugin)
|
||||
update (Plugin (Context ctx) id _) wasm functions useWasi =
|
||||
let nfunctions = fromIntegral (Prelude.length functions) in
|
||||
update :: Plugin -> B.ByteString -> Bool -> IO (Result ())
|
||||
update (Plugin (Context ctx) id) wasm useWasi =
|
||||
let length = fromIntegral (B.length wasm) in
|
||||
let wasi = fromInteger (if useWasi then 1 else 0) in
|
||||
do
|
||||
funcs <- Prelude.mapM (\(Function ptr _ ) -> withForeignPtr ptr (\x -> do return x)) functions
|
||||
withForeignPtr ctx (\ctx' -> do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- unsafeUseAsCString wasm (\s ->
|
||||
withArray funcs (\funcs ->
|
||||
extism_plugin_update ctx' id (castPtr s) length funcs nfunctions wasi))
|
||||
extism_plugin_update ctx id (castPtr s) length wasi)
|
||||
if b <= 0 then do
|
||||
err <- extism_error ctx' (-1)
|
||||
err <- extism_error ctx (-1)
|
||||
e <- peekCString err
|
||||
return $ Left (ExtismError e)
|
||||
else
|
||||
return (Right (Plugin (Context ctx) id functions)))
|
||||
return (Right ()))
|
||||
|
||||
-- | Update a 'Plugin' with a new 'Manifest'
|
||||
updateManifest :: Plugin -> Manifest -> [Function] -> Bool -> IO (Result Plugin)
|
||||
updateManifest plugin manifest functions useWasi =
|
||||
updateManifest :: Plugin -> Manifest -> Bool -> IO (Result ())
|
||||
updateManifest plugin manifest useWasi =
|
||||
let wasm = toByteString $ toString manifest in
|
||||
update plugin wasm functions useWasi
|
||||
update plugin wasm useWasi
|
||||
|
||||
-- | Check if a 'Plugin' is valid
|
||||
isValid :: Plugin -> Bool
|
||||
isValid (Plugin _ p _) = p >= 0
|
||||
isValid (Plugin _ p) = p >= 0
|
||||
|
||||
-- | Set configuration values for a plugin
|
||||
setConfig :: Plugin -> [(String, Maybe String)] -> IO Bool
|
||||
setConfig (Plugin (Context ctx) plugin _) x =
|
||||
setConfig (Plugin (Context ctx) plugin) x =
|
||||
if plugin < 0
|
||||
then return False
|
||||
else
|
||||
@@ -187,14 +141,14 @@ setLogFile filename level =
|
||||
|
||||
-- | Check if a function exists in the given plugin
|
||||
functionExists :: Plugin -> String -> IO Bool
|
||||
functionExists (Plugin (Context ctx) plugin _) name = do
|
||||
functionExists (Plugin (Context ctx) plugin) name = do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
b <- withCString name (extism_plugin_function_exists ctx plugin)
|
||||
if b == 1 then return True else return False)
|
||||
|
||||
--- | Call a function provided by the given plugin
|
||||
call :: Plugin -> String -> B.ByteString -> IO (Result B.ByteString)
|
||||
call (Plugin (Context ctx) plugin _) name input =
|
||||
call (Plugin (Context ctx) plugin) name input =
|
||||
let length = fromIntegral (B.length input) in
|
||||
do
|
||||
withForeignPtr ctx (\ctx -> do
|
||||
@@ -216,73 +170,5 @@ call (Plugin (Context ctx) plugin _) name input =
|
||||
-- | Free a 'Plugin', this will automatically be called for every plugin
|
||||
-- | associated with a 'Context' when that 'Context' is freed
|
||||
free :: Plugin -> IO ()
|
||||
free (Plugin (Context ctx) plugin _) =
|
||||
free (Plugin (Context ctx) plugin) =
|
||||
withForeignPtr ctx (`extism_plugin_free` plugin)
|
||||
|
||||
-- | Create a new 'CancelHandle' that can be used to cancel a running plugin
|
||||
-- | from another thread.
|
||||
cancelHandle :: Plugin -> IO CancelHandle
|
||||
cancelHandle (Plugin (Context ctx) plugin _) = do
|
||||
handle <- withForeignPtr ctx (`extism_plugin_cancel_handle` plugin)
|
||||
return (CancelHandle handle)
|
||||
|
||||
-- | Cancel a running plugin using a 'CancelHandle'
|
||||
cancel :: CancelHandle -> IO Bool
|
||||
cancel (CancelHandle handle) =
|
||||
extism_plugin_cancel handle
|
||||
|
||||
|
||||
-- | Create a new 'Function' that can be called from a 'Plugin'
|
||||
hostFunction :: String -> [ValType] -> [ValType] -> (CurrentPlugin -> [Val] -> a -> IO [Val]) -> a -> IO Function
|
||||
hostFunction name params results f v =
|
||||
let nparams = fromIntegral $ Prelude.length params in
|
||||
let nresults = fromIntegral $ Prelude.length results in
|
||||
do
|
||||
cb <- callbackWrap (callback f :: CCallback)
|
||||
free <- freePtrWrap freePtr
|
||||
userData <- newStablePtr (v, free, cb)
|
||||
let userDataPtr = castStablePtrToPtr userData
|
||||
x <- withCString name (\name -> do
|
||||
withArray params (\params ->
|
||||
withArray results (\results -> do
|
||||
extism_function_new name params nparams results nresults cb userDataPtr free)))
|
||||
let freeFn = extism_function_free x
|
||||
fptr <- Foreign.Concurrent.newForeignPtr x freeFn
|
||||
return $ Function fptr (castPtrToStablePtr userDataPtr)
|
||||
|
||||
|
||||
-- | Create a new I32 'Val'
|
||||
toI32 :: Integral a => a -> Val
|
||||
toI32 x = ValI32 (fromIntegral x)
|
||||
|
||||
-- | Create a new I64 'Val'
|
||||
toI64 :: Integral a => a -> Val
|
||||
toI64 x = ValI64 (fromIntegral x)
|
||||
|
||||
-- | Create a new F32 'Val'
|
||||
toF32 :: Float -> Val
|
||||
toF32 = ValF32
|
||||
|
||||
-- | Create a new F64 'Val'
|
||||
toF64 :: Double -> Val
|
||||
toF64 = ValF64
|
||||
|
||||
-- | Get I32 'Val'
|
||||
fromI32 :: Integral a => Val -> Maybe a
|
||||
fromI32 (ValI32 x) = Just (fromIntegral x)
|
||||
fromI32 _ = Nothing
|
||||
|
||||
-- | Get I64 'Val'
|
||||
fromI64 :: Integral a => Val -> Maybe a
|
||||
fromI64 (ValI64 x) = Just (fromIntegral x)
|
||||
fromI64 _ = Nothing
|
||||
|
||||
-- | Get F32 'Val'
|
||||
fromF32 :: Val -> Maybe Float
|
||||
fromF32 (ValF32 x) = Just x
|
||||
fromF32 _ = Nothing
|
||||
|
||||
-- | Get F64 'Val'
|
||||
fromF64 :: Val -> Maybe Double
|
||||
fromF64 (ValF64 x) = Just x
|
||||
fromF64 _ = Nothing
|
||||
@@ -7,116 +7,20 @@ import Foreign.Ptr
|
||||
import Foreign.C.String
|
||||
import Data.Int
|
||||
import Data.Word
|
||||
import Foreign.Storable
|
||||
import Foreign.Marshal.Array
|
||||
import Foreign.StablePtr
|
||||
|
||||
type FreeCallback = Ptr () -> IO ()
|
||||
|
||||
newtype ExtismContext = ExtismContext () deriving Show
|
||||
newtype ExtismFunction = ExtismFunction () deriving Show
|
||||
newtype ExtismCancelHandle = ExtismCancelHandle () deriving Show
|
||||
newtype ExtismCurrentPlugin = ExtismCurrentPlugin () deriving Show
|
||||
data ValType = I32 | I64 | F32 | F64 | V128 | FuncRef | ExternRef deriving (Show, Eq)
|
||||
data Val = ValI32 Int32 | ValI64 Int64 | ValF32 Float | ValF64 Double deriving (Show, Eq)
|
||||
|
||||
typeOfVal (ValI32 _) = I32
|
||||
typeOfVal (ValI64 _) = I64
|
||||
typeOfVal (ValF32 _) = F32
|
||||
typeOfVal (ValF64 _) = F64
|
||||
|
||||
type CCallback = Ptr ExtismCurrentPlugin -> Ptr Val -> Word64 -> Ptr Val -> Word64 -> Ptr () -> IO ()
|
||||
|
||||
_32Bit = sizeOf (undefined :: Int) == 4
|
||||
|
||||
instance Storable Val where
|
||||
sizeOf _ =
|
||||
if _32Bit then 12 else 16
|
||||
alignment _ = 1
|
||||
peek ptr = do
|
||||
let offs = if _32Bit then 4 else 8
|
||||
t <- valTypeOfInt <$> peekByteOff ptr 0
|
||||
case t of
|
||||
I32 -> ValI32 <$> peekByteOff ptr offs
|
||||
I64 -> ValI64 <$> peekByteOff ptr offs
|
||||
F32 -> ValF32 <$> peekByteOff ptr offs
|
||||
F64 -> ValF64 <$> peekByteOff ptr offs
|
||||
poke ptr x = do
|
||||
let offs = if _32Bit then 4 else 8
|
||||
pokeByteOff ptr 0 (typeOfVal x)
|
||||
case x of
|
||||
ValI32 x -> pokeByteOff ptr offs x
|
||||
ValI64 x -> pokeByteOff ptr offs x
|
||||
ValF32 x -> pokeByteOff ptr offs x
|
||||
ValF64 x -> pokeByteOff ptr offs x
|
||||
|
||||
|
||||
intOfValType :: ValType -> CInt
|
||||
intOfValType I32 = 0
|
||||
intOfValType I64 = 1
|
||||
intOfValType F32 = 2
|
||||
intOfValType F64 = 3
|
||||
intOfValType V128 = 4
|
||||
intOfValType FuncRef = 5
|
||||
intOfValType ExternRef = 6
|
||||
|
||||
valTypeOfInt :: CInt -> ValType
|
||||
valTypeOfInt 0 = I32
|
||||
valTypeOfInt 1 = I64
|
||||
valTypeOfInt 2 = F32
|
||||
valTypeOfInt 3 = F64
|
||||
valTypeOfInt 4 = V128
|
||||
valTypeOfInt 5 = FuncRef
|
||||
valTypeOfInt 6 = ExternRef
|
||||
valTypeOfInt _ = error "Invalid ValType"
|
||||
|
||||
instance Storable ValType where
|
||||
sizeOf _ = 4
|
||||
alignment _ = 1
|
||||
peek ptr = do
|
||||
x <- peekByteOff ptr 0
|
||||
return $ valTypeOfInt (x :: CInt)
|
||||
poke ptr x = do
|
||||
pokeByteOff ptr 0 (intOfValType x)
|
||||
|
||||
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
|
||||
foreign import ccall safe "extism.h extism_plugin_cancel_handle" extism_plugin_cancel_handle :: Ptr ExtismContext -> Int32 -> IO (Ptr ExtismCancelHandle)
|
||||
foreign import ccall safe "extism.h extism_plugin_cancel" extism_plugin_cancel :: Ptr ExtismCancelHandle -> IO Bool
|
||||
|
||||
foreign import ccall safe "extism.h extism_function_new" extism_function_new :: CString -> Ptr ValType -> Word64 -> Ptr ValType -> Word64 -> FunPtr CCallback -> Ptr () -> FunPtr FreeCallback -> IO (Ptr ExtismFunction)
|
||||
foreign import ccall safe "extism.h extism_function_free" extism_function_free :: Ptr ExtismFunction -> IO ()
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory" extism_current_plugin_memory :: Ptr ExtismCurrentPlugin -> IO (Ptr Word8)
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_alloc" extism_current_plugin_memory_alloc :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_length" extism_current_plugin_memory_length :: Ptr ExtismCurrentPlugin -> Word64 -> IO Word64
|
||||
foreign import ccall safe "extism.h extism_current_plugin_memory_free" extism_current_plugin_memory_free :: Ptr ExtismCurrentPlugin -> Word64 -> IO ()
|
||||
|
||||
freePtr ptr = do
|
||||
let s = castPtrToStablePtr ptr
|
||||
(a, b, c) <- deRefStablePtr s
|
||||
freeHaskellFunPtr b
|
||||
freeHaskellFunPtr c
|
||||
freeStablePtr s
|
||||
|
||||
foreign import ccall "wrapper" freePtrWrap :: FreeCallback -> IO (FunPtr FreeCallback)
|
||||
|
||||
foreign import ccall "wrapper" callbackWrap :: CCallback -> IO (FunPtr CCallback)
|
||||
|
||||
callback :: (Ptr ExtismCurrentPlugin -> [Val] -> a -> IO [Val]) -> (Ptr ExtismCurrentPlugin -> Ptr Val -> Word64 -> Ptr Val -> Word64 -> Ptr () -> IO ())
|
||||
callback f plugin params nparams results nresults ptr = do
|
||||
p <- peekArray (fromIntegral nparams) params
|
||||
(userData, _, _) <- deRefStablePtr (castPtrToStablePtr ptr)
|
||||
res <- f plugin p userData
|
||||
pokeArray results res
|
||||
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
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
module Extism.CurrentPlugin where
|
||||
|
||||
import Extism
|
||||
import Extism.Bindings
|
||||
import Data.Word
|
||||
import Data.ByteString as B
|
||||
import Foreign.Ptr
|
||||
import Foreign.Marshal.Array
|
||||
|
||||
-- | Allocate a new handle of the given size
|
||||
memoryAlloc :: CurrentPlugin -> Word64 -> IO Word64
|
||||
memoryAlloc = extism_current_plugin_memory_alloc
|
||||
|
||||
-- | Get the length of a handle, returns 0 if the handle is invalid
|
||||
memoryLength :: CurrentPlugin -> Word64 -> IO Word64
|
||||
memoryLength = extism_current_plugin_memory_length
|
||||
|
||||
-- | Free allocated memory
|
||||
memoryFree :: CurrentPlugin -> Word64 -> IO ()
|
||||
memoryFree = extism_current_plugin_memory_free
|
||||
|
||||
-- | Access a pointer to the entire memory region
|
||||
memory :: CurrentPlugin -> IO (Ptr Word8)
|
||||
memory = extism_current_plugin_memory
|
||||
|
||||
-- | Access a pointer the a specific offset in memory
|
||||
memoryOffset :: CurrentPlugin -> Word64 -> IO (Ptr Word8)
|
||||
memoryOffset plugin offs = do
|
||||
x <- extism_current_plugin_memory plugin
|
||||
return $ plusPtr x (fromIntegral offs)
|
||||
|
||||
-- | Access the data associated with a handle as a 'ByteString'
|
||||
memoryBytes :: CurrentPlugin -> Word64 -> IO B.ByteString
|
||||
memoryBytes plugin offs = do
|
||||
ptr <- memoryOffset plugin offs
|
||||
len <- memoryLength plugin offs
|
||||
arr <- peekArray (fromIntegral len) ptr
|
||||
return $ B.pack arr
|
||||
|
||||
-- | Allocate memory and copy an existing 'ByteString' into it
|
||||
allocBytes :: CurrentPlugin -> B.ByteString -> IO Word64
|
||||
allocBytes plugin s = do
|
||||
let length = B.length s
|
||||
offs <- memoryAlloc plugin (fromIntegral length)
|
||||
ptr <- memoryOffset plugin offs
|
||||
pokeArray ptr (B.unpack s)
|
||||
return offs
|
||||
|
||||
@@ -1,66 +1,53 @@
|
||||
import Test.HUnit
|
||||
import Extism
|
||||
import Extism.Manifest
|
||||
import Extism.CurrentPlugin
|
||||
|
||||
|
||||
unwrap (Right x) = return x
|
||||
unwrap (Left (ExtismError msg)) =
|
||||
assertFailure msg
|
||||
|
||||
defaultManifest = manifest [wasmFile "../../wasm/code.wasm"]
|
||||
hostFunctionManifest = manifest [wasmFile "../../wasm/code-functions.wasm"]
|
||||
defaultManifest = manifest [wasmFile "test/code.wasm"]
|
||||
|
||||
initPlugin :: Maybe Context -> IO Plugin
|
||||
initPlugin Nothing =
|
||||
Extism.createPluginFromManifest defaultManifest [] False >>= unwrap
|
||||
initPlugin (Just ctx) =
|
||||
Extism.pluginFromManifest ctx defaultManifest [] False >>= unwrap
|
||||
initPlugin :: Context -> IO Plugin
|
||||
initPlugin context =
|
||||
Extism.pluginFromManifest context defaultManifest False >>= unwrap
|
||||
|
||||
pluginFunctionExists = do
|
||||
p <- initPlugin Nothing
|
||||
exists <- functionExists p "count_vowels"
|
||||
assertBool "function exists" exists
|
||||
exists' <- functionExists p "function_doesnt_exist"
|
||||
assertBool "function doesn't exist" (not exists')
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
exists <- functionExists p "count_vowels"
|
||||
assertBool "function exists" exists
|
||||
exists' <- functionExists p "function_doesnt_exist"
|
||||
assertBool "function doesn't exist" (not exists'))
|
||||
|
||||
checkCallResult p = do
|
||||
res <- call p "count_vowels" (toByteString "this is a test") >>= unwrap
|
||||
assertEqual "count vowels output" "{\"count\": 4}" (fromByteString res)
|
||||
res <- call p "count_vowels" (toByteString "this is a test") >>= unwrap
|
||||
assertEqual "count vowels output" "{\"count\": 4}" (fromByteString res)
|
||||
|
||||
pluginCall = do
|
||||
p <- initPlugin Nothing
|
||||
checkCallResult p
|
||||
|
||||
|
||||
hello plugin params () = do
|
||||
putStrLn "Hello from Haskell!"
|
||||
offs <- allocBytes plugin (toByteString "{\"count\": 999}")
|
||||
return [toI64 offs]
|
||||
|
||||
pluginCallHostFunction = do
|
||||
p <- Extism.createPluginFromManifest hostFunctionManifest [] False >>= unwrap
|
||||
res <- call p "count_vowels" (toByteString "this is a test") >>= unwrap
|
||||
assertEqual "count vowels output" "{\"count\": 999}" (fromByteString res)
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
checkCallResult p)
|
||||
|
||||
pluginMultiple = do
|
||||
withContext(\ctx -> do
|
||||
p <- initPlugin (Just ctx)
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin ctx
|
||||
checkCallResult p
|
||||
q <- initPlugin (Just ctx)
|
||||
r <- initPlugin (Just ctx)
|
||||
q <- initPlugin ctx
|
||||
r <- initPlugin ctx
|
||||
checkCallResult q
|
||||
checkCallResult r)
|
||||
|
||||
pluginUpdate = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin (Just ctx)
|
||||
updateManifest p defaultManifest [] True >>= unwrap
|
||||
p <- initPlugin ctx
|
||||
updateManifest p defaultManifest True >>= unwrap
|
||||
checkCallResult p)
|
||||
|
||||
pluginConfig = do
|
||||
withContext (\ctx -> do
|
||||
p <- initPlugin (Just ctx)
|
||||
p <- initPlugin ctx
|
||||
b <- setConfig p [("a", Just "1"), ("b", Just "2"), ("c", Just "3"), ("d", Nothing)]
|
||||
assertBool "set config" b)
|
||||
|
||||
@@ -75,7 +62,6 @@ main = do
|
||||
[
|
||||
t "Plugin.FunctionExists" pluginFunctionExists
|
||||
, t "Plugin.Call" pluginCall
|
||||
, t "Plugin.CallHostFunction" pluginCallHostFunction
|
||||
, t "Plugin.Multiple" pluginMultiple
|
||||
, t "Plugin.Update" pluginUpdate
|
||||
, t "Plugin.Config" pluginConfig
|
||||
|
||||
BIN
haskell/test/code.wasm
Executable file
BIN
haskell/test/code.wasm
Executable file
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<groupId>org.extism.sdk</groupId>
|
||||
<artifactId>extism</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.4.0</version>
|
||||
<version>0.1.0</version>
|
||||
<name>extism</name>
|
||||
<url>https://github.com/extism/extism</url>
|
||||
<description>Java-SDK for Extism to use webassembly from Java</description>
|
||||
@@ -36,7 +36,7 @@
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/extism/extism.git</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/extism/extism.git</developerConnection>
|
||||
<url>https://github.com/extism/extism/tree/main/java</url>
|
||||
<url>https://github.com/extism/extism/java</url>
|
||||
<tag>main</tag>
|
||||
</scm>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</issueManagement>
|
||||
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependencies -->
|
||||
@@ -74,9 +74,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<additionalJOption>-Xdoclint:none</additionalJOption>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadoc</id>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* CancelHandle is used to cancel a running Plugin
|
||||
*/
|
||||
public class CancelHandle {
|
||||
private Pointer handle;
|
||||
|
||||
public CancelHandle(Pointer handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel execution of the Plugin associated with the CancelHandle
|
||||
*/
|
||||
boolean cancel() {
|
||||
return LibExtism.INSTANCE.extism_plugin_cancel(this.handle);
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,10 @@ public class Context implements AutoCloseable {
|
||||
*
|
||||
* @param manifest The manifest for the plugin
|
||||
* @param withWASI Set to true to enable WASI
|
||||
* @param functions List of Host functions
|
||||
* @return the plugin instance
|
||||
*/
|
||||
public Plugin newPlugin(Manifest manifest, boolean withWASI, HostFunction[] functions) {
|
||||
return new Plugin(this, manifest, withWASI, functions);
|
||||
public Plugin newPlugin(Manifest manifest, boolean withWASI) {
|
||||
return new Plugin(this, manifest, withWASI);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ public class Extism {
|
||||
*/
|
||||
public static String invokeFunction(Manifest manifest, String function, String input) throws ExtismException {
|
||||
try (var ctx = new Context()) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false, null)) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false)) {
|
||||
return plugin.call(function, input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ExtismCurrentPlugin {
|
||||
public Pointer pointer;
|
||||
|
||||
public ExtismCurrentPlugin(Pointer pointer) {
|
||||
this.pointer = pointer;
|
||||
}
|
||||
|
||||
public Pointer memory() {
|
||||
return LibExtism.INSTANCE.extism_current_plugin_memory(this.pointer);
|
||||
}
|
||||
|
||||
public int alloc(int n) {
|
||||
return LibExtism.INSTANCE.extism_current_plugin_memory_alloc(this.pointer, n);
|
||||
}
|
||||
|
||||
public void free(long offset) {
|
||||
LibExtism.INSTANCE.extism_current_plugin_memory_free(this.pointer, offset);
|
||||
}
|
||||
|
||||
public long memoryLength(long offset) {
|
||||
return LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string from a host function
|
||||
* @param output - The output to set
|
||||
* @param s - The string to return
|
||||
*/
|
||||
public void returnString(LibExtism.ExtismVal output, String s) {
|
||||
returnBytes(output, s.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes from a host function
|
||||
* @param output - The output to set
|
||||
* @param b - The buffer to return
|
||||
*/
|
||||
public void returnBytes(LibExtism.ExtismVal output, byte[] b) {
|
||||
int offs = this.alloc(b.length);
|
||||
Pointer ptr = this.memory();
|
||||
ptr.write(offs, b, 0, b.length);
|
||||
output.v.i64 = offs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bytes from host function parameter
|
||||
* @param input - The input to read
|
||||
*/
|
||||
public byte[] inputBytes(LibExtism.ExtismVal input) {
|
||||
switch (input.t) {
|
||||
case 0:
|
||||
return this.memory()
|
||||
.getByteArray(input.v.i32,
|
||||
LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, input.v.i32));
|
||||
case 1:
|
||||
return this.memory()
|
||||
.getByteArray(input.v.i64,
|
||||
LibExtism.INSTANCE.extism_current_plugin_memory_length(this.pointer, input.v.i64));
|
||||
default:
|
||||
throw new ExtismException("inputBytes error: ExtismValType " + LibExtism.ExtismValType.values()[input.t] + " not implemtented");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get string from host function parameter
|
||||
* @param input - The input to read
|
||||
*/
|
||||
public String inputString(LibExtism.ExtismVal input) {
|
||||
return new String(this.inputBytes(input));
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ExtismFunction<T extends HostUserData> {
|
||||
void invoke(
|
||||
ExtismCurrentPlugin plugin,
|
||||
LibExtism.ExtismVal[] params,
|
||||
LibExtism.ExtismVal[] returns,
|
||||
Optional<T> data
|
||||
);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HostFunction<T extends HostUserData> {
|
||||
|
||||
private final LibExtism.InternalExtismFunction callback;
|
||||
|
||||
public final Pointer pointer;
|
||||
|
||||
public final String name;
|
||||
|
||||
public final LibExtism.ExtismValType[] params;
|
||||
|
||||
public final LibExtism.ExtismValType[] returns;
|
||||
|
||||
public final Optional<T> userData;
|
||||
|
||||
public HostFunction(String name, LibExtism.ExtismValType[] params, LibExtism.ExtismValType[] returns, ExtismFunction f, Optional<T> userData) {
|
||||
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
this.returns = returns;
|
||||
this.userData = userData;
|
||||
this.callback = (Pointer currentPlugin,
|
||||
LibExtism.ExtismVal inputs,
|
||||
int nInputs,
|
||||
LibExtism.ExtismVal outs,
|
||||
int nOutputs,
|
||||
Pointer data) -> {
|
||||
|
||||
LibExtism.ExtismVal[] outputs = (LibExtism.ExtismVal []) outs.toArray(nOutputs);
|
||||
|
||||
f.invoke(
|
||||
new ExtismCurrentPlugin(currentPlugin),
|
||||
(LibExtism.ExtismVal []) inputs.toArray(nInputs),
|
||||
outputs,
|
||||
userData
|
||||
);
|
||||
|
||||
for (LibExtism.ExtismVal output : outputs) {
|
||||
convertOutput(output, output);
|
||||
}
|
||||
};
|
||||
|
||||
this.pointer = LibExtism.INSTANCE.extism_function_new(
|
||||
this.name,
|
||||
Arrays.stream(this.params).mapToInt(r -> r.v).toArray(),
|
||||
this.params.length,
|
||||
Arrays.stream(this.returns).mapToInt(r -> r.v).toArray(),
|
||||
this.returns.length,
|
||||
this.callback,
|
||||
userData.map(PointerType::getPointer).orElse(null),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
void convertOutput(LibExtism.ExtismVal original, LibExtism.ExtismVal fromHostFunction) {
|
||||
if (fromHostFunction.t != original.t)
|
||||
throw new ExtismException(String.format("Output type mismatch, got %d but expected %d", fromHostFunction.t, original.t));
|
||||
|
||||
if (fromHostFunction.t == LibExtism.ExtismValType.I32.v) {
|
||||
original.v.setType(Integer.TYPE);
|
||||
original.v.i32 = fromHostFunction.v.i32;
|
||||
} else if (fromHostFunction.t == LibExtism.ExtismValType.I64.v) {
|
||||
original.v.setType(Long.TYPE);
|
||||
original.v.i64 = fromHostFunction.v.i64;
|
||||
} else if (fromHostFunction.t == LibExtism.ExtismValType.F32.v) {
|
||||
original.v.setType(Float.TYPE);
|
||||
original.v.f32 = fromHostFunction.v.f32;
|
||||
} else if (fromHostFunction.t == LibExtism.ExtismValType.F64.v) {
|
||||
original.v.setType(Double.TYPE);
|
||||
original.v.f64 = fromHostFunction.v.f64;
|
||||
} else
|
||||
throw new ExtismException(String.format("Unsupported return type: %s", original.t));
|
||||
}
|
||||
|
||||
public void setNamespace(String name) {
|
||||
if (this.pointer != null) {
|
||||
LibExtism.INSTANCE.extism_function_set_namespace(this.pointer, name);
|
||||
}
|
||||
}
|
||||
|
||||
HostFunction withNamespace(String name) {
|
||||
this.setNamespace(name);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.PointerType;
|
||||
|
||||
public class HostUserData extends PointerType {
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.*;
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
/**
|
||||
* Wrapper around the Extism library.
|
||||
@@ -13,80 +15,7 @@ public interface LibExtism extends Library {
|
||||
*/
|
||||
LibExtism INSTANCE = Native.load("extism", LibExtism.class);
|
||||
|
||||
interface InternalExtismFunction extends Callback {
|
||||
void invoke(
|
||||
Pointer currentPlugin,
|
||||
ExtismVal inputs,
|
||||
int nInputs,
|
||||
ExtismVal outputs,
|
||||
int nOutputs,
|
||||
Pointer data
|
||||
);
|
||||
}
|
||||
|
||||
@Structure.FieldOrder({"t", "v"})
|
||||
class ExtismVal extends Structure {
|
||||
public int t;
|
||||
public ExtismValUnion v;
|
||||
}
|
||||
|
||||
class ExtismValUnion extends Union {
|
||||
public int i32;
|
||||
public long i64;
|
||||
public float f32;
|
||||
public double f64;
|
||||
}
|
||||
|
||||
enum ExtismValType {
|
||||
I32(0),
|
||||
I64(1),
|
||||
F32(2),
|
||||
F64(3),
|
||||
V128(4),
|
||||
FuncRef(5),
|
||||
ExternRef(6);
|
||||
|
||||
public final int v;
|
||||
|
||||
ExtismValType(int value) {
|
||||
this.v = value;
|
||||
}
|
||||
}
|
||||
|
||||
Pointer extism_function_new(String name,
|
||||
int[] inputs,
|
||||
int nInputs,
|
||||
int[] outputs,
|
||||
int nOutputs,
|
||||
InternalExtismFunction func,
|
||||
Pointer userData,
|
||||
Pointer freeUserData);
|
||||
|
||||
/**
|
||||
* Get the length of an allocated block
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
int extism_current_plugin_memory_length(Pointer plugin, long n);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the memory of the currently running plugin
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
Pointer extism_current_plugin_memory(Pointer plugin);
|
||||
|
||||
/**
|
||||
* Allocate a memory block in the currently running plugin
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
int extism_current_plugin_memory_alloc(Pointer plugin, long n);
|
||||
|
||||
/**
|
||||
* Free an allocated memory block
|
||||
* NOTE: this should only be called from host functions.
|
||||
*/
|
||||
void extism_current_plugin_memory_free(Pointer plugin, long ptr);
|
||||
|
||||
/**
|
||||
* Create a new context
|
||||
*/
|
||||
Pointer extism_context_new();
|
||||
@@ -127,18 +56,27 @@ 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(Pointer contextPointer, byte[] wasm, long wasmSize, Pointer[] functions, int nFunctions, boolean withWASI);
|
||||
int extism_plugin_new(long contextPointer, byte[] wasm, long wasmSize, boolean withWASI);
|
||||
|
||||
/**
|
||||
* Returns the Extism version string
|
||||
*/
|
||||
String extism_version();
|
||||
|
||||
/**
|
||||
* Create a new plugin.
|
||||
*
|
||||
* @param contextPointer pointer to the {@link Context}.
|
||||
* @param wasm is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
* @param length the length of the `wasm` parameter
|
||||
* @param withWASI enables/disables WASI
|
||||
* @return id of the plugin or {@literal -1} in case of error
|
||||
* @see #extism_plugin_new(long, byte[], long, boolean)
|
||||
*/
|
||||
int extism_plugin_new(Pointer contextPointer, byte[] wasm, int length, boolean withWASI);
|
||||
|
||||
/**
|
||||
* Calls a function from the @{@link Plugin} at the given {@code pluginIndex}.
|
||||
@@ -172,19 +110,17 @@ public interface LibExtism extends Library {
|
||||
|
||||
/**
|
||||
* Update a plugin, keeping the existing ID.
|
||||
* Similar to {@link #extism_plugin_new(Pointer, byte[], long, Pointer[], int, boolean)} but takes an {@code pluginIndex} argument to specify which plugin to update.
|
||||
* Similar to {@link #extism_plugin_new(long, byte[], long, boolean)} but takes an {@code pluginIndex} argument to specify which plugin to update.
|
||||
* Note: Memory for this plugin will be reset upon update.
|
||||
*
|
||||
* @param contextPointer
|
||||
* @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, Pointer[] functions, int nFunctions, boolean withWASI);
|
||||
boolean extism_plugin_update(Pointer contextPointer, int pluginIndex, byte[] wasm, int length, boolean withWASI);
|
||||
|
||||
/**
|
||||
* Remove a plugin from the registry and free associated memory.
|
||||
@@ -204,7 +140,4 @@ public interface LibExtism extends Library {
|
||||
* @return {@literal true} if update was successful
|
||||
*/
|
||||
boolean extism_plugin_config(Pointer contextPointer, int pluginIndex, byte[] json, int jsonLength);
|
||||
Pointer extism_plugin_cancel_handle(Pointer contextPointer, int n);
|
||||
boolean extism_plugin_cancel(Pointer contextPointer);
|
||||
void extism_function_set_namespace(Pointer p, String name);
|
||||
}
|
||||
|
||||
@@ -28,27 +28,15 @@ public class Plugin implements AutoCloseable {
|
||||
*
|
||||
* @param context The context to manage the plugin
|
||||
* @param manifestBytes The manifest for the plugin
|
||||
* @param functions The Host functions for th eplugin
|
||||
* @param withWASI Set to true to enable WASI
|
||||
*/
|
||||
public Plugin(Context context, byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
|
||||
public Plugin(Context context, byte[] manifestBytes, boolean withWASI) {
|
||||
|
||||
Objects.requireNonNull(context, "context");
|
||||
Objects.requireNonNull(manifestBytes, "manifestBytes");
|
||||
|
||||
Pointer[] ptrArr = new Pointer[functions == null ? 0 : functions.length];
|
||||
|
||||
if (functions != null)
|
||||
for (int i = 0; i < functions.length; i++) {
|
||||
ptrArr[i] = functions[i].pointer;
|
||||
}
|
||||
|
||||
Pointer contextPointer = context.getPointer();
|
||||
|
||||
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length,
|
||||
ptrArr,
|
||||
functions == null ? 0 : functions.length,
|
||||
withWASI);
|
||||
int index = LibExtism.INSTANCE.extism_plugin_new(contextPointer, manifestBytes, manifestBytes.length, withWASI);
|
||||
if (index == -1) {
|
||||
String error = context.error(this);
|
||||
throw new ExtismException(error);
|
||||
@@ -58,18 +46,8 @@ public class Plugin implements AutoCloseable {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public Plugin(Context context, Manifest manifest, boolean withWASI, HostFunction[] functions) {
|
||||
this(context, serialize(manifest), withWASI, functions);
|
||||
}
|
||||
|
||||
|
||||
public Plugin(byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
|
||||
this(new Context(), manifestBytes, withWASI, functions);
|
||||
}
|
||||
|
||||
|
||||
public Plugin(Manifest manifest, boolean withWASI, HostFunction[] functions) {
|
||||
this(new Context(), serialize(manifest), withWASI, functions);
|
||||
public Plugin(Context context, Manifest manifest, boolean withWASI) {
|
||||
this(context, serialize(manifest), withWASI);
|
||||
}
|
||||
|
||||
private static byte[] serialize(Manifest manifest) {
|
||||
@@ -134,8 +112,8 @@ public class Plugin implements AutoCloseable {
|
||||
* @param withWASI Set to true to enable WASI
|
||||
* @return {@literal true} if update was successful
|
||||
*/
|
||||
public boolean update(Manifest manifest, boolean withWASI, HostFunction[] functions) {
|
||||
return update(serialize(manifest), withWASI, functions);
|
||||
public boolean update(Manifest manifest, boolean withWASI) {
|
||||
return update(serialize(manifest), withWASI);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,19 +123,9 @@ public class Plugin implements AutoCloseable {
|
||||
* @param withWASI Set to true to enable WASI
|
||||
* @return {@literal true} if update was successful
|
||||
*/
|
||||
public boolean update(byte[] manifestBytes, boolean withWASI, HostFunction[] functions) {
|
||||
public boolean update(byte[] manifestBytes, boolean withWASI) {
|
||||
Objects.requireNonNull(manifestBytes, "manifestBytes");
|
||||
Pointer[] ptrArr = new Pointer[functions == null ? 0 : functions.length];
|
||||
|
||||
if (functions != null)
|
||||
for (int i = 0; i < functions.length; i++) {
|
||||
ptrArr[i] = functions[i].pointer;
|
||||
}
|
||||
|
||||
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length,
|
||||
ptrArr,
|
||||
functions == null ? 0 : functions.length,
|
||||
withWASI);
|
||||
return LibExtism.INSTANCE.extism_plugin_update(context.getPointer(), index, manifestBytes, manifestBytes.length, withWASI);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,15 +165,4 @@ public class Plugin implements AutoCloseable {
|
||||
public void close() {
|
||||
free();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new `CancelHandle`, which can be used to cancel a running Plugin
|
||||
*/
|
||||
public CancelHandle cancelHandle() {
|
||||
if (this.context.getPointer() == null) {
|
||||
throw new ExtismException("No Context set");
|
||||
}
|
||||
Pointer handle = LibExtism.INSTANCE.extism_plugin_cancel_handle(this.context.getPointer(), this.index);
|
||||
return new CancelHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,11 @@ public class Manifest {
|
||||
@SerializedName("allowed_hosts")
|
||||
private final List<String> allowedHosts;
|
||||
|
||||
@SerializedName("allowed_paths")
|
||||
private final Map<String, String> allowedPaths;
|
||||
|
||||
@SerializedName("config")
|
||||
private final Map<String, String> config;
|
||||
|
||||
public Manifest() {
|
||||
this(new ArrayList<>(), null, null, null, null);
|
||||
this(new ArrayList<>(), null, null, null);
|
||||
}
|
||||
|
||||
public Manifest(WasmSource source) {
|
||||
@@ -35,27 +32,22 @@ public class Manifest {
|
||||
}
|
||||
|
||||
public Manifest(List<WasmSource> sources) {
|
||||
this(sources, null, null, null, null);
|
||||
this(sources, null, null, null);
|
||||
}
|
||||
|
||||
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions) {
|
||||
this(sources, memoryOptions, null, null, null);
|
||||
this(sources, memoryOptions, null, null);
|
||||
}
|
||||
|
||||
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config) {
|
||||
this(sources, memoryOptions, config, null, null);
|
||||
this(sources, memoryOptions, config, null);
|
||||
}
|
||||
|
||||
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config, List<String> allowedHosts) {
|
||||
this(sources, memoryOptions, config, allowedHosts, null);
|
||||
}
|
||||
|
||||
public Manifest(List<WasmSource> sources, MemoryOptions memoryOptions, Map<String, String> config, List<String> allowedHosts, Map<String, String> allowedPaths) {
|
||||
this.sources = sources;
|
||||
this.memoryOptions = memoryOptions;
|
||||
this.config = config;
|
||||
this.allowedHosts = allowedHosts;
|
||||
this.allowedPaths = allowedPaths;
|
||||
}
|
||||
|
||||
public void addSource(WasmSource source) {
|
||||
@@ -83,11 +75,4 @@ public class Manifest {
|
||||
}
|
||||
return Collections.unmodifiableList(allowedHosts);
|
||||
}
|
||||
|
||||
public Map<String, String> getAllowedPaths() {
|
||||
if (allowedPaths == null || allowedPaths.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return Collections.unmodifiableMap(allowedPaths);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,6 @@ package org.extism.sdk.manifest;
|
||||
import java.util.Map;
|
||||
|
||||
// FIXME remove this and related stuff if not supported in java-sdk
|
||||
public class ManifestHttpRequest {
|
||||
public record ManifestHttpRequest(String url, Map<String, String> header, String method) {
|
||||
}
|
||||
|
||||
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,11 +8,5 @@ import com.google.gson.annotations.SerializedName;
|
||||
*
|
||||
* @param max Max number of pages.
|
||||
*/
|
||||
public class MemoryOptions {
|
||||
@SerializedName("max")
|
||||
private final Integer max;
|
||||
|
||||
public MemoryOptions(Integer max) {
|
||||
this.max = max;
|
||||
}
|
||||
public record MemoryOptions(@SerializedName("max") Integer max) {
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.extism.sdk.support;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import org.extism.sdk.manifest.Manifest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
public class JsonSerde {
|
||||
@@ -19,7 +23,7 @@ public class JsonSerde {
|
||||
GSON = new GsonBuilder() //
|
||||
.disableHtmlEscaping() //
|
||||
// needed to convert the byte[] to a base64 encoded String
|
||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayAdapter()) //
|
||||
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()) //
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) //
|
||||
.setPrettyPrinting() //
|
||||
.create();
|
||||
@@ -29,28 +33,14 @@ public class JsonSerde {
|
||||
return GSON.toJson(manifest);
|
||||
}
|
||||
|
||||
private static class ByteArrayAdapter extends TypeAdapter<byte[]> {
|
||||
private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, byte[] byteValue) throws IOException {
|
||||
out.value(new String(Base64.getEncoder().encode(byteValue)));
|
||||
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return Base64.getDecoder().decode(json.getAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(JsonReader in) {
|
||||
try {
|
||||
if (in.peek() == JsonToken.NULL) {
|
||||
in.nextNull();
|
||||
return new byte[]{};
|
||||
}
|
||||
String byteValue = in.nextString();
|
||||
if (byteValue != null) {
|
||||
return Base64.getDecoder().decode(byteValue);
|
||||
}
|
||||
return new byte[]{};
|
||||
} catch (Exception e) {
|
||||
throw new JsonParseException(e);
|
||||
}
|
||||
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(Base64.getEncoder().withoutPadding().encodeToString(src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,10 @@ 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 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;
|
||||
}
|
||||
public record ByteArrayWasmSource(String name, byte[] data, String hash) implements WasmSource {
|
||||
}
|
||||
|
||||
@@ -2,39 +2,11 @@ package org.extism.sdk.wasm;
|
||||
|
||||
/**
|
||||
* WASM Source represented by a file referenced by a path.
|
||||
*
|
||||
* @param name
|
||||
* @param path
|
||||
* @param hash
|
||||
*/
|
||||
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;
|
||||
}
|
||||
public record PathWasmSource(String name, String path, String hash) implements WasmSource {
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.extism.sdk.support.JsonSerde;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.extism.sdk.TestWasmSources.CODE;
|
||||
@@ -17,16 +16,13 @@ public class ManifestTests {
|
||||
|
||||
@Test
|
||||
public void shouldSerializeManifestWithWasmSourceToJson() {
|
||||
var paths = new HashMap<String, String>();
|
||||
paths.put("/tmp/foo", "/tmp/extism-plugins/foo");
|
||||
var manifest = new Manifest(List.of(CODE.pathWasmSource()), null, null, null, paths);
|
||||
|
||||
var manifest = new Manifest(CODE.pathWasmSource());
|
||||
var json = JsonSerde.toJson(manifest);
|
||||
assertNotNull(json);
|
||||
|
||||
assertJson(json).at("/wasm").isArray();
|
||||
assertJson(json).at("/wasm").hasSize(1);
|
||||
assertJson(json).at("/allowed_paths").isObject();
|
||||
assertJson(json).at("/allowed_paths").hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package org.extism.sdk;
|
||||
|
||||
import com.sun.jna.Pointer;
|
||||
import org.extism.sdk.manifest.Manifest;
|
||||
import org.extism.sdk.manifest.MemoryOptions;
|
||||
import org.extism.sdk.wasm.WasmSourceResolver;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.extism.sdk.TestWasmSources.CODE;
|
||||
@@ -20,10 +20,10 @@ public class PluginTests {
|
||||
|
||||
@Test
|
||||
public void shouldInvokeFunctionWithMemoryOptions() {
|
||||
//FIXME check whether memory options are effective
|
||||
var manifest = new Manifest(List.of(CODE.pathWasmSource()), new MemoryOptions(0));
|
||||
assertThrows(ExtismException.class, () -> {
|
||||
Extism.invokeFunction(manifest, "count_vowels", "Hello World");
|
||||
});
|
||||
var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
|
||||
assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,12 +42,14 @@ public class PluginTests {
|
||||
assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInvokeFunctionFromByteArrayWasmSource() {
|
||||
var manifest = new Manifest(CODE.byteArrayWasmSource());
|
||||
var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
|
||||
assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
}
|
||||
// TODO This test breaks on CI with error:
|
||||
// data did not match any variant of untagged enum Wasm at line 8 column 3
|
||||
// @Test
|
||||
// public void shouldInvokeFunctionFromByteArrayWasmSource() {
|
||||
// var manifest = new Manifest(CODE.byteArrayWasmSource());
|
||||
// var output = Extism.invokeFunction(manifest, "count_vowels", "Hello World");
|
||||
// assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void shouldFailToInvokeUnknownFunction() {
|
||||
@@ -78,7 +80,7 @@ public class PluginTests {
|
||||
var input = "Hello World";
|
||||
|
||||
try (var ctx = new Context()) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false, null)) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false)) {
|
||||
var output = plugin.call(functionName, input);
|
||||
assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
}
|
||||
@@ -92,7 +94,7 @@ public class PluginTests {
|
||||
var input = "Hello World";
|
||||
|
||||
try (var ctx = new Context()) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false, null)) {
|
||||
try (var plugin = ctx.newPlugin(manifest, false)) {
|
||||
var output = plugin.call(functionName, input);
|
||||
assertThat(output).isEqualTo("{\"count\": 3}");
|
||||
|
||||
@@ -102,118 +104,4 @@ public class PluginTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAllowInvokeHostFunctionFromPDK() {
|
||||
var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
|
||||
var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
|
||||
|
||||
class MyUserData extends HostUserData {
|
||||
private String data1;
|
||||
private int data2;
|
||||
|
||||
public MyUserData(String data1, int data2) {
|
||||
super();
|
||||
this.data1 = data1;
|
||||
this.data2 = data2;
|
||||
}
|
||||
}
|
||||
|
||||
ExtismFunction helloWorldFunction = (ExtismFunction<MyUserData>) (plugin, params, returns, data) -> {
|
||||
System.out.println("Hello from Java Host Function!");
|
||||
System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
|
||||
|
||||
int offs = plugin.alloc(4);
|
||||
Pointer mem = plugin.memory();
|
||||
mem.write(offs, "test".getBytes(), 0, 4);
|
||||
returns[0].v.i64 = offs;
|
||||
|
||||
data.ifPresent(d -> System.out.println(String.format("Host user data, %s, %d", d.data1, d.data2)));
|
||||
};
|
||||
|
||||
HostFunction helloWorld = new HostFunction<>(
|
||||
"hello_world",
|
||||
parametersTypes,
|
||||
resultsTypes,
|
||||
helloWorldFunction,
|
||||
Optional.of(new MyUserData("test", 2))
|
||||
);
|
||||
|
||||
HostFunction[] functions = {helloWorld};
|
||||
|
||||
try (var ctx = new Context()) {
|
||||
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
|
||||
String functionName = "count_vowels";
|
||||
|
||||
try (var plugin = ctx.newPlugin(manifest, true, functions)) {
|
||||
var output = plugin.call(functionName, "this is a test");
|
||||
assertThat(output).isEqualTo("test");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAllowInvokeHostFunctionWithoutUserData() {
|
||||
|
||||
var parametersTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
|
||||
var resultsTypes = new LibExtism.ExtismValType[]{LibExtism.ExtismValType.I64};
|
||||
|
||||
ExtismFunction helloWorldFunction = (plugin, params, returns, data) -> {
|
||||
System.out.println("Hello from Java Host Function!");
|
||||
System.out.println(String.format("Input string received from plugin, %s", plugin.inputString(params[0])));
|
||||
|
||||
int offs = plugin.alloc(4);
|
||||
Pointer mem = plugin.memory();
|
||||
mem.write(offs, "test".getBytes(), 0, 4);
|
||||
returns[0].v.i64 = offs;
|
||||
|
||||
assertThat(data.isEmpty());
|
||||
};
|
||||
|
||||
HostFunction f = new HostFunction<>(
|
||||
"hello_world",
|
||||
parametersTypes,
|
||||
resultsTypes,
|
||||
helloWorldFunction,
|
||||
Optional.empty()
|
||||
)
|
||||
.withNamespace("env");
|
||||
|
||||
HostFunction g = new HostFunction<>(
|
||||
"hello_world",
|
||||
parametersTypes,
|
||||
resultsTypes,
|
||||
helloWorldFunction,
|
||||
Optional.empty()
|
||||
)
|
||||
.withNamespace("test");
|
||||
|
||||
HostFunction[] functions = {f,g};
|
||||
|
||||
try (var ctx = new Context()) {
|
||||
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
|
||||
String functionName = "count_vowels";
|
||||
|
||||
try (var plugin = ctx.newPlugin(manifest, true, functions)) {
|
||||
var output = plugin.call(functionName, "this is a test");
|
||||
assertThat(output).isEqualTo("test");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldFailToInvokeUnknownHostFunction() {
|
||||
try (var ctx = new Context()) {
|
||||
Manifest manifest = new Manifest(Arrays.asList(CODE.pathWasmFunctionsSource()));
|
||||
String functionName = "count_vowels";
|
||||
|
||||
try {
|
||||
var plugin = ctx.newPlugin(manifest, true, null);
|
||||
plugin.call(functionName, "this is a test");
|
||||
} catch (ExtismException e) {
|
||||
assertThat(e.getMessage()).contains("unknown import: `env::hello_world` has not been defined");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,28 +16,19 @@ public enum TestWasmSources {
|
||||
public Path getWasmFilePath() {
|
||||
return Paths.get(WASM_LOCATION, "code.wasm");
|
||||
}
|
||||
public Path getWasmFunctionsFilePath() {
|
||||
return Paths.get(WASM_LOCATION, "code-functions.wasm");
|
||||
}
|
||||
};
|
||||
|
||||
public static final String WASM_LOCATION = "src/test/resources";
|
||||
|
||||
public abstract Path getWasmFilePath();
|
||||
|
||||
public abstract Path getWasmFunctionsFilePath();
|
||||
|
||||
public PathWasmSource pathWasmSource() {
|
||||
return resolvePathWasmSource(getWasmFilePath());
|
||||
}
|
||||
|
||||
public PathWasmSource pathWasmFunctionsSource() {
|
||||
return resolvePathWasmSource(getWasmFunctionsFilePath());
|
||||
}
|
||||
|
||||
public ByteArrayWasmSource byteArrayWasmSource() {
|
||||
try {
|
||||
byte[] wasmBytes = Files.readAllBytes(getWasmFilePath());
|
||||
var wasmBytes = Files.readAllBytes(getWasmFilePath());
|
||||
return new WasmSourceResolver().resolve("wasm@" + Arrays.hashCode(wasmBytes), wasmBytes);
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
|
||||
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "extism-runtime-kernel"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"."
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Extism kernel
|
||||
|
||||
The Extism kernel implements core parts of the Extism runtime in Rust compiled to WebAssembly. This code is a conceptual
|
||||
re-write of [memory.rs][] with the goal of making core parts of the Extism implementation more portable across WebAssembly
|
||||
runtimes.
|
||||
|
||||
See [lib.rs][] for more details about the implementation itself.
|
||||
|
||||
## Building
|
||||
|
||||
Because this crate is built using the `wasm32-unknown-unknown` target, it is a separate build process from the `extism-runtime` crate.
|
||||
|
||||
To build `extism-runtime.wasm`, strip it and copy it to the proper location in the `extism-runtime` tree you can run:
|
||||
|
||||
```shell
|
||||
$ sh build.sh
|
||||
```
|
||||
|
||||
[memory.rs]: https://github.com/extism/extism/blob/f4aa139eced4a74eb4a103f78222ba503e146109/runtime/src/memory.rs
|
||||
[lib.rs]: ./src/lib.rs
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cargo build --release --target wasm32-unknown-unknown --package extism-runtime-kernel --bin extism-runtime
|
||||
cp target/wasm32-unknown-unknown/release/extism-runtime.wasm .
|
||||
wasm-strip extism-runtime.wasm || :
|
||||
mv extism-runtime.wasm ../runtime/src/extism-runtime.wasm
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
pub use extism_runtime_kernel::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
core::arch::wasm32::unreachable()
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
//! # Extism kernel
|
||||
//!
|
||||
//! - Isolated memory from both host and plugin
|
||||
//! - An allocator for managing that memory
|
||||
//! - Input/output handling
|
||||
//! - Error message handling
|
||||
//! - Backward compatible `extism_*` functions
|
||||
//!
|
||||
//! ## Allocator
|
||||
//!
|
||||
//! The Extism allocator is a bump allocator that tracks the `length` of the total number of bytes
|
||||
//! available to the allocator and `position` to track how much of the data has been used. Things like memory
|
||||
//! have not really been optimized at all. When a new allocation that is larger than the remaning size is made,
|
||||
//! the allocator attempts to call `memory.grow` if that fails a `0` offset is returned, which should be interpreted
|
||||
//! as a failed allocation.
|
||||
//!
|
||||
//! ## Input/Output
|
||||
//!
|
||||
//! Input and output are just allocated blocks of memory that are marked as either input or output using
|
||||
//! the `extism_input_set` or `extism_output_set` functions. The global variables `INPUT_OFFSET` contains
|
||||
//! the offset in memory to the input data and `INPUT_LENGTH` contains the size of the input data. `OUTPUT_OFFSET`
|
||||
//! and `OUTPUT_LENGTH` are used for the output data.
|
||||
//!
|
||||
//! ## Error handling
|
||||
//!
|
||||
//! The `ERROR` global is used to track the current error message. If it is set to `0` then there is no error.
|
||||
//! The length of the error message can be retreived using `extism_length`.
|
||||
//!
|
||||
//! ## Memory offsets
|
||||
//! An offset of `0` is similar to a `NULL` pointer in C - it implies an allocation failure or memory error
|
||||
//! of some kind
|
||||
//!
|
||||
//! ## Extism functions
|
||||
//!
|
||||
//! These functions are backward compatible with the pre-kernel runtime, but a few new functions are added to
|
||||
//! give runtimes more access to the internals necesarry to load data in and out of a plugin.
|
||||
#![no_std]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
use core::sync::atomic::*;
|
||||
|
||||
pub type Pointer = u64;
|
||||
pub type Length = u64;
|
||||
|
||||
/// WebAssembly page size
|
||||
const PAGE_SIZE: usize = 65536;
|
||||
|
||||
/// Determines the amount of bytes that can be wasted by re-using a block. If more than this number is wasted by re-using
|
||||
/// a block then it will be split into two smaller blocks.
|
||||
const BLOCK_SPLIT_SIZE: usize = 128;
|
||||
|
||||
/// Offset to the input data
|
||||
static mut INPUT_OFFSET: Pointer = 0;
|
||||
|
||||
/// Length of the input data
|
||||
static mut INPUT_LENGTH: Length = 0;
|
||||
|
||||
/// Offset to the output data
|
||||
static mut OUTPUT_OFFSET: Pointer = 0;
|
||||
|
||||
/// Offset to the input data
|
||||
static mut OUTPUT_LENGTH: Length = 0;
|
||||
|
||||
/// Current error message
|
||||
static mut ERROR: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Determines if the kernel has been initialized already
|
||||
static mut INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// A pointer to the first page that will be managed by Extism, this is set during initialization
|
||||
static mut START_PAGE: usize = 0;
|
||||
|
||||
/// Provides information about the usage status of a `MemoryBlock`
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq)]
|
||||
pub enum MemoryStatus {
|
||||
/// Unused memory that is available b
|
||||
Unused = 0,
|
||||
/// In-use memory
|
||||
Active = 1,
|
||||
/// Free memory that is available for re-use
|
||||
Free = 2,
|
||||
}
|
||||
|
||||
/// A single `MemoryRoot` exists at the start of the memory to track information about the total
|
||||
/// size of the allocated memory and the position of the bump allocator.
|
||||
///
|
||||
/// The overall layout of the Extism-manged memory is organized like this:
|
||||
|
||||
/// |------|-------|---------|-------|--------------|
|
||||
/// | Root | Block | Data | Block | Data | ...
|
||||
/// |------|-------|---------|-------|--------------|
|
||||
///
|
||||
/// Where `Root` and `Block` are fixed to the size of the `MemoryRoot` and `MemoryBlock` structs. But
|
||||
/// the size of `Data` is dependent on the allocation size.
|
||||
#[repr(C)]
|
||||
pub struct MemoryRoot {
|
||||
/// Position of the bump allocator, relative to `START_PAGE`
|
||||
pub position: AtomicU64,
|
||||
/// The total size of all data allocated using this allocator
|
||||
pub length: AtomicU64,
|
||||
/// A pointer to where the blocks begin
|
||||
pub blocks: [MemoryBlock; 0],
|
||||
}
|
||||
|
||||
/// A `MemoryBlock` contains some metadata about a single allocation
|
||||
#[repr(C)]
|
||||
pub struct MemoryBlock {
|
||||
/// The usage status of the block, `Unused` or `Free` blocks can be re-used.
|
||||
pub status: AtomicU8,
|
||||
/// The total size of the allocation
|
||||
pub size: usize,
|
||||
/// The number of bytes currently being used. If this block is a fresh allocation then `size` and `used` will
|
||||
/// always be the same. If a block is re-used then these numbers may differ.
|
||||
pub used: usize,
|
||||
/// A pointer to the block data
|
||||
pub data: [u8; 0],
|
||||
}
|
||||
|
||||
/// Returns the number of pages needed for the given number of bytes
|
||||
pub fn num_pages(nbytes: u64) -> usize {
|
||||
let nbytes = nbytes as f64;
|
||||
let page = PAGE_SIZE as f64;
|
||||
((nbytes / page) + 0.5) as usize
|
||||
}
|
||||
|
||||
// Get the `MemoryRoot` at the correct offset in memory
|
||||
#[inline]
|
||||
unsafe fn memory_root() -> &'static mut MemoryRoot {
|
||||
&mut *((START_PAGE * PAGE_SIZE) as *mut MemoryRoot)
|
||||
}
|
||||
|
||||
impl MemoryRoot {
|
||||
/// Initialize or load the `MemoryRoot` from the correct position in memory
|
||||
pub unsafe fn new() -> &'static mut MemoryRoot {
|
||||
// If this fails then `INITIALIZED` is already `true` and we can just return the
|
||||
// already initialized `MemoryRoot`
|
||||
if INITIALIZED
|
||||
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
return memory_root();
|
||||
}
|
||||
|
||||
// Ensure that at least one page is allocated to store the `MemoryRoot` data
|
||||
START_PAGE = core::arch::wasm32::memory_grow(0, 1);
|
||||
if START_PAGE == usize::MAX {
|
||||
panic!("Out of memory");
|
||||
}
|
||||
|
||||
// Initialize the `MemoryRoot` length, position and data
|
||||
let root = memory_root();
|
||||
root.length.store(
|
||||
PAGE_SIZE as u64 - core::mem::size_of::<MemoryRoot>() as u64,
|
||||
Ordering::Release,
|
||||
);
|
||||
root.position.store(0, Ordering::Release);
|
||||
|
||||
// Ensure the first block is marked as `Unused`
|
||||
#[allow(clippy::size_of_in_element_count)]
|
||||
core::ptr::write_bytes(
|
||||
root.blocks.as_mut_ptr() as *mut _,
|
||||
MemoryStatus::Unused as u8,
|
||||
core::mem::size_of::<MemoryBlock>(),
|
||||
);
|
||||
root
|
||||
}
|
||||
|
||||
/// Resets the position of the allocator and zeroes out all allocations
|
||||
pub unsafe fn reset(&mut self) {
|
||||
core::ptr::write_bytes(
|
||||
self.blocks.as_mut_ptr() as *mut u8,
|
||||
0,
|
||||
self.length.load(Ordering::Acquire) as usize,
|
||||
);
|
||||
self.position.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
|
||||
// is used to avoid loading the allocators position more than once when performing an allocation.
|
||||
unsafe fn find_free_block(
|
||||
&mut self,
|
||||
length: Length,
|
||||
self_position: u64,
|
||||
) -> Option<&'static mut MemoryBlock> {
|
||||
// Get the first block
|
||||
let mut block = self.blocks.as_mut_ptr();
|
||||
|
||||
// Only loop while the block pointer is less then the current position
|
||||
while (block as u64) < self.blocks.as_ptr() as u64 + self_position {
|
||||
let b = &mut *block;
|
||||
|
||||
let status = b.status.load(Ordering::Acquire);
|
||||
|
||||
// An unused block is safe to use
|
||||
if status == MemoryStatus::Unused as u8 {
|
||||
return Some(b);
|
||||
}
|
||||
|
||||
// Re-use freed blocks when they're large enough
|
||||
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
|
||||
// Split block if there is too much excess
|
||||
if b.size - length as usize >= BLOCK_SPLIT_SIZE {
|
||||
b.size -= length as usize;
|
||||
b.used = 0;
|
||||
|
||||
let block1 = b.data.as_mut_ptr().add(b.size) as *mut MemoryBlock;
|
||||
let b1 = &mut *block1;
|
||||
b1.size = length as usize;
|
||||
b1.used = 0;
|
||||
b1.status.store(MemoryStatus::Free as u8, Ordering::Release);
|
||||
return Some(b1);
|
||||
}
|
||||
|
||||
// Otherwise return the whole block
|
||||
return Some(b);
|
||||
}
|
||||
|
||||
// Get the next block
|
||||
block = b.next_ptr();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Create a new `MemoryBlock`, when `Some(block)` is returned, `block` will contain at least enough room for `length` bytes
|
||||
/// but may be as large as `length` + `BLOCK_SPLIT_SIZE` bytes. When `None` is returned the allocation has failed.
|
||||
pub unsafe fn alloc(&mut self, length: Length) -> Option<&'static mut MemoryBlock> {
|
||||
let self_position = self.position.load(Ordering::Acquire);
|
||||
let self_length = self.length.load(Ordering::Acquire);
|
||||
let b = self.find_free_block(length, self_position);
|
||||
|
||||
// If there's a free block then re-use it
|
||||
if let Some(b) = b {
|
||||
b.used = length as usize;
|
||||
b.status
|
||||
.store(MemoryStatus::Active as u8, Ordering::Release);
|
||||
return Some(b);
|
||||
}
|
||||
|
||||
// Get the current index for a new block
|
||||
let curr = self.blocks.as_ptr() as u64 + self_position;
|
||||
|
||||
// Get the number of bytes available
|
||||
let mem_left = self_length - self_position;
|
||||
|
||||
// When the allocation is larger than the number of bytes available
|
||||
// we will need to try to grow the memory
|
||||
if length >= mem_left {
|
||||
// Calculate the number of pages needed to cover the remaining bytes
|
||||
let npages = num_pages(length);
|
||||
let x = core::arch::wasm32::memory_grow(0, npages);
|
||||
if x == usize::MAX {
|
||||
return None;
|
||||
}
|
||||
self.length
|
||||
.fetch_add(npages as u64 * PAGE_SIZE as u64, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
// Bump the position by the size of the actual data + the size of the MemoryBlock structure
|
||||
self.position.fetch_add(
|
||||
length + core::mem::size_of::<MemoryBlock>() as u64,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
// Initialize a new block at the current position
|
||||
let ptr = curr as *mut MemoryBlock;
|
||||
let block = &mut *ptr;
|
||||
block
|
||||
.status
|
||||
.store(MemoryStatus::Active as u8, Ordering::Release);
|
||||
block.size = length as usize;
|
||||
block.used = length as usize;
|
||||
Some(block)
|
||||
}
|
||||
|
||||
/// Finds the block at an offset in memory
|
||||
pub unsafe fn find_block(&mut self, offs: Pointer) -> Option<&mut MemoryBlock> {
|
||||
if offs >= self.blocks.as_ptr() as Pointer + self.length.load(Ordering::Acquire) as Pointer
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let ptr = offs - core::mem::size_of::<MemoryBlock>() as u64;
|
||||
let ptr = ptr as *mut MemoryBlock;
|
||||
Some(&mut *ptr)
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryBlock {
|
||||
/// Get a pointer to the next block
|
||||
///
|
||||
/// NOTE: This does no checking to ensure the resulting pointer is valid, the offset
|
||||
/// is calculated based on metadata provided by the current block
|
||||
#[inline]
|
||||
pub unsafe fn next_ptr(&mut self) -> *mut MemoryBlock {
|
||||
self.data
|
||||
.as_mut_ptr()
|
||||
.add(self.size + core::mem::size_of::<MemoryBlock>()) as *mut MemoryBlock
|
||||
}
|
||||
|
||||
/// Mark a block as free
|
||||
pub fn free(&mut self) {
|
||||
self.status
|
||||
.store(MemoryStatus::Free as u8, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
// Extism functions - these functions should be
|
||||
|
||||
/// Allocate a block of memory and return the offset
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_alloc(n: Length) -> Pointer {
|
||||
let region = MemoryRoot::new();
|
||||
let block = region.alloc(n);
|
||||
match block {
|
||||
Some(block) => block.data.as_mut_ptr() as Pointer,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Free allocated memory
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_free(p: Pointer) {
|
||||
let block = MemoryRoot::new().find_block(p);
|
||||
if let Some(block) = block {
|
||||
block.free();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the length of an allocated memory block
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_length(p: Pointer) -> Length {
|
||||
if p == 0 {
|
||||
return 0;
|
||||
}
|
||||
if let Some(block) = MemoryRoot::new().find_block(p) {
|
||||
block.used as Length
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a byte from Extism-managed memory
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_load_u8(p: Pointer) -> u8 {
|
||||
*(p as *mut u8)
|
||||
}
|
||||
|
||||
/// Load a u64 from Extism-managed memory
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_load_u64(p: Pointer) -> u64 {
|
||||
*(p as *mut u64)
|
||||
}
|
||||
|
||||
/// Load a byte from the input data
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_input_load_u8(p: Pointer) -> u8 {
|
||||
*((INPUT_OFFSET + p) as *mut u8)
|
||||
}
|
||||
|
||||
/// Load a u64 from the input data
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_input_load_u64(p: Pointer) -> u64 {
|
||||
*((INPUT_OFFSET + p) as *mut u64)
|
||||
}
|
||||
|
||||
/// Write a byte in Extism-managed memory
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_store_u8(p: Pointer, x: u8) {
|
||||
*(p as *mut u8) = x;
|
||||
}
|
||||
|
||||
/// Write a u64 in Extism-managed memory
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_store_u64(p: Pointer, x: u64) {
|
||||
unsafe {
|
||||
*(p as *mut u64) = x;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the range of the input data in memory
|
||||
#[no_mangle]
|
||||
pub fn extism_input_set(p: Pointer, len: Length) {
|
||||
unsafe {
|
||||
INPUT_OFFSET = p;
|
||||
INPUT_LENGTH = len;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the range of the output data in memory
|
||||
#[no_mangle]
|
||||
pub fn extism_output_set(p: Pointer, len: Length) {
|
||||
unsafe {
|
||||
OUTPUT_OFFSET = p;
|
||||
OUTPUT_LENGTH = len;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the input length
|
||||
#[no_mangle]
|
||||
pub fn extism_input_length() -> Length {
|
||||
unsafe { INPUT_LENGTH }
|
||||
}
|
||||
|
||||
/// Get the input offset in Exitsm-managed memory
|
||||
#[no_mangle]
|
||||
pub fn extism_input_offset() -> Length {
|
||||
unsafe { INPUT_OFFSET }
|
||||
}
|
||||
|
||||
/// Get the output length
|
||||
#[no_mangle]
|
||||
pub fn extism_output_length() -> Length {
|
||||
unsafe { OUTPUT_LENGTH }
|
||||
}
|
||||
|
||||
/// Get the output offset in Extism-managed memory
|
||||
#[no_mangle]
|
||||
pub fn extism_output_offset() -> Length {
|
||||
unsafe { OUTPUT_OFFSET }
|
||||
}
|
||||
|
||||
/// Reset the allocator
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_reset() {
|
||||
ERROR.store(0, Ordering::SeqCst);
|
||||
MemoryRoot::new().reset()
|
||||
}
|
||||
|
||||
/// Set the error message offset
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_error_set(ptr: Pointer) {
|
||||
ERROR.store(ptr, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Get the error message offset, if it's `0` then no error has been set
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_error_get() -> Pointer {
|
||||
ERROR.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Get the position of the allocator, this can be used as an indication of how many bytes are currently in-use
|
||||
#[no_mangle]
|
||||
pub unsafe fn extism_memory_bytes() -> Length {
|
||||
MemoryRoot::new().position.load(Ordering::Acquire)
|
||||
}
|
||||
10
libextism.pc
Normal file
10
libextism.pc
Normal file
@@ -0,0 +1,10 @@
|
||||
prefix=/usr/local
|
||||
exec_prefix=${prefix}
|
||||
includedir=${prefix}/include
|
||||
libdir=${exec_prefix}/lib
|
||||
|
||||
Name: extism
|
||||
Description: The Extism universal plug-in system.
|
||||
Version: 0.1.0
|
||||
Cflags: -I${includedir}
|
||||
Libs: -L${libdir} -lextism
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "libextism"
|
||||
version = "0.4.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["The Extism Authors", "oss@extism.org"]
|
||||
license = "BSD-3-Clause"
|
||||
@@ -20,4 +20,4 @@ default = ["http", "register-http", "register-filesystem"]
|
||||
nn = ["extism-runtime/nn"]
|
||||
register-http = ["extism-runtime/register-http"] # enables wasm to be downloaded using http
|
||||
register-filesystem = ["extism-runtime/register-filesystem"] # enables wasm to be loaded from disk
|
||||
http = ["extism-runtime/http"] # enables extism_http_request
|
||||
http = ["extism-runtime/http"] # enables extism_http_request
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "extism-manifest"
|
||||
version = "0.5.0"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["The Extism Authors", "oss@extism.org"]
|
||||
license = "BSD-3-Clause"
|
||||
|
||||
@@ -4,17 +4,15 @@ use std::path::{Path, PathBuf};
|
||||
#[deprecated]
|
||||
pub type ManifestMemory = MemoryOptions;
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryOptions {
|
||||
#[serde(alias = "max")]
|
||||
pub max_pages: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct HttpRequest {
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
@@ -43,9 +41,8 @@ impl HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WasmMetadata {
|
||||
pub name: Option<String>,
|
||||
pub hash: Option<String>,
|
||||
@@ -81,10 +78,9 @@ impl From<Vec<u8>> for Wasm {
|
||||
#[deprecated]
|
||||
pub type ManifestWasm = Wasm;
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(untagged)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum Wasm {
|
||||
File {
|
||||
path: PathBuf,
|
||||
@@ -153,9 +149,8 @@ fn base64_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::
|
||||
schema.into()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "json_schema", derive(schemars::JsonSchema))]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Manifest {
|
||||
#[serde(default)]
|
||||
pub wasm: Vec<Wasm>,
|
||||
@@ -255,19 +250,16 @@ 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 = general_purpose::STANDARD.encode(v);
|
||||
let base64 = base64::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)?;
|
||||
general_purpose::STANDARD
|
||||
.decode(base64.as_bytes())
|
||||
.map_err(serde::de::Error::custom)
|
||||
base64::decode(base64.as_bytes()).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,22 @@
|
||||
const {
|
||||
Plugin,
|
||||
HostFunction,
|
||||
ValType,
|
||||
} = require("./dist/index.js");
|
||||
const { readFileSync } = require("fs");
|
||||
const { withContext, Context } = require('./dist/index.js');
|
||||
const { readFileSync } = require('fs');
|
||||
|
||||
function f(currentPlugin, inputs, outputs, userData) {
|
||||
console.log(currentPlugin.inputString(inputs[0]));
|
||||
console.log("Hello from Javascript!");
|
||||
console.log(userData);
|
||||
outputs[0] = inputs[0];
|
||||
}
|
||||
|
||||
const hello_world = new HostFunction(
|
||||
"hello_world",
|
||||
[ValType.I64],
|
||||
[ValType.I64],
|
||||
f,
|
||||
"Hello again!",
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const functions = [hello_world];
|
||||
|
||||
const wasm = readFileSync("../wasm/code-functions.wasm");
|
||||
const p = new Plugin(wasm, true, functions);
|
||||
withContext(async function (context) {
|
||||
let wasm = readFileSync("../wasm/code.wasm");
|
||||
let p = context.plugin(wasm);
|
||||
|
||||
if (!p.functionExists("count_vowels")) {
|
||||
console.log("no function 'count_vowels' in wasm");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const buf = await p.call("count_vowels", process.argv[2] || "this is a test");
|
||||
let buf = await p.call("count_vowels", process.argv[2] || "this is a test");
|
||||
console.log(JSON.parse(buf.toString())["count"]);
|
||||
p.free();
|
||||
}
|
||||
|
||||
main();
|
||||
});
|
||||
|
||||
// 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.
|
||||
|
||||
4003
node/package-lock.json
generated
4003
node/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@extism/extism",
|
||||
"version": "0.4.0",
|
||||
"version": "0.1.0",
|
||||
"description": "Extism Host SDK for Node",
|
||||
"keywords": [
|
||||
"extism",
|
||||
@@ -21,17 +21,12 @@
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "npm run build",
|
||||
"example": "npm run build && node example.js",
|
||||
"example": "node example.js",
|
||||
"build": "tsc",
|
||||
"test": "jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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"
|
||||
"ffi-napi": "^4.0.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -39,12 +34,12 @@
|
||||
"devDependencies": {
|
||||
"@types/ffi-napi": "^4.0.6",
|
||||
"@types/jest": "^29.2.0",
|
||||
"@types/node": "^20.1.0",
|
||||
"@types/node": "^18.11.4",
|
||||
"jest": "^29.2.2",
|
||||
"prettier": "3.0.2",
|
||||
"prettier": "2.8.2",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.24.1",
|
||||
"typescript": "^5.0.4"
|
||||
"typedoc": "^0.23.18",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +1,30 @@
|
||||
import ffi from "ffi-napi";
|
||||
import ref from "ref-napi";
|
||||
import path from "path";
|
||||
|
||||
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 context = "void*";
|
||||
const _functions = {
|
||||
extism_context_new: [context, []],
|
||||
extism_context_free: ["void", [context]],
|
||||
extism_plugin_new: [
|
||||
pluginIndex,
|
||||
[context, "string", "uint64", PtrArray, "uint64", "bool"],
|
||||
],
|
||||
extism_plugin_new: ["int32", [context, "string", "uint64", "bool"]],
|
||||
extism_plugin_update: [
|
||||
"bool",
|
||||
[context, pluginIndex, "string", "uint64", PtrArray, "uint64", "bool"],
|
||||
[context, "int32", "string", "uint64", "bool"],
|
||||
],
|
||||
extism_error: ["string", [context, pluginIndex]],
|
||||
extism_error: ["char*", [context, "int32"]],
|
||||
extism_plugin_call: [
|
||||
"int32",
|
||||
[context, pluginIndex, "string", "string", "uint64"],
|
||||
[context, "int32", "string", "string", "uint64"],
|
||||
],
|
||||
extism_plugin_output_length: ["uint64", [context, pluginIndex]],
|
||||
extism_plugin_output_data: ["uint8*", [context, pluginIndex]],
|
||||
extism_plugin_output_length: ["uint64", [context, "int32"]],
|
||||
extism_plugin_output_data: ["uint8*", [context, "int32"]],
|
||||
extism_log_file: ["bool", ["string", "char*"]],
|
||||
extism_plugin_function_exists: ["bool", [context, pluginIndex, "string"]],
|
||||
extism_plugin_config: ["void", [context, pluginIndex, "char*", "uint64"]],
|
||||
extism_plugin_free: ["void", [context, pluginIndex]],
|
||||
extism_plugin_function_exists: ["bool", [context, "int32", "string"]],
|
||||
extism_plugin_config: ["void", [context, "int32", "char*", "uint64"]],
|
||||
extism_plugin_free: ["void", [context, "int32"]],
|
||||
extism_context_reset: ["void", [context]],
|
||||
extism_version: ["string", []],
|
||||
extism_function_new: [
|
||||
function_t,
|
||||
[
|
||||
"string",
|
||||
ValTypeArray,
|
||||
"uint64",
|
||||
ValTypeArray,
|
||||
"uint64",
|
||||
"void*",
|
||||
"void*",
|
||||
"void*",
|
||||
],
|
||||
],
|
||||
extism_function_free: ["void", [function_t]],
|
||||
extism_function_set_namespace: ["void", [function_t, "string"]],
|
||||
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"]],
|
||||
extism_plugin_cancel_handle: ["void*", [context, pluginIndex]],
|
||||
extism_plugin_cancel: ["bool", ["void*"]],
|
||||
extism_version: ["char*", []],
|
||||
};
|
||||
|
||||
/**
|
||||
* An enumeration of all possible `Val` types
|
||||
*/
|
||||
export enum ValType {
|
||||
I32 = 0,
|
||||
I64,
|
||||
F32,
|
||||
F64,
|
||||
V128,
|
||||
FuncRef,
|
||||
ExternRef,
|
||||
}
|
||||
|
||||
interface LibExtism {
|
||||
extism_context_new: () => Buffer;
|
||||
extism_context_free: (ctx: Buffer) => void;
|
||||
@@ -102,62 +32,40 @@ interface LibExtism {
|
||||
ctx: Buffer,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
functions: Buffer,
|
||||
nfunctions: number,
|
||||
wasi: boolean,
|
||||
wasi: boolean
|
||||
) => number;
|
||||
extism_plugin_update: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
functions: Buffer,
|
||||
nfunctions: number,
|
||||
wasi: boolean,
|
||||
wasi: boolean
|
||||
) => boolean;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => string;
|
||||
extism_error: (ctx: Buffer, plugin_id: number) => Buffer;
|
||||
extism_plugin_call: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
func: string,
|
||||
input: string,
|
||||
input_len: number,
|
||||
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,
|
||||
plugin_id: number,
|
||||
func: string,
|
||||
func: string
|
||||
) => boolean;
|
||||
extism_plugin_config: (
|
||||
ctx: Buffer,
|
||||
plugin_id: number,
|
||||
data: string | Buffer,
|
||||
data_len: number,
|
||||
data_len: number
|
||||
) => void;
|
||||
extism_plugin_free: (ctx: Buffer, plugin_id: number) => void;
|
||||
extism_context_reset: (ctx: Buffer) => void;
|
||||
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_set_namespace: (f: Buffer, s: string) => void;
|
||||
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;
|
||||
extism_plugin_cancel_handle: (p: Buffer, n: number) => Buffer;
|
||||
extism_plugin_cancel: (p: Buffer) => boolean;
|
||||
extism_version: () => Buffer;
|
||||
}
|
||||
|
||||
function locate(paths: string[]): LibExtism {
|
||||
@@ -202,19 +110,19 @@ export function setLogFile(filename: string, level?: string) {
|
||||
* @returns The version string of the Extism runtime
|
||||
*/
|
||||
export function extismVersion(): string {
|
||||
return lib.extism_version();
|
||||
return lib.extism_version().toString();
|
||||
}
|
||||
|
||||
// @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
|
||||
*/
|
||||
@@ -253,7 +161,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 = {
|
||||
@@ -306,7 +214,7 @@ export class Context {
|
||||
*/
|
||||
constructor() {
|
||||
this.pointer = lib.extism_context_new();
|
||||
contextRegistry.register(this, this.pointer, this.pointer);
|
||||
contextRegistry.register(this, this.pointer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,24 +225,19 @@ export class Context {
|
||||
* @param config - Config details for the plugin
|
||||
* @returns A new Plugin scoped to this Context
|
||||
*/
|
||||
plugin(
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig,
|
||||
) {
|
||||
return new Plugin(manifest, wasi, functions, config, this);
|
||||
plugin(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
return new Plugin(this, manifest, wasi, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,246 +268,27 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string from a host function
|
||||
* @param output - The output to set
|
||||
* @param s - The string to return
|
||||
*/
|
||||
returnString(output: typeof Val, s: string) {
|
||||
var offs = this.memoryAlloc(Buffer.byteLength(s));
|
||||
this.memory(offs).write(s);
|
||||
output.v.i64 = offs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return bytes from a host function
|
||||
* @param output - The output to set
|
||||
* @param b - The buffer to return
|
||||
*/
|
||||
returnBytes(output: typeof Val, b: Buffer) {
|
||||
var offs = this.memoryAlloc(b.length);
|
||||
this.memory(offs).fill(b);
|
||||
output.v.i64 = offs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bytes from host function parameter
|
||||
* @param input - The input to read
|
||||
*/
|
||||
inputBytes(input: typeof Val): Buffer {
|
||||
return this.memory(input.v.i64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string from host function parameter
|
||||
* @param input - The input to read
|
||||
*/
|
||||
inputString(input: typeof Val): string {
|
||||
return this.memory(input.v.i64).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set function namespace
|
||||
*/
|
||||
setNamespace(name: string) {
|
||||
if (this.pointer !== null) {
|
||||
lib.extism_function_set_namespace(this.pointer, name);
|
||||
}
|
||||
}
|
||||
|
||||
withNamespace(name: string): HostFunction {
|
||||
this.setNamespace(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CancelHandle is used to cancel a running Plugin
|
||||
*/
|
||||
export class CancelHandle {
|
||||
handle: Buffer;
|
||||
|
||||
constructor(handle: Buffer) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel execution of the Plugin associated with the CancelHandle
|
||||
*/
|
||||
cancel(): boolean {
|
||||
return lib.extism_plugin_cancel(this.handle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @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
|
||||
* @param ctx - The context to manage this plugin, or null to use a new context
|
||||
*/
|
||||
constructor(
|
||||
ctx: Context,
|
||||
manifest: ManifestData,
|
||||
wasi: boolean = false,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig,
|
||||
ctx: Context | null = null,
|
||||
config?: PluginConfig
|
||||
) {
|
||||
if (ctx == null) {
|
||||
ctx = new Context();
|
||||
}
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
|
||||
dataRaw = manifest;
|
||||
@@ -614,17 +298,11 @@ 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"),
|
||||
this.functions,
|
||||
functions.length,
|
||||
wasi,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
wasi
|
||||
);
|
||||
if (plugin < 0) {
|
||||
var err = lib.extism_error(ctx.pointer, -1);
|
||||
@@ -634,43 +312,27 @@ 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'),);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new `CancelHandle`, which can be used to cancel a running Plugin
|
||||
*/
|
||||
cancelHandle(): CancelHandle {
|
||||
if (!this.ctx.pointer) throw Error("No Context set");
|
||||
let handle = lib.extism_plugin_cancel_handle(this.ctx.pointer, this.id);
|
||||
return new CancelHandle(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing plugin with new WASM or manifest
|
||||
*
|
||||
* @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,
|
||||
functions: HostFunction[] = [],
|
||||
config?: PluginConfig,
|
||||
) {
|
||||
update(manifest: ManifestData, wasi: boolean = false, config?: PluginConfig) {
|
||||
let dataRaw: string | Buffer;
|
||||
if (Buffer.isBuffer(manifest) || typeof manifest === "string") {
|
||||
dataRaw = manifest;
|
||||
@@ -680,18 +342,12 @@ 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"),
|
||||
this.functions,
|
||||
functions.length,
|
||||
wasi,
|
||||
Buffer.byteLength(dataRaw, 'utf-8'),
|
||||
wasi
|
||||
);
|
||||
if (!ok) {
|
||||
var err = lib.extism_error(this.ctx.pointer, -1);
|
||||
@@ -703,12 +359,7 @@ 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'),);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,7 +375,7 @@ export class Plugin {
|
||||
return lib.extism_plugin_function_exists(
|
||||
this.ctx.pointer,
|
||||
this.id,
|
||||
functionName,
|
||||
functionName
|
||||
);
|
||||
}
|
||||
|
||||
@@ -752,7 +403,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);
|
||||
@@ -766,7 +417,7 @@ export class Plugin {
|
||||
var buf = Buffer.from(
|
||||
lib.extism_plugin_output_data(this.ctx.pointer, this.id).buffer,
|
||||
0,
|
||||
out_len,
|
||||
out_len
|
||||
);
|
||||
resolve(buf);
|
||||
});
|
||||
@@ -776,7 +427,8 @@ export class Plugin {
|
||||
* Free a plugin, this should be called when the plugin is no longer needed
|
||||
*/
|
||||
free() {
|
||||
if (this.ctx.pointer && this.id >= 0) {
|
||||
if (this.ctx.pointer && this.id !== -1) {
|
||||
pluginRegistry.unregister(this);
|
||||
lib.extism_plugin_free(this.ctx.pointer, this.id);
|
||||
this.id = -1;
|
||||
}
|
||||
|
||||
BIN
node/tests/code.wasm
Executable file
BIN
node/tests/code.wasm
Executable file
Binary file not shown.
@@ -2,23 +2,14 @@ import * as extism from "../src/index";
|
||||
import { readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
function manifest(functions: boolean = false): extism.Manifest {
|
||||
function manifest(): extism.Manifest {
|
||||
return {
|
||||
wasm: [
|
||||
{
|
||||
path: join(
|
||||
__dirname,
|
||||
functions
|
||||
? "/../../wasm/code-functions.wasm"
|
||||
: "/../../wasm/code.wasm"
|
||||
),
|
||||
},
|
||||
],
|
||||
wasm: [{ path: join(__dirname, "/code.wasm") }],
|
||||
};
|
||||
}
|
||||
|
||||
function wasmBuffer(): Buffer {
|
||||
return readFileSync(join(__dirname, "/../../wasm/code.wasm"));
|
||||
return readFileSync(join(__dirname, "/code.wasm"));
|
||||
}
|
||||
|
||||
describe("test extism", () => {
|
||||
@@ -111,27 +102,4 @@ describe("test extism", () => {
|
||||
).rejects.toMatch(/Plugin error/);
|
||||
});
|
||||
});
|
||||
|
||||
test("host functions work", async () => {
|
||||
await extism.withContext(async (ctx: extism.Context) => {
|
||||
const plugin = ctx.plugin(manifest(true), true, [
|
||||
new extism.HostFunction(
|
||||
"hello_world",
|
||||
[extism.ValType.I64],
|
||||
[extism.ValType.I64],
|
||||
(plugin: any, params: any, results: any, user_data: string) => {
|
||||
const offs = plugin.memoryAlloc(user_data.length);
|
||||
const mem = plugin.memory(offs);
|
||||
mem.write(user_data);
|
||||
results[0].v.i64 = offs;
|
||||
},
|
||||
"test"
|
||||
),
|
||||
]);
|
||||
|
||||
const res = await plugin.call("count_vowels", "aaa");
|
||||
|
||||
expect(res.toString()).toBe("test");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user