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/generators/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 211 pnaclgen = 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. 218 def CleanString(instr): 219 instr = instr.strip() 220 instr = instr.split() 221 return ' '.join(instr) 222 223 224 def 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 232 def 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 244 def 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 259 def 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 276 if __name__ == '__main__': 277 retval = Main(sys.argv[1:]) 278 sys.exit(retval) 279