aboutsummaryrefslogtreecommitdiff
path: root/tools/js_optimizer.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/js_optimizer.py')
-rw-r--r--tools/js_optimizer.py152
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))]