#
# Copyright 2017-2020 Intel Corporation All Rights Reserved.
# 
# The source code, information and material ("Material") contained herein is
# owned by Intel Corporation or its suppliers or licensors, and title
# to such Material remains with Intel Corporation or its suppliers or
# licensors. The Material contains proprietary information of Intel
# or its suppliers and licensors. The Material is protected by worldwide
# copyright laws and treaty provisions. No part of the Material may be used,
# copied, reproduced, modified, published, uploaded, posted, transmitted,
# distributed or disclosed in any way without Intel's prior express written
# permission. No license under any patent, copyright or other intellectual
# property rights in the Material is granted to or conferred upon you,
# either expressly, by implication, inducement, estoppel or otherwise.
# Any license under such intellectual property rights must be express and
# approved by Intel in writing.
# 
# Unless otherwise agreed by Intel in writing,
# you may not remove or alter this notice or any other notice embedded in
# Materials by Intel or Intel's suppliers or licensors in any way.
# 

#!perl
#use strict;
use Getopt::Std;
use vars qw( $opt_h $opt_L $opt_s $opt_o $opt_l );

my $str_ia32 = "w7,s8,p8,g9,h9";
my $str_x64  = "m7,n8,y8,e9,l9,n0,k0";

sub Usage {
    die <<USAGE;
Generates stubs for merged libraries.
  Usage: $0 -L <set of Intel(R) 64 libraries> -l <set of ia32 libs> -s <Intel IPP header> -o <output dir> [-h]
  set of x64  libs   at least 2 from <m7,n8,y8,e9,l9,n0,k0>
  set of ia32 libs   at least 2 from <w7,s8,p8,g9,h9> 
  Intel IPP header   main header file of Intel IPP domain, ippi.h, ipps.h etc.
  output dir         output directory
  h                  short help

USAGE
}
  
getopts( "hL:l:s:o:" );
Usage if ( $opt_h );
exit 0 if ( $opt_h );

die "$0: input error: <lib> is not defined\n" unless( $opt_l|$opt_L );
die "$0: input error: <Intel IPP header> not defined\n" unless( $opt_s );
$opt_s =~ s|\\|/|g;
die "$0: input error: <output dir> not defined\n" unless( $opt_o );
$opt_o =~ s|\\|/|g;
die "$0: directory '$opt_o' not exists or not accessible\n" unless( -w $opt_o && -d $opt_o );
my @hname = split(/\./, $opt_s);
die "$0: input error: non-header file for -s option\n" unless( lc($hname[@hname-1]) eq 'h' ); 

my $include = $opt_s;
$include =~ s/.+\/(.+)/$1/;

my $pref = $include;
$pref =~ s/(.+)\..+/$1/;
my $domain = $pref;
$domain =~ s/ipp(.+)/$1/;

open( IPP_H, $opt_s ) || die "$0: '$opt_s': $!\n";
my @inputfile = <IPP_H>;
close( IPP_H );

chomp @inputfile;

my @ipplibs32 = split(/,/, $opt_l);
my @ipplibs64 = split(/,/, $opt_L);

my @ippia32 = split(/,/, $str_ia32);
my @ippx64  = split(/,/, $str_x64);

my @funclist = map {s/\s{2,}/ /g;$_} grep {/^.+/} map { (/^\s*\([\w\d,\s*]+\([^)]*\)\s*\)/) ? "IPPAPI".$& : "" } split "IPPAPI", join " ", @inputfile;

if( 1 ==  @ipplibs32 ){
  die "$0: dispatcher for less than 2 optimizations '$opt_l' doesn't make sense\n";
}
if( 1 ==  @ipplibs64 ) {
  die "$0: dispatcher for less than 2 optimizations '$opt_L' doesn't make sense\n";
}

if( @ipplibs32 > 0 ) {
  for( my $i = 0; $i < @ipplibs32; $i++ ){
    if( !grep {$_ eq $ipplibs32[$i]} @ippia32){
      die "$0: '$ipplibs32[$i]' is not supported for ia32\n"; 
    }
  }
} 
if( @ipplibs64 > 0 ) {
  for( my $i = 0; $i < @ipplibs64; $i++ ){
    if( !grep {$_ eq $ipplibs64[$i]} @ippx64){
      die "$0: '$ipplibs64[$i]' is not supported for x64\n"; 
    }
  }
}

my @fname = split(/\//,$opt_s); #extract header file name from the full path
my $hname = $fname[@fname-1];   #domain header initial name - for example ippcp.h


$fname[@fname-1] =~ s/\./d./;   #new header will have modified name with 'd' suffix -> "name.ext"=>"name'd'.ext"

open( FILE_H, ">$opt_o/$fname[@fname-1]" ) or die "$0: $opt_o/$fname[@fname-1]: $!\n";
  print FILE_H<<OUT
#ifdef __cplusplus
extern "C" {
#endif

#ifndef NULL
#ifdef  __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif
#endif

OUT
;


for( my $i = 0; $i < @funclist; $i++ ){
  my $lxdef64 = "";
  my $lxdef32 = "";
  my $apistr = "";
  my $retval = "";
  my $retsts = "";
  my @ippapi = grep { $_ ne ' ' } grep { $_ ne '' } split (/,|\(|\)/, $funclist[$i]); #split IPPAPI definition to separate items and remove empty ones - to remove then data types and transfer to call
  my @apiname64 = split( /,/, $funclist[$i] ); # just in order to extract function name and add cpu-specific prefix
  my @apiname32 = split( /,/, $funclist[$i] ); # just in order to extract function name and add cpu-specific prefix
  if( $ippapi[1] =~ /void/ ) {
    $retval = '';
    $retsts = '';
  } else {
    $retval = "return";
    if( $ippapi[1] =~ /IppStatus/ ){
      $retsts = "ippStsCpuNotSupportedErr";
    } else {
      $retsts = "NULL";
    }
  }
  $apiname64[1] =~ s/^\s*//; # remove leading spaces
  my $dname = $apiname64[1];
  for( my $l = 0; $l <= @ipplibs64; $l++ ){
    if( $l < @ipplibs64 ){
      $apiname64[1] = " ".$ipplibs64[$l]."_".$dname; # add cpu-specific prefix
    } else {
      my @tmp = split( /\(/, $apiname64[0] );
      $tmp[0] = "\nIPPFUN";
      $apiname64[0] = join( '(', @tmp );
      $apiname64[1] = "d".$dname; # here we can add renaming prefix
    }
    $lxdef64 = $lxdef64."\n".join( ',', @apiname64 ); # string that accumulates cpu-specific declarations
  }
  for( my $l = 0; $l <= @ipplibs32; $l++ ){
    if( $l < @ipplibs32 ){
      $apiname32[1] = " ".$ipplibs32[$l]."_".$dname; # add cpu-specific prefix
    } else {
      my @tmp = split( /\(/, $apiname32[0] );
      $tmp[0] = "\nIPPFUN";
      $apiname32[0] = join( '(', @tmp );
      $apiname32[1] = "d".$dname; # here we can add renaming prefix
    }
    $lxdef32 = $lxdef32."\n".join( ',', @apiname32 ); # string that accumulates cpu-specific declarations
  }
  print FILE_H<<OUT
  #define ${dname} ${apiname64[1]}
OUT
;
    $apistr = $dname."( ";
    for( my $j = 3; $j < @ippapi; $j++){
      my @param = split( / /, $ippapi[$j] );
      my $tmp = $param[@param-1];
      $tmp =~ s/^\**//; # remove leading *
      @param = split( /\[/, $tmp );
      $tmp = $param[0];
      if( $tmp =~ /void/ ) { $tmp = '';}
        $apistr = $apistr.$tmp;
        if( $j == @ippapi-1 ){ 
          $apistr = $apistr." );";
        } else {
          $apistr = $apistr.", ";
        }
    }

  my $str = "0" x (5-length($i)) . $i;
  open( FILE, ">$opt_o/$pref$str\.c" ) or die "$0: $opt_o/$pref$str\.c: $!\n";
  print FILE<<OUT
#include "ippcore.h"
#include "$fname[@fname-1]"

#define IPPFUN(type,name,arg) extern type IPP_STDCALL name arg

#ifndef NULL
#ifdef  __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif
#endif

#if defined (_M_AMD64) || defined (__x86_64__)

OUT
   ;

if( @ipplibs64 > 0 ) {
  print FILE<<OUT
#define AVX3X_FEATURES ( ippCPUID_AVX512F|ippCPUID_AVX512CD|ippCPUID_AVX512VL|ippCPUID_AVX512BW|ippCPUID_AVX512DQ )
#define AVX3M_FEATURES ( ippCPUID_AVX512F|ippCPUID_AVX512CD|ippCPUID_AVX512PF|ippCPUID_AVX512ER )

$lxdef64
{
  Ipp64u features;
  ippGetCpuFeatures( &features, NULL );

OUT
   ;
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /k0/ ) {
      print FILE<<OUT
      if( AVX3X_FEATURES  == ( features & AVX3X_FEATURES  )) {
        ${retval} k0_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /n0/ ) {
      print FILE<<OUT
      if( AVX3M_FEATURES  == ( features & AVX3M_FEATURES  )) {
        ${retval} n0_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /l9/ ) {
      print FILE<<OUT
      if( ippCPUID_AVX2  == ( features & ippCPUID_AVX2  )) {
        ${retval} l9_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /e9/ ) {
      print FILE<<OUT
      if( ippCPUID_AVX   == ( features & ippCPUID_AVX   )) {
        ${retval} e9_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /y8/ ) {
      print FILE<<OUT
      if( ippCPUID_SSE42 == ( features & ippCPUID_SSE42 )) {
        ${retval} y8_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /n8/ ) {
      print FILE<<OUT
      if( ippCPUID_SSSE3 == ( features & ippCPUID_SSSE3 )) {
        ${retval} n8_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /m7/ ) {
      print FILE<<OUT
      if( ippCPUID_SSE3  == ( features & ippCPUID_SSE3  )) {
        ${retval} m7_${apistr}
      } else 
OUT
;
    }
  }
  my $flag = 0;
  for( my $l = 0; $l < @ipplibs64; $l++ ){
    if( $ipplibs64[$l] =~ /mx/ ) {
      $flag = 1;
      print FILE<<OUT
        ${retval} mx_${apistr}
}
OUT
;
    } 
  }
  if( $flag < 1 ){
      print FILE<<OUT
        ${retval} ${retsts};
}
OUT
;
    }
}
  print FILE<<OUT
#else
OUT
   ;
if( @ipplibs32 > 0 ) { # ia32
  print FILE<<OUT

$lxdef32
{
  Ipp64u features;
  ippGetCpuFeatures( &features, NULL );

OUT
   ;
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /h9/ ) {
      print FILE<<OUT
      if( ippCPUID_AVX2  == ( features & ippCPUID_AVX2  )) {
        ${retval} h9_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /g9/ ) {
      print FILE<<OUT
      if( ippCPUID_AVX   == ( features & ippCPUID_AVX   )) {
        ${retval} g9_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /p8/ ) {
      print FILE<<OUT
      if( ippCPUID_SSE42 == ( features & ippCPUID_SSE42 )) {
        ${retval} p8_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /s8/ ) {
      print FILE<<OUT
      if( ippCPUID_SSSE3 == ( features & ippCPUID_SSSE3 )) {
        ${retval} s8_${apistr}
      } else 
OUT
;
    }
  }
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /w7/ ) {
      print FILE<<OUT
      if( ippCPUID_SSE2  == ( features & ippCPUID_SSE2  )) {
        ${retval} w7_${apistr}
      } else 
OUT
;
    }
  }
  my $flag = 0;
  for( my $l = 0; $l < @ipplibs32; $l++ ){
    if( $ipplibs32[$l] =~ /px/ ) {
      $flag = 1;
      print FILE<<OUT
        ${retval} px_${apistr}
}
OUT
;
    } 
  }
  if( $flag < 1 ){
      print FILE<<OUT
        ${retval} ${retsts};
}
OUT
;
    }
} 
  print FILE<<OUT
#endif
OUT
   ;
  close FILE;
}

print FILE_H<<OUT

#include "${hname}"

#ifdef __cplusplus
}
#endif
OUT
;
  close FILE_H;

exit;

