1# Copyright (c) 2005-2006 The Regents of The University of Michigan
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met: redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer;
8# redistributions in binary form must reproduce the above copyright
9# notice, this list of conditions and the following disclaimer in the
10# documentation and/or other materials provided with the distribution;
11# neither the name of the copyright holders nor the names of its
12# contributors may be used to endorse or promote products derived from
13# this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
19# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26#
27# Authors: Nathan Binkert
28
29from __future__ import print_function
30from __future__ import absolute_import
31
32import sys
33
34class Data(object):
35    def __init__(self, name, desc, **kwargs):
36        self.name = name
37        self.desc = desc
38        self.__dict__.update(kwargs)
39
40    def update(self, obj):
41        if not isinstance(obj, Data):
42            raise AttributeError("can only update from Data object")
43
44        for key,val in obj.__dict__.items():
45            if key.startswith('_') or key in ('name', 'desc'):
46                continue
47
48            if key not in self.__dict__:
49                self.__dict__[key] = val
50                continue
51
52            if not isinstance(val, dict):
53                if self.__dict__[key] == val:
54                    continue
55
56                raise AttributeError(
57                    "%s specified more than once old: %s new: %s" % \
58                    (key, self.__dict__[key], val))
59
60            d = self.__dict__[key]
61            for k,v in val.items():
62                if k in d:
63                    raise AttributeError(
64                        "%s specified more than once in %s" % (k, key))
65                d[k] = v
66
67        if hasattr(self, 'system') and hasattr(obj, 'system'):
68            if self.system != obj.system:
69                raise AttributeError(
70                    "conflicting values for system: '%s'/'%s'" % \
71                    (self.system, obj.system))
72
73    def printinfo(self):
74        if self.name:
75            print('name: %s' % self.name)
76        if self.desc:
77            print('desc: %s' % self.desc)
78        try:
79            if self.system:
80                print('system: %s' % self.system)
81        except AttributeError:
82            pass
83
84    def printverbose(self):
85        for key in self:
86            val = self[key]
87            if isinstance(val, dict):
88                import pprint
89                val = pprint.pformat(val)
90            print('%-20s = %s' % (key, val))
91        print()
92
93    def __contains__(self, attr):
94        if attr.startswith('_'):
95            return False
96        return attr in self.__dict__
97
98    def __getitem__(self, key):
99        if key.startswith('_'):
100            raise KeyError("Key '%s' not found" % attr)
101        return self.__dict__[key]
102
103    def __iter__(self):
104        keys = list(self.__dict__.keys())
105        keys.sort()
106        for key in keys:
107            if not key.startswith('_'):
108                yield key
109
110    def optiondict(self):
111        import m5.util
112        result = m5.util.optiondict()
113        for key in self:
114            result[key] = self[key]
115        return result
116
117    def __repr__(self):
118        d = {}
119        for key,value in self.__dict__.items():
120            if not key.startswith('_'):
121                d[key] = value
122
123        return "<%s: %s>" % (type(self).__name__, d)
124
125    def __str__(self):
126        return self.name
127
128class Job(Data):
129    def __init__(self, options):
130        super(Job, self).__init__('', '')
131
132        config = options[0]._config
133        for opt in options:
134            if opt._config != config:
135                raise AttributeError(
136                    "All options are not from the same Configuration")
137
138        self._config = config
139        self._groups = [ opt._group for opt in options ]
140        self._options = options
141
142        self.update(self._config)
143        for group in self._groups:
144            self.update(group)
145
146        self._is_checkpoint = True
147
148        for option in self._options:
149            self.update(option)
150            if not option._group._checkpoint:
151                self._is_checkpoint = False
152
153            if option._suboption:
154                self.update(option._suboption)
155                self._is_checkpoint = False
156
157        names = [ ]
158        for opt in self._options:
159            if opt.name:
160                names.append(opt.name)
161        self.name = ':'.join(names)
162
163        descs = [ ]
164        for opt in self._options:
165            if opt.desc:
166                descs.append(opt.desc)
167        self.desc = ', '.join(descs)
168
169        self._checkpoint = None
170        if not self._is_checkpoint:
171            opts = []
172            for opt in options:
173                cpt = opt._group._checkpoint
174                if not cpt:
175                    continue
176                if isinstance(cpt, Option):
177                    opt = cpt.clone(suboptions=False)
178                else:
179                    opt = opt.clone(suboptions=False)
180
181                opts.append(opt)
182
183            if opts:
184                self._checkpoint = Job(opts)
185
186    def clone(self):
187        return Job(self._options)
188
189    def printinfo(self):
190        super(Job, self).printinfo()
191        if self._checkpoint:
192            print('checkpoint: %s' % self._checkpoint.name)
193        print('config: %s' % self._config.name)
194        print('groups: %s' % [ g.name for g in self._groups ])
195        print('options: %s' % [ o.name for o in self._options ])
196        super(Job, self).printverbose()
197
198class SubOption(Data):
199    def __init__(self, name, desc, **kwargs):
200        super(SubOption, self).__init__(name, desc, **kwargs)
201        self._number = None
202
203class Option(Data):
204    def __init__(self, name, desc, **kwargs):
205        super(Option, self).__init__(name, desc, **kwargs)
206        self._suboptions = []
207        self._suboption = None
208        self._number = None
209
210    def __getattribute__(self, attr):
211        if attr == 'name':
212            name = self.__dict__[attr]
213            if self._suboption is not None:
214                name = '%s:%s' % (name, self._suboption.name)
215            return name
216
217        if attr == 'desc':
218            desc = [ self.__dict__[attr] ]
219            if self._suboption is not None and self._suboption.desc:
220                desc.append(self._suboption.desc)
221            return ', '.join(desc)
222
223        return super(Option, self).__getattribute__(attr)
224
225    def suboption(self, name, desc, **kwargs):
226        subo = SubOption(name, desc, **kwargs)
227        subo._config = self._config
228        subo._group = self._group
229        subo._option = self
230        subo._number = len(self._suboptions)
231        self._suboptions.append(subo)
232        return subo
233
234    def clone(self, suboptions=True):
235        option = Option(self.__dict__['name'], self.__dict__['desc'])
236        option.update(self)
237        option._group = self._group
238        option._config = self._config
239        option._number = self._number
240        if suboptions:
241            option._suboptions.extend(self._suboptions)
242            option._suboption = self._suboption
243        return option
244
245    def subopts(self):
246        if not self._suboptions:
247            return [ self ]
248
249        subopts = []
250        for subo in self._suboptions:
251            option = self.clone()
252            option._suboption = subo
253            subopts.append(option)
254
255        return subopts
256
257    def printinfo(self):
258        super(Option, self).printinfo()
259        print('config: %s' % self._config.name)
260        super(Option, self).printverbose()
261
262class Group(Data):
263    def __init__(self, name, desc, **kwargs):
264        super(Group, self).__init__(name, desc, **kwargs)
265        self._options = []
266        self._number = None
267        self._checkpoint = False
268
269    def option(self, name, desc, **kwargs):
270        opt = Option(name, desc, **kwargs)
271        opt._config = self._config
272        opt._group = self
273        opt._number = len(self._options)
274        self._options.append(opt)
275        return opt
276
277    def options(self):
278        return self._options
279
280    def subopts(self):
281        subopts = []
282        for opt in self._options:
283            for subo in opt.subopts():
284                subopts.append(subo)
285        return subopts
286
287    def printinfo(self):
288        super(Group, self).printinfo()
289        print('config: %s' % self._config.name)
290        print('options: %s' % [ o.name for o in self._options ])
291        super(Group, self).printverbose()
292
293class Configuration(Data):
294    def __init__(self, name, desc, **kwargs):
295        super(Configuration, self).__init__(name, desc, **kwargs)
296        self._groups = []
297        self._posfilters = []
298        self._negfilters = []
299
300    def group(self, name, desc, **kwargs):
301        grp = Group(name, desc, **kwargs)
302        grp._config = self
303        grp._number = len(self._groups)
304        self._groups.append(grp)
305        return grp
306
307    def groups(self):
308        return self._groups
309
310    def checkchildren(self, kids):
311        for kid in kids:
312            if kid._config != self:
313                raise AttributeError("child from the wrong configuration")
314
315    def sortgroups(self, groups):
316        groups = [ (grp._number, grp) for grp in groups ]
317        groups.sort()
318        return [ grp[1] for grp in groups ]
319
320    def options(self, groups=None, checkpoint=False):
321        if groups is None:
322            groups = self._groups
323        self.checkchildren(groups)
324        groups = self.sortgroups(groups)
325        if checkpoint:
326            groups = [ grp for grp in groups if grp._checkpoint ]
327            optgroups = [ g.options() for g in groups ]
328        else:
329            optgroups = [ g.subopts() for g in groups ]
330        if not optgroups:
331            return
332
333        import m5.util
334        for options in m5.util.crossproduct(optgroups):
335            for opt in options:
336                cpt = opt._group._checkpoint
337                if not isinstance(cpt, bool) and cpt != opt:
338                    if checkpoint:
339                        break
340                    else:
341                        yield options
342            else:
343                if checkpoint:
344                    yield options
345
346    def addfilter(self, filt, pos=True):
347        import re
348        filt = re.compile(filt)
349        if pos:
350            self._posfilters.append(filt)
351        else:
352            self._negfilters.append(filt)
353
354    def jobfilter(self, job):
355        for filt in self._negfilters:
356            if filt.match(job.name):
357                return False
358
359        if not self._posfilters:
360            return True
361
362        for filt in self._posfilters:
363            if filt.match(job.name):
364                return True
365
366        return False
367
368    def checkpoints(self, groups=None):
369        for options in self.options(groups, True):
370            job = Job(options)
371            if self.jobfilter(job):
372                yield job
373
374    def jobs(self, groups=None):
375        for options in self.options(groups, False):
376            job = Job(options)
377            if self.jobfilter(job):
378                yield job
379
380    def alljobs(self, groups=None):
381        for options in self.options(groups, True):
382            yield Job(options)
383        for options in self.options(groups, False):
384            yield Job(options)
385
386    def find(self, jobname):
387        for job in self.alljobs():
388            if job.name == jobname:
389                return job
390        else:
391            raise AttributeError("job '%s' not found" % jobname)
392
393    def job(self, options):
394        self.checkchildren(options)
395        options = [ (opt._group._number, opt) for opt in options ]
396        options.sort()
397        options = [ opt[1] for opt in options ]
398        job = Job(options)
399        return job
400
401    def printinfo(self):
402        super(Configuration, self).printinfo()
403        print('groups: %s' % [ g.name for g in self._groups ])
404        super(Configuration, self).printverbose()
405
406def JobFile(jobfile):
407    from os.path import expanduser, isfile, join as joinpath
408    filename = expanduser(jobfile)
409
410    # Can't find filename in the current path, search sys.path
411    if not isfile(filename):
412        for path in sys.path:
413            testname = joinpath(path, filename)
414            if isfile(testname):
415                filename = testname
416                break
417        else:
418            raise AttributeError("Could not find file '%s'" % jobfile)
419
420    data = {}
421    exec(compile(open(filename).read(), filename, 'exec'), data)
422    if 'conf' not in data:
423        raise ImportError('cannot import name conf from %s' % jobfile)
424    return data['conf']
425
426def main(conf=None):
427    usage = 'Usage: %s [-b] [-c] [-v]' % sys.argv[0]
428    if conf is None:
429        usage += ' <jobfile>'
430
431    try:
432        import getopt
433        opts, args = getopt.getopt(sys.argv[1:], '-bcv')
434    except getopt.GetoptError:
435        sys.exit(usage)
436
437    both = False
438    checkpoint = False
439    verbose = False
440    for opt,arg in opts:
441        if opt == '-b':
442            both = True
443            checkpoint = True
444        if opt == '-c':
445            checkpoint = True
446        if opt == '-v':
447            verbose = True
448
449    if conf is None:
450        if len(args) != 1:
451            raise AttributeError(usage)
452        conf = JobFile(args[0])
453    else:
454        if len(args) != 0:
455            raise AttributeError(usage)
456
457    if both:
458        jobs = conf.alljobs()
459    elif checkpoint:
460        jobs = conf.checkpoints()
461    else:
462        jobs = conf.jobs()
463
464    for job in jobs:
465        if verbose:
466            job.printinfo()
467        else:
468            cpt = ''
469            if job._checkpoint:
470                cpt = job._checkpoint.name
471            print(job.name, cpt)
472
473if __name__ == '__main__':
474    main()
475