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   1263 use strict;
  5         12  
  5         193  
4 5     5   28 use warnings;
  5         28  
  5         128  
5 5     5   94 use 5.008004;
  5         24  
6 5     5   31 use Ref::Util qw( is_plain_arrayref );
  5         10  
  5         230  
7 5     5   27 use Carp ();
  5         9  
  5         146  
8 5     5   1701 use Wasm::Trap;
  5         18  
  5         3407  
9              
10             # ABSTRACT: Write Perl extensions using Wasm
11             our $VERSION = '0.21'; # 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   2410 require Wasm::Wasmtime;
24 21   66     95 $linker ||= do {
25 5         985 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         29 $linker->allow_shadowing(0);
36 5         27 $linker;
37             };
38             }
39              
40             sub import
41             {
42 25     25   10808 my $class = shift;
43 25         120 my($caller, $fn) = caller;
44              
45 25 100       156 return unless @_;
46              
47 23 100 66     153 if(defined $_[0] && $_[0] ne '-api')
48             {
49 1         93 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         40 my $package = $caller;
56 22         36 my $file = $fn;
57              
58 22         38 my @global;
59              
60 22         64 while(@_)
61             {
62 46         72 my $key = shift;
63 46 100       166 if($key eq '-api')
    100          
    100          
    100          
    100          
    100          
    50          
    0          
64             {
65 23 100       53 if(defined $api)
66             {
67 1         96 Carp::croak("Specified -api more than once");
68             }
69 22         38 $api = shift;
70 22 100 66     136 unless(defined $api && $api == 0)
71             {
72 1         169 Carp::croak("Currently only -api => 0 is supported");
73             }
74             }
75             elsif($key eq '-wat')
76             {
77 4         9 my $wat = shift;
78 4 50       14 Carp::croak("-wat undefined") unless defined $wat;
79 4         16 @module = (wat => $wat);
80             }
81             elsif($key eq '-file')
82             {
83 6         18 my $path = shift;
84 6 50 33     126 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         36 require Path::Tiny;
95 8         57 my $perl_path = Path::Tiny->new($fn);
96 8         369 my $basename = $perl_path->basename;
97 8         386 $basename =~ s/\.(pl|pm)$//;
98 8         59 my @maybe = sort { $b->stat->mtime <=> $a->stat->mtime } grep { -f $_ } (
  1         25  
  16         1498  
99             $perl_path->parent->child($basename . ".wasm"),
100             $perl_path->parent->child($basename . ".wat"),
101             );
102 8 50       6987 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         18 $file = shift @maybe;
109 8         45 @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         5 $package = shift;
119             }
120             elsif($key eq '-global')
121             {
122 1 50       8 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         72 _linker();
142              
143 20 100       68 if(@global)
144             {
145 1 50       4 Carp::croak("Cannot specify both Wasm and -global") if @module;
146 1         5 foreach my $spec (@global)
147             {
148 1         4 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   44 no strict 'refs';
  5         11  
  5         2515  
155 1         11 *{"${package}::$name"} = $global->tie;
  1         11  
156 1         6 $pp{$package} = $file;
157             }
158 1         77 return;
159             }
160              
161 19 100       69 @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       70 Carp::croak("Wasm for $package already loaded") if $inst{$package};
165              
166 19         81 my $module = Wasm::Wasmtime::Module->new($linker->store->engine, @module);
167              
168 19         41 foreach my $import (@{ $module->imports })
  19         72  
169             {
170              
171 11         43 my $module = $import->module;
172              
173 11 100       130 if($module =~ /^(wasi_unstable|wasi_snapshot_preview1)$/)
174             {
175 2 50       10 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         10 $WASM{$module} = __FILE__; # Maybe Wasi::Snapshot::Preview1 etc.
191 2         7 next;
192             }
193              
194 9 100 100     66 if($module ne 'main' && !$inst{$module} && !$pp{$module})
      100        
195             {
196 4         12 my $pm = "$module.pm";
197 4         16 $pm =~ s{::}{/}g;
198 4         8 eval { require $pm };
  4         1563  
199 4 100       23 if(my $error = $@)
200             {
201 1         14 $error =~ s/ at (.*?)$//;
202 1         6 $error .= " module required by WebAssembly at $file";
203 1         324 Carp::croak("$error");
204             }
205             }
206              
207 8 100       29 next if $inst{$module};
208              
209 4         22 my $name = $import->name;
210 4         39 my $type = $import->type;
211 4         51 my $kind = $type->kind;
212              
213 4         9 my $extern;
214              
215 4 100       26 if($kind eq 'functype')
    50          
216             {
217 2 50       32 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         12 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         19  
  5         1514  
  2         5  
  2         2  
  2         16  
230             {
231 2         4 $extern = $global;
232             }
233             }
234              
235 4 50       16 if($extern)
236             {
237             # TODO: check that the store is the same?
238 4         8 eval {
239 4         19 $linker->define(
240             $module,
241             $name,
242             $extern,
243             );
244             };
245 4 100       21 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       12 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         82 my $instance = $inst{$package} = $linker->instantiate($module);
266 18         80 $linker->define_instance($package, $instance);
267 18         63 $WASM{$package} = "$file";
268              
269 18         62 my @me = @{ $module->exports };
  18         82  
270 18         65 my @ie = @{ $instance->exports };
  18         129  
271              
272 18         58 my @function_names;
273              
274 18         48 for my $i (0..$#ie)
275             {
276 40         76 my $exporttype = $me[$i];
277 40         115 my $name = $me[$i]->name;
278 40         368 my $externtype = $exporttype->type;
279 40         123 my $extern = $ie[$i];
280 40         206 my $kind = $extern->kind;
281 40 100       152 if($kind eq 'func')
    100          
    50          
282             {
283 27         41 my $func = $extern;
284 27         120 $func->attach($package, $name);
285 27         2241 push @function_names, $name;
286             }
287             elsif($kind eq 'global')
288             {
289 2         4 my $global = $extern;
290 5     5   42 no strict 'refs';
  5         12  
  5         411  
291 2         11 *{"${package}::$name"} = $global->tie;
  2         22  
292             }
293             elsif($kind eq 'memory')
294             {
295 11         1971 require Wasm::Memory;
296 11         116 my $memory = Wasm::Memory->new($extern);
297 5     5   34 no strict 'refs';
  5         10  
  5         388  
298 11         22 *{"${package}::$name"} = \$memory;
  11         103  
299             }
300             }
301              
302 18 100       102 if($exporter)
303             {
304 3         15 require Exporter;
305 5     5   43 no strict 'refs';
  5         20  
  5         1053  
306 3         6 push @{ "${package}::ISA" }, 'Exporter';
  3         43  
307 3 100       14 if($exporter eq 'all')
308             {
309 1         2 push @{ "${package}::EXPORT" }, @function_names;
  1         10  
310             }
311             else
312             {
313 2         4 push @{ "${package}::EXPORT_OK" }, @function_names;
  2         17  
314             }
315             }
316             }
317              
318             1;
319              
320             __END__