예제 #1
0
def load_metadata_wasm(metadata_raw, DEBUG):
    try:
        metadata_json = json.loads(metadata_raw)
    except Exception:
        logger.error(
            'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n'
            + metadata_raw)
        raise

    metadata = {
        'declares': [],
        'globalImports': [],
        'staticBump': 0,
        'tableSize': 0,
        'exports': [],
        'namedGlobals': {},
        'emJsFuncs': {},
        'asmConsts': {},
        'invokeFuncs': [],
        'features': [],
        'mainReadsParams': 1,
    }
    legacy_keys = set(
        ['implementedFunctions', 'initializers', 'simd', 'externs'])

    assert 'tableSize' in metadata_json.keys()
    for key, value in metadata_json.items():
        if key in legacy_keys:
            continue
        if key not in metadata:
            exit_with_error(
                'unexpected metadata key received from wasm-emscripten-finalize: %s',
                key)
        metadata[key] = value

    # Support older metadata when asmConsts values were lists.  We only use the first element
    # nowadays
    # TODO(sbc): remove this once binaryen has been changed to only emit the single element
    metadata['asmConsts'] = {
        k: v[0] if type(v) is list else v
        for k, v in metadata['asmConsts'].items()
    }

    if DEBUG:
        logger.debug("Metadata parsed: " + pprint.pformat(metadata))

    # Calculate the subset of exports that were explicitly marked with llvm.used.
    # These are any exports that were not requested on the command line and are
    # not known auto-generated system functions.
    unexpected_exports = [
        e for e in metadata['exports'] if treat_as_user_function(e)
    ]
    unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports]
    unexpected_exports = [
        e for e in unexpected_exports if e not in settings.EXPORTED_FUNCTIONS
    ]
    building.user_requested_exports.update(unexpected_exports)
    settings.EXPORTED_FUNCTIONS.extend(unexpected_exports)

    return metadata
예제 #2
0
def create_sending(invoke_funcs, metadata):
    em_js_funcs = set(metadata['emJsFuncs'].keys())
    declares = [asmjs_mangle(d) for d in metadata['declares']]
    externs = [asmjs_mangle(e) for e in metadata['globalImports']]
    send_items = set(invoke_funcs + declares + externs)
    send_items.update(em_js_funcs)

    def fix_import_name(g):
        # Unlike fastcomp the wasm backend doesn't use the '_' prefix for native
        # symbols.  Emscripten currently expects symbols to start with '_' so we
        # artificially add them to the output of emscripten-wasm-finalize and them
        # strip them again here.
        # note that we don't do this for EM_JS functions (which, rarely, may have
        # a '_' prefix)
        if g.startswith('_') and g not in em_js_funcs:
            return g[1:]
        return g

    send_items_map = OrderedDict()
    for name in send_items:
        internal_name = fix_import_name(name)
        if internal_name in send_items_map:
            exit_with_error('duplicate symbol in exports to wasm: %s', name)
        send_items_map[internal_name] = name

    add_standard_wasm_imports(send_items_map)

    sorted_keys = sorted(send_items_map.keys())
    return '{\n  ' + ',\n  '.join('"' + k + '": ' + send_items_map[k]
                                  for k in sorted_keys) + '\n}'
예제 #3
0
def report_missing_symbols(js_library_funcs):
    # Report any symbol that was explicitly exported but is present neither
    # as a native function nor as a JS library function.
    defined_symbols = set(
        asmjs_mangle(e) for e in settings.WASM_EXPORTS).union(js_library_funcs)
    missing = set(settings.USER_EXPORTED_FUNCTIONS) - defined_symbols
    for symbol in sorted(missing):
        diagnostics.warning('undefined',
                            f'undefined exported symbol: "{symbol}"')

    # Special hanlding for the `_main` symbol

    if settings.STANDALONE_WASM:
        # standalone mode doesn't use main, and it always reports missing entry point at link time.
        # In this mode we never expect _main in the export list.
        return

    # PROXY_TO_PTHREAD only makes sense with a main(), so error if one is
    # missing. note that when main() might arrive from another module we cannot
    # error here.
    if settings.PROXY_TO_PTHREAD and '_main' not in defined_symbols and \
       not settings.RELOCATABLE:
        exit_with_error(
            'PROXY_TO_PTHREAD proxies main() for you, but no main exists')

    if settings.IGNORE_MISSING_MAIN:
        # The default mode for emscripten is to ignore the missing main function allowing
        # maximum compatibility.
        return

    if settings.EXPECT_MAIN and 'main' not in settings.WASM_EXPORTS:
        # For compatibility with the output of wasm-ld we use the same wording here in our
        # error message as if wasm-ld had failed (i.e. in LLD_REPORT_UNDEFINED mode).
        exit_with_error(
            'entry symbol not defined (pass --no-entry to suppress): main')
예제 #4
0
def load_metadata_json(metadata_raw):
    try:
        metadata_json = json.loads(metadata_raw)
    except Exception:
        logger.error(
            'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n'
            + metadata_raw)
        raise

    metadata = {
        'declares': [],
        'globalImports': [],
        'exports': [],
        'namedGlobals': {},
        'emJsFuncs': {},
        'asmConsts': {},
        'invokeFuncs': [],
        'features': [],
        'mainReadsParams': 1,
    }

    for key, value in metadata_json.items():
        if key not in metadata:
            exit_with_error(
                'unexpected metadata key received from wasm-emscripten-finalize: %s',
                key)
        metadata[key] = value

    if DEBUG:
        logger.debug("Metadata parsed: " + pprint.pformat(metadata))

    return metadata
예제 #5
0
def error_on_legacy_suite_names(args):
    for a in args:
        if a.startswith('wasm') and not any(
                a.startswith(p) for p in ('wasm2js', 'wasmfs', 'wasm64')):
            new = a.replace('wasm', 'core', 1)
            utils.exit_with_error(
                '`%s` test suite has been replaced with `%s`', a, new)
예제 #6
0
def c_to_s(c):
    if c == 'WASM_RT_I32':
        return 'i'
    elif c == 'WASM_RT_I64':
        return 'j'
    elif c == 'WASM_RT_F32':
        return 'f'
    elif c == 'WASM_RT_F64':
        return 'd'
    else:
        exit_with_error('invalid wasm2c type element:' + str(c))
예제 #7
0
def s_to_c(s):
    if s == 'v':
        return 'void'
    elif s == 'i':
        return 'u32'
    elif s == 'j':
        return 'u64'
    elif s == 'f':
        return 'f32'
    elif s == 'd':
        return 'f64'
    else:
        exit_with_error('invalid sig element:' + str(s))
예제 #8
0
def load_metadata_json(metadata_raw):
    try:
        metadata_json = json.loads(metadata_raw)
    except Exception:
        logger.error(
            'emscript: failure to parse metadata output from wasm-emscripten-finalize. raw output is: \n'
            + metadata_raw)
        raise

    metadata = {
        'declares': [],
        'globalImports': [],
        'exports': [],
        'namedGlobals': {},
        'emJsFuncs': {},
        'asmConsts': {},
        'invokeFuncs': [],
        'features': [],
        'mainReadsParams': 1,
    }

    for key, value in metadata_json.items():
        if key not in metadata:
            exit_with_error(
                'unexpected metadata key received from wasm-emscripten-finalize: %s',
                key)
        metadata[key] = value

    if DEBUG:
        logger.debug("Metadata parsed: " + pprint.pformat(metadata))

    expected_exports = set(settings.EXPORTED_FUNCTIONS)
    expected_exports.update(asmjs_mangle(s) for s in settings.REQUIRED_EXPORTS)

    # Calculate the subset of exports that were explicitly marked with llvm.used.
    # These are any exports that were not requested on the command line and are
    # not known auto-generated system functions.
    unexpected_exports = [
        e for e in metadata['exports'] if treat_as_user_function(e)
    ]
    unexpected_exports = [asmjs_mangle(e) for e in unexpected_exports]
    unexpected_exports = [
        e for e in unexpected_exports if e not in expected_exports
    ]
    building.user_requested_exports.update(unexpected_exports)
    settings.EXPORTED_FUNCTIONS.extend(unexpected_exports)

    return metadata
예제 #9
0
def compare_metadata(metadata, pymetadata):
    if sorted(metadata.keys()) != sorted(pymetadata.keys()):
        print(sorted(metadata.keys()))
        print(sorted(pymetadata.keys()))
        exit_with_error('metadata keys mismatch')
    for key in metadata:
        old = metadata[key]
        new = pymetadata[key]
        if key == 'features':
            old = sorted(old)
            new = sorted(new)
        if old != new:
            print(key)
            open(path_from_root('first.txt'), 'w').write(pprint.pformat(old))
            open(path_from_root('second.txt'), 'w').write(pprint.pformat(new))
            print(pprint.pformat(old))
            print(pprint.pformat(new))
            exit_with_error('metadata mismatch')
예제 #10
0
        def retrieve():
            # retrieve from remote server
            logger.info(f'retrieving port: {name} from {url}')
            try:
                import requests
                response = requests.get(url)
                data = response.content
            except ImportError:
                from urllib.request import urlopen
                f = urlopen(url)
                data = f.read()

            if sha512hash:
                actual_hash = hashlib.sha512(data).hexdigest()
                if actual_hash != sha512hash:
                    utils.exit_with_error(
                        f'Unexpected hash: {actual_hash}\n'
                        'If you are updating the port, please update the hash.'
                    )
            utils.write_binary(fullpath, data)
예제 #11
0
def read_ports():
    expected_attrs = ['get', 'clear', 'process_args', 'show', 'needed']
    for filename in os.listdir(ports_dir):
        if not filename.endswith('.py') or filename == '__init__.py':
            continue
        filename = os.path.splitext(filename)[0]
        port = __import__(filename, globals(), level=1)
        ports.append(port)
        port.name = filename
        ports_by_name[port.name] = port
        for a in expected_attrs:
            assert hasattr(port, a), 'port %s is missing %s' % (port, a)
        if not hasattr(port, 'process_dependencies'):
            port.process_dependencies = lambda x: 0
        if not hasattr(port, 'linker_setup'):
            port.linker_setup = lambda x, y: 0
        if not hasattr(port, 'deps'):
            port.deps = []

    for dep in port.deps:
        if dep not in ports_by_name:
            utils.exit_with_error('unknown dependency in port: %s' % dep)
예제 #12
0
def emscript(in_wasm, out_wasm, outfile_js, memfile, DEBUG):
    # Overview:
    #   * Run wasm-emscripten-finalize to extract metadata and modify the binary
    #     to use emscripten's wasm<->JS ABI
    #   * Use the metadata to generate the JS glue that goes with the wasm

    if settings.SINGLE_FILE:
        # placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen
        settings.WASM_BINARY_FILE = '<<< WASM_BINARY_FILE >>>'
    else:
        # set file locations, so that JS glue can find what it needs
        settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string(
            os.path.basename(out_wasm))

    metadata = finalize_wasm(in_wasm, out_wasm, memfile, DEBUG)

    update_settings_glue(metadata, DEBUG)

    if settings.SIDE_MODULE:
        if metadata['asmConsts']:
            exit_with_error('EM_ASM is not supported in side modules')
        if metadata['emJsFuncs']:
            exit_with_error('EM_JS is not supported in side modules')
        logger.debug('emscript: skipping remaining js glue generation')
        return

    if DEBUG:
        logger.debug('emscript: js compiler glue')
        t = time.time()

    # memory and global initializers

    if settings.RELOCATABLE:
        dylink_sec = webassembly.parse_dylink_section(in_wasm)
        static_bump = align_memory(dylink_sec.mem_size)
        set_memory(static_bump)
        logger.debug('stack_base: %d, stack_max: %d, heap_base: %d',
                     settings.STACK_BASE, settings.STACK_MAX,
                     settings.HEAP_BASE)

        # When building relocatable output (e.g. MAIN_MODULE) the reported table
        # size does not include the reserved slot at zero for the null pointer.
        # So we need to offset the elements by 1.
        if settings.INITIAL_TABLE == -1:
            settings.INITIAL_TABLE = dylink_sec.table_size + 1

    glue, forwarded_data = compile_settings()
    if DEBUG:
        logger.debug('  emscript: glue took %s seconds' % (time.time() - t))
        t = time.time()

    forwarded_json = json.loads(forwarded_data)

    pre, post = glue.split('// EMSCRIPTEN_END_FUNCS')

    exports = metadata['exports']

    if settings.ASYNCIFY:
        exports += [
            'asyncify_start_unwind', 'asyncify_stop_unwind',
            'asyncify_start_rewind', 'asyncify_stop_rewind'
        ]

    report_missing_symbols(forwarded_json['libraryFunctions'])

    if not outfile_js:
        logger.debug('emscript: skipping remaining js glue generation')
        return

    if settings.MINIMAL_RUNTIME:
        # In MINIMAL_RUNTIME, atinit exists in the postamble part
        post = apply_static_code_hooks(forwarded_json, post)
    else:
        # In regular runtime, atinits etc. exist in the preamble part
        pre = apply_static_code_hooks(forwarded_json, pre)

    asm_consts = create_asm_consts(metadata)
    em_js_funcs = create_em_js(metadata)
    asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts]
    asm_const_map = 'var ASM_CONSTS = {\n  ' + ',  \n '.join(
        asm_const_pairs) + '\n};\n'
    pre = pre.replace('// === Body ===',
                      ('// === Body ===\n\n' + asm_const_map +
                       '\n'.join(em_js_funcs) + '\n'))

    with open(outfile_js, 'w') as out:
        out.write(normalize_line_endings(pre))
        pre = None

        invoke_funcs = metadata['invokeFuncs']
        sending = create_sending(invoke_funcs, metadata)
        receiving = create_receiving(exports)

        if settings.MINIMAL_RUNTIME:
            if settings.DECLARE_ASM_MODULE_EXPORTS:
                post = compute_minimal_runtime_initializer_and_exports(
                    post, exports, receiving)
            receiving = ''

        module = create_module(sending, receiving, invoke_funcs, metadata)

        write_output_file(out, module)

        out.write(normalize_line_endings(post))
        module = None
예제 #13
0
    def fetch_project(name, url, subdir, sha512hash=None):
        # To compute the sha512 hash, run `curl URL | sha512sum`.
        fullname = os.path.join(Ports.get_dir(), name)

        # EMCC_LOCAL_PORTS: A hacky way to use a local directory for a port. This
        #                   is not tested but can be useful for debugging
        #                   changes to a port.
        #
        # if EMCC_LOCAL_PORTS is set, we use a local directory as our ports. This is useful
        # for testing. This env var should be in format
        #     name=dir,name=dir
        # e.g.
        #     sdl2=/home/username/dev/ports/SDL2
        # so you could run
        #     EMCC_LOCAL_PORTS="sdl2=/home/alon/Dev/ports/SDL2" ./tests/runner.py browser.test_sdl2_mouse
        # this will simply copy that directory into the ports directory for sdl2, and use that. It also
        # clears the build, so that it is rebuilt from that source.
        local_ports = os.environ.get('EMCC_LOCAL_PORTS')
        if local_ports:
            logger.warning('using local ports: %s' % local_ports)
            local_ports = [
                pair.split('=', 1) for pair in local_ports.split(',')
            ]
            with shared.Cache.lock():
                for local in local_ports:
                    if name == local[0]:
                        path = local[1]
                        if name not in ports_by_name:
                            utils.exit_with_error('%s is not a known port' %
                                                  name)
                        port = ports_by_name[name]
                        if not hasattr(port, 'SUBDIR'):
                            logger.error(
                                f'port {name} lacks .SUBDIR attribute, which we need in order to override it locally, please update it'
                            )
                            sys.exit(1)
                        subdir = port.SUBDIR
                        target = os.path.join(fullname, subdir)
                        if os.path.exists(target) and not dir_is_newer(
                                path, target):
                            logger.warning(
                                f'not grabbing local port: {name} from {path} to {fullname} (subdir: {subdir}) as the destination {target} is newer (run emcc --clear-ports if that is incorrect)'
                            )
                        else:
                            logger.warning(
                                f'grabbing local port: {name} from {path} to {fullname} (subdir: {subdir})'
                            )
                            shared.try_delete(fullname)
                            shutil.copytree(path, target)
                            Ports.clear_project_build(name)
                        return

        url_filename = url.rsplit('/')[-1]
        ext = url_filename.split('.', 1)[1]
        fullpath = fullname + '.' + ext

        if name not in Ports.name_cache:  # only mention each port once in log
            logger.debug(f'including port: {name}')
            logger.debug(f'    (at {fullname})')
            Ports.name_cache.add(name)

        def retrieve():
            # retrieve from remote server
            logger.info(f'retrieving port: {name} from {url}')
            try:
                import requests
                response = requests.get(url)
                data = response.content
            except ImportError:
                from urllib.request import urlopen
                f = urlopen(url)
                data = f.read()

            if sha512hash:
                actual_hash = hashlib.sha512(data).hexdigest()
                if actual_hash != sha512hash:
                    utils.exit_with_error(
                        f'Unexpected hash: {actual_hash}\n'
                        'If you are updating the port, please update the hash.'
                    )
            utils.write_binary(fullpath, data)

        marker = os.path.join(fullname, '.emscripten_url')

        def unpack():
            logger.info(f'unpacking port: {name}')
            shared.safe_ensure_dirs(fullname)
            shutil.unpack_archive(filename=fullpath, extract_dir=fullname)
            utils.write_file(marker, url + '\n')

        def up_to_date():
            if os.path.exists(marker):
                if utils.read_file(marker).strip() == url:
                    return True
            return False

        # before acquiring the lock we have an early out if the port already exists
        if up_to_date():
            return

        # main logic. do this under a cache lock, since we don't want multiple jobs to
        # retrieve the same port at once
        with shared.Cache.lock():
            if os.path.exists(fullpath):
                # Another early out in case another process build the library while we were
                # waiting for the lock
                if up_to_date():
                    return
                # file exists but tag is bad
                logger.warning(
                    'local copy of port is not correct, retrieving from remote server'
                )
                shared.try_delete(fullname)
                shared.try_delete(fullpath)

            retrieve()
            unpack()

            # we unpacked a new version, clear the build in the cache
            Ports.clear_project_build(name)
예제 #14
0
def do_wasm2c(infile):
    assert settings.STANDALONE_WASM
    WASM2C = config.NODE_JS + [path_from_root('node_modules/wasm2c/wasm2c.js')]
    WASM2C_DIR = path_from_root('node_modules/wasm2c')
    c_file = unsuffixed(infile) + '.wasm.c'
    h_file = unsuffixed(infile) + '.wasm.h'
    cmd = WASM2C + [infile, '-o', c_file]
    check_call(cmd)
    total = '''\
/*
 * This file was generated by emcc+wasm2c. To compile it, use something like
 *
 *   $CC FILE.c -O2 -lm -DWASM_RT_MAX_CALL_STACK_DEPTH=8000
 */
'''
    SEP = '\n/* ==================================== */\n'

    def bundle_file(filename):
        nonlocal total
        with open(filename) as f:
            total += '// ' + filename + '\n' + f.read() + SEP

    # hermeticize the C file, by bundling in the wasm2c/ includes
    headers = [(WASM2C_DIR, 'wasm-rt.h'), (WASM2C_DIR, 'wasm-rt-impl.h'),
               ('', h_file)]
    for header in headers:
        bundle_file(os.path.join(header[0], header[1]))
    # add the wasm2c output
    bundle_file(c_file)
    # add the wasm2c runtime
    bundle_file(os.path.join(WASM2C_DIR, 'wasm-rt-impl.c'))
    # add the support code
    support_files = ['base.c']
    if settings.AUTODEBUG:
        support_files.append('autodebug.c')
    if settings.EXPECT_MAIN:
        # TODO: add an option for direct OS access. For now, do that when building
        #       an executable with main, as opposed to a library
        support_files.append('os.c')
        support_files.append('main.c')
    else:
        support_files.append('os_sandboxed.c')
        support_files.append('reactor.c')
        # for a reactor, also append wasmbox_* API definitions
        with open(h_file, 'a') as f:
            f.write('''
// wasmbox_* API
// TODO: optional prefixing
extern void wasmbox_init(void);
''')
    for support_file in support_files:
        bundle_file(path_from_root(f'tools/wasm2c/{support_file}'))
    # remove #includes of the headers we bundled
    for header in headers:
        total = total.replace('#include "%s"\n' % header[1],
                              '/* include of %s */\n' % header[1])
    # generate the necessary invokes
    invokes = []
    for sig in re.findall(r"\/\* import\: 'env' 'invoke_(\w+)' \*\/", total):
        all_func_types = get_func_types(total)

        def name(i):
            return 'a' + str(i)

        wabt_sig = sig[0] + 'i' + sig[1:]
        typed_args = [
            s_to_c(sig[i]) + ' ' + name(i) for i in range(1, len(sig))
        ]
        full_typed_args = ['u32 fptr'] + typed_args
        types = [s_to_c(sig[i]) for i in range(1, len(sig))]
        args = [name(i) for i in range(1, len(sig))]
        c_func_type = s_to_c(
            sig[0]) + ' (*)(' + (', '.join(types) if types else 'void') + ')'
        if sig not in all_func_types:
            exit_with_error('could not find signature ' + sig +
                            ' in function types ' + str(all_func_types))
        type_index = all_func_types[sig]

        invokes.append(
            r'''
IMPORT_IMPL(%(return_type)s, Z_envZ_invoke_%(sig)sZ_%(wabt_sig)s, (%(full_typed_args)s), {
  VERBOSE_LOG("invoke\n"); // waka
  u32 sp = WASM_RT_ADD_PREFIX(Z_stackSaveZ_iv)();
  if (next_setjmp >= MAX_SETJMP_STACK) {
    abort_with_message("too many nested setjmps");
  }
  u32 id = next_setjmp++;
  int result = setjmp(setjmp_stack[id]);
  %(declare_return)s
  if (result == 0) {
    %(receive)sCALL_INDIRECT(w2c___indirect_function_table, %(c_func_type)s, %(type_index)s, fptr %(args)s);
    /* if we got here, no longjmp or exception happened, we returned normally */
  } else {
    /* A longjmp or an exception took us here. */
    WASM_RT_ADD_PREFIX(Z_stackRestoreZ_vi)(sp);
    WASM_RT_ADD_PREFIX(Z_setThrewZ_vii)(1, 0);
  }
  next_setjmp--;
  %(return)s
});
''' % {
                'return_type':
                s_to_c(sig[0]) if sig[0] != 'v' else 'void',
                'sig':
                sig,
                'wabt_sig':
                wabt_sig,
                'full_typed_args':
                ', '.join(full_typed_args),
                'type_index':
                type_index,
                'c_func_type':
                c_func_type,
                'args': (', ' + ', '.join(args)) if args else '',
                'declare_return':
                (s_to_c(sig[0]) +
                 ' returned_value = 0;') if sig[0] != 'v' else '',
                'receive':
                'returned_value = ' if sig[0] != 'v' else '',
                'return':
                'return returned_value;' if sig[0] != 'v' else ''
            })

    total += '\n'.join(invokes)

    # adjust sandboxing
    TRAP_OOB = 'TRAP(OOB)'
    assert total.count(TRAP_OOB) == 2
    if settings.WASM2C_SANDBOXING == 'full':
        pass  # keep it
    elif settings.WASM2C_SANDBOXING == 'none':
        total = total.replace(TRAP_OOB, '{}')
    elif settings.WASM2C_SANDBOXING == 'mask':
        assert not settings.ALLOW_MEMORY_GROWTH
        assert (settings.INITIAL_MEMORY &
                (settings.INITIAL_MEMORY - 1)) == 0, 'poewr of 2'
        total = total.replace(TRAP_OOB, '{}')
        MEM_ACCESS = '[addr]'
        assert total.count(MEM_ACCESS) == 3, '2 from wasm2c, 1 from runtime'
        total = total.replace(MEM_ACCESS,
                              '[addr & %d]' % (settings.INITIAL_MEMORY - 1))
    else:
        exit_with_error('bad sandboxing')

    # adjust prefixing: emit simple output that works with multiple libraries,
    # each compiled into its own single .c file, by adding 'static' in some places
    # TODO: decide on the proper pattern for this in an upstream discussion in
    #       wasm2c; another option would be to prefix all these things.
    for rep in [
            'uint32_t wasm_rt_register_func_type(',
            'void wasm_rt_trap(',
            'void wasm_rt_allocate_memory(',
            'uint32_t wasm_rt_grow_memory(',
            'void wasm_rt_allocate_table(',
            'jmp_buf g_jmp_buf',
            'uint32_t g_func_type_count',
            'FuncType* g_func_types',
            'uint32_t wasm_rt_call_stack_depth',
            'uint32_t g_saved_call_stack_depth',
    ]:
        # remove 'extern' from declaration
        total = total.replace('extern ' + rep, rep)
        # add 'static' to implementation
        old = total
        total = total.replace(rep, 'static ' + rep)
        assert old != total, f'did not find "{rep}"'

    # write out the final file
    with open(c_file, 'w') as out:
        out.write(total)
예제 #15
0
def emscript(in_wasm, out_wasm, outfile_js, memfile):
    # Overview:
    #   * Run wasm-emscripten-finalize to extract metadata and modify the binary
    #     to use emscripten's wasm<->JS ABI
    #   * Use the metadata to generate the JS glue that goes with the wasm

    if settings.SINGLE_FILE:
        # placeholder strings for JS glue, to be replaced with subresource locations in do_binaryen
        settings.WASM_BINARY_FILE = '<<< WASM_BINARY_FILE >>>'
    else:
        # set file locations, so that JS glue can find what it needs
        settings.WASM_BINARY_FILE = js_manipulation.escape_for_js_string(
            os.path.basename(out_wasm))

    metadata = finalize_wasm(in_wasm, out_wasm, memfile)

    update_settings_glue(out_wasm, metadata)

    if not settings.WASM_BIGINT and metadata['emJsFuncs']:
        module = webassembly.Module(in_wasm)
        types = module.get_types()
        import_map = {}
        for imp in module.get_imports():
            import_map[imp.field] = imp
        for em_js_func, raw in metadata.get('emJsFuncs', {}).items():
            c_sig = raw.split('<::>')[0].strip('()')
            if not c_sig or c_sig == 'void':
                c_sig = []
            else:
                c_sig = c_sig.split(',')
            if em_js_func in import_map:
                imp = import_map[em_js_func]
                assert (imp.kind == webassembly.ExternType.FUNC)
                signature = types[imp.type]
                if len(signature.params) != len(c_sig):
                    diagnostics.warning(
                        'em-js-i64',
                        'using 64-bit arguments in EM_JS function without WASM_BIGINT is not yet fully supported: `%s` (%s, %s)',
                        em_js_func, c_sig, signature.params)

    if settings.SIDE_MODULE:
        if metadata['asmConsts']:
            exit_with_error('EM_ASM is not supported in side modules')
        if metadata['emJsFuncs']:
            exit_with_error('EM_JS is not supported in side modules')
        logger.debug('emscript: skipping remaining js glue generation')
        return

    if DEBUG:
        logger.debug('emscript: js compiler glue')
        t = time.time()

    # memory and global initializers

    if settings.RELOCATABLE:
        dylink_sec = webassembly.parse_dylink_section(in_wasm)
        static_bump = align_memory(dylink_sec.mem_size)
        set_memory(static_bump)
        logger.debug('stack_base: %d, stack_max: %d, heap_base: %d',
                     settings.STACK_BASE, settings.STACK_MAX,
                     settings.HEAP_BASE)

        # When building relocatable output (e.g. MAIN_MODULE) the reported table
        # size does not include the reserved slot at zero for the null pointer.
        # So we need to offset the elements by 1.
        if settings.INITIAL_TABLE == -1:
            settings.INITIAL_TABLE = dylink_sec.table_size + 1

        if settings.ASYNCIFY:
            metadata['globalImports'] += [
                '__asyncify_state', '__asyncify_data'
            ]

    invoke_funcs = metadata['invokeFuncs']
    if invoke_funcs:
        settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$getWasmTableEntry']

    glue, forwarded_data = compile_settings()
    if DEBUG:
        logger.debug('  emscript: glue took %s seconds' % (time.time() - t))
        t = time.time()

    forwarded_json = json.loads(forwarded_data)

    if forwarded_json['warnings']:
        diagnostics.warning('js-compiler',
                            'warnings in JS library compilation')

    pre, post = glue.split('// EMSCRIPTEN_END_FUNCS')

    if settings.ASSERTIONS:
        pre += "function checkIncomingModuleAPI() {\n"
        for sym in settings.ALL_INCOMING_MODULE_JS_API:
            if sym not in settings.INCOMING_MODULE_JS_API:
                pre += f"  ignoredModuleProp('{sym}');\n"
        pre += "}\n"

    exports = metadata['exports']

    if settings.ASYNCIFY:
        exports += [
            'asyncify_start_unwind', 'asyncify_stop_unwind',
            'asyncify_start_rewind', 'asyncify_stop_rewind'
        ]

    report_missing_symbols(forwarded_json['librarySymbols'])

    if not outfile_js:
        logger.debug('emscript: skipping remaining js glue generation')
        return

    if settings.MINIMAL_RUNTIME:
        # In MINIMAL_RUNTIME, atinit exists in the postamble part
        post = apply_static_code_hooks(forwarded_json, post)
    else:
        # In regular runtime, atinits etc. exist in the preamble part
        pre = apply_static_code_hooks(forwarded_json, pre)

    asm_consts = create_asm_consts(metadata)
    em_js_funcs = create_em_js(metadata)
    asm_const_pairs = ['%s: %s' % (key, value) for key, value in asm_consts]
    asm_const_map = 'var ASM_CONSTS = {\n  ' + ',  \n '.join(
        asm_const_pairs) + '\n};\n'
    pre = pre.replace('// === Body ===',
                      ('// === Body ===\n\n' + asm_const_map +
                       '\n'.join(em_js_funcs) + '\n'))

    with open(outfile_js, 'w', encoding='utf-8') as out:
        out.write(normalize_line_endings(pre))
        pre = None

        sending = create_sending(invoke_funcs, metadata)
        receiving = create_receiving(exports)

        if settings.MINIMAL_RUNTIME:
            if settings.DECLARE_ASM_MODULE_EXPORTS:
                post = compute_minimal_runtime_initializer_and_exports(
                    post, exports, receiving)
            receiving = ''

        module = create_module(sending, receiving, invoke_funcs, metadata)

        write_output_file(out, module)

        out.write(normalize_line_endings(post))
        module = None