From 586454bda46228da88516e2d37f2bef7f98d76de Mon Sep 17 00:00:00 2001 From: Mathieu Faverge <mathieu.faverge@inria.fr> Date: Mon, 14 May 2018 11:51:26 +0200 Subject: [PATCH] Add scripts to generate wrappers --- tools/gen_wrappers.py | 543 +++++++++++++++++++++++++++++++++ tools/wrappers/__init__.py | 41 +++ tools/wrappers/wrap_fortran.py | 474 ++++++++++++++++++++++++++++ tools/wrappers/wrap_python.py | 518 +++++++++++++++++++++++++++++++ 4 files changed, 1576 insertions(+) create mode 100755 tools/gen_wrappers.py create mode 100644 tools/wrappers/__init__.py create mode 100644 tools/wrappers/wrap_fortran.py create mode 100644 tools/wrappers/wrap_python.py diff --git a/tools/gen_wrappers.py b/tools/gen_wrappers.py new file mode 100755 index 00000000..841177e1 --- /dev/null +++ b/tools/gen_wrappers.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python +""" + @file gen_wrappers.py + + Python and Fortran 90 wrapper generator for some of the solverstack + libraries, inspired from the PLASMA-OMP fortran generator. + + @copyright 2016-2017 University of Tennessee, US, University of + Manchester, UK. All rights reserved. + @copyright 2017-2018 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, + Univ. Bordeaux. All rights reserved. + + @version 6.0.0 + @author Pierre Ramet + @author Mathieu Faverge + @date 2017-05-04 + +""" +import os +import re +import argparse +import wrappers + +description = '''\ +Generates Fortran 90 and Python wrappers from the spm header files.''' + +help = '''\ +---------------------------------------------------------------------- +Example uses: + + $SPM_SRC_DIR/gen_wrappers.py + +---------------------------------------------------------------------- +''' + +# ------------------------------------------------------------ +# command line options +parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=description, + epilog=help ) +parser.add_argument('--prefix', action='store', help='Prefix for variables in Makefile', default='./') +parser.add_argument('args', nargs='*', action='store', help='Files to process') +opts = parser.parse_args() + +# exclude inline functions from the interface +exclude_list = [ "inline", "spmIntSort" ] + +def polish_file(whole_file): + """Preprocessing and cleaning of the header file. + Do not change the order of the regular expressions ! + Works with a long string.""" + + clean_file = whole_file + + # borrowed from cfwrapper.py + # Remove C comments: + clean_file = re.sub(r"(?s)/\*.*?\*/", "", clean_file) + clean_file = re.sub(r"//.*", "", clean_file) + # Remove C directives (multilines then monoline): + clean_file = re.sub(r"(?m)^#(.*[\\][\n])+.*?$", "", clean_file) + clean_file = re.sub("(?m)^#.*$", "", clean_file) + clean_file = re.sub("(?m)#.*", "", clean_file) + # Remove TABs and overnumerous spaces: + clean_file = clean_file.replace("\t", " ") + clean_file = re.sub("[ ]{2,}", " ", clean_file) + # Remove extern C statement: + clean_file = re.sub("(?m)^(extern).*$", "", clean_file) + # Remove empty lines: + clean_file = re.sub(r"(?m)^\n$", "", clean_file) + # Merge structs + clean_file = re.sub(r"(?m)$", "", clean_file) + + # Merge string into single line + clean_file = re.sub(r"\n", "", clean_file) + + # Split the line based on ";" and "}" + clean_file = re.sub(r";", "\n", clean_file) + clean_file = re.sub(r"}", "}\n", clean_file) + + return clean_file + +def preprocess_list(initial_list): + """Preprocessing and cleaning of the header file. + Works with a list of strings. + Produces a new list in which each function, enum or struct + corresponds to a single item.""" + + # merge braces + list1 = [] + merged_line = "" + nopen = 0 + inStruct = False + for line in initial_list: + + if (line.find("struct") > -1): + inStruct = True + + if (inStruct): + split_character = "," + else: + split_character = "" + + nopen += line.count("{") - line.count("}") + merged_line += line + split_character + + if (nopen <= 0): + list1.append(merged_line) + merged_line = "" + isOpen = False + inStruct = False + nopen = 0 + + # merge structs + list2 = [] + merged_line = "" + for line in list1: + + merged_line += line + + if (line.find("struct") == -1): + list2.append(merged_line) + merged_line = "" + + # clean orphan braces + list3 = [] + for line in list2: + + if (line.strip() != "}"): + list3.append(line) + + #print '\n\n'.join(list3) + + return list3 + +def parse_triple(string): + """Parse string of + type (*)name + into triple of [type, pointer, name, const]""" + + if "const" in string: + const=1 + string = re.sub(r"const", "", string) + else: + const=0 + + parts = string.split() + if (len(parts) < 2 or len(parts) > 3): + print("Error: Cannot detect type for ", string) + + type_part = str.strip(parts[0]) + + if (len(parts) == 2): + name_with_pointer = str.strip(parts[1]) + if (name_with_pointer.find("**") > -1): + pointer_part = "**" + name_part = name_with_pointer.replace("**", "") + elif (name_with_pointer.find("*") > -1): + pointer_part = "*" + name_part = name_with_pointer.replace("*", "") + else: + pointer_part = "" + name_part = name_with_pointer + + elif (len(parts) == 3): + if (str.strip(parts[1]) == "**"): + pointer_part = "**" + name_part = str.strip(parts[2]) + elif (str.strip(parts[1]) == "*"): + pointer_part = "*" + name_part = str.strip(parts[2]) + else: + print("Error: Too many parts for ", string) + + name_part = name_part.strip() + + return [type_part, pointer_part, name_part, const] + + +def parse_enums(preprocessed_list): + """Each enum will be parsed into a list of its arguments.""" + + enum_list = [] + for proto in preprocessed_list: + + # extract the part of the function from the prototype + fun_parts = proto.split("{") + + split_fun = fun_parts[0].strip().split() + if len(split_fun) == 0: + continue + + if ((split_fun[0] == "enum") or + (split_fun[0] == "typedef" and split_fun[1] == "enum")): + + + if split_fun[0] == "enum": + enumname = split_fun[1] + else: + enumname = split_fun[2] + enumname = re.sub(r"_e$", "", enumname) + enumname = re.sub(r"^pastix_", "", enumname) + + args_string = fun_parts[1]; + args_string = re.sub(r"}", "", args_string) + args_list = args_string.split(",") + params = []; + for args in args_list: + args = args.strip(); + if (args != ""): + values = args.split("=") + + name = values[0].strip() + if (len(values) > 1): + value = values[1].strip() + else: + if (len(params) > 0): + value = params[len(params)-1][1] + 1 + else: + value = 0 + + params.append([name, value]) + + enum_list.append([enumname, params]) + + return enum_list + + +def parse_structs(preprocessed_list): + """Each struct will be parsed into a list of its arguments.""" + + struct_list = [] + for proto in preprocessed_list: + + # extract the part of the function from the prototype + fun_parts = proto.split("{") + + if (fun_parts[0].find("struct") > -1): + args_string = fun_parts[1] + parts = args_string.split("}") + args_string = parts[0].strip() + args_string = re.sub(r"volatile", "", args_string) + if (len(parts) > 1): + name_string = parts[1] + name_string = re.sub(r"(?m),", "", name_string) + name_string = name_string.strip() + else: + print("Error: Cannot detect name for ", proto) + name_string = "name_not_recognized" + + args_list = args_string.split(",") + params = []; + params.append(["struct","",name_string]) + for arg in args_list: + if (not (arg == "" or arg == " ")): + params.append(parse_triple(arg)) + + struct_list.append(params) + wrappers.derived_types.append(name_string) + + # reorder the list so that only defined types are exported + goAgain = True + while (goAgain): + goAgain = False + for istruct in range(0,len(struct_list)-1): + struct = struct_list[istruct] + for j in range(1,len(struct)-1): + type_name = struct_list[istruct][j][0] + + if (type_name in wrappers.derived_types): + + # try to find the name in the registered types + definedEarlier = False + for jstruct in range(0,istruct): + struct2 = struct_list[jstruct] + that_name = struct2[0][2] + if (that_name == type_name): + definedEarlier = True + + # if not found, try to find it behind + if (not definedEarlier): + definedLater = False + for jstruct in range(istruct+1,len(struct_list)-1): + struct2 = struct_list[jstruct] + that_name = struct2[0][2] + if (that_name == type_name): + index = jstruct + definedLater = True + + # swap the entries + if (definedLater): + print("Swapping " + struct_list[istruct][0][2] + " and " + struct_list[index][0][2]) + tmp = struct_list[index] + struct_list[index] = struct_list[istruct] + struct_list[istruct] = tmp + goAgain = True + else: + print("Error: Cannot find a derived type " + type_name + " in imported structs.") + + return struct_list + + +def parse_prototypes(preprocessed_list): + """Each prototype will be parsed into a list of its arguments.""" + + function_list = [] + for proto in preprocessed_list: + + if (proto.find("(") == -1): + continue + + # extract the part of the function from the prototype + fun_parts = proto.split("(") + fun_def = str.strip(fun_parts[0]) + + exclude_this_function = False + for exclude in exclude_list: + if (fun_def.find(exclude) != -1): + exclude_this_function = True + + if (exclude_this_function): + continue + + # clean keywords + fun_def = fun_def.replace("static", "") + + # extract arguments from the prototype and make a list from them + if (len(fun_parts) > 1): + fun_args = fun_parts[1] + else: + fun_args = "" + + fun_args = fun_args.split(")")[0] + fun_args = fun_args.replace(";", "") + fun_args = re.sub(r"volatile", "", fun_args) + fun_args = fun_args.replace("\n", "") + fun_args_list = fun_args.split(",") + + # generate argument list + argument_list = [] + # function itself on the first position + argument_list.append(parse_triple(fun_def)) + # append arguments + for arg in fun_args_list: + if (not (arg == "" or arg == " ")): + argument_list.append(parse_triple(arg)) + + # add it only if there is no duplicity with previous one + is_function_already_present = False + fun_name = argument_list[0][2] + for fun in function_list: + if (fun_name == fun[0][2]): + is_function_already_present = True + + if (not is_function_already_present): + function_list.append(argument_list) + + return function_list + +def write_module(f, wrapper, generator, enum_list, struct_list, function_list): + """Generate a single Fortran module. Its structure will be: + enums converted to constants + structs converted to derived types + interfaces of all C functions + Fortran wrappers of the C functions""" + + modulefile = open( f['filename'], "w" ) + + f_interface = generator.header( f ) + modulefile.write(f_interface + "\n") + + # enums + if (len(enum_list) > 0): + for enum in enum_list: + f_interface = generator.enum( f, enum ) + modulefile.write(f_interface + "\n") + + # derived types + if (len(struct_list) > 0): + for struct in struct_list: + f_interface = generator.struct(struct) + modulefile.write(f_interface + "\n") + + # functions + if (len(function_list) > 0): + for function in function_list: + f_interface = generator.function(function) + modulefile.write(f_interface + "\n") + + if wrapper: + modulefile.write("contains\n\n") + modulefile.write(" " + "! Wrappers of the C functions.\n") + + for function in function_list: + f_wrapper = generator.wrapper(function) + modulefile.write(f_wrapper + "\n") + + footer=generator.footer( f ) + modulefile.write(footer + "\n") + + modulefile.close() + + return f['filename'] + + +enums_python_coeftype=''' + @staticmethod + def getptype ( dtype ): + np_dict = { + np.dtype('float32') : coeftype.Float, + np.dtype('float64') : coeftype.Double, + np.dtype('complex64') : coeftype.Complex32, + np.dtype('complex128') : coeftype.Complex64, + } + if dtype in np_dict: + return np_dict[dtype] + else: + return -1 + + @staticmethod + def getctype ( flttype ): + class c_float_complex(Structure): + _fields_ = [("r",c_float),("i", c_float)] + class c_double_complex(Structure): + _fields_ = [("r",c_double),("i", c_double)] + + np_dict = { + coeftype.Float : c_float, + coeftype.Double : c_double, + coeftype.Complex32 : c_float_complex, + coeftype.Complex64 : c_double_complex + } + if flttype in np_dict: + return np_dict[flttype] + else: + return -1 + + @staticmethod + def getnptype ( flttype ): + np_dict = { + coeftype.Float : np.dtype('float32'), + coeftype.Double : np.dtype('float64'), + coeftype.Complex32 : np.dtype('complex64'), + coeftype.Complex64 : np.dtype('complex128') + } + if flttype in np_dict: + return np_dict[flttype] + else: + return -1 +''' + +enums_fortran_footer=''' + integer, parameter :: spm_int_t = SPM_INT_KIND + +contains + + function spm_getintsize() + integer :: spm_getintsize + spm_getintsize = SPM_INT_KIND + return + end function spm_getintsize +''' + +spm_enums = { + 'filename' : [ "include/spm_const.h" ], + 'python' : { 'filename' : "wrappers/python/spm/enum.py.in", + 'description' : "SPM python wrapper to define enums and datatypes", + 'header' : "spm_int = @SPM_PYTHON_INTEGER@", + 'footer' : "", + 'enums' : { 'coeftype' : enums_python_coeftype } + }, + 'fortran' : { 'filename' : "wrappers/fortran90/src/spm_enums.F90", + 'description' : "SPM fortran 90 wrapper to define enums and datatypes", + 'header' : " implicit none\n", + 'footer' : enums_fortran_footer, + 'enums' : {} + }, +} + +spm = { + 'filename' : [ "include/spm.h" ], + 'python' : { 'filename' : "wrappers/python/spm/__spm__.py", + 'description' : "SPM python wrapper", + 'header' : "from . import libspm\nfrom .enum import spm_int\n", + 'footer' : "", + 'enums' : {} + }, + 'fortran' : { 'filename' : "wrappers/fortran90/src/spmf.f90", + 'description' : "SPM Fortran 90 wrapper", + 'header' : " use spm_enums\n implicit none\n", + 'footer' : "", + 'enums' : {} + }, +} + +def main(): + + # common cleaned header files + preprocessed_list = [] + + # source header files + for f in [ spm_enums, spm ]: + preprocessed_list = [] + for filename in f['filename']: + + # source a header file + c_header_file = open(filename, 'r').read() + + # clean the string (remove comments, macros, etc.) + clean_file = polish_file(c_header_file) + + # convert the string to a list of strings + initial_list = clean_file.split("\n") + + # process the list so that each enum, struct or function + # are just one item + nice_list = preprocess_list(initial_list) + + # compose all files into one big list + preprocessed_list += nice_list + + # register all enums + enum_list = parse_enums(preprocessed_list) + + # register all structs + struct_list = parse_structs(preprocessed_list) + + # register all individual functions and their signatures + function_list = parse_prototypes(preprocessed_list) + + # export the module + modulefilename = write_module( f['fortran'], True, + wrappers.wrap_fortran, + enum_list, struct_list, function_list) + print( "Exported file: " + modulefilename ) + + modulefilename = write_module( f['python'], False, + wrappers.wrap_python, + enum_list, struct_list, function_list) + print( "Exported file: " + modulefilename ) + +# execute the program +main() diff --git a/tools/wrappers/__init__.py b/tools/wrappers/__init__.py new file mode 100644 index 00000000..249cb8e5 --- /dev/null +++ b/tools/wrappers/__init__.py @@ -0,0 +1,41 @@ +""" +Wrappers +======== + + @file wrappers/__init__.py + + PaStiX wrapper generators module intialization + + @copyright 2017-2018 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, + Univ. Bordeaux. All rights reserved. + + @version 6.0.0 + @author Mathieu Faverge + @date 2017-05-04 + +""" + +# translation_table with names of auxiliary variables +return_variables_dict = { + "double": ("value"), + "float": ("value"), + "pastix_int_t": ("value"), + "pastix_spm_t": ("spmo"), + "pastix_order_t": ("order"), + "spm_int_t" : ("value"), + "spmatrix_t": ("spmo"), +} + +# global list used to determine derived types +derived_types = [ 'pastix_int_t', 'pastix_data_t', 'pastix_order_t'] + +# name arrays which will be translated to assumed-size arrays, e.g. pA(*) +arrays_names_2D = ["pA", "pB", "pC", "pAB", "pQ", "pX", "pAs"] +arrays_names_1D = ["colptr", "rowptr", "loc2glob", "dofs", "row", + "iparm", "dparm", "bindtab", "perm", "invp", "schur_list", + "rang", "tree" ] + +__all__ = [ 'return_variables_dict', 'derived_types', 'arrays_names_1D', 'arrays_names_2D' ] + +from .wrap_python import * +from .wrap_fortran import * diff --git a/tools/wrappers/wrap_fortran.py b/tools/wrappers/wrap_fortran.py new file mode 100644 index 00000000..e4250f83 --- /dev/null +++ b/tools/wrappers/wrap_fortran.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +""" +Wrapper Fortran 90 +================== + + @file wrappers/wrap_fortran.py + + PaStiX generator for the Fortran 90 wrapper + + @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, + Univ. Bordeaux. All rights reserved. + + @version 6.0.0 + @author Mathieu Faverge + @date 2017-05-04 + +""" +import os +import re +import argparse +from . import * + +# set indentation in the f90 file +tab = " " +indent = " " + +itab=2 +iindent=3 + +# translation_table of types +types_dict = { + "int": ("integer(kind=c_int)"), + "spm_coeftype_t": ("integer(c_int)"), + "spm_dir_t": ("integer(c_int)"), + "spm_trans_t": ("integer(c_int)"), + "spm_uplo_t": ("integer(c_int)"), + "spm_diag_t": ("integer(c_int)"), + "spm_side_t": ("integer(c_int)"), + "spm_driver_t": ("integer(c_int)"), + "spm_fmttype_t": ("integer(c_int)"), + "spm_layout_t": ("integer(c_int)"), + "spm_normtype_t": ("integer(c_int)"), + "spm_rhstype_t": ("integer(c_int)"), + "spm_mtxtype_t": ("integer(c_int)"), + "spmatrix_t": ("type(spmatrix_t)"), + "spm_int_t": ("integer(kind=spm_int_t)"), + "size_t": ("integer(kind=c_size_t)"), + "char": ("character(kind=c_char)"), + "double": ("real(kind=c_double)"), + "float": ("real(kind=c_float)"), + "spm_complex64_t":("complex(kind=c_double_complex)"), + "spm_complex32_t":("complex(kind=c_float_complex)"), + "void": ("type(c_ptr)"), + "MPI_Comm": ("integer(kind=c_int)"), + "FILE": ("type(c_ptr)"), +} + +def iso_c_interface_type(arg, return_value, list): + """Generate a declaration for a variable in the interface.""" + + if (arg[1] == "*" or arg[1] == "**"): + is_pointer = True + else: + is_pointer = False + + if (is_pointer): + f_type = "type(c_ptr)" + else: + f_type = types_dict[arg[0]] + + if (not return_value and arg[1] != "**"): + f_type += ", " + f_pointer = "value" + else: + f_pointer = "" + + f_name = arg[2] + + list.append( [ f_type, f_pointer, f_name ] ); + return len(f_type + f_pointer) + +def iso_c_wrapper_type(arg, args_list, args_size): + """Generate a declaration for a variable in the Fortran wrapper.""" + + if (arg[1] == "*" or arg[1] == "**"): + is_pointer = True + else: + is_pointer = False + + if (is_pointer and arg[0].strip() == "void"): + f_type = "type(c_ptr), " + else: + f_type = types_dict[arg[0]] + ", " + + if is_pointer and not arg[3]: + f_intent = "intent(inout)" + else: + f_intent = "intent(in)" + + if (is_pointer): + f_intent += ", " + if (arg[1] == "*"): + f_target = "target" + else: + f_target = "pointer" + else: + f_target = "" + + f_name = arg[2] + + # Force case of myorder + if f_name == "myorder": + f_intent = "intent(in), " + f_target = "pointer" + + # detect array argument + if (is_pointer and f_name in arrays_names_2D): + f_array = "(:,:)" + elif (is_pointer and f_name in arrays_names_1D): + f_array = "(:)" + else: + f_array = "" + + f_line = f_type + f_intent + f_target + " :: " + f_name + f_array + + args_size[0] = max(args_size[0], len(f_type)) + args_size[1] = max(args_size[1], len(f_intent)) + args_size[2] = max(args_size[2], len(f_target)) + args_list.append( (f_type, f_intent, f_target, f_name, f_array ) ) + return f_line + +class wrap_fortran: + + @staticmethod + def header( f ): + filename = os.path.basename( f['filename'] ) + modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE) + header = ''' +! +! @file '''+ filename +''' +! +! ''' + f['description'] + ''' +! +! @copyright 2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, +! Univ. Bordeaux. All rights reserved. +! +! @version 6.0.0 +! @author Mathieu Faverge +! @date 2017-01-01 +! +! This file has been automatically generated with gen_wrappers.py +! +module ''' + modname + ''' + use iso_c_binding +''' + + if f['header'] != "": + header += f['header'] + + return header + + @staticmethod + def footer( f ): + filename = os.path.basename( f['filename'] ) + modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE) + footer = f['footer'] + ''' +end module ''' + modname + return footer + + @staticmethod + def enum( f, enum ): + """Generate an interface for an enum. + Translate it into constants.""" + + ename = enum[0] + params = enum[1] + + # initialize a string with the fortran interface + f_interface = tab + "! enum " + ename + "\n" + f_interface += tab + "enum, bind(C)\n" + + # loop over the arguments of the enum to get max param length + length=0 + for param in params: + length= max( length, len(param[0]) ) + fmt="%-"+ str(length) + "s" + + # Increment for index array enums + inc = 0 + if ename[1:5] == "parm": + inc=1 + + # loop over the arguments of the enum + for param in params: + name = param[0] + if isinstance(param[1],int): + if name[1:10] == "PARM_SIZE": + value = str(param[1]) + else: + value = str(param[1] + inc) + else: + value = str(param[1]) + f_interface += tab + " enumerator :: " + format(fmt % name) + " = " + value + "\n" + + f_interface += tab + "end enum\n" + return f_interface + + @staticmethod + def struct(struct): + """Generate an interface for a struct. + Translate it into a derived type.""" + + # initialize a string with the fortran interface + f_interface = "" + + s = itab + name = struct[0][2] + f_interface += s*" " + "type, bind(c) :: " + name + "\n" + + # loop over the arguments of the struct to get the length + s += iindent + slist = [] + length= 0 + for j in range(1,len(struct)): + length = max( length, iso_c_interface_type(struct[j], True, slist) ) + fmt = s*" " + "%-"+ str(length) + "s :: %s\n" + + # loop over the arguments of the struct + for j in range(0,len(slist)): + f_interface += format( fmt % (slist[j][0], slist[j][2]) ) + + s -= iindent + f_interface += s*" " + "end type " + name + "\n" + + return f_interface + + @staticmethod + def function(function): + """Generate an interface for a function.""" + + # is it a function or a subroutine + if (function[0][0] == "void"): + is_function = False + symbol="subroutine" + else: + is_function = True + symbol="function" + + c_symbol = function[0][2] + f_symbol = function[0][2] + "_c" + + used_derived_types = set([]) + for arg in function: + type_name = arg[0] + if (type_name in derived_types): + used_derived_types.add(type_name) + + # initialize a string with the fortran interface + s = itab + f_interface = s*" " + "interface\n" + s += iindent + + f_interface += s*" " + symbol + " " + f_symbol + "(" + s += itab + + initial_len = s + len(symbol + " " + f_symbol + "(" ) + + # loop over the arguments to compose the first line + s += iindent + for j in range(1,len(function)): + if (j != 1): + f_interface += ", " + initial_len += 2 + + l = len(function[j][2]) + if ((initial_len + l) > 77): + f_interface += "&\n" + s*" " + initial_len = s + + f_interface += function[j][2] + initial_len += l + + f_interface += ") &\n" + f_interface += s*" " + "bind(c, name='" + c_symbol +"')\n" + s -= iindent + + # add common header + f_interface += s*" " + "use iso_c_binding\n" + # import derived types + for derived_type in used_derived_types: + f_interface += s*" " + "import " + derived_type +"\n" + f_interface += s*" " + "implicit none\n" + + plist = [] + length = 0 + # add the return value of the function + if (is_function): + l = iso_c_interface_type(function[0], True, plist ) + 2 + length = max( length, l ); + plist[0][2] += "_c" + + # loop over the arguments to describe them + for j in range(1,len(function)): + length = max( length, iso_c_interface_type(function[j], False, plist ) ) + + # loop over the parameters + for j in range(0,len(plist)): + fmt = s*" " + "%s%" + str(length-len(plist[j][0])) + "s :: %s\n" + f_interface += format( fmt % (plist[j][0], plist[j][1], plist[j][2]) ) + + s -= itab + f_interface += s*" " + "end " + symbol + " " + f_symbol + "\n" + + s -= iindent + f_interface += s*" " + "end interface\n" + + return f_interface + + @staticmethod + def wrapper(function): + """Generate a wrapper for a function. + void functions in C will be called as subroutines, + functions in C will be turned to subroutines by appending + the return value as the last argument.""" + + return_type = function[0][0] + return_pointer = function[0][1] + return_var = "" + + # is it a function or a subroutine + if (return_type == "void"): + is_function = False + f_return = "call " + else: + is_function = True + if (return_type == "int"): + return_var = "info" + else: + return_var = return_variables_dict[return_type] + f_return = return_var + " = " + + c_symbol = function[0][2] + f_symbol = c_symbol + "_c" + + # loop over the arguments to compose the first line and call line + s = itab + signature_line = s*" " + "subroutine " + c_symbol + "(" + call_line = "" + signature_line_length = len(signature_line) + call_line_length = s + itab + len(f_return + f_symbol + "(" ) + double_pointers = [] + args_list = [] + args_size = [ 0, 0, 0 ] + length = 0 + for j in range(1,len(function)): + # pointers + arg_type = function[j][0] + arg_pointer = function[j][1] + arg_name = function[j][2] + + isfile = (arg_type == "FILE") + + if (j != 1): + if not isfile: + signature_line += ", " + signature_line_length += 2 + call_line += ", " + call_line_length += 2 + + # Signature line (do not make the argument list too long) + if not isfile: + line = iso_c_wrapper_type(function[j], args_list, args_size) + + l = len(arg_name) + if ((signature_line_length + l) > 77): + signature_line_length = s+itab+iindent + signature_line += "&\n" + signature_line_length*" " + signature_line += arg_name + signature_line_length += l + + # Call line + if isfile: + call_param = "c_null_ptr" + elif (arg_pointer == "**"): + aux_name = arg_name + "_aux" + call_param = aux_name + double_pointers.append(arg_name) + elif (arg_pointer == "*") and (not "type(c_ptr), " in args_list[len(args_list)-1]): + call_param = "c_loc(" + arg_name + ")" + else: + call_param = arg_name + + # Do not make the call line too long + l = len(call_param) + if ((call_line_length + l) > 77): + call_line_length = s+itab+iindent+itab + call_line += "&\n" + call_line_length*" " + call_line += call_param + call_line_length += l + + + # initialize a string with the fortran interface + f_wrapper = signature_line + if (is_function): + if (len(function) > 1): + f_wrapper += ", " + signature_line_length += 2 + + return_type = function[0][0] + return_pointer = function[0][1] + + l = len(return_var) + if ((signature_line_length + l) > 78): + signature_line_length = s+itab+iindent + f_wrapper += "&\n" + signature_line_length*" " + + f_wrapper += return_var + + f_wrapper += ")\n" + + s += itab + # add common header + f_wrapper += s*" " + "use iso_c_binding\n" + f_wrapper += s*" " + "implicit none\n" + + # add the return info value of the function + if (is_function): + f_type = types_dict[return_type] + ", " + f_intent = "intent(out)" + if (function[0][1] == "*"): + f_intent += ", " + f_target = "pointer" + else: + f_target = "" + + args_size[0] = max(args_size[0], len(f_type)) + args_size[1] = max(args_size[1], len(f_intent)) + args_size[2] = max(args_size[2], len(f_target)) + args_list.append( (f_type, f_intent, f_target, return_var, "" ) ) + + # loop over potential double pointers and generate auxiliary variables for them + init_line = "" + for double_pointer in double_pointers: + aux_name = double_pointer + "_aux" + + args_size[0] = max(args_size[0], len("type(c_ptr)")) + args_list.append( ("type(c_ptr)", "", "", aux_name, "") ) + init_line += s*" " + aux_name + " = c_loc(" + double_pointer + ")\n" + + # loop over the arguments to describe them + fmt = s*" " + "%-" + str(args_size[0]) + "s%-" + str(args_size[1]) + "s%-" + str(args_size[2]) + "s :: %s%s\n" + for j in range(0,len(args_list)): + f_wrapper += format( fmt % args_list[j] ) + + f_wrapper += "\n" + f_wrapper += init_line + if init_line != "": + f_wrapper += "\n" + + # generate the call to the C function + if (is_function and return_pointer == "*"): + f_wrapper += s*" " + "call c_f_pointer(" + f_symbol + "(" + call_line + "), " + return_var + ")\n" + else: + f_wrapper += s*" " + f_return + f_symbol + "(" + call_line + ")\n" + + # loop over potential double pointers and translate them to Fortran pointers + for double_pointer in double_pointers: + aux_name = double_pointer + "_aux" + f_wrapper += s*" " + "call c_f_pointer(" + aux_name + ", " + double_pointer + ")\n" + + s -= itab + f_wrapper += s*" " + "end subroutine " + c_symbol + "\n" + + return f_wrapper diff --git a/tools/wrappers/wrap_python.py b/tools/wrappers/wrap_python.py new file mode 100644 index 00000000..ed5b79b4 --- /dev/null +++ b/tools/wrappers/wrap_python.py @@ -0,0 +1,518 @@ +#!/usr/bin/env python +""" +Wrapper Python +============== + + @file wrappers/wrap_python.py + + PaStiX generator for the python wrapper + + @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, + Univ. Bordeaux. All rights reserved. + + @version 6.0.0 + @author Mathieu Faverge + @date 2017-05-04 + +""" +import os +import re +import argparse +from . import * + +indent=" " +iindent=4 + +# translation_table of types +types_dict = { + "int": ("c_int"), + "spm_coeftype_t": ("c_int"), + "spm_dir_t": ("c_int"), + "spm_trans_t": ("c_int"), + "spm_uplo_t": ("c_int"), + "spm_diag_t": ("c_int"), + "spm_side_t": ("c_int"), + "spm_driver_t": ("c_int"), + "spm_fmttype_t": ("c_int"), + "spm_layout_t": ("c_int"), + "spm_normtype_t": ("c_int"), + "spm_rhstype_t": ("c_int"), + "spm_mtxtype_t": ("c_int"), + "spm_int_t": ("pastix_int"), + "spmatrix_t": ("pyspmatrix_t"), + "size_t": ("c_size_t"), + "char": ("c_char"), + "double": ("c_double"), + "float": ("c_float"), + "void": ("c_void"), + "MPI_Comm": ("c_int"), + "FILE": ("c_void"), +} + +def iso_c_interface_type(arg, return_value, args_list, args_size): + """Generate a declaration for a variable in the interface.""" + + if (arg[1] == "*" or arg[1] == "**"): + is_pointer = True + else: + is_pointer = False + + f_type = types_dict[arg[0]] + if is_pointer: + if f_type == "c_void": + f_type = "c_void_p" + elif f_type == "c_char": + f_type = "c_char_p" + elif f_type == "c_int": + f_type = "c_int_p" + else: + f_type = "POINTER("+f_type+")" + + if (not return_value and arg[1] != "**"): + f_pointer = "value" + else: + f_pointer = "" + + f_name = format("\"%s\", " % arg[2] ) + + args_size[0] = max(args_size[0], len(f_name)) + args_size[1] = max(args_size[1], len(f_type)) + args_list.append( [ f_name, f_type ] ); + +def iso_c_wrapper_type(arg, args_list, args_size): + """Generate a declaration for a variable in the Fortran wrapper.""" + + if (arg[1] == "*" or arg[1] == "**"): + is_pointer = True + else: + is_pointer = False + + isfile = (arg[0] == "FILE") + f_type = types_dict[arg[0]] + + if is_pointer: + if f_type == "c_void": + f_type = "c_void_p" + elif f_type == "c_char": + f_type = "c_char_p" + elif f_type == "c_int": + f_type = "c_int_p" + else: + f_type = "POINTER("+f_type+")" + + f_name = arg[2] + f_call = f_name + + if isfile: + f_name = "" + f_call = "None" + + # detect array argument + if (is_pointer and f_name in arrays_names_2D): + f_call = f_name + ".ctypes.data_as( " + f_type + " )" + elif (is_pointer and f_name in arrays_names_1D): + f_call = f_name + ".ctypes.data_as( " + f_type + " )" + + if arg[1] == "**": + f_call = "pointer( " + f_call + " )" + + args_size[0] = max(args_size[0], len(f_name)) + args_size[1] = max(args_size[1], len(f_type)) + args_size[2] = max(args_size[2], len(f_call)) + args_list.append( [f_name, f_type, f_call ] ) + +class wrap_python: + + @staticmethod + def header( f ): + filename = os.path.basename( f['filename'] ) + filename = re.sub(r"\.in", "", filename) + header = '''""" + + @file ''' + filename + ''' + + ''' + f['description'] + ''' + + @copyright 2017-2017 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, + Univ. Bordeaux. All rights reserved. + + @version 6.0.0 + @author Pierre Ramet + @author Mathieu Faverge + @author Louis Poirel + @date 2017-05-04 + +This file has been automatically generated with gen_wrappers.py + +""" +from ctypes import * +import numpy as np +''' + if f['header'] != "": + header += "\n" + f['header'] + return header; + + @staticmethod + def footer( f ): + filename = os.path.basename( f['filename'] ) + modname = re.sub(r".f90", "", filename, flags=re.IGNORECASE) + footer = f['footer'] + return footer + + @staticmethod + def enum( f, enum ): + """Generate an interface for an enum. + Translate it into constants.""" + + ename = enum[0] + params = enum[1] + + # initialize a string with the fortran interface + py_interface = "class " + ename + ":\n" + + # loop over the arguments of the enum to get max param length + length=0 + for param in params: + # Convert IPARM/DPARM to lower case + if param[0][1:5] == "PARM": + param[0] = re.sub(r"[ID]PARM_", "", param[0]) + param[0] = param[0].lower() + + # Remove Pastix from everything + param[0] = re.sub(r"Pastix", "", param[0]) + + if ename == "error": + param[0] = re.sub(r"PASTIX_", "", param[0]) + param[0] = re.sub(r"ERR_", "", param[0]) + elif ename == "fact_mode" or ename == "factotype" or ename == "solv_mode": + param[0] = re.sub(r"Fact", "", param[0]) + param[0] = re.sub(r"Solv", "", param[0]) + param[0] = re.sub(r"Mode", "", param[0]) + elif ename == "scheduler": + param[0] = re.sub(r"Sched", "", param[0]) + elif ename == "threadmode": + param[0] = re.sub(r"Thread", "", param[0]) + elif ename[0:8] == "compress": + param[0] = re.sub(r"Compress", "", param[0]) + param[0] = re.sub(r"When", "", param[0]) + param[0] = re.sub(r"Method", "", param[0]) + elif ename == "rhstype": + param[0] = re.sub(r"Rhs", "", param[0]) + elif ename == "trans": + param[0] = param[0] + elif ename == "mtxtype": + param[1] = re.sub(r"Pastix", "trans.", param[1]) + elif ename == "normtype": + param[0] = re.sub(r"Norm", "", param[0]) + else: + param[0] = re.sub(ename, "", param[0], flags=re.IGNORECASE) + length = max( length, len(param[0])) + fmt="%-"+ str(length) + "s" + + # loop over the arguments of the enum + for param in params: + name = param[0] + value = str(param[1]) + + py_interface += indent + format(fmt % name) + " = " + value + "\n" + + if ename in f['enums']: + py_interface += f['enums'][ename] + + return py_interface + + @staticmethod + def struct(struct): + """Generate an interface for a struct. + Translate it into a derived type.""" + + # initialize a string with the fortran interface + py_interface = "" + + s = 0 + name = struct[0][2] + name = re.sub(r"pastix_", "", name) + py_interface += "class pypastix_" + name + "(Structure):\n" + + s = iindent + py_interface += s*" " + "_fields_ = [" + headline = (s+len("_fields_ = ["))*" " + + slist = [] + ssize = [ 0, 0 ] + + # loop over the arguments of the enum + for j in range(1,len(struct)): + iso_c_interface_type(struct[j], True, slist, ssize) + + s += iindent + fmt = "(%-"+ str(ssize[0]) + "s %-"+ str(ssize[1]) +"s)" + + for j in range(0,len(slist)): + if (j > 0): + py_interface += ",\n" + headline + + py_interface += format( fmt % (slist[j][0], slist[j][1]) ) + + py_interface += " ]\n" + + return py_interface + + @staticmethod + def function(function): + """Generate an interface for a function.""" + + return_type = function[0][0] + return_pointer = function[0][1] + + # is it a function or a subroutine + if (return_type == "void"): + is_function = False + else: + is_function = True + + c_symbol = function[0][2] + if "pastix" in c_symbol: + libname = "libpastix" + prefix = "pypastix_" + elif "spm" in c_symbol: + libname = "libspm" + prefix = "pyspm_" + else: + print("ERROR: function name without pastix nor spm") + return + + # loop over the arguments to compose the different call lines + func_line = "def " + prefix + c_symbol + "(" + func_line_length = len(func_line) + func_line_indent = len(func_line) + + args_line = iindent*" " + libname + "." + c_symbol + ".argtypes = [" + args_line_length = len(args_line) + args_line_indent = len(args_line) + + if is_function: + call_line = iindent*" " + "return " + libname + "." + c_symbol + "(" + + py_type = types_dict[return_type] + if return_pointer == "*": + if py_type == "c_void": + py_type = "c_void_p" + else: + py_type = "POINTER("+py_type+")" + elif return_pointer == "**": + py_type = "c_void_p" + + retv_line = iindent*" " + libname + "." + c_symbol + ".restype = " + py_type + else: + call_line = iindent*" " + libname + "." + c_symbol + "(" + retv_line = "" + call_line_length = len(call_line) + call_line_indent = len(call_line) + + args_list = [] + args_size = [ 0, 0, 0 ] + + # loop over the arguments to compose the first line + for j in range(1,len(function)): + iso_c_wrapper_type(function[j], args_list, args_size) + + for j in range(0, len(args_list)): + # pointers + arg_name = args_list[j][0] + arg_type = args_list[j][1] + arg_call = args_list[j][2] + + isfile = (len(arg_name) == 0) + + if j > 0: + if not isfile: + func_line += "," + func_line_length += 2 + args_line += "," + args_line_length += 2 + call_line += "," + call_line_length += 2 + + # func_line + l = len(arg_name) + if not isfile: + if ((func_line_length + l) > 78): + func_line_length = func_line_indent + func_line += "\n" + func_line_indent*" " + func_line += " " + arg_name + func_line_length += l + + # args_line + l = len(arg_type) + if ((args_line_length + l) > 78): + args_line_length = args_line_indent + args_line += "\n" + args_line_indent*" " + args_line += " " + arg_type + args_line_length += l + + # call_line + l = len(arg_call) + if ((call_line_length + l) > 78): + call_line_length = call_line_indent + call_line += "\n" + call_line_indent*" " + call_line += " " + arg_call + call_line_length += l + + py_interface = func_line + " ):\n" + py_interface += args_line + " ]\n" + if len(retv_line) > 0: + py_interface += retv_line + "\n" + py_interface += call_line + " )\n" + + # # add common header + # py_interface += indent + 2*indent + "use iso_c_binding\n" + # # import derived types + # for derived_type in used_derived_types: + # py_interface += indent + 2*indent + "import " + derived_type +"\n" + # py_interface += indent + 2*indent + "implicit none\n" + + + # # add the return value of the function + # if (is_function): + # py_interface += indent + 2*indent + iso_c_interface_type(function[0], True) + "_c" + # py_interface += "\n" + + # # loop over the arguments to describe them + # for j in range(1,len(function)): + # py_interface += indent + 2*indent + iso_c_interface_type(function[j], False) + # py_interface += "\n" + + # if (is_function): + # py_interface += indent + indent + "end function\n" + # else: + # py_interface += indent + indent + "end subroutine\n" + + # py_interface += indent + "end interface\n" + + return py_interface + + @staticmethod + def wrapper(function): + """Generate a wrapper for a function. + void functions in C will be called as subroutines, + functions in C will be turned to subroutines by appending + the return value as the last argument.""" + + # is it a function or a subroutine + if (function[0][0] == "void"): + is_function = False + else: + is_function = True + + c_symbol = function[0][2] + f_symbol = c_symbol + "_c" + + if (is_function): + initial_indent_signature = len(indent + "subroutine " + c_symbol + "(") * " " + initial_indent_call = len(indent + indent + "info = " + f_symbol + "(") * " " + else: + initial_indent_signature = len(indent + "subroutine " + c_symbol + "(") * " " + initial_indent_call = len(indent + indent + "call " + f_symbol + "(") * " " + + # loop over the arguments to compose the first line and call line + signature_line = "" + call_line = "" + double_pointers = [] + for j in range(1,len(function)): + if (j != 1): + signature_line += ", " + call_line += ", " + + # do not make the argument list too long + if (j%9 == 0): + call_line += "&\n" + initial_indent_call + signature_line += "&\n" + initial_indent_signature + + # pointers + arg_type = function[j][0] + arg_pointer = function[j][1] + arg_name = function[j][2] + + signature_line += arg_name + if (arg_pointer == "**"): + aux_name = arg_name + "_aux" + call_line += aux_name + double_pointers.append(arg_name) + elif (arg_pointer == "*"): + call_line += "c_loc(" + arg_name + ")" + else: + call_line += arg_name + + contains_derived_types = False + for arg in function: + if (arg[0] in derived_types): + contains_derived_types = True + + # initialize a string with the fortran interface + f_wrapper = "" + f_wrapper += indent + "subroutine " + f_wrapper += c_symbol + "(" + + # add the info argument at the end + f_wrapper += signature_line + if (is_function): + if (len(function) > 1): + f_wrapper += ", " + + return_type = function[0][0] + return_pointer = function[0][1] + if (return_type == "int"): + return_var = "info" + else: + return_var = return_variables_dict[return_type] + + f_wrapper += return_var + + f_wrapper += ")\n" + + # add common header + f_wrapper += indent + indent + "use iso_c_binding\n" + f_wrapper += indent + indent + "implicit none\n" + + # loop over the arguments to describe them + for j in range(1,len(function)): + f_wrapper += indent + indent + iso_c_wrapper_type(function[j]) + "\n" + + # add the return info value of the function + if (is_function): + if (function[0][1] == "*"): + f_target = ", pointer" + else: + f_target = "" + + f_wrapper += indent + indent + types_dict[return_type] + ", intent(out)" + f_target + " :: " + return_var + "\n" + + f_wrapper += "\n" + + # loop over potential double pointers and generate auxiliary variables for them + for double_pointer in double_pointers: + aux_name = double_pointer + "_aux" + f_wrapper += indent + indent + "type(c_ptr) :: " + aux_name + "\n" + f_wrapper += "\n" + + if (is_function): + f_return = return_var + f_return += " = " + else: + f_return = "call " + + # generate the call to the C function + if (is_function and return_pointer == "*"): + f_wrapper += indent + indent + "call c_f_pointer(" + f_symbol + "(" + call_line + "), " + return_var + ")\n" + else: + f_wrapper += indent + indent + f_return + f_symbol + "(" + call_line + ")\n" + + # loop over potential double pointers and translate them to Fortran pointers + for double_pointer in double_pointers: + aux_name = double_pointer + "_aux" + f_wrapper += indent + indent + "call c_f_pointer(" + aux_name + ", " + double_pointer + ")\n" + + f_wrapper += indent + "end subroutine\n" + + return f_wrapper -- GitLab