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   1318 use strict;
  5         19  
  5         152  
4 5     5   24 use warnings;
  5         35  
  5         116  
5 5     5   85 use 5.008004;
  5         44  
6 5     5   38 use Ref::Util qw( is_plain_arrayref );
  5         10  
  5         251  
7 5     5   35 use Carp ();
  5         8  
  5         97  
8 5     5   1754 use Wasm::Trap;
  5         17  
  5         3615  
9              
10             # ABSTRACT: Write Perl extensions using Wasm
11             our $VERSION = '0.23'; # 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   2260 require Wasm::Wasmtime;
24 21   66     86 $linker ||= do {
25 5         1091 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         24 $linker->allow_shadowing(0);
36 5         30 $linker;
37             };
38             }
39              
40             sub import
41             {
42 25     25   11099 my $class = shift;
43 25         106 my($caller, $fn) = caller;
44              
45 25 100       157 return unless @_;
46              
47 23 100 66     125 if(defined $_[0] && $_[0] ne '-api')
48             {
49 1         91 Carp::croak("You MUST specify an api level as the first option");
50             }
51              
52 22         55 my $api;
53             my $exporter;
54 22         0 my @module;
55 22         32 my $package = $caller;
56 22         47 my $file = $fn;
57              
58 22         33 my @global;
59              
60 22         52 while(@_)
61             {
62 46         64 my $key = shift;
63 46 100       193 if($key eq '-api')
    100          
    100          
    100          
    100          
    100          
    50          
    0          
64             {
65 23 100       50 if(defined $api)
66             {
67 1         97 Carp::croak("Specified -api more than once");
68             }
69 22         33 $api = shift;
70 22 100 66     122 unless(defined $api && $api == 0)
71             {
72 1         163 Carp::croak("Currently only -api => 0 is supported");
73             }
74             }
75             elsif($key eq '-wat')
76             {
77 4         8 my $wat = shift;
78 4 50       19 Carp::croak("-wat undefined") unless defined $wat;
79 4         17 @module = (wat => $wat);
80             }
81             elsif($key eq '-file')
82             {
83 6         9 my $path = shift;
84 6 50 33     121 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         22 $file = "$path";
90 6         26 @module = (file => $file);
91             }
92             elsif($key eq '-self')
93             {
94 8         47 require Path::Tiny;
95 8         54 my $perl_path = Path::Tiny->new($fn);
96 8         333 my $basename = $perl_path->basename;
97 8         324 $basename =~ s/\.(pl|pm)$//;
98 8         38 my @maybe = sort { $b->stat->mtime <=> $a->stat->mtime } grep { -f $_ } (
  1         22  
  16         1500  
99             $perl_path->parent->child($basename . ".wasm"),
100             $perl_path->parent->child($basename . ".wat"),
101             );
102 8 50       7180 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         19 $file = shift @maybe;
109 8         42 @module = (file => $file);
110             }
111             }
112             elsif($key eq '-exporter')
113             {
114 3         8 $exporter = shift;
115             }
116             elsif($key eq '-package')
117             {
118 1         3 $package = shift;
119             }
120             elsif($key eq '-global')
121             {
122 1 50       6 if(is_plain_arrayref $_[0])
123             {
124 1         4 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         64 _linker();
142              
143 20 100       62 if(@global)
144             {
145 1 50       9 Carp::croak("Cannot specify both Wasm and -global") if @module;
146 1         3 foreach my $spec (@global)
147             {
148 1         4 my($name, $content, $mutability, $value) = @$spec;
149 1         14 my $global = Wasm::Wasmtime::Global->new(
150             $linker->store,
151             Wasm::Wasmtime::GlobalType->new($content, $mutability),
152             $value,
153             );
154 5     5   57 no strict 'refs';
  5         13  
  5         2583  
155 1         12 *{"${package}::$name"} = $global->tie;
  1         8  
156 1         5 $pp{$package} = $file;
157             }
158 1         67 return;
159             }
160              
161 19 100       54 @module = (wat => '(module)') unless @module;
162              
163 19 50       81 Carp::croak("The wasm_ namespace is reserved for internal use") if $package =~ /^wasi_/;
164 19 50       56 Carp::croak("Wasm for $package already loaded") if $inst{$package};
165              
166 19         92 my $module = Wasm::Wasmtime::Module->new($linker->store->engine, @module);
167              
168 19         43 foreach my $import (@{ $module->imports })
  19         71  
169             {
170              
171 11         34 my $module = $import->module;
172              
173 11 100       156 if($module =~ /^(wasi_unstable|wasi_snapshot_preview1)$/)
174             {
175 2 50       14 next if $WASM{$module};
176 2   33     17 $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         9 next;
192             }
193              
194 9 100 100     52 if($module ne 'main' && !$inst{$module} && !$pp{$module})
      100        
195             {
196 4         11 my $pm = "$module.pm";
197 4         19 $pm =~ s{::}{/}g;
198 4         8 eval { require $pm };
  4         1638  
199 4 100       28 if(my $error = $@)
200             {
201 1         26 $error =~ s/ at (.*?)$//;
202 1         7 $error .= " module required by WebAssembly at $file";
203 1         223 Carp::croak("$error");
204             }
205             }
206              
207 8 100       29 next if $inst{$module};
208              
209 4         29 my $name = $import->name;
210 4         37 my $type = $import->type;
211 4         37 my $kind = $type->kind;
212              
213 4         8 my $extern;
214              
215 4 100       19 if($kind eq 'functype')
    50          
216             {
217 2 50       33 if(my $f = $module->can("${module}::$name"))
218             {
219 2         10 $extern = Wasm::Wasmtime::Func->new(
220             $linker->store,
221             $type,
222             $f,
223             );
224 2         8 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         11  
  5         1532  
  2         4  
  2         3  
  2         13  
230             {
231 2         5 $extern = $global;
232             }
233             }
234              
235 4 50       14 if($extern)
236             {
237             # TODO: check that the store is the same?
238 4         8 eval {
239 4         15 $linker->define(
240             $module,
241             $name,
242             $extern,
243             );
244             };
245 4 100       20 if(my $error = $@)
246             {
247 1 50       10 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       8 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         70 my $instance = $inst{$package} = $linker->instantiate($module);
266 18         70 $linker->define_instance($package, $instance);
267 18         66 $WASM{$package} = "$file";
268              
269 18         66 my @me = @{ $module->exports };
  18         113  
270 18         51 my @ie = @{ $instance->exports };
  18         63  
271              
272 18         53 my @function_names;
273              
274 18         49 for my $i (0..$#ie)
275             {
276 40         73 my $exporttype = $me[$i];
277 40         115 my $name = $me[$i]->name;
278 40         391 my $externtype = $exporttype->type;
279 40         84 my $extern = $ie[$i];
280 40         215 my $kind = $extern->kind;
281 40 100       158 if($kind eq 'func')
    100          
    50          
282             {
283 27         43 my $func = $extern;
284 27         107 $func->attach($package, $name);
285 27         1803 push @function_names, $name;
286             }
287             elsif($kind eq 'global')
288             {
289 2         4 my $global = $extern;
290 5     5   47 no strict 'refs';
  5         11  
  5         423  
291 2         16 *{"${package}::$name"} = $global->tie;
  2         22  
292             }
293             elsif($kind eq 'memory')
294             {
295 11         1983 require Wasm::Memory;
296 11         70 my $memory = Wasm::Memory->new($extern);
297 5     5   35 no strict 'refs';
  5         29  
  5         367  
298 11         26 *{"${package}::$name"} = \$memory;
  11         113  
299             }
300             }
301              
302 18 100       114 if($exporter)
303             {
304 3         19 require Exporter;
305 5     5   34 no strict 'refs';
  5         9  
  5         1102  
306 3         6 push @{ "${package}::ISA" }, 'Exporter';
  3         43  
307 3 100       24 if($exporter eq 'all')
308             {
309 1         3 push @{ "${package}::EXPORT" }, @function_names;
  1         10  
310             }
311             else
312             {
313 2         5 push @{ "${package}::EXPORT_OK" }, @function_names;
  2         17  
314             }
315             }
316             }
317              
318             1;
319              
320             __END__