Publishing 2019 R1.1 content and Myriad plugin sources (#162)
[platform/upstream/dldt.git] / inference-engine / thirdparty / clDNN / utils / codegen / generate_api_wrappers.py
1 #!/usr/bin/env python2
2
3 import argparse
4 import os
5
6 import re
7
8
9 # Pattern that filters file names that are headers.
10 headerFileNamePattern = re.compile('^[a-zA-Z0-9_]+\\.[hH]$')
11 # Marker that marks API function/data member (at its beginning).
12 apiMemberMarker = 'CLDNN_API'
13 # Macros that should be expanded to API functions.
14 apiMacroMemberMatchers = [
15     (re.compile('^\\s*CLDNN_DECLARE_PRIMITIVE_TYPE_ID\\s*\\(\\s*([a-zA-Z0-9_]+)\\s*\\)\\s*;', re.MULTILINE),
16      'cldnn_primitive_type_id cldnn_\\1_type_id(cldnn_status* status)')
17 ]
18 # C language and project reserved keywords (that cannot be used as function/parameter name).
19 reservedKeywords = [
20     'auto', 'else', 'long', 'switch', 'break', 'enum', 'register', 'typedef', 'case', 'extern', 'return', 'union',
21     'char', 'float', 'short', 'unsigned', 'const', 'for', 'signed', 'void', 'continue', 'goto', 'sizeof', 'volatile',
22     'default', 'if', 'static', 'while', 'do', 'int', 'struct', '_Packed', 'double'
23 ]
24 # C language and project reserved keyword patterns (that cannot be used as function/parameter name).
25 reservedKeywordPatterns = [
26     re.compile('^__[a-z0-9_]+__$', re.IGNORECASE)
27 ]
28
29
30 apiMemberMatcher = re.compile('^\\s*' + re.escape(apiMemberMarker) + '\\s+([^;]+);', re.MULTILINE)
31 typeIdentifierSplitter = re.compile('^(.*?)([a-zA-Z_][a-zA-Z0-9_]*)$')
32
33
34 def stripCommentsAndPreprocessor(content):
35     """ Strips out comments and preprocessor constructs from text written in C language (or compatible).
36
37     :param content: Text with code written in C language (or compatible).
38     :type content: str
39     :return: Content of C language code with comments and preprocessor constructs stripped out.
40     :rtype: str
41     """
42     # FSA states:
43     # 0 - normal context, start state
44     # 1 - string context
45     # 2 - string context, after \ character (character escape)
46     # 3 - normal context, after / character (possible comment)
47     # 4 - multi-line comment context
48     # 5 - multi-line comment context, after * character (possible end of comment)
49     # 6 - single-line comment context
50     # 7 - single-line comment context, after \ character (escape)
51     # 8 - preprocessor definition/instruction context
52     # 9 - preprocessor definition/instruction context, after \ character (escape)
53
54     state = 0
55     strippedOutputArray = []
56     for c in content:
57         # normal context, start state
58         if state == 0:
59             if c == '"':
60                 state = 1   # string
61                 strippedOutputArray.append(c)
62             elif c == '/':
63                 state = 3   # possible comment (no out)
64             elif c == '#':
65                 state = 8   # preprocessor (no out)
66             else:
67                 strippedOutputArray.append(c)
68         # string context
69         elif state == 1:
70             if c == '\\':
71                 state = 2   # escape sequence
72                 strippedOutputArray.append(c)
73             elif c == '"':
74                 state = 0
75                 strippedOutputArray.append(c)
76             else:
77                 strippedOutputArray.append(c)
78         # string context, after \ character (character escape)
79         elif state == 2:
80             state = 1   # do not leave string context on any character
81             strippedOutputArray.append(c)
82         # normal context, after / character (possible comment)
83         elif state == 3:
84             if c == '*':
85                 state = 4   # multi-line comment (no out)
86             elif c == '/':
87                 state = 6   # single-line comment (no out)
88             else:
89                 state = 0   # not comment (flush previous token)
90                 strippedOutputArray.append('/')
91                 strippedOutputArray.append(c)
92         # multi-line comment context
93         elif state == 4:
94             if c == '*':
95                 state = 5   # possible end of comment (no out)
96         # multi-line comment context, after * character (possible end of comment)
97         elif state == 5:
98             if c == '/':
99                 state = 0   # end of comment (no out)
100             elif c == '*':
101                 pass   # not end of comment, but check next token for possible end of comment (no out)
102             else:
103                 state = 4   # not end of comment (no out)
104         # single-line comment context
105         elif state == 6:
106             if c == '\n':
107                 state = 0   # end of comment (append new line)
108                 strippedOutputArray.append('\n')
109             elif c == '\\':
110                 state = 7   # escape in comment (can escape new line character) (no out)
111         # single-line comment context, after \ character (escape)
112         elif state == 7:
113             state = 6   # do not leave comment on any character (no out)
114         # preprocessor definition/instruction context
115         elif state == 8:
116             if c == '\n':
117                 state = 0   # end of preprocessor construct (no out)
118             elif c == '\\':
119                 state = 9   # escape in preprocessor construct (no out)
120         # preprocessor definition/instruction context, after \ character (escape)
121         elif state == 9:
122             state = 8   # do not leave preprocessor construct on any character (no out)
123
124     return ''.join(strippedOutputArray)
125
126
127 def isReservedName(name):
128     """ Determines whether specified name is reserved in C language or project.
129
130     :param name: Name to check.
131     :type name: str
132     :return: True, if name is reserved; otherwise, False.
133     :rtype: bool
134     """
135     if name.strip() in reservedKeywords:
136         return True
137     for keywordPattern in reservedKeywordPatterns:
138         if keywordPattern.match(name.strip()):
139             return True
140     return False
141
142
143 automaticSplitVarIndex = 0
144
145
146 def splitTypeAndIdentifier(decl):
147     match = typeIdentifierSplitter.match(decl.strip())
148     if match and not isReservedName(match.group(2)):
149         return match.group(1).strip(), match.group(2).strip()
150     else:
151         global automaticSplitVarIndex
152         automaticSplitVarIndex += 1
153         return decl.strip(), 'arg{0:05d}'.format(automaticSplitVarIndex)
154
155
156 def parseApiMemberDeclarator(apiDecl):
157     parenLevel = 0
158     state = 0
159
160     name = ''
161     returnType = ''
162     isFunction = False
163     paramDecls = []   # Collection of extracted parameter declarations
164     attrs = ''
165
166     # Reversed array where tokens are collected:
167     nameRArray = []         # API member name
168     returnTypeRArray = []   # Return type declaration
169     paramRArray = []        # Parameter declarator
170
171     cLoc = len(apiDecl)
172     cAttributeSplitLoc = cLoc
173     while cLoc > 0:
174         cLoc -= 1
175         c = apiDecl[cLoc]
176
177         if parenLevel == 0:
178             # API member declarator context, start state
179             if state == 0:
180                 if c == ')':
181                     state = 1   # possible function declarator
182                     isFunction = True
183                     attrs = apiDecl[cLoc + 1:]
184             # function parameter declaration
185             elif state == 1:
186                 if c == ')':   # nesting of parentheses (stop normal parsing, only collect tokens)
187                     parenLevel += 1
188                     paramRArray.append(c)
189                 elif c == '(':
190                     state = 2   # end of parameters declaration (move to function name, store parameter if needed)
191                     if len(paramRArray) > 0:
192                         paramDecls.append(''.join(paramRArray[::-1]).strip())
193                         paramRArray = []
194                 elif c == ',':   # start of next parameter declaration
195                     paramDecls.append(''.join(paramRArray[::-1]).strip())
196                     paramRArray = []
197                 else:
198                     paramRArray.append(c)
199             # function name (optional whitespace)
200             elif state == 2:
201                 if not c.isspace():
202                     cLoc += 1
203                     state = 3   # ignore whitespace until non-whitespace character is encountered (re-parse token)
204             # function name
205             elif state == 3:
206                 if c.isalnum() or c == '_':
207                     nameRArray.append(c)
208                 else:
209                     name = ''.join(nameRArray[::-1]).strip()
210                     nameRArray = []
211
212                     cLoc += 1   # re-parse unmatched token
213                     if isReservedName(name):
214                         cAttributeSplitLoc = cLoc
215
216                         name = ''
217                         returnType = ''
218                         isFunction = False
219                         paramDecls = []
220                         attrs = apiDecl[cLoc:]
221
222                         state = 0   # if parsed function declaration has reserved name, it need to be treated as attribute
223                     else:
224                         state = 4   # name is not reserved - treat next tokens as return type
225             # return type declarator
226             elif state == 4:
227                 returnTypeRArray.append(c)
228         else:
229             # Nesting of parentheses - collect tokens only.
230             if c == ')':
231                 parenLevel += 1
232             elif c == '(':
233                 parenLevel -= 1
234             paramRArray.append(c)
235
236     if isFunction:
237         if len(nameRArray) > 0:
238             name = ''.join(nameRArray[::-1]).strip()
239         if len(returnTypeRArray) > 0:
240             returnType = ''.join(returnTypeRArray[::-1]).strip()
241         if len(paramRArray) > 0:
242             paramDecls.append(''.join(paramRArray[::-1]).strip())
243     else:
244         returnType, name = splitTypeAndIdentifier(apiDecl[:cAttributeSplitLoc])
245
246     paramDeclInfos = []
247     for decl in reversed(paramDecls):
248         paramType, paramName = splitTypeAndIdentifier(decl)
249         paramDeclInfos.append({'name': paramName, 'type': paramType})
250
251     return {
252         'name': name,
253         'isFunction': isFunction,
254         'returnType': returnType,
255         'params': paramDeclInfos,
256         'attrs': attrs
257     }
258
259
260 # Tests:
261 # print parseApiMemberDeclarator('int const   __attribute__((pure)) ')
262 # print parseApiMemberDeclarator('int foo1   __attribute__((pure)) ')
263 # print parseApiMemberDeclarator('int foo1')
264 # print parseApiMemberDeclarator('void a(int, const a*bb)')
265 # print parseApiMemberDeclarator('int foo __attribute__((static))')
266 # print parseApiMemberDeclarator('int foo()__attribute__((static))')
267 # print parseApiMemberDeclarator('int foo()__attribute__((static)) __attribute__((data(1,2,3))) do() NN')
268 # print parseApiMemberDeclarator('int foo (int a, int b)__attribute__((static)) __attribute__((data(1,2,3))) do() NN')
269 # print parseApiMemberDeclarator('DD(int,a)* foo(int a, const D(1,I())* b)__attribute__((static)) __attribute__((data(1,2,3))) do() NN')
270
271
272 def parseHeaderFile(headerFilePath):
273     """ Opens, reads and parses header file and extracts information about API functions inside.
274
275     :param headerFilePath: Path to header file that will be parsed.
276     :return: List of API function declarations. Each declaration contains dictionary describing function name,
277              parameter types and return type.
278     :rtype: list
279     """
280     apiMembersInfo = []
281
282     headerFile = file(headerFilePath)
283     headerContent = headerFile.read()
284     strippedContent = stripCommentsAndPreprocessor(headerContent)
285     matchedFunctionDecls = apiMemberMatcher.findall(strippedContent)
286     for decl in matchedFunctionDecls:
287         apiMembersInfo.append(parseApiMemberDeclarator(decl))
288
289     for matcher, replace in apiMacroMemberMatchers:
290         matchedMacroDecls = matcher.finditer(strippedContent)
291         for decl in matchedMacroDecls:
292             apiMembersInfo.append(parseApiMemberDeclarator(decl.expand(replace)))
293
294     return apiMembersInfo
295
296
297 def main(parsedOptions):
298     """ Main script function.
299
300     The script generates header file with wrappers for all API functions from headers contained in specific directory.
301
302     :param parsedOptions: Arguments parsed by argparse.ArgumentParser class.
303     :return: Exit code for script.
304     :rtype: int
305     """
306     scanDirectory = parsedOptions.dir if parsedOptions.dir is not None and parsedOptions.dir != '' else os.curdir
307
308     apiMembersInfo = []
309
310     for scanDir, scanSubdirectories, scanFileNames in os.walk(scanDirectory):
311         for scanFileName in scanFileNames:
312             if headerFileNamePattern.match(scanFileName):
313                 apiMembersInfo.extend(parseHeaderFile(os.path.join(scanDir, scanFileName)))
314
315     print r'''/*******************************************************************************
316 * Copyright 2016 Intel Corporation
317 *
318 * Licensed under the Apache License, Version 2.0 (the "License");
319 * you may not use this file except in compliance with the License.
320 * You may obtain a copy of the License at
321 *
322 *     http://www.apache.org/licenses/LICENSE-2.0
323 *
324 * Unless required by applicable law or agreed to in writing, software
325 * distributed under the License is distributed on an "AS IS" BASIS,
326 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
327 * See the License for the specific language governing permissions and
328 * limitations under the License.
329 *******************************************************************************/
330
331 /*********************************************************
332  * AUTOGENERATED FILE; DO NOT EDIT
333  ********************************************************/
334
335 #ifdef _WIN32
336 #include <windows.h>
337 typedef HINSTANCE lib_handle_t;
338 #define NULL_LIB 0
339 #else
340 #define _GNU_SOURCE /* for dlvsym() */
341 #include <dlfcn.h>
342 typedef void * lib_handle_t;
343 #define NULL_LIB NULL
344 #endif
345
346 #include <assert.h>
347 #include <stdio.h>
348
349 #include "cldnn_prv.h"
350 #include "cldnn/cldnn.h"
351
352 static inline lib_handle_t load_library(const char *lib_name)
353 {
354 #ifdef _WIN32
355     return LoadLibraryEx(lib_name, NULL, 0);
356 #else
357     return dlopen(lib_name, RTLD_LAZY | RTLD_GLOBAL);
358 #endif
359 }
360
361 static inline void *load_symbol(const lib_handle_t lib,
362         const char *name)
363 {
364 #ifdef _WIN32
365     return GetProcAddress(lib, name);
366 #else
367     return dlsym(lib, name);
368 #endif
369 }
370
371 '''
372
373     for apiMemberInfo in apiMembersInfo:
374         if apiMemberInfo['isFunction']:
375             print '{0} (*{1}_fptr)({2}){5} = NULL;\n{0} {1}({4}){5} {{\n    assert({1}_fptr != NULL);\n    return {1}_fptr({3});\n}}\n'.format(
376                 apiMemberInfo['returnType'],
377                 apiMemberInfo['name'],
378                 ', '.join([x['type'] for x in apiMemberInfo['params']]),
379                 ', '.join([x['name'] for x in apiMemberInfo['params']]),
380                 ', '.join([x['type'] + ' ' + x['name'] for x in apiMemberInfo['params']]),
381                 (' ' + apiMemberInfo['attrs']) if len(apiMemberInfo['attrs']) > 0 else '')
382
383     print 'int cldnn_load_symbols(lib_handle_t handle) {'
384     for apiMemberInfo in apiMembersInfo:
385         if apiMemberInfo['isFunction']:
386             print '    {1}_fptr = ({0} (*)({2}){5}) load_symbol(handle, "{1}");\n    if ({1}_fptr == NULL) {{\n        return -1;\n    }}\n'.format(
387                 apiMemberInfo['returnType'],
388                 apiMemberInfo['name'],
389                 ', '.join([x['type'] for x in apiMemberInfo['params']]),
390                 ', '.join([x['name'] for x in apiMemberInfo['params']]),
391                 ', '.join([x['type'] + ' ' + x['name'] for x in apiMemberInfo['params']]),
392                 (' ' + apiMemberInfo['attrs']) if len(apiMemberInfo['attrs']) > 0 else '')
393     print '    return 0;\n}'
394
395     print r'''
396
397 enum _lib_status {
398     lib_unloaded = 1,
399     lib_loaded = 0,
400     lib_failed = -1
401 };
402
403 int cldnn_load_lib(const char *lib_name)
404 {
405     printf("begin cldnn_load_lib: %s\n", lib_name);
406     static int lib_status = lib_unloaded;
407     lib_handle_t lib_handle = NULL;
408
409     if (lib_status != lib_unloaded)
410         return lib_status;
411
412     lib_handle = load_library(lib_name);
413     if (lib_handle == NULL)
414     {
415         lib_status = lib_failed;
416         printf("Could not load library '%s'\n", lib_name);
417         return lib_status;
418     }
419
420     lib_status = cldnn_load_symbols(lib_handle) == 0 ? lib_loaded : lib_failed;
421     return lib_status;
422 }
423 '''
424
425
426 if __name__ == "__main__":
427     optParser = argparse.ArgumentParser(description = 'Generates wrappers for all API functions contained in headers' +
428                                                       'of specific directory.')
429
430     optParser.add_argument('dir', metavar = '<dir>', type = str, default = None,
431                            help = 'Directory to scan for header files. Default/None specified:' +
432                                   ' current working directory.')
433     optParser.add_argument('--version', action = 'version', version = '%(prog)s 1.0')
434
435     options = optParser.parse_args()
436
437     exitCode = main(options)
438     optParser.exit(exitCode)