Rewrite of emscripten.py:
* Uses option arguments instead of positional ones. * Allows linking to dlmalloc. * Accepts both .bc and .ll files and takes care of annotations. * Allows running the LLVM optimization pass automatically. * Updated test runner to use the new emscripten.py interface. Refactoring: * Moved settings.py to root folder. It no longer applies just to tests. * Updated references to settings.py. * Added an __init__.py to tools, so we don't have to hack around imports.
This commit is contained in:
Родитель
d94b2423ae
Коммит
7dcdb044d3
213
emscripten.py
213
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 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)
|
||||
|
||||
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)
|
||||
|
||||
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())
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -5,7 +5,7 @@ See settings.py file for options¶ms. Edit as needed.
|
|||
'''
|
||||
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re
|
||||
import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, json
|
||||
|
||||
# Setup
|
||||
|
||||
|
@ -19,7 +19,7 @@ exec(open(path_from_root('tools', 'shared.py'), 'r').read())
|
|||
try:
|
||||
assert COMPILER_OPTS != None
|
||||
except:
|
||||
raise Exception('Cannot find "COMPILER_OPTS" definition. Is ~/.emscripten set up properly? You may need to copy the template at ~/tests/settings.py into it.')
|
||||
raise Exception('Cannot find "COMPILER_OPTS" definition. Is ~/.emscripten set up properly? You may need to copy the template from settings.py into it.')
|
||||
|
||||
# Paths
|
||||
|
||||
|
@ -246,7 +246,8 @@ class RunnerCore(unittest.TestCase):
|
|||
exported_settings[setting] = value
|
||||
except:
|
||||
pass
|
||||
compiler_output = timeout_run(Popen([EMSCRIPTEN, filename + '.o.ll', str(exported_settings).replace("'", '"'), filename + '.o.js'], stdout=PIPE, stderr=STDOUT), TIMEOUT, 'Compiling')
|
||||
settings = ['%s=%s' % (k, json.dumps(v)) for k, v in exported_settings.items()]
|
||||
compiler_output = timeout_run(Popen([EMSCRIPTEN, filename + '.o.ll', '-o', filename + '.o.js', '-s'] + settings, stdout=PIPE, stderr=STDOUT), TIMEOUT, 'Compiling')
|
||||
|
||||
# Detect compilation crashes and errors
|
||||
if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
import shutil, time
|
||||
import shutil, time, os
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
|
||||
CONFIG_FILE = os.path.expanduser('~/.emscripten')
|
||||
if not os.path.exists(CONFIG_FILE):
|
||||
shutil.copy(path_from_root('tests', 'settings.py'), CONFIG_FILE)
|
||||
shutil.copy(path_from_root('settings.py'), CONFIG_FILE)
|
||||
exec(open(CONFIG_FILE, 'r').read())
|
||||
|
||||
# Tools
|
||||
|
@ -67,4 +66,3 @@ def line_splitter(data):
|
|||
def limit_size(string, MAX=80*20):
|
||||
if len(string) < MAX: return string
|
||||
return string[0:MAX] + '...'
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче