#!/usr/bin/env python # Copyright 2015 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Utility functions used to parse a list of DLL entry points. # Expected format: # # -> ignored # # -> ignored # % -> verbatim output for header files. # ! -> prefix name for header files. # ; -> entry point declaration. # # Anything else is an error. import re import sys import argparse re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""") re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_\[\]]*)$""") class Entry: """Small class used to model a single DLL entry point.""" def __init__(self, func_name, return_type, parameters): """Initialize Entry instance. |func_name| is the function name, |return_type| its return type, and |parameters| is a list of (type,name) tuples from the entry's signature. """ self.__name__ = func_name self.return_type = return_type self.parameters = "" self.vartypes = [] self.varnames = [] self.call = "" comma = "" for param in parameters: self.vartypes.append(param[0]) self.varnames.append(param[1]) self.parameters += "%s%s %s" % (comma, param[0], param[1]) self.call += "%s%s" % (comma, param[1]) comma = ", " def banner_command(argv): """Return sanitized command-line description. |argv| must be a list of command-line parameters, e.g. sys.argv. Return a string corresponding to the command, with platform-specific paths removed.""" # Remove path from first parameter argv = argv[:] argv[0] = "android/scripts/gen-entries.py" return ' '.join(argv) def parse_entries_file(lines): """Parse an .entries file and return a tuple of: entries: list of Entry instances from the file. prefix_name: prefix name from the file, or None. verbatim: list of verbatim lines from the file. errors: list of errors in the file, prefixed by line number. """ entries = [] verbatim = [] errors = [] lineno = 0 prefix_name = None namespaces = [] for line in lines: lineno += 1 line = line.strip() if len(line) == 0: # Ignore empty lines continue if line[0] == '#': # Ignore comments continue if line[0] == '!': # Prefix name prefix_name = line[1:] continue if line[0] == '%': # Verbatim line copy verbatim.append(line[1:]) continue if line.startswith("namespaces"): # Namespaces namespaces = list(map(lambda t: t.strip(), line.split("namespaces")[1].strip().split(","))) continue # Must be a function signature. m = re_func.match(line) if not m: errors.append("%d: '%s'" % (lineno, line)) continue return_type, func_name, parameters = m.groups() return_type = return_type.strip() parameters = parameters.strip() params = [] failure = False if parameters != "void": for parameter in parameters.split(','): parameter = parameter.strip() m = re_param.match(parameter) if not m: errors.append("%d: parameter '%s'" % (lineno, parameter)) failure = True break else: param_type, param_name = m.groups() params.append((param_type.strip(), param_name.strip())) if not failure: entries.append(Entry(func_name, return_type, params)) return (entries, prefix_name, verbatim, namespaces, errors) def gen_functions_header(entries, prefix_name, verbatim, filename, with_args): """Generate a C header containing a macro listing all entry points. |entries| is a list of Entry instances. |prefix_name| is a prefix-name, it will be converted to upper-case. |verbatim| is a list of verbatim lines that must appear before the macro declaration. Useful to insert #include <> statements. |filename| is the name of the original file. """ prefix_name = prefix_name.upper() print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// DO NOT EDIT THIS FILE") print("") print("#ifndef %s_FUNCTIONS_H" % prefix_name) print("#define %s_FUNCTIONS_H" % prefix_name) print("") for line in verbatim: print(line) print("#define LIST_%s_FUNCTIONS(X) \\" % prefix_name) new_header_apis = [ # AEMU private exts "eglGetMaxGLESVersion", "eglBlitFromCurrentReadBufferANDROID", "eglSetImageFenceANDROID", "eglWaitImageFenceANDROID", "eglAddLibrarySearchPathANDROID", "eglQueryVulkanInteropSupportANDROID", "eglQueryVulkanInteropSupportANDROID", # For snapshotting "eglLoadConfig", "eglLoadContext", "eglLoadAllImages", "eglSaveConfig", "eglSaveContext", "eglSaveAllImages", "eglPreSaveContext", "eglPostLoadAllImages", "eglPostSaveContext", "eglUseOsEglApi", "eglFillUsages", "eglSetMaxGLESVersion", ] need_decls = [] for entry in entries: if entry.__name__ in new_header_apis: need_decls.append(entry) if with_args: print(" X(%s, %s, (%s), (%s)) \\" % \ (entry.return_type, entry.__name__, entry.parameters, entry.call)) else: print(" X(%s, %s, (%s)) \\" % \ (entry.return_type, entry.__name__, entry.parameters)) print("") for entry in need_decls: print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) print("") print("#endif // %s_FUNCTIONS_H" % prefix_name) def gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, with_args): """Generate a C++ header containing a header for |entries| with nesting inside |namespaces|. |prefix_name| is a prefix-name, it will be converted to upper-case. |verbatim| is a list of verbatim lines that must appear before the macro declaration. Useful to insert #include <> statements. |filename| is the name of the original file. """ prefix_name = prefix_name.upper() print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// DO NOT EDIT THIS FILE") print("") print("#pragma once") print("") for line in verbatim: print(line) for ns in namespaces: print("namespace %s {" % ns) for entry in entries: if "gles" in filename: print("GL_APICALL %s GL_APIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) else: print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) for ns in namespaces: print("} // namespace %s" % ns) def gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, with_args): """Generate a C++ header containing a header for |entries| with nesting inside |namespaces|. |prefix_name| is a prefix-name, it will be converted to upper-case. |verbatim| is a list of verbatim lines that must appear before the macro declaration. Useful to insert #include <> statements. |filename| is the name of the original file. """ prefix_name = prefix_name.upper() print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// DO NOT EDIT THIS FILE") print("") for line in verbatim: print(line) for ns in namespaces: print("namespace %s {" % ns) for entry in entries: if "gles" in filename: if "void" == entry.return_type: return_part = "return" else: return_part = "return (%s)0" % entry.return_type print("GL_APICALL %s GL_APIENTRY %s(%s) { %s; }" % (entry.return_type, entry.__name__, ", ".join(entry.vartypes), return_part)) else: print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) namespaces.reverse() for ns in namespaces: print("} // namespace %s" % ns) # The purpose of gen_translator() # is to quickly generate implementations on the host Translator, # which processes commands that just got onto the renderthread off goldfish pipe # and are fed to system OpenGL. def gen_translator(entries): # Definitions for custom implementation bodies go in # android/scripts/gles3translatorgen/gles30_custom.py # android/scripts/gles3translatorgen/gles31_custom.py from gles3translatorgen import gles30_custom from gles3translatorgen import gles31_custom translator_custom_share_processing = { } for (k, v) in list(gles30_custom.custom_share_processing.items()): translator_custom_share_processing[k] = v for (k, v) in list(gles31_custom.custom_share_processing.items()): translator_custom_share_processing[k] = v translator_custom_pre = { } for (k, v) in list(gles30_custom.custom_preprocesses.items()): translator_custom_pre[k] = v for (k, v) in list(gles31_custom.custom_preprocesses.items()): translator_custom_pre[k] = v translator_custom_post = { } for (k, v) in list(gles30_custom.custom_postprocesses.items()): translator_custom_post[k] = v for (k, v) in list(gles31_custom.custom_postprocesses.items()): translator_custom_post[k] = v translator_no_passthrough = {} for (k, v) in list(gles30_custom.no_passthrough.items()): translator_no_passthrough[k] = v for (k, v) in list(gles31_custom.no_passthrough.items()): translator_no_passthrough[k] = v translator_needexternc = { "glGetStringi": 1, "glUniform4ui": 1, "glGetUniformIndices": 1, "glTransformFeedbackVaryings": 1, "glCreateShaderProgramv": 1, "glProgramUniform2ui": 1, "glProgramUniform3ui": 1, "glProgramUniform4ui": 1, "glBindVertexBuffer": 1, }; translator_nocontext_fail_codes = { "glClientWaitSync" : "GL_WAIT_FAILED", }; def needExternC(entry): if entry.__name__ in translator_needexternc: return "extern \"C\" " else: return "" def get_fail_code(entry): if entry.__name__ in translator_nocontext_fail_codes: return translator_nocontext_fail_codes[entry.__name__]; else: return "0" def gen_cxt_getter(entry): if (entry.return_type == "void"): print(" GET_CTX_V2();") else: print(" GET_CTX_V2_RET(%s);" % get_fail_code(entry)) def gen_validations_custom_impl(entry): isGen = entry.__name__.startswith("glGen") isDelete = entry.__name__.startswith("glDelete") isBufferOp = "Buffer" in entry.__name__ hasTargetArg = "target" in entry.varnames hasProgramArg = "program" in entry.varnames def mySetError(condition, glerr): if entry.return_type == "void": return "SET_ERROR_IF(%s,%s)" % (condition, glerr); else: return "RET_AND_SET_ERROR_IF(%s,%s,%s)" % (condition, glerr, get_fail_code(entry)); if (isGen or isDelete) and ("n" in entry.varnames): print(" %s;" % mySetError("n < 0", "GL_INVALID_VALUE")); if (isBufferOp and hasTargetArg): print(" %s;" % mySetError("!GLESv2Validate::bufferTarget(ctx, target)", "GL_INVALID_ENUM")); if entry.__name__ in translator_custom_pre: print(translator_custom_pre[entry.__name__]) def gen_call_ret(entry): globalNameTypes = { ("GLuint", "program") : "NamedObjectType::SHADER_OR_PROGRAM", ("GLuint", "texture") : "NamedObjectType::TEXTURE", ("GLuint", "buffer") : "NamedObjectType::VERTEXBUFFER", ("GLuint", "sampler") : "NamedObjectType::SAMPLER", ("GLuint", "query") : "NamedObjectType::QUERY", } globalNames = { ("GLuint", "program") : "globalProgramName", ("GLuint", "texture") : "globalTextureName", ("GLuint", "buffer") : "globalBufferName", ("GLuint", "sampler") : "globalSampler", ("GLuint", "query") : "globalQuery", } needsShareGroup = False for v in zip(entry.vartypes, entry.varnames): if v in list(globalNameTypes.keys()): needsShareGroup = True if needsShareGroup: print(" if (ctx->shareGroup().get()) {") for key in zip(entry.vartypes, entry.varnames): vartype, varname = key if key in globalNames: print(" const GLuint %s = ctx->shareGroup()->getGlobalName(%s, %s);" % (globalNames[key], globalNameTypes[key], varname)) globalCall = ", ".join([globalNames.get(k, k[1]) for k in zip(entry.vartypes, entry.varnames)]) if needsShareGroup and entry.__name__ in translator_custom_share_processing: print(translator_custom_share_processing[entry.__name__]) if (entry.return_type == "void"): if (needsShareGroup): print(" ") if entry.__name__ not in translator_no_passthrough: print(" ctx->dispatcher().%s(%s);" % (entry.__name__, globalCall)) if needsShareGroup: print(" }") if entry.__name__ in translator_custom_post: print(translator_custom_post[entry.__name__]); else: if (needsShareGroup): print(" ") if entry.__name__ not in translator_no_passthrough: print(" %s %s = ctx->dispatcher().%s(%s);" % (entry.return_type, entry.__name__ + "RET", entry.__name__, globalCall)) else: print(" %s %s = %s" % (entry.return_type, entry_func_name + "RET", get_fail_code(entry))) if entry.__name__ in translator_custom_post: print(translator_custom_post[entry.__name__]); print(" return %s;" % (entry.__name__ + "RET")); if needsShareGroup: print(" } else return %s;" % (get_fail_code(entry))) print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// This file is best left unedited.") print("// Try to make changes through gen_translator in gen-entries.py,") print("// and/or parcel out custom functionality in separate code.") for entry in entries: print("%sGL_APICALL %s GL_APIENTRY %s(%s) {" % (needExternC(entry), entry.return_type, entry.__name__, entry.parameters)) gen_cxt_getter(entry); gen_validations_custom_impl(entry); gen_call_ret(entry); print("}\n") def gen_dll_wrapper(entries, prefix_name, verbatim, filename): """Generate a C source file that contains functions that act as wrappers for entry points located in another shared library. This allows the code that calls these functions to perform lazy-linking to system libraries. |entries|, |prefix_name|, |verbatim| and |filename| are the same as for gen_functions_header() above. """ upper_name = prefix_name.upper() ENTRY_PREFIX = "__dll_" print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// DO NOT EDIT THIS FILE") print("") print("#include ") for line in verbatim: print(line) print("") print("///") print("/// W R A P P E R P O I N T E R S") print("///") print("") for entry in entries: ptr_name = ENTRY_PREFIX + entry.__name__ print("static %s (*%s)(%s) = 0;" % \ (entry.return_type, ptr_name, entry.parameters)) print("") print("///") print("/// W R A P P E R F U N C T I O N S") print("///") print("") for entry in entries: print("%s %s(%s) {" % \ (entry.return_type, entry.__name__, entry.parameters)) ptr_name = ENTRY_PREFIX + entry.__name__ if entry.return_type != "void": print(" return %s(%s);" % (ptr_name, entry.call)) else: print(" %s(%s);" % (ptr_name, entry.call)) print("}\n") print("") print("///") print("/// I N I T I A L I Z A T I O N F U N C T I O N") print("///") print("") print("int %s_dynlink_init(void* lib) {" % prefix_name) for entry in entries: ptr_name = ENTRY_PREFIX + entry.__name__ print(" %s = (%s(*)(%s))dlsym(lib, \"%s\");" % \ (ptr_name, entry.return_type, entry.parameters, entry.__name__)) print(" if (!%s) return -1;" % ptr_name) print(" return 0;") print("}") def gen_windows_def_file(entries): """Generate a windows DLL .def file. |entries| is a list of Entry instances. """ print("EXPORTS") for entry in entries: print(" %s" % entry.__name__) def gen_unix_sym_file(entries): """Generate an ELF linker version file. |entries| is a list of Entry instances. """ print("VERSION {") print("\tglobal:") for entry in entries: print("\t\t%s;" % entry.__name__) print("\tlocal:") print("\t\t*;") print("};") def gen_symbols(entries, underscore): """Generate a list of symbols from |entries|, a list of Entry instances. |underscore| is a boolean. If True, then prepend an underscore to each symbol name. """ prefix = "" if underscore: prefix = "_" for entry in entries: print("%s%s" % (prefix, entry.__name__)) VARTYPE_TO_PRINT_FORMAT = { 'GLenum': '0x%X', 'GLboolean': '%d', 'GLbitfield': '%d', 'GLvoid': '%d', 'GLubyte': '%u', 'GLbyte': '%c', 'GLshort': '%d', 'GLushort': '%d', 'GLint': '%d', 'GLuint': '%d', 'GLclampx': '%d', 'GLsizei': '%d', 'GLfloat': '%f', 'GLclampf': '%f', 'GLdouble': '%f', 'GLclampd': '%f', 'GLchar': '%c', 'GLcharARB': '%c', 'GLfixed': '%d', 'GLintptr': '%ld', 'GLsizeiptr': '%ld', 'GLsync': '%p', 'GLuint64': "%lu", 'GLeglImageOES': '%p', } def get_printf_format(var_type, var_name): if '*' in var_type: return "%p" if '[' in var_name: return "%p" # Function pointer if 'PROC' in var_type: return "%p" return VARTYPE_TO_PRINT_FORMAT[var_type] def get_printf_name(var_name): if '[' in var_name: return var_name[:var_name.index('[')] return var_name def gen_dispatch_logging_wrappers(entries): print("// Auto-generated with: %s" % banner_command(sys.argv)) print("// DO NOT EDIT THIS FILE") print("") for entry in entries: print("%s %s_dispatchLoggingWrapper(%s) {" % \ (entry.return_type, entry.__name__, entry.parameters)) print_var_formats = [] print_var_names = [] for (param_type, param_name) in zip(entry.vartypes, entry.varnames): print_var_formats.append("%s:%s" % (param_name, get_printf_format(param_type, param_name))) print_var_names.append(get_printf_name(param_name)) optional_comma = ", " if print_var_formats else "" print("\tDISPATCH_DEBUG_LOG(\"%s(%s)\"%s%s);" % (entry.__name__, ", ".join(print_var_formats), optional_comma, ", ".join(print_var_names))) if entry.return_type == "void": optional_return = "" else: optional_return = "return " print("\t%sGLDispatch::%s_underlying(%s);" % (optional_return, entry.__name__, ", ".join(print_var_names))) print("}") print("") def parse_file(filename, lines, mode): """Generate one of possible outputs from |filename|. |lines| must be a list of text lines from the file, and |mode| is one of the --mode option values. """ entries, prefix_name, verbatim, namespaces, errors = parse_entries_file(lines) if errors: for error in errors: sys.stderr.write("ERROR: %s:%s" % (filename, error)) sys.exit(1) if not prefix_name: prefix_name = "unknown" if mode == 'def': gen_windows_def_file(entries) elif mode == 'sym': gen_unix_sym_file(entries) elif mode == 'translator_passthrough': gen_translator(entries) elif mode == 'wrapper': gen_dll_wrapper(entries, prefix_name, verbatim, filename) elif mode == 'symbols': gen_symbols(entries, False) elif mode == '_symbols': gen_symbols(entries, True) elif mode == 'functions': gen_functions_header(entries, prefix_name, verbatim, filename, False) elif mode == 'funcargs': gen_functions_header(entries, prefix_name, verbatim, filename, True) elif mode == 'static_translator_namespaced_header': gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, True) elif mode == 'static_translator_namespaced_stubs': gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, True) elif mode == 'dispatch_logging_wrappers': gen_dispatch_logging_wrappers(entries) # List of valid --mode option values. mode_list = [ 'def', 'sym', 'translator_passthrough', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs', 'static_translator_namespaced_header', 'static_translator_namespaced_stubs', 'dispatch_logging_wrappers', ] # Argument parsing. parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description="""\ A script used to parse an .entries input file containing a list of function declarations, and generate various output files depending on the value of the --mode option, which can be: def Generate a windows DLL .def file. sym Generate a Unix .so linker script. wrapper Generate a C source file containing wrapper functions. symbols Generate a simple list of symbols, one per line. _symbols Generate a simple list of symbols, prefixed with _. functions Generate a C header containing a macro listing all functions. funcargs Like 'functions', but adds function call arguments to listing. static_translator_namespaced_header Generate C++ header with namespaced versions of the api declarations. dispatch_logging_wrappers Generate C++ functions which debug log the function with arguments and then call the underlying function. """) parser.add_argument("--mode", help="Output mode", choices=mode_list) parser.add_argument("--output", help="output file") parser.add_argument("file", help=".entries file path") args = parser.parse_args() if not args.mode: sys,stderr.write("ERROR: Please use --mode=, see --help.") sys.exit(1) if args.output: sys.stdout = open(args.output, "w+") if args.file == '--': parse_file("", sys.stdin, args.mode) else: parse_file(args.file, open(args.file), args.mode)