3 from __future__
import print_function
7 # Try to use the C parser.
9 from yaml
import CLoader
as Loader
11 print("For faster parsing, you may want to install libYAML for PyYAML")
12 from yaml
import Loader
15 from collections
import defaultdict
18 from multiprocessing
import Lock
22 # The previously builtin function `intern()` was moved
23 # to the `sys` module in Python 3.
24 from sys
import intern
34 except AttributeError:
37 return iter(d
.values())
39 return iter(d
.items())
48 def html_file_name(filename
):
49 return filename
.replace('/', '_').replace('#', '_') + ".html"
52 def make_link(File
, Line
):
53 return "\"{}#L{}\"".format(html_file_name(File
), Line
)
56 class Remark(yaml
.YAMLObject
):
57 # Work-around for http://pyyaml.org/ticket/154.
60 default_demangler
= 'c++filt -n'
64 def set_demangler(cls
, demangler
):
65 cls
.demangler_proc
= subprocess
.Popen(demangler
.split(), stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
)
66 cls
.demangler_lock
= Lock()
69 def demangle(cls
, name
):
70 with cls
.demangler_lock
:
71 cls
.demangler_proc
.stdin
.write((name
+ '\n').encode('utf-8'))
72 cls
.demangler_proc
.stdin
.flush()
73 return cls
.demangler_proc
.stdout
.readline().rstrip().decode('utf-8')
75 # Intern all strings since we have lot of duplication across filenames,
78 # Change Args from a list of dicts to a tuple of tuples. This saves
79 # memory in two ways. One, a small tuple is significantly smaller than a
80 # small dict. Two, using tuple instead of list allows Args to be directly
81 # used as part of the key (in Python only immutable types are hashable).
82 def _reduce_memory(self
):
83 self
.Pass
= intern(self
.Pass
)
84 self
.Name
= intern(self
.Name
)
86 # Can't intern unicode strings.
87 self
.Function
= intern(self
.Function
)
91 def _reduce_memory_dict(old_dict
):
93 for (k
, v
) in iteritems(old_dict
):
100 # This handles [{'Caller': ..., 'DebugLoc': { 'File': ... }}]
101 v
= _reduce_memory_dict(v
)
103 return tuple(new_dict
.items())
105 self
.Args
= tuple([_reduce_memory_dict(arg_dict
) for arg_dict
in self
.Args
])
107 # The inverse operation of the dictonary-related memory optimization in
108 # _reduce_memory_dict. E.g.
109 # (('DebugLoc', (('File', ...) ... ))) -> [{'DebugLoc': {'File': ...} ....}]
110 def recover_yaml_structure(self
):
111 def tuple_to_dict(t
):
119 self
.Args
= [tuple_to_dict(arg_tuple
) for arg_tuple
in self
.Args
]
121 def canonicalize(self
):
122 if not hasattr(self
, 'Hotness'):
124 if not hasattr(self
, 'Args'):
126 self
._reduce
_memory
()
130 return self
.DebugLoc
['File']
134 return int(self
.DebugLoc
['Line'])
138 return self
.DebugLoc
['Column']
141 def DebugLocString(self
):
142 return "{}:{}:{}".format(self
.File
, self
.Line
, self
.Column
)
145 def DemangledFunctionName(self
):
146 return self
.demangle(self
.Function
)
150 return make_link(self
.File
, self
.Line
)
152 def getArgString(self
, mapping
):
153 mapping
= dict(list(mapping
))
154 dl
= mapping
.get('DebugLoc')
156 del mapping
['DebugLoc']
158 assert(len(mapping
) == 1)
159 (key
, value
) = list(mapping
.items())[0]
161 if key
== 'Caller' or key
== 'Callee' or key
== 'DirectCallee':
162 value
= html
.escape(self
.demangle(value
))
164 if dl
and key
!= 'Caller':
165 dl_dict
= dict(list(dl
))
166 return u
"<a href={}>{}</a>".format(
167 make_link(dl_dict
['File'], dl_dict
['Line']), value
)
171 # Return a cached dictionary for the arguments. The key for each entry is
172 # the argument key (e.g. 'Callee' for inlining remarks. The value is a
173 # list containing the value (e.g. for 'Callee' the function) and
174 # optionally a DebugLoc.
175 def getArgDict(self
):
176 if hasattr(self
, 'ArgDict'):
179 for arg
in self
.Args
:
181 if arg
[0][0] == 'DebugLoc':
184 assert(arg
[1][0] == 'DebugLoc')
187 key
= arg
[1 - dbgidx
][0]
188 entry
= (arg
[1 - dbgidx
][1], arg
[dbgidx
][1])
194 self
.ArgDict
[key
] = entry
197 def getDiffPrefix(self
):
198 if hasattr(self
, 'Added'):
206 def PassWithDiffPrefix(self
):
207 return self
.getDiffPrefix() + self
.Pass
211 # Args is a list of mappings (dictionaries)
212 values
= [self
.getArgString(mapping
) for mapping
in self
.Args
]
213 return "".join(values
)
216 def RelativeHotness(self
):
218 return "{0:.2f}%".format(self
.Hotness
* 100. / self
.max_hotness
)
224 return (self
.__class
__, self
.PassWithDiffPrefix
, self
.Name
, self
.File
,
225 self
.Line
, self
.Column
, self
.Function
, self
.Args
)
228 return hash(self
.key
)
230 def __eq__(self
, other
):
231 return self
.key
== other
.key
237 class Analysis(Remark
):
238 yaml_tag
= '!Analysis'
245 class AnalysisFPCommute(Analysis
):
246 yaml_tag
= '!AnalysisFPCommute'
249 class AnalysisAliasing(Analysis
):
250 yaml_tag
= '!AnalysisAliasing'
253 class Passed(Remark
):
261 class Missed(Remark
):
268 class Failure(Missed
):
269 yaml_tag
= '!Failure'
271 def get_remarks(input_file
, filter_
=None):
274 file_remarks
= defaultdict(functools
.partial(defaultdict
, list))
276 with io
.open(input_file
, encoding
= 'utf-8') as f
:
277 docs
= yaml
.load_all(f
, Loader
=Loader
)
281 filter_e
= re
.compile(filter_
)
283 remark
.canonicalize()
284 # Avoid remarks withoug debug location or if they are duplicated
285 if not hasattr(remark
, 'DebugLoc') or remark
.key
in all_remarks
:
288 if filter_e
and not filter_e
.search(remark
.Pass
):
291 all_remarks
[remark
.key
] = remark
293 file_remarks
[remark
.File
][remark
.Line
].append(remark
)
295 # If we're reading a back a diff yaml file, max_hotness is already
296 # captured which may actually be less than the max hotness found
298 if hasattr(remark
, 'max_hotness'):
299 max_hotness
= remark
.max_hotness
300 max_hotness
= max(max_hotness
, remark
.Hotness
)
302 return max_hotness
, all_remarks
, file_remarks
305 def gather_results(filenames
, num_jobs
, should_print_progress
, filter_
=None):
306 if should_print_progress
:
307 print('Reading YAML files...')
308 if not Remark
.demangler_proc
:
309 Remark
.set_demangler(Remark
.default_demangler
)
310 remarks
= optpmap
.pmap(
311 get_remarks
, filenames
, num_jobs
, should_print_progress
, filter_
)
312 max_hotness
= max(entry
[0] for entry
in remarks
)
314 def merge_file_remarks(file_remarks_job
, all_remarks
, merged
):
315 for filename
, d
in iteritems(file_remarks_job
):
316 for line
, remarks
in iteritems(d
):
317 for remark
in remarks
:
318 # Bring max_hotness into the remarks so that
319 # RelativeHotness does not depend on an external global.
320 remark
.max_hotness
= max_hotness
321 if remark
.key
not in all_remarks
:
322 merged
[filename
][line
].append(remark
)
325 file_remarks
= defaultdict(functools
.partial(defaultdict
, list))
326 for _
, all_remarks_job
, file_remarks_job
in remarks
:
327 merge_file_remarks(file_remarks_job
, all_remarks
, file_remarks
)
328 all_remarks
.update(all_remarks_job
)
330 return all_remarks
, file_remarks
, max_hotness
!= 0
333 def find_opt_files(*dirs_or_files
):
335 for dir_or_file
in dirs_or_files
:
336 if os
.path
.isfile(dir_or_file
):
337 all
.append(dir_or_file
)
339 for dir, subdirs
, files
in os
.walk(dir_or_file
):
340 # Exclude mounted directories and symlinks (os.walk default).
341 subdirs
[:] = [d
for d
in subdirs
342 if not os
.path
.ismount(os
.path
.join(dir, d
))]
344 if fnmatch
.fnmatch(file, "*.opt.yaml*"):
345 all
.append(os
.path
.join(dir, file))