Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / native_client / build / build_nexe.py
index fb6b0bd..ca53cf4 100644 (file)
@@ -20,6 +20,9 @@ import tempfile
 import threading
 import urllib2
 
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+import pynacl.platform
+
 class Error(Exception):
   pass
 
@@ -32,7 +35,7 @@ def FixPath(path):
   # function that still has the path length limit. Instead, we'll cheat and
   # normalize the path lexically.
   path = os.path.normpath(os.path.join(os.getcwd(), path))
-  if sys.platform in ['win32', 'cygwin']:
+  if pynacl.platform.IsWindows():
     if len(path) > 255:
       raise Error('Path "%s" is too long (%d characters), and will fail.' % (
           path, len(path)))
@@ -115,61 +118,26 @@ def IsEnvFlagTrue(flag_name, default=False):
   flag_value = os.environ.get(flag_name)
   if flag_value is None:
     return default
-  return bool(re.search(flag_value, r'^([tTyY]|1:?)'))
-
-
-def GetGomaConfig(gomadir, osname, arch, toolname, is_pnacl_toolchain):
-  """Returns full-path of gomacc if goma is available or None."""
-  # Start goma support from os/arch/toolname that have been tested.
-  # Set NO_NACL_GOMA=true to force to avoid using goma.
-  if (osname not in ['linux', 'mac'] or arch not in ['x86-32', 'x86-64']
-      or toolname not in ['newlib', 'glibc']
-      or IsEnvFlagTrue('NO_NACL_GOMA', default=False)):
-    return {}
-  # TODO(yyanagisawa): should fix ambiguous executable selection on pnacl-clang.
-  if is_pnacl_toolchain:
-    return {}
-
-  goma_config = {}
-  try:
-    gomacc_base = 'gomacc.exe' if os.name == 'nt' else 'gomacc'
-    # Search order of gomacc:
-    # --gomadir command argument -> GOMA_DIR env. -> PATH env.
-    search_path = []
-    # 1. --gomadir in the command argument.
-    if gomadir:
-      search_path.append(gomadir)
-    # 2. Use GOMA_DIR environment variable if exist.
-    goma_dir_env = os.environ.get('GOMA_DIR')
-    if goma_dir_env:
-      search_path.append(gomadir_env)
-    # 3. Append PATH env.
-    path_env = os.environ.get('PATH')
-    if path_env:
-      search_path.extend(path_env.split(os.path.pathsep))
+  return bool(re.search(r'^([tTyY]|1:?)', flag_value))
 
-    for directory in search_path:
-      gomacc = os.path.join(directory, gomacc_base)
-      if os.path.isfile(gomacc):
-        port = subprocess.Popen(
-            [gomacc, 'port'], stdout=subprocess.PIPE).communicate()[0].strip()
-        if port:
-          status = urllib2.urlopen(
-              'http://127.0.0.1:%s/healthz' % port).read().strip()
-          if status == 'ok':
-            goma_config['gomacc'] = gomacc
-            break
-  except Exception:
-    # Anyway, fallbacks to non-goma mode.
-    pass
 
-  if goma_config:
-    default_value = False
-    if osname == 'linux':
-      default_value = True
-    goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST',
-                                         default=default_value)
-  return goma_config
+def GetIntegerEnv(flag_name, default=0):
+  """Parses and returns integer environment variable.
+
+  Args:
+    flag_name: a string name of a flag.
+    default: default return value if the flag is not set.
+
+  Returns:
+    Integer value of the flag.
+  """
+  flag_value = os.environ.get(flag_name)
+  if flag_value is None:
+    return default
+  try:
+    return int(flag_value)
+  except ValueError:
+    raise Error('Invalid ' + flag_name + ': ' + flag_value)
 
 
 class Builder(object):
@@ -184,23 +152,15 @@ class Builder(object):
     build_type = options.build.split('_')
     toolname = build_type[0]
     self.outtype = build_type[1]
-
-    if sys.platform.startswith('linux'):
-      self.osname = 'linux'
-    elif sys.platform.startswith('win'):
-      self.osname = 'win'
-    elif sys.platform.startswith('darwin'):
-      self.osname = 'mac'
-    else:
-      raise Error('Toolchain OS %s not supported.' % sys.platform)
+    self.osname = pynacl.platform.GetOS()
 
     # pnacl toolchain can be selected in three different ways
     # 1. by specifying --arch=pnacl directly to generate
     #    pexe targets.
     # 2. by specifying --build=newlib_translate to generated
     #    nexe via translation
-    # 3. by specifying --build=newlib_nexe_pnacl use pnacl
-    #    toolchain in arm-native mode (e.g. the arm IRT)
+    # 3. by specifying --build=newlib_{nexe,nlib}_pnacl use pnacl
+    #    toolchain in native mode (e.g. the IRT shim)
     self.is_pnacl_toolchain = False
     if self.outtype == 'translate':
       self.is_pnacl_toolchain = True
@@ -208,18 +168,18 @@ class Builder(object):
     if len(build_type) > 2 and build_type[2] == 'pnacl':
       self.is_pnacl_toolchain = True
 
+    if arch.endswith('-nonsfi'):
+      arch = arch[:-len('-nonsfi')]
+
     if arch in ['x86-32', 'x86-64']:
       mainarch = 'x86'
-      self.subarch = arch.split('-')[1]
       self.tool_prefix = 'x86_64-nacl-'
     elif arch == 'arm':
-      self.subarch = ''
       self.tool_prefix = 'arm-nacl-'
       mainarch = 'arm'
     elif arch == 'mips':
       self.is_pnacl_toolchain = True
     elif arch == 'pnacl':
-      self.subarch = ''
       self.is_pnacl_toolchain = True
     else:
       raise Error('Toolchain architecture %s not supported.' % arch)
@@ -238,9 +198,12 @@ class Builder(object):
 
     if self.is_pnacl_toolchain:
       self.tool_prefix = 'pnacl-'
-      tooldir = '%s_pnacl' % self.osname
+      tool_subdir = 'pnacl_newlib'
     else:
-      tooldir = '%s_%s_%s' % (self.osname, mainarch, toolname)
+      tool_subdir = 'nacl_%s_%s' % (mainarch, toolname)
+
+    build_arch = pynacl.platform.GetArch()
+    tooldir = os.path.join('%s_%s' % (self.osname, build_arch), tool_subdir)
 
     self.root_path = options.root
     self.nacl_path = os.path.join(self.root_path, 'native_client')
@@ -269,11 +232,16 @@ class Builder(object):
     self.empty = options.empty
     self.strip_all = options.strip_all
     self.strip_debug = options.strip_debug
+    self.tls_edit = options.tls_edit
     self.finalize_pexe = options.finalize_pexe and arch == 'pnacl'
-    goma_config = GetGomaConfig(options.gomadir, self.osname, arch, toolname,
-                                self.is_pnacl_toolchain)
+    goma_config = self.GetGomaConfig(options.gomadir, arch, toolname)
     self.gomacc = goma_config.get('gomacc', '')
     self.goma_burst = goma_config.get('burst', False)
+    self.goma_threads = goma_config.get('threads', 1)
+
+    # Define NDEBUG for Release builds.
+    if options.build_config == 'Release':
+      self.compile_options.append('-DNDEBUG')
 
     # Use unoptimized native objects for debug IRT builds for faster compiles.
     if (self.is_pnacl_toolchain
@@ -288,6 +256,27 @@ class Builder(object):
         # Also use fast translation because we are still translating libc/libc++
         self.link_options.append('-Wt,-O0')
 
+    self.irt_layout = options.irt_layout
+    if self.irt_layout:
+      # IRT constraints for auto layout.
+      # IRT text can only go up to 256MB. Addresses after that are for data.
+      # Reserve an extra page because:
+      # * sel_ldr requires a HLT sled at the end of the dynamic code area;
+      # * dynamic_load_test currently tests loading at the end of the dynamic
+      #   code area.
+      self.irt_text_max = 0x10000000 - 0x10000
+      # Data can only go up to the sandbox_top - sizeof(stack).
+      # NaCl allocates 16MB for the initial thread's stack (see
+      # NACL_DEFAULT_STACK_MAX in sel_ldr.h).
+      # Assume sandbox_top is 1GB, since even on x86-64 the limit would
+      # only be 2GB (rip-relative references can only be +/- 2GB).
+      sandbox_top = 0x40000000
+      self.irt_data_max = sandbox_top - (16 << 20)
+      # Initialize layout flags with "too-close-to-max" flags so that
+      # we can relax this later and get a tight fit.
+      self.link_options += [
+          '-Wl,-Ttext-segment=0x%x' % (self.irt_text_max - 0x10000),
+          '-Wl,-Trodata-segment=0x%x' % (self.irt_data_max - 0x10000)]
     self.Log('Compile options: %s' % self.compile_options)
     self.Log('Linker options: %s' % self.link_options)
 
@@ -325,6 +314,10 @@ class Builder(object):
     """Helper which returns objcopy path."""
     return self.GetBinName('objcopy')
 
+  def GetReadElf(self):
+    """Helper which returns readelf path."""
+    return self.GetBinName('readelf')
+
   def GetPnaclFinalize(self):
     """Helper which returns pnacl-finalize path."""
     assert self.is_pnacl_toolchain
@@ -375,13 +368,15 @@ class Builder(object):
                            define.startswith('NACL_WINDOWS=') or
                            define.startswith('NACL_OSX=') or
                            define.startswith('NACL_LINUX=') or
+                           define.startswith('NACL_ANDROID=') or
                            define == 'COMPONENT_BUILD' or
                            'WIN32' in define or
                            'WINDOWS' in define or
                            'WINVER' in define)]
     define_list.extend(['NACL_WINDOWS=0',
                         'NACL_OSX=0',
-                        'NACL_LINUX=0'])
+                        'NACL_LINUX=0',
+                        'NACL_ANDROID=0'])
     options += ['-D' + define for define in define_list]
     self.compile_options = options + ['-I' + name for name in self.inc_paths]
 
@@ -402,8 +397,12 @@ class Builder(object):
     if self.verbose:
       sys.stderr.write(str(msg) + '\n')
 
-  def Run(self, cmd_line, out):
-    """Helper which runs a command line."""
+  def Run(self, cmd_line, get_output=False, **kwargs):
+    """Helper which runs a command line.
+
+    Returns the error code if get_output is False.
+    Returns the output if get_output is True.
+    """
 
     # For POSIX style path on windows for POSIX based toolchain
     # (just for arguments, not for the path to the command itself)
@@ -417,18 +416,21 @@ class Builder(object):
 
     self.Log(' '.join(cmd_line))
     try:
-      if self.is_pnacl_toolchain:
+      runner = subprocess.call
+      if get_output:
+        runner = subprocess.check_output
+      if self.is_pnacl_toolchain and pynacl.platform.IsWindows():
         # PNaCl toolchain executable is a script, not a binary, so it doesn't
         # want to run on Windows without a shell
-        ecode = subprocess.call(' '.join(cmd_line), shell=True)
+        result = runner(' '.join(cmd_line), shell=True, **kwargs)
       else:
-        ecode = subprocess.call(cmd_line)
+        result = runner(cmd_line, **kwargs)
     except Exception as err:
       raise Error('%s\nFAILED: %s' % (' '.join(cmd_line), str(err)))
-
-    if temp_file is not None:
-      RemoveFile(temp_file.name)
-    return ecode
+    finally:
+      if temp_file is not None:
+        RemoveFile(temp_file.name)
+    return result
 
   def GetObjectName(self, src):
     if self.strip:
@@ -464,6 +466,57 @@ class Builder(object):
       path = os.path.normpath(os.path.join(self.toolchain, path[1:]))
     return path
 
+  def GetGomaConfig(self, gomadir, arch, toolname):
+    """Returns a goma config dictionary if goma is available or {}."""
+
+    # Start goma support from os/arch/toolname that have been tested.
+    # Set NO_NACL_GOMA=true to force to avoid using goma.
+    default_no_nacl_goma = True if pynacl.platform.IsWindows() else False
+    if (arch not in ['x86-32', 'x86-64', 'pnacl']
+        or toolname not in ['newlib', 'glibc']
+        or IsEnvFlagTrue('NO_NACL_GOMA', default=default_no_nacl_goma)):
+      return {}
+
+    goma_config = {}
+    gomacc_base = 'gomacc.exe' if pynacl.platform.IsWindows() else 'gomacc'
+    # Search order of gomacc:
+    # --gomadir command argument -> GOMA_DIR env. -> PATH env.
+    search_path = []
+    # 1. --gomadir in the command argument.
+    if gomadir:
+      search_path.append(gomadir)
+    # 2. Use GOMA_DIR environment variable if exist.
+    goma_dir_env = os.environ.get('GOMA_DIR')
+    if goma_dir_env:
+      search_path.append(goma_dir_env)
+    # 3. Append PATH env.
+    path_env = os.environ.get('PATH')
+    if path_env:
+      search_path.extend(path_env.split(os.path.pathsep))
+
+    for directory in search_path:
+      gomacc = os.path.join(directory, gomacc_base)
+      if os.path.isfile(gomacc):
+        try:
+          port = int(subprocess.Popen(
+              [gomacc, 'port'],
+              stdout=subprocess.PIPE).communicate()[0].strip())
+          status = urllib2.urlopen(
+              'http://127.0.0.1:%d/healthz' % port).read().strip()
+          if status == 'ok':
+            goma_config['gomacc'] = gomacc
+            break
+        except (OSError, ValueError, urllib2.URLError) as e:
+          # Try another gomacc in the search path.
+          self.Log('Strange gomacc %s found, try another one: %s' % (gomacc, e))
+
+    if goma_config:
+      goma_config['burst'] = IsEnvFlagTrue('NACL_GOMA_BURST')
+      default_threads = 100 if pynacl.platform.IsLinux() else 1
+      goma_config['threads'] = GetIntegerEnv('NACL_GOMA_THREADS',
+                                             default=default_threads)
+    return goma_config
+
   def NeedsRebuild(self, outd, out, src, rebuilt=False):
     if not IsFile(self.toolstamp):
       if rebuilt:
@@ -477,23 +530,33 @@ class Builder(object):
       if rebuilt:
         raise Error('Could not find output file %s.' % out)
       return True
-    stamp_tm = GetMTime(self.toolstamp)
-    out_tm = GetMTime(out)
-    outd_tm = GetMTime(outd)
-    src_tm = GetMTime(src)
-    if IsStale(out_tm, stamp_tm, rebuilt):
-      if rebuilt:
-        raise Error('Output %s is older than toolchain stamp %s' % (
-            out, self.toolstamp))
-      return True
-    if IsStale(out_tm, src_tm, rebuilt):
+
+    inputs = [__file__, self.toolstamp, src]
+    outputs = [out, outd]
+
+    # Find their timestamps if any.
+    input_times = [(GetMTime(f), f) for f in inputs]
+    output_times = [(GetMTime(f), f) for f in outputs]
+
+    # All inputs must exist.
+    missing_inputs = [p[1] for p in input_times if p[0] is None]
+    if missing_inputs:
+      raise Error('Missing inputs: %s' % str(missing_inputs))
+
+    # Rebuild if any outputs are missing.
+    missing_outputs = [p[1] for p in output_times if p[0] is None]
+    if missing_outputs:
       if rebuilt:
-        raise Error('Output %s is older than source %s.' % (out, src))
+        raise Error('Outputs missing after rebuild: %s' % str(missing_outputs))
       return True
 
-    if IsStale(outd_tm, src_tm, rebuilt):
+    newest_input = max(input_times)
+    oldest_output = min(output_times)
+
+    if IsStale(oldest_output[0], newest_input[0], rebuilt):
       if rebuilt:
-        raise Error('Dependency file is older than source %s.' % src)
+        raise Error('Output %s is older than toolchain stamp %s' % (
+            oldest_output[1], newest_input[1]))
       return True
 
     # Decode emitted makefile.
@@ -503,44 +566,49 @@ class Builder(object):
     deps = deps.replace('\\\n', ' ')
     deps = deps.replace('\n', '')
     # The dependencies are whitespace delimited following the first ':'
-    deps = deps.split(':', 1)[1]
+    # (that is not part of a windows drive letter)
+    deps = deps.split(':', 1)
+    if pynacl.platform.IsWindows() and len(deps[0]) == 1:
+      # The path has a drive letter, find the next ':'
+      deps = deps[1].split(':', 1)[1]
+    else:
+      deps = deps[1]
     deps = deps.split()
-    if sys.platform in ['win32', 'cygwin']:
+    if pynacl.platform.IsWindows():
       deps = [self.FixWindowsPath(d) for d in deps]
     # Check if any input has changed.
     for filename in deps:
       file_tm = GetMTime(filename)
-      if IsStale(out_tm, file_tm, rebuilt):
+      if IsStale(oldest_output[0], file_tm, rebuilt):
         if rebuilt:
           raise Error('Dependency %s is older than output %s.' % (
-              filename, out))
-        return True
-
-      if IsStale(outd_tm, file_tm, rebuilt):
-        if rebuilt:
-          raise Error('Dependency %s is older than dep file %s.' % (
-              filename, outd))
+              filename, oldest_output[1]))
         return True
     return False
 
   def Compile(self, src):
     """Compile the source with pre-determined options."""
 
+    compile_options = self.compile_options[:]
     _, ext = os.path.splitext(src)
     if ext in ['.c', '.S']:
       bin_name = self.GetCCompiler()
-      extra = ['-std=gnu99']
+      compile_options.append('-std=gnu99')
       if self.is_pnacl_toolchain and ext == '.S':
-        extra.append('-arch')
-        extra.append(self.arch)
+        compile_options.append('-arch')
+        compile_options.append(self.arch)
     elif ext in ['.cc', '.cpp']:
       bin_name = self.GetCXXCompiler()
-      extra = []
     else:
       if ext != '.h':
         self.Log('Skipping unknown type %s for %s.' % (ext, src))
       return None
 
+    # This option is only applicable to C, and C++ compilers warn if
+    # it is present, so remove it for C++ to avoid the warning.
+    if ext != '.c' and '-Wstrict-prototypes' in compile_options:
+      compile_options.remove('-Wstrict-prototypes')
+
     self.Log('\nCompile %s' % src)
 
     out = self.GetObjectName(src)
@@ -554,7 +622,7 @@ class Builder(object):
     self.CleanOutput(out)
     self.CleanOutput(outd)
     cmd_line = [bin_name, '-c', src, '-o', out,
-                '-MD', '-MF', outd] + extra + self.compile_options
+                '-MD', '-MF', outd] + compile_options
     if self.gomacc:
       cmd_line.insert(0, self.gomacc)
     err = self.Run(cmd_line, out)
@@ -570,22 +638,130 @@ class Builder(object):
                         src, out, outd, ' '.join(cmd_line), e))
     return out
 
+  def IRTLayoutFits(self, irt_file):
+    """Check if the IRT's data and text segment fit layout constraints.
+
+    Returns a tuple containing:
+      * whether the IRT data/text top addresses fit within the max limit
+      * current data/text top addrs
+    """
+    cmd_line = [self.GetReadElf(), '-W', '--segments', irt_file]
+    # Put LC_ALL=C in the environment for readelf, so that its messages
+    # will reliably match what we're looking for rather than being in some
+    # other language and/or character set.
+    env = dict(os.environ)
+    env['LC_ALL'] = 'C'
+    seginfo = self.Run(cmd_line, get_output=True, env=env)
+    lines = seginfo.splitlines()
+    ph_start = -1
+    for i, line in enumerate(lines):
+      if line == 'Program Headers:':
+        ph_start = i + 1
+        break
+    if ph_start == -1:
+      raise Error('Could not find Program Headers start: %s\n' % lines)
+    seg_lines = lines[ph_start:]
+    text_top = 0
+    data_top = 0
+    for line in seg_lines:
+      pieces = line.split()
+      # Type, Offset, Vaddr, Paddr, FileSz, MemSz, Flg(multiple), Align
+      if len(pieces) >= 8 and pieces[0] == 'LOAD':
+        # Vaddr + MemSz
+        segment_top = int(pieces[2], 16) + int(pieces[5], 16)
+        if pieces[6] == 'R' and pieces[7] == 'E':
+          text_top = max(segment_top, text_top)
+          continue
+        if pieces[6] == 'R' or pieces[6] == 'RW':
+          data_top = max(segment_top, data_top)
+          continue
+    if text_top == 0 or data_top == 0:
+      raise Error('Could not parse IRT Layout: text_top=0x%x data_top=0x%x\n'
+                  'readelf output: %s\n' % (text_top, data_top, lines))
+    return (text_top <= self.irt_text_max and
+            data_top <= self.irt_data_max), text_top, data_top
+
+  def FindOldIRTFlagPosition(self, cmd_line, flag_name):
+    """Search for a given IRT link flag's position and value."""
+    pos = -1
+    old_start = ''
+    for i, option in enumerate(cmd_line):
+      m = re.search('.*%s=(0x.*)' % flag_name, option)
+      if m:
+        if pos != -1:
+          raise Exception('Duplicate %s flag at position %d' % (flag_name, i))
+        pos = i
+        old_start = m.group(1)
+    if pos == -1:
+      raise Exception('Could not find IRT layout flag %s' % flag_name)
+    return pos, old_start
+
+  def AdjustIRTLinkToFit(self, cmd_line, text_top, data_top):
+    """Adjust the linker options so that the IRT's data and text segment fit."""
+    def RoundDownToAlign(x):
+      return x - (x % 0x10000)
+    def AdjustFlag(flag_name, orig_max, expected_max):
+      if orig_max < expected_max:
+        return
+      pos, old_start = self.FindOldIRTFlagPosition(cmd_line, flag_name)
+      size = orig_max - int(old_start, 16)
+      self.Log('IRT %s size is %s' % (flag_name, size))
+      new_start = RoundDownToAlign(expected_max - size)
+      self.Log('Adjusting link flag %s from %s to %s' % (flag_name,
+                                                         old_start,
+                                                         hex(new_start)))
+      cmd_line[pos] = cmd_line[pos].replace(old_start, hex(new_start))
+    AdjustFlag('-Ttext-segment', text_top, self.irt_text_max)
+    AdjustFlag('-Trodata-segment', data_top, self.irt_data_max)
+    self.Log('Adjusted link options to %s' % ' '.join(cmd_line))
+    return cmd_line
+
+  def RunLink(self, cmd_line, link_out):
+    self.CleanOutput(link_out)
+    err = self.Run(cmd_line, link_out)
+    if err:
+      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
+
   def Link(self, srcs):
     """Link these objects with predetermined options and output name."""
     out = self.LinkOutputName()
     self.Log('\nLink %s' % out)
     bin_name = self.GetCXXCompiler()
-    MakeDir(os.path.dirname(out))
-    self.CleanOutput(out)
 
-    cmd_line = [bin_name, '-o', out, '-Wl,--as-needed']
+    link_out = out
+    if self.tls_edit is not None:
+      link_out = out + '.raw'
+
+    MakeDir(os.path.dirname(link_out))
+
+    cmd_line = [bin_name, '-o', link_out, '-Wl,--as-needed']
     if not self.empty:
       cmd_line += srcs
     cmd_line += self.link_options
 
-    err = self.Run(cmd_line, out)
-    if err:
-      raise Error('FAILED with %d: %s' % (err, ' '.join(cmd_line)))
+    self.RunLink(cmd_line, link_out)
+
+    if self.irt_layout:
+      fits, text_top, data_top = self.IRTLayoutFits(link_out)
+      if not fits:
+        self.Log('IRT layout does not fit: text_top=0x%x and data_top=0x%x' %
+                 (text_top, data_top))
+        cmd_line = self.AdjustIRTLinkToFit(cmd_line, text_top, data_top)
+        self.RunLink(cmd_line, link_out)
+        fits, text_top, data_top = self.IRTLayoutFits(link_out)
+        if not fits:
+          raise Error('Already re-linked IRT and it still does not fit:\n'
+                      'text_top=0x%x and data_top=0x%x\n' % (
+                          text_top, data_top))
+      self.Log('IRT layout fits: text_top=0x%x and data_top=0x%x' %
+               (text_top, data_top))
+
+    if self.tls_edit is not None:
+      tls_edit_cmd = [FixPath(self.tls_edit), link_out, out]
+      tls_edit_err = self.Run(tls_edit_cmd, out)
+      if tls_edit_err:
+        raise Error('FAILED with %d: %s' % (err, ' '.join(tls_edit_cmd)))
+
     return out
 
   # For now, only support translating a pexe, and not .o file(s)
@@ -717,6 +893,11 @@ def Main(argv):
                     action='store_false')
   parser.add_option('--source-list', dest='source_list',
                     help='Filename to load a source list from')
+  parser.add_option('--tls-edit', dest='tls_edit', default=None,
+                    help='tls_edit location if TLS should be modified for IRT')
+  parser.add_option('--irt-layout', dest='irt_layout', default=False,
+                    help='Apply the IRT layout (pick data/text seg addresses)',
+                    action='store_true')
   parser.add_option('-a', '--arch', dest='arch',
                     help='Set target architecture')
   parser.add_option('-c', '--compile', dest='compile_only', default=False,
@@ -751,6 +932,17 @@ def Main(argv):
                     help='Path of the goma directory.')
   options, files = parser.parse_args(argv[1:])
 
+  if options.name is None:
+    parser.error('--name is required!')
+  if options.build_config is None:
+    parser.error('--config-name is required!')
+  if options.root is None:
+    parser.error('--root is required!')
+  if options.arch is None:
+    parser.error('--arch is required!')
+  if options.build is None:
+    parser.error('--build is required!')
+
   if not argv:
     parser.print_help()
     return 1
@@ -783,22 +975,41 @@ def Main(argv):
       build.Translate(list(files)[0])
       return 0
 
-    if build.gomacc and build.goma_burst:  # execute gomacc as many as possible.
+    if build.gomacc and (build.goma_burst or build.goma_threads > 1):
       returns = Queue.Queue()
-      def CompileThread(filename, queue):
+
+      # Push all files into the inputs queue
+      inputs = Queue.Queue()
+      for filename in files:
+        inputs.put(filename)
+
+      def CompileThread(input_queue, output_queue):
         try:
-          queue.put(build.Compile(filename))
+          while True:
+            try:
+              filename = input_queue.get_nowait()
+            except Queue.Empty:
+              return
+            output_queue.put(build.Compile(filename))
         except Exception:
           # Put current exception info to the queue.
-          queue.put(sys.exc_info())
-      build_threads = []
+          output_queue.put(sys.exc_info())
+
+      # Don't limit number of threads in the burst mode.
+      if build.goma_burst:
+        num_threads = len(files)
+      else:
+        num_threads = min(build.goma_threads, len(files))
+
       # Start parallel build.
-      for filename in files:
-        thr = threading.Thread(target=CompileThread, args=(filename, returns))
+      build_threads = []
+      for _ in xrange(num_threads):
+        thr = threading.Thread(target=CompileThread, args=(inputs, returns))
         thr.start()
         build_threads.append(thr)
-      for thr in build_threads:
-        thr.join()
+
+      # Wait for results.
+      for _ in files:
         out = returns.get()
         # An exception raised in the thread may come through the queue.
         # Raise it again here.
@@ -807,6 +1018,15 @@ def Main(argv):
           raise out[0], None, out[2]
         elif out:
           objs.append(out)
+
+      assert inputs.empty()
+
+      # Wait until all threads have stopped and verify that there are no more
+      # results.
+      for thr in build_threads:
+        thr.join()
+      assert returns.empty()
+
     else:  # slow path.
       for filename in files:
         out = build.Compile(filename)