aboutsummaryrefslogtreecommitdiff
path: root/emscripten.py
diff options
context:
space:
mode:
Diffstat (limited to 'emscripten.py')
-rwxr-xr-xemscripten.py213
1 files changed, 176 insertions, 37 deletions
diff --git a/emscripten.py b/emscripten.py
index ec2c1662..903eb78a 100755
--- a/emscripten.py
+++ b/emscripten.py
@@ -1,43 +1,182 @@
-#!/usr/bin/python
+#!/usr/bin/python2
-import os, sys, subprocess
+import argparse
+import json
+import os
+import subprocess
+import sys
+import tempfile
+import tools.shared as shared
-abspath = os.path.abspath(os.path.dirname(__file__))
-def path_from_root(*pathelems):
- return os.path.join(os.path.sep, *(abspath.split(os.sep) + list(pathelems)))
-exec(open(path_from_root('tools', 'shared.py'), 'r').read())
-COMPILER = path_from_root('src', 'compiler.js')
+# TODO: Clean up temporary files.
-def emscripten(filename, settings, outfile):
- data = open(filename, 'r').read()
- try:
- cwd = os.getcwd()
- except:
- cwd = None
- os.chdir(os.path.dirname(COMPILER))
- subprocess.Popen(COMPILER_ENGINE + [COMPILER], stdin=subprocess.PIPE, stdout=outfile, stderr=subprocess.STDOUT).communicate(settings+'\n'+data)
- if outfile: outfile.close()
- if cwd is not None:
- os.chdir(cwd)
-if __name__ == '__main__':
- if sys.argv.__len__() not in range(2,6):
- print '''
-Emscripten usage: emscripten.py INFILE [SETTINGS] [OUTPUT_FILE]
-
- INFILE must be in human-readable LLVM disassembly form (i.e., as text,
- not binary).
- SETTINGS is an optional set of compiler settings, overriding the defaults,
- in JSON format. See src/settings.js.
- OUTPUT_FILE is the file to create with the output. If not given, we write
- to stdout.
-
- You should have an ~/.emscripten file set up, see tests/settings.py, which
- in particular includes COMPILER_ENGINE.
-'''
- else:
- settings = sys.argv[2] if len(sys.argv) >= 3 else "{}"
- outfile = open(sys.argv[3], 'w') if len(sys.argv) >= 4 else None
- emscripten(sys.argv[1], settings, outfile)
+def path_from_root(*target):
+ """Returns the absolute path to the target from the emscripten root."""
+ abspath = os.path.abspath(os.path.dirname(__file__))
+ return os.path.join(os.path.sep, *(abspath.split(os.sep) + list(target)))
+
+
+def get_temp_file(suffix):
+ """Returns a named temp file with the given prefix."""
+ return tempfile.NamedTemporaryFile(
+ dir=shared.TEMP_DIR, suffix=suffix, delete=False)
+
+
+def assemble(filepath):
+ """Converts human-readable LLVM assembly to binary LLVM bitcode.
+
+ Args:
+ filepath: The path to the file to assemble. If the name ends with ".bc", the
+ file is assumed to be in bitcode format already.
+
+ Returns:
+ The path to the assembled file.
+ """
+ if not filepath.endswith('.bc'):
+ out = get_temp_file('.bc')
+ ret = subprocess.call([shared.LLVM_AS, '-o=-', filepath], stdout=out)
+ out.close()
+ if ret != 0: raise RuntimeError('Could not assemble %s.' % filepath)
+ filepath = out.name
+ return filepath
+
+
+def disassemble(filepath):
+ """Converts binary LLVM bitcode to human-readable LLVM assembly.
+
+ Args:
+ filepath: The path to the file to disassemble. If the name ends with ".ll",
+ the file is assumed to be in human-readable assembly format already.
+
+ Returns:
+ The path to the disassembled file.
+ """
+ if not filepath.endswith('.ll'):
+ out = get_temp_file('.ll')
+ command = [shared.LLVM_DIS, '-o=-', filepath] + shared.LLVM_DIS_OPTS
+ ret = subprocess.call(command, stdout=out)
+ out.close()
+ if ret != 0: raise RuntimeError('Could not disassemble %s.' % filepath)
+ filepath = out.name
+ return filepath
+
+
+def optimize(filepath):
+ """Runs LLVM's optimization passes on a given bitcode file.
+
+ Args:
+ filepath: The path to the bitcode file to optimize.
+
+ Returns:
+ The path to the optimized file.
+ """
+ out = get_temp_file('.bc')
+ ret = subprocess.call([shared.LLVM_OPT, '-O3', '-o=-', filepath], stdout=out)
+ out.close()
+ if ret != 0: raise RuntimeError('Could not optimize %s.' % filepath)
+ return out.name
+
+def link(*objects):
+ """Links multiple LLVM bitcode files into a single file.
+
+ Args:
+ objects: The bitcode files to link.
+
+ Returns:
+ The path to the linked file.
+ """
+ out = get_temp_file('.bc')
+ ret = subprocess.call([shared.LLVM_LINK] + list(objects), stdout=out)
+ out.close()
+ if ret != 0: raise RuntimeError('Could not link %s.' % objects)
+ return out.name
+
+
+def compile_malloc():
+ """Compiles dlmalloc to LLVM bitcode and returns the path to the .bc file."""
+ src = path_from_root('src', 'dlmalloc.c')
+ out = get_temp_file('.bc')
+ clang = shared.to_cc(shared.CLANG)
+ include_dir = '-I' + path_from_root('src', 'include')
+ command = [clang, '-c', '-g', '-emit-llvm', '-m32', '-o-', include_dir, src]
+ ret = subprocess.call(command, stdout=out)
+ out.close()
+ if ret != 0: raise RuntimeError('Could not compile dlmalloc.')
+ return out.name
+
+
+def emscript(infile, settings, outfile):
+ """Runs the emscripten LLVM-to-JS compiler.
+
+ Args:
+ infile: The path to the input LLVM assembly file.
+ settings: JSON-formatted string of settings that overrides the values
+ defined in src/settings.js.
+ outfile: The file where the output is written.
+ """
+ data = open(infile, 'r').read()
+ compiler = path_from_root('src', 'compiler.js')
+ subprocess.Popen(shared.COMPILER_ENGINE + [compiler],
+ stdin=subprocess.PIPE,
+ stdout=outfile,
+ cwd=path_from_root('src'),
+ stderr=subprocess.STDOUT).communicate(settings + '\n' + data)
+ outfile.close()
+
+
+def main(args):
+ # Construct a final linked and disassembled file.
+ args.infile = assemble(args.infile)
+ if args.dlmalloc: args.infile = link(args.infile, compile_malloc())
+ if args.optimize: args.infile = optimize(args.infile)
+ args.infile = disassemble(args.infile)
+
+ # Prepare settings for serialization to JSON.
+ settings = {}
+ for setting in args.settings:
+ name, value = setting.split('=', 1)
+ settings[name] = json.loads(value)
+
+ # Adjust sign correction for dlmalloc.
+ if args.dlmalloc:
+ CORRECT_SIGNS = int(settings.get('CORRECT_SIGNS', 0))
+ if CORRECT_SIGNS in (0, 2):
+ path = path_from_root('src', 'dlmalloc.c')
+ old_lines = json.loads(settings.get('CORRECT_SIGNS_LINES', '[]'))
+ line_nums = [4816, 4191, 4246, 4199, 4205, 4235, 4227]
+ lines = old_lines + [path + ':' + str(i) for i in line_nums]
+ settings['CORRECT_SIGNS'] = 2
+ settings['CORRECT_SIGNS_LINES'] = lines
+
+ # Compile the assembly to Javascript.
+ emscript(args.infile, json.dumps(settings), args.outfile)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(
+ description='Compile LLVM assembly to Javascript.',
+ epilog='You should have an ~/.emscripten file set up; see settings.py.')
+ parser.add_argument('infile',
+ help='The LLVM assembly file to compile, either in '
+ 'human-readable (*.ll) or in bitcode (*.bc) format.')
+ parser.add_argument('-O', '--optimize',
+ default=False,
+ action='store_true',
+ help='Run LLVM -O3 optimizations on the input.')
+ parser.add_argument('-m', '--dlmalloc',
+ default=False,
+ action='store_true',
+ help='Use dlmalloc. Without, uses a dummy allocator.')
+ parser.add_argument('-o', '--outfile',
+ default=sys.stdout,
+ type=argparse.FileType('w'),
+ help='Where to write the output; defaults to stdout.')
+ parser.add_argument('-s', '--settings',
+ default=[],
+ nargs=argparse.ZERO_OR_MORE,
+ metavar='FOO=BAR',
+ help='Overrides for settings defined in settings.js.')
+ main(parser.parse_args())