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 7 between GCC and PNaCl. """ 8 9 from datetime import datetime 10 import difflib 11 import glob 12 import os 13 import sys 14 15 from idl_c_proto import CGen 16 from idl_gen_wrapper import Interface, WrapperGen 17 from idl_log import ErrOut, InfoOut, WarnOut 18 from idl_option import GetOption, Option, ParseOptions 19 from idl_parser import ParseFiles 20 21 Option('pnaclshim', 'Name of the pnacl shim file.', 22 default='temp_pnacl_shim.c') 23 24 Option('disable_pnacl_opt', 'Turn off optimization of pnacl shim.') 25 26 27 class 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 217 pnaclgen = 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. 224 def CleanString(instr): 225 instr = instr.strip() 226 instr = instr.split() 227 return ' '.join(instr) 228 229 230 def 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 238 def 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 250 def 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 265 def 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 282 if __name__ == '__main__': 283 retval = Main(sys.argv[1:]) 284 sys.exit(retval) 285