File Coverage

blib/lib/Wasm.pm
Criterion Covered Total %
statement 160 166 96.3
branch 60 80 75.0
condition 14 21 66.6
subroutine 13 13 100.0
pod n/a
total 247 280 88.2


line stmt bran cond sub pod time code
1             package Wasm;
2              
3 5     5   1292 use strict;
  5         10  
  5         154  
4 5     5   26 use warnings;
  5         20  
  5         123  
5 5     5   87 use 5.008004;
  5         28  
6 5     5   30 use Ref::Util qw( is_plain_arrayref );
  5         11  
  5         232  
7 5     5   30 use Carp ();
  5         18  
  5         160  
8 5     5   1630 use Wasm::Trap;
  5         14  
  5         3398  
9              
10             # ABSTRACT: Write Perl extensions using Wasm
11             our $VERSION = '0.22'; # VERSION
12              
13              
14             our %WASM;
15             my $linker;
16             my %inst;
17             my %pp;
18             my $wasi;
19             my @keep;
20              
21             sub _linker
22             {
23 21     21   2440 require Wasm::Wasmtime;
24 21   66     102 $linker ||= do {
25 5         737 my $linker = Wasm::Wasmtime::Linker->new(
26             Wasm::Wasmtime::Store->new(
27             Wasm::Wasmtime::Engine->new(
28             Wasm::Wasmtime::Config
29             ->new
30             ->wasm_multi_value(1)
31             ->cache_config_default,
32             ),
33             ),
34             );
35 5         30 $linker->allow_shadowing(0);
36 5         25 $linker;
37             };
38             }
39              
40             sub import
41             {
42 25     25   10628 my $class = shift;
43 25         107 my($caller, $fn) = caller;
44              
45 25 100       153 return unless @_;
46              
47 23 100 66     129 if(defined $_[0] && $_[0] ne '-api')
48             {
49 1         90 Carp::croak("You MUST specify an api level as the first option");
50             }
51              
52 22         59 my $api;
53             my $exporter;
54 22         0 my @module;
55 22         37 my $package = $caller;
56 22         41 my $file = $fn;
57              
58 22         40 my @global;
59              
60 22         74 while(@_)
61             {
62 46         78 my $key = shift;
63 46 100       175 if($key eq '-api')
    100          
    100          
    100          
    100          
    100          
    50          
    0          
64             {
65 23 100       56 if(defined $api)
66             {
67 1         98 Carp::croak("Specified -api more than once");
68             }
69 22         37 $api = shift;
70 22 100 66     115 unless(defined $api && $api == 0)
71             {
72 1         164 Carp::croak("Currently only -api => 0 is supported");
73             }
74             }
75             elsif($key eq '-wat')
76             {
77 4         10 my $wat = shift;
78 4 50       13 Carp::croak("-wat undefined") unless defined $wat;
79 4         27 @module = (wat => $wat);
80             }
81             elsif($key eq '-file')
82             {
83 6         8 my $path = shift;
84 6 50 33     119 unless(defined $path && -f $path)
85             {
86 0 0       0 $path = 'undef' unless defined $path;
87 0         0 Carp::croak("no such file $path");
88             }
89 6         20 $file = "$path";
90 6         27 @module = (file => $file);
91             }
92             elsif($key eq '-self')
93             {
94 8         41 require Path::Tiny;
95 8         81 my $perl_path = Path::Tiny->new($fn);
96 8         431 my $basename = $perl_path->basename;
97 8         458 $basename =~ s/\.(pl|pm)$//;
98 8         51 my @maybe = sort { $b->stat->mtime <=> $a->stat->mtime } grep { -f $_ } (
  1         24  
  16         1524  
99             $perl_path->parent->child($basename . ".wasm"),
100             $perl_path->parent->child($basename . ".wat"),
101             );
102 8 50       6841 if(@maybe == 0)
103             {
104 0         0 Carp::croak("unable to find .wasm or .wat file relative to Perl source");
105             }
106             else
107             {
108 8         21 $file = shift @maybe;
109 8         46 @module = (file => $file);
110             }
111             }
112             elsif($key eq '-exporter')
113             {
114 3         7 $exporter = shift;
115             }
116             elsif($key eq '-package')
117             {
118 1         4 $package = shift;
119             }
120             elsif($key eq '-global')
121             {
122 1 50       7 if(is_plain_arrayref $_[0])
123             {
124 1         5 push @global, shift;
125             }
126             else
127             {
128 0         0 Carp::croak("-global should be an array reference");
129             }
130             }
131             elsif($key eq '-imports')
132             {
133 0         0 Carp::croak("-imports was removed in Wasm.pm 0.08");
134             }
135             else
136             {
137 0         0 Carp::croak("Unknown Wasm option: $key");
138             }
139             }
140              
141 20         70 _linker();
142              
143 20 100       66 if(@global)
144             {
145 1 50       4 Carp::croak("Cannot specify both Wasm and -global") if @module;
146 1         3 foreach my $spec (@global)
147             {
148 1         5 my($name, $content, $mutability, $value) = @$spec;
149 1         6 my $global = Wasm::Wasmtime::Global->new(
150             $linker->store,
151             Wasm::Wasmtime::GlobalType->new($content, $mutability),
152             $value,
153             );
154 5     5   40 no strict 'refs';
  5         24  
  5         2619  
155 1         14 *{"${package}::$name"} = $global->tie;
  1         12  
156 1         5 $pp{$package} = $file;
157             }
158 1         76 return;
159             }
160              
161 19 100       65 @module = (wat => '(module)') unless @module;
162              
163 19 50       82 Carp::croak("The wasm_ namespace is reserved for internal use") if $package =~ /^wasi_/;
164 19 50       68 Carp::croak("Wasm for $package already loaded") if $inst{$package};
165              
166 19         94 my $module = Wasm::Wasmtime::Module->new($linker->store->engine, @module);
167              
168 19         47 foreach my $import (@{ $module->imports })
  19         95  
169             {
170              
171 11         43 my $module = $import->module;
172              
173 11 100       127 if($module =~ /^(wasi_unstable|wasi_snapshot_preview1)$/)
174             {
175 2 50       10 next if $WASM{$module};
176 2   33     19 $linker->define_wasi(
177             $wasi ||= Wasm::Wasmtime::WasiInstance->new(
178             $linker->store,
179             $module,
180             Wasm::Wasmtime::WasiConfig
181             ->new
182             ->set_argv($0, @ARGV)
183             ->inherit_env
184             ->inherit_stdin
185             ->inherit_stdout
186             ->inherit_stderr
187             ->preopen_dir("/", "/"),
188             )
189             );
190 2         11 $WASM{$module} = __FILE__; # Maybe Wasi::Snapshot::Preview1 etc.
191 2         8 next;
192             }
193              
194 9 100 100     80 if($module ne 'main' && !$inst{$module} && !$pp{$module})
      100        
195             {
196 4         12 my $pm = "$module.pm";
197 4         15 $pm =~ s{::}{/}g;
198 4         11 eval { require $pm };
  4         1844  
199 4 100       28 if(my $error = $@)
200             {
201 1         13 $error =~ s/ at (.*?)$//;
202 1         10 $error .= " module required by WebAssembly at $file";
203 1         272 Carp::croak("$error");
204             }
205             }
206              
207 8 100       33 next if $inst{$module};
208              
209 4         18 my $name = $import->name;
210 4         37 my $type = $import->type;
211 4         38 my $kind = $type->kind;
212              
213 4         9 my $extern;
214              
215 4 100       17 if($kind eq 'functype')
    50          
216             {
217 2 50       39 if(my $f = $module->can("${module}::$name"))
218             {
219 2         11 $extern = Wasm::Wasmtime::Func->new(
220             $linker->store,
221             $type,
222             $f,
223             );
224 2         9 push @keep, $extern;
225             }
226             }
227             elsif($kind eq 'globaltype')
228             {
229 5 50   5   40 if(my $global = do { no strict 'refs'; tied ${"${module}::$name"} })
  5         10  
  5         1496  
  2         4  
  2         4  
  2         17  
230             {
231 2         5 $extern = $global;
232             }
233             }
234              
235 4 50       17 if($extern)
236             {
237             # TODO: check that the store is the same?
238 4         8 eval {
239 4         21 $linker->define(
240             $module,
241             $name,
242             $extern,
243             );
244             };
245 4 100       23 if(my $error = $@)
246             {
247 1 50       11 if(Wasm::Wasmtime::Error->can('new'))
248             {
249             # TODO: if we can do a get on the define that would
250             # be better than doing this regex on the diagnostic.
251             # this is available in the rust api, but not the c api
252             # as of this writing.
253 1 50       11 die $error unless $error =~ /defined twice/;
254             }
255             else
256             {
257             # TODO: also for the prod version of wasmtime we don't
258             # have an error so we end up swallowing other types
259             # of errors, if there are any.
260             }
261             }
262             }
263             }
264              
265 18         87 my $instance = $inst{$package} = $linker->instantiate($module);
266 18         128 $linker->define_instance($package, $instance);
267 18         74 $WASM{$package} = "$file";
268              
269 18         76 my @me = @{ $module->exports };
  18         76  
270 18         61 my @ie = @{ $instance->exports };
  18         72  
271              
272 18         56 my @function_names;
273              
274 18         55 for my $i (0..$#ie)
275             {
276 40         77 my $exporttype = $me[$i];
277 40         114 my $name = $me[$i]->name;
278 40         365 my $externtype = $exporttype->type;
279 40         65 my $extern = $ie[$i];
280 40         177 my $kind = $extern->kind;
281 40 100       143 if($kind eq 'func')
    100          
    50          
282             {
283 27         55 my $func = $extern;
284 27         107 $func->attach($package, $name);
285 27         1902 push @function_names, $name;
286             }
287             elsif($kind eq 'global')
288             {
289 2         5 my $global = $extern;
290 5     5   38 no strict 'refs';
  5         12  
  5         401  
291 2         11 *{"${package}::$name"} = $global->tie;
  2         25  
292             }
293             elsif($kind eq 'memory')
294             {
295 11         1992 require Wasm::Memory;
296 11         63 my $memory = Wasm::Memory->new($extern);
297 5     5   31 no strict 'refs';
  5         10  
  5         399  
298 11         23 *{"${package}::$name"} = \$memory;
  11         95  
299             }
300             }
301              
302 18 100       90 if($exporter)
303             {
304 3         16 require Exporter;
305 5     5   62 no strict 'refs';
  5         19  
  5         1063  
306 3         8 push @{ "${package}::ISA" }, 'Exporter';
  3         38  
307 3 100       16 if($exporter eq 'all')
308             {
309 1         3 push @{ "${package}::EXPORT" }, @function_names;
  1         7  
310             }
311             else
312             {
313 2         5 push @{ "${package}::EXPORT_OK" }, @function_names;
  2         18  
314             }
315             }
316             }
317              
318             1;
319              
320             __END__