1#!/usr/bin/env perl
2
3no strict 'refs';
4use warnings;
5use Getopt::Long;
6Getopt::Long::Configure("auto_help") if $Getopt::Long::VERSION > 2.32;
7
8my %ALL_FUNCS = ();
9my @ALL_ARCHS;
10my @ALL_FORWARD_DECLS;
11my @REQUIRES;
12
13my %opts = ();
14my %disabled = ();
15my %required = ();
16
17my @argv;
18foreach (@ARGV) {
19  $disabled{$1} = 1, next if /--disable-(.*)/;
20  $required{$1} = 1, next if /--require-(.*)/;
21  push @argv, $_;
22}
23
24# NB: use GetOptions() instead of GetOptionsFromArray() for compatibility.
25@ARGV = @argv;
26GetOptions(
27  \%opts,
28  'arch=s',
29  'sym=s',
30  'config=s',
31);
32
33foreach my $opt (qw/arch config/) {
34  if (!defined($opts{$opt})) {
35    warn "--$opt is required!\n";
36    Getopt::Long::HelpMessage('-exit' => 1);
37  }
38}
39
40foreach my $defs_file (@ARGV) {
41  if (!-f $defs_file) {
42    warn "$defs_file: $!\n";
43    Getopt::Long::HelpMessage('-exit' => 1);
44  }
45}
46
47open CONFIG_FILE, $opts{config} or
48  die "Error opening config file '$opts{config}': $!\n";
49
50my %config = ();
51while (<CONFIG_FILE>) {
52  next if !/^(?:CONFIG_|HAVE_)/;
53  chomp;
54  my @pair = split /=/;
55  $config{$pair[0]} = $pair[1];
56}
57close CONFIG_FILE;
58
59#
60# Routines for the RTCD DSL to call
61#
62sub vpx_config($) {
63  return (defined $config{$_[0]}) ? $config{$_[0]} : "";
64}
65
66sub specialize {
67  my $fn=$_[0];
68  shift;
69  foreach my $opt (@_) {
70    eval "\$${fn}_${opt}=${fn}_${opt}";
71  }
72}
73
74sub add_proto {
75  my $fn = splice(@_, -2, 1);
76  $ALL_FUNCS{$fn} = \@_;
77  specialize $fn, "c";
78}
79
80sub require {
81  foreach my $fn (keys %ALL_FUNCS) {
82    foreach my $opt (@_) {
83      my $ofn = eval "\$${fn}_${opt}";
84      next if !$ofn;
85
86      # if we already have a default, then we can disable it, as we know
87      # we can do better.
88      my $best = eval "\$${fn}_default";
89      if ($best) {
90        my $best_ofn = eval "\$${best}";
91        if ($best_ofn && "$best_ofn" ne "$ofn") {
92          eval "\$${best}_link = 'false'";
93        }
94      }
95      eval "\$${fn}_default=${fn}_${opt}";
96      eval "\$${fn}_${opt}_link='true'";
97    }
98  }
99}
100
101sub forward_decls {
102  push @ALL_FORWARD_DECLS, @_;
103}
104
105#
106# Include the user's directives
107#
108foreach my $f (@ARGV) {
109  open FILE, "<", $f or die "cannot open $f: $!\n";
110  my $contents = join('', <FILE>);
111  close FILE;
112  eval $contents or warn "eval failed: $@\n";
113}
114
115#
116# Process the directives according to the command line
117#
118sub process_forward_decls() {
119  foreach (@ALL_FORWARD_DECLS) {
120    $_->();
121  }
122}
123
124sub determine_indirection {
125  vpx_config("CONFIG_RUNTIME_CPU_DETECT") eq "yes" or &require(@ALL_ARCHS);
126  foreach my $fn (keys %ALL_FUNCS) {
127    my $n = "";
128    my @val = @{$ALL_FUNCS{$fn}};
129    my $args = pop @val;
130    my $rtyp = "@val";
131    my $dfn = eval "\$${fn}_default";
132    $dfn = eval "\$${dfn}";
133    foreach my $opt (@_) {
134      my $ofn = eval "\$${fn}_${opt}";
135      next if !$ofn;
136      my $link = eval "\$${fn}_${opt}_link";
137      next if $link && $link eq "false";
138      $n .= "x";
139    }
140    if ($n eq "x") {
141      eval "\$${fn}_indirect = 'false'";
142    } else {
143      eval "\$${fn}_indirect = 'true'";
144    }
145  }
146}
147
148sub declare_function_pointers {
149  foreach my $fn (sort keys %ALL_FUNCS) {
150    my @val = @{$ALL_FUNCS{$fn}};
151    my $args = pop @val;
152    my $rtyp = "@val";
153    my $dfn = eval "\$${fn}_default";
154    $dfn = eval "\$${dfn}";
155    foreach my $opt (@_) {
156      my $ofn = eval "\$${fn}_${opt}";
157      next if !$ofn;
158      print "$rtyp ${ofn}($args);\n";
159    }
160    if (eval "\$${fn}_indirect" eq "false") {
161      print "#define ${fn} ${dfn}\n";
162    } else {
163      print "RTCD_EXTERN $rtyp (*${fn})($args);\n";
164    }
165    print "\n";
166  }
167}
168
169sub set_function_pointers {
170  foreach my $fn (sort keys %ALL_FUNCS) {
171    my @val = @{$ALL_FUNCS{$fn}};
172    my $args = pop @val;
173    my $rtyp = "@val";
174    my $dfn = eval "\$${fn}_default";
175    $dfn = eval "\$${dfn}";
176    if (eval "\$${fn}_indirect" eq "true") {
177      print "    $fn = $dfn;\n";
178      foreach my $opt (@_) {
179        my $ofn = eval "\$${fn}_${opt}";
180        next if !$ofn;
181        next if "$ofn" eq "$dfn";
182        my $link = eval "\$${fn}_${opt}_link";
183        next if $link && $link eq "false";
184        my $cond = eval "\$have_${opt}";
185        print "    if (${cond}) $fn = $ofn;\n"
186      }
187    }
188  }
189}
190
191sub filter {
192  my @filtered;
193  foreach (@_) { push @filtered, $_ unless $disabled{$_}; }
194  return @filtered;
195}
196
197#
198# Helper functions for generating the arch specific RTCD files
199#
200sub common_top() {
201  my $include_guard = uc($opts{sym})."_H_";
202  print <<EOF;
203#ifndef ${include_guard}
204#define ${include_guard}
205
206#ifdef RTCD_C
207#define RTCD_EXTERN
208#else
209#define RTCD_EXTERN extern
210#endif
211
212EOF
213
214process_forward_decls();
215print <<EOF;
216
217#ifdef __cplusplus
218extern "C" {
219#endif
220
221EOF
222declare_function_pointers("c", @ALL_ARCHS);
223
224print <<EOF;
225void $opts{sym}(void);
226
227EOF
228}
229
230sub common_bottom() {
231  print <<EOF;
232
233#ifdef __cplusplus
234}  // extern "C"
235#endif
236
237#endif
238EOF
239}
240
241sub x86() {
242  determine_indirection("c", @ALL_ARCHS);
243
244  # Assign the helper variable for each enabled extension
245  foreach my $opt (@ALL_ARCHS) {
246    my $opt_uc = uc $opt;
247    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
248  }
249
250  common_top;
251  print <<EOF;
252#ifdef RTCD_C
253#include "vpx_ports/x86.h"
254static void setup_rtcd_internal(void)
255{
256    int flags = x86_simd_caps();
257
258    (void)flags;
259
260EOF
261
262  set_function_pointers("c", @ALL_ARCHS);
263
264  print <<EOF;
265}
266#endif
267EOF
268  common_bottom;
269}
270
271sub arm() {
272  determine_indirection("c", @ALL_ARCHS);
273
274  # Assign the helper variable for each enabled extension
275  foreach my $opt (@ALL_ARCHS) {
276    my $opt_uc = uc $opt;
277    # Enable neon assembly based on HAVE_NEON logic instead of adding new
278    # HAVE_NEON_ASM logic
279    if ($opt eq 'neon_asm') { $opt_uc = 'NEON' }
280    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
281  }
282
283  common_top;
284  print <<EOF;
285#include "vpx_config.h"
286
287#ifdef RTCD_C
288#include "vpx_ports/arm.h"
289static void setup_rtcd_internal(void)
290{
291    int flags = arm_cpu_caps();
292
293    (void)flags;
294
295EOF
296
297  set_function_pointers("c", @ALL_ARCHS);
298
299  print <<EOF;
300}
301#endif
302EOF
303  common_bottom;
304}
305
306sub mips() {
307  determine_indirection("c", @ALL_ARCHS);
308  common_top;
309
310  print <<EOF;
311#include "vpx_config.h"
312
313#ifdef RTCD_C
314static void setup_rtcd_internal(void)
315{
316EOF
317
318  set_function_pointers("c", @ALL_ARCHS);
319
320  print <<EOF;
321#if HAVE_DSPR2
322#if CONFIG_VP8
323void dsputil_static_init();
324dsputil_static_init();
325#endif
326#if CONFIG_VP9
327void vp9_dsputil_static_init();
328vp9_dsputil_static_init();
329#endif
330#endif
331}
332#endif
333EOF
334  common_bottom;
335}
336
337sub unoptimized() {
338  determine_indirection "c";
339  common_top;
340  print <<EOF;
341#include "vpx_config.h"
342
343#ifdef RTCD_C
344static void setup_rtcd_internal(void)
345{
346EOF
347
348  set_function_pointers "c";
349
350  print <<EOF;
351}
352#endif
353EOF
354  common_bottom;
355}
356
357#
358# Main Driver
359#
360
361&require("c");
362if ($opts{arch} eq 'x86') {
363  @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2/);
364  x86;
365} elsif ($opts{arch} eq 'x86_64') {
366  @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2/);
367  @REQUIRES = filter(keys %required ? keys %required : qw/mmx sse sse2/);
368  &require(@REQUIRES);
369  x86;
370} elsif ($opts{arch} eq 'mips32' || $opts{arch} eq 'mips64') {
371  @ALL_ARCHS = filter("$opts{arch}");
372  open CONFIG_FILE, $opts{config} or
373    die "Error opening config file '$opts{config}': $!\n";
374  while (<CONFIG_FILE>) {
375    if (/HAVE_DSPR2=yes/) {
376      @ALL_ARCHS = filter("$opts{arch}", qw/dspr2/);
377      last;
378    }
379  }
380  close CONFIG_FILE;
381  mips;
382} elsif ($opts{arch} eq 'armv5te') {
383  @ALL_ARCHS = filter(qw/edsp/);
384  arm;
385} elsif ($opts{arch} eq 'armv6') {
386  @ALL_ARCHS = filter(qw/edsp media/);
387  arm;
388} elsif ($opts{arch} eq 'armv7') {
389  @ALL_ARCHS = filter(qw/edsp media neon_asm neon/);
390  @REQUIRES = filter(keys %required ? keys %required : qw/media/);
391  &require(@REQUIRES);
392  arm;
393} elsif ($opts{arch} eq 'armv8' || $opts{arch} eq 'arm64' ) {
394  @ALL_ARCHS = filter(qw/neon/);
395  arm;
396} else {
397  unoptimized;
398}
399
400__END__
401
402=head1 NAME
403
404rtcd -
405
406=head1 SYNOPSIS
407
408Usage: rtcd.pl [options] FILE
409
410See 'perldoc rtcd.pl' for more details.
411
412=head1 DESCRIPTION
413
414Reads the Run Time CPU Detections definitions from FILE and generates a
415C header file on stdout.
416
417=head1 OPTIONS
418
419Options:
420  --arch=ARCH       Architecture to generate defs for (required)
421  --disable-EXT     Disable support for EXT extensions
422  --require-EXT     Require support for EXT extensions
423  --sym=SYMBOL      Unique symbol to use for RTCD initialization function
424  --config=FILE     File with CONFIG_FOO=yes lines to parse
425