1#!/usr/bin/env perl
2##
3##  Copyright (c) 2017 The WebM project authors. All Rights Reserved.
4##
5##  Use of this source code is governed by a BSD-style license
6##  that can be found in the LICENSE file in the root of the source
7##  tree. An additional intellectual property rights grant can be found
8##  in the file PATENTS.  All contributing project authors may
9##  be found in the AUTHORS file in the root of the source tree.
10##
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 !/^(?:CONFIG_|HAVE_)/;
62  chomp;
63  my @pair = split /=/;
64  $config{$pair[0]} = $pair[1];
65}
66close CONFIG_FILE;
67
68#
69# Routines for the RTCD DSL to call
70#
71sub vpx_config($) {
72  return (defined $config{$_[0]}) ? $config{$_[0]} : "";
73}
74
75sub specialize {
76  my $fn=$_[0];
77  shift;
78  foreach my $opt (@_) {
79    eval "\$${fn}_${opt}=${fn}_${opt}";
80  }
81}
82
83sub add_proto {
84  my $fn = splice(@_, -2, 1);
85  $ALL_FUNCS{$fn} = \@_;
86  specialize $fn, "c";
87}
88
89sub require {
90  foreach my $fn (keys %ALL_FUNCS) {
91    foreach my $opt (@_) {
92      my $ofn = eval "\$${fn}_${opt}";
93      next if !$ofn;
94
95      # if we already have a default, then we can disable it, as we know
96      # we can do better.
97      my $best = eval "\$${fn}_default";
98      if ($best) {
99        my $best_ofn = eval "\$${best}";
100        if ($best_ofn && "$best_ofn" ne "$ofn") {
101          eval "\$${best}_link = 'false'";
102        }
103      }
104      eval "\$${fn}_default=${fn}_${opt}";
105      eval "\$${fn}_${opt}_link='true'";
106    }
107  }
108}
109
110sub forward_decls {
111  push @ALL_FORWARD_DECLS, @_;
112}
113
114#
115# Include the user's directives
116#
117foreach my $f (@ARGV) {
118  open FILE, "<", $f or die "cannot open $f: $!\n";
119  my $contents = join('', <FILE>);
120  close FILE;
121  eval $contents or warn "eval failed: $@\n";
122}
123
124#
125# Process the directives according to the command line
126#
127sub process_forward_decls() {
128  foreach (@ALL_FORWARD_DECLS) {
129    $_->();
130  }
131}
132
133sub determine_indirection {
134  vpx_config("CONFIG_RUNTIME_CPU_DETECT") eq "yes" or &require(@ALL_ARCHS);
135  foreach my $fn (keys %ALL_FUNCS) {
136    my $n = "";
137    my @val = @{$ALL_FUNCS{$fn}};
138    my $args = pop @val;
139    my $rtyp = "@val";
140    my $dfn = eval "\$${fn}_default";
141    $dfn = eval "\$${dfn}";
142    foreach my $opt (@_) {
143      my $ofn = eval "\$${fn}_${opt}";
144      next if !$ofn;
145      my $link = eval "\$${fn}_${opt}_link";
146      next if $link && $link eq "false";
147      $n .= "x";
148    }
149    if ($n eq "x") {
150      eval "\$${fn}_indirect = 'false'";
151    } else {
152      eval "\$${fn}_indirect = 'true'";
153    }
154  }
155}
156
157sub declare_function_pointers {
158  foreach my $fn (sort keys %ALL_FUNCS) {
159    my @val = @{$ALL_FUNCS{$fn}};
160    my $args = pop @val;
161    my $rtyp = "@val";
162    my $dfn = eval "\$${fn}_default";
163    $dfn = eval "\$${dfn}";
164    foreach my $opt (@_) {
165      my $ofn = eval "\$${fn}_${opt}";
166      next if !$ofn;
167      print "$rtyp ${ofn}($args);\n";
168    }
169    if (eval "\$${fn}_indirect" eq "false") {
170      print "#define ${fn} ${dfn}\n";
171    } else {
172      print "RTCD_EXTERN $rtyp (*${fn})($args);\n";
173    }
174    print "\n";
175  }
176}
177
178sub set_function_pointers {
179  foreach my $fn (sort keys %ALL_FUNCS) {
180    my @val = @{$ALL_FUNCS{$fn}};
181    my $args = pop @val;
182    my $rtyp = "@val";
183    my $dfn = eval "\$${fn}_default";
184    $dfn = eval "\$${dfn}";
185    if (eval "\$${fn}_indirect" eq "true") {
186      print "    $fn = $dfn;\n";
187      foreach my $opt (@_) {
188        my $ofn = eval "\$${fn}_${opt}";
189        next if !$ofn;
190        next if "$ofn" eq "$dfn";
191        my $link = eval "\$${fn}_${opt}_link";
192        next if $link && $link eq "false";
193        my $cond = eval "\$have_${opt}";
194        print "    if (${cond}) $fn = $ofn;\n"
195      }
196    }
197  }
198}
199
200sub filter {
201  my @filtered;
202  foreach (@_) { push @filtered, $_ unless $disabled{$_}; }
203  return @filtered;
204}
205
206#
207# Helper functions for generating the arch specific RTCD files
208#
209sub common_top() {
210  my $include_guard = uc($opts{sym})."_H_";
211  print <<EOF;
212// This file is generated. Do not edit.
213#ifndef ${include_guard}
214#define ${include_guard}
215
216#ifdef RTCD_C
217#define RTCD_EXTERN
218#else
219#define RTCD_EXTERN extern
220#endif
221
222EOF
223
224process_forward_decls();
225print <<EOF;
226
227#ifdef __cplusplus
228extern "C" {
229#endif
230
231EOF
232declare_function_pointers("c", @ALL_ARCHS);
233
234print <<EOF;
235void $opts{sym}(void);
236
237EOF
238}
239
240sub common_bottom() {
241  print <<EOF;
242
243#ifdef __cplusplus
244}  // extern "C"
245#endif
246
247#endif
248EOF
249}
250
251sub x86() {
252  determine_indirection("c", @ALL_ARCHS);
253
254  # Assign the helper variable for each enabled extension
255  foreach my $opt (@ALL_ARCHS) {
256    my $opt_uc = uc $opt;
257    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
258  }
259
260  common_top;
261  print <<EOF;
262#ifdef RTCD_C
263#include "vpx_ports/x86.h"
264static void setup_rtcd_internal(void)
265{
266    int flags = x86_simd_caps();
267
268    (void)flags;
269
270EOF
271
272  set_function_pointers("c", @ALL_ARCHS);
273
274  print <<EOF;
275}
276#endif
277EOF
278  common_bottom;
279}
280
281sub arm() {
282  determine_indirection("c", @ALL_ARCHS);
283
284  # Assign the helper variable for each enabled extension
285  foreach my $opt (@ALL_ARCHS) {
286    my $opt_uc = uc $opt;
287    # Enable neon assembly based on HAVE_NEON logic instead of adding new
288    # HAVE_NEON_ASM logic
289    if ($opt eq 'neon_asm') { $opt_uc = 'NEON' }
290    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
291  }
292
293  common_top;
294  print <<EOF;
295#include "vpx_config.h"
296
297#ifdef RTCD_C
298#include "vpx_ports/arm.h"
299static void setup_rtcd_internal(void)
300{
301    int flags = arm_cpu_caps();
302
303    (void)flags;
304
305EOF
306
307  set_function_pointers("c", @ALL_ARCHS);
308
309  print <<EOF;
310}
311#endif
312EOF
313  common_bottom;
314}
315
316sub mips() {
317  determine_indirection("c", @ALL_ARCHS);
318
319  # Assign the helper variable for each enabled extension
320  foreach my $opt (@ALL_ARCHS) {
321    my $opt_uc = uc $opt;
322    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
323  }
324
325  common_top;
326
327  print <<EOF;
328#include "vpx_config.h"
329
330#ifdef RTCD_C
331#include "vpx_ports/mips.h"
332static void setup_rtcd_internal(void)
333{
334    int flags = mips_cpu_caps();
335
336    (void)flags;
337
338EOF
339
340  set_function_pointers("c", @ALL_ARCHS);
341
342  print <<EOF;
343#if HAVE_DSPR2
344void vpx_dsputil_static_init();
345#if CONFIG_VP8
346void dsputil_static_init();
347#endif
348
349vpx_dsputil_static_init();
350#if CONFIG_VP8
351dsputil_static_init();
352#endif
353#endif
354}
355#endif
356EOF
357  common_bottom;
358}
359
360sub ppc() {
361  determine_indirection("c", @ALL_ARCHS);
362
363  # Assign the helper variable for each enabled extension
364  foreach my $opt (@ALL_ARCHS) {
365    my $opt_uc = uc $opt;
366    eval "\$have_${opt}=\"flags & HAS_${opt_uc}\"";
367  }
368
369  common_top;
370  print <<EOF;
371#include "vpx_config.h"
372
373#ifdef RTCD_C
374#include "vpx_ports/ppc.h"
375static void setup_rtcd_internal(void)
376{
377    int flags = ppc_simd_caps();
378    (void)flags;
379EOF
380
381  set_function_pointers("c", @ALL_ARCHS);
382
383  print <<EOF;
384}
385#endif
386EOF
387  common_bottom;
388}
389
390sub unoptimized() {
391  determine_indirection "c";
392  common_top;
393  print <<EOF;
394#include "vpx_config.h"
395
396#ifdef RTCD_C
397static void setup_rtcd_internal(void)
398{
399EOF
400
401  set_function_pointers "c";
402
403  print <<EOF;
404}
405#endif
406EOF
407  common_bottom;
408}
409
410#
411# Main Driver
412#
413
414&require("c");
415&require(keys %required);
416if ($opts{arch} eq 'x86') {
417  @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2 avx512/);
418  x86;
419} elsif ($opts{arch} eq 'x86_64') {
420  @ALL_ARCHS = filter(qw/mmx sse sse2 sse3 ssse3 sse4_1 avx avx2 avx512/);
421  @REQUIRES = filter(qw/mmx sse sse2/);
422  &require(@REQUIRES);
423  x86;
424} elsif ($opts{arch} eq 'mips32' || $opts{arch} eq 'mips64') {
425  my $have_dspr2 = 0;
426  my $have_msa = 0;
427  my $have_mmi = 0;
428  @ALL_ARCHS = filter("$opts{arch}");
429  open CONFIG_FILE, $opts{config} or
430    die "Error opening config file '$opts{config}': $!\n";
431  while (<CONFIG_FILE>) {
432    if (/HAVE_DSPR2=yes/) {
433      $have_dspr2 = 1;
434    }
435    if (/HAVE_MSA=yes/) {
436      $have_msa = 1;
437    }
438    if (/HAVE_MMI=yes/) {
439      $have_mmi = 1;
440    }
441  }
442  close CONFIG_FILE;
443  if ($have_dspr2 == 1) {
444    @ALL_ARCHS = filter("$opts{arch}", qw/dspr2/);
445  } elsif ($have_msa == 1 && $have_mmi == 1) {
446    @ALL_ARCHS = filter("$opts{arch}", qw/mmi msa/);
447  } elsif ($have_msa == 1) {
448    @ALL_ARCHS = filter("$opts{arch}", qw/msa/);
449  } elsif ($have_mmi == 1) {
450    @ALL_ARCHS = filter("$opts{arch}", qw/mmi/);
451  } else {
452    unoptimized;
453  }
454  mips;
455} elsif ($opts{arch} =~ /armv7\w?/) {
456  @ALL_ARCHS = filter(qw/neon_asm neon/);
457  arm;
458} elsif ($opts{arch} eq 'armv8' || $opts{arch} eq 'arm64' ) {
459  @ALL_ARCHS = filter(qw/neon/);
460  &require("neon");
461  arm;
462} elsif ($opts{arch} =~ /^ppc/ ) {
463  @ALL_ARCHS = filter(qw/vsx/);
464  ppc;
465} else {
466  unoptimized;
467}
468
469__END__
470
471=head1 NAME
472
473rtcd -
474
475=head1 SYNOPSIS
476
477Usage: rtcd.pl [options] FILE
478
479See 'perldoc rtcd.pl' for more details.
480
481=head1 DESCRIPTION
482
483Reads the Run Time CPU Detections definitions from FILE and generates a
484C header file on stdout.
485
486=head1 OPTIONS
487
488Options:
489  --arch=ARCH       Architecture to generate defs for (required)
490  --disable-EXT     Disable support for EXT extensions
491  --require-EXT     Require support for EXT extensions
492  --sym=SYMBOL      Unique symbol to use for RTCD initialization function
493  --config=FILE     File with CONFIG_FOO=yes lines to parse
494