| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Generator for Pnacl Shim functions that bridges the calling conventions |
| between GCC and PNaCl. """ |
| |
| from datetime import datetime |
| import difflib |
| import glob |
| import os |
| import sys |
| |
| from idl_c_proto import CGen |
| from idl_gen_wrapper import Interface, WrapperGen |
| from idl_log import ErrOut, InfoOut, WarnOut |
| from idl_option import GetOption, Option, ParseOptions |
| from idl_parser import ParseFiles |
| |
| Option('pnaclshim', 'Name of the pnacl shim file.', |
| default='temp_pnacl_shim.c') |
| |
| Option('disable_pnacl_opt', 'Turn off optimization of pnacl shim.') |
| |
| |
| class PnaclGen(WrapperGen): |
| """PnaclGen generates shim code to bridge the Gcc ABI with PNaCl. |
| |
| This subclass of WrapperGenerator takes the IDL sources and |
| generates shim methods for bridging the calling conventions between GCC |
| and PNaCl (LLVM). Some of the PPAPI methods do not need shimming, so |
| this will also detect those situations and provide direct access to the |
| original PPAPI methods (rather than the shim methods). |
| """ |
| |
| def __init__(self): |
| WrapperGen.__init__(self, |
| 'Pnacl', |
| 'Pnacl Shim Gen', |
| 'pnacl', |
| 'Generate the PNaCl shim.') |
| self.cgen = CGen() |
| self._skip_opt = False |
| |
| ############################################################ |
| |
| def OwnHeaderFile(self): |
| """Return the header file that specifies the API of this wrapper. |
| We do not generate the header files. """ |
| return 'ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.h' |
| |
| |
| def InterfaceVersionNeedsWrapping(self, iface, version): |
| """Return true if the interface+version has ANY methods that |
| need wrapping. |
| """ |
| if self._skip_opt: |
| return True |
| if iface.GetName().endswith('Trusted'): |
| return False |
| # TODO(dmichael): We have no way to wrap PPP_ interfaces without an |
| # interface string. If any ever need wrapping, we'll need to figure out a |
| # way to get the plugin-side of the Pepper proxy (within the IRT) to access |
| # and use the wrapper. |
| if iface.GetProperty("no_interface_string"): |
| return False |
| for member in iface.GetListOf('Member'): |
| release = member.GetRelease(version) |
| if self.MemberNeedsWrapping(member, release): |
| return True |
| return False |
| |
| |
| def MemberNeedsWrapping(self, member, release): |
| """Return true if a particular member function at a particular |
| release needs wrapping. |
| """ |
| if self._skip_opt: |
| return True |
| if not member.InReleases([release]): |
| return False |
| ret, name, array, args_spec = self.cgen.GetComponents(member, |
| release, |
| 'store') |
| return self.TypeNeedsWrapping(ret, []) or self.ArgsNeedWrapping(args_spec) |
| |
| |
| def ArgsNeedWrapping(self, args): |
| """Return true if any parameter in the list needs wrapping. |
| """ |
| for arg in args: |
| (type_str, name, array_dims, more_args) = arg |
| if self.TypeNeedsWrapping(type_str, array_dims): |
| return True |
| return False |
| |
| |
| def TypeNeedsWrapping(self, type_node, array_dims): |
| """Return true if a parameter type needs wrapping. |
| Currently, this is true for byval aggregates. |
| """ |
| is_aggregate = type_node.startswith('struct') or \ |
| type_node.startswith('union') |
| is_reference = (type_node.find('*') != -1 or array_dims != []) |
| return is_aggregate and not is_reference |
| |
| ############################################################ |
| |
| |
| def ConvertByValueReturnType(self, ret, args_spec): |
| if self.TypeNeedsWrapping(ret, array_dims=[]): |
| args_spec = [(ret, '_struct_result', [], None)] + args_spec |
| ret2 = 'void' |
| wrap_return = True |
| else: |
| ret2 = ret |
| wrap_return = False |
| return wrap_return, ret2, args_spec |
| |
| |
| def ConvertByValueArguments(self, args_spec): |
| args = [] |
| for type_str, name, array_dims, more_args in args_spec: |
| if self.TypeNeedsWrapping(type_str, array_dims): |
| type_str += '*' |
| args.append((type_str, name, array_dims, more_args)) |
| return args |
| |
| |
| def FormatArgs(self, c_operator, args_spec): |
| args = [] |
| for type_str, name, array_dims, more_args in args_spec: |
| if self.TypeNeedsWrapping(type_str, array_dims): |
| args.append(c_operator + name) |
| else: |
| args.append(name) |
| return ', '.join(args) |
| |
| |
| def GenerateWrapperForPPBMethod(self, iface, member): |
| result = [] |
| func_prefix = self.WrapperMethodPrefix(iface.node, iface.release) |
| ret, name, array, cspec = self.cgen.GetComponents(member, |
| iface.release, |
| 'store') |
| wrap_return, ret2, cspec2 = self.ConvertByValueReturnType(ret, cspec) |
| cspec2 = self.ConvertByValueArguments(cspec2) |
| sig = self.cgen.Compose(ret2, name, array, cspec2, |
| prefix=func_prefix, |
| func_as_ptr=False, |
| include_name=True, |
| unsized_as_ptr=False) |
| result.append('static %s {\n' % sig) |
| result.append(' const struct %s *iface = %s.real_iface;\n' % |
| (iface.struct_name, self.GetWrapperInfoName(iface))) |
| |
| return_prefix = '' |
| if wrap_return: |
| return_prefix = '*_struct_result = ' |
| elif ret != 'void': |
| return_prefix = 'return ' |
| |
| result.append(' %siface->%s(%s);\n}\n\n' % (return_prefix, |
| member.GetName(), |
| self.FormatArgs('*', cspec))) |
| return result |
| |
| |
| def GenerateWrapperForPPPMethod(self, iface, member): |
| result = [] |
| func_prefix = self.WrapperMethodPrefix(iface.node, iface.release) |
| sig = self.cgen.GetSignature(member, iface.release, 'store', |
| func_prefix, False) |
| result.append('static %s {\n' % sig) |
| result.append(' const struct %s *iface = %s.real_iface;\n' % |
| (iface.struct_name, self.GetWrapperInfoName(iface))) |
| ret, name, array, cspec = self.cgen.GetComponents(member, |
| iface.release, |
| 'store') |
| wrap_return, ret2, cspec = self.ConvertByValueReturnType(ret, cspec) |
| cspec2 = self.ConvertByValueArguments(cspec) |
| temp_fp = self.cgen.Compose(ret2, name, array, cspec2, |
| prefix='temp_fp', |
| func_as_ptr=True, |
| include_name=False, |
| unsized_as_ptr=False) |
| cast = self.cgen.Compose(ret2, name, array, cspec2, |
| prefix='', |
| func_as_ptr=True, |
| include_name=False, |
| unsized_as_ptr=False) |
| result.append(' %s =\n ((%s)iface->%s);\n' % (temp_fp, |
| cast, |
| member.GetName())) |
| return_prefix = '' |
| if wrap_return: |
| result.append(' %s _struct_result;\n' % ret) |
| elif ret != 'void': |
| return_prefix = 'return ' |
| |
| result.append(' %stemp_fp(%s);\n' % (return_prefix, |
| self.FormatArgs('&', cspec))) |
| if wrap_return: |
| result.append(' return _struct_result;\n') |
| result.append('}\n\n') |
| return result |
| |
| |
| def GenerateRange(self, ast, releases, options): |
| """Generate shim code for a range of releases. |
| """ |
| self._skip_opt = GetOption('disable_pnacl_opt') |
| self.SetOutputFile(GetOption('pnaclshim')) |
| return WrapperGen.GenerateRange(self, ast, releases, options) |
| |
| pnaclgen = PnaclGen() |
| |
| ###################################################################### |
| # Tests. |
| |
| # Clean a string representing an object definition and return then string |
| # as a single space delimited set of tokens. |
| def CleanString(instr): |
| instr = instr.strip() |
| instr = instr.split() |
| return ' '.join(instr) |
| |
| |
| def PrintErrorDiff(old, new): |
| oldlines = old.split(';') |
| newlines = new.split(';') |
| d = difflib.Differ() |
| diff = d.compare(oldlines, newlines) |
| ErrOut.Log('Diff is:\n%s' % '\n'.join(diff)) |
| |
| |
| def GetOldTestOutput(ast): |
| # Scan the top-level comments in the IDL file for comparison. |
| old = [] |
| for filenode in ast.GetListOf('File'): |
| for node in filenode.GetChildren(): |
| instr = node.GetOneOf('Comment') |
| if not instr: continue |
| instr.Dump() |
| old.append(instr.GetName()) |
| return CleanString(''.join(old)) |
| |
| |
| def TestFiles(filenames, test_releases): |
| ast = ParseFiles(filenames) |
| iface_releases = pnaclgen.DetermineInterfaces(ast, test_releases) |
| new_output = CleanString(pnaclgen.GenerateWrapperForMethods( |
| iface_releases, comments=False)) |
| old_output = GetOldTestOutput(ast) |
| if new_output != old_output: |
| PrintErrorDiff(old_output, new_output) |
| ErrOut.Log('Failed pnacl generator test.') |
| return 1 |
| else: |
| InfoOut.Log('Passed pnacl generator test.') |
| return 0 |
| |
| |
| def Main(args): |
| filenames = ParseOptions(args) |
| test_releases = ['M13', 'M14', 'M15'] |
| if not filenames: |
| idldir = os.path.split(sys.argv[0])[0] |
| idldir = os.path.join(idldir, 'test_gen_pnacl', '*.idl') |
| filenames = glob.glob(idldir) |
| filenames = sorted(filenames) |
| if GetOption('test'): |
| # Run the tests. |
| return TestFiles(filenames, test_releases) |
| |
| # Otherwise, generate the output file (for potential use as golden file). |
| ast = ParseFiles(filenames) |
| return pnaclgen.GenerateRange(ast, test_releases, filenames) |
| |
| |
| if __name__ == '__main__': |
| retval = Main(sys.argv[1:]) |
| sys.exit(retval) |