3 from __future__ import print_function
6 # Try to use the C parser.
8 from yaml import CLoader as Loader
10 print("For faster parsing, you may want to install libYAML for PyYAML")
11 from yaml import Loader
14 from collections import defaultdict
17 from multiprocessing import Lock
21 # The previously builtin function `intern()` was moved
22 # to the `sys` module in Python 3.
23 from sys import intern
31 except AttributeError:
34 return iter(d.values())
36 return iter(d.items())
45 def html_file_name(filename):
46 return filename.replace('/', '_').replace('#', '_') + ".html"
49 def make_link(File, Line):
50 return "\"{}#L{}\"".format(html_file_name(File), Line)
53 class Remark(yaml.YAMLObject):
54 # Work-around for http://pyyaml.org/ticket/154.
57 default_demangler = 'c++filt -n'
61 def set_demangler(cls, demangler):
62 cls.demangler_proc = subprocess.Popen(demangler.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE)
63 cls.demangler_lock = Lock()
66 def demangle(cls, name):
67 with cls.demangler_lock:
68 cls.demangler_proc.stdin.write((name + '\n').encode('utf-8'))
69 cls.demangler_proc.stdin.flush()
70 return cls.demangler_proc.stdout.readline().rstrip().decode('utf-8')
72 # Intern all strings since we have lot of duplication across filenames,
75 # Change Args from a list of dicts to a tuple of tuples. This saves
76 # memory in two ways. One, a small tuple is significantly smaller than a
77 # small dict. Two, using tuple instead of list allows Args to be directly
78 # used as part of the key (in Python only immutable types are hashable).
79 def _reduce_memory(self):
80 self.Pass = intern(self.Pass)
81 self.Name = intern(self.Name)
83 # Can't intern unicode strings.
84 self.Function = intern(self.Function)
88 def _reduce_memory_dict(old_dict):
90 for (k, v) in iteritems(old_dict):
97 # This handles [{'Caller': ..., 'DebugLoc': { 'File': ... }}]
98 v = _reduce_memory_dict(v)
100 return tuple(new_dict.items())
102 self.Args = tuple([_reduce_memory_dict(arg_dict) for arg_dict in self.Args])
104 # The inverse operation of the dictonary-related memory optimization in
105 # _reduce_memory_dict. E.g.
106 # (('DebugLoc', (('File', ...) ... ))) -> [{'DebugLoc': {'File': ...} ....}]
107 def recover_yaml_structure(self):
108 def tuple_to_dict(t):
116 self.Args = [tuple_to_dict(arg_tuple) for arg_tuple in self.Args]
118 def canonicalize(self):
119 if not hasattr(self, 'Hotness'):
121 if not hasattr(self, 'Args'):
123 self._reduce_memory()
127 return self.DebugLoc['File']
131 return int(self.DebugLoc['Line'])
135 return self.DebugLoc['Column']
138 def DebugLocString(self):
139 return "{}:{}:{}".format(self.File, self.Line, self.Column)
142 def DemangledFunctionName(self):
143 return self.demangle(self.Function)
147 return make_link(self.File, self.Line)
149 def getArgString(self, mapping):
150 mapping = dict(list(mapping))
151 dl = mapping.get('DebugLoc')
153 del mapping['DebugLoc']
155 assert(len(mapping) == 1)
156 (key, value) = list(mapping.items())[0]
158 if key == 'Caller' or key == 'Callee' or key == 'DirectCallee':
159 value = cgi.escape(self.demangle(value))
161 if dl and key != 'Caller':
162 dl_dict = dict(list(dl))
163 return u"<a href={}>{}</a>".format(
164 make_link(dl_dict['File'], dl_dict['Line']), value)
168 # Return a cached dictionary for the arguments. The key for each entry is
169 # the argument key (e.g. 'Callee' for inlining remarks. The value is a
170 # list containing the value (e.g. for 'Callee' the function) and
171 # optionally a DebugLoc.
172 def getArgDict(self):
173 if hasattr(self, 'ArgDict'):
176 for arg in self.Args:
178 if arg[0][0] == 'DebugLoc':
181 assert(arg[1][0] == 'DebugLoc')
184 key = arg[1 - dbgidx][0]
185 entry = (arg[1 - dbgidx][1], arg[dbgidx][1])
191 self.ArgDict[key] = entry
194 def getDiffPrefix(self):
195 if hasattr(self, 'Added'):
203 def PassWithDiffPrefix(self):
204 return self.getDiffPrefix() + self.Pass
208 # Args is a list of mappings (dictionaries)
209 values = [self.getArgString(mapping) for mapping in self.Args]
210 return "".join(values)
213 def RelativeHotness(self):
215 return "{0:.2f}%".format(self.Hotness * 100. / self.max_hotness)
221 return (self.__class__, self.PassWithDiffPrefix, self.Name, self.File,
222 self.Line, self.Column, self.Function, self.Args)
225 return hash(self.key)
227 def __eq__(self, other):
228 return self.key == other.key
234 class Analysis(Remark):
235 yaml_tag = '!Analysis'
242 class AnalysisFPCommute(Analysis):
243 yaml_tag = '!AnalysisFPCommute'
246 class AnalysisAliasing(Analysis):
247 yaml_tag = '!AnalysisAliasing'
250 class Passed(Remark):
258 class Missed(Remark):
266 def get_remarks(input_file):
269 file_remarks = defaultdict(functools.partial(defaultdict, list))
271 with open(input_file) as f:
272 docs = yaml.load_all(f, Loader=Loader)
274 remark.canonicalize()
275 # Avoid remarks withoug debug location or if they are duplicated
276 if not hasattr(remark, 'DebugLoc') or remark.key in all_remarks:
278 all_remarks[remark.key] = remark
280 file_remarks[remark.File][remark.Line].append(remark)
282 # If we're reading a back a diff yaml file, max_hotness is already
283 # captured which may actually be less than the max hotness found
285 if hasattr(remark, 'max_hotness'):
286 max_hotness = remark.max_hotness
287 max_hotness = max(max_hotness, remark.Hotness)
289 return max_hotness, all_remarks, file_remarks
292 def gather_results(filenames, num_jobs, should_print_progress):
293 if should_print_progress:
294 print('Reading YAML files...')
295 if not Remark.demangler_proc:
296 Remark.set_demangler(Remark.default_demangler)
297 remarks = optpmap.pmap(
298 get_remarks, filenames, num_jobs, should_print_progress)
299 max_hotness = max(entry[0] for entry in remarks)
301 def merge_file_remarks(file_remarks_job, all_remarks, merged):
302 for filename, d in iteritems(file_remarks_job):
303 for line, remarks in iteritems(d):
304 for remark in remarks:
305 # Bring max_hotness into the remarks so that
306 # RelativeHotness does not depend on an external global.
307 remark.max_hotness = max_hotness
308 if remark.key not in all_remarks:
309 merged[filename][line].append(remark)
312 file_remarks = defaultdict(functools.partial(defaultdict, list))
313 for _, all_remarks_job, file_remarks_job in remarks:
314 merge_file_remarks(file_remarks_job, all_remarks, file_remarks)
315 all_remarks.update(all_remarks_job)
317 return all_remarks, file_remarks, max_hotness != 0
320 def find_opt_files(*dirs_or_files):
322 for dir_or_file in dirs_or_files:
323 if os.path.isfile(dir_or_file):
324 all.append(dir_or_file)
326 for dir, subdirs, files in os.walk(dir_or_file):
327 # Exclude mounted directories and symlinks (os.walk default).
328 subdirs[:] = [d for d in subdirs
329 if not os.path.ismount(os.path.join(dir, d))]
331 if fnmatch.fnmatch(file, "*.opt.yaml*"):
332 all.append(os.path.join(dir, file))