Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion embuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
'libunwind-wasmexcept',
'libnoexit',
'bullet',
'libstb_image',
'libwasmfs_no_fs',
]

# Additional tasks on top of MINIMAL_TASKS that are necessary for PIC testing on
Expand Down Expand Up @@ -127,7 +129,6 @@
'libfetch-mt',
'libwasmfs',
'libwasmfs-debug',
'libwasmfs_no_fs',
'giflib',
'sdl2',
'sdl2_gfx',
Expand Down
14 changes: 7 additions & 7 deletions system/lib/standalone/standalone.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,21 @@ void __cxa_throw(void* ptr, void* type, void* destructor) {
// WasmFS integration. We stub out file preloading and such, that are not
// expected to work anyhow.

size_t _wasmfs_get_num_preloaded_files() { return 0; }
int _wasmfs_get_num_preloaded_files() { return 0; }

size_t _wasmfs_get_num_preloaded_dirs() { return 0; }
int _wasmfs_get_num_preloaded_dirs() { return 0; }

int _wasmfs_get_preloaded_file_size(int index) { return 0; }
size_t _wasmfs_get_preloaded_file_size(uint32_t index) { return 0; }

int _wasmfs_get_preloaded_file_mode(int index) { return 0; }

void _wasmfs_copy_preloaded_file_data(int index, void* buffer) {}
void _wasmfs_copy_preloaded_file_data(uint32_t index, uint8_t* buffer) {}

void _wasmfs_get_preloaded_parent_path(int index, void* buffer) {}
void _wasmfs_get_preloaded_parent_path(int index, char* buffer) {}

void _wasmfs_get_preloaded_child_path(int index, void* buffer) {}
void _wasmfs_get_preloaded_child_path(int index, char* buffer) {}

void _wasmfs_get_preloaded_path_name(int index, void* buffer) {}
void _wasmfs_get_preloaded_path_name(int index, char* buffer) {}

// Import the VM's fd_write under a different name. Then we can interpose in
// between it and WasmFS's fd_write. That is, libc calls fd_write, which WasmFS
Expand Down
5 changes: 5 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -12712,6 +12712,11 @@ def test_gen_sig_info(self):
self.run_process([PYTHON, path_from_root('tools/maint/gen_sig_info.py'), '-o', 'out.js'])
self.assertFilesMatch(path_from_root('src/lib/libsigs.js'), 'out.js')

@crossplatform
def test_gen_native_sig_info(self):
self.run_process([PYTHON, path_from_root('tools/maint/gen_native_sig_info.py'), '-o', 'out.py'])
self.assertFilesMatch(path_from_root('tools/native_sigs.py'), 'out.py')

def test_gen_struct_info_env(self):
# gen_struct_info.py builds C code in a very specific and low level way. We don't want
# EMCC_CFLAGS (or any of the other environment variables that might effect compilation or
Expand Down
107 changes: 6 additions & 101 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
utils,
webassembly,
)
from tools.native_sigs import native_sigs
from tools.settings import settings, user_settings
from tools.shared import DEBUG, asmjs_mangle, in_temp
from tools.toolchain_profiler import ToolchainProfiler
Expand Down Expand Up @@ -1122,113 +1123,17 @@ def create_invoke_wrappers(metadata):


def create_pointer_conversion_wrappers(metadata):
# TODO(sbc): Move this into somewhere less static. Maybe it can become
# part of library.js file, even though this metadata relates specifically
# to native (non-JS) functions.
#
# The signature format here is similar to the one used for JS libraries
# but with the following as the only valid char:
# '_' - non-pointer argument (pass through unchanged)
# 'p' - pointer/int53 argument (convert to/from BigInt)
# 'P' - same as above but allow `undefined` too (requires extra check)
mapping = {
'sbrk': 'pP',
'_emscripten_stack_alloc': 'pp',
'emscripten_get_sbrk_ptr': 'p',
'emscripten_builtin_malloc': 'pp',
'emscripten_builtin_calloc': 'ppp',
'wasmfs_create_node_backend': 'pp',
'malloc': 'pp',
'realloc': 'ppp',
'calloc': 'ppp',
'webidl_malloc': 'pp',
'memalign': 'ppp',
'memcmp': '_ppp',
'memcpy': 'pppp',
'__getTypeName': 'pp',
'setThrew': '_p',
'free': '_p',
'webidl_free': '_p',
'_emscripten_stack_restore': '_p',
'fflush': '_p',
'emscripten_stack_get_end': 'p',
'emscripten_stack_get_base': 'p',
'pthread_self': 'p',
'emscripten_stack_get_current': 'p',
'__errno_location': 'p',
'emscripten_builtin_memalign': 'ppp',
'emscripten_builtin_free': 'vp',
'main': '__PP',
'__main_argc_argv': '__PP',
'emscripten_stack_set_limits': '_pp',
'__set_stack_limits': '_pp',
'__set_thread_state': '_p___',
'__cxa_can_catch': '_ppp',
'__cxa_increment_exception_refcount': '_p',
'__cxa_decrement_exception_refcount': '_p',
'__cxa_get_exception_ptr': 'pp',
'_wasmfs_write_file': '_ppp',
'_wasmfs_mknod': '_p__',
'_wasmfs_symlink': '_pp',
'_wasmfs_chmod': '_p_',
'_wasmfs_lchmod': '_p_',
'_wasmfs_get_cwd': 'p_',
'_wasmfs_identify': '_p',
'_wasmfs_read_file': '_ppp',
'_wasmfs_node_record_dirent': '_pp_',
'__dl_seterr': '_pp',
'_emscripten_run_js_on_main_thread': '__p_p_',
'_emscripten_run_js_on_main_thread_done': '_pp_',
'_emscripten_thread_exit': '_p',
'_emscripten_thread_init': '_p_____',
'_emscripten_thread_free_data': '_p',
'_emscripten_dlsync_self_async': '_p',
'_emscripten_proxy_dlsync': '_p',
'_emscripten_proxy_dlsync_async': '_pp',
'_emscripten_wasm_worker_initialize': '__p_',
'_emscripten_proxy_poll_finish': '_pp_',
'_wasmfs_rename': '_pp',
'_wasmfs_readlink': '_pp',
'_wasmfs_truncate': '_p_',
'_wasmfs_mmap': 'pp____',
'_wasmfs_munmap': '_pp',
'_wasmfs_msync': '_pp_',
'_wasmfs_read': '__pp',
'_wasmfs_pread': '__pp_',
'_wasmfs_utime': '_p__',
'_wasmfs_rmdir': '_p',
'_wasmfs_unlink': '_p',
'_wasmfs_mkdir': '_p_',
'_wasmfs_open': '_p__',
'_wasmfs_mount': '_pp',
'_wasmfs_chdir': '_p',
'_wasmfs_opfs_record_entry': '_pp_',
'_wasmfs_fetch_get_file_url': 'pp',
'_wasmfs_fetch_get_chunk_size': '_p',
'asyncify_start_rewind': '_p',
'asyncify_start_unwind': '_p',
'__get_exception_message': '_ppp',
'stbi_image_free': 'vp',
'stbi_load': 'ppppp_',
'stbi_load_from_memory': 'pp_ppp_',
'strerror': 'p_',
'emscripten_proxy_finish': '_p',
'emscripten_proxy_execute_queue': '_p',
'_emval_coro_resume': '_pp',
'_emval_coro_reject': '_pp',
'emscripten_main_runtime_thread_id': 'p',
'_emscripten_set_offscreencanvas_size_on_thread': '_pp__',
'fileno': '_p',
'_emscripten_run_callback_on_thread': '_pp_ppp',
'_emscripten_find_dylib': 'ppppp',
}

for function in settings.SIGNATURE_CONVERSIONS:
sym, sig = function.split(':')
mapping[sym] = sig
native_sigs[sym] = sig

for f in ASAN_C_HELPERS:
mapping[f] = '_pp'
native_sigs[f] = '_pp'

wrappers = '''
// Argument name here must shadow the `wasmExports` global so
Expand All @@ -1251,8 +1156,8 @@ def create_pointer_conversion_wrappers(metadata):
sig = ['p' if t == 'p' else '_' for t in sig]
sig.insert(1, 'p')
sig = ''.join(sig)
mapping[symbol] = sig
sig = mapping.get(symbol)
native_sigs[symbol] = sig
sig = native_sigs.get(symbol)
if sig:
if settings.MEMORY64:
if sig not in sigs_seen:
Expand All @@ -1266,7 +1171,7 @@ def create_pointer_conversion_wrappers(metadata):
wrap_functions.append(symbol)

for f in wrap_functions:
sig = mapping[f]
sig = native_sigs[f]
wrappers += f"\n wasmExports['{f}'] = makeWrapper_{sig}(wasmExports['{f}']);"
wrappers += '\n return wasmExports;\n}'

Expand Down
178 changes: 178 additions & 0 deletions tools/maint/gen_native_sig_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
# Copyright 2026 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.

"""Extract native signature information for use in pointer conversion wrappers.

It generates a file (or prints) a signature mapping for native functions.
"""

import argparse
import os
import sys

__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
__rootdir__ = os.path.dirname(os.path.dirname(__scriptdir__))
sys.path.insert(0, __rootdir__)

from tools import shared, utils, webassembly

# Overrides to convert 'p' to 'P' (allow undefined) in signatures.
# Maps symbol name to the list of 0-based argument indices that should be 'P'.
signature_overrides = {
'sbrk': [0], # sbrk(increment)
'main': [1, 2], # main(argc, argv, envp)
}


def apply_overrides(sym, sig):
if sym in signature_overrides:
indices = signature_overrides[sym]
sig_chars = list(sig)
for idx in indices:
sig_idx = idx + 1
if sig_idx < len(sig_chars) and sig_chars[sig_idx] == 'p':
sig_chars[sig_idx] = 'P'
return ''.join(sig_chars)
return sig


# WebIDL functions might not be linked in unless we use webidl binder.
# We can just define them in our dummy file to get their signatures,
# since they are just wrappers with predictable signatures.
# Same for other symbols that are hard to link.
extra_declarations = '''
#include <stddef.h>
#include <stdint.h>
extern "C" {
void* webidl_malloc(size_t len) { return nullptr; }
void webidl_free(void* ptr) {}
__attribute__((weak)) uintptr_t __get_tp() { return 0; }
__attribute__((weak)) void __set_stack_limits(void* low, void* high) {}
}
'''


def extract_sigs(wasm_file):
sig_info = {}
with webassembly.Module(wasm_file) as mod:
exports = mod.get_exports()
for exp in exports:
# Ignore C++ mangled symbols (starting with '_Z')
if exp.name.startswith('_Z'):
continue
# Ignore `dynCall_` symbols generated by binaryen.
if exp.name.startswith('dynCall_'):
continue
if exp.kind == webassembly.ExternType.FUNC:
sig_info[exp.name] = mod.get_function_type(exp.index)
return sig_info


def valuetype_to_chr(t, t64):
if t == webassembly.Type.I32 and t64 == webassembly.Type.I64:
return 'p'
return '_'


def functype_to_str(t, t64):
assert len(t.returns) == len(t64.returns)
assert len(t.params) == len(t64.params)
if t.returns:
assert len(t.returns) == 1
rtn = valuetype_to_chr(t.returns[0], t64.returns[0])
else:
rtn = '_'
for p, p64 in zip(t.params, t64.params, strict=True):
rtn += valuetype_to_chr(p, p64)
return rtn


def run_build(config_flags, explicit_exports, main_args, suffix):
utils.safe_ensure_dirs(utils.path_from_root('out'))
cpp_file = utils.path_from_root('out/gen_native_sig_info.cpp')
# We write the dummy file with extra declarations to ensure they are defined
# and exported (since MAIN_MODULE=1 will export them if they are defined in the main module).
utils.write_file(cpp_file, f'int main({main_args}) {{ return 0; }}\n' + extra_declarations)

wasm32 = utils.path_from_root(f'out/test_{suffix}32')
wasm64 = utils.path_from_root(f'out/test_{suffix}64')

flags = config_flags.copy()

if explicit_exports:
exports_str = ','.join(explicit_exports)
flags.append(f'-sEXPORTED_FUNCTIONS=_main,{exports_str}')
else:
flags += ['-Wl,--whole-archive', '-Wl,--export-all']

# Wasm32 build
cmd = [shared.EMXX, cpp_file, '-o', wasm32 + '.js', *flags]
print(f"Building wasm32 for {suffix}...")
shared.check_call(cmd)

# Wasm64 build
cmd_64 = [shared.EMXX, cpp_file, '-o', wasm64 + '.js', '-m64', *flags]
print(f"Building wasm64 for {suffix}...")
shared.check_call(cmd_64)

return wasm32 + '.wasm', wasm64 + '.wasm'


def main(args):
parser = argparse.ArgumentParser()
default_output = utils.path_from_root('tools/native_sigs.py')
parser.add_argument('-o', '--output', default=default_output, help='Python output file (default: tools/native_sigs.py, use "-" for stdout)')
args = parser.parse_args()

# Define configurations to cover different sets of symbols.
# We use MAIN_MODULE=1 to export everything that gets linked.
configs = {
'base': (['-pthread', '-sASYNCIFY', '-sSTB_IMAGE', '--bind', '-sSTACK_OVERFLOW_CHECK=1'], [], 'int argc, char* argv[], char* envp[]'),
'wasmfs': (['-sWASMFS', '-pthread', '-sSTB_IMAGE', '--bind', '-sSTACK_OVERFLOW_CHECK=1'], [], ''),
'wasm_workers': (['-sWASM_WORKERS', '-sSTACK_OVERFLOW_CHECK=1'], ['__emscripten_wasm_worker_initialize'], ''),
'main_module': (['-sMAIN_MODULE', '-pthread', '-Wno-experimental'], [], ''),
}

merged_sigs = {}

for name, (flags, exports, main_args) in configs.items():
wasm32, wasm64 = run_build(flags, exports, main_args, name)
sigs32 = extract_sigs(wasm32)
sigs64 = extract_sigs(wasm64)

for sym, sig32 in sigs32.items():
sig64 = sigs64[sym]
type_str = functype_to_str(sig32, sig64)
if sym in merged_sigs:
if merged_sigs[sym] != type_str:
print(f'symbol type mismatch for {sym}: existing={merged_sigs[sym]} vs new={type_str}')
continue
merged_sigs[sym] = type_str

# Filter results to only include signatures containing pointers ('p')
result_sigs = {}
for sym, sig in merged_sigs.items():
if 'p' in sig.lower():
result_sigs[sym] = apply_overrides(sym, sig)

# Duplicate main signature to __main_argc_argv
assert '__main_argc_argv' not in result_sigs
result_sigs['__main_argc_argv'] = result_sigs['main']

content = '# Auto-generated by tools/maint/gen_native_sig_info.py. DO NOT EDIT.\n'
content += 'native_sigs = {\n'
for sym, sig in sorted(result_sigs.items()):
content += f" '{sym}': '{sig}',\n"
content += '}\n'

if args.output == '-':
print(content, end='')
else:
utils.write_file(args.output, content)


if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
Loading
Loading