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