1# Copyright 2011 The Emscripten Authors. All rights reserved. 2# Emscripten is available under two separate licenses, the MIT license and the 3# University of Illinois/NCSA Open Source License. Both these licenses can be 4# found in the LICENSE file. 5 6from __future__ import print_function 7 8from subprocess import PIPE 9import atexit 10import binascii 11import base64 12import difflib 13import json 14import logging 15import math 16import os 17import re 18import shutil 19import subprocess 20import time 21import sys 22import tempfile 23 24if sys.version_info < (3, 5): 25 print('error: emscripten requires python 3.5 or above', file=sys.stderr) 26 sys.exit(1) 27 28from .toolchain_profiler import ToolchainProfiler 29from .tempfiles import try_delete 30from . import cache, tempfiles, colored_logger 31from . import diagnostics 32 33 34__rootpath__ = '/usr/local/lib/emscripten' 35WINDOWS = sys.platform.startswith('win') 36MACOS = sys.platform == 'darwin' 37LINUX = sys.platform.startswith('linux') 38DEBUG = int(os.environ.get('EMCC_DEBUG', '0')) 39EXPECTED_NODE_VERSION = (4, 1, 1) 40EXPECTED_BINARYEN_VERSION = 95 41EXPECTED_LLVM_VERSION = "12.0" 42SIMD_INTEL_FEATURE_TOWER = ['-msse', '-msse2', '-msse3', '-mssse3', '-msse4.1', '-msse4.2', '-mavx'] 43SIMD_NEON_FLAGS = ['-mfpu=neon'] 44 45# can add %(asctime)s to see timestamps 46logging.basicConfig(format='%(name)s:%(levelname)s: %(message)s', 47 level=logging.DEBUG if DEBUG else logging.INFO) 48colored_logger.enable() 49logger = logging.getLogger('shared') 50 51if sys.version_info < (2, 7, 12): 52 logger.debug('python versions older than 2.7.12 are known to run into outdated SSL certificate related issues, https://github.com/emscripten-core/emscripten/issues/6275') 53 54# warning about absolute-paths is disabled by default, and not enabled by -Wall 55diagnostics.add_warning('absolute-paths', enabled=False, part_of_all=False) 56diagnostics.add_warning('almost-asm') 57diagnostics.add_warning('invalid-input') 58# Don't show legacy settings warnings by default 59diagnostics.add_warning('legacy-settings', enabled=False, part_of_all=False) 60# Catch-all for other emcc warnings 61diagnostics.add_warning('linkflags') 62diagnostics.add_warning('emcc') 63diagnostics.add_warning('undefined', error=True) 64diagnostics.add_warning('deprecated') 65diagnostics.add_warning('version-check') 66diagnostics.add_warning('export-main') 67diagnostics.add_warning('unused-command-line-argument', shared=True) 68diagnostics.add_warning('pthreads-mem-growth') 69 70 71def exit_with_error(msg, *args): 72 diagnostics.error(msg, *args) 73 74 75# On Windows python suffers from a particularly nasty bug if python is spawning 76# new processes while python itself is spawned from some other non-console 77# process. 78# Use a custom replacement for Popen on Windows to avoid the "WindowsError: 79# [Error 6] The handle is invalid" errors when emcc is driven through cmake or 80# mingw32-make. 81# See http://bugs.python.org/issue3905 82class WindowsPopen(object): 83 def __init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, 84 shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0): 85 self.stdin = stdin 86 self.stdout = stdout 87 self.stderr = stderr 88 89 # (stdin, stdout, stderr) store what the caller originally wanted to be done with the streams. 90 # (stdin_, stdout_, stderr_) will store the fixed set of streams that workaround the bug. 91 self.stdin_ = stdin 92 self.stdout_ = stdout 93 self.stderr_ = stderr 94 95 # If the caller wants one of these PIPEd, we must PIPE them all to avoid the 'handle is invalid' bug. 96 if self.stdin_ == PIPE or self.stdout_ == PIPE or self.stderr_ == PIPE: 97 if self.stdin_ is None: 98 self.stdin_ = PIPE 99 if self.stdout_ is None: 100 self.stdout_ = PIPE 101 if self.stderr_ is None: 102 self.stderr_ = PIPE 103 104 try: 105 # Call the process with fixed streams. 106 self.process = subprocess.Popen(args, bufsize, executable, self.stdin_, self.stdout_, self.stderr_, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags) 107 self.pid = self.process.pid 108 except Exception as e: 109 logger.error('\nsubprocess.Popen(args=%s) failed! Exception %s\n' % (shlex_join(args), str(e))) 110 raise 111 112 def communicate(self, input=None): 113 output = self.process.communicate(input) 114 self.returncode = self.process.returncode 115 116 # If caller never wanted to PIPE stdout or stderr, route the output back to screen to avoid swallowing output. 117 if self.stdout is None and self.stdout_ == PIPE and len(output[0].strip()): 118 print(output[0], file=sys.stdout) 119 if self.stderr is None and self.stderr_ == PIPE and len(output[1].strip()): 120 print(output[1], file=sys.stderr) 121 122 # Return a mock object to the caller. This works as long as all emscripten code immediately .communicate()s the result, and doesn't 123 # leave the process object around for longer/more exotic uses. 124 if self.stdout is None and self.stderr is None: 125 return (None, None) 126 if self.stdout is None: 127 return (None, output[1]) 128 if self.stderr is None: 129 return (output[0], None) 130 return (output[0], output[1]) 131 132 def poll(self): 133 return self.process.poll() 134 135 def kill(self): 136 return self.process.kill() 137 138 139def path_from_root(*pathelems): 140 return os.path.join(__rootpath__, *pathelems) 141 142 143def root_is_writable(): 144 return os.access(__rootpath__, os.W_OK) 145 146 147# Switch to shlex.quote once we can depend on python 3 148def shlex_quote(arg): 149 if ' ' in arg and (not (arg.startswith('"') and arg.endswith('"'))) and (not (arg.startswith("'") and arg.endswith("'"))): 150 return '"' + arg.replace('"', '\\"') + '"' 151 152 return arg 153 154 155# Switch to shlex.join once we can depend on python 3.8: 156# https://docs.python.org/3/library/shlex.html#shlex.join 157def shlex_join(cmd): 158 return ' '.join(shlex_quote(x) for x in cmd) 159 160 161# This is a workaround for https://bugs.python.org/issue9400 162class Py2CalledProcessError(subprocess.CalledProcessError): 163 def __init__(self, returncode, cmd, output=None, stderr=None): 164 super(Exception, self).__init__(returncode, cmd, output, stderr) 165 self.returncode = returncode 166 self.cmd = cmd 167 self.output = output 168 self.stderr = stderr 169 170 171# https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess 172class Py2CompletedProcess: 173 def __init__(self, args, returncode, stdout, stderr): 174 self.args = args 175 self.returncode = returncode 176 self.stdout = stdout 177 self.stderr = stderr 178 179 def __repr__(self): 180 _repr = ['args=%s' % repr(self.args), 'returncode=%s' % self.returncode] 181 if self.stdout is not None: 182 _repr.append('stdout=' + repr(self.stdout)) 183 if self.stderr is not None: 184 _repr.append('stderr=' + repr(self.stderr)) 185 return 'CompletedProcess(%s)' % ', '.join(_repr) 186 187 def check_returncode(self): 188 if self.returncode != 0: 189 raise Py2CalledProcessError(returncode=self.returncode, cmd=self.args, output=self.stdout, stderr=self.stderr) 190 191 192def run_process(cmd, check=True, input=None, *args, **kw): 193 """Runs a subpocess returning the exit code. 194 195 By default this function will raise an exception on failure. Therefor this should only be 196 used if you want to handle such failures. For most subprocesses, failures are not recoverable 197 and should be fatal. In those cases the `check_call` wrapper should be preferred. 198 """ 199 200 kw.setdefault('universal_newlines', True) 201 202 debug_text = '%sexecuted %s' % ('successfully ' if check else '', shlex_join(cmd)) 203 204 if hasattr(subprocess, "run"): 205 ret = subprocess.run(cmd, check=check, input=input, *args, **kw) 206 logger.debug(debug_text) 207 return ret 208 209 # Python 2 compatibility: Introduce Python 3 subprocess.run-like behavior 210 if input is not None: 211 kw['stdin'] = subprocess.PIPE 212 proc = Popen(cmd, *args, **kw) 213 stdout, stderr = proc.communicate(input) 214 result = Py2CompletedProcess(cmd, proc.returncode, stdout, stderr) 215 if check: 216 result.check_returncode() 217 logger.debug(debug_text) 218 return result 219 220 221def check_call(cmd, *args, **kw): 222 """Like `run_process` above but treat failures as fatal and exit_with_error.""" 223 try: 224 return run_process(cmd, *args, **kw) 225 except subprocess.CalledProcessError as e: 226 exit_with_error("'%s' failed (%d)", shlex_join(cmd), e.returncode) 227 except OSError as e: 228 exit_with_error("'%s' failed: %s", shlex_join(cmd), str(e)) 229 230 231def run_js_tool(filename, jsargs=[], *args, **kw): 232 """Execute a javascript tool. 233 234 This is used by emcc to run parts of the build process that are written 235 implemented in javascript. 236 """ 237 command = NODE_JS + [filename] + jsargs 238 print_compiler_stage(command) 239 return check_call(command, *args, **kw).stdout 240 241 242# Finds the given executable 'program' in PATH. Operates like the Unix tool 'which'. 243def which(program): 244 def is_exe(fpath): 245 return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 246 247 if os.path.isabs(program): 248 if os.path.isfile(program): 249 return program 250 251 if WINDOWS: 252 for suffix in ['.exe', '.cmd', '.bat']: 253 if is_exe(program + suffix): 254 return program + suffix 255 256 fpath, fname = os.path.split(program) 257 if fpath: 258 if is_exe(program): 259 return program 260 else: 261 for path in os.environ["PATH"].split(os.pathsep): 262 path = path.strip('"') 263 exe_file = os.path.join(path, program) 264 if is_exe(exe_file): 265 return exe_file 266 if WINDOWS: 267 for suffix in ('.exe', '.cmd', '.bat'): 268 if is_exe(exe_file + suffix): 269 return exe_file + suffix 270 271 return None 272 273 274# Only used by tests and by ctor_evaller.py. Once fastcomp is removed 275# this can most likely be moved into the tests/jsrun.py. 276def timeout_run(proc, timeout=None, full_output=False, check=True): 277 start = time.time() 278 if timeout is not None: 279 while time.time() - start < timeout and proc.poll() is None: 280 time.sleep(0.1) 281 if proc.poll() is None: 282 proc.kill() # XXX bug: killing emscripten.py does not kill it's child process! 283 raise Exception("Timed out") 284 stdout, stderr = proc.communicate() 285 out = ['' if o is None else o for o in (stdout, stderr)] 286 if check and proc.returncode != 0: 287 raise subprocess.CalledProcessError(proc.returncode, '', stdout, stderr) 288 if TRACK_PROCESS_SPAWNS: 289 logging.info('Process ' + str(proc.pid) + ' finished after ' + str(time.time() - start) + ' seconds. Exit code: ' + str(proc.returncode)) 290 return '\n'.join(out) if full_output else out[0] 291 292 293def generate_config(path, first_time=False): 294 # Note: repr is used to ensure the paths are escaped correctly on Windows. 295 # The full string is replaced so that the template stays valid Python. 296 config_file = open(path_from_root('tools', 'settings_template.py')).read().splitlines() 297 config_file = config_file[3:] # remove the initial comment 298 config_file = '\n'.join(config_file) 299 # autodetect some default paths 300 config_file = config_file.replace('\'{{{ EMSCRIPTEN_ROOT }}}\'', repr(EMSCRIPTEN_ROOT)) 301 llvm_root = os.path.dirname(which('llvm-dis') or '/usr/bin/llvm-dis') 302 config_file = config_file.replace('\'{{{ LLVM_ROOT }}}\'', repr(llvm_root)) 303 304 node = which('nodejs') or which('node') or 'node' 305 config_file = config_file.replace('\'{{{ NODE }}}\'', repr(node)) 306 307 abspath = os.path.abspath(os.path.expanduser(path)) 308 # write 309 with open(abspath, 'w') as f: 310 f.write(config_file) 311 312 if first_time: 313 print(''' 314============================================================================== 315Welcome to Emscripten! 316 317This is the first time any of the Emscripten tools has been run. 318 319A settings file has been copied to %s, at absolute path: %s 320 321It contains our best guesses for the important paths, which are: 322 323 LLVM_ROOT = %s 324 NODE_JS = %s 325 EMSCRIPTEN_ROOT = %s 326 327Please edit the file if any of those are incorrect. 328 329This command will now exit. When you are done editing those paths, re-run it. 330============================================================================== 331''' % (path, abspath, llvm_root, node, EMSCRIPTEN_ROOT), file=sys.stderr) 332 333 334def parse_config_file(): 335 """Parse the emscripten config file using python's exec. 336 337 Also also EM_<KEY> environment variables to override specific config keys. 338 """ 339 config = {} 340 config_text = open(CONFIG_FILE, 'r').read() if CONFIG_FILE else EM_CONFIG 341 try: 342 exec(config_text, config) 343 except Exception as e: 344 exit_with_error('Error in evaluating %s (at %s): %s, text: %s', EM_CONFIG, CONFIG_FILE, str(e), config_text) 345 346 CONFIG_KEYS = ( 347 'NODE_JS', 348 'BINARYEN_ROOT', 349 'POPEN_WORKAROUND', 350 'SPIDERMONKEY_ENGINE', 351 'V8_ENGINE', 352 'LLVM_ROOT', 353 'LLVM_ADD_VERSION', 354 'CLANG_ADD_VERSION', 355 'CLOSURE_COMPILER', 356 'JAVA', 357 'JS_ENGINE', 358 'JS_ENGINES', 359 'WASMER', 360 'WASMTIME', 361 'WASM_ENGINES', 362 'FROZEN_CACHE', 363 'CACHE', 364 'PORTS', 365 ) 366 367 # Only propagate certain settings from the config file. 368 for key in CONFIG_KEYS: 369 env_var = 'EM_' + key 370 env_value = os.environ.get(env_var) 371 if env_value is not None: 372 globals()[key] = env_value 373 elif key in config: 374 globals()[key] = config[key] 375 376 # Certain keys are mandatory 377 for key in ('LLVM_ROOT', 'NODE_JS', 'BINARYEN_ROOT'): 378 if key not in config: 379 exit_with_error('%s is not defined in %s', key, config_file_location()) 380 if not globals()[key]: 381 exit_with_error('%s is set to empty value in %s', key, config_file_location()) 382 383 if not NODE_JS: 384 exit_with_error('NODE_JS is not defined in %s', config_file_location()) 385 386 387def listify(x): 388 if type(x) is not list: 389 return [x] 390 return x 391 392 393def fix_js_engine(old, new): 394 global JS_ENGINES 395 if old is None: 396 return 397 JS_ENGINES = [new if x == old else x for x in JS_ENGINES] 398 return new 399 400 401def get_npm_cmd(name): 402 if WINDOWS: 403 cmd = [path_from_root('node_modules', '.bin', name + '.cmd')] 404 else: 405 cmd = NODE_JS + [path_from_root('node_modules', '.bin', name)] 406 if not os.path.exists(cmd[-1]): 407 exit_with_error('%s was not found! Please run "npm install" in Emscripten root directory to set up npm dependencies' % name) 408 return cmd 409 410 411def normalize_config_settings(): 412 global CACHE, PORTS, JAVA 413 global NODE_JS, V8_ENGINE, JS_ENGINE, JS_ENGINES, SPIDERMONKEY_ENGINE, WASM_ENGINES 414 415 # EM_CONFIG stuff 416 if not JS_ENGINES: 417 JS_ENGINES = [NODE_JS] 418 if not JS_ENGINE: 419 JS_ENGINE = JS_ENGINES[0] 420 421 # Engine tweaks 422 if SPIDERMONKEY_ENGINE: 423 new_spidermonkey = SPIDERMONKEY_ENGINE 424 if '-w' not in str(new_spidermonkey): 425 new_spidermonkey += ['-w'] 426 SPIDERMONKEY_ENGINE = fix_js_engine(SPIDERMONKEY_ENGINE, new_spidermonkey) 427 NODE_JS = fix_js_engine(NODE_JS, listify(NODE_JS)) 428 V8_ENGINE = fix_js_engine(V8_ENGINE, listify(V8_ENGINE)) 429 JS_ENGINE = fix_js_engine(JS_ENGINE, listify(JS_ENGINE)) 430 JS_ENGINES = [listify(engine) for engine in JS_ENGINES] 431 WASM_ENGINES = [listify(engine) for engine in WASM_ENGINES] 432 if not CACHE: 433 if root_is_writable(): 434 CACHE = path_from_root('cache') 435 else: 436 # Use the legacy method of putting the cache in the user's home directory 437 # if the emscripten root is not writable. 438 # This is useful mostly for read-only installation and perhaps could 439 # be removed in the future since such installations should probably be 440 # setting a specific cache location. 441 logger.debug('Using home-directory for emscripten cache due to read-only root') 442 CACHE = os.path.expanduser(os.path.join('~', '.emscripten_cache')) 443 if not PORTS: 444 PORTS = os.path.join(CACHE, 'ports') 445 446 if JAVA is None: 447 logger.debug('JAVA not defined in ' + config_file_location() + ', using "java"') 448 JAVA = 'java' 449 450 451# Returns the location of the emscripten config file. 452def config_file_location(): 453 # Handle the case where there is no config file at all (i.e. If EM_CONFIG is passed as python code 454 # direclty on the command line). 455 if not CONFIG_FILE: 456 return '<inline config>' 457 458 return CONFIG_FILE 459 460 461def get_clang_version(): 462 if not hasattr(get_clang_version, 'found_version'): 463 if not os.path.exists(CLANG_CC): 464 exit_with_error('clang executable not found at `%s`' % CLANG_CC) 465 proc = check_call([CLANG_CC, '--version'], stdout=PIPE) 466 m = re.search(r'[Vv]ersion\s+(\d+\.\d+)', proc.stdout) 467 get_clang_version.found_version = m and m.group(1) 468 return get_clang_version.found_version 469 470 471def check_llvm_version(): 472 actual = get_clang_version() 473 if EXPECTED_LLVM_VERSION in actual: 474 return True 475 diagnostics.warning('version-check', 'LLVM version appears incorrect (seeing "%s", expected "%s")', actual, EXPECTED_LLVM_VERSION) 476 return False 477 478 479def get_llc_targets(): 480 if not os.path.exists(LLVM_COMPILER): 481 exit_with_error('llc executable not found at `%s`' % LLVM_COMPILER) 482 try: 483 llc_version_info = run_process([LLVM_COMPILER, '--version'], stdout=PIPE).stdout 484 except subprocess.CalledProcessError: 485 exit_with_error('error running `llc --version`. Check your llvm installation (%s)' % LLVM_COMPILER) 486 if 'Registered Targets:' not in llc_version_info: 487 exit_with_error('error parsing output of `llc --version`. Check your llvm installation (%s)' % LLVM_COMPILER) 488 pre, targets = llc_version_info.split('Registered Targets:') 489 return targets 490 491 492def check_llvm(): 493 targets = get_llc_targets() 494 if 'wasm32' not in targets and 'WebAssembly 32-bit' not in targets: 495 logger.critical('LLVM has not been built with the WebAssembly backend, llc reports:') 496 print('===========================================================================', file=sys.stderr) 497 print(targets, file=sys.stderr) 498 print('===========================================================================', file=sys.stderr) 499 return False 500 501 return True 502 503 504def get_node_directory(): 505 return os.path.dirname(NODE_JS[0] if type(NODE_JS) is list else NODE_JS) 506 507 508# When we run some tools from npm (closure, html-minifier-terser), those 509# expect that the tools have node.js accessible in PATH. Place our node 510# there when invoking those tools. 511def env_with_node_in_path(): 512 env = os.environ.copy() 513 env['PATH'] = get_node_directory() + os.pathsep + env['PATH'] 514 return env 515 516 517def check_node_version(): 518 try: 519 actual = run_process(NODE_JS + ['--version'], stdout=PIPE).stdout.strip() 520 version = tuple(map(int, actual.replace('v', '').replace('-pre', '').split('.'))) 521 except Exception as e: 522 diagnostics.warning('version-check', 'cannot check node version: %s', e) 523 return False 524 525 if version < EXPECTED_NODE_VERSION: 526 diagnostics.warning('version-check', 'node version appears too old (seeing "%s", expected "%s")', actual, 'v' + ('.'.join(map(str, EXPECTED_NODE_VERSION)))) 527 return False 528 529 return True 530 531 532def set_version_globals(): 533 global EMSCRIPTEN_VERSION, EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY 534 filename = path_from_root('emscripten-version.txt') 535 with open(filename) as f: 536 EMSCRIPTEN_VERSION = f.read().strip().replace('"', '') 537 parts = [int(x) for x in EMSCRIPTEN_VERSION.split('.')] 538 EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY = parts 539 540 541def generate_sanity(): 542 sanity_file_content = EMSCRIPTEN_VERSION + '|' + LLVM_ROOT + '|' + get_clang_version() 543 if CONFIG_FILE: 544 config = open(CONFIG_FILE).read() 545 else: 546 config = EM_CONFIG 547 # Convert to unsigned for python2 and python3 compat 548 checksum = binascii.crc32(config.encode()) & 0xffffffff 549 sanity_file_content += '|%#x\n' % checksum 550 return sanity_file_content 551 552 553def perform_sanify_checks(): 554 logger.info('(Emscripten: Running sanity checks)') 555 556 with ToolchainProfiler.profile_block('sanity compiler_engine'): 557 try: 558 run_process(NODE_JS + ['-e', 'console.log("hello")'], stdout=PIPE) 559 except Exception as e: 560 exit_with_error('The configured node executable (%s) does not seem to work, check the paths in %s (%s)', NODE_JS, config_file_location, str(e)) 561 562 with ToolchainProfiler.profile_block('sanity LLVM'): 563 for cmd in [CLANG_CC, LLVM_AR, LLVM_AS, LLVM_NM]: 564 if not os.path.exists(cmd) and not os.path.exists(cmd + '.exe'): # .exe extension required for Windows 565 exit_with_error('Cannot find %s, check the paths in %s', cmd, EM_CONFIG) 566 567 568def check_sanity(force=False): 569 """Check that basic stuff we need (a JS engine to compile, Node.js, and Clang 570 and LLVM) exists. 571 572 The test runner always does this check (through |force|). emcc does this less 573 frequently, only when ${EM_CONFIG}_sanity does not exist or is older than 574 EM_CONFIG (so, we re-check sanity when the settings are changed). We also 575 re-check sanity and clear the cache when the version changes. 576 """ 577 if not force and os.environ.get('EMCC_SKIP_SANITY_CHECK') == '1': 578 return 579 # We set EMCC_SKIP_SANITY_CHECK so that any subprocesses that we launch will 580 # not re-run the tests. 581 os.environ['EMCC_SKIP_SANITY_CHECK'] = '1' 582 with ToolchainProfiler.profile_block('sanity'): 583 check_llvm_version() 584 if not CONFIG_FILE: 585 return # config stored directly in EM_CONFIG => skip sanity checks 586 expected = generate_sanity() 587 588 sanity_file = Cache.get_path('sanity.txt', root=True) 589 if os.path.exists(sanity_file): 590 sanity_data = open(sanity_file).read() 591 if sanity_data != expected: 592 logger.debug('old sanity: %s' % sanity_data) 593 logger.debug('new sanity: %s' % expected) 594 if FROZEN_CACHE: 595 logger.info('(Emscripten: config changed, cache may need to be cleared, but FROZEN_CACHE is set)') 596 else: 597 logger.info('(Emscripten: config changed, clearing cache)') 598 Cache.erase() 599 # the check actually failed, so definitely write out the sanity file, to 600 # avoid others later seeing failures too 601 force = False 602 elif not force: 603 return # all is well 604 605 # some warning, mostly not fatal checks - do them even if EM_IGNORE_SANITY is on 606 check_node_version() 607 608 llvm_ok = check_llvm() 609 610 if os.environ.get('EM_IGNORE_SANITY'): 611 logger.info('EM_IGNORE_SANITY set, ignoring sanity checks') 612 return 613 614 if not llvm_ok: 615 exit_with_error('failing sanity checks due to previous llvm failure') 616 617 perform_sanify_checks() 618 619 if not force: 620 # Only create/update this file if the sanity check succeeded, i.e., we got here 621 Cache.ensure() 622 with open(sanity_file, 'w') as f: 623 f.write(expected) 624 625 626# Some distributions ship with multiple llvm versions so they add 627# the version to the binaries, cope with that 628def build_llvm_tool_path(tool): 629 if LLVM_ADD_VERSION: 630 return os.path.join(LLVM_ROOT, tool + "-" + LLVM_ADD_VERSION) 631 else: 632 return os.path.join(LLVM_ROOT, tool) 633 634 635# Some distributions ship with multiple clang versions so they add 636# the version to the binaries, cope with that 637def build_clang_tool_path(tool): 638 if CLANG_ADD_VERSION: 639 return os.path.join(LLVM_ROOT, tool + "-" + CLANG_ADD_VERSION) 640 else: 641 return os.path.join(LLVM_ROOT, tool) 642 643 644def exe_suffix(cmd): 645 return cmd + '.exe' if WINDOWS else cmd 646 647 648def bat_suffix(cmd): 649 return cmd + '.bat' if WINDOWS else cmd 650 651 652def replace_suffix(filename, new_suffix): 653 assert new_suffix[0] == '.' 654 return os.path.splitext(filename)[0] + new_suffix 655 656 657# In MINIMAL_RUNTIME mode, keep suffixes of generated files simple 658# ('.mem' instead of '.js.mem'; .'symbols' instead of '.js.symbols' etc) 659# Retain the original naming scheme in traditional runtime. 660def replace_or_append_suffix(filename, new_suffix): 661 assert new_suffix[0] == '.' 662 return replace_suffix(filename, new_suffix) if Settings.MINIMAL_RUNTIME else filename + new_suffix 663 664 665def safe_ensure_dirs(dirname): 666 try: 667 os.makedirs(dirname) 668 except OSError: 669 # Python 2 compatibility: makedirs does not support exist_ok parameter 670 # Ignore error for already existing dirname as exist_ok does 671 if not os.path.isdir(dirname): 672 raise 673 674 675# Temp dir. Create a random one, unless EMCC_DEBUG is set, in which case use the canonical 676# temp directory (TEMP_DIR/emscripten_temp). 677def get_emscripten_temp_dir(): 678 """Returns a path to EMSCRIPTEN_TEMP_DIR, creating one if it didn't exist.""" 679 global configuration, EMSCRIPTEN_TEMP_DIR 680 if not EMSCRIPTEN_TEMP_DIR: 681 EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_', dir=configuration.TEMP_DIR) 682 683 def prepare_to_clean_temp(d): 684 def clean_temp(): 685 try_delete(d) 686 687 atexit.register(clean_temp) 688 prepare_to_clean_temp(EMSCRIPTEN_TEMP_DIR) # this global var might change later 689 return EMSCRIPTEN_TEMP_DIR 690 691 692def get_canonical_temp_dir(temp_dir): 693 return os.path.join(temp_dir, 'emscripten_temp') 694 695 696class Configuration(object): 697 def __init__(self): 698 self.EMSCRIPTEN_TEMP_DIR = None 699 700 self.TEMP_DIR = os.environ.get("EMCC_TEMP_DIR", tempfile.gettempdir()) 701 if not os.path.isdir(self.TEMP_DIR): 702 exit_with_error("The temporary directory `" + self.TEMP_DIR + "` does not exist! Please make sure that the path is correct.") 703 704 self.CANONICAL_TEMP_DIR = get_canonical_temp_dir(self.TEMP_DIR) 705 706 if DEBUG: 707 self.EMSCRIPTEN_TEMP_DIR = self.CANONICAL_TEMP_DIR 708 try: 709 safe_ensure_dirs(self.EMSCRIPTEN_TEMP_DIR) 710 except Exception as e: 711 exit_with_error(str(e) + 'Could not create canonical temp dir. Check definition of TEMP_DIR in ' + config_file_location()) 712 713 def get_temp_files(self): 714 return tempfiles.TempFiles( 715 tmp=self.TEMP_DIR if not DEBUG else get_emscripten_temp_dir(), 716 save_debug_files=os.environ.get('EMCC_DEBUG_SAVE')) 717 718 719def apply_configuration(): 720 global configuration, EMSCRIPTEN_TEMP_DIR, CANONICAL_TEMP_DIR, TEMP_DIR 721 configuration = Configuration() 722 EMSCRIPTEN_TEMP_DIR = configuration.EMSCRIPTEN_TEMP_DIR 723 CANONICAL_TEMP_DIR = configuration.CANONICAL_TEMP_DIR 724 TEMP_DIR = configuration.TEMP_DIR 725 726 727def get_llvm_target(): 728 return LLVM_TARGET 729 730 731def emsdk_ldflags(user_args): 732 if os.environ.get('EMMAKEN_NO_SDK'): 733 return [] 734 735 library_paths = [ 736 path_from_root('system', 'local', 'lib'), 737 path_from_root('system', 'lib'), 738 Cache.dirname 739 ] 740 ldflags = ['-L' + l for l in library_paths] 741 742 if '-nostdlib' in user_args: 743 return ldflags 744 745 # TODO(sbc): Add system libraries here rather than conditionally including 746 # them via .symbols files. 747 libraries = [] 748 ldflags += ['-l' + l for l in libraries] 749 750 return ldflags 751 752 753def emsdk_cflags(user_args, cxx): 754 # Disable system C and C++ include directories, and add our own (using 755 # -isystem so they are last, like system dirs, which allows projects to 756 # override them) 757 758 c_opts = ['-Xclang', '-nostdsysteminc'] 759 760 c_include_paths = [ 761 path_from_root('system', 'include', 'compat'), 762 path_from_root('system', 'include'), 763 path_from_root('system', 'include', 'libc'), 764 path_from_root('system', 'lib', 'libc', 'musl', 'arch', 'emscripten'), 765 path_from_root('system', 'local', 'include'), 766 path_from_root('system', 'include', 'SSE'), 767 path_from_root('system', 'include', 'neon'), 768 path_from_root('system', 'lib', 'compiler-rt', 'include'), 769 path_from_root('system', 'lib', 'libunwind', 'include'), 770 Cache.get_path('include') 771 ] 772 773 cxx_include_paths = [ 774 path_from_root('system', 'include', 'libcxx'), 775 path_from_root('system', 'lib', 'libcxxabi', 'include'), 776 ] 777 778 def include_directive(paths): 779 result = [] 780 for path in paths: 781 result += ['-Xclang', '-isystem' + path] 782 return result 783 784 def array_contains_any_of(hay, needles): 785 for n in needles: 786 if n in hay: 787 return True 788 789 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER) or array_contains_any_of(user_args, SIMD_NEON_FLAGS): 790 if '-msimd128' not in user_args: 791 exit_with_error('Passing any of ' + ', '.join(SIMD_INTEL_FEATURE_TOWER + SIMD_NEON_FLAGS) + ' flags also requires passing -msimd128!') 792 c_opts += ['-D__SSE__=1'] 793 794 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[1:]): 795 c_opts += ['-D__SSE2__=1'] 796 797 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[2:]): 798 c_opts += ['-D__SSE3__=1'] 799 800 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[3:]): 801 c_opts += ['-D__SSSE3__=1'] 802 803 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[4:]): 804 c_opts += ['-D__SSE4_1__=1'] 805 806 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[5:]): 807 c_opts += ['-D__SSE4_2__=1'] 808 809 if array_contains_any_of(user_args, SIMD_INTEL_FEATURE_TOWER[6:]): 810 c_opts += ['-D__AVX__=1'] 811 812 if array_contains_any_of(user_args, SIMD_NEON_FLAGS): 813 c_opts += ['-D__ARM_NEON__=1'] 814 815 # libcxx include paths must be defined before libc's include paths otherwise libcxx will not build 816 if cxx: 817 c_opts += include_directive(cxx_include_paths) 818 return c_opts + include_directive(c_include_paths) 819 820 821def get_asmflags(user_args): 822 return ['-target', get_llvm_target()] 823 824 825def get_cflags(user_args, cxx): 826 # Set the LIBCPP ABI version to at least 2 so that we get nicely aligned string 827 # data and other nice fixes. 828 c_opts = [# '-fno-threadsafe-statics', # disabled due to issue 1289 829 '-target', get_llvm_target(), 830 '-D__EMSCRIPTEN_major__=' + str(EMSCRIPTEN_VERSION_MAJOR), 831 '-D__EMSCRIPTEN_minor__=' + str(EMSCRIPTEN_VERSION_MINOR), 832 '-D__EMSCRIPTEN_tiny__=' + str(EMSCRIPTEN_VERSION_TINY), 833 '-D_LIBCPP_ABI_VERSION=2'] 834 835 # For compatability with the fastcomp compiler that defined these 836 c_opts += ['-Dunix', 837 '-D__unix', 838 '-D__unix__'] 839 840 # Changes to default clang behavior 841 842 # Implicit functions can cause horribly confusing function pointer type errors, see #2175 843 # If your codebase really needs them - very unrecommended! - you can disable the error with 844 # -Wno-error=implicit-function-declaration 845 # or disable even a warning about it with 846 # -Wno-implicit-function-declaration 847 c_opts += ['-Werror=implicit-function-declaration'] 848 849 if os.environ.get('EMMAKEN_NO_SDK') or '-nostdinc' in user_args: 850 return c_opts 851 852 return c_opts + emsdk_cflags(user_args, cxx) 853 854 855# Settings. A global singleton. Not pretty, but nicer than passing |, settings| everywhere 856class SettingsManager(object): 857 858 class __impl(object): 859 attrs = {} 860 internal_settings = set() 861 862 def __init__(self): 863 self.reset() 864 865 @classmethod 866 def reset(cls): 867 cls.attrs = {} 868 869 # Load the JS defaults into python. 870 settings = open(path_from_root('src', 'settings.js')).read().replace('//', '#') 871 settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) 872 # Variable TARGET_NOT_SUPPORTED is referenced by value settings.js (also beyond declaring it), 873 # so must pass it there explicitly. 874 exec(settings, {'attrs': cls.attrs}) 875 876 settings = open(path_from_root('src', 'settings_internal.js')).read().replace('//', '#') 877 settings = re.sub(r'var ([\w\d]+)', r'attrs["\1"]', settings) 878 internal_attrs = {} 879 exec(settings, {'attrs': internal_attrs}) 880 cls.attrs.update(internal_attrs) 881 882 if 'EMCC_STRICT' in os.environ: 883 cls.attrs['STRICT'] = int(os.environ.get('EMCC_STRICT')) 884 885 # Special handling for LEGACY_SETTINGS. See src/setting.js for more 886 # details 887 cls.legacy_settings = {} 888 cls.alt_names = {} 889 for legacy in cls.attrs['LEGACY_SETTINGS']: 890 if len(legacy) == 2: 891 name, new_name = legacy 892 cls.legacy_settings[name] = (None, 'setting renamed to ' + new_name) 893 cls.alt_names[name] = new_name 894 cls.alt_names[new_name] = name 895 default_value = cls.attrs[new_name] 896 else: 897 name, fixed_values, err = legacy 898 cls.legacy_settings[name] = (fixed_values, err) 899 default_value = fixed_values[0] 900 assert name not in cls.attrs, 'legacy setting (%s) cannot also be a regular setting' % name 901 if not cls.attrs['STRICT']: 902 cls.attrs[name] = default_value 903 904 cls.internal_settings = set(internal_attrs.keys()) 905 906 # Transforms the Settings information into emcc-compatible args (-s X=Y, etc.). Basically 907 # the reverse of load_settings, except for -Ox which is relevant there but not here 908 @classmethod 909 def serialize(cls): 910 ret = [] 911 for key, value in cls.attrs.items(): 912 if key == key.upper(): # this is a hack. all of our settings are ALL_CAPS, python internals are not 913 jsoned = json.dumps(value, sort_keys=True) 914 ret += ['-s', key + '=' + jsoned] 915 return ret 916 917 @classmethod 918 def to_dict(cls): 919 return cls.attrs.copy() 920 921 @classmethod 922 def copy(cls, values): 923 cls.attrs = values 924 925 @classmethod 926 def apply_opt_level(cls, opt_level, shrink_level=0, noisy=False): 927 if opt_level >= 1: 928 cls.attrs['ASSERTIONS'] = 0 929 if shrink_level >= 2: 930 cls.attrs['EVAL_CTORS'] = 1 931 932 def keys(self): 933 return self.attrs.keys() 934 935 def __getattr__(self, attr): 936 if attr in self.attrs: 937 return self.attrs[attr] 938 else: 939 raise AttributeError("Settings object has no attribute '%s'" % attr) 940 941 def __setattr__(self, attr, value): 942 if attr == 'STRICT' and value: 943 for a in self.legacy_settings: 944 self.attrs.pop(a, None) 945 946 if attr in self.legacy_settings: 947 # TODO(sbc): Rather then special case this we should have STRICT turn on the 948 # legacy-settings warning below 949 if self.attrs['STRICT']: 950 exit_with_error('legacy setting used in strict mode: %s', attr) 951 fixed_values, error_message = self.legacy_settings[attr] 952 if fixed_values and value not in fixed_values: 953 exit_with_error('Invalid command line option -s ' + attr + '=' + str(value) + ': ' + error_message) 954 diagnostics.warning('legacy-settings', 'use of legacy setting: %s (%s)', attr, error_message) 955 956 if attr in self.alt_names: 957 alt_name = self.alt_names[attr] 958 self.attrs[alt_name] = value 959 960 if attr not in self.attrs: 961 msg = "Attempt to set a non-existent setting: '%s'\n" % attr 962 suggestions = difflib.get_close_matches(attr, list(self.attrs.keys())) 963 suggestions = [s for s in suggestions if s not in self.legacy_settings] 964 suggestions = ', '.join(suggestions) 965 if suggestions: 966 msg += ' - did you mean one of %s?\n' % suggestions 967 msg += " - perhaps a typo in emcc's -s X=Y notation?\n" 968 msg += ' - (see src/settings.js for valid values)' 969 exit_with_error(msg) 970 971 self.attrs[attr] = value 972 973 @classmethod 974 def get(cls, key): 975 return cls.attrs.get(key) 976 977 @classmethod 978 def __getitem__(cls, key): 979 return cls.attrs[key] 980 981 @classmethod 982 def target_environment_may_be(self, environment): 983 return self.attrs['ENVIRONMENT'] == '' or environment in self.attrs['ENVIRONMENT'].split(',') 984 985 __instance = None 986 987 @staticmethod 988 def instance(): 989 if SettingsManager.__instance is None: 990 SettingsManager.__instance = SettingsManager.__impl() 991 return SettingsManager.__instance 992 993 def __getattr__(self, attr): 994 return getattr(self.instance(), attr) 995 996 def __setattr__(self, attr, value): 997 return setattr(self.instance(), attr, value) 998 999 def get(self, key): 1000 return self.instance().get(key) 1001 1002 def __getitem__(self, key): 1003 return self.instance()[key] 1004 1005 1006def verify_settings(): 1007 if Settings.SAFE_HEAP not in [0, 1]: 1008 exit_with_error('emcc: SAFE_HEAP must be 0 or 1 in fastcomp') 1009 1010 if not Settings.WASM: 1011 # When the user requests non-wasm output, we enable wasm2js. that is, 1012 # we still compile to wasm normally, but we compile the final output 1013 # to js. 1014 Settings.WASM = 1 1015 Settings.WASM2JS = 1 1016 if Settings.WASM == 2: 1017 # Requesting both Wasm and Wasm2JS support 1018 Settings.WASM2JS = 1 1019 1020 1021def print_compiler_stage(cmd): 1022 """Emulate the '-v' of clang/gcc by printing the name of the sub-command 1023 before executing it.""" 1024 if PRINT_STAGES: 1025 print(' "%s" %s' % (cmd[0], shlex_join(cmd[1:])), file=sys.stderr) 1026 sys.stderr.flush() 1027 1028 1029def mangle_c_symbol_name(name): 1030 return '_' + name if not name.startswith('$') else name[1:] 1031 1032 1033def demangle_c_symbol_name(name): 1034 return name[1:] if name.startswith('_') else '$' + name 1035 1036 1037def is_c_symbol(name): 1038 return name.startswith('_') 1039 1040 1041def treat_as_user_function(name): 1042 if name.startswith('dynCall_'): 1043 return False 1044 if name in Settings.WASM_FUNCTIONS_THAT_ARE_NOT_NAME_MANGLED: 1045 return False 1046 return True 1047 1048 1049def asmjs_mangle(name): 1050 """Mangle a name the way asm.js/JSBackend globals are mangled. 1051 1052 Prepends '_' and replaces non-alphanumerics with '_'. 1053 Used by wasm backend for JS library consistency with asm.js. 1054 """ 1055 if treat_as_user_function(name): 1056 return '_' + name 1057 else: 1058 return name 1059 1060 1061def reconfigure_cache(): 1062 global Cache 1063 Cache = cache.Cache(CACHE) 1064 1065 1066# Placeholder strings used for SINGLE_FILE 1067class FilenameReplacementStrings: 1068 WASM_TEXT_FILE = '{{{ FILENAME_REPLACEMENT_STRINGS_WASM_TEXT_FILE }}}' 1069 WASM_BINARY_FILE = '{{{ FILENAME_REPLACEMENT_STRINGS_WASM_BINARY_FILE }}}' 1070 ASMJS_CODE_FILE = '{{{ FILENAME_REPLACEMENT_STRINGS_ASMJS_CODE_FILE }}}' 1071 1072 1073class JS(object): 1074 memory_initializer_pattern = r'/\* memory initializer \*/ allocate\(\[([\d, ]*)\], "i8", ALLOC_NONE, ([\d+\.GLOBAL_BASEHgb]+)\);' 1075 no_memory_initializer_pattern = r'/\* no memory initializer \*/' 1076 1077 memory_staticbump_pattern = r'STATICTOP = STATIC_BASE \+ (\d+);' 1078 1079 global_initializers_pattern = r'/\* global initializers \*/ __ATINIT__.push\((.+)\);' 1080 1081 emscripten_license = '''\ 1082/** 1083 * @license 1084 * Copyright 2010 The Emscripten Authors 1085 * SPDX-License-Identifier: MIT 1086 */ 1087''' 1088 1089 # handle the above form, and also what closure can emit which is stuff like 1090 # /* 1091 # 1092 # Copyright 2019 The Emscripten Authors 1093 # SPDX-License-Identifier: MIT 1094 # 1095 # Copyright 2017 The Emscripten Authors 1096 # SPDX-License-Identifier: MIT 1097 # */ 1098 emscripten_license_regex = r'\/\*\*?(\s*\*?\s*@license)?(\s*\*?\s*Copyright \d+ The Emscripten Authors\s*\*?\s*SPDX-License-Identifier: MIT)+\s*\*\/' 1099 1100 @staticmethod 1101 def handle_license(js_target): 1102 # ensure we emit the license if and only if we need to, and exactly once 1103 with open(js_target) as f: 1104 js = f.read() 1105 # first, remove the license as there may be more than once 1106 processed_js = re.sub(JS.emscripten_license_regex, '', js) 1107 if Settings.EMIT_EMSCRIPTEN_LICENSE: 1108 processed_js = JS.emscripten_license + processed_js 1109 if processed_js != js: 1110 with open(js_target, 'w') as f: 1111 f.write(processed_js) 1112 1113 @staticmethod 1114 def to_nice_ident(ident): # limited version of the JS function toNiceIdent 1115 return ident.replace('%', '$').replace('@', '_').replace('.', '_') 1116 1117 # Returns the given string with escapes added so that it can safely be placed inside a string in JS code. 1118 @staticmethod 1119 def escape_for_js_string(s): 1120 s = s.replace('\\', '/').replace("'", "\\'").replace('"', '\\"') 1121 return s 1122 1123 # Returns the subresource location for run-time access 1124 @staticmethod 1125 def get_subresource_location(path, data_uri=None): 1126 if data_uri is None: 1127 data_uri = Settings.SINGLE_FILE 1128 if data_uri: 1129 # if the path does not exist, then there is no data to encode 1130 if not os.path.exists(path): 1131 return '' 1132 with open(path, 'rb') as f: 1133 data = base64.b64encode(f.read()) 1134 return 'data:application/octet-stream;base64,' + asstr(data) 1135 else: 1136 return os.path.basename(path) 1137 1138 @staticmethod 1139 def make_initializer(sig, settings=None): 1140 settings = settings or Settings 1141 if sig == 'i': 1142 return '0' 1143 elif sig == 'f': 1144 return 'Math_fround(0)' 1145 elif sig == 'j': 1146 if settings: 1147 assert settings['WASM'], 'j aka i64 only makes sense in wasm-only mode in binaryen' 1148 return 'i64(0)' 1149 else: 1150 return '+0' 1151 1152 FLOAT_SIGS = ['f', 'd'] 1153 1154 @staticmethod 1155 def make_coercion(value, sig, settings=None, ffi_arg=False, ffi_result=False, convert_from=None): 1156 settings = settings or Settings 1157 if sig == 'i': 1158 if convert_from in JS.FLOAT_SIGS: 1159 value = '(~~' + value + ')' 1160 return value + '|0' 1161 if sig in JS.FLOAT_SIGS and convert_from == 'i': 1162 value = '(' + value + '|0)' 1163 if sig == 'f': 1164 if ffi_arg: 1165 return '+Math_fround(' + value + ')' 1166 elif ffi_result: 1167 return 'Math_fround(+(' + value + '))' 1168 else: 1169 return 'Math_fround(' + value + ')' 1170 elif sig == 'd' or sig == 'f': 1171 return '+' + value 1172 elif sig == 'j': 1173 if settings: 1174 assert settings['WASM'], 'j aka i64 only makes sense in wasm-only mode in binaryen' 1175 return 'i64(' + value + ')' 1176 else: 1177 return value 1178 1179 @staticmethod 1180 def legalize_sig(sig): 1181 # with BigInt support all sigs are legal since we can use i64s. 1182 if Settings.WASM_BIGINT: 1183 return sig 1184 legal = [sig[0]] 1185 # a return of i64 is legalized into an i32 (and the high bits are 1186 # accessible on the side through getTempRet0). 1187 if legal[0] == 'j': 1188 legal[0] = 'i' 1189 # a parameter of i64 is legalized into i32, i32 1190 for s in sig[1:]: 1191 if s != 'j': 1192 legal.append(s) 1193 else: 1194 legal.append('i') 1195 legal.append('i') 1196 return ''.join(legal) 1197 1198 @staticmethod 1199 def is_legal_sig(sig): 1200 # with BigInt support all sigs are legal since we can use i64s. 1201 if Settings.WASM_BIGINT: 1202 return True 1203 return sig == JS.legalize_sig(sig) 1204 1205 @staticmethod 1206 def make_jscall(sig): 1207 fnargs = ','.join('a' + str(i) for i in range(1, len(sig))) 1208 args = (',' if fnargs else '') + fnargs 1209 ret = '''\ 1210function jsCall_%s(index%s) { 1211 %sfunctionPointers[index](%s); 1212}''' % (sig, args, 'return ' if sig[0] != 'v' else '', fnargs) 1213 return ret 1214 1215 @staticmethod 1216 def make_dynCall(sig, args): 1217 # wasm2c and asyncify are not yet compatible with direct wasm table calls 1218 if Settings.USE_LEGACY_DYNCALLS or not JS.is_legal_sig(sig): 1219 args = ','.join(args) 1220 if not Settings.MAIN_MODULE and not Settings.SIDE_MODULE: 1221 # Optimize dynCall accesses in the case when not building with dynamic 1222 # linking enabled. 1223 return 'dynCall_%s(%s)' % (sig, args) 1224 else: 1225 return 'Module["dynCall_%s"](%s)' % (sig, args) 1226 else: 1227 return 'wasmTable.get(%s)(%s)' % (args[0], ','.join(args[1:])) 1228 1229 @staticmethod 1230 def make_invoke(sig, named=True): 1231 legal_sig = JS.legalize_sig(sig) # TODO: do this in extcall, jscall? 1232 args = ['index'] + ['a' + str(i) for i in range(1, len(legal_sig))] 1233 ret = 'return ' if sig[0] != 'v' else '' 1234 body = '%s%s;' % (ret, JS.make_dynCall(sig, args)) 1235 # C++ exceptions are numbers, and longjmp is a string 'longjmp' 1236 if Settings.SUPPORT_LONGJMP: 1237 rethrow = "if (e !== e+0 && e !== 'longjmp') throw e;" 1238 else: 1239 rethrow = "if (e !== e+0) throw e;" 1240 1241 name = (' invoke_' + sig) if named else '' 1242 ret = '''\ 1243function%s(%s) { 1244 var sp = stackSave(); 1245 try { 1246 %s 1247 } catch(e) { 1248 stackRestore(sp); 1249 %s 1250 _setThrew(1, 0); 1251 } 1252}''' % (name, ','.join(args), body, rethrow) 1253 1254 return ret 1255 1256 @staticmethod 1257 def align(x, by): 1258 while x % by != 0: 1259 x += 1 1260 return x 1261 1262 @staticmethod 1263 def generate_string_initializer(s): 1264 if Settings.ASSERTIONS: 1265 # append checksum of length and content 1266 crcTable = [] 1267 for i in range(256): 1268 crc = i 1269 for bit in range(8): 1270 crc = (crc >> 1) ^ ((crc & 1) * 0xedb88320) 1271 crcTable.append(crc) 1272 crc = 0xffffffff 1273 n = len(s) 1274 crc = crcTable[(crc ^ n) & 0xff] ^ (crc >> 8) 1275 crc = crcTable[(crc ^ (n >> 8)) & 0xff] ^ (crc >> 8) 1276 for i in s: 1277 crc = crcTable[(crc ^ i) & 0xff] ^ (crc >> 8) 1278 for i in range(4): 1279 s.append((crc >> (8 * i)) & 0xff) 1280 s = ''.join(map(chr, s)) 1281 s = s.replace('\\', '\\\\').replace("'", "\\'") 1282 s = s.replace('\n', '\\n').replace('\r', '\\r') 1283 1284 # Escape the ^Z (= 0x1a = substitute) ASCII character and all characters higher than 7-bit ASCII. 1285 def escape(x): 1286 return '\\x{:02x}'.format(ord(x.group())) 1287 1288 return re.sub('[\x1a\x80-\xff]', escape, s) 1289 1290 @staticmethod 1291 def is_dyn_call(func): 1292 return func.startswith('dynCall_') 1293 1294 @staticmethod 1295 def is_function_table(name): 1296 return name.startswith('FUNCTION_TABLE_') 1297 1298 1299class WebAssembly(object): 1300 @staticmethod 1301 def toLEB(x): 1302 assert x >= 0, 'TODO: signed' 1303 ret = [] 1304 while 1: 1305 byte = x & 127 1306 x >>= 7 1307 more = x != 0 1308 if more: 1309 byte = byte | 128 1310 ret.append(byte) 1311 if not more: 1312 break 1313 return bytearray(ret) 1314 1315 @staticmethod 1316 def readLEB(buf, offset): 1317 result = 0 1318 shift = 0 1319 while True: 1320 byte = bytearray(buf[offset:offset + 1])[0] 1321 offset += 1 1322 result |= (byte & 0x7f) << shift 1323 if not (byte & 0x80): 1324 break 1325 shift += 7 1326 return (result, offset) 1327 1328 @staticmethod 1329 def add_emscripten_metadata(js_file, wasm_file): 1330 WASM_PAGE_SIZE = 65536 1331 1332 mem_size = Settings.INITIAL_MEMORY // WASM_PAGE_SIZE 1333 table_size = Settings.WASM_TABLE_SIZE 1334 global_base = Settings.GLOBAL_BASE 1335 1336 js = open(js_file).read() 1337 m = re.search(r"(^|\s)DYNAMIC_BASE\s+=\s+(\d+)", js) 1338 dynamic_base = int(m.group(2)) 1339 1340 logger.debug('creating wasm emscripten metadata section with mem size %d, table size %d' % (mem_size, table_size,)) 1341 name = b'\x13emscripten_metadata' # section name, including prefixed size 1342 contents = ( 1343 # metadata section version 1344 WebAssembly.toLEB(EMSCRIPTEN_METADATA_MAJOR) + 1345 WebAssembly.toLEB(EMSCRIPTEN_METADATA_MINOR) + 1346 1347 # NB: The structure of the following should only be changed 1348 # if EMSCRIPTEN_METADATA_MAJOR is incremented 1349 # Minimum ABI version 1350 WebAssembly.toLEB(EMSCRIPTEN_ABI_MAJOR) + 1351 WebAssembly.toLEB(EMSCRIPTEN_ABI_MINOR) + 1352 1353 # Wasm backend, always 1 now 1354 WebAssembly.toLEB(1) + 1355 1356 WebAssembly.toLEB(mem_size) + 1357 WebAssembly.toLEB(table_size) + 1358 WebAssembly.toLEB(global_base) + 1359 WebAssembly.toLEB(dynamic_base) + 1360 # dynamictopPtr, always 0 now 1361 WebAssembly.toLEB(0) + 1362 1363 # tempDoublePtr, always 0 in wasm backend 1364 WebAssembly.toLEB(0) + 1365 1366 WebAssembly.toLEB(int(Settings.STANDALONE_WASM)) 1367 1368 # NB: more data can be appended here as long as you increase 1369 # the EMSCRIPTEN_METADATA_MINOR 1370 ) 1371 1372 orig = open(wasm_file, 'rb').read() 1373 with open(wasm_file, 'wb') as f: 1374 f.write(orig[0:8]) # copy magic number and version 1375 # write the special section 1376 f.write(b'\0') # user section is code 0 1377 # need to find the size of this section 1378 size = len(name) + len(contents) 1379 f.write(WebAssembly.toLEB(size)) 1380 f.write(name) 1381 f.write(contents) 1382 f.write(orig[8:]) 1383 1384 @staticmethod 1385 def add_dylink_section(wasm_file, needed_dynlibs): 1386 # a wasm shared library has a special "dylink" section, see tools-conventions repo 1387 # TODO: use this in the wasm backend 1388 assert False 1389 mem_align = Settings.MAX_GLOBAL_ALIGN 1390 mem_size = Settings.STATIC_BUMP 1391 table_size = Settings.WASM_TABLE_SIZE 1392 mem_align = int(math.log(mem_align, 2)) 1393 logger.debug('creating wasm dynamic library with mem size %d, table size %d, align %d' % (mem_size, table_size, mem_align)) 1394 1395 # Write new wasm binary with 'dylink' section 1396 wasm = open(wasm_file, 'rb').read() 1397 section_name = b"\06dylink" # section name, including prefixed size 1398 contents = (WebAssembly.toLEB(mem_size) + WebAssembly.toLEB(mem_align) + 1399 WebAssembly.toLEB(table_size) + WebAssembly.toLEB(0)) 1400 1401 # we extend "dylink" section with information about which shared libraries 1402 # our shared library needs. This is similar to DT_NEEDED entries in ELF. 1403 # 1404 # In theory we could avoid doing this, since every import in wasm has 1405 # "module" and "name" attributes, but currently emscripten almost always 1406 # uses just "env" for "module". This way we have to embed information about 1407 # required libraries for the dynamic linker somewhere, and "dylink" section 1408 # seems to be the most relevant place. 1409 # 1410 # Binary format of the extension: 1411 # 1412 # needed_dynlibs_count varuint32 ; number of needed shared libraries 1413 # needed_dynlibs_entries dynlib_entry* ; repeated dynamic library entries as described below 1414 # 1415 # dynlib_entry: 1416 # 1417 # dynlib_name_len varuint32 ; length of dynlib_name_str in bytes 1418 # dynlib_name_str bytes ; name of a needed dynamic library: valid UTF-8 byte sequence 1419 # 1420 # a proposal has been filed to include the extension into "dylink" specification: 1421 # https://github.com/WebAssembly/tool-conventions/pull/77 1422 contents += WebAssembly.toLEB(len(needed_dynlibs)) 1423 for dyn_needed in needed_dynlibs: 1424 dyn_needed = bytes(asbytes(dyn_needed)) 1425 contents += WebAssembly.toLEB(len(dyn_needed)) 1426 contents += dyn_needed 1427 1428 section_size = len(section_name) + len(contents) 1429 with open(wasm_file, 'wb') as f: 1430 # copy magic number and version 1431 f.write(wasm[0:8]) 1432 # write the special section 1433 f.write(b'\0') # user section is code 0 1434 f.write(WebAssembly.toLEB(section_size)) 1435 f.write(section_name) 1436 f.write(contents) 1437 # copy rest of binary 1438 f.write(wasm[8:]) 1439 1440 1441# Python 2-3 compatibility helper function: 1442# Converts a string to the native str type. 1443def asstr(s): 1444 if str is bytes: 1445 if isinstance(s, type(u'')): 1446 return s.encode('utf-8') 1447 elif isinstance(s, bytes): 1448 return s.decode('utf-8') 1449 return s 1450 1451 1452def asbytes(s): 1453 if isinstance(s, bytes): 1454 # Do not attempt to encode bytes 1455 return s 1456 return s.encode('utf-8') 1457 1458 1459def suffix(name): 1460 """Return the file extension""" 1461 return os.path.splitext(name)[1] 1462 1463 1464def unsuffixed(name): 1465 """Return the filename without the extention. 1466 1467 If there are multiple extensions this strips only the final one. 1468 """ 1469 return os.path.splitext(name)[0] 1470 1471 1472def unsuffixed_basename(name): 1473 return os.path.basename(unsuffixed(name)) 1474 1475 1476def safe_move(src, dst): 1477 src = os.path.abspath(src) 1478 dst = os.path.abspath(dst) 1479 if os.path.isdir(dst): 1480 dst = os.path.join(dst, os.path.basename(src)) 1481 if src == dst: 1482 return 1483 if dst == os.devnull: 1484 return 1485 logging.debug('move: %s -> %s', src, dst) 1486 shutil.move(src, dst) 1487 1488 1489def safe_copy(src, dst): 1490 src = os.path.abspath(src) 1491 dst = os.path.abspath(dst) 1492 if os.path.isdir(dst): 1493 dst = os.path.join(dst, os.path.basename(src)) 1494 if src == dst: 1495 return 1496 if dst == os.devnull: 1497 return 1498 shutil.copyfile(src, dst) 1499 1500 1501def read_and_preprocess(filename, expand_macros=False): 1502 temp_dir = get_emscripten_temp_dir() 1503 # Create a settings file with the current settings to pass to the JS preprocessor 1504 # Note: Settings.serialize returns an array of -s options i.e. ['-s', '<setting1>', '-s', '<setting2>', ...] 1505 # we only want the actual settings, hence the [1::2] slice operation. 1506 settings_str = "var " + ";\nvar ".join(Settings.serialize()[1::2]) 1507 settings_file = os.path.join(temp_dir, 'settings.js') 1508 with open(settings_file, 'w') as f: 1509 f.write(settings_str) 1510 1511 # Run the JS preprocessor 1512 # N.B. We can't use the default stdout=PIPE here as it only allows 64K of output before it hangs 1513 # and shell.html is bigger than that! 1514 # See https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ 1515 dirname, filename = os.path.split(filename) 1516 if not dirname: 1517 dirname = None 1518 stdout = os.path.join(temp_dir, 'stdout') 1519 args = [settings_file, filename] 1520 if expand_macros: 1521 args += ['--expandMacros'] 1522 1523 run_js_tool(path_from_root('tools/preprocessor.js'), args, True, stdout=open(stdout, 'w'), cwd=dirname) 1524 out = open(stdout, 'r').read() 1525 1526 return out 1527 1528 1529# ============================================================================ 1530# End declarations. 1531# ============================================================================ 1532 1533# Everything below this point is top level code that get run when importing this 1534# file. TODO(sbc): We should try to reduce that amount we do here and instead 1535# have consumers explicitly call initialization functions. 1536 1537# Emscripten configuration is done through the --em-config command line option 1538# or the EM_CONFIG environment variable. If the specified string value contains 1539# newline or semicolon-separated definitions, then these definitions will be 1540# used to configure Emscripten. Otherwise, the string is understood to be a 1541# path to a settings file that contains the required definitions. 1542# The search order from the config file is as follows: 1543# 1. Specified on the command line (--em-config) 1544# 2. Specified via EM_CONFIG environment variable 1545# 3. Local .emscripten file, if found 1546# 4. Local .emscripten file, as used by `emsdk --embedded` (two levels above, 1547# see below) 1548# 5. User home directory config (~/.emscripten), if found. 1549 1550embedded_config = path_from_root('.emscripten') 1551# For compatibility with `emsdk --embedded` mode also look two levels up. The 1552# layout of the emsdk puts emcc two levels below emsdk. For exmaple: 1553# - emsdk/upstream/emscripten/emcc 1554# - emsdk/emscipten/1.38.31/emcc 1555# However `emsdk --embedded` stores the config file in the emsdk root. 1556# Without this check, when emcc is run from within the emsdk in embedded mode 1557# and the user forgets to first run `emsdk_env.sh` (which sets EM_CONFIG) emcc 1558# will not see any config file at all and fall back to creating a new/emtpy 1559# one. 1560# We could remove this special case if emsdk were to write its embedded config 1561# file into the emscripten directory itself. 1562# See: https://github.com/emscripten-core/emsdk/pull/367 1563emsdk_root = os.path.dirname(os.path.dirname(__rootpath__)) 1564emsdk_embedded_config = os.path.join(emsdk_root, '.emscripten') 1565user_home_config = os.path.expanduser('~/.emscripten') 1566 1567EMSCRIPTEN_ROOT = __rootpath__ 1568 1569if '--em-config' in sys.argv: 1570 EM_CONFIG = sys.argv[sys.argv.index('--em-config') + 1] 1571 # And now remove it from sys.argv 1572 skip = False 1573 newargs = [] 1574 for arg in sys.argv: 1575 if not skip and arg != '--em-config': 1576 newargs += [arg] 1577 elif arg == '--em-config': 1578 skip = True 1579 elif skip: 1580 skip = False 1581 sys.argv = newargs 1582 if not os.path.isfile(EM_CONFIG): 1583 if EM_CONFIG.startswith('-'): 1584 exit_with_error('Passed --em-config without an argument. Usage: --em-config /path/to/.emscripten or --em-config LLVM_ROOT=/path;...') 1585 if '=' not in EM_CONFIG: 1586 exit_with_error('File ' + EM_CONFIG + ' passed to --em-config does not exist!') 1587 else: 1588 EM_CONFIG = EM_CONFIG.replace(';', '\n') + '\n' 1589elif 'EM_CONFIG' in os.environ: 1590 EM_CONFIG = os.environ['EM_CONFIG'] 1591elif os.path.exists(embedded_config): 1592 EM_CONFIG = embedded_config 1593elif os.path.exists(emsdk_embedded_config): 1594 EM_CONFIG = emsdk_embedded_config 1595elif os.path.exists(user_home_config): 1596 EM_CONFIG = user_home_config 1597else: 1598 if root_is_writable(): 1599 generate_config(embedded_config, first_time=True) 1600 else: 1601 generate_config(user_home_config, first_time=True) 1602 sys.exit(0) 1603 1604PYTHON = sys.executable 1605 1606# The following globals can be overridden by the config file. 1607# See parse_config_file below. 1608NODE_JS = None 1609BINARYEN_ROOT = '/usr/local' 1610EM_POPEN_WORKAROUND = None 1611SPIDERMONKEY_ENGINE = None 1612V8_ENGINE = None 1613LLVM_ROOT = '/usr/local' 1614LLVM_ADD_VERSION = 'devel' 1615CLANG_ADD_VERSION = 'devel' 1616CLOSURE_COMPILER = None 1617JAVA = None 1618JS_ENGINE = None 1619JS_ENGINES = [] 1620WASMER = None 1621WASMTIME = None 1622WASM_ENGINES = [] 1623CACHE = None 1624PORTS = None 1625FROZEN_CACHE = False 1626 1627# Emscripten compiler spawns other processes, which can reimport shared.py, so 1628# make sure that those child processes get the same configuration file by 1629# setting it to the currently active environment. 1630os.environ['EM_CONFIG'] = EM_CONFIG 1631 1632if '\n' in EM_CONFIG: 1633 CONFIG_FILE = None 1634 logger.debug('EM_CONFIG is specified inline without a file') 1635else: 1636 CONFIG_FILE = os.path.expanduser(EM_CONFIG) 1637 logger.debug('EM_CONFIG is located in ' + CONFIG_FILE) 1638 if not os.path.exists(CONFIG_FILE): 1639 exit_with_error('emscripten config file not found: ' + CONFIG_FILE) 1640 1641parse_config_file() 1642normalize_config_settings() 1643 1644# Install our replacement Popen handler if we are running on Windows to avoid 1645# python spawn process function. 1646# nb. This is by default disabled since it has the adverse effect of buffering 1647# up all logging messages, which makes builds look unresponsive (messages are 1648# printed only after the whole build finishes). Whether this workaround is 1649# needed seems to depend on how the host application that invokes emcc has set 1650# up its stdout and stderr. 1651if EM_POPEN_WORKAROUND and os.name == 'nt': 1652 logger.debug('Installing Popen workaround handler to avoid bug http://bugs.python.org/issue3905') 1653 Popen = WindowsPopen 1654else: 1655 Popen = subprocess.Popen 1656 1657# Verbosity level control for any intermediate subprocess spawns from the compiler. Useful for internal debugging. 1658# 0: disabled. 1659# 1: Log stderr of subprocess spawns. 1660# 2: Log stdout and stderr of subprocess spawns. Print out subprocess commands that were executed. 1661# 3: Log stdout and stderr, and pass VERBOSE=1 to CMake configure steps. 1662EM_BUILD_VERBOSE = int(os.getenv('EM_BUILD_VERBOSE', '0')) 1663TRACK_PROCESS_SPAWNS = EM_BUILD_VERBOSE >= 3 1664 1665set_version_globals() 1666 1667# For the Emscripten-specific WASM metadata section, follows semver, changes 1668# whenever metadata section changes structure. 1669# NB: major version 0 implies no compatibility 1670# NB: when changing the metadata format, we should only append new fields, not 1671# reorder, modify, or remove existing ones. 1672EMSCRIPTEN_METADATA_MAJOR, EMSCRIPTEN_METADATA_MINOR = (0, 3) 1673# For the JS/WASM ABI, specifies the minimum ABI version required of 1674# the WASM runtime implementation by the generated WASM binary. It follows 1675# semver and changes whenever C types change size/signedness or 1676# syscalls change signature. By semver, the maximum ABI version is 1677# implied to be less than (EMSCRIPTEN_ABI_MAJOR + 1, 0). On an ABI 1678# change, increment EMSCRIPTEN_ABI_MINOR if EMSCRIPTEN_ABI_MAJOR == 0 1679# or the ABI change is backwards compatible, otherwise increment 1680# EMSCRIPTEN_ABI_MAJOR and set EMSCRIPTEN_ABI_MINOR = 0. 1681EMSCRIPTEN_ABI_MAJOR, EMSCRIPTEN_ABI_MINOR = (0, 27) 1682 1683# Tools/paths 1684if LLVM_ADD_VERSION is None: 1685 LLVM_ADD_VERSION = os.getenv('LLVM_ADD_VERSION') 1686 1687if CLANG_ADD_VERSION is None: 1688 CLANG_ADD_VERSION = os.getenv('CLANG_ADD_VERSION') 1689 1690CLANG_CC = os.path.expanduser(build_clang_tool_path(exe_suffix('clang'))) 1691CLANG_CXX = os.path.expanduser(build_clang_tool_path(exe_suffix('clang++'))) 1692LLVM_LINK = build_llvm_tool_path(exe_suffix('llvm-link')) 1693LLVM_AR = build_llvm_tool_path(exe_suffix('llvm-ar')) 1694LLVM_RANLIB = build_llvm_tool_path(exe_suffix('llvm-ranlib')) 1695LLVM_OPT = os.path.expanduser(build_llvm_tool_path(exe_suffix('opt'))) 1696LLVM_AS = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-as'))) 1697LLVM_DIS = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-dis'))) 1698LLVM_NM = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-nm'))) 1699LLVM_INTERPRETER = os.path.expanduser(build_llvm_tool_path(exe_suffix('lli'))) 1700LLVM_COMPILER = os.path.expanduser(build_llvm_tool_path(exe_suffix('llc'))) 1701LLVM_DWARFDUMP = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-dwarfdump'))) 1702LLVM_OBJCOPY = os.path.expanduser(build_llvm_tool_path(exe_suffix('llvm-objcopy'))) 1703WASM_LD = os.path.expanduser(build_llvm_tool_path(exe_suffix('wasm-ld'))) 1704 1705EMCC = bat_suffix(path_from_root('emcc')) 1706EMXX = bat_suffix(path_from_root('em++')) 1707EMAR = bat_suffix(path_from_root('emar')) 1708EMRANLIB = bat_suffix(path_from_root('emranlib')) 1709FILE_PACKAGER = path_from_root('tools', 'file_packager.py') 1710 1711apply_configuration() 1712 1713# Target choice. 1714LLVM_TARGET = 'wasm32-unknown-emscripten' 1715 1716Settings = SettingsManager() 1717verify_settings() 1718Cache = cache.Cache(CACHE) 1719 1720PRINT_STAGES = int(os.getenv('EMCC_VERBOSE', '0')) 1721 1722# compatibility with existing emcc, etc. scripts 1723chunkify = cache.chunkify 1724