# Copyright (C) 2014-2018 Intel Corporation.   All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

from __future__ import print_function
import os, sys, re
from gen_common import *
from argparse import FileType

inst_aliases = {
    'SHUFFLE_VECTOR': 'VSHUFFLE',
    'INSERT_ELEMENT': 'VINSERT',
    'EXTRACT_ELEMENT': 'VEXTRACT',
    'MEM_SET': 'MEMSET',
    'MEM_CPY': 'MEMCOPY',
    'MEM_MOVE': 'MEMMOVE',
    'L_SHR': 'LSHR',
    'A_SHR': 'ASHR',
    'BIT_CAST': 'BITCAST',
    'U_DIV': 'UDIV',
    'S_DIV': 'SDIV',
    'U_REM': 'UREM',
    'S_REM': 'SREM',
    'BIN_OP': 'BINOP',
}

intrinsics = [
    ['VGATHERPD',   ['src', 'pBase', 'indices', 'mask', 'scale'], 'src'],
    ['VGATHERPS',   ['src', 'pBase', 'indices', 'mask', 'scale'], 'src'],
    ['VGATHERDD',   ['src', 'pBase', 'indices', 'mask', 'scale'], 'src'],
    ['VRCPPS',      ['a'], 'a'],
    ['VROUND',      ['a', 'rounding'], 'a'],
    ['BEXTR_32',    ['src', 'control'], 'src'],
    ['VPSHUFB',     ['a', 'b'], 'a'],
    ['VPERMD',      ['a', 'idx'], 'a'],
    ['VPERMPS',     ['idx', 'a'], 'a'],
    ['VCVTPD2PS',   ['a'], 'VectorType::get(mFP32Ty, a->getType()->getVectorNumElements())'],
    ['VCVTPH2PS',   ['a'], 'VectorType::get(mFP32Ty, a->getType()->getVectorNumElements())'],
    ['VCVTPS2PH',   ['a', 'round'], 'mSimdInt16Ty'],
    ['VHSUBPS',     ['a', 'b'], 'a'],
    ['VPTESTC',     ['a', 'b'], 'mInt32Ty'],
    ['VPTESTZ',     ['a', 'b'], 'mInt32Ty'],
    ['VPHADDD',     ['a', 'b'], 'a'],
    ['PDEP32',      ['a', 'b'], 'a'],
    ['RDTSC',       [], 'mInt64Ty'],
]

llvm_intrinsics = [
    ['CTTZ', 'cttz', ['a', 'flag'], ['a']],
    ['CTLZ', 'ctlz', ['a', 'flag'], ['a']],
    ['VSQRTPS', 'sqrt', ['a'], ['a']],
    ['STACKSAVE', 'stacksave', [], []],
    ['STACKRESTORE', 'stackrestore', ['a'], []],
    ['VMINPS', 'minnum', ['a', 'b'], ['a']],
    ['VMAXPS', 'maxnum', ['a', 'b'], ['a']],
    ['VFMADDPS', 'fmuladd', ['a', 'b', 'c'], ['a']],
    ['DEBUGTRAP', 'debugtrap', [], []],
    ['POPCNT', 'ctpop', ['a'], ['a']],
    ['LOG2', 'log2', ['a'], ['a']],
    ['FABS', 'fabs', ['a'], ['a']],
    ['EXP2', 'exp2', ['a'], ['a']],
    ['COS', 'cos', ['a'], ['a']],
    ['SIN', 'sin', ['a'], ['a']],
    ['FLOOR', 'floor', ['a'], ['a']],
    ['POW', 'pow', ['a', 'b'], ['a']]
]

this_dir = os.path.dirname(os.path.abspath(__file__))
template = os.path.join(this_dir, 'templates', 'gen_builder.hpp')

def convert_uppercamel(name):
    s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).upper()

'''
    Given an input file (e.g. IRBuilder.h) generates function dictionary.
'''
def parse_ir_builder(input_file):

    functions = []

    lines = input_file.readlines()

    idx = 0
    while idx < len(lines) - 1:
        line = lines[idx].rstrip()
        idx += 1

        #match = re.search(r'\*Create', line)
        match = re.search(r'[\*\s]Create(\w*)\(', line)
        if match is not None:
            #print('Line: %s' % match.group(1))

            if re.search(r'^\s*Create', line) is not None:
                func_sig = lines[idx-2].rstrip() + line
            else:
                func_sig = line

            end_of_args = False
            while not end_of_args:
                end_paren = re.search(r'\)', line)
                if end_paren is not None:
                    end_of_args = True
                else:
                    line = lines[idx].rstrip()
                    func_sig += line
                    idx += 1

            delfunc = re.search(r'LLVM_DELETED_FUNCTION|= delete;', func_sig)

            if not delfunc:
                func = re.search(r'(.*?)\*[\n\s]*(Create\w*)\((.*?)\)', func_sig)
                if func is not None:

                    return_type = func.group(1).strip() + '*'
                    func_name = func.group(2)
                    arguments = func.group(3)

                    func_args = []
                    arg_names = []
                    args = arguments.split(',')
                    for arg in args:
                        arg = arg.strip()
                        if arg:
                            func_args.append(arg)

                            split_args = arg.split('=')
                            arg_name = split_args[0].rsplit(None, 1)[-1]

                            reg_arg = re.search(r'[\&\*]*(\w*)', arg_name)
                            if reg_arg:
                                arg_names += [reg_arg.group(1)]

                    ignore = False

                    # The following functions need to be ignored in openswr.
                    # API change in llvm-5.0 breaks baked autogen files
                    if (
                        (func_name == 'CreateFence' or
                         func_name == 'CreateAtomicCmpXchg' or
                         func_name == 'CreateAtomicRMW')):
                        ignore = True

                    # The following functions need to be ignored.
                    if (func_name == 'CreateInsertNUWNSWBinOp' or
                        func_name == 'CreateMaskedIntrinsic' or
                        func_name == 'CreateAlignmentAssumptionHelper' or
                        func_name == 'CreateGEP' or
                        func_name == 'CreateLoad' or
                        func_name == 'CreateMaskedLoad' or
                        func_name == 'CreateStore' or
                        func_name == 'CreateMaskedStore' or
                        func_name == 'CreateElementUnorderedAtomicMemCpy'):
                        ignore = True

                    # Convert CamelCase to CAMEL_CASE
                    func_mod = re.search(r'Create(\w*)', func_name)
                    if func_mod:
                        func_mod = func_mod.group(1)
                        func_mod = convert_uppercamel(func_mod)
                        if func_mod[0:2] == 'F_' or func_mod[0:2] == 'I_':
                            func_mod = func_mod[0] + func_mod[2:]

                    # Substitute alias based on CAMEL_CASE name.
                    func_alias = inst_aliases.get(func_mod)
                    if not func_alias:
                        func_alias = func_mod

                        if func_name == 'CreateCall' or func_name == 'CreateGEP':
                            arglist = re.search(r'ArrayRef', ', '.join(func_args))
                            if arglist:
                                func_alias = func_alias + 'A'

                    if not ignore:
                        functions.append({
                                'name'      : func_name,
                                'alias'     : func_alias,
                                'return'    : return_type,
                                'args'      : ', '.join(func_args),
                                'arg_names' : arg_names,
                            })

    return functions

'''
    Auto-generates macros for LLVM IR
'''
def generate_gen_h(functions, output_dir):
    filename = 'gen_builder.hpp'
    output_filename = os.path.join(output_dir, filename)

    templfuncs = []
    for func in functions:
        decl = '%s %s(%s)' % (func['return'], func['alias'], func['args'])

        templfuncs.append({
            'decl'      : decl,
            'intrin'    : func['name'],
            'args'      : func['arg_names'],
        })

    MakoTemplateWriter.to_file(
        template,
        output_filename,
        cmdline=sys.argv,
        comment='Builder IR Wrappers',
        filename=filename,
        functions=templfuncs,
        isX86=False, isIntrin=False)

'''
    Auto-generates macros for LLVM IR
'''
def generate_meta_h(output_dir):
    filename = 'gen_builder_meta.hpp'
    output_filename = os.path.join(output_dir, filename)

    functions = []
    for inst in intrinsics:
        name = inst[0]
        args = inst[1]
        ret = inst[2]

        #print('Inst: %s, x86: %s numArgs: %d' % (inst[0], inst[1], len(inst[2])))
        if len(args) != 0:
            declargs = 'Value* ' + ', Value* '.join(args)
            decl = 'Value* %s(%s, const llvm::Twine& name = "")' % (name, declargs)
        else:
            decl = 'Value* %s(const llvm::Twine& name = "")' % (name)

        # determine the return type of the intrinsic. It can either be:
        # - type of one of the input arguments
        # - snippet of code to set the return type
        
        if ret in args:
            returnTy = ret + '->getType()'
        else:
            returnTy = ret

        functions.append({
            'decl'      : decl,
            'name'      : name,
            'args'      : args,
            'returnType': returnTy
        })

    MakoTemplateWriter.to_file(
        template,
        output_filename,
        cmdline=sys.argv,
        comment='meta intrinsics',
        filename=filename,
        functions=functions,
        isX86=True, isIntrin=False)

def generate_intrin_h(output_dir):
    filename = 'gen_builder_intrin.hpp'
    output_filename = os.path.join(output_dir, filename)

    functions = []
    for inst in llvm_intrinsics:
        #print('Inst: %s, x86: %s numArgs: %d' % (inst[0], inst[1], len(inst[2])))
        if len(inst[2]) != 0:
            declargs = 'Value* ' + ', Value* '.join(inst[2])
            decl = 'Value* %s(%s, const llvm::Twine& name = "")' % (inst[0], declargs)
        else:
            decl = 'Value* %s(const llvm::Twine& name = "")' % (inst[0])

        functions.append({
            'decl'      : decl,
            'intrin'    : inst[1],
            'args'      : inst[2],
            'types'     : inst[3],
        })

    MakoTemplateWriter.to_file(
        template,
        output_filename,
        cmdline=sys.argv,
        comment='llvm intrinsics',
        filename=filename,
        functions=functions,
        isX86=False, isIntrin=True)
'''
    Function which is invoked when this script is started from a command line.
    Will present and consume a set of arguments which will tell this script how
    to behave
'''
def main():

    # Parse args...
    parser = ArgumentParser()
    parser.add_argument('--input', '-i', type=FileType('r'), help='Path to IRBuilder.h', required=False)
    parser.add_argument('--output-dir', '-o', action='store', dest='output', help='Path to output directory', required=True)
    parser.add_argument('--gen_h', help='Generate builder_gen.h', action='store_true', default=False)
    parser.add_argument('--gen_meta_h', help='Generate meta intrinsics. No input is needed.', action='store_true', default=False)
    parser.add_argument('--gen_intrin_h', help='Generate llvm intrinsics. No input is needed.', action='store_true', default=False)
    args = parser.parse_args()

    if not os.path.exists(args.output):
        os.makedirs(args.output)

    final_output_dir = args.output
    args.output = MakeTmpDir('_codegen')

    rval = 0
    try:
        if args.input:
            functions = parse_ir_builder(args.input)

            if args.gen_h:
                generate_gen_h(functions, args.output)

        elif args.gen_h:
            print('Need to specify --input for --gen_h!')

        if args.gen_meta_h:
            generate_meta_h(args.output)

        if args.gen_intrin_h:
            generate_intrin_h(args.output)

        rval = CopyDirFilesIfDifferent(args.output, final_output_dir)

    except:
        print('ERROR: Could not generate llvm_ir_macros', file=sys.stderr)
        rval = 1

    finally:
        DeleteDirTree(args.output)

    return rval

if __name__ == '__main__':
    sys.exit(main())
# END OF FILE
