aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2012-03-16 12:39:12 -0700
committerAlon Zakai <alonzakai@gmail.com>2012-03-16 12:39:12 -0700
commit1a2df275c5fb4bf9f3df3550bd8cdb156268ff83 (patch)
tree3c26bff9aa401569c52e9636aed6423084b4db16
parent37011d48e315393cb0e271fd00a490585fba5b9d (diff)
source code compression option in emcc
-rwxr-xr-xemcc103
-rwxr-xr-xtests/runner.py25
2 files changed, 116 insertions, 12 deletions
diff --git a/emcc b/emcc
index 567206fd..984e4014 100755
--- a/emcc
+++ b/emcc
@@ -160,18 +160,23 @@ Options that are modified or new in %s include:
break the generated code! If that happens, try
-O2 and then adding dangerous optimizations one
by one.
+
-s OPTION=VALUE JavaScript code generation option passed
into the emscripten compiler. For the
available options, see src/settings.js
+
--typed-arrays <mode> 0: No typed arrays
1: Parallel typed arrays
2: Shared (C-like) typed arrays (default)
+
--llvm-opts <level> 0: No LLVM optimizations (default in -O0)
1: -O1 LLVM optimizations (default in -O1)
2: -O2 LLVM optimizations
3: -O3 LLVM optimizations (default in -O2+)
+
--closure <on> 0: No closure compiler (default in -O0, -O1)
1: Run closure compiler (default in -O2, -O3)
+
--js-transform <cmd> <cmd> will be called on the generated code
before it is optimized. This lets you modify
the JavaScript, for example adding some code
@@ -186,18 +191,13 @@ Options that are modified or new in %s include:
list of arguments, for example, <cmd> of
"python processor.py" will cause a python
script to be run.
+
--pre-js <file> A file whose contents are added before the
generated code
+
--post-js <file> A file whose contents are added after the
generated code
- --minify <on> 0: Do not minify the generated JavaScript's
- whitespace (default if closure compiler
- will not be run)
- 1: Minify the generated JavaScript's
- whitespace (default if closure compiler
- will be run). Note that this by itself
- will not minify the code (closure does
- that)
+
--embed-file <file> A file to embed inside the generated
JavaScript. The compiled code will be able
to access the file in the current directory
@@ -205,6 +205,7 @@ Options that are modified or new in %s include:
just the filename, without a path to it).
If a directory is passed here, its entire
contents will be embedded.
+
--preload-file <name> A file to preload before running the
compiled code asynchronously. Otherwise
similar to --embed-file, except that this
@@ -218,6 +219,31 @@ Options that are modified or new in %s include:
the alternative, change the suffix).
If a directory is passed here, its entire
contents will be preloaded.
+
+ --compression <codec> Compress both the compiled code and embedded/
+ preloaded files. <codec> should be a triple,
+
+ <native_encoder>,<js_decoder>,<js_name>
+
+ where native_encoder is a native executable
+ that compresses stdin to stdout (the simplest
+ possible interface), js_decoder is a
+ JavaScript file that implements a decoder,
+ and js_name is the name of the function to
+ call in the decoder file (which should
+ receive an array/typed array and return
+ an array/typed array.
+ Compression only works when generating HTML.
+
+ --minify <on> 0: Do not minify the generated JavaScript's
+ whitespace (default if closure compiler
+ will not be run)
+ 1: Minify the generated JavaScript's
+ whitespace (default if closure compiler
+ will be run). Note that this by itself
+ will not minify the code (closure does
+ that)
+
--ignore-dynamic-linking Normally emcc will treat dynamic linking like
static linking, by linking in the code from
the dynamic library. This fails if the same
@@ -226,6 +252,7 @@ Options that are modified or new in %s include:
which allows the build system to proceed without
errors. However, you will need to manually
link to the shared libraries later on yourself.
+
--shell-file <path> The path name to a skeleton HTML file used
when generating HTML output. The shell file
used needs to have this token inside it:
@@ -349,6 +376,17 @@ else:
def in_temp(name):
return os.path.join(temp_dir, name)
+class Compression:
+ on = False
+
+ @staticmethod
+ def compressed_name(filename):
+ return filename + '.compress'
+
+ @staticmethod
+ def compress(filename):
+ execute(Compression.encoder, stdin=open(filename, 'rb'), stdout=open(Compression.compressed_name(filename), 'wb'))
+
try:
call = CXX if use_cxx else CC
@@ -365,6 +403,7 @@ try:
minify_whitespace = None
embed_files = []
preload_files = []
+ compression = None
ignore_dynamic_linking = False
shell_path = shared.path_from_root('src', 'shell.html')
@@ -419,6 +458,18 @@ try:
preload_files.append(newargs[i+1])
newargs[i] = ''
newargs[i+1] = ''
+ elif newargs[i].startswith('--compression'):
+ check_bad_eq(newargs[i])
+ parts = newargs[i+1].split(',')
+ assert len(parts) == 3, '--compression requires specifying native_encoder,js_decoder,js_name - see emcc --help. got: %s' % newargs[i+1]
+ Compression.encoder = parts[0]
+ Compression.decoder = parts[1]
+ Compression.js_name = parts[2]
+ assert os.path.exists(Compression.encoder), 'native encoder %s does not exist' % Compression.encoder
+ assert os.path.exists(Compression.decoder), 'js decoder %s does not exist' % Compression.decoder
+ Compression.on = True
+ newargs[i] = ''
+ newargs[i+1] = ''
elif newargs[i] == '-MF': # clang cannot handle this, so we fake it
f = open(newargs[i+1], 'w')
f.write('\n')
@@ -535,6 +586,8 @@ try:
else:
final_suffix = ''
+ assert not (Compression.on and final_suffix != 'html'), 'Compression only works when generating HTML'
+
# Apply optimization level settings
shared.Settings.apply_opt_level(opt_level, noisy=True)
@@ -754,6 +807,9 @@ try:
# Embed and preload files
if len(embed_files) + len(preload_files) > 0:
if DEBUG: print >> sys.stderr, 'emcc: setting up files'
+
+ assert not Compression.on
+
code = ''
# Sanity checks
@@ -915,7 +971,36 @@ try:
if DEBUG: print >> sys.stderr, 'emcc: generating HTML'
shell = open(shell_path).read()
html = open(target, 'w')
- html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(final).read()))
+ if not Compression.on:
+ html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(final).read()))
+ else:
+ # Add the decompressor in the html, and code to
+ # 1. download the compressed file
+ # 2. decompress to a typed array
+ # 3. convert to a string of source code
+ # 4. insert a script element with that source code (more effective than eval)
+ js_target = unsuffixed(target) + '.js'
+ shutil.move(final, js_target)
+ Compression.compress(js_target)
+ decoding = open(Compression.decoder).read()
+ decoding += '''
+ var compiledCodeXHR = new XMLHttpRequest();
+ compiledCodeXHR.open('GET', '%s', true);
+ compiledCodeXHR.responseType = 'arraybuffer';
+ compiledCodeXHR.onload = function() {
+ var arrayBuffer = compiledCodeXHR.response;
+ if (!arrayBuffer) throw('Loading compressed code failed.');
+ var byteArray = new Uint8Array(arrayBuffer);
+ var decompressed = %s(byteArray);
+ var source = Array.prototype.slice.apply(decompressed).map(function(x) { return String.fromCharCode(x) }).join('');
+ var scriptTag = document.createElement('script');
+ scriptTag.setAttribute('type', 'text/javascript');
+ scriptTag.innerHTML = source;
+ document.body.appendChild(scriptTag);
+ };
+ compiledCodeXHR.send(null);
+''' % (Compression.compressed_name(js_target), Compression.js_name)
+ html.write(shell.replace('{{{ SCRIPT_CODE }}}', decoding))
html.close()
else:
# copy final JS to output
diff --git a/tests/runner.py b/tests/runner.py
index 66aa63b4..28e918b7 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -6220,6 +6220,28 @@ f.close()
emscripten_run_script(output);
''')
+ def test_emcc_compression(self):
+ open(os.path.join(self.get_dir(), 'main.cpp'), 'w').write(self.with_report_result(r'''
+ #include <stdio.h>
+ #include <emscripten.h>
+ int main() {
+ printf("hello compressed world\n");
+ int result = 1;
+ REPORT_RESULT();
+ return 0;
+ }
+ '''))
+
+ Popen([EMCC, os.path.join(self.get_dir(), 'main.cpp'), '-o', 'page.html',
+ '--compression', '%s,%s,%s' % (path_from_root('third_party', 'lzma.js', 'lzma-native'),
+ path_from_root('third_party', 'lzma.js', 'lzma-decoder.js'),
+ 'LZMA.decompress')]).communicate()
+ assert os.path.exists(os.path.join(self.get_dir(), 'page.js')), 'must be side js'
+ assert os.path.exists(os.path.join(self.get_dir(), 'page.js.compress')), 'must be side compressed js'
+ assert os.stat(os.path.join(self.get_dir(), 'page.js')).st_size > os.stat(os.path.join(self.get_dir(), 'page.js.compress')).st_size, 'compressed file must be smaller'
+ shutil.move(os.path.join(self.get_dir(), 'page.js'), 'page.js.renamedsoitcannotbefound');
+ self.run_browser('page.html', '', '/report_result?1')
+
def test_emcc_preload_file(self):
open(os.path.join(self.get_dir(), 'somefile.txt'), 'w').write('''load me right before running the code please''')
open(os.path.join(self.get_dir(), 'main.cpp'), 'w').write(self.with_report_result(r'''
@@ -6291,9 +6313,6 @@ f.close()
Popen([EMCC, os.path.join(self.get_dir(), 'sdl_image.c'), '--preload-file', 'screenshot.jpg', '-o', 'page.html']).communicate()
self.run_browser('page.html', 'You should see |load me right before|.', '/report_result?600')
- def test_emcc_compression(self):
- pass # test compression of both the compiled code itself in a side file, and of data files
-
def test_emcc_worker(self):
# Test running in a web worker
output = Popen([EMCC, path_from_root('tests', 'hello_world_worker.cpp'), '-o', 'worker.js'], stdout=PIPE, stderr=PIPE).communicate()