diff options
Diffstat (limited to 'tools/js_optimizer.py')
-rw-r--r-- | tools/js_optimizer.py | 152 |
1 files changed, 129 insertions, 23 deletions
diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 13e6e4f6..60093bca 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -1,5 +1,5 @@ -import os, sys, subprocess, multiprocessing, re +import os, sys, subprocess, multiprocessing, re, string, json import shared configuration = shared.configuration @@ -19,6 +19,78 @@ WINDOWS = sys.platform.startswith('win') DEBUG = os.environ.get('EMCC_DEBUG') +func_sig = re.compile('( *)function ([_\w$]+)\(') + +class Minifier: + ''' + asm.js minification support. We calculate possible names and minification of + globals here, then pass that into the parallel js-optimizer.js runners which + during registerize perform minification of locals. + ''' + + def __init__(self, js, js_engine): + self.js = js + self.js_engine = js_engine + + # Create list of valid short names + + MAX_NAMES = 6000#0 + INVALID_2 = set(['do', 'if', 'in']) + INVALID_3 = set(['for', 'new', 'try', 'var', 'env']) + + self.names = [] + init_possibles = string.ascii_letters + '_$' + later_possibles = init_possibles + string.digits + for a in init_possibles: + if len(self.names) >= MAX_NAMES: break + self.names.append(a) + for a in init_possibles: + for b in later_possibles: + if len(self.names) >= MAX_NAMES: break + curr = a + b + if curr not in INVALID_2: self.names.append(curr) + for a in init_possibles: + for b in later_possibles: + for c in later_possibles: + if len(self.names) >= MAX_NAMES: break + curr = a + b + c + if curr not in INVALID_3: self.names.append(curr) + #print >> sys.stderr, self.names + + def minify_shell(self, shell, compress): + #print >> sys.stderr, "MINIFY SHELL 1111111111", shell, "\n222222222222222" + # Run through js-optimizer.js to find and minify the global symbols + # We send it the globals, which it parses at the proper time. JS decides how + # to minify all global names, we receive a dictionary back, which is then + # used by the function processors + + shell = shell.replace('0.0', '13371337') # avoid uglify doing 0.0 => 0 + + # Find all globals in the JS functions code + self.globs = [m.group(2) for m in func_sig.finditer(self.js)] + + temp_file = temp_files.get('.minifyglobals.js').name + f = open(temp_file, 'w') + f.write(shell) + f.write('\n') + self + f.write('// MINIFY_INFO:' + self.serialize()) + f.close() + + output = subprocess.Popen(self.js_engine + [JS_OPTIMIZER, temp_file, 'minifyGlobals', 'noPrintMetadata'] + (['compress'] if compress else []), stdout=subprocess.PIPE).communicate()[0] + assert len(output) > 0 and not output.startswith('Assertion failed'), 'Error in js optimizer: ' + output + #print >> sys.stderr, "minified SHELL 3333333333333333", output, "\n44444444444444444444" + code, metadata = output.split('// MINIFY_INFO:') + self.globs = json.loads(metadata) + return code.replace('13371337', '0.0') + + + def serialize(self): + return json.dumps({ + 'names': self.names, + 'globals': self.globs + }) + def run_on_chunk(command): filename = command[2] # XXX hackish #print >> sys.stderr, 'running js optimizer command', ' '.join(command), '""""', open(filename).read() @@ -46,10 +118,25 @@ def run_on_js(filename, passes, js_engine, jcache): suffix_start = js.find(suffix_marker) suffix = '' if suffix_start >= 0: - suffix = js[suffix_start:js.find('\n', suffix_start)] + '\n' + suffix_end = js.find('\n', suffix_start) + suffix = js[suffix_start:suffix_end] + '\n' # if there is metadata, we will run only on the generated functions. If there isn't, we will run on everything. generated = set(eval(suffix[len(suffix_marker)+1:])) + # Find markers + start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n' + end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n' + start_funcs = js.find(start_funcs_marker) + end_funcs = js.rfind(end_funcs_marker) + assert (start_funcs >= 0) == (end_funcs >= 0) == (not not suffix) + asm_registerize = 'asm' in passes and 'registerize' in passes + if asm_registerize: + start_asm_marker = '// EMSCRIPTEN_START_ASM\n' + end_asm_marker = '// EMSCRIPTEN_END_ASM\n' + start_asm = js.find(start_asm_marker) + end_asm = js.rfind(end_asm_marker) + assert (start_asm >= 0) == (end_asm >= 0) + if not suffix and jcache: # JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered # This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure @@ -57,29 +144,45 @@ def run_on_js(filename, passes, js_engine, jcache): if DEBUG: print >>sys.stderr, 'js optimizer: no metadata, so disabling jcache' jcache = False - # If we process only generated code, find that and save the rest on the side - func_sig = re.compile('( *)function (_[\w$]+)\(') if suffix: - pos = 0 - gen_start = 0 - gen_end = 0 - while 1: - m = func_sig.search(js, pos) - if not m: break - pos = m.end() - indent = m.group(1) - ident = m.group(2) - if ident in generated: - if not gen_start: - gen_start = m.start() - assert gen_start - gen_end = js.find('\n%s}\n' % indent, m.end()) + (3 + len(indent)) - assert gen_end > gen_start - pre = js[:gen_start] - post = js[gen_end:] + if not asm_registerize: + pre = js[:start_funcs + len(start_funcs_marker)] + post = js[end_funcs + len(end_funcs_marker):] + js = js[start_funcs + len(start_funcs_marker):end_funcs] + if 'asm' not in passes: # can have Module[..] and inlining prevention code, push those to post + class Finals: + buf = [] + def process(line): + if len(line) > 0 and not line.startswith((' ', 'function', '}')): + Finals.buf.append(line) + return False + return True + js = '\n'.join(filter(process, js.split('\n'))) + post = '\n'.join(Finals.buf) + '\n' + post + post = end_funcs_marker + post + else: + # We need to split out the asm shell as well, for minification + pre = js[:start_asm + len(start_asm_marker)] + post = js[end_asm:] + asm_shell = js[start_asm + len(start_asm_marker):start_funcs + len(start_funcs_marker)] + ''' +EMSCRIPTEN_FUNCS(); +''' + js[end_funcs + len(end_funcs_marker):end_asm + len(end_asm_marker)] + js = js[start_funcs + len(start_funcs_marker):end_funcs] + + minifier = Minifier(js, js_engine) + asm_shell_pre, asm_shell_post = minifier.minify_shell(asm_shell, 'compress' in passes).split('EMSCRIPTEN_FUNCS();'); + asm_shell_post = asm_shell_post.replace('});', '})'); + pre += asm_shell_pre + '\n' + start_funcs_marker + post = end_funcs_marker + asm_shell_post + post + + minify_info = minifier.serialize() + #if DEBUG: print >> sys.stderr, 'minify info:', minify_info + # remove suffix if no longer needed if 'last' in passes: - post = post.replace(suffix, '') # no need to write out the metadata - nothing after us needs it - js = js[gen_start:gen_end] + suffix_start = post.find(suffix_marker) + suffix_end = post.find('\n', suffix_start) + post = post[:suffix_start] + post[suffix_end:] + else: pre = '' post = '' @@ -132,6 +235,9 @@ def run_on_js(filename, passes, js_engine, jcache): f = open(temp_file, 'w') f.write(chunk) f.write(suffix_marker) + if asm_registerize: + f.write('\n') + f.write('// MINIFY_INFO:' + minify_info) f.close() return temp_file filenames = [write_chunk(chunks[i], i) for i in range(len(chunks))] |