File Coverage

blib/lib/XS/TCC.pm
Criterion Covered Total %
statement 164 184 89.1
branch 36 58 62.0
condition 5 17 29.4
subroutine 21 24 87.5
pod 1 1 100.0
total 227 284 79.9


line stmt bran cond sub pod time code
1             package XS::TCC;
2 3     3   43606 use 5.10.1;
  3         10  
3 3     3   12 use strict;
  3         4  
  3         62  
4 3     3   12 use warnings;
  3         6  
  3         165  
5              
6             our $VERSION = '0.05';
7              
8             use constant {
9 3         336 TCC_OUTPUT_MEMORY => 0,
10             TCC_OUTPUT_EXE => 1,
11             TCC_OUTPUT_DLL => 2,
12             TCC_OUTPUT_OBJ => 3,
13             TCC_OUTPUT_PREPROCESS => 4,
14 3     3   15 };
  3         3  
15              
16 3     3   13 use Carp ();
  3         3  
  3         48  
17 3     3   10 use Exporter 'import';
  3         2  
  3         96  
18 3     3   10 use XSLoader;
  3         4  
  3         58  
19              
20 3     3   1485 use ExtUtils::Embed ();
  3         6365  
  3         72  
21 3     3   1822 use ExtUtils::Typemaps;
  3         86539  
  3         85  
22 3     3   33 use ExtUtils::ParseXS::Eval;
  3         3  
  3         44  
23 3     3   8 use File::Spec;
  3         4  
  3         42  
24 3     3   1187 use File::ShareDir;
  3         13911  
  3         150  
25 3     3   1133 use Alien::TinyCC;
  3         9918  
  3         71  
26 3     3   18 use Config;
  3         3  
  3         158  
27              
28             # Needed for typemap_func.h:
29             our $RuntimeIncludeDir = File::ShareDir::dist_dir('XS-TCC');
30             our $PerlCoreDir = File::Spec->catfile($Config{archlib}, 'CORE');
31              
32 3     3   961 use XS::TCC::Typemaps;
  3         5  
  3         78  
33 3     3   1087 use XS::TCC::Parser;
  3         3  
  3         1903  
34              
35             XSLoader::load('XS::TCC', $VERSION);
36              
37             our @EXPORT_OK = qw(
38             tcc_inline
39             TCC_OUTPUT_MEMORY
40             TCC_OUTPUT_EXE
41             TCC_OUTPUT_DLL
42             TCC_OUTPUT_OBJ
43             TCC_OUTPUT_PREPROCESS
44             );
45             our %EXPORT_TAGS = (all => \@EXPORT_OK);
46              
47             our $CCOPTS;
48             {
49             local $0 = "NOT A -e LINE!"; # ExtUtils::Embed is daft
50             $CCOPTS = ExtUtils::Embed::ccopts;
51              
52             # -DDEBUGGING causes some 'interesting' corner cases for tcc and
53             # some people/systems, see:
54             # https://rt.perl.org/Public/Bug/Display.html?id=130046
55             # So remove -DDEBUGGING from the tcc invocation by default.
56             $CCOPTS =~ s/\b-DDEBUGGING\b//g;
57             }
58              
59             my $CodeHeader = <<'HERE';
60             #ifndef XS_TCC_INIT
61             #define XS_TCC_INIT
62             /* #define PERL_NO_GET_CONTEXT */
63              
64             #ifdef __XS_TCC_DARWIN__
65             /* http://comments.gmane.org/gmane.comp.compilers.tinycc.devel/325 */
66             typedef unsigned short __uint16_t, uint16_t;
67             typedef unsigned int __uint32_t, uint32_t;
68             typedef unsigned long __uint64_t, uint64_t;
69             #endif
70              
71             #ifdef __XS_TCC_WIN__
72             #define __C89_NAMELESS
73             #define __MINGW_EXTENSION
74             typedef long __int64;
75             typedef int uid_t;
76             typedef int gid_t;
77             #endif
78              
79             #include
80             #include
81             #include
82              
83             #ifdef HAS_BUILTIN_EXPECT
84             # undef HAS_BUILTIN_EXPECT
85             # ifdef EXPECT
86             # undef EXPECT
87             # define EXPECT(expr, val) (expr)
88             # endif
89             #endif
90              
91             #include
92              
93             /* The XS_EXTERNAL macro is used for functions that must not be static
94             * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
95             * macro defined, the best we can do is assume XS is the same.
96             * Dito for XS_INTERNAL.
97             */
98             #ifndef XS_EXTERNAL
99             # define XS_EXTERNAL(name) XS(name)
100             #endif
101             #ifndef XS_INTERNAL
102             # define XS_INTERNAL(name) XS(name)
103             #endif
104              
105             #ifndef PERL_UNUSED_VAR
106             # define PERL_UNUSED_VAR(var) if (0) var = var
107             #endif
108              
109             #ifndef dVAR
110             # define dVAR dNOOP
111             #endif
112              
113              
114             #ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
115             #define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
116              
117             /* prototype to pass -Wmissing-prototypes */
118             STATIC void
119             S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params);
120              
121             STATIC void
122             S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params)
123             {
124             const GV *const gv = CvGV(cv);
125              
126             PERL_ARGS_ASSERT_CROAK_XS_USAGE;
127              
128             if (gv) {
129             const char *const gvname = GvNAME(gv);
130             const HV *const stash = GvSTASH(gv);
131             const char *const hvname = stash ? HvNAME(stash) : NULL;
132              
133             if (hvname)
134             Perl_croak(aTHX_ "Usage: %s::%s(%s)", hvname, gvname, params);
135             else
136             Perl_croak(aTHX_ "Usage: %s(%s)", gvname, params);
137             } else {
138             /* Pants. I don't think that it should be possible to get here. */
139             Perl_croak(aTHX_ "Usage: CODE(0x%"UVxf")(%s)", PTR2UV(cv), params);
140             }
141             }
142             #undef PERL_ARGS_ASSERT_CROAK_XS_USAGE
143              
144             #ifdef PERL_IMPLICIT_CONTEXT
145             # define croak_xs_usage(a,b) S_croak_xs_usage(aTHX_ a,b)
146             #else
147             # define croak_xs_usage S_croak_xs_usage
148             #endif
149              
150             #endif
151              
152             #endif /* XS_TCC_INIT */
153             HERE
154              
155             SCOPE: {
156             my @compilers; # never die...
157             #my $compiler;
158             sub _get_compiler {
159             #return $compiler if $compiler;
160 6     6   1209 my $compiler = XS::TCC::TCCState->new;
161 6         31 $compiler->add_sysinclude_path($RuntimeIncludeDir);
162 6         16 $compiler->add_sysinclude_path($PerlCoreDir);
163 6 50       42 if ($^O eq 'darwin') {
    50          
164 0         0 $compiler->define_symbol("__XS_TCC_DARWIN__", 1);
165             }
166             elsif ($^O =~ /MSWin/) {
167 0         0 $compiler->define_symbol("__XS_TCC_WIN__", 1);
168             }
169             #push @compilers, $compiler;
170 6         11 return $compiler;
171             } # end _get_compiler
172             } # end SCOPE
173              
174              
175             SCOPE: {
176             my $core_typemap;
177             sub _get_core_typemap {
178 8 100   8   27 return $core_typemap if $core_typemap;
179              
180 1         2 my @tm;
181 1         2 foreach my $dir (@INC) {
182 11         47 my $file = File::Spec->catfile($dir, ExtUtils => 'typemap');
183 11 100       136 unshift @tm, $file if -e $file;
184             }
185              
186 1         10 $core_typemap = ExtUtils::Typemaps->new();
187 1         37 foreach my $typemap_loc (@tm) {
188 1 50       26 next unless -f $typemap_loc;
189             # skip directories, binary files etc.
190 1 50       60 warn("Warning: ignoring non-text typemap file '$typemap_loc'\n"), next
191             unless -T $typemap_loc;
192              
193 1         8 $core_typemap->merge(file => $typemap_loc, replace => 1);
194             }
195              
196             # Override core typemaps with custom function-based replacements.
197             # This is because GCC compiled functions are likely faster than inlined code in TCC.
198 1         14844 $core_typemap->merge(replace => 1, typemap => $XS::TCC::Typemaps::Typemap);
199              
200 1         1262 return $core_typemap;
201             } # end _get_core_typemap
202             } # end SCOPE
203              
204             # current options:
205             # code, warn_code, package, typemap, add_files, ccopts
206             sub tcc_inline (@) {
207 8     8 1 5675 my $code;
208              
209 8 50       40 $code = pop @_ if @_ % 2;
210 8         16 my %args = @_;
211              
212 8 50 33     53 if (defined $code and defined $args{code}) {
213 0         0 Carp::croak("Can't specify code both as a named and as a positional parameter");
214             }
215 8   33     22 $code //= $args{code};
216 8 50       17 Carp::croak("Need code to compile") if not defined $code;
217              
218 8   33     51 my $package = $args{package} // (caller())[0];
219              
220             # Set up the typemap object if any (defaulting to core typemaps)
221 8         11 my $typemap;
222 8         10 my $typemap_arg = $args{typemap};
223 8 100       22 if (not defined($typemap_arg)) {
    50          
224 7         17 $typemap = _get_core_typemap();
225             }
226             elsif (ref($typemap_arg)) {
227 1         3 $typemap = _get_core_typemap()->clone(shallow => 1);
228 1         62 $typemap->merge(typemap => $typemap_arg);
229             }
230             else {
231 0         0 $typemap = _get_core_typemap()->clone(shallow => 1);
232 0         0 $typemap->add_string(string => $typemap_arg);
233             }
234              
235             # Function signature parsing
236 8         43 my $parse_result = XS::TCC::Parser::extract_function_metadata($code);
237             return
238             if not $parse_result
239 8 100 33     23 or not @{$parse_result->{function_names}};
  8         36  
240              
241             # eval the typemaps for the function sig
242 6         13 my @code = ($CodeHeader, $code);
243 6         7 foreach my $cfun_name (@{$parse_result->{function_names}}) {
  6         16  
244 8         8 my $fun_info = $parse_result->{functions}{$cfun_name};
245 8         18 my $xs_fun = _gen_single_function_xs_wrapper($package, $cfun_name, $fun_info, $typemap, \@code);
246 8         18 $fun_info->{xs_function_name} = $xs_fun;
247             }
248              
249 6         32 my $final_code = join "\n", @code;
250              
251 6 50       15 warn _add_line_nums($final_code) if $args{warn_code};
252              
253 6         14 my $compiler = _get_compiler();
254              
255             # Code to catch compile errors
256 6         6 my $errmsg;
257 6     0   22 my $err_hook = sub { $errmsg = $_[0] };
  0         0  
258              
259 6         19 $compiler->set_error_callback($err_hook);
260              
261             # Add user-specified files
262 6         4 my @add_files;
263 0         0 @add_files = ref($args{add_files}) ? @{$args{add_files}} : $args{add_files}
264 6 50       18 if defined $args{add_files};
    100          
265 6         81 $compiler->add_file($_) for @add_files;
266              
267             # Do the compilation
268 6   33     105 $compiler->set_options(($args{ccopts} // $CCOPTS));
269             # compile_string() returns 0 if succeeded, -1 otherwise.
270 6         420281 my $fatal = $compiler->compile_string($final_code);
271 6         3182 $compiler->relocate();
272              
273 6 50       43 if (defined $errmsg) {
274 0         0 $errmsg = _build_compile_error_msg($errmsg, 1);
275 0 0       0 if ($fatal) {
276 0         0 Carp::croak($errmsg);
277             } else {
278 0         0 Carp::carp($errmsg);
279             }
280             }
281              
282             # install the XSUBs
283 6         16 foreach my $cfun_name (@{$parse_result->{function_names}}) {
  6         58  
284 8         30 my $fun_info = $parse_result->{functions}{$cfun_name};
285 8         56 my $sym = $compiler->get_symbol($fun_info->{xs_function_name});
286 8         50 my $perl_name = $package . "::" . $cfun_name;
287 8         59 my $sub = $sym->as_xsub();
288 3     3   15 no strict 'refs';
  3         4  
  3         2176  
289 8         13 *{"$perl_name"} = $sub;
  8         5016  
290             }
291              
292             }
293              
294              
295             sub _build_compile_error_msg {
296 0     0   0 my ($msg, $caller_level) = @_;
297 0         0 $caller_level++;
298             # TODO write code to emit file/line info
299 0         0 return $msg;
300             }
301              
302             sub _gen_single_function_xs_wrapper {
303 8     8   13 my ($package, $cfun_name, $fun_info, $typemap, $code_ary) = @_;
304              
305 8         10 my $arg_names = $fun_info->{arg_names};
306 8         12 my $nparams = scalar(@$arg_names);
307 8         15 my $arg_names_str = join ", ", map {s/\W/_/; $_} @$arg_names;
  7         10  
  7         18  
308              
309             # Return type and output typemap preparation
310 8         8 my $ret_type = $fun_info->{return_type};
311 8         11 my $is_void_function = $ret_type eq 'void';
312 8 50       16 my $retval_decl = $is_void_function ? '' : "$ret_type RETVAL;";
313              
314 8         6 my $out_typemap;
315             my $outputmap;
316 8         29 my $dxstarg = "";
317 8 50       17 if (not $is_void_function) {
318 8         38 $out_typemap = $typemap->get_typemap(ctype => $ret_type);
319 8 50       221 $outputmap = $out_typemap
320             ? $typemap->get_outputmap(xstype => $out_typemap->xstype)
321             : undef;
322 8 50       139 Carp::croak("No output typemap found for return type '$ret_type'")
323             if not $outputmap;
324             # TODO implement TARG optimization below
325             #$dxstarg = $outputmap->targetable ? " dXSTARG;" : "";
326             }
327              
328             # Emit function header and declarations
329 8         15 (my $xs_pkg_name = $package) =~ s/:+/_/g;
330 8         15 my $xs_fun_name = "XS_${xs_pkg_name}_$cfun_name";
331 8         38 push @$code_ary, <
332             XS_EXTERNAL($xs_fun_name); /* prototype to pass -Wmissing-prototypes */
333             XS_EXTERNAL($xs_fun_name)
334             {
335             dVAR; dXSARGS;$dxstarg
336             if (items != $nparams)
337             croak_xs_usage(cv, "$arg_names_str");
338             /* PERL_UNUSED_VAR(ax); */ /* -Wall */
339             /* SP -= items; */
340             {
341             $retval_decl
342              
343              
344             FUN_HEADER
345              
346 8         10 my $do_pass_threading_context = $fun_info->{need_threading_context};
347              
348             # emit input typemaps
349 8         9 my @input_decl;
350             my @input_assign;
351 8         10 for my $argno (0..$#{$fun_info->{arg_names}}) {
  8         32  
352 7         11 my $aname = $fun_info->{arg_names}[$argno];
353 7         9 my $atype = $fun_info->{arg_types}[$argno];
354 7         11 (my $decl_type = $atype) =~ s/^\s*const\b\s*//;
355              
356 7         15 my $tm = $typemap->get_typemap(ctype => $atype);
357 7 50       131 my $im = !$tm ? undef : $typemap->get_inputmap(xstype => $tm->xstype);
358              
359 7 50       95 Carp::croak("No input typemap found for type '$atype'")
360             if not $im;
361 7         29 my $imcode = $im->cleaned_code;
362              
363 7         365 my $vars = {
364             Package => $package,
365             ALIAS => $cfun_name,
366             func_name => $cfun_name,
367             Full_func_name => $cfun_name,
368             pname => $package . "::" . $cfun_name,
369             type => $decl_type,
370             ntype => $decl_type,
371             arg => "ST($argno)",
372             var => $aname,
373             init => undef,
374             # FIXME some of these are guesses at their true meaning. Validate in EU::PXS
375             num => $argno,
376             printed_name => $aname,
377             argoff => $argno,
378             };
379              
380             # FIXME do we want to support the obscure ARRAY/Ptr logic (subtype, ntype)?
381 7         31 my $out = ExtUtils::ParseXS::Eval::eval_input_typemap_code(
382             $vars, qq{"$imcode"}, $vars
383             );
384              
385 7         677 $out =~ s/;\s*$//;
386 7 100       203 if ($out =~ /^\s*\Q$aname\E\s*=/) {
387 5         32 push @input_decl, " $decl_type $out;";
388             }
389             else {
390 2         6 push @input_decl, " $decl_type $aname;";
391 2         13 push @input_assign, " $out;";
392             }
393             }
394 8         17 push @$code_ary, @input_decl, @input_assign;
395              
396             # emit function call
397 8 50       14 my $fun_call_assignment = $is_void_function ? "" : "RETVAL = ";
398 8         7 my $arglist = join ", ", @{ $fun_info->{arg_names} };
  8         18  
399 8         10 my $threading_context = "";
400 8 100       19 if ($do_pass_threading_context) {
401 3 100       4 $threading_context = scalar(@{ $fun_info->{arg_names} }) == 0
  3         9  
402             ? "aTHX " : "aTHX_ ";
403             }
404 8         28 push @$code_ary, " ${fun_call_assignment}$cfun_name($threading_context$arglist);\n";
405              
406             # emit output typemap
407 8 50       16 if (not $is_void_function) {
408 8         30 my $omcode = $outputmap->cleaned_code;
409 8         158 my $vars = {
410             Package => $package,
411             ALIAS => $cfun_name,
412             func_name => $cfun_name,
413             Full_func_name => $cfun_name,
414             pname => $package . "::" . $cfun_name,
415             type => $ret_type,
416             ntype => $ret_type,
417             arg => "ST(0)",
418             var => "RETVAL",
419             };
420              
421             # FIXME do we want to support the obscure ARRAY/Ptr logic (subtype, ntype)?
422              
423             # TODO TARG ($om->targetable) optimization!
424 8         28 my $out = ExtUtils::ParseXS::Eval::eval_output_typemap_code(
425             $vars, qq{"$omcode"}, $vars
426             );
427 8         483 push @$code_ary, " ST(0) = sv_newmortal();";
428 8         25 push @$code_ary, " " . $out;
429             }
430              
431              
432 8 50       14 my $nreturnvalues = $is_void_function ? 0 : 1;
433 8         14 push @$code_ary, <
434             }
435             XSRETURN($nreturnvalues);
436             }
437             FUN_FOOTER
438              
439 8         20 return($xs_fun_name);
440             }
441              
442             # just for debugging
443             sub _add_line_nums {
444 0     0     my $code = shift;
445 0   0       my $i = shift || 1;
446 0           my @l = split /\n/, $code;
447 0           my $n = @l + $i - 1;
448 0           my $len = length($n);
449 0           return join("\n", map sprintf("% ${len}u: %s", $i++, $_), @l);
450             }
451              
452             1;
453              
454             __END__