idl_gen_pnacl.py revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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
46  ############################################################
47
48  def OwnHeaderFile(self):
49    """Return the header file that specifies the API of this wrapper.
50    We do not generate the header files.  """
51    return 'ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.h'
52
53
54  def InterfaceVersionNeedsWrapping(self, iface, version):
55    """Return true if the interface+version has ANY methods that
56    need wrapping.
57    """
58    if self._skip_opt:
59      return True
60    if iface.GetName().endswith('Trusted'):
61      return False
62    for member in iface.GetListOf('Member'):
63      release = member.GetRelease(version)
64      if self.MemberNeedsWrapping(member, release):
65        return True
66    return False
67
68
69  def MemberNeedsWrapping(self, member, release):
70    """Return true if a particular member function at a particular
71    release needs wrapping.
72    """
73    if self._skip_opt:
74      return True
75    if not member.InReleases([release]):
76      return False
77    ret, name, array, args_spec = self.cgen.GetComponents(member,
78                                                          release,
79                                                          'store')
80    return self.TypeNeedsWrapping(ret, []) or self.ArgsNeedWrapping(args_spec)
81
82
83  def ArgsNeedWrapping(self, args):
84    """Return true if any parameter in the list needs wrapping.
85    """
86    for arg in args:
87      (type_str, name, array_dims, more_args) = arg
88      if self.TypeNeedsWrapping(type_str, array_dims):
89        return True
90    return False
91
92
93  def TypeNeedsWrapping(self, type_node, array_dims):
94    """Return true if a parameter type needs wrapping.
95    Currently, this is true for byval aggregates.
96    """
97    is_aggregate = type_node.startswith('struct') or \
98        type_node.startswith('union')
99    is_reference = (type_node.find('*') != -1 or array_dims != [])
100    return is_aggregate and not is_reference
101
102  ############################################################
103
104
105  def ConvertByValueReturnType(self, ret, args_spec):
106    if self.TypeNeedsWrapping(ret, array_dims=[]):
107      args_spec = [(ret, '_struct_result', [], None)] + args_spec
108      ret2 = 'void'
109      wrap_return = True
110    else:
111      ret2 = ret
112      wrap_return = False
113    return wrap_return, ret2, args_spec
114
115
116  def ConvertByValueArguments(self, args_spec):
117    args = []
118    for type_str, name, array_dims, more_args in args_spec:
119      if self.TypeNeedsWrapping(type_str, array_dims):
120        type_str += '*'
121      args.append((type_str, name, array_dims, more_args))
122    return args
123
124
125  def FormatArgs(self, c_operator, args_spec):
126    args = []
127    for type_str, name, array_dims, more_args in args_spec:
128      if self.TypeNeedsWrapping(type_str, array_dims):
129        args.append(c_operator + name)
130      else:
131        args.append(name)
132    return ', '.join(args)
133
134
135  def GenerateWrapperForPPBMethod(self, iface, member):
136    result = []
137    func_prefix = self.WrapperMethodPrefix(iface.node, iface.release)
138    ret, name, array, cspec = self.cgen.GetComponents(member,
139                                                      iface.release,
140                                                      'store')
141    wrap_return, ret2, cspec2 = self.ConvertByValueReturnType(ret, cspec)
142    cspec2 = self.ConvertByValueArguments(cspec2)
143    sig = self.cgen.Compose(ret2, name, array, cspec2,
144                            prefix=func_prefix,
145                            func_as_ptr=False,
146                            include_name=True,
147                            unsized_as_ptr=False)
148    result.append('static %s {\n' % sig)
149    result.append('  const struct %s *iface = %s.real_iface;\n' %
150                  (iface.struct_name, self.GetWrapperInfoName(iface)))
151
152    return_prefix = ''
153    if wrap_return:
154      return_prefix = '*_struct_result = '
155    elif ret != 'void':
156      return_prefix = 'return '
157
158    result.append('  %siface->%s(%s);\n}\n\n' % (return_prefix,
159                                                 member.GetName(),
160                                                 self.FormatArgs('*', cspec)))
161    return result
162
163
164  def GenerateWrapperForPPPMethod(self, iface, member):
165    result = []
166    func_prefix = self.WrapperMethodPrefix(iface.node, iface.release)
167    sig = self.cgen.GetSignature(member, iface.release, 'store',
168                                 func_prefix, False)
169    result.append('static %s {\n' % sig)
170    result.append('  const struct %s *iface = %s.real_iface;\n' %
171                  (iface.struct_name, self.GetWrapperInfoName(iface)))
172    ret, name, array, cspec = self.cgen.GetComponents(member,
173                                                      iface.release,
174                                                      'store')
175    wrap_return, ret2, cspec = self.ConvertByValueReturnType(ret, cspec)
176    cspec2 = self.ConvertByValueArguments(cspec)
177    temp_fp = self.cgen.Compose(ret2, name, array, cspec2,
178                                prefix='temp_fp',
179                                func_as_ptr=True,
180                                include_name=False,
181                                unsized_as_ptr=False)
182    cast = self.cgen.Compose(ret2, name, array, cspec2,
183                             prefix='',
184                             func_as_ptr=True,
185                             include_name=False,
186                             unsized_as_ptr=False)
187    result.append('  %s =\n    ((%s)iface->%s);\n' % (temp_fp,
188                                                      cast,
189                                                      member.GetName()))
190    return_prefix = ''
191    if wrap_return:
192      result.append('  %s _struct_result;\n' % ret)
193    elif ret != 'void':
194      return_prefix = 'return '
195
196    result.append('  %stemp_fp(%s);\n' % (return_prefix,
197                                          self.FormatArgs('&', cspec)))
198    if wrap_return:
199      result.append('  return _struct_result;\n')
200    result.append('}\n\n')
201    return result
202
203
204  def GenerateRange(self, ast, releases, options):
205    """Generate shim code for a range of releases.
206    """
207    self._skip_opt = GetOption('disable_pnacl_opt')
208    self.SetOutputFile(GetOption('pnaclshim'))
209    return WrapperGen.GenerateRange(self, ast, releases, options)
210
211pnaclgen = PnaclGen()
212
213######################################################################
214# Tests.
215
216# Clean a string representing an object definition and return then string
217# as a single space delimited set of tokens.
218def CleanString(instr):
219  instr = instr.strip()
220  instr = instr.split()
221  return ' '.join(instr)
222
223
224def PrintErrorDiff(old, new):
225  oldlines = old.split(';')
226  newlines = new.split(';')
227  d = difflib.Differ()
228  diff = d.compare(oldlines, newlines)
229  ErrOut.Log('Diff is:\n%s' % '\n'.join(diff))
230
231
232def GetOldTestOutput(ast):
233  # Scan the top-level comments in the IDL file for comparison.
234  old = []
235  for filenode in ast.GetListOf('File'):
236    for node in filenode.GetChildren():
237      instr = node.GetOneOf('Comment')
238      if not instr: continue
239      instr.Dump()
240      old.append(instr.GetName())
241  return CleanString(''.join(old))
242
243
244def TestFiles(filenames, test_releases):
245  ast = ParseFiles(filenames)
246  iface_releases = pnaclgen.DetermineInterfaces(ast, test_releases)
247  new_output = CleanString(pnaclgen.GenerateWrapperForMethods(
248      iface_releases, comments=False))
249  old_output = GetOldTestOutput(ast)
250  if new_output != old_output:
251    PrintErrorDiff(old_output, new_output)
252    ErrOut.Log('Failed pnacl generator test.')
253    return 1
254  else:
255    InfoOut.Log('Passed pnacl generator test.')
256    return 0
257
258
259def Main(args):
260  filenames = ParseOptions(args)
261  test_releases = ['M13', 'M14', 'M15']
262  if not filenames:
263    idldir = os.path.split(sys.argv[0])[0]
264    idldir = os.path.join(idldir, 'test_gen_pnacl', '*.idl')
265    filenames = glob.glob(idldir)
266  filenames = sorted(filenames)
267  if GetOption('test'):
268    # Run the tests.
269    return TestFiles(filenames, test_releases)
270
271  # Otherwise, generate the output file (for potential use as golden file).
272  ast = ParseFiles(filenames)
273  return pnaclgen.GenerateRange(ast, test_releases, filenames)
274
275
276if __name__ == '__main__':
277  retval = Main(sys.argv[1:])
278  sys.exit(retval)
279