~ubuntu-branches/ubuntu/vivid/emscripten/vivid

« back to all changes in this revision

Viewing changes to tools/cache.py

  • Committer: Package Import Robot
  • Author(s): Sylvestre Ledru
  • Date: 2013-05-02 13:11:51 UTC
  • Revision ID: package-import@ubuntu.com-20130502131151-q8dvteqr1ef2x7xz
Tags: upstream-1.4.1~20130504~adb56cb
ImportĀ upstreamĀ versionĀ 1.4.1~20130504~adb56cb

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os.path, sys, shutil, hashlib, cPickle, zlib, time
 
2
 
 
3
import tempfiles
 
4
 
 
5
# Permanent cache for dlmalloc and stdlibc++
 
6
class Cache:
 
7
  def __init__(self, dirname=None, debug=False):
 
8
    if dirname is None:
 
9
      dirname = os.environ.get('EM_CACHE')
 
10
    if not dirname:
 
11
      dirname = os.path.expanduser(os.path.join('~', '.emscripten_cache'))
 
12
    self.dirname = dirname
 
13
    self.debug = debug
 
14
 
 
15
  def ensure(self):
 
16
    if not os.path.exists(self.dirname):
 
17
      os.makedirs(self.dirname)
 
18
 
 
19
  def erase(self):
 
20
    tempfiles.try_delete(self.dirname)
 
21
    try:
 
22
      open(self.dirname + '__last_clear', 'w').write('last clear: ' + time.asctime() + '\n')
 
23
    except Exception, e:
 
24
      print >> sys.stderr, 'failed to save last clear time: ', e
 
25
 
 
26
  def get_path(self, shortname):
 
27
    return os.path.join(self.dirname, shortname)
 
28
 
 
29
  # Request a cached file. If it isn't in the cache, it will be created with
 
30
  # the given creator function
 
31
  def get(self, shortname, creator, extension='.bc'):
 
32
    if not shortname.endswith(extension): shortname += extension
 
33
    cachename = os.path.join(self.dirname, shortname)
 
34
    if os.path.exists(cachename):
 
35
      return cachename
 
36
    self.ensure()
 
37
    shutil.copyfile(creator(), cachename)
 
38
    return cachename
 
39
 
 
40
# JS-specific cache. We cache the results of compilation and optimization,
 
41
# so that in incremental builds we can just load from cache.
 
42
# We cache reasonably-large-sized chunks
 
43
class JCache:
 
44
  def __init__(self, cache):
 
45
    self.cache = cache
 
46
    self.dirname = os.path.join(cache.dirname, 'jcache')
 
47
    self.debug = cache.debug
 
48
 
 
49
  def ensure(self):
 
50
    self.cache.ensure()
 
51
    if not os.path.exists(self.dirname):
 
52
      os.makedirs(self.dirname)
 
53
 
 
54
  def get_shortkey(self, keys):
 
55
    if type(keys) not in [list, tuple]:
 
56
      keys = [keys]
 
57
    ret = ''
 
58
    for key in keys:
 
59
      assert type(key) == str
 
60
      ret += hashlib.md5(key).hexdigest()
 
61
    return ret
 
62
 
 
63
  def get_cachename(self, shortkey):
 
64
    return os.path.join(self.dirname, shortkey)
 
65
 
 
66
  # Returns a cached value, if it exists. Make sure the full key matches
 
67
  def get(self, shortkey, keys):
 
68
    if self.debug: print >> sys.stderr, 'jcache get?', shortkey
 
69
    cachename = self.get_cachename(shortkey)
 
70
    if not os.path.exists(cachename):
 
71
      if self.debug: print >> sys.stderr, 'jcache none at all'
 
72
      return
 
73
    try:
 
74
      data = cPickle.loads(zlib.decompress(open(cachename).read()))
 
75
    except Exception, e:
 
76
      if self.debug: print >> sys.stderr, 'jcache decompress/unpickle error:', e
 
77
      return
 
78
    if len(data) != 2:
 
79
      if self.debug: print >> sys.stderr, 'jcache error in get'
 
80
      return
 
81
    oldkeys = data[0]
 
82
    if len(oldkeys) != len(keys):
 
83
      if self.debug: print >> sys.stderr, 'jcache collision (a)'
 
84
      return
 
85
    for i in range(len(oldkeys)):
 
86
      if oldkeys[i] != keys[i]:
 
87
        if self.debug: print >> sys.stderr, 'jcache collision (b)'
 
88
        return
 
89
    if self.debug: print >> sys.stderr, 'jcache win'
 
90
    return data[1]
 
91
 
 
92
  # Sets the cached value for a key (from get_key)
 
93
  def set(self, shortkey, keys, value):
 
94
    cachename = self.get_cachename(shortkey)
 
95
    try:
 
96
      f = open(cachename, 'w')
 
97
      f.write(zlib.compress(cPickle.dumps([keys, value])))
 
98
      f.close()
 
99
    except Exception, e:
 
100
      if self.debug: print >> sys.stderr, 'jcache compress/pickle error:', e
 
101
      return
 
102
    #  for i in range(len(keys)):
 
103
    #    open(cachename + '.key' + str(i), 'w').write(keys[i])
 
104
    #  open(cachename + '.value', 'w').write(value)
 
105
 
 
106
# Given a set of functions of form (ident, text), and a preferred chunk size,
 
107
# generates a set of chunks for parallel processing and caching.
 
108
# It is very important to generate similar chunks in incremental builds, in
 
109
# order to maximize the chance of cache hits. To achieve that, we save the
 
110
# chunking used in the previous compilation of this phase, and we try to
 
111
# generate the same chunks, barring big differences in function sizes that
 
112
# violate our chunk size guideline. If caching is not used, chunking_file
 
113
# should be None
 
114
def chunkify(funcs, chunk_size, chunking_file, DEBUG=False):
 
115
  previous_mapping = None
 
116
  if chunking_file:
 
117
    chunking_file = chunking_file
 
118
    if os.path.exists(chunking_file):
 
119
      try:
 
120
        previous_mapping = cPickle.Unpickler(open(chunking_file, 'rb')).load() # maps a function identifier to the chunk number it will be in
 
121
        #if DEBUG: print >> sys.stderr, 'jscache previous mapping', previous_mapping
 
122
      except:
 
123
        pass
 
124
  chunks = []
 
125
  if previous_mapping:
 
126
    # initialize with previous chunking
 
127
    news = []
 
128
    for func in funcs:
 
129
      ident, data = func
 
130
      assert ident, 'need names for jcache chunking'
 
131
      if not ident in previous_mapping:
 
132
        news.append(func)
 
133
      else:
 
134
        n = previous_mapping[ident]
 
135
        while n >= len(chunks): chunks.append([])
 
136
        chunks[n].append(func)
 
137
    if DEBUG: print >> sys.stderr, 'jscache not in previous chunking', len(news)
 
138
    # add news and adjust for new sizes
 
139
    spilled = news
 
140
    for i in range(len(chunks)):
 
141
      chunk = chunks[i]
 
142
      size = sum([len(func[1]) for func in chunk])
 
143
      #if DEBUG: print >> sys.stderr, 'need spilling?', i, size, len(chunk), 'vs', chunk_size, 1.5*chunk_size
 
144
      while size > 1.5*chunk_size and len(chunk) > 1:
 
145
        spill = chunk.pop()
 
146
        spilled.append(spill)
 
147
        size -= len(spill[1])
 
148
    #if DEBUG: print >> sys.stderr, 'jscache new + spilled', len(spilled)
 
149
    for chunk in chunks:
 
150
      size = sum([len(func[1]) for func in chunk])
 
151
      while size < 0.66*chunk_size and len(spilled) > 0:
 
152
        spill = spilled.pop()
 
153
        chunk.append(spill)
 
154
        size += len(spill[1])
 
155
    chunks = filter(lambda chunk: len(chunk) > 0, chunks) # might have empty ones, eliminate them
 
156
    funcs = spilled # we will allocate these into chunks as if they were normal inputs
 
157
    #if DEBUG: print >> sys.stderr, 'leftover spills', len(spilled)
 
158
  # initialize reasonably, the rest of the funcs we need to split out
 
159
  curr = []
 
160
  total_size = 0
 
161
  for i in range(len(funcs)):
 
162
    func = funcs[i]
 
163
    curr_size = len(func[1])
 
164
    if total_size + curr_size < chunk_size:
 
165
      curr.append(func)
 
166
      total_size += curr_size
 
167
    else:
 
168
      chunks.append(curr)
 
169
      curr = [func]
 
170
      total_size = curr_size
 
171
  if curr:
 
172
    chunks.append(curr)
 
173
    curr = None
 
174
  if chunking_file:
 
175
    # sort within each chunk, to keep the order identical
 
176
    for chunk in chunks:
 
177
      chunk.sort(key=lambda func: func[0])
 
178
    # save new mapping info
 
179
    new_mapping = {}
 
180
    for i in range(len(chunks)):
 
181
      chunk = chunks[i]
 
182
      for ident, data in chunk:
 
183
        assert ident not in new_mapping, 'cannot have duplicate names in jcache chunking'
 
184
        new_mapping[ident] = i
 
185
    cPickle.Pickler(open(chunking_file, 'wb')).dump(new_mapping)
 
186
    #if DEBUG:
 
187
    #  for i in range(len(chunks)):
 
188
    #    chunk = chunks[i]
 
189
    #    print >> sys.stderr, 'final chunk', i, len(chunk)
 
190
    #  print >> sys.stderr, 'new mapping:', new_mapping
 
191
    #  if previous_mapping:
 
192
    #    for ident in set(previous_mapping.keys() + new_mapping.keys()):
 
193
    #      if previous_mapping.get(ident) != new_mapping.get(ident):
 
194
    #        print >> sys.stderr, 'mapping inconsistency', ident, previous_mapping.get(ident), new_mapping.get(ident)
 
195
  return [''.join([func[1] for func in chunk]) for chunk in chunks] # remove function names