Buckets:
| # Copyright 2014 The Emscripten Authors. All rights reserved. | |
| # Emscripten is available under two separate licenses, the MIT license and the | |
| # University of Illinois/NCSA Open Source License. Both these licenses can be | |
| # found in the LICENSE file. | |
| import itertools | |
| import logging | |
| import os | |
| import re | |
| import shlex | |
| import shutil | |
| import subprocess | |
| import textwrap | |
| from enum import IntEnum, auto | |
| from glob import iglob | |
| from time import time | |
| from . import building, cache, diagnostics, shared, utils | |
| from .settings import settings | |
| from .toolchain_profiler import ToolchainProfiler | |
| from .utils import read_file | |
| logger = logging.getLogger('system_libs') | |
| # Files that are part of libsockets.a and so should be excluded from libc.a | |
| LIBC_SOCKETS = ['socket.c', 'socketpair.c', 'shutdown.c', 'bind.c', 'connect.c', | |
| 'listen.c', 'accept.c', 'getsockname.c', 'getpeername.c', 'send.c', | |
| 'recv.c', 'sendto.c', 'recvfrom.c', 'sendmsg.c', 'recvmsg.c', | |
| 'getsockopt.c', 'setsockopt.c', 'freeaddrinfo.c', | |
| 'gethostbyaddr.c', 'gethostbyaddr_r.c', 'gethostbyname.c', | |
| 'gethostbyname_r.c', 'gethostbyname2.c', 'gethostbyname2_r.c', | |
| 'in6addr_any.c', 'in6addr_loopback.c', 'accept4.c'] | |
| # Experimental: Setting EMCC_USE_NINJA will cause system libraries to get built with ninja rather | |
| # than simple subprocesses. The primary benefit here is that we get accurate dependency tracking. | |
| # This means we can avoid completely rebuilding a library and just rebuild based on what changed. | |
| # | |
| # Setting EMCC_USE_NINJA=2 means that ninja will automatically be run for each library needed at | |
| # link time. | |
| USE_NINJA = int(os.environ.get('EMCC_USE_NINJA', '0')) | |
| # A (fake) deterministic emscripten path to use in __FILE__ macro and debug info | |
| # to produce reproducible builds across platforms. | |
| DETERMINISTIC_PREFIX = '/emsdk/emscripten' | |
| def files_in_path(path, filenames): | |
| srcdir = utils.path_from_root(path) | |
| return [os.path.join(srcdir, f) for f in filenames] | |
| def glob_in_path(path, glob_pattern, excludes=()): | |
| srcdir = utils.path_from_root(path) | |
| files = iglob(os.path.join(srcdir, glob_pattern), recursive=True) | |
| return sorted(f for f in files if os.path.basename(f) not in excludes) | |
| def get_base_cflags(build_dir, force_object_files=False, preprocess=True): | |
| # Always build system libraries with debug information. Non-debug builds | |
| # will ignore this at link time because we link with `-strip-debug`. | |
| flags = ['-g', '-sSTRICT', '-Werror'] | |
| if settings.LTO and not force_object_files: | |
| flags += ['-flto=' + settings.LTO] | |
| if settings.MAIN_MODULE or settings.SIDE_MODULE: | |
| # Explicitly include `-sMAIN_MODULE` when building system libraries. | |
| # `-fPIC` alone is not enough to configure trigger the building and | |
| # caching of `pic` libraries (see `get_lib_dir` in `cache.py`) | |
| # FIXME(sbc): `-fPIC` should really be enough here. | |
| flags += ['-fPIC', '-sMAIN_MODULE'] | |
| if preprocess: | |
| flags += ['-DEMSCRIPTEN_DYNAMIC_LINKING'] | |
| if settings.MEMORY64: | |
| flags += ['-sMEMORY64'] | |
| source_dir = utils.path_from_root() | |
| relative_source_dir = os.path.relpath(source_dir, build_dir) | |
| flags += [f'-ffile-prefix-map={source_dir}={DETERMINISTIC_PREFIX}', | |
| f'-ffile-prefix-map={relative_source_dir}={DETERMINISTIC_PREFIX}', | |
| f'-fdebug-compilation-dir={DETERMINISTIC_PREFIX}'] | |
| return flags | |
| def get_build_dir(): | |
| return cache.get_path('build') | |
| def clean_env(): | |
| # building system libraries and ports should be hermetic in that it is not | |
| # affected by things like EMCC_CFLAGS which the user may have set. | |
| # At least one port also uses autoconf (harfbuzz) so we also need to clear | |
| # CFLAGS/LDFLAGS which we don't want to affect the inner call to configure. | |
| safe_env = os.environ.copy() | |
| for opt in ['CPATH', 'C_INCLUDE_PATH', 'CPLUS_INCLUDE_PATH', 'OBJC_INCLUDE_PATH', | |
| 'CFLAGS', 'CXXFLAGS', 'LDFLAGS', | |
| 'EMCC_CFLAGS', | |
| 'EMCC_AUTODEBUG', | |
| 'EMCC_FORCE_STDLIBS', | |
| 'EMCC_ONLY_FORCED_STDLIBS', | |
| 'EMMAKEN_JUST_CONFIGURE']: | |
| if opt in safe_env: | |
| del safe_env[opt] | |
| return safe_env | |
| def run_build_commands(commands, num_inputs, build_dir=None): | |
| # Before running a set of build commands make sure the common sysroot | |
| # headers are installed. This prevents each sub-process from attempting | |
| # to setup the sysroot itself. | |
| ensure_sysroot() | |
| start_time = time() | |
| shared.run_multiple_processes(commands, env=clean_env(), cwd=build_dir) | |
| logger.info(f'compiled {num_inputs} inputs in {time() - start_time:.2f}s') | |
| def objectfile_sort_key(filename): | |
| """Sort object files that we pass to llvm-ar.""" | |
| # In general, we simply use alphabetical order, but we special case certain | |
| # object files such they come first. For example, `vmlock.o` contains the | |
| # definition of `__vm_wait`, but `mmap.o` also contains a dummy/weak definition | |
| # for use by `mmap.o` when `vmlock.o` is not otherwise included. | |
| # | |
| # When another object looks for `__vm_wait` we prefer that it always find the | |
| # real definition first and not refer to the dummy one (which is really | |
| # intended to be local to `mmap.o` but due to the fact the weak aliases can't | |
| # have internal linkage). | |
| # | |
| # The reason we care is that once an object file is pulled into certain aspects | |
| # of it cannot be undone/removed by the linker. For example, static | |
| # constructors or stub library dependencies. | |
| # | |
| # In the case of `mmap.o`, once it get included by the linker, it pulls in the | |
| # the reverse dependencies of the mmap syscall (memalign). If we don't do this | |
| # sorting we see a slight regression in test_metadce_minimal_pthreads due to | |
| # memalign being included. | |
| basename = os.path.basename(filename) | |
| if basename in {'vmlock.o'}: | |
| return 'AAA_' + basename | |
| else: | |
| return basename | |
| def create_lib(libname, inputs): | |
| """Create a library from a set of input objects.""" | |
| suffix = utils.suffix(libname) | |
| inputs = sorted(inputs, key=objectfile_sort_key) | |
| if suffix in {'.bc', '.o'}: | |
| if len(inputs) == 1: | |
| if inputs[0] != libname: | |
| shutil.copyfile(inputs[0], libname) | |
| else: | |
| building.link_to_object(inputs, libname) | |
| else: | |
| assert suffix == '.a' | |
| building.emar('cr', libname, inputs) | |
| def get_top_level_ninja_file(): | |
| return os.path.join(get_build_dir(), 'build.ninja') | |
| def run_ninja(build_dir): | |
| cmd = ['ninja', '-C', build_dir, f'-j{utils.get_num_cores()}'] | |
| if shared.PRINT_SUBPROCS: | |
| cmd.append('-v') | |
| shared.check_call(cmd, env=clean_env()) | |
| def ensure_target_in_ninja_file(ninja_file, target): | |
| if os.path.isfile(ninja_file) and target in read_file(ninja_file): | |
| return | |
| with open(ninja_file, 'a', encoding='utf-8') as f: | |
| f.write(target + '\n') | |
| def escape_ninja_path(path): | |
| """Escape a path to be used in a ninja file.""" | |
| # Replace Windows backslashes with forward slashes. | |
| path = path.replace('\\', '/') | |
| # Escape special Ninja chars. | |
| return re.sub(r'([ :$])', r'$\1', path) | |
| def create_ninja_file(input_files, filename, libname, cflags, asflags=None, customize_build_flags=None): | |
| if asflags is None: | |
| asflags = [] | |
| join_cmd = shlex.join if os.name == 'posix' else subprocess.list2cmdline | |
| out = f'''\ | |
| # Automatically generated by tools/system_libs.py. DO NOT EDIT | |
| ninja_required_version = 1.5 | |
| ASFLAGS = {join_cmd(asflags)} | |
| CFLAGS = {join_cmd(cflags)} | |
| EMCC = {shared.EMCC} | |
| EMXX = {shared.EMXX} | |
| EMAR = {shared.EMAR} | |
| rule cc | |
| depfile = $out.d | |
| command = $EMCC -MD -MF $out.d $CFLAGS -c $in -o $out | |
| description = CC $out | |
| rule cxx | |
| depfile = $out.d | |
| command = $EMXX -MD -MF $out.d $CFLAGS -c $in -o $out | |
| description = CXX $out | |
| rule asm | |
| command = $EMCC $ASFLAGS -c $in -o $out | |
| description = ASM $out | |
| rule asm_cpp | |
| depfile = $out.d | |
| command = $EMCC -MD -MF $out.d $CFLAGS -c $in -o $out | |
| description = ASM $out | |
| rule direct_cc | |
| depfile = $with_depfile | |
| command = $EMCC -MD -MF $with_depfile $CFLAGS -c $in -o $out | |
| description = CC $out | |
| rule archive | |
| # Workaround command line too long issue (https://github.com/ninja-build/ninja/pull/217) by using a response file. | |
| rspfile = $out.rsp | |
| rspfile_content = $in | |
| command = $EMAR cr $out @$rspfile | |
| description = AR $out | |
| ''' | |
| suffix = utils.suffix(libname) | |
| build_dir = os.path.dirname(filename) | |
| if suffix == '.o': | |
| assert len(input_files) == 1 | |
| input_file = escape_ninja_path(input_files[0]) | |
| depfile = utils.unsuffixed_basename(input_file) + '.d' | |
| out += f'build {escape_ninja_path(libname)}: direct_cc {input_file}\n' | |
| out += f' with_depfile = {depfile}\n' | |
| else: | |
| objects = [] | |
| for src in input_files: | |
| # Resolve duplicates by appending unique. This is needed on case | |
| # insensitive filesystem to handle, for example, _exit.o and _Exit.o. | |
| # This is done even on case sensitive filesystem so that builds are | |
| # reproducible across platforms. | |
| object_basename = utils.unsuffixed_basename(src).lower() | |
| o = os.path.join(build_dir, object_basename + '.o') | |
| object_uuid = 0 | |
| # Find a unique basename | |
| while o in objects: | |
| object_uuid += 1 | |
| o = os.path.join(build_dir, f'{object_basename}__{object_uuid}.o') | |
| objects.append(o) | |
| ext = utils.suffix(src) | |
| match ext: | |
| case '.s': | |
| cmd = 'asm' | |
| flags = asflags | |
| case '.S': | |
| cmd = 'asm_cpp' | |
| flags = cflags | |
| case '.c': | |
| cmd = 'cc' | |
| flags = cflags | |
| case _: | |
| cmd = 'cxx' | |
| flags = cflags | |
| out += f'build {escape_ninja_path(o)}: {cmd} {escape_ninja_path(src)}\n' | |
| if customize_build_flags: | |
| custom_flags = customize_build_flags(flags, src) | |
| if custom_flags != flags: | |
| out += f' CFLAGS = {join_cmd(custom_flags)}' | |
| out += '\n' | |
| objects = sorted(objects, key=objectfile_sort_key) | |
| objects = ' '.join(escape_ninja_path(o) for o in objects) | |
| out += f'build {escape_ninja_path(libname)}: archive {objects}\n' | |
| utils.write_file(filename, out) | |
| ensure_target_in_ninja_file(get_top_level_ninja_file(), f'subninja {escape_ninja_path(filename)}') | |
| class Library: | |
| """`Library` is the base class of all system libraries. | |
| There are two types of libraries: abstract and concrete. | |
| * An abstract library, e.g. MTLibrary, is a subclass of `Library` that | |
| implements certain behaviour common to multiple libraries. The features | |
| of multiple abstract libraries can be used through multiple inheritance. | |
| * A concrete library, e.g. libc, is a subclass of `Library` that describes | |
| how to build a particular library, and its properties, such as name and | |
| dependencies. | |
| This library system is meant to handle having many versions of the same library, | |
| which we call *variations*. For example, some libraries (those that inherit | |
| from MTLibrary), have both single-threaded and multi-threaded versions. | |
| An instance of a `Library` subclass represents a specific variation of the | |
| library. Instance methods perform operations relating to this variation. | |
| For example, `get_cflags()` would return the emcc flags needed to build this | |
| variation, and `build()` would generate the library file for this variation. | |
| The constructor takes keyword arguments that defines the variation. | |
| Class methods perform tasks relating to all variations. For example, | |
| `variations()` returns a list of all variations that exists for this library, | |
| and `get_default_variation()` returns the variation suitable for the current | |
| environment. | |
| Other class methods act upon a group of libraries. For example, | |
| `Library.get_all_variations()` returns a mapping of all variations of | |
| existing libraries. | |
| To add a new type of variation, you must add an parameter to `__init__` that | |
| selects the variant. Then, override one of `vary_on` or `variations`, as well | |
| as `get_default_variation`. | |
| If the parameter is boolean, overriding `vary_on` to add the parameter name | |
| to the returned list is sufficient: | |
| @classmethod | |
| def vary_on(cls): | |
| return super().vary_on() + ['my_parameter'] | |
| Otherwise, you must override `variations`: | |
| @classmethod | |
| def variations(cls): | |
| return [{'my_parameter': value, **other} for value, other in | |
| itertools.product([1, 2, 3], super().variations())] | |
| Overriding either `vary_on` or `variations` allows `embuilder.py` to know all | |
| possible variations so it can build all of them. | |
| You then need to modify `get_default_variation` to detect the correct value | |
| for your new parameter based on the settings: | |
| @classmethod | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation(my_parameter=settings.MY_PARAMETER, **kwargs) | |
| This allows the correct variation of the library to be selected when building | |
| code with Emscripten. | |
| """ | |
| # The simple name of the library. When linking, this is the name to use to | |
| # automatically get the correct version of the library. | |
| # This should only be overridden in a concrete library class, e.g. libc, | |
| # and left as None in an abstract library class, e.g. MTLibrary. | |
| name: str | None = None | |
| # Set to true to prevent EMCC_FORCE_STDLIBS from linking this library. | |
| never_force = False | |
| # A list of flags to pass to emcc. | |
| # The flags for the parent class is automatically inherited. | |
| # TODO: Investigate whether perf gains from loop unrolling would be worth the | |
| # extra code size. The -fno-unroll-loops flags was added here when loop | |
| # unrolling landed upstream in LLVM to avoid changing behavior but was not | |
| # specifically evaluated. | |
| cflags = ['-O2', '-Wall', '-fno-unroll-loops'] | |
| # A list of directories to put in the include path when building. | |
| # This is a list of tuples of path components. | |
| # For example, to put system/lib/a and system/lib/b under the emscripten | |
| # directory into the include path, you would write: | |
| # includes = [('system', 'lib', 'a'), ('system', 'lib', 'b')] | |
| # The include path of the parent class is automatically inherited. | |
| includes: list[str] = [] | |
| # By default, `get_files` look for source files for this library under `src_dir`. | |
| # It will either use the files listed in `src_files`, or use the glob pattern in | |
| # `src_glob`. You may not specify both `src_files` and `src_glob`. | |
| # When using `src_glob`, you can specify a list of files in `src_glob_exclude` | |
| # to be excluded from the library. | |
| # Alternatively, you can override `get_files` to use your own logic. | |
| src_dir: str | None = None | |
| src_files: list[str] | None = [] | |
| src_glob: str | None = None | |
| src_glob_exclude: list[str] | None = None | |
| # Whether to always generate WASM object files, even when LTO is set | |
| force_object_files = False | |
| def __init__(self): | |
| """Create a variation of the library. | |
| A variation is a specific combination of settings a library can have. | |
| For example, libc++-mt-noexcept is a variation of libc++. | |
| There might be only one variation of a library. | |
| The constructor keyword arguments will define what variation to use. | |
| Use the `variations` classmethod to get the list of all possible constructor | |
| arguments for this library. | |
| Use the `get_default_variation` classmethod to construct the variation | |
| suitable for the current invocation of emscripten. | |
| """ | |
| if not self.name: | |
| raise NotImplementedError('Cannot instantiate an abstract library') | |
| def can_use(self): | |
| """Whether this library can be used in the current environment. | |
| For example, libmalloc would override this and return False | |
| if the user requested no malloc. | |
| """ | |
| return True | |
| def can_build(self): | |
| """Whether this library can be built in the current environment. | |
| Override this if, for example, the library can only be built on WASM backend. | |
| """ | |
| return True | |
| def erase(self): | |
| cache.erase_file(self.get_path()) | |
| def get_path(self, absolute=False): | |
| return cache.get_lib_name(self.get_filename(), absolute=absolute) | |
| def build(self): | |
| """Get the cached path of this library. | |
| This will trigger a build if this library is not in the cache. | |
| """ | |
| return cache.get(self.get_path(), self.do_build, force=USE_NINJA == 2, quiet=USE_NINJA) | |
| def generate(self): | |
| return cache.get(self.get_path(), self.do_generate, force=USE_NINJA == 2, quiet=USE_NINJA) | |
| def get_link_flag(self): | |
| """Get the link flags needed to use the library. | |
| This will trigger a build if this library is not in the cache. | |
| """ | |
| fullpath = self.build() | |
| # For non-libraries (e.g. crt1.o) we pass the entire path to the linker | |
| if self.get_ext() != '.a': | |
| return fullpath | |
| # For libraries (.a) files, we pass the abbreviated `-l` form. | |
| base = utils.unsuffixed_basename(fullpath) | |
| return '-l' + base.removeprefix('lib') | |
| def get_files(self): | |
| """Get a list of source files for this library. | |
| Typically, you will use `src_dir`, `src_files`, `src_glob` and `src_glob_exclude`. | |
| If those are insufficient to describe the files needed, you can override this method. | |
| """ | |
| if self.src_dir: | |
| if self.src_files and self.src_glob: | |
| raise Exception('Cannot use src_files and src_glob together') | |
| if self.src_files: | |
| return files_in_path(self.src_dir, self.src_files) | |
| elif self.src_glob: | |
| return glob_in_path(self.src_dir, self.src_glob, self.src_glob_exclude or ()) | |
| raise NotImplementedError() | |
| def generate_ninja(self, build_dir, libname): | |
| ensure_sysroot() | |
| utils.safe_ensure_dirs(build_dir) | |
| self.build_dir = build_dir | |
| cflags = self.get_cflags() | |
| asflags = get_base_cflags(self.build_dir, preprocess=False) | |
| input_files = self.get_files() | |
| ninja_file = os.path.join(build_dir, 'build.ninja') | |
| create_ninja_file(input_files, ninja_file, libname, cflags, asflags=asflags, customize_build_flags=self.customize_build_cmd) | |
| def build_objects(self, build_dir): | |
| """Return a list of compiled object files for this library. | |
| By default, this builds all the source files returned by `self.get_files()`, | |
| with the `cflags` returned by `self.get_cflags()`. | |
| """ | |
| batch_inputs = int(os.environ.get('EMCC_BATCH_BUILD', '1')) | |
| self.build_dir = build_dir | |
| batches = {} | |
| commands = [] | |
| objects = set() | |
| objects_lowercase = set() | |
| cflags = self.get_cflags() | |
| for src in self.get_files(): | |
| ext = utils.suffix(src) | |
| if ext in {'.s', '.S', '.c'}: | |
| cmd = shared.EMCC | |
| else: | |
| cmd = shared.EMXX | |
| cmd = [cmd, '-c'] | |
| if ext == '.s': | |
| # .s files are processed directly by the assembler. In this case we can't pass | |
| # pre-processor flags such as `-I` and `-D` but we still want core flags such as | |
| # `-sMEMORY64`. | |
| cmd += get_base_cflags(self.build_dir, preprocess=False) | |
| else: | |
| cmd += cflags | |
| cmd = self.customize_build_cmd(cmd, src) | |
| object_basename = utils.unsuffixed_basename(src) | |
| o = os.path.join(build_dir, object_basename + '.o') | |
| if o.lower() in objects_lowercase: | |
| # If we have seen a file with the same name before, we need a separate | |
| # command to compile this file with a custom unique output object | |
| # filename, as batch compile doesn't allow such customization. | |
| # | |
| # This is needed to handle, for example, _exit.o and _Exit.o. | |
| object_uuid = 0 | |
| # Find a unique basename | |
| while o.lower() in objects_lowercase: | |
| object_uuid += 1 | |
| o = os.path.join(build_dir, f'{object_basename}__{object_uuid}.o') | |
| commands.append(cmd + [src, '-o', o]) | |
| elif batch_inputs: | |
| # Use relative paths to reduce the length of the command line. | |
| # This allows to avoid switching to a response file as often. | |
| src = os.path.relpath(src, build_dir) | |
| src = utils.normalize_path(src) | |
| batches.setdefault(tuple(cmd), []).append(src) | |
| else: | |
| commands.append(cmd + [src, '-o', o]) | |
| objects.add(o) | |
| objects_lowercase.add(o.lower()) | |
| if batch_inputs: | |
| # Choose a chunk size that is large enough to avoid too many subprocesses | |
| # but not too large to avoid task starvation. | |
| # For now the heuristic is to split inputs by 2x number of cores. | |
| chunk_size = max(1, len(objects) // (2 * utils.get_num_cores())) | |
| # Convert batches to commands. | |
| for cmd, srcs in batches.items(): | |
| cmd = list(cmd) | |
| for i in range(0, len(srcs), chunk_size): | |
| chunk_srcs = srcs[i:i + chunk_size] | |
| commands.append(building.get_command_with_possible_response_file(cmd + chunk_srcs)) | |
| run_build_commands(commands, num_inputs=len(objects), build_dir=build_dir) | |
| return objects | |
| def customize_build_cmd(self, cmd, _filename): | |
| """Return a build command specialized for the given filename. | |
| This allows libraries to customize the build command used on per-file basis. | |
| For example, libc uses this to replace -Oz with -O2 for some subset of files. | |
| """ | |
| return cmd | |
| def do_build(self, out_filename, generate_only=False): | |
| """Build the library and returns the path to the file.""" | |
| assert out_filename == self.get_path(absolute=True) | |
| build_dir = os.path.join(get_build_dir(), self.get_base_name()) | |
| if USE_NINJA: | |
| self.generate_ninja(build_dir, out_filename) | |
| if not generate_only: | |
| run_ninja(build_dir) | |
| else: | |
| # Use a separate build directory to the ninja flavor so that building without | |
| # EMCC_USE_NINJA doesn't clobber the ninja build tree | |
| build_dir += '-tmp' | |
| utils.safe_ensure_dirs(build_dir) | |
| create_lib(out_filename, self.build_objects(build_dir)) | |
| if not shared.DEBUG: | |
| utils.delete_dir(build_dir) | |
| def do_generate(self, out_filename): | |
| self.do_build(out_filename, generate_only=True) | |
| def _inherit_list(cls, attr): | |
| # Some properties, like cflags and includes, makes more sense to inherit | |
| # via concatenation than replacement. | |
| result = [] | |
| for item in cls.__mro__[::-1]: | |
| # Using __dict__ to avoid inheritance | |
| result += item.__dict__.get(attr, []) | |
| return result | |
| def get_cflags(self): | |
| """Return the list emcc flags to use when building this variation of the library. | |
| Override and add any flags as needed to handle new variations. | |
| """ | |
| cflags = self._inherit_list('cflags') | |
| cflags += get_base_cflags(self.build_dir, force_object_files=self.force_object_files) | |
| if self.includes: | |
| cflags += ['-I' + utils.path_from_root(i) for i in self._inherit_list('includes')] | |
| return cflags | |
| def get_base_name_prefix(self): | |
| """Return the base name of the library without any suffixes.""" | |
| return self.name | |
| def get_base_name(self): | |
| """Return the base name of the library file. | |
| This will include suffixes such as -mt, but will not include a file extension. | |
| """ | |
| return self.get_base_name_prefix() | |
| def get_ext(self): | |
| """Return the appropriate file extension for this library.""" | |
| return '.a' | |
| def get_filename(self): | |
| """Return the full name of the library file, including the file extension.""" | |
| return self.get_base_name() + self.get_ext() | |
| def vary_on(cls): | |
| """Return a list of boolean string variations for this library. | |
| Return a list of strings that are the names of boolean constructor arguments | |
| that defines the variations of this library. | |
| This is used by the default implementation of `cls.variations()` to generate | |
| every possible combination of boolean values to pass to these arguments. | |
| """ | |
| return [] | |
| def variations(cls): | |
| """Return every possible variation of this library. | |
| Return a list of keyword arguments to pass to the constructor to create | |
| every possible variation of this library. | |
| By default, this is every possible combination of boolean values to pass | |
| to the list of arguments returned by `vary_on`, but you can override | |
| the behaviour. | |
| """ | |
| vary_on = cls.vary_on() | |
| return [dict(zip(vary_on, toggles, strict=True)) for toggles in | |
| itertools.product([False, True], repeat=len(vary_on))] | |
| def get_default_variation(cls, **kwargs): | |
| """Construct the variation suitable for the current invocation of emscripten. | |
| Subclasses should pass the keyword arguments they introduce to the | |
| superclass version, and propagate **kwargs. The base class collects | |
| all the keyword arguments and creates the instance. | |
| """ | |
| return cls(**kwargs) | |
| def get_inheritance_tree(cls): | |
| """Return all the classes in the inheritance tree of the current class.""" | |
| yield cls | |
| for subclass in cls.__subclasses__(): | |
| yield from subclass.get_inheritance_tree() | |
| def get_all_variations(cls): | |
| """Get all the variations of libraries in the inheritance tree of the current library. | |
| Calling Library.get_all_variations() returns the variations of ALL libraries | |
| that can be built as a dictionary of variation names to Library objects. | |
| """ | |
| result = {} | |
| for library in cls.get_inheritance_tree(): | |
| if library.name: | |
| for flags in library.variations(): | |
| variation = library(**flags) | |
| if variation.can_build(): | |
| result[variation.get_base_name()] = variation | |
| return result | |
| def get_usable_variations(cls): | |
| """Get all libraries suitable for the current invocation of emscripten. | |
| This returns a dictionary of simple names to Library objects. | |
| """ | |
| if not hasattr(cls, 'usable_variations'): | |
| cls.usable_variations = {} | |
| for subclass in cls.get_inheritance_tree(): | |
| if subclass.name: | |
| library = subclass.get_default_variation() | |
| if library.can_build() and library.can_use(): | |
| cls.usable_variations[subclass.name] = library | |
| return cls.usable_variations | |
| class MTLibrary(Library): | |
| def __init__(self, **kwargs): | |
| self.is_mt = kwargs.pop('is_mt') | |
| self.is_ww = kwargs.pop('is_ww') and not self.is_mt | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if self.is_mt: | |
| cflags += ['-pthread'] | |
| if self.is_ww: | |
| cflags += ['-sWASM_WORKERS'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_mt: | |
| name += '-mt' | |
| if self.is_ww: | |
| name += '-ww' | |
| return name | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_mt', 'is_ww'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation( | |
| is_mt=settings.PTHREADS, | |
| is_ww=settings.SHARED_MEMORY and not settings.PTHREADS, | |
| **kwargs, | |
| ) | |
| def variations(cls): | |
| combos = super().variations() | |
| # These are mutually exclusive, only one flag will be set at any give time. | |
| return [combo for combo in combos if not combo['is_mt'] or not combo['is_ww']] | |
| def can_build(self): | |
| # Wasm workers do not support dynamic linking. | |
| return super().can_build() and not (settings.MAIN_MODULE and self.is_ww) | |
| class DebugLibrary(Library): | |
| def __init__(self, **kwargs): | |
| self.is_debug = kwargs.pop('is_debug') | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if not self.is_debug: | |
| cflags += ['-DNDEBUG'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_debug: | |
| name += '-debug' | |
| return name | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_debug'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation(is_debug=settings.ASSERTIONS, **kwargs) | |
| class Exceptions(IntEnum): | |
| """Represents the exception handling mode of Emscripten. | |
| Currently there are three modes of exception handling: | |
| - NONE: Does not handle exceptions. This includes -fno-exceptions, which | |
| prevents both throwing and catching, and -fignore-exceptions, which only | |
| allows throwing, but library-wise they use the same version. | |
| - EMSCRIPTEN: Emscripten provides exception handling capability using JS | |
| emulation. This causes code size increase and performance degradation. | |
| - WASM_LEGACY: Wasm native exception handling support (legacy) | |
| - WASM: Wasm native exception handling support | |
| """ | |
| NONE = auto() | |
| EMSCRIPTEN = auto() | |
| WASM_LEGACY = auto() | |
| WASM = auto() | |
| class ExceptionLibrary(Library): | |
| def __init__(self, **kwargs): | |
| self.eh_mode = kwargs.pop('eh_mode') | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| match self.eh_mode: | |
| case Exceptions.NONE: | |
| cflags += ['-fno-exceptions'] | |
| case Exceptions.EMSCRIPTEN: | |
| cflags += ['-sDISABLE_EXCEPTION_CATCHING=0'] | |
| case Exceptions.WASM_LEGACY: | |
| cflags += ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS'] | |
| case Exceptions.WASM: | |
| cflags += ['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS=0'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| # TODO Currently emscripten-based exception is the default mode, thus no | |
| # suffixes. Change the default to wasm exception later. | |
| match self.eh_mode: | |
| case Exceptions.NONE: | |
| name += '-noexcept' | |
| case Exceptions.WASM_LEGACY: | |
| name += '-legacyexcept' | |
| case Exceptions.WASM: | |
| name += '-wasmexcept' | |
| return name | |
| def variations(cls): | |
| combos = super().variations() | |
| return ([dict(eh_mode=Exceptions.NONE, **combo) for combo in combos] + | |
| [dict(eh_mode=Exceptions.EMSCRIPTEN, **combo) for combo in combos] + | |
| [dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos] + | |
| [dict(eh_mode=Exceptions.WASM, **combo) for combo in combos]) | |
| def get_default_variation(cls, **kwargs): | |
| if settings.WASM_EXCEPTIONS: | |
| if settings.WASM_LEGACY_EXCEPTIONS: | |
| eh_mode = Exceptions.WASM_LEGACY | |
| else: | |
| eh_mode = Exceptions.WASM | |
| elif settings.DISABLE_EXCEPTION_CATCHING == 1: | |
| eh_mode = Exceptions.NONE | |
| else: | |
| eh_mode = Exceptions.EMSCRIPTEN | |
| return super().get_default_variation(eh_mode=eh_mode, **kwargs) | |
| class SjLjLibrary(Library): | |
| def __init__(self, **kwargs): | |
| # Which EH method we use for SjLj support | |
| self.eh_mode = kwargs.pop('eh_mode') | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| match self.eh_mode: | |
| case Exceptions.EMSCRIPTEN: | |
| cflags += ['-sSUPPORT_LONGJMP=emscripten', | |
| '-sDISABLE_EXCEPTION_THROWING=0'] | |
| case Exceptions.WASM_LEGACY: | |
| cflags += ['-sSUPPORT_LONGJMP=wasm', | |
| '-sWASM_LEGACY_EXCEPTIONS', | |
| '-sDISABLE_EXCEPTION_THROWING', | |
| '-D__WASM_SJLJ__'] | |
| case Exceptions.WASM: | |
| cflags += ['-sSUPPORT_LONGJMP=wasm', | |
| '-sWASM_LEGACY_EXCEPTIONS=0', | |
| '-sDISABLE_EXCEPTION_THROWING', | |
| '-D__WASM_SJLJ__'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| # TODO Currently emscripten-based SjLj is the default mode, thus no | |
| # suffixes. Change the default to wasm exception later. | |
| match self.eh_mode: | |
| case Exceptions.WASM_LEGACY: | |
| name += '-legacysjlj' | |
| case Exceptions.WASM: | |
| name += '-wasmsjlj' | |
| return name | |
| def variations(cls): | |
| combos = super().variations() | |
| return ([dict(eh_mode=Exceptions.EMSCRIPTEN, **combo) for combo in combos] + | |
| [dict(eh_mode=Exceptions.WASM_LEGACY, **combo) for combo in combos] + | |
| [dict(eh_mode=Exceptions.WASM, **combo) for combo in combos]) | |
| def get_default_variation(cls, **kwargs): | |
| if settings.SUPPORT_LONGJMP == 'wasm': | |
| if settings.WASM_LEGACY_EXCEPTIONS: | |
| eh_mode = Exceptions.WASM_LEGACY | |
| else: | |
| eh_mode = Exceptions.WASM | |
| else: | |
| eh_mode = Exceptions.EMSCRIPTEN | |
| return super().get_default_variation(eh_mode=eh_mode, **kwargs) | |
| class MuslInternalLibrary(Library): | |
| includes = [ | |
| 'system/lib/libc/musl/arch/emscripten', | |
| 'system/lib/libc/musl/arch/generic', | |
| 'system/lib/libc/musl/src/internal', | |
| 'system/lib/libc/musl/src/include', | |
| 'system/lib/libc/musl/include', | |
| 'system/lib/libc', | |
| 'system/lib/pthread', | |
| ] | |
| cflags = [ | |
| '-std=c99', | |
| '-D_XOPEN_SOURCE=700', | |
| '-Wno-unused-result', # system call results are often ignored in musl, and in wasi that warns | |
| '-Wno-bitwise-op-parentheses', | |
| '-Wno-shift-op-parentheses', | |
| '-Wno-constant-conversion', | |
| ] | |
| class AsanInstrumentedLibrary(Library): | |
| def __init__(self, **kwargs): | |
| self.is_asan = kwargs.pop('is_asan', False) | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if self.is_asan: | |
| cflags += ['-fsanitize=address'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_asan: | |
| name += '-asan' | |
| return name | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_asan'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation(is_asan=settings.USE_ASAN, **kwargs) | |
| # Subclass of SjLjLibrary because emscripten_setjmp.c uses SjLj support | |
| class libcompiler_rt(MTLibrary, SjLjLibrary): | |
| name = 'libcompiler_rt' | |
| # compiler_rt files can't currently be part of LTO although we are hoping to remove this | |
| # restriction soon: https://reviews.llvm.org/D71738 | |
| force_object_files = True | |
| cflags = [ | |
| '-fno-builtin', | |
| '-DNDEBUG', | |
| '-DCOMPILER_RT_HAS_ATOMICS=1', | |
| # TODO: Remove this if the emutls_key_created variable in emutls.c is | |
| # fixed or if the scope of the warning is modified again (see | |
| # https://github.com/llvm/llvm-project/pull/178342) | |
| '-Wno-unused-but-set-variable', | |
| ] | |
| src_dir = 'system/lib/compiler-rt/lib/builtins' | |
| profile_src_dir = 'system/lib/compiler-rt/lib/profile' | |
| includes = ['system/lib/libc', 'system/lib/compiler-rt/include'] | |
| excludes = [ | |
| # gcc_personality_v0.c depends on libunwind, which don't include by default. | |
| 'gcc_personality_v0.c', | |
| # bfloat16 | |
| 'extendbfsf2.c', | |
| 'truncdfbf2.c', | |
| 'truncxfbf2.c', | |
| 'truncsfbf2.c', | |
| 'trunctfbf2.c', | |
| # We provide our own crt | |
| 'crtbegin.c', | |
| 'crtend.c', | |
| # 80-bit long double (xf_float) | |
| 'divxc3.c', | |
| 'extendhfxf2.c', | |
| 'extendxftf2.c', | |
| 'fixxfdi.c', | |
| 'fixxfti.c', | |
| 'fixunsxfdi.c', | |
| 'fixunsxfsi.c', | |
| 'fixunsxfti.c', | |
| 'floatdixf.c', | |
| 'floattixf.c', | |
| 'floatundixf.c', | |
| 'floatuntixf.c', | |
| 'mulxc3.c', | |
| 'powixf2.c', | |
| 'trunctfxf2.c', | |
| 'truncxfhf2.c', | |
| ] | |
| src_files = glob_in_path(src_dir, '*.c', excludes=excludes) | |
| src_files += glob_in_path(profile_src_dir, '*.c') | |
| src_files += glob_in_path(profile_src_dir, '*.cpp') | |
| src_files += files_in_path( | |
| path='system/lib/compiler-rt', | |
| filenames=[ | |
| 'stack_ops.S', | |
| 'stack_limits.S', | |
| 'emscripten_setjmp.c', | |
| 'emscripten_exception_builtins.c', | |
| 'emscripten_tempret.s', | |
| '__c_longjmp.S', | |
| '__trap.c', | |
| ]) | |
| class libnoexit(Library): | |
| name = 'libnoexit' | |
| src_dir = 'system/lib/libc' | |
| src_files = ['atexit_dummy.c'] | |
| class llvmlibc(DebugLibrary, AsanInstrumentedLibrary, MTLibrary): | |
| name = 'libllvmlibc' | |
| never_force = True | |
| includes = ['system/lib/llvm-libc'] | |
| cflags = [ | |
| '-Os', | |
| '-DLIBC_NAMESPACE=__llvm_libc', | |
| '-DLLVM_LIBC', | |
| '-DLIBC_COPT_PUBLIC_PACKAGING', | |
| # Disable accurate pass to speed up certain math operations | |
| '-DLIBC_MATH=LIBC_MATH_FAST', | |
| '-D__LIBC_USE_BUILTIN_CEIL_FLOOR_RINT_TRUNC', | |
| # Reduce size bloats from string conversions. | |
| '-DLIBC_COPT_STRTOFLOAT_DISABLE_EISEL_LEMIRE', | |
| # To Enable FMA, we need to set the following flags. But we can't really ship this in a default libc build. | |
| # Once llvm-libc gets used, we might need to have a FMA-enalbed flavor to enable these following flags. | |
| '-Wno-unused-variable', | |
| '-mrelaxed-simd', | |
| '-ffp-contract=fast', | |
| ] | |
| def get_files(self): | |
| files = glob_in_path('system/lib/llvm-libc/src/assert', '*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/complex', '**/*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/string', '**/*.cpp', excludes=['memset.cpp', 'memcpy.cpp'] if self.is_asan else []) | |
| files += glob_in_path('system/lib/llvm-libc/src/intypes', '*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/strings', '**/*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/errno', '**/*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/math', '*.cpp') | |
| # Overlay mode doesn't support mbstate_t which is used by these wchar sources. | |
| files += glob_in_path('system/lib/llvm-libc/src/wchar', '*.cpp', excludes=['wcrtomb.cpp', 'mbrtowc.cpp', 'wctomb.cpp', 'mbtowc.cpp']) | |
| files += glob_in_path('system/lib/llvm-libc/src/setjmp', '*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/setjmp', '**/*.cpp') | |
| files += glob_in_path('system/lib/llvm-libc/src/stdlib', '*.cpp', excludes=['at_quick_exit.cpp', | |
| 'quick_exit.cpp', | |
| 'atexit.cpp', | |
| 'exit.cpp', | |
| '_Exit.cpp', | |
| 'getenv.cpp']) | |
| files += glob_in_path('system/lib/llvm-libc/src/math/generic', '**/*.cpp', excludes=['atan2l.cpp', 'exp_utils.cpp']) | |
| files += glob_in_path('system/lib/llvm-libc/src/__support/StringUtil', '**/*.cpp') | |
| return files | |
| class libc(MuslInternalLibrary, | |
| DebugLibrary, | |
| AsanInstrumentedLibrary, | |
| MTLibrary): | |
| name = 'libc' | |
| # Without -fno-builtin, LLVM can optimize away or convert calls to library | |
| # functions to something else based on assumptions that they behave exactly | |
| # like the standard library. This can cause unexpected bugs when we use our | |
| # custom standard library. The same for other libc/libm builds. | |
| cflags = ['-Os', '-fno-inline-functions', '-fno-builtin'] | |
| # Disable certain warnings for code patterns that are contained in upstream musl | |
| cflags += ['-Wno-ignored-attributes', | |
| # tre.h defines NDEBUG internally itself | |
| '-Wno-macro-redefined', | |
| '-Wno-string-plus-int', | |
| '-Wno-missing-braces', | |
| '-Wno-logical-op-parentheses', | |
| # TODO: remove this if the cause variable in cxa_default_handlers | |
| # is fixed or if the scope of the warning is reduced (see | |
| # https://github.com/llvm/llvm-project/pull/178342) | |
| '-Wno-unused-but-set-variable', | |
| '-Wno-unused-variable', | |
| '-Wno-unused-label', | |
| '-Wno-pointer-sign'] | |
| def __init__(self, **kwargs): | |
| self.non_lto_files = self.get_libcall_files() | |
| super().__init__(**kwargs) | |
| def get_libcall_files(self): | |
| # Combining static linking with LTO is tricky under LLVM. The codegen that | |
| # happens during LTO can generate references to new symbols that didn't exist | |
| # in the linker inputs themselves. | |
| # These symbols are called libcalls in LLVM and are the result of intrinsics | |
| # and builtins at the LLVM level. These libcalls cannot themselves be part | |
| # of LTO because once the linker is running the LTO phase new bitcode objects | |
| # cannot be added to link. Another way of putting it: by the time LTO happens | |
| # the decision about which bitcode symbols to compile has already been made. | |
| # See: https://bugs.llvm.org/show_bug.cgi?id=44353. | |
| # To solve this we force certain parts of libc to never be compiled as LTO/bitcode. | |
| # Note that this also includes things that may be depended on by those | |
| # functions - fmin uses signbit, for example, so signbit must be here (so if | |
| # fmin is added by codegen, it will have all it needs). | |
| math_files = [ | |
| 'fmin.c', 'fminf.c', 'fminl.c', | |
| 'fmax.c', 'fmaxf.c', 'fmaxl.c', | |
| 'fmod.c', 'fmodf.c', 'fmodl.c', | |
| 'logf.c', 'logf_data.c', | |
| 'log2f.c', 'log2f_data.c', | |
| 'log10.c', 'log10f.c', | |
| 'exp.c', 'exp_data.c', | |
| 'exp2.c', | |
| 'exp2f.c', 'exp2f_data.c', | |
| 'exp10.c', 'exp10f.c', | |
| 'ldexp.c', 'ldexpf.c', 'ldexpl.c', | |
| 'scalbn.c', '__fpclassifyl.c', | |
| '__signbitl.c', '__signbitf.c', '__signbit.c', | |
| '__math_divzero.c', '__math_divzerof.c', | |
| '__math_oflow.c', '__math_oflowf.c', | |
| '__math_uflow.c', '__math_uflowf.c', | |
| '__math_invalid.c', '__math_invalidf.c', '__math_invalidl.c', | |
| 'pow.c', 'pow_data.c', 'log.c', 'log_data.c', 'log2.c', 'log2_data.c', | |
| 'scalbnf.c', | |
| ] | |
| math_files = files_in_path(path='system/lib/libc/musl/src/math', filenames=math_files) | |
| exit_files = files_in_path( | |
| path='system/lib/libc/musl/src/exit', | |
| filenames=['atexit.c']) | |
| other_files = files_in_path( | |
| path='system/lib/libc', | |
| filenames=['emscripten_memcpy.c', 'emscripten_memset.c', | |
| 'emscripten_memcpy_bulkmem.S', 'emscripten_memset_bulkmem.S', | |
| 'emscripten_scan_stack.c', | |
| 'emscripten_get_heap_size.c', # needed by malloc | |
| 'emscripten_memmove.c']) | |
| # Calls to iprintf can be generated during codegen. Ideally we wouldn't | |
| # compile these with -O2 like we do the rest of compiler-rt since its | |
| # probably not performance sensitive. However we don't currently have | |
| # a way to set per-file compiler flags. And hopefully we should be able | |
| # move all this stuff back into libc once we it LTO compatible. | |
| iprintf_files = files_in_path( | |
| path='system/lib/libc/musl/src/stdio', | |
| filenames=['__towrite.c', '__overflow.c', 'fwrite.c', 'fputs.c', | |
| 'getc.c', | |
| 'fputc.c', | |
| 'fgets.c', | |
| 'putc.c', 'putc_unlocked.c', | |
| 'putchar.c', 'putchar_unlocked.c', | |
| 'printf.c', 'puts.c', '__lockfile.c']) | |
| iprintf_files += files_in_path( | |
| path='system/lib/libc/musl/src/string', | |
| filenames=['strlen.c']) | |
| # Transitively required by many system call imports | |
| errno_files = files_in_path( | |
| path='system/lib/libc/musl/src/errno', | |
| filenames=['__errno_location.c', 'strerror.c']) | |
| return math_files + exit_files + other_files + iprintf_files + errno_files | |
| def get_files(self): | |
| libc_files = [] | |
| musl_srcdir = utils.path_from_root('system/lib/libc/musl/src') | |
| # musl modules | |
| ignore = [ | |
| 'ipc', 'passwd', 'signal', 'sched', 'time', 'linux', | |
| 'aio', 'legacy', 'mq', 'setjmp', | |
| 'ldso', 'malloc', | |
| ] | |
| # individual files | |
| ignore += [ | |
| 'memcpy.c', 'memset.c', 'memmove.c', 'getaddrinfo.c', 'getnameinfo.c', | |
| 'res_query.c', 'res_querydomain.c', | |
| 'proto.c', | |
| 'syscall.c', 'popen.c', 'pclose.c', | |
| 'getgrouplist.c', 'initgroups.c', 'wordexp.c', 'timer_create.c', | |
| 'getauxval.c', | |
| 'lookup_name.c', | |
| # 'process' exclusions | |
| 'fork.c', 'vfork.c', 'posix_spawn.c', 'posix_spawnp.c', 'execve.c', 'waitid.c', 'system.c', | |
| '_Fork.c', | |
| # 'env' exclusions | |
| '__reset_tls.c', '__init_tls.c', '__libc_start_main.c', | |
| # 'exit' exclusions | |
| 'assert.c', 'exit.c', | |
| ] | |
| ignore += LIBC_SOCKETS | |
| if self.is_mt: | |
| ignore += [ | |
| 'clone.c', | |
| 'pthread_create.c', | |
| 'pthread_kill.c', 'pthread_sigmask.c', | |
| '__set_thread_area.c', 'synccall.c', | |
| '__syscall_cp.c', '__tls_get_addr.c', | |
| '__unmapself.c', | |
| # Empty files, simply ignore them. | |
| 'syscall_cp.c', 'tls.c', | |
| # TODO: Support these. See #12216. | |
| 'pthread_setname_np.c', | |
| 'pthread_getname_np.c', | |
| ] | |
| libc_files += files_in_path( | |
| path='system/lib/pthread', | |
| filenames=[ | |
| 'library_pthread.c', | |
| 'em_task_queue.c', | |
| 'proxying.c', | |
| 'proxying_legacy.c', | |
| 'thread_mailbox.c', | |
| 'pthread_create.c', | |
| 'pthread_kill.c', | |
| 'emscripten_thread_init.c', | |
| 'emscripten_yield.c', | |
| 'thread_profiler.c', | |
| ]) | |
| else: | |
| ignore += ['thread'] | |
| libc_files += files_in_path( | |
| path='system/lib/libc', | |
| filenames=['emscripten_yield_stub.c']) | |
| if self.is_ww: | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/thread', | |
| filenames=[ | |
| '__lock.c', | |
| '__wait.c', | |
| 'lock_ptc.c', | |
| ]) | |
| else: | |
| # Include stub version of thread functions when building | |
| # in single theaded mode. | |
| # Note: We do *not* include these stubs in the Wasm Workers build since it would | |
| # never be safe to call these from a Wasm Worker. | |
| libc_files += files_in_path( | |
| path='system/lib/pthread', | |
| filenames=[ | |
| 'library_pthread_stub.c', | |
| 'pthread_self_stub.c', | |
| 'proxying_stub.c', | |
| ]) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/thread', | |
| filenames=[ | |
| 'pthread_self.c', | |
| 'pthread_cleanup_push.c', | |
| 'pthread_attr_init.c', | |
| 'pthread_attr_destroy.c', | |
| 'pthread_attr_get.c', | |
| 'pthread_attr_setdetachstate.c', | |
| 'pthread_attr_setguardsize.c', | |
| 'pthread_attr_setinheritsched.c', | |
| 'pthread_attr_setschedparam.c', | |
| 'pthread_attr_setschedpolicy.c', | |
| 'pthread_attr_setscope.c', | |
| 'pthread_attr_setstack.c', | |
| 'pthread_attr_setstacksize.c', | |
| 'pthread_condattr_destroy.c', | |
| 'pthread_condattr_init.c', | |
| 'pthread_condattr_setpshared.c', | |
| 'pthread_condattr_setclock.c', | |
| 'pthread_mutexattr_destroy.c', | |
| 'pthread_mutexattr_init.c', | |
| 'pthread_mutexattr_setprotocol.c', | |
| 'pthread_mutexattr_settype.c', | |
| 'pthread_mutexattr_setpshared.c', | |
| 'pthread_rwlockattr_destroy.c', | |
| 'pthread_rwlockattr_init.c', | |
| 'pthread_rwlockattr_setpshared.c', | |
| 'pthread_getattr_np.c', | |
| 'pthread_getconcurrency.c', | |
| 'pthread_getcpuclockid.c', | |
| 'pthread_getschedparam.c', | |
| 'pthread_spin_destroy.c', | |
| 'pthread_spin_init.c', | |
| 'pthread_spin_lock.c', | |
| 'pthread_spin_trylock.c', | |
| 'pthread_spin_unlock.c', | |
| 'pthread_setschedprio.c', | |
| 'pthread_setconcurrency.c', | |
| 'default_attr.c', | |
| # C11 thread library functions | |
| 'call_once.c', | |
| 'tss_create.c', | |
| 'tss_delete.c', | |
| 'tss_set.c', | |
| 'cnd_broadcast.c', | |
| 'cnd_destroy.c', | |
| 'cnd_init.c', | |
| 'cnd_signal.c', | |
| 'cnd_timedwait.c', | |
| 'cnd_wait.c', | |
| 'mtx_destroy.c', | |
| 'mtx_init.c', | |
| 'mtx_lock.c', | |
| 'mtx_timedlock.c', | |
| 'mtx_trylock.c', | |
| 'mtx_unlock.c', | |
| 'sem_destroy.c', | |
| 'sem_init.c', | |
| 'thrd_create.c', | |
| 'thrd_exit.c', | |
| 'thrd_join.c', | |
| 'thrd_sleep.c', | |
| 'thrd_yield.c', | |
| ]) | |
| if self.is_mt or self.is_ww: | |
| # Low level thread primitives available in both pthreads and wasm workers builds. | |
| libc_files += files_in_path( | |
| path='system/lib/pthread', | |
| filenames=[ | |
| 'emscripten_get_next_tid.c', | |
| 'emscripten_thread_state.S', | |
| 'emscripten_thread_primitives.c', | |
| 'emscripten_futex_wait.c', | |
| 'emscripten_futex_wake.c', | |
| ]) | |
| # These files are in libc directories, but only built in libc_optz. | |
| ignore += ['pow_small.c', 'log_small.c', 'log2_small.c'] | |
| ignore = set(ignore) | |
| for dirpath, dirnames, filenames in os.walk(musl_srcdir): | |
| # Don't recurse into ignored directories | |
| remove = [d for d in dirnames if d in ignore] | |
| for r in remove: | |
| dirnames.remove(r) | |
| for f in filenames: | |
| if f.endswith('.c') and f not in ignore: | |
| libc_files.append(os.path.join(musl_srcdir, dirpath, f)) | |
| # Allowed files from ignored modules | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/time', | |
| filenames=[ | |
| 'clock_settime.c', | |
| 'asctime_r.c', | |
| 'asctime.c', | |
| 'ctime.c', | |
| 'difftime.c', | |
| 'ftime.c', | |
| 'gmtime.c', | |
| 'localtime.c', | |
| 'nanosleep.c', | |
| 'clock.c', | |
| 'clock_nanosleep.c', | |
| 'clock_getres.c', | |
| 'clock_gettime.c', | |
| 'ctime_r.c', | |
| 'timespec_get.c', | |
| 'utime.c', | |
| '__map_file.c', | |
| 'strftime.c', | |
| '__tz.c', | |
| '__tm_to_secs.c', | |
| '__year_to_secs.c', | |
| '__month_to_secs.c', | |
| 'wcsftime.c', | |
| ]) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/legacy', | |
| filenames=['getpagesize.c', 'err.c', 'euidaccess.c']) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/linux', | |
| filenames=['getdents.c', 'gettid.c', 'utimes.c', 'statx.c', 'wait4.c', 'wait3.c']) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/malloc', | |
| filenames=['reallocarray.c']) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/sched', | |
| filenames=['sched_yield.c']) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/ldso', | |
| filenames=['dladdr.c', 'dlerror.c', 'dlsym.c', 'dlclose.c']) | |
| libc_files += files_in_path( | |
| path='system/lib/libc/musl/src/signal', | |
| filenames=[ | |
| 'block.c', | |
| 'getitimer.c', | |
| 'killpg.c', | |
| 'setitimer.c', | |
| 'sigorset.c', | |
| 'sigandset.c', | |
| 'sigaddset.c', | |
| 'sigdelset.c', | |
| 'sigemptyset.c', | |
| 'sigisemptyset.c', | |
| 'sigfillset.c', | |
| 'sigismember.c', | |
| 'siginterrupt.c', | |
| 'signal.c', | |
| 'sigprocmask.c', | |
| 'sigrtmax.c', | |
| 'sigrtmin.c', | |
| 'sigwait.c', | |
| 'sigwaitinfo.c', | |
| ]) | |
| libc_files += files_in_path( | |
| path='system/lib/libc', | |
| filenames=[ | |
| 'emscripten_console.c', | |
| 'emscripten_fiber.c', | |
| 'emscripten_get_heap_size.c', | |
| 'emscripten_memcpy.c', | |
| 'emscripten_memmove.c', | |
| 'emscripten_memset.c', | |
| 'emscripten_memcpy_bulkmem.S', | |
| 'emscripten_memset_bulkmem.S', | |
| 'emscripten_mmap.c', | |
| 'emscripten_scan_stack.c', | |
| 'emscripten_time.c', | |
| 'mktime.c', | |
| 'kill.c', | |
| 'lookup_name.c', | |
| 'pthread_sigmask.c', | |
| 'raise.c', | |
| 'sigaction.c', | |
| 'sigtimedwait.c', | |
| 'wasi-helpers.c', | |
| 'system.c', | |
| ]) | |
| if settings.MAIN_MODULE: | |
| libc_files += files_in_path(path='system/lib/libc', filenames=['dynlink.c']) | |
| libc_files += glob_in_path('system/lib/libc/compat', '*.c') | |
| # Check for missing file in non_lto_files list. Do this here | |
| # rather than in the constructor so it only happens when the | |
| # library is actually built (not when its instantiated). | |
| for f in self.non_lto_files: | |
| assert os.path.exists(f), f | |
| return libc_files | |
| def customize_build_cmd(self, cmd, filename): | |
| if filename in self.non_lto_files: | |
| # These files act more like the part of compiler-rt in that | |
| # references to them can be generated at compile time. | |
| # Treat them like compiler-rt in as much as never compile | |
| # them as LTO and build them with -O2 rather then -Os (which | |
| # use used for the rest of libc) because this set of files | |
| # also contains performance sensitive math functions. | |
| cmd = [a for a in cmd if not a.startswith('-flto')] | |
| cmd = [a for a in cmd if not a.startswith('-O')] | |
| cmd += ['-O2'] | |
| return cmd | |
| # Contains the files from libc that are optimized differently in -Oz mode, where | |
| # we want to aggressively optimize them for size. This is linked in before libc | |
| # so we can override those specific files, when in -Oz. | |
| class libc_optz(libc): | |
| name = 'libc_optz' | |
| cflags = ['-Os', '-fno-inline-functions', '-fno-builtin', '-DEMSCRIPTEN_OPTIMIZE_FOR_OZ'] | |
| def __init__(self, **kwargs): | |
| super().__init__(**kwargs) | |
| self.non_lto_files = self.get_libcall_files() | |
| def get_libcall_files(self): | |
| # see comments in libc.customize_build_cmd | |
| # some files also appear in libc, and a #define affects them | |
| mem_files = files_in_path( | |
| path='system/lib/libc', | |
| filenames=['emscripten_memcpy.c', 'emscripten_memset.c', | |
| 'emscripten_memmove.c']) | |
| # some functions have separate files | |
| math_files = files_in_path( | |
| path='system/lib/libc/musl/src/math', | |
| filenames=['pow_small.c', 'log_small.c', 'log2_small.c']) | |
| return mem_files + math_files | |
| def get_files(self): | |
| libcall_files = self.get_libcall_files() | |
| # some files also appear in libc, and a #define affects them | |
| mem_files = files_in_path( | |
| path='system/lib/libc/musl/src/string', | |
| filenames=['memcmp.c']) | |
| return libcall_files + mem_files | |
| def customize_build_cmd(self, cmd, filename): | |
| if filename in self.non_lto_files: | |
| # see comments in libc.customize_build_cmd | |
| cmd = [a for a in cmd if not a.startswith('-flto')] | |
| cmd = [a for a in cmd if not a.startswith('-O')] | |
| cmd += ['-O2'] | |
| return cmd | |
| def can_use(self): | |
| # Because libc_optz overrides parts of libc, it is not compatible with | |
| # dynamic linking which uses --whole-archive. In addition, | |
| # EMCC_FORCE_STDLIBS can have a similar effect of forcing all libraries. | |
| # In both cases, the build is not one that is hyper-focused on code size, | |
| # and so optz is not that important. | |
| return super().can_use() and settings.SHRINK_LEVEL >= 2 and \ | |
| not settings.LINKABLE and not os.environ.get('EMCC_FORCE_STDLIBS') | |
| class libprintf_long_double(libc): | |
| name = 'libprintf_long_double' | |
| cflags = ['-DEMSCRIPTEN_PRINTF_LONG_DOUBLE'] | |
| # Need to define get_files | |
| # so libprintf_long_double.get_files() does not call libc.get_files() | |
| def get_files(self): | |
| return files_in_path( | |
| path='system/lib/libc/musl/src/stdio', | |
| filenames=['vfprintf.c']) | |
| def can_use(self): | |
| return super().can_use() and settings.PRINTF_LONG_DOUBLE | |
| class libwasm_workers(MuslInternalLibrary, DebugLibrary): | |
| name = 'libwasm_workers' | |
| includes = ['system/lib/libc'] | |
| src_dir = 'system/lib/wasm_worker' | |
| src_files = ['library_wasm_worker.c', 'wasm_worker_initialize.S', 'audio_worklet.c'] | |
| def __init__(self, **kwargs): | |
| self.is_mt = kwargs.pop('is_mt') | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() + ['-sWASM_WORKERS', '-Wno-c23-extensions'] | |
| if self.is_mt: | |
| cflags += ['-pthread'] | |
| if self.is_debug: | |
| # library_wasm_worker.c contains an assert that a nonnull parameter | |
| # is no NULL, which llvm now warns is redundant/tautological. | |
| cflags += ['-Wno-tautological-pointer-compare'] | |
| # Override the `-O2` default. Building library_wasm_worker.c with | |
| # `-O1` or `-O2` currently causes tests to fail. | |
| # https://github.com/emscripten-core/emscripten/issues/19331 | |
| cflags += ['-O0'] | |
| else: | |
| cflags += ['-Oz'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_mt: | |
| name += '-mt' | |
| return name | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_mt'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation( | |
| is_mt=settings.PTHREADS, | |
| **kwargs, | |
| ) | |
| def can_use(self): | |
| # see src/library_wasm_worker.js | |
| return super().can_use() and not settings.SINGLE_FILE and not settings.MAIN_MODULE and settings.WASM_WORKERS | |
| class libsockets(MuslInternalLibrary, MTLibrary): | |
| name = 'libsockets' | |
| src_dir = 'system/lib/libc/musl/src/network' | |
| src_files = LIBC_SOCKETS | |
| cflags = ['-Os', '-fno-builtin', '-Wno-shift-op-parentheses'] | |
| def can_use(self): | |
| return super().can_use() and not settings.PROXY_POSIX_SOCKETS | |
| class libsockets_proxy(MTLibrary): | |
| name = 'libsockets_proxy' | |
| src_dir = 'system/lib/websocket' | |
| src_files = ['websocket_to_posix_socket.c'] | |
| cflags = ['-Os'] | |
| def can_use(self): | |
| return super().can_use() and settings.PROXY_POSIX_SOCKETS | |
| class crt1(MuslInternalLibrary): | |
| name = 'crt1' | |
| src_dir = 'system/lib/libc' | |
| src_files = ['crt1.c'] | |
| force_object_files = True | |
| def get_ext(self): | |
| return '.o' | |
| def can_use(self): | |
| return super().can_use() and settings.STANDALONE_WASM | |
| class crt1_reactor(MuslInternalLibrary): | |
| name = 'crt1_reactor' | |
| src_dir = 'system/lib/libc' | |
| src_files = ['crt1_reactor.c'] | |
| force_object_files = True | |
| def get_ext(self): | |
| return '.o' | |
| def can_use(self): | |
| return super().can_use() and settings.STANDALONE_WASM | |
| class crt1_proxy_main(MuslInternalLibrary): | |
| name = 'crt1_proxy_main' | |
| src_dir = 'system/lib/libc' | |
| src_files = ['crt1_proxy_main.c'] | |
| force_object_files = True | |
| def get_ext(self): | |
| return '.o' | |
| def can_use(self): | |
| return super().can_use() and settings.PROXY_TO_PTHREAD | |
| class crtbegin_mt(MuslInternalLibrary): | |
| # This library defines _emscripten_tls_init/_emscripten_tls_free which are linked into | |
| # every module (i.e. not just the main module). | |
| name = 'crtbegin-mt' | |
| cflags = ['-pthread'] | |
| src_dir = 'system/lib/pthread' | |
| src_files = ['emscripten_tls_init.c'] | |
| force_object_files = True | |
| def get_ext(self): | |
| return '.o' | |
| def can_use(self): | |
| return super().can_use() and settings.PTHREADS | |
| class libcxxabi(ExceptionLibrary, MTLibrary, DebugLibrary): | |
| name = 'libc++abi' | |
| cflags = [ | |
| '-Oz', | |
| '-D_LIBCXXABI_USE_FUTEX', | |
| '-D_LIBCPP_BUILDING_LIBRARY', | |
| '-D_LIBCXXABI_BUILDING_LIBRARY', | |
| '-DLIBCXXABI_NON_DEMANGLING_TERMINATE', | |
| '-std=c++23', | |
| '-Wno-unused-but-set-variable', | |
| ] | |
| includes = ['system/lib/libcxx/src'] | |
| def __init__(self, **kwargs): | |
| super().__init__(**kwargs) | |
| # TODO EXCEPTION_STACK_TRACES currently requires the debug version of | |
| # libc++abi, causing the debug version of libc++abi to be linked, which | |
| # increases code size. libc++abi is not a big library to begin with, but if | |
| # this becomes a problem, consider making EXCEPTION_STACK_TRACES work with | |
| # the non-debug version of libc++abi. | |
| self.is_debug |= settings.EXCEPTION_STACK_TRACES | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if not self.is_mt and not self.is_ww: | |
| cflags.append('-D_LIBCXXABI_HAS_NO_THREADS') | |
| match self.eh_mode: | |
| case Exceptions.NONE: | |
| cflags.append('-D_LIBCXXABI_NO_EXCEPTIONS') | |
| case Exceptions.EMSCRIPTEN: | |
| cflags.append('-D__EMSCRIPTEN_EXCEPTIONS__') | |
| # The code used to interpret exceptions during terminate | |
| # is not compatible with emscripten exceptions. | |
| cflags.append('-DLIBCXXABI_SILENT_TERMINATE') | |
| return cflags | |
| def get_files(self): | |
| filenames = [ | |
| 'abort_message.cpp', | |
| 'cxa_aux_runtime.cpp', | |
| 'cxa_default_handlers.cpp', | |
| 'cxa_demangle.cpp', | |
| 'cxa_guard.cpp', | |
| 'cxa_handlers.cpp', | |
| 'cxa_virtual.cpp', | |
| 'cxa_thread_atexit.cpp', | |
| 'fallback_malloc.cpp', | |
| 'stdlib_new_delete.cpp', | |
| 'stdlib_exception.cpp', | |
| 'stdlib_stdexcept.cpp', | |
| 'stdlib_typeinfo.cpp', | |
| 'private_typeinfo.cpp', | |
| 'cxa_exception_js_utils.cpp', | |
| '__cpp_exception.S', | |
| ] | |
| match self.eh_mode: | |
| case Exceptions.NONE: | |
| filenames += ['cxa_noexception.cpp'] | |
| case Exceptions.EMSCRIPTEN: | |
| filenames += ['cxa_exception_emscripten.cpp'] | |
| case Exceptions.WASM_LEGACY | Exceptions.WASM: | |
| filenames += [ | |
| 'cxa_exception_storage.cpp', | |
| 'cxa_exception.cpp', | |
| 'cxa_personality.cpp', | |
| ] | |
| case _: | |
| assert False | |
| return files_in_path( | |
| path='system/lib/libcxxabi/src', | |
| filenames=filenames) | |
| class libcxx(ExceptionLibrary, MTLibrary, DebugLibrary): | |
| name = 'libc++' | |
| cflags = [ | |
| '-Oz', | |
| '-DLIBCXX_BUILDING_LIBCXXABI=1', | |
| '-D_LIBCPP_BUILDING_LIBRARY', | |
| '-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS', | |
| # TODO(sbc): clang recently introduced this new warning which is triggered | |
| # by `filesystem/directory_iterator.cpp`: https://reviews.llvm.org/D119670 | |
| '-Wno-unqualified-std-cast-call', | |
| '-Wno-unknown-warning-option', | |
| '-std=c++23', | |
| '-DLIBC_NAMESPACE=__llvm_libc', | |
| ] | |
| includes = ['system/lib/libcxx/src', 'system/lib/llvm-libc'] | |
| src_dir = 'system/lib/libcxx/src' | |
| src_glob = '**/*.cpp' | |
| src_glob_exclude = [ | |
| 'xlocale_zos.cpp', | |
| 'mbsnrtowcs.cpp', | |
| 'wcsnrtombs.cpp', | |
| 'int128_builtins.cpp', | |
| 'libdispatch.cpp', | |
| # Win32-specific files | |
| 'locale_win32.cpp', | |
| 'thread_win32.cpp', | |
| 'support.cpp', | |
| 'compiler_rt_shims.cpp', | |
| # Emscripten does not have C++20's time zone support which requires access | |
| # to IANA Time Zone Database. TODO Implement this using JS timezone | |
| 'time_zone.cpp', | |
| 'tzdb.cpp', | |
| 'tzdb_list.cpp', | |
| ] | |
| class libunwind(ExceptionLibrary, MTLibrary): | |
| name = 'libunwind' | |
| # Because calls to _Unwind_CallPersonality are generated during LTO, libunwind | |
| # can't currently be part of LTO. | |
| # See https://bugs.llvm.org/show_bug.cgi?id=44353 | |
| force_object_files = True | |
| cflags = ['-Oz', '-D_LIBUNWIND_HIDE_SYMBOLS', | |
| # TODO Remove this once | |
| # https://github.com/llvm/llvm-project/pull/175776 lands | |
| '-Wno-c23-extensions'] | |
| src_dir = 'system/lib/libunwind/src' | |
| # Without this we can't build libunwind since it will pickup the unwind.h | |
| # that is part of llvm (which is not compatible for some reason). | |
| includes = ['system/lib/libunwind/include'] | |
| src_files = ['Unwind-wasm.c'] | |
| def __init__(self, **kwargs): | |
| super().__init__(**kwargs) | |
| def can_use(self): | |
| return super().can_use() and self.eh_mode in {Exceptions.WASM_LEGACY, Exceptions.WASM} | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| cflags.append('-DNDEBUG') | |
| if not self.is_mt and not self.is_ww: | |
| cflags.append('-D_LIBUNWIND_HAS_NO_THREADS') | |
| match self.eh_mode: | |
| case Exceptions.NONE: | |
| cflags.append('-D_LIBUNWIND_HAS_NO_EXCEPTIONS') | |
| case Exceptions.EMSCRIPTEN: | |
| cflags.append('-D__EMSCRIPTEN_EXCEPTIONS__') | |
| return cflags | |
| class libmalloc(MTLibrary): | |
| name = 'libmalloc' | |
| cflags = ['-fno-builtin', '-Wno-unused-function', '-Wno-unused-but-set-variable', '-Wno-unused-variable'] | |
| # malloc/free/calloc are runtime functions and can be generated during LTO | |
| # Therefore they cannot themselves be part of LTO. | |
| force_object_files = True | |
| def __init__(self, **kwargs): | |
| self.malloc = kwargs.pop('malloc') | |
| if self.malloc not in {'dlmalloc', 'emmalloc', 'emmalloc-debug', 'emmalloc-memvalidate', 'emmalloc-verbose', 'emmalloc-memvalidate-verbose', 'mimalloc', 'none'}: | |
| raise Exception('malloc must be one of "emmalloc[-debug|-memvalidate][-verbose]", "mimalloc", "dlmalloc" or "none", see settings.js') | |
| self.is_tracing = kwargs.pop('is_tracing') | |
| self.memvalidate = kwargs.pop('memvalidate') | |
| self.verbose = kwargs.pop('verbose') | |
| self.is_debug = kwargs.pop('is_debug') or self.memvalidate or self.verbose | |
| super().__init__(**kwargs) | |
| def get_files(self): | |
| malloc_base = self.malloc.replace('-memvalidate', '').replace('-verbose', '').replace('-debug', '') | |
| malloc = utils.path_from_root('system/lib', { | |
| 'dlmalloc': 'dlmalloc.c', 'emmalloc': 'emmalloc.c', | |
| }[malloc_base]) | |
| # Include sbrk.c in libc, it uses tracing and libc itself doesn't have a tracing variant. | |
| sbrk = utils.path_from_root('system/lib/libc/sbrk.c') | |
| return [malloc, sbrk] | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if self.memvalidate: | |
| cflags += ['-DEMMALLOC_MEMVALIDATE'] | |
| if self.verbose: | |
| cflags += ['-DEMMALLOC_VERBOSE'] | |
| if self.is_debug: | |
| cflags += ['-UNDEBUG', '-DDLMALLOC_DEBUG'] | |
| else: | |
| cflags += ['-DNDEBUG'] | |
| if self.is_tracing: | |
| cflags += ['--tracing'] | |
| return cflags | |
| def get_base_name_prefix(self): | |
| return 'lib%s' % self.malloc | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_debug and not self.memvalidate and not self.verbose: | |
| name += '-debug' | |
| if self.is_tracing: | |
| name += '-tracing' | |
| return name | |
| def can_use(self): | |
| return super().can_use() and settings.MALLOC not in {'none', 'mimalloc'} | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_debug', 'is_tracing', 'memvalidate', 'verbose'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation( | |
| malloc=settings.MALLOC, | |
| is_debug=settings.ASSERTIONS, | |
| is_tracing=settings.EMSCRIPTEN_TRACING, | |
| memvalidate='memvalidate' in settings.MALLOC, | |
| verbose='verbose' in settings.MALLOC, | |
| **kwargs, | |
| ) | |
| def variations(cls): | |
| combos = super().variations() | |
| return ([dict(malloc='dlmalloc', **combo) for combo in combos if not combo['memvalidate'] and not combo['verbose']] + | |
| [dict(malloc='emmalloc', **combo) for combo in combos if not combo['memvalidate'] and not combo['verbose']] + | |
| [dict(malloc='emmalloc-memvalidate-verbose', **combo) for combo in combos if combo['memvalidate'] and combo['verbose']] + | |
| [dict(malloc='emmalloc-memvalidate', **combo) for combo in combos if combo['memvalidate'] and not combo['verbose']] + | |
| [dict(malloc='emmalloc-verbose', **combo) for combo in combos if combo['verbose'] and not combo['memvalidate']]) | |
| class libmimalloc(MTLibrary): | |
| name = 'libmimalloc' | |
| cflags = [ | |
| '-fno-builtin', | |
| '-Wno-unused-function', | |
| '-Wno-unused-but-set-variable', | |
| '-Wno-unused-variable', | |
| '-Wno-deprecated-pragma', | |
| # build emmalloc as only a system allocator, without exporting itself onto | |
| # malloc/free in the global scope | |
| '-DEMMALLOC_NO_STD_EXPORTS', | |
| # disable large pages by default, see: | |
| # https://github.com/microsoft/mimalloc/commit/9199d54bcf1e6dea0deb61a3a8a4b3ea4b45a341 | |
| '-DMI_ENABLE_LARGE_PAGES=0', | |
| # halve the page size to 32KiB on wasm64 and to 16KiB on wasm32 | |
| # https://github.com/microsoft/mimalloc/issues/647#issuecomment-1324109021 | |
| # https://github.com/emscripten-core/emscripten/issues/20645#issuecomment-1962964755 | |
| '-DMI_ARENA_SLICE_SHIFT=(12+MI_SIZE_SHIFT)', | |
| # `malloc`ed pointers must be aligned at least as strictly as max_align_t | |
| '-DMI_MAX_ALIGN_SIZE=8', | |
| # reserve memory in 64 MiB chunks (internally divided by 4) | |
| # Note: keep in sync with the -sINITIAL_HEAP default | |
| '-DMI_DEFAULT_ARENA_RESERVE=65536', | |
| # build mimalloc with an override of malloc/free | |
| '-DMI_MALLOC_OVERRIDE', | |
| # TODO: add build modes that include debug checks 1,2,3 | |
| '-DMI_DEBUG=0', | |
| # disable `assert()` in the underlying emmalloc allocator | |
| '-DNDEBUG', | |
| # Emscripten uses musl libc internally | |
| '-DMI_LIBC_MUSL', | |
| # enable use of `__builtin_thread_pointer()` | |
| '-DMI_USE_BUILTIN_THREAD_POINTER', | |
| ] | |
| # malloc/free/calloc are runtime functions and can be generated during LTO | |
| # Therefore they cannot themselves be part of LTO. | |
| force_object_files = True | |
| includes = ['system/lib/mimalloc/include'] | |
| # Build all of mimalloc, and also emmalloc which is used as the system | |
| # allocator underneath it. | |
| src_dir = 'system/lib/' | |
| src_files = glob_in_path( | |
| path='system/lib/mimalloc/src', | |
| glob_pattern='*.c', | |
| # mimalloc includes some files at the source level, so exclude them here. | |
| excludes=['alloc-override.c', 'free.c', 'page-queue.c', 'static.c'], | |
| ) | |
| src_files += [utils.path_from_root('system/lib/mimalloc/src/prim/prim.c')] | |
| src_files += [utils.path_from_root('system/lib/emmalloc.c')] | |
| # Include sbrk.c in libc, it uses tracing and libc itself doesn't have a tracing variant. | |
| src_files += [utils.path_from_root('system/lib/libc/sbrk.c')] | |
| def can_use(self): | |
| return super().can_use() and settings.MALLOC == 'mimalloc' | |
| class libal(Library): | |
| name = 'libal' | |
| cflags = ['-Os'] | |
| src_dir = 'system/lib' | |
| src_files = ['al.c'] | |
| class libGL(MTLibrary): | |
| name = 'libGL' | |
| src_dir = 'system/lib/gl' | |
| src_files = ['gl.c', 'webgl1.c', 'libprocaddr.c', 'webgl2.c'] | |
| cflags = ['-Oz'] | |
| def __init__(self, **kwargs): | |
| self.is_legacy = kwargs.pop('is_legacy') | |
| self.is_webgl2 = kwargs.pop('is_webgl2') | |
| self.is_ofb = kwargs.pop('is_ofb') | |
| self.is_full_es3 = kwargs.pop('is_full_es3') | |
| self.is_enable_get_proc_address = kwargs.pop('is_enable_get_proc_address') | |
| super().__init__(**kwargs) | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.is_legacy: | |
| name += '-emu' | |
| if self.is_webgl2: | |
| name += '-webgl2' | |
| if self.is_ofb: | |
| name += '-ofb' | |
| if self.is_full_es3: | |
| name += '-full_es3' | |
| if self.is_enable_get_proc_address: | |
| name += '-getprocaddr' | |
| return name | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if self.is_legacy: | |
| cflags += ['-DLEGACY_GL_EMULATION=1'] | |
| cflags += [f'-DMAX_WEBGL_VERSION={2 if self.is_webgl2 else 1}'] | |
| if self.is_ofb: | |
| cflags += ['-D__EMSCRIPTEN_OFFSCREEN_FRAMEBUFFER__'] | |
| if self.is_full_es3: | |
| cflags += ['-D__EMSCRIPTEN_FULL_ES3__'] | |
| if self.is_enable_get_proc_address: | |
| cflags += ['-DGL_ENABLE_GET_PROC_ADDRESS=1'] | |
| return cflags | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_legacy', 'is_webgl2', 'is_ofb', 'is_full_es3', 'is_enable_get_proc_address'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation( | |
| is_legacy=settings.LEGACY_GL_EMULATION, | |
| is_webgl2=settings.MAX_WEBGL_VERSION >= 2, | |
| is_ofb=settings.OFFSCREEN_FRAMEBUFFER, | |
| is_full_es3=settings.FULL_ES3, | |
| is_enable_get_proc_address=settings.GL_ENABLE_GET_PROC_ADDRESS, | |
| **kwargs, | |
| ) | |
| class libembind(MTLibrary): | |
| name = 'libembind' | |
| never_force = True | |
| def __init__(self, **kwargs): | |
| self.with_rtti = kwargs.pop('with_rtti', False) | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| cflags.append('-std=c++20') | |
| if not self.with_rtti: | |
| cflags += ['-fno-rtti', '-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0'] | |
| return cflags | |
| def vary_on(cls): | |
| return super().vary_on() + ['with_rtti'] | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.with_rtti: | |
| name += '-rtti' | |
| return name | |
| def get_files(self): | |
| return [utils.path_from_root('system/lib/embind/bind.cpp')] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation(with_rtti=settings.USE_RTTI, **kwargs) | |
| class libfetch(MTLibrary): | |
| name = 'libfetch' | |
| never_force = True | |
| includes = ['system/lib/libc'] | |
| def get_files(self): | |
| return [utils.path_from_root('system/lib/fetch/emscripten_fetch.c')] | |
| class libstb_image(Library): | |
| name = 'libstb_image' | |
| never_force = True | |
| includes = ['third_party'] | |
| def get_files(self): | |
| return [utils.path_from_root('system/lib/stb_image.c')] | |
| class libwasmfs(DebugLibrary, AsanInstrumentedLibrary, MTLibrary): | |
| name = 'libwasmfs' | |
| cflags = ['-fno-exceptions', '-std=c++17'] | |
| includes = ['system/lib/wasmfs', 'system/lib/pthread'] | |
| def __init__(self, **kwargs): | |
| self.ignore_case = kwargs.pop('ignore_case') | |
| super().__init__(**kwargs) | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| if self.ignore_case: | |
| cflags += ['-DWASMFS_CASE_INSENSITIVE'] | |
| return cflags | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.ignore_case: | |
| name += '-icase' | |
| return name | |
| def vary_on(cls): | |
| return super().vary_on() + ['ignore_case'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation(ignore_case=settings.CASE_INSENSITIVE_FS, **kwargs) | |
| def get_files(self): | |
| backends = files_in_path( | |
| path='system/lib/wasmfs/backends', | |
| filenames=['fetch_backend.cpp', | |
| 'ignore_case_backend.cpp', | |
| 'js_file_backend.cpp', | |
| 'memory_backend.cpp', | |
| 'node_backend.cpp', | |
| 'opfs_backend.cpp']) | |
| return backends + files_in_path( | |
| path='system/lib/wasmfs', | |
| filenames=['file.cpp', | |
| 'file_table.cpp', | |
| 'js_api.cpp', | |
| 'emscripten.cpp', | |
| 'paths.cpp', | |
| 'special_files.cpp', | |
| 'support.cpp', | |
| 'syscalls.cpp', | |
| 'wasmfs.cpp']) | |
| def can_use(self): | |
| return settings.WASMFS | |
| # Minimal syscall implementation, enough for printf. If this can be linked in | |
| # instead of the full WasmFS then it saves a lot of code size for simple | |
| # programs that don't need a full FS implementation. | |
| class libwasmfs_no_fs(Library): | |
| name = 'libwasmfs_no_fs' | |
| src_dir = 'system/lib/wasmfs' | |
| src_files = ['no_fs.c'] | |
| def can_use(self): | |
| # If the filesystem is forced then we definitely do not need this library. | |
| return settings.WASMFS and not settings.FORCE_FILESYSTEM | |
| class libwasmfs_noderawfs(Library): | |
| name = 'libwasmfs_noderawfs' | |
| cflags = ['-fno-exceptions', '-std=c++17'] | |
| includes = ['system/lib/wasmfs'] | |
| def get_files(self): | |
| return files_in_path( | |
| path='system/lib/wasmfs/backends', | |
| filenames=['noderawfs_root.cpp']) | |
| def can_use(self): | |
| return settings.WASMFS and settings.NODERAWFS | |
| class libhtml5(Library): | |
| name = 'libhtml5' | |
| includes = ['system/lib/libc'] | |
| cflags = ['-Oz'] | |
| src_dir = 'system/lib/html5' | |
| src_glob = '*.c' | |
| class CompilerRTLibrary(Library): | |
| cflags = ['-fno-builtin'] | |
| # compiler_rt files can't currently be part of LTO although we are hoping to remove this | |
| # restriction soon: https://reviews.llvm.org/D71738 | |
| force_object_files = True | |
| class libubsan_minimal_rt(CompilerRTLibrary, MTLibrary): | |
| name = 'libubsan_minimal_rt' | |
| never_force = True | |
| includes = ['system/lib/compiler-rt/lib'] | |
| src_dir = 'system/lib/compiler-rt/lib/ubsan_minimal' | |
| src_files = ['ubsan_minimal_handlers.cpp'] | |
| class libsanitizer_common_rt(CompilerRTLibrary, MTLibrary): | |
| name = 'libsanitizer_common_rt' | |
| includes = ['system/lib/compiler-rt/lib', | |
| 'system/lib/libc'] | |
| never_force = True | |
| cflags = [ | |
| '-D_LARGEFILE64_SOURCE', | |
| # The upstream code has many format violations and suppresses it with | |
| # -Wno-format, so we match that. | |
| # https://github.com/llvm/llvm-project/blob/da675b922cca3dc9a76642d792e882979a3d8c82/compiler-rt/lib/sanitizer_common/CMakeLists.txt#L225-L226 | |
| # TODO Remove this when the issues are resolved. | |
| '-Wno-format', | |
| ] | |
| src_dir = 'system/lib/compiler-rt/lib/sanitizer_common' | |
| src_glob = '*.cpp' | |
| src_glob_exclude = ['sanitizer_common_nolibc.cpp'] | |
| class SanitizerLibrary(CompilerRTLibrary, MTLibrary): | |
| never_force = True | |
| includes = ['system/lib/compiler-rt/lib'] | |
| src_glob = '*.cpp' | |
| class libubsan_rt(SanitizerLibrary): | |
| name = 'libubsan_rt' | |
| includes = ['system/lib/libc'] | |
| cflags = ['-DUBSAN_CAN_USE_CXXABI'] | |
| src_dir = 'system/lib/compiler-rt/lib/ubsan' | |
| src_glob_exclude = ['ubsan_diag_standalone.cpp'] | |
| class liblsan_common_rt(SanitizerLibrary): | |
| name = 'liblsan_common_rt' | |
| src_dir = 'system/lib/compiler-rt/lib/lsan' | |
| src_glob = 'lsan_common*.cpp' | |
| class liblsan_rt(SanitizerLibrary): | |
| name = 'liblsan_rt' | |
| includes = ['system/lib/libc'] | |
| src_dir = 'system/lib/compiler-rt/lib/lsan' | |
| src_glob_exclude = ['lsan_common.cpp', 'lsan_common_mac.cpp', 'lsan_common_linux.cpp', | |
| 'lsan_common_emscripten.cpp'] | |
| class libasan_rt(SanitizerLibrary): | |
| name = 'libasan_rt' | |
| includes = ['system/lib/libc'] | |
| src_dir = 'system/lib/compiler-rt/lib/asan' | |
| # This library is used when STANDALONE_WASM is set. In that mode, we don't | |
| # want to depend on JS, and so this library contains implementations of | |
| # things that we'd normally do in JS. That includes some general things | |
| # as well as some additional musl components (that normally we reimplement | |
| # in JS as it's more efficient that way). | |
| class libstandalonewasm(MuslInternalLibrary): | |
| name = 'libstandalonewasm' | |
| # LTO defeats the weak linking trick used in __original_main.c | |
| force_object_files = True | |
| cflags = ['-Os', '-fno-builtin'] | |
| src_dir = 'system/lib' | |
| def __init__(self, **kwargs): | |
| self.is_mem_grow = kwargs.pop('is_mem_grow') | |
| self.is_pure = kwargs.pop('is_pure') | |
| self.nocatch = kwargs.pop('nocatch') | |
| super().__init__(**kwargs) | |
| def get_base_name(self): | |
| name = super().get_base_name() | |
| if self.nocatch: | |
| name += '-nocatch' | |
| if self.is_mem_grow: | |
| name += '-memgrow' | |
| if self.is_pure: | |
| name += '-pure' | |
| return name | |
| def get_cflags(self): | |
| cflags = super().get_cflags() | |
| cflags += ['-DNDEBUG', '-DEMSCRIPTEN_STANDALONE_WASM'] | |
| if self.is_mem_grow: | |
| cflags += ['-DEMSCRIPTEN_MEMORY_GROWTH'] | |
| if self.is_pure: | |
| cflags += ['-DEMSCRIPTEN_PURE_WASI'] | |
| if self.nocatch: | |
| cflags.append('-DEMSCRIPTEN_NOCATCH') | |
| return cflags | |
| def vary_on(cls): | |
| return super().vary_on() + ['is_mem_grow', 'is_pure', 'nocatch'] | |
| def get_default_variation(cls, **kwargs): | |
| return super().get_default_variation( | |
| is_mem_grow=settings.ALLOW_MEMORY_GROWTH, | |
| is_pure=settings.PURE_WASI or settings.GROWABLE_ARRAYBUFFERS, | |
| nocatch=settings.DISABLE_EXCEPTION_CATCHING and not settings.WASM_EXCEPTIONS, | |
| **kwargs, | |
| ) | |
| def get_files(self): | |
| files = files_in_path( | |
| path='system/lib/standalone', | |
| filenames=['standalone.c', | |
| 'standalone_wasm_stdio.c', | |
| '__main_void.c']) | |
| # It is more efficient to use JS methods for time, normally. | |
| files += files_in_path( | |
| path='system/lib/libc/musl/src/time', | |
| filenames=['__secs_to_tm.c', | |
| '__tz.c', | |
| 'gettimeofday.c', | |
| 'localtime_r.c', | |
| 'gmtime_r.c', | |
| 'mktime.c', | |
| 'strptime.c', | |
| 'timegm.c', | |
| 'time.c']) | |
| # It is more efficient to use JS for __assert_fail, as it avoids always | |
| # including fprintf etc. | |
| files += files_in_path( | |
| path='system/lib/libc/musl/src/exit', | |
| filenames=['assert.c', 'exit.c']) | |
| return files | |
| def can_use(self): | |
| return super().can_use() and settings.STANDALONE_WASM | |
| class libjsmath(Library): | |
| name = 'libjsmath' | |
| cflags = ['-Os'] | |
| src_dir = 'system/lib' | |
| src_files = ['jsmath.c'] | |
| def can_use(self): | |
| return super().can_use() and settings.JS_MATH | |
| class libstubs(DebugLibrary): | |
| name = 'libstubs' | |
| src_dir = 'system/lib/libc' | |
| includes = ['system/lib/libc/musl/src/include'] | |
| src_files = ['emscripten_syscall_stubs.c', 'emscripten_libc_stubs.c'] | |
| def get_libs_to_link(options): | |
| libs_to_link = [] | |
| if options.nostdlib: | |
| return libs_to_link | |
| already_included = set() | |
| system_libs_map = Library.get_usable_variations() | |
| # Setting this in the environment will avoid checking dependencies and make | |
| # building big projects a little faster 1 means include everything; otherwise | |
| # it can be the name of a lib (libc++, etc.). | |
| # You can provide 1 to include everything, or a comma-separated list with the | |
| # ones you want | |
| force_include = [] | |
| force = os.environ.get('EMCC_FORCE_STDLIBS') | |
| # Setting this will only use the forced libs in EMCC_FORCE_STDLIBS. This | |
| # avoids spending time checking for unresolved symbols in your project files, | |
| # which can speed up linking, but if you do not have the proper list of | |
| # actually needed libraries, errors can occur. | |
| only_forced = os.environ.get('EMCC_ONLY_FORCED_STDLIBS') | |
| if only_forced: | |
| # One of the purposes EMCC_ONLY_FORCED_STDLIBS was to skip the scanning | |
| # of the input files for reverse dependencies. | |
| diagnostics.warning('deprecated', 'EMCC_ONLY_FORCED_STDLIBS is deprecated. Use `-nostdlib` to avoid linking standard libraries') | |
| if force == '1': | |
| force_include = [name for name, lib in system_libs_map.items() if not lib.never_force] | |
| elif force is not None: | |
| force_include = force.split(',') | |
| if force_include: | |
| logger.debug(f'forcing stdlibs: {force_include}') | |
| def add_library(libname, whole_archive=False): | |
| lib = system_libs_map[libname] | |
| if lib.name in already_included: | |
| return | |
| already_included.add(lib.name) | |
| logger.debug('including %s (%s)' % (lib.name, lib.get_filename())) | |
| need_whole_archive = lib.name in force_include and lib.get_ext() == '.a' | |
| libs_to_link.append((lib.get_link_flag(), whole_archive or need_whole_archive)) | |
| if not options.nostartfiles: | |
| if settings.PTHREADS: | |
| add_library('crtbegin-mt') | |
| if not settings.SIDE_MODULE: | |
| if settings.STANDALONE_WASM: | |
| if settings.EXPECT_MAIN: | |
| add_library('crt1') | |
| else: | |
| add_library('crt1_reactor') | |
| elif settings.PROXY_TO_PTHREAD: | |
| add_library('crt1_proxy_main') | |
| if settings.SIDE_MODULE: | |
| return libs_to_link | |
| # We add the forced libs last so that any libraries that are added in the normal | |
| # sequence below are added in the correct order even when they are also part of | |
| # EMCC_FORCE_STDLIBS. | |
| def add_forced_libs(): | |
| for forced in force_include: | |
| if forced not in system_libs_map: | |
| utils.exit_with_error('invalid forced library: %s', forced) | |
| add_library(forced) | |
| if options.nodefaultlibs: | |
| add_forced_libs() | |
| return libs_to_link | |
| sanitize = settings.USE_LSAN or settings.USE_ASAN or settings.UBSAN_RUNTIME | |
| def add_sanitizer_libs(): | |
| if settings.USE_ASAN: | |
| force_include.append('libasan_rt') | |
| add_library('libasan_rt') | |
| elif settings.USE_LSAN: | |
| force_include.append('liblsan_rt') | |
| add_library('liblsan_rt') | |
| if settings.UBSAN_RUNTIME == 1: | |
| add_library('libubsan_minimal_rt') | |
| elif settings.UBSAN_RUNTIME == 2: | |
| add_library('libubsan_rt') | |
| if settings.USE_LSAN or settings.USE_ASAN: | |
| add_library('liblsan_common_rt') | |
| if sanitize: | |
| add_library('libsanitizer_common_rt') | |
| if only_forced: | |
| add_library('libcompiler_rt') | |
| add_sanitizer_libs() | |
| add_forced_libs() | |
| return libs_to_link | |
| if settings.AUTO_NATIVE_LIBRARIES: | |
| add_library('libGL') | |
| add_library('libal') | |
| add_library('libhtml5') | |
| # JS math must come before anything else, so that it overrides the normal | |
| # libc math. | |
| if settings.JS_MATH: | |
| add_library('libjsmath') | |
| # C libraries that override libc must come before it | |
| if settings.PRINTF_LONG_DOUBLE: | |
| add_library('libprintf_long_double') | |
| # See comment in libc_optz itself | |
| if settings.SHRINK_LEVEL >= 2 and not settings.LINKABLE and \ | |
| not os.environ.get('EMCC_FORCE_STDLIBS'): | |
| add_library('libc_optz') | |
| if settings.STANDALONE_WASM: | |
| add_library('libstandalonewasm') | |
| if settings.ALLOW_UNIMPLEMENTED_SYSCALLS: | |
| add_library('libstubs') | |
| if not options.nolibc: | |
| if not settings.EXIT_RUNTIME: | |
| add_library('libnoexit') | |
| add_library('libc') | |
| if settings.MALLOC == 'mimalloc': | |
| add_library('libmimalloc') | |
| if settings.USE_ASAN: | |
| # See https://github.com/emscripten-core/emscripten/issues/23288#issuecomment-2571648258 | |
| utils.exit_with_error('mimalloc is not compatible with -fsanitize=address') | |
| elif settings.MALLOC != 'none': | |
| add_library('libmalloc') | |
| add_library('libcompiler_rt') | |
| if settings.LINK_AS_CXX: | |
| add_library('libc++') | |
| if settings.LINK_AS_CXX or sanitize: | |
| add_library('libc++abi') | |
| if settings.WASM_EXCEPTIONS: | |
| add_library('libunwind') | |
| if settings.PROXY_POSIX_SOCKETS: | |
| add_library('libsockets_proxy') | |
| else: | |
| add_library('libsockets') | |
| if settings.WASM_WORKERS and (not settings.SINGLE_FILE and not settings.MAIN_MODULE): | |
| # When we include libwasm_workers we use `--whole-archive` to ensure | |
| # that the static constructor (`emscripten_wasm_worker_main_thread_initialize`) | |
| # is run. | |
| add_library('libwasm_workers', whole_archive=True) | |
| if settings.WASMFS: | |
| # Link in the no-fs version first, so that if it provides all the needed | |
| # system libraries then WasmFS is not linked in at all. (We only do this if | |
| # the filesystem is not forced; if it is then we know we definitely need the | |
| # whole thing, and it would be unnecessary work to try to link in the no-fs | |
| # version). | |
| if not settings.FORCE_FILESYSTEM: | |
| add_library('libwasmfs_no_fs') | |
| add_library('libwasmfs') | |
| add_sanitizer_libs() | |
| add_forced_libs() | |
| return libs_to_link | |
| def calculate(options): | |
| libs_to_link = get_libs_to_link(options) | |
| # When LINKABLE is set the entire link command line is wrapped in --whole-archive by | |
| # building.link_ldd. And since --whole-archive/--no-whole-archive processing does not nest we | |
| # shouldn't add any extra `--no-whole-archive` or we will undo the intent of building.link_ldd. | |
| if settings.LINKABLE or settings.SIDE_MODULE: | |
| return [l[0] for l in libs_to_link] | |
| # Wrap libraries in --whole-archive, as needed. We need to do this last | |
| # since otherwise the abort sorting won't make sense. | |
| ret = [] | |
| in_group = False | |
| for name, need_whole_archive in libs_to_link: | |
| if need_whole_archive and not in_group: | |
| ret.append('--whole-archive') | |
| in_group = True | |
| if in_group and not need_whole_archive: | |
| ret.append('--no-whole-archive') | |
| in_group = False | |
| ret.append(name) | |
| if in_group: | |
| ret.append('--no-whole-archive') | |
| return ret | |
| def safe_copytree(src, dst, excludes=None): | |
| # We cannot use `shutil.copytree` there because we need to ensure the | |
| # output tree is writable, and in some cases the emscripten tree | |
| # itself is readonly (e.g. NixOS). | |
| # Even if we pass copy_function=safe_copy python's `shutil.copytree` | |
| # will use its internal logic for copying directories and it will | |
| # unconditionally copy the source directory's mode bits. | |
| os.makedirs(dst, exist_ok=True) | |
| for entry in os.scandir(src): | |
| if excludes and entry.name in excludes: | |
| continue | |
| srcname = os.path.join(src, entry.name) | |
| dstname = os.path.join(dst, entry.name) | |
| if entry.is_dir(): | |
| safe_copytree(srcname, dstname, excludes) | |
| else: | |
| utils.safe_copy(srcname, dstname) | |
| def install_system_headers(stamp): | |
| install_dirs = { | |
| 'system/include': '', | |
| 'system/lib/compiler-rt/include': '', | |
| 'system/lib/libunwind/include': '', | |
| # Copy the generic arch files first then | |
| 'system/lib/libc/musl/arch/generic/bits': 'bits', | |
| # Then overlay the emscripten directory on top. | |
| # This mimics how musl itself installs its headers. | |
| 'system/lib/libc/musl/arch/emscripten/bits': 'bits', | |
| 'system/lib/libc/musl/include': '', | |
| 'system/lib/libcxx/include': 'c++/v1', | |
| 'system/lib/libcxxabi/include': 'c++/v1', | |
| 'system/lib/mimalloc/include': '', | |
| } | |
| target_include_dir = cache.get_include_dir() | |
| for src, dest in install_dirs.items(): | |
| src = utils.path_from_root(src) | |
| dest = os.path.join(target_include_dir, dest) | |
| safe_copytree(src, dest, excludes={'alltypes.h.in'}) | |
| pkgconfig_src = utils.path_from_root('system/lib/pkgconfig') | |
| pkgconfig_dest = cache.get_sysroot_dir('lib/pkgconfig') | |
| safe_copytree(pkgconfig_src, pkgconfig_dest) | |
| bin_src = utils.path_from_root('system/bin') | |
| bin_dest = cache.get_sysroot_dir('bin') | |
| safe_copytree(bin_src, bin_dest) | |
| # Create a version header based on the emscripten-version.txt | |
| version_file = cache.get_include_dir('emscripten/version.h') | |
| utils.write_file(version_file, textwrap.dedent(f'''\ | |
| /* Automatically generated by tools/system_libs.py */ | |
| #define __EMSCRIPTEN_MAJOR__ {utils.EMSCRIPTEN_VERSION_MAJOR} | |
| #define __EMSCRIPTEN_MINOR__ {utils.EMSCRIPTEN_VERSION_MINOR} | |
| #define __EMSCRIPTEN_TINY__ {utils.EMSCRIPTEN_VERSION_TINY} | |
| // Legacy mixed-case macros: | |
| #define __EMSCRIPTEN_major__ __EMSCRIPTEN_MAJOR__ | |
| #define __EMSCRIPTEN_minor__ __EMSCRIPTEN_MINOR__ | |
| #define __EMSCRIPTEN_tiny__ __EMSCRIPTEN_TINY__ | |
| #pragma clang deprecated(__EMSCRIPTEN_major__, "Use __EMSCRIPTEN_MAJOR__ instead") | |
| #pragma clang deprecated(__EMSCRIPTEN_minor__, "Use __EMSCRIPTEN_MINOR__ instead") | |
| #pragma clang deprecated(__EMSCRIPTEN_tiny__, "Use __EMSCRIPTEN_TINY__ instead") | |
| ''')) | |
| # Create a stamp file that signal that the headers have been installed | |
| # Removing this file, or running `emcc --clear-cache` or running | |
| # `./embuilder build sysroot --force` will cause the re-installation of | |
| # the system headers. | |
| utils.write_file(stamp, 'x') | |
| return stamp | |
| def ensure_sysroot(): | |
| cache.get('sysroot_install.stamp', install_system_headers, what='system headers') | |
| def build_deferred(): | |
| assert USE_NINJA | |
| top_level_ninja = get_top_level_ninja_file() | |
| if os.path.isfile(top_level_ninja): | |
| run_ninja(os.path.dirname(top_level_ninja)) | |
Xet Storage Details
- Size:
- 89.8 kB
- Xet hash:
- 363654b75c30b2bc17aedc9e15092f209270768a3be0412e264cce747c541e08
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.