a186729a7dfb6b9cb58087aff00485b280f0c88c
[platform/framework/web/crosswalk.git] / src / native_client / pnacl / driver / filetype.py
1 #!/usr/bin/python
2 # Copyright (c) 2013 The Native Client 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 """Utilities for determining (and overriding) the types of files
7 """
8
9 import os
10
11 import artools
12 import driver_log
13 import elftools
14
15 LLVM_BITCODE_MAGIC = 'BC\xc0\xde'
16 LLVM_WRAPPER_MAGIC = '\xde\xc0\x17\x0b'
17 PNACL_BITCODE_MAGIC = 'PEXE'
18
19 class SimpleCache(object):
20   """ Cache results of a function using a dictionary. """
21
22   __all_caches = dict()
23
24   @classmethod
25   def ClearAllCaches(cls):
26     """ Clear cached results from all functions. """
27     for d in cls.__all_caches.itervalues():
28       d.clear()
29
30   def __init__(self, f):
31     SimpleCache.__all_caches[self] = dict()
32     self.cache = SimpleCache.__all_caches[self]
33     self.func = f
34
35   def __call__(self, *args):
36     if args in self.cache:
37       return self.cache[args]
38     else:
39       result = self.func(*args)
40       self.cache[args] = result
41       return result
42
43   def __repr__(self):
44     return self.func.__doc__
45
46   def OverrideValue(self, value, *args):
47     """ Force a function call with |args| to return |value|. """
48     self.cache[args] = value
49
50   def ClearCache(self):
51     """ Clear cached results for one instance (function). """
52     self.cache.clear()
53
54
55 @SimpleCache
56 def IsNative(filename):
57   return (IsNativeObject(filename) or
58           IsNativeDSO(filename) or
59           IsNativeArchive(filename))
60
61 @SimpleCache
62 def IsNativeObject(filename):
63   return FileType(filename) == 'o'
64
65 @SimpleCache
66 def IsNativeDSO(filename):
67   return FileType(filename) == 'so'
68
69 @SimpleCache
70 def GetBitcodeMagic(filename):
71   fp = driver_log.DriverOpen(filename, 'rb')
72   header = fp.read(4)
73   driver_log.DriverClose(fp)
74   return header
75
76 def IsLLVMBitcodeWrapperHeader(data):
77   return data[:4] == LLVM_WRAPPER_MAGIC
78
79 @SimpleCache
80 def IsLLVMWrappedBitcode(filename):
81   return IsLLVMBitcodeWrapperHeader(GetBitcodeMagic(filename))
82
83 def IsPNaClBitcodeHeader(data):
84   return data[:4] == PNACL_BITCODE_MAGIC
85
86 @SimpleCache
87 def IsPNaClBitcode(filename):
88   return IsPNaClBitcodeHeader(GetBitcodeMagic(filename))
89
90 def IsLLVMRawBitcodeHeader(data):
91   return data[:4] == LLVM_BITCODE_MAGIC
92
93 @SimpleCache
94 def IsLLVMBitcode(filename):
95   header = GetBitcodeMagic(filename)
96   return IsLLVMRawBitcodeHeader(header) or IsLLVMBitcodeWrapperHeader(header)
97
98 @SimpleCache
99 def IsArchive(filename):
100   return artools.IsArchive(filename)
101
102 @SimpleCache
103 def IsBitcodeArchive(filename):
104   filetype = FileType(filename)
105   return filetype == 'archive-bc'
106
107 @SimpleCache
108 def IsNativeArchive(filename):
109   return IsArchive(filename) and not IsBitcodeArchive(filename)
110
111
112 @SimpleCache
113 def IsELF(filename):
114   return elftools.IsELF(filename)
115
116 @SimpleCache
117 def GetELFType(filename):
118   """ ELF type as determined by ELF metadata """
119   assert(elftools.IsELF(filename))
120   elfheader = elftools.GetELFHeader(filename)
121   elf_type_map = {
122     'EXEC': 'nexe',
123     'REL' : 'o',
124     'DYN' : 'so'
125   }
126   return elf_type_map[elfheader.type]
127
128
129 # Parses a linker script to determine additional ld arguments specified.
130 # Returns a list of linker arguments.
131 #
132 # For example, if the linker script contains
133 #
134 #     GROUP ( libc.so.6 libc_nonshared.a  AS_NEEDED ( ld-linux.so.2 ) )
135 #
136 # Then this function will return:
137 #
138 #     ['--start-group', '-l:libc.so.6', '-l:libc_nonshared.a',
139 #      '--as-needed', '-l:ld-linux.so.2', '--no-as-needed', '--end-group']
140 #
141 # Returns None on any parse error.
142 def ParseLinkerScript(filename):
143   fp = driver_log.DriverOpen(filename, 'r')
144
145   ret = []
146   stack = []
147   expect = ''  # Expected next token
148   while True:
149     token = GetNextToken(fp)
150     if token is None:
151       # Tokenization error
152       return None
153
154     if not token:
155       # EOF
156       break
157
158     if expect:
159       if token == expect:
160         expect = ''
161         continue
162       else:
163         return None
164
165     if not stack:
166       if token == 'INPUT':
167         expect = '('
168         stack.append(token)
169       elif token == 'GROUP':
170         expect = '('
171         ret.append('--start-group')
172         stack.append(token)
173       elif token == 'OUTPUT_FORMAT':
174         expect = '('
175         stack.append(token)
176       elif token == 'EXTERN':
177         expect = '('
178         stack.append(token)
179       elif token == ';':
180         pass
181       else:
182         return None
183     else:
184       if token == ')':
185         section = stack.pop()
186         if section == 'AS_NEEDED':
187           ret.append('--no-as-needed')
188         elif section == 'GROUP':
189           ret.append('--end-group')
190       elif token == 'AS_NEEDED':
191         expect = '('
192         ret.append('--as-needed')
193         stack.append('AS_NEEDED')
194       elif stack[-1] == 'OUTPUT_FORMAT':
195         # Ignore stuff inside OUTPUT_FORMAT
196         pass
197       elif stack[-1] == 'EXTERN':
198         ret.append('--undefined=' + token)
199       else:
200         ret.append('-l:' + token)
201
202   fp.close()
203   return ret
204
205
206 # Get the next token from the linker script
207 # Returns: ''   for EOF.
208 #          None on error.
209 def GetNextToken(fp):
210   token = ''
211   while True:
212     ch = fp.read(1)
213
214     if not ch:
215       break
216
217     # Whitespace terminates a token
218     # (but ignore whitespace before the token)
219     if ch in (' ', '\t', '\n'):
220       if token:
221         break
222       else:
223         continue
224
225     # ( and ) are tokens themselves (or terminate existing tokens)
226     if ch in ('(',')'):
227       if token:
228         fp.seek(-1, os.SEEK_CUR)
229         break
230       else:
231         token = ch
232         break
233
234     token += ch
235     if token.endswith('/*'):
236       if not ReadPastComment(fp, '*/'):
237         return None
238       token = token[:-2]
239
240   return token
241
242 def ReadPastComment(fp, terminator):
243   s = ''
244   while True:
245     ch = fp.read(1)
246     if not ch:
247       return False
248     s += ch
249     if s.endswith(terminator):
250       break
251
252   return True
253
254 def IsLinkerScript(filename):
255   _, ext = os.path.splitext(filename)
256   return (len(ext) > 0 and ext[1:] in ('o', 'so', 'a', 'po', 'pa', 'x')
257           and not IsELF(filename)
258           and not IsArchive(filename)
259           and not IsLLVMBitcode(filename)
260           and ParseLinkerScript(filename) is not None)
261
262
263 # If FORCED_FILE_TYPE is set, FileType() will return FORCED_FILE_TYPE for all
264 # future input files. This is useful for the "as" incarnation, which
265 # needs to accept files of any extension and treat them as ".s" (or ".ll")
266 # files. Also useful for gcc's "-x", which causes all files between the
267 # current -x and the next -x to be treated in a certain way.
268 FORCED_FILE_TYPE = None
269 def SetForcedFileType(t):
270   global FORCED_FILE_TYPE
271   FORCED_FILE_TYPE = t
272
273 def GetForcedFileType():
274   return FORCED_FILE_TYPE
275
276 def ForceFileType(filename, newtype = None):
277   if newtype is None:
278     if FORCED_FILE_TYPE is None:
279       return
280     newtype = FORCED_FILE_TYPE
281   FileType.OverrideValue(newtype, filename)
282
283 def ClearFileTypeCaches():
284   """ Clear caches for all filetype functions (externally they must all be
285       cleared together because they can call each other)
286   """
287   SimpleCache.ClearAllCaches()
288
289 # File Extension -> Type string
290 # TODO(pdox): Add types for sources which should not be preprocessed.
291 ExtensionMap = {
292   'c'   : 'c',
293   'i'   : 'c',    # C, but should not be preprocessed.
294
295   'cc'  : 'c++',
296   'cp'  : 'c++',
297   'cxx' : 'c++',
298   'cpp' : 'c++',
299   'CPP' : 'c++',
300   'c++' : 'c++',
301   'C'   : 'c++',
302   'ii'  : 'c++',  # C++, but should not be preprocessed.
303
304   'h'   : 'c-header',
305   'hpp' : 'c++-header',
306
307   'm'   : 'objc',  # .m = "Objective-C source file"
308
309   'll'  : 'll',
310   'bc'  : 'po',
311   'po'  : 'po',   # .po = "Portable object file"
312   'pexe': 'pexe', # .pexe = "Portable executable"
313   'asm' : 'S',
314   'S'   : 'S',
315   'sx'  : 'S',
316   's'   : 's',
317   'o'   : 'o',
318   'os'  : 'o',
319   'so'  : 'so',
320   'nexe': 'nexe',
321 }
322
323 def IsSourceType(filetype):
324   return filetype in ('c','c++','objc')
325
326 def IsHeaderType(filetype):
327   return filetype in ('c-header', 'c++-header')
328
329 # The SimpleCache decorator is required for correctness, due to the
330 # ForceFileType mechanism.
331 @SimpleCache
332 def FileType(filename):
333   # Auto-detect bitcode files, since we can't rely on extensions
334   ext = filename.split('.')[-1]
335
336   # TODO(pdox): We open and read the the first few bytes of each file
337   #             up to 4 times, when we only need to do it once. The
338   #             OS cache prevents us from hitting the disk, but this
339   #             is still slower than it needs to be.
340   if IsArchive(filename):
341     return artools.GetArchiveType(filename)
342
343   if elftools.IsELF(filename):
344     return GetELFType(filename)
345
346   # If this is LLVM bitcode, we don't have a good way of determining if it
347   # is an object file or a non-finalized program, so just say 'po' for now.
348   if IsLLVMBitcode(filename):
349     return 'po'
350
351   if IsPNaClBitcode(filename):
352     return 'pexe'
353
354   if IsLinkerScript(filename):
355     return 'ldscript'
356
357   # Use the file extension if it is recognized
358   if ext in ExtensionMap:
359     return ExtensionMap[ext]
360
361   driver_log.Log.Fatal('%s: Unrecognized file type', filename)
362
363 # Map from GCC's -x file types and this driver's file types.
364 FILE_TYPE_MAP = {
365     'c'                 : 'c',
366     'c++'               : 'c++',
367     'assembler'         : 's',
368     'assembler-with-cpp': 'S',
369     'c-header'          : 'c-header',
370     'c++-header'        : 'c++-header',
371 }
372 FILE_TYPE_MAP_REVERSE = dict([reversed(_tmp) for _tmp in FILE_TYPE_MAP.items()])
373
374 def FileTypeToGCCType(filetype):
375   return FILE_TYPE_MAP_REVERSE[filetype]
376
377 def GCCTypeToFileType(gcctype):
378   if gcctype not in FILE_TYPE_MAP:
379     driver_log.Log.Fatal('language "%s" not recognized' % gcctype)
380   return FILE_TYPE_MAP[gcctype]