From 2efbc90041887daee1c4b84f0a0f76ba86bd43ea Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Tue, 26 Nov 2024 15:16:09 -0500 Subject: [PATCH 1/4] feat: per call context --- Extism/lib/Extism/CurrentPlugin.pm | 5 +++++ Extism/lib/Extism/Plugin.pm | 4 ++-- Extism/lib/Extism/XS.pm | 1 + Extism/lib/Extism/XS.xs | 16 ++++++++++++++-- Extism/t/02-extism.t | 29 ++++++++++++++++++++++++++++- 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Extism/lib/Extism/CurrentPlugin.pm b/Extism/lib/Extism/CurrentPlugin.pm index 96568d4..e719468 100644 --- a/Extism/lib/Extism/CurrentPlugin.pm +++ b/Extism/lib/Extism/CurrentPlugin.pm @@ -8,6 +8,7 @@ use Extism::XS qw(current_plugin_memory current_plugin_memory_alloc current_plugin_memory_length current_plugin_memory_free + current_plugin_host_context CopyToPtr); use version 0.77; @@ -53,4 +54,8 @@ sub memory_alloc_and_store { return $ptr; } +sub host_context { + current_plugin_host_context($Extism::CurrentPlugin::instance) +} + 1; # End of Extism::CurrentPlugin diff --git a/Extism/lib/Extism/Plugin.pm b/Extism/lib/Extism/Plugin.pm index a556435..56edba9 100644 --- a/Extism/lib/Extism/Plugin.pm +++ b/Extism/lib/Extism/Plugin.pm @@ -59,14 +59,14 @@ sub new { # passed to the plugin. If INPUT is a reference, the referenced item will be # encoded with json and then passed to the plugin. sub call { - my ($self, $func_name, $input) = @_; + my ($self, $func_name, $input, $host_context) = @_; $input //= ''; my $type = reftype($input); if ($type) { $input = $$input if($type eq 'SCALAR'); $input = encode_json($input); } - my $rc = plugin_call($$self, $func_name, $input, length($input)); + my $rc = plugin_call($$self, $func_name, $input, length($input), $host_context); if ($rc != 0) { die Extism::Plugin::CallException->new($rc, plugin_error($$self)); } diff --git a/Extism/lib/Extism/XS.pm b/Extism/lib/Extism/XS.pm index 1f0a9ca..3ca1a24 100644 --- a/Extism/lib/Extism/XS.pm +++ b/Extism/lib/Extism/XS.pm @@ -33,6 +33,7 @@ our @EXPORT_OK = qw( current_plugin_memory_alloc current_plugin_memory_length current_plugin_memory_free + current_plugin_host_context log_file log_custom log_drain diff --git a/Extism/lib/Extism/XS.xs b/Extism/lib/Extism/XS.xs index 172424e..f26db38 100644 --- a/Extism/lib/Extism/XS.xs +++ b/Extism/lib/Extism/XS.xs @@ -109,13 +109,14 @@ plugin_new_error_free(err); extism_plugin_new_error_free(err); int32_t -plugin_call(plugin, func_name, data, data_len) +plugin_call(plugin, func_name, data, data_len, host_context=&PL_sv_undef) ExtismPlugin *plugin const char *func_name const uint8_t *data ExtismSize data_len + SV *host_context CODE: - RETVAL = extism_plugin_call(plugin, func_name, data, data_len); + RETVAL = extism_plugin_call_with_host_context(plugin, func_name, data, data_len, host_context); OUTPUT: RETVAL @@ -261,6 +262,17 @@ current_plugin_memory_free(plugin, handle) CODE: extism_current_plugin_memory_free(plugin, handle); +SV * +current_plugin_host_context(plugin) + ExtismCurrentPlugin *plugin + CODE: + RETVAL = extism_current_plugin_host_context(plugin); + if (RETVAL != &PL_sv_undef) { + SvREFCNT_inc_simple_NN(RETVAL); + } + OUTPUT: + RETVAL + void log_file(filename, log_level) const char *filename diff --git a/Extism/t/02-extism.t b/Extism/t/02-extism.t index e6413d3..86d81eb 100644 --- a/Extism/t/02-extism.t +++ b/Extism/t/02-extism.t @@ -7,7 +7,7 @@ use Extism ':all'; use JSON::PP qw(encode_json decode_json); use File::Temp qw(tempfile); use Devel::Peek qw(Dump); -plan tests => 45; +plan tests => 48; # ... ok(Extism::version()); @@ -213,3 +213,30 @@ eval { ok($@); ok($@->{code} != 0); ok($@->{message}); + +# Test host_context +{ + my $voidfunction = Extism::Function->new("hello_void", [], [], sub { + ok(! defined Extism::CurrentPlugin::host_context); + return; + }); + my $paramsfunction = Extism::Function->new("hello_params", [Extism_F64, Extism_I32, Extism_F32, Extism_I64], [Extism_I64], sub { + # not called + }); + my $fplugin = Extism::Plugin->new($hostwasm, {functions => [$voidfunction, $paramsfunction], wasi => 1}); + $fplugin->call('call_hello_void'); +} +{ + my %context = ( abc => 123); + my $voidfunction = Extism::Function->new("hello_void", [], [], sub { + my $ctx = Extism::CurrentPlugin::host_context; + is_deeply($ctx, \%context); + ok($ctx == \%context); + return; + }); + my $paramsfunction = Extism::Function->new("hello_params", [Extism_F64, Extism_I32, Extism_F32, Extism_I64], [Extism_I64], sub { + # not called + }); + my $fplugin = Extism::Plugin->new($hostwasm, {functions => [$voidfunction, $paramsfunction], wasi => 1}); + $fplugin->call('call_hello_void', '', \%context); +} From 4f98e963814002d21811ff37bbe6555bbbbd962b Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Tue, 26 Nov 2024 15:54:29 -0500 Subject: [PATCH 2/4] feat: add fuel_limit to Extism::Plugin->new --- Extism/lib/Extism/Plugin.pm | 9 ++++++++- Extism/lib/Extism/XS.pm | 1 + Extism/lib/Extism/XS.xs | 15 +++++++++++++++ Extism/t/02-extism.t | 17 ++++++++++++++++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Extism/lib/Extism/Plugin.pm b/Extism/lib/Extism/Plugin.pm index 56edba9..9caeed2 100644 --- a/Extism/lib/Extism/Plugin.pm +++ b/Extism/lib/Extism/Plugin.pm @@ -6,6 +6,7 @@ use warnings; use Carp qw(croak); use Extism::XS qw( plugin_new + plugin_new_with_fuel_limit plugin_new_error_free plugin_call plugin_error @@ -31,6 +32,7 @@ sub new { my ($name, $wasm, $options) = @_; my $functions = []; my $with_wasi = 0; + my $fuel_limit; if ($options) { if (exists $options->{functions}) { $functions = $options->{functions}; @@ -38,13 +40,18 @@ sub new { if (exists $options->{wasi}) { $with_wasi = $options->{wasi}; } + if (exists $options->{fuel_limit}) { + $fuel_limit = $options->{fuel_limit}; + } } my $errptr = "\x00" x 8; my $errptrptr = unpack('Q', pack('P', $errptr)); my @rawfunctions = map {$$_} @{$functions}; my $functionsarray = pack('Q*', @rawfunctions); my $functionsptr = unpack('Q', pack('P', $functionsarray)); - my $plugin = plugin_new($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $errptrptr); + my $plugin = ! defined $fuel_limit + ? plugin_new($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $errptrptr) + : plugin_new_with_fuel_limit($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $fuel_limit, $errptrptr); if (! $plugin) { my $errmsg = unpack('p', $errptr); plugin_new_error_free(unpack('Q', $errptr)); diff --git a/Extism/lib/Extism/XS.pm b/Extism/lib/Extism/XS.pm index 3ca1a24..e2f02b4 100644 --- a/Extism/lib/Extism/XS.pm +++ b/Extism/lib/Extism/XS.pm @@ -14,6 +14,7 @@ XSLoader::load('Extism::XS', $VERSION); our @EXPORT_OK = qw( version plugin_new + plugin_new_with_fuel_limit plugin_new_error_free plugin_call plugin_error diff --git a/Extism/lib/Extism/XS.xs b/Extism/lib/Extism/XS.xs index f26db38..bc95341 100644 --- a/Extism/lib/Extism/XS.xs +++ b/Extism/lib/Extism/XS.xs @@ -80,6 +80,7 @@ ExtismCurrentPlugin * T_PTR ExtismMemoryHandle T_UV const ExtismCancelHandle * T_PTR PV T_PV +uint64_t T_UV HERE const char * @@ -102,6 +103,20 @@ plugin_new(wasm, wasm_size, functions, n_functions, with_wasi, errmsg) OUTPUT: RETVAL +ExtismPlugin * +plugin_new_with_fuel_limit(wasm, wasm_size, functions, n_functions, with_wasi, fuel_limit, errmsg) + const uint8_t *wasm + ExtismSize wasm_size + const ExtismFunction **functions + ExtismSize n_functions + bool with_wasi + uint64_t fuel_limit + char **errmsg + CODE: + RETVAL = extism_plugin_new_with_fuel_limit(wasm, wasm_size, functions, n_functions, with_wasi, fuel_limit, errmsg); + OUTPUT: + RETVAL + void plugin_new_error_free(err); void *err diff --git a/Extism/t/02-extism.t b/Extism/t/02-extism.t index 86d81eb..074a3bd 100644 --- a/Extism/t/02-extism.t +++ b/Extism/t/02-extism.t @@ -7,7 +7,7 @@ use Extism ':all'; use JSON::PP qw(encode_json decode_json); use File::Temp qw(tempfile); use Devel::Peek qw(Dump); -plan tests => 48; +plan tests => 50; # ... ok(Extism::version()); @@ -240,3 +240,18 @@ ok($@->{message}); my $fplugin = Extism::Plugin->new($hostwasm, {functions => [$voidfunction, $paramsfunction], wasi => 1}); $fplugin->call('call_hello_void', '', \%context); } + +# fuel +{ + my $plugin = Extism::Plugin->new($wasm, {wasi => 1, fuel_limit => 100000}); + my $output = $plugin->call('count_vowels', "this is a test"); + my $outputhash = decode_json($output); + ok($outputhash->{count} == 4); +} +{ + my $plugin = Extism::Plugin->new($wasm, {wasi => 1, fuel_limit => 5}); + eval { + my $output = $plugin->call('count_vowels', "this is a test"); + }; + ok($@); +} From 999eb3b870fe2bbc906c9b42ff2f2c699a455eb3 Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Tue, 26 Nov 2024 16:19:11 -0500 Subject: [PATCH 3/4] feat: add allow_http_response_headers to Plugin->new to allow plugins to access http response headers --- Extism/lib/Extism/Plugin.pm | 8 ++++++++ Extism/lib/Extism/XS.pm | 1 + Extism/lib/Extism/XS.xs | 8 +++++++- Extism/t/02-extism.t | 8 +++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Extism/lib/Extism/Plugin.pm b/Extism/lib/Extism/Plugin.pm index 9caeed2..1623959 100644 --- a/Extism/lib/Extism/Plugin.pm +++ b/Extism/lib/Extism/Plugin.pm @@ -7,6 +7,7 @@ use Carp qw(croak); use Extism::XS qw( plugin_new plugin_new_with_fuel_limit + plugin_allow_http_response_headers plugin_new_error_free plugin_call plugin_error @@ -33,6 +34,7 @@ sub new { my $functions = []; my $with_wasi = 0; my $fuel_limit; + my $allow_http_response_headers; if ($options) { if (exists $options->{functions}) { $functions = $options->{functions}; @@ -43,6 +45,9 @@ sub new { if (exists $options->{fuel_limit}) { $fuel_limit = $options->{fuel_limit}; } + if (exists $options->{allow_http_response_headers}) { + $allow_http_response_headers = $options->{allow_http_response_headers}; + } } my $errptr = "\x00" x 8; my $errptrptr = unpack('Q', pack('P', $errptr)); @@ -57,6 +62,9 @@ sub new { plugin_new_error_free(unpack('Q', $errptr)); croak $errmsg; } + if ($allow_http_response_headers) { + plugin_allow_http_response_headers($plugin); + } bless \$plugin, $name } diff --git a/Extism/lib/Extism/XS.pm b/Extism/lib/Extism/XS.pm index e2f02b4..8769555 100644 --- a/Extism/lib/Extism/XS.pm +++ b/Extism/lib/Extism/XS.pm @@ -15,6 +15,7 @@ our @EXPORT_OK = qw( version plugin_new plugin_new_with_fuel_limit + plugin_allow_http_response_headers plugin_new_error_free plugin_call plugin_error diff --git a/Extism/lib/Extism/XS.xs b/Extism/lib/Extism/XS.xs index bc95341..fff9932 100644 --- a/Extism/lib/Extism/XS.xs +++ b/Extism/lib/Extism/XS.xs @@ -118,7 +118,13 @@ plugin_new_with_fuel_limit(wasm, wasm_size, functions, n_functions, with_wasi, f RETVAL void -plugin_new_error_free(err); +plugin_allow_http_response_headers(plugin) + ExtismPlugin *plugin + CODE: + extism_plugin_allow_http_response_headers(plugin); + +void +plugin_new_error_free(err) void *err CODE: extism_plugin_new_error_free(err); diff --git a/Extism/t/02-extism.t b/Extism/t/02-extism.t index 074a3bd..61c0a81 100644 --- a/Extism/t/02-extism.t +++ b/Extism/t/02-extism.t @@ -7,7 +7,7 @@ use Extism ':all'; use JSON::PP qw(encode_json decode_json); use File::Temp qw(tempfile); use Devel::Peek qw(Dump); -plan tests => 50; +plan tests => 51; # ... ok(Extism::version()); @@ -255,3 +255,9 @@ ok($@->{message}); }; ok($@); } + +# http headers +{ + my $plugin = Extism::Plugin->new($wasm, {wasi => 1, allow_http_response_headers => 1}); + ok($plugin); +} From cee682e2a22869277c8ffc89c2f6bd0814538736 Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Wed, 27 Nov 2024 16:09:25 -0500 Subject: [PATCH 4/4] feat: compiled plugin --- Extism/lib/Extism.pm | 1 + Extism/lib/Extism/CompiledPlugin.pm | 58 +++++++++++++++++++++++++++++ Extism/lib/Extism/Plugin.pm | 42 ++++++++------------- Extism/lib/Extism/XS.pm | 3 ++ Extism/lib/Extism/XS.xs | 30 +++++++++++++++ Extism/t/02-extism.t | 19 +++++++++- 6 files changed, 126 insertions(+), 27 deletions(-) create mode 100644 Extism/lib/Extism/CompiledPlugin.pm diff --git a/Extism/lib/Extism.pm b/Extism/lib/Extism.pm index eb3aecb..143f233 100644 --- a/Extism/lib/Extism.pm +++ b/Extism/lib/Extism.pm @@ -4,6 +4,7 @@ use 5.016; use strict; use warnings; use Extism::XS qw(version log_file); +use Extism::CompiledPlugin; use Extism::Plugin; use Extism::Function ':all'; use Exporter 'import'; diff --git a/Extism/lib/Extism/CompiledPlugin.pm b/Extism/lib/Extism/CompiledPlugin.pm new file mode 100644 index 0000000..5ce4114 --- /dev/null +++ b/Extism/lib/Extism/CompiledPlugin.pm @@ -0,0 +1,58 @@ +package Extism::CompiledPlugin; + +use 5.016; +use strict; +use warnings; +use Carp qw(croak); +use Extism::XS qw( + compiled_plugin_new + compiled_plugin_free +); +use Exporter 'import'; +use Data::Dumper qw(Dumper); +use Devel::Peek qw(Dump); +use version 0.77; +our $VERSION = qv(v0.2.0); + +our @EXPORT_OK = qw(BuildPluginNewParams); + +sub BuildPluginNewParams { + my ($wasm, $opt) = @_; + my $functions = $opt->{functions} // []; + my @rawfunctions = map {$$_} @{$functions}; + my %p = ( + wasm => $wasm, + _functions_array => pack('Q*', @rawfunctions) + ); + $p{functions} = unpack('Q', pack('P', $p{_functions_array})); + $p{n_functions} = scalar(@rawfunctions); + $p{wasi} = $opt->{wasi} // 0; + $p{fuel_limit} = $opt->{fuel_limit}; + $p{errptr} = "\x00" x 8; + $p{errmsg} = unpack('Q', pack('P', $p{errptr})); + \%p +} + +sub new { + my ($name, $wasm, $options) = @_; + my %opt = %{$options // {}}; + if (defined $opt{fuel_limit}) { + croak "No way to set fuel for CompiledPlugins yet"; + } + my $p = BuildPluginNewParams($wasm, \%opt); + my $compiled = compiled_plugin_new($p->{wasm}, length($p->{wasm}), $p->{functions}, $p->{n_functions}, $p->{wasi}, $p->{errmsg}); + my %savedoptions; + if ($opt{allow_http_response_headers}) { + $savedoptions{allow_http_response_headers} = $opt{allow_http_response_headers}; + } + my %obj = ( compiled => $compiled, options => \%savedoptions); + bless \%obj, $name +} + +sub DESTROY { + my ($self) = @_; + $self->{compiled} or return; + compiled_plugin_free($self->{compiled}); +} + +1; \ No newline at end of file diff --git a/Extism/lib/Extism/Plugin.pm b/Extism/lib/Extism/Plugin.pm index 1623959..d17389e 100644 --- a/Extism/lib/Extism/Plugin.pm +++ b/Extism/lib/Extism/Plugin.pm @@ -7,6 +7,7 @@ use Carp qw(croak); use Extism::XS qw( plugin_new plugin_new_with_fuel_limit + plugin_new_from_compiled plugin_allow_http_response_headers plugin_new_error_free plugin_call @@ -22,6 +23,7 @@ use Extism::XS qw( ); use Extism::Plugin::CallException; use Extism::Plugin::CancelHandle; +use Extism::CompiledPlugin qw(BuildPluginNewParams); use Data::Dumper qw(Dumper); use Devel::Peek qw(Dump); use JSON::PP qw(encode_json); @@ -31,38 +33,26 @@ our $VERSION = qv(v0.2.0); sub new { my ($name, $wasm, $options) = @_; - my $functions = []; - my $with_wasi = 0; - my $fuel_limit; - my $allow_http_response_headers; - if ($options) { - if (exists $options->{functions}) { - $functions = $options->{functions}; - } - if (exists $options->{wasi}) { - $with_wasi = $options->{wasi}; - } - if (exists $options->{fuel_limit}) { - $fuel_limit = $options->{fuel_limit}; - } - if (exists $options->{allow_http_response_headers}) { - $allow_http_response_headers = $options->{allow_http_response_headers}; - } + my ($plugin, $opt, $errptr); + if (!ref($wasm) || !$wasm->isa('Extism::CompiledPlugin')) { + $opt = defined $options ? {%$options} : {}; + my $p = BuildPluginNewParams($wasm, $opt); + $plugin = ! defined $p->{fuel_limit} + ? plugin_new($p->{wasm}, length($p->{wasm}), $p->{functions}, $p->{n_functions}, $p->{wasi}, $p->{errmsg}) + : plugin_new_with_fuel_limit($p->{wasm}, length($p->{wasm}), $p->{functions}, $p->{n_functions}, $p->{wasi}, $p->{fuel_limit}, $p->{errmsg}); + $errptr = $p->{errptr}; + } else { + $opt = $wasm->{options}; + $errptr = "\x00" x 8; + my $errmsg = unpack('Q', pack('P', $errptr)); + $plugin = plugin_new_from_compiled($wasm->{compiled}, $errmsg); } - my $errptr = "\x00" x 8; - my $errptrptr = unpack('Q', pack('P', $errptr)); - my @rawfunctions = map {$$_} @{$functions}; - my $functionsarray = pack('Q*', @rawfunctions); - my $functionsptr = unpack('Q', pack('P', $functionsarray)); - my $plugin = ! defined $fuel_limit - ? plugin_new($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $errptrptr) - : plugin_new_with_fuel_limit($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $fuel_limit, $errptrptr); if (! $plugin) { my $errmsg = unpack('p', $errptr); plugin_new_error_free(unpack('Q', $errptr)); croak $errmsg; } - if ($allow_http_response_headers) { + if ($opt->{allow_http_response_headers}) { plugin_allow_http_response_headers($plugin); } bless \$plugin, $name diff --git a/Extism/lib/Extism/XS.pm b/Extism/lib/Extism/XS.pm index 8769555..e2e8778 100644 --- a/Extism/lib/Extism/XS.pm +++ b/Extism/lib/Extism/XS.pm @@ -39,6 +39,9 @@ our @EXPORT_OK = qw( log_file log_custom log_drain + compiled_plugin_new + compiled_plugin_free + plugin_new_from_compiled CopyToPtr ); diff --git a/Extism/lib/Extism/XS.xs b/Extism/lib/Extism/XS.xs index fff9932..d7beb4c 100644 --- a/Extism/lib/Extism/XS.xs +++ b/Extism/lib/Extism/XS.xs @@ -81,6 +81,8 @@ ExtismMemoryHandle T_UV const ExtismCancelHandle * T_PTR PV T_PV uint64_t T_UV +ExtismCompiledPlugin * T_PTR +const ExtismCompiledPlugin * T_PTR HERE const char * @@ -90,6 +92,34 @@ version() OUTPUT: RETVAL +ExtismCompiledPlugin * +compiled_plugin_new(wasm, wasm_size, functions, n_functions, with_wasi, errmsg) + const uint8_t *wasm + ExtismSize wasm_size + const ExtismFunction **functions + ExtismSize n_functions + bool with_wasi + char **errmsg + CODE: + RETVAL = extism_compiled_plugin_new(wasm, wasm_size, functions, n_functions, with_wasi, errmsg); + OUTPUT: + RETVAL + +void +compiled_plugin_free(compiled_plugin) + ExtismCompiledPlugin *compiled_plugin + CODE: + extism_compiled_plugin_free(compiled_plugin); + +ExtismPlugin * +plugin_new_from_compiled(compiled_plugin, errmsg) + const ExtismCompiledPlugin *compiled_plugin + char **errmsg + CODE: + RETVAL = extism_plugin_new_from_compiled(compiled_plugin, errmsg); + OUTPUT: + RETVAL + ExtismPlugin * plugin_new(wasm, wasm_size, functions, n_functions, with_wasi, errmsg) const uint8_t *wasm diff --git a/Extism/t/02-extism.t b/Extism/t/02-extism.t index 61c0a81..262f4e0 100644 --- a/Extism/t/02-extism.t +++ b/Extism/t/02-extism.t @@ -7,7 +7,7 @@ use Extism ':all'; use JSON::PP qw(encode_json decode_json); use File::Temp qw(tempfile); use Devel::Peek qw(Dump); -plan tests => 51; +plan tests => 54; # ... ok(Extism::version()); @@ -261,3 +261,20 @@ ok($@->{message}); my $plugin = Extism::Plugin->new($wasm, {wasi => 1, allow_http_response_headers => 1}); ok($plugin); } + +# compiled plugin +{ + my $compiled = Extism::CompiledPlugin->new($wasm, {wasi => 1}); + for (1..2) { + my $plugin = Extism::Plugin->new($compiled); + my $output = $plugin->call('count_vowels', "this is a test"); + my $outputhash = decode_json($output); + ok($outputhash->{count} == 4); + } +} +{ + eval { + Extism::CompiledPlugin->new($wasm, {wasi => 1, fuel_limit => 20}); + }; + ok($@); +}