idl_gen_pnacl.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Generator for Pnacl Shim functions that bridges the calling conventions
7between GCC and PNaCl.  """
8
9from datetime import datetime
10import difflib
11import glob
12import os
13import sys
14
15from idl_c_proto import CGen
16from idl_gen_wrapper import Interface, WrapperGen
17from idl_log import ErrOut, InfoOut, WarnOut
18from idl_option import GetOption, Option, ParseOptions
19from idl_parser import ParseFiles
20
21Option('pnaclshim', 'Name of the pnacl shim file.',
22       default='temp_pnacl_shim.c')
23
24Option('disable_pnacl_opt', 'Turn off optimization of pnacl shim.')
25
26
27class PnaclGen(WrapperGen):
28  """PnaclGen generates shim code to bridge the Gcc ABI with PNaCl.
29
30  This subclass of WrapperGenerator takes the IDL sources and
31  generates shim methods for bridging the calling conventions between GCC
32  and PNaCl (LLVM). Some of the PPAPI methods do not need shimming, so
33  this will also detect those situations and provide direct access to the
34  original PPAPI methods (rather than the shim methods).
35  """
36
37  def __init__(self):
38    WrapperGen.__init__(self,
39                        'Pnacl',
40                        'Pnacl Shim Gen',
41                        'pnacl',
42                        'Generate the PNaCl shim.')
43    self.cgen = CGen()
44    self._skip_opt = False
45    self._pnacl_attribute = '__attribute__((pnaclcall))'
46
47  ############################################################
48
49  def OwnHeaderFile(self):
50    """Return the header file that specifies the API of this wrapper.
51    We do not generate the header files.  """
52    return 'ppapi/generators/pnacl_shim.h'
53
54  def GetGuardStart(self):
55    return ('\n/* The PNaCl PPAPI shims are only needed on x86-64 and arm. */\n'
56            '#if defined(__x86_64__) || defined(__arm__)\n\n')
57
58  def GetGuardEnd(self):
59    return '\n#endif\n'
60
61  def InterfaceNeedsWrapper(self, iface, releases):
62    """Return true if the interface has ANY methods that need wrapping.
63    """
64    if self._skip_opt:
65      return True
66    for release in iface.GetUniqueReleases(releases):
67      version = iface.GetVersion(release)
68      if self.InterfaceVersionNeedsWrapping(iface, version):
69        return True
70    return False
71
72
73  def InterfaceVersionNeedsWrapping(self, iface, version):
74    """Return true if the interface+version has ANY methods that
75    need wrapping.
76    """
77    if self._skip_opt:
78      return True
79    for member in iface.GetListOf('Member'):
80      release = member.GetRelease(version)
81      if self.MemberNeedsWrapping(member, release):
82        return True
83    return False
84
85
86  def MemberNeedsWrapping(self, member, release):
87    """Return true if a particular member function at a particular
88    release needs wrapping.
89    """
90    if self._skip_opt:
91      return True
92    if not member.InReleases([release]):
93      return False
94    ret, name, array, args_spec = self.cgen.GetComponents(member,
95                                                          release,
96                                                          'store')
97    return self.TypeNeedsWrapping(ret, []) or self.ArgsNeedWrapping(args_spec)
98
99
100  def ArgsNeedWrapping(self, args):
101    """Return true if any parameter in the list needs wrapping.
102    """
103    for arg in args:
104      (type_str, name, array_dims, more_args) = arg
105      if self.TypeNeedsWrapping(type_str, array_dims):
106        return True
107    return False
108
109
110  def TypeNeedsWrapping(self, type_node, array_dims):
111    """Return true if a parameter type needs wrapping.
112    Currently, this is true for byval aggregates.
113    """
114    is_aggregate = type_node.startswith('struct') or \
115        type_node.startswith('union')
116    is_reference = (type_node.find('*') != -1 or array_dims != [])
117    return is_aggregate and not is_reference
118
119  ############################################################
120
121
122  def GenerateWrapperForPPBMethod(self, iface, member):
123    result = []
124    func_prefix = self.WrapperMethodPrefix(iface.node, iface.release)
125    sig = self.cgen.GetSignature(member, iface.release, 'store',
126                                 func_prefix, False)
127    result.append('static %s\n%s {\n' % (self._pnacl_attribute, sig))
128    result.append('  const struct %s *iface = %s.real_iface;\n' %
129                  (iface.struct_name, self.GetWrapperInfoName(iface)))
130    ret, name, array, cspec = self.cgen.GetComponents(member,
131                                                      iface.release,
132                                                      'store')
133    ret_str, args_str = self.GetReturnArgs(ret, cspec)
134    result.append('  %siface->%s(%s);\n}\n\n' % (ret_str,
135                                                 member.GetName(), args_str))
136    return result
137
138
139  def GenerateWrapperForPPPMethod(self, iface, member):
140    result = []
141    func_prefix = self.WrapperMethodPrefix(iface.node, iface.release)
142    sig = self.cgen.GetSignature(member, iface.release, 'store',
143                                 func_prefix, False)
144    result.append('static %s {\n' % sig)
145    result.append('  const struct %s *iface = %s.real_iface;\n' %
146                  (iface.struct_name, self.GetWrapperInfoName(iface)))
147    temp_fp = self.cgen.GetSignature(member, iface.release, 'return',
148                                     'temp_fp',
149                                     func_as_ptr=True,
150                                     ptr_prefix=self._pnacl_attribute + ' ',
151                                     include_name=False)
152    cast = self.cgen.GetSignature(member, iface.release, 'return',
153                                  prefix='',
154                                  func_as_ptr=True,
155                                  ptr_prefix=self._pnacl_attribute + ' ',
156                                  include_name=False)
157    result.append('  %s = ((%s)iface->%s);\n' % (temp_fp,
158                                                 cast,
159                                                 member.GetName()))
160    ret, name, array, cspec = self.cgen.GetComponents(member,
161                                                      iface.release,
162                                                      'store')
163    ret_str, args_str = self.GetReturnArgs(ret, cspec)
164    result.append('  %stemp_fp(%s);\n}\n\n' % (ret_str, args_str))
165    return result
166
167
168  def GenerateRange(self, ast, releases, options):
169    """Generate shim code for a range of releases.
170    """
171    self._skip_opt = GetOption('disable_pnacl_opt')
172    self.SetOutputFile(GetOption('pnaclshim'))
173    return WrapperGen.GenerateRange(self, ast, releases, options)
174
175pnaclgen = PnaclGen()
176
177######################################################################
178# Tests.
179
180# Clean a string representing an object definition and return then string
181# as a single space delimited set of tokens.
182def CleanString(instr):
183  instr = instr.strip()
184  instr = instr.split()
185  return ' '.join(instr)
186
187
188def PrintErrorDiff(old, new):
189  oldlines = old.split(';')
190  newlines = new.split(';')
191  d = difflib.Differ()
192  diff = d.compare(oldlines, newlines)
193  ErrOut.Log('Diff is:\n%s' % '\n'.join(diff))
194
195
196def GetOldTestOutput(ast):
197  # Scan the top-level comments in the IDL file for comparison.
198  old = []
199  for filenode in ast.GetListOf('File'):
200    for node in filenode.GetChildren():
201      instr = node.GetOneOf('Comment')
202      if not instr: continue
203      instr.Dump()
204      old.append(instr.GetName())
205  return CleanString(''.join(old))
206
207
208def TestFiles(filenames, test_releases):
209  ast = ParseFiles(filenames)
210  iface_releases = pnaclgen.DetermineInterfaces(ast, test_releases)
211  new_output = CleanString(pnaclgen.GenerateWrapperForMethods(
212      iface_releases, comments=False))
213  old_output = GetOldTestOutput(ast)
214  if new_output != old_output:
215    PrintErrorDiff(old_output, new_output)
216    ErrOut.Log('Failed pnacl generator test.')
217    return 1
218  else:
219    InfoOut.Log('Passed pnacl generator test.')
220    return 0
221
222
223def Main(args):
224  filenames = ParseOptions(args)
225  test_releases = ['M13', 'M14', 'M15']
226  if not filenames:
227    idldir = os.path.split(sys.argv[0])[0]
228    idldir = os.path.join(idldir, 'test_gen_pnacl', '*.idl')
229    filenames = glob.glob(idldir)
230  filenames = sorted(filenames)
231  if GetOption('test'):
232    # Run the tests.
233    return TestFiles(filenames, test_releases)
234
235  # Otherwise, generate the output file (for potential use as golden file).
236  ast = ParseFiles(filenames)
237  return pnaclgen.GenerateRange(ast, test_releases, filenames)
238
239
240if __name__ == '__main__':
241  retval = Main(sys.argv[1:])
242  sys.exit(retval)
243