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