File Coverage

blib/lib/XS/TCC.pm
Criterion Covered Total %
statement 163 182 89.5
branch 34 56 60.7
condition 5 17 29.4
subroutine 20 23 86.9
pod 0 1 0.0
total 222 279 79.5


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