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/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..d17389e 100644 --- a/Extism/lib/Extism/Plugin.pm +++ b/Extism/lib/Extism/Plugin.pm @@ -6,6 +6,9 @@ use warnings; 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 plugin_error @@ -20,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); @@ -29,27 +33,28 @@ our $VERSION = qv(v0.2.0); sub new { my ($name, $wasm, $options) = @_; - my $functions = []; - my $with_wasi = 0; - if ($options) { - if (exists $options->{functions}) { - $functions = $options->{functions}; - } - if (exists $options->{wasi}) { - $with_wasi = $options->{wasi}; - } + 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 = plugin_new($wasm, length($wasm), $functionsptr, scalar(@rawfunctions), $with_wasi, $errptrptr); if (! $plugin) { my $errmsg = unpack('p', $errptr); plugin_new_error_free(unpack('Q', $errptr)); croak $errmsg; } + if ($opt->{allow_http_response_headers}) { + plugin_allow_http_response_headers($plugin); + } bless \$plugin, $name } @@ -59,14 +64,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..e2e8778 100644 --- a/Extism/lib/Extism/XS.pm +++ b/Extism/lib/Extism/XS.pm @@ -14,6 +14,8 @@ XSLoader::load('Extism::XS', $VERSION); 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 @@ -33,9 +35,13 @@ 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 + 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 172424e..d7beb4c 100644 --- a/Extism/lib/Extism/XS.xs +++ b/Extism/lib/Extism/XS.xs @@ -80,6 +80,9 @@ ExtismCurrentPlugin * T_PTR ExtismMemoryHandle T_UV const ExtismCancelHandle * T_PTR PV T_PV +uint64_t T_UV +ExtismCompiledPlugin * T_PTR +const ExtismCompiledPlugin * T_PTR HERE const char * @@ -89,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 @@ -102,20 +133,41 @@ 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); +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); 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 +313,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..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 => 45; +plan tests => 54; # ... ok(Extism::version()); @@ -213,3 +213,68 @@ 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); +} + +# 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($@); +} + +# http headers +{ + 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($@); +}