aboutsummaryrefslogtreecommitdiff
path: root/emscripten.py
blob: a5ce0e09f39c8d97f8dfb710e70d9c84402304c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/python2

import argparse
import json
import os
import subprocess
import sys
import tempfile
import tools.shared as shared


# 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')
  opts = shared.pick_llvm_opts(3, True)
  ret = subprocess.call([shared.LLVM_OPT, '-o=-', filepath] + opts, 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 has_annotations(filepath):
  """Tests whether an assembly file contains annotations."""
  return filepath.endswith('.ll') and '[#uses=' in open(filepath).read()


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.
  if args.dlmalloc or args.optimize or not has_annotations(args.infile):
    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 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())