Skip to content
Closed
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
19 changes: 19 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,25 @@ It only does ``Module['X'] = X;``

Default value: true

.. _tsd_skip_exports:

TSD_SKIP_EXPORTS
================

List of wasm export names that ``--emit-tsd`` should omit from the
generated ``.d.ts``. Intended for cases where a downstream toolchain
(e.g. wasm-bindgen, via ``--js-library``) owns the user-facing JS type
for those exports and provides its own type declarations. emcc has no
way to recover that JS-level type from the raw wasm signature, so the
right thing is to omit the export and let the toolchain's own ``.d.ts``
supply the type via downstream merging.

Wasm exports with multi-value returns that are not on this list are
skipped with a warning rather than typed, since the wasm-level tuple
type is rarely what JS callers actually see.

Default value: []

.. _retain_compiler_settings:

RETAIN_COMPILER_SETTINGS
Expand Down
14 changes: 14 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,20 @@ var EXPORT_ALL = false;
// It only does ``Module['X'] = X;``
var EXPORT_KEEPALIVE = true;

// List of wasm export names that ``--emit-tsd`` should omit from the
// generated ``.d.ts``. Intended for cases where a downstream toolchain
// (e.g. wasm-bindgen, via ``--js-library``) owns the user-facing JS type
// for those exports and provides its own type declarations. emcc has no
// way to recover that JS-level type from the raw wasm signature, so the
// right thing is to omit the export and let the toolchain's own ``.d.ts``
// supply the type via downstream merging.
//
// Wasm exports with multi-value returns that are not on this list are
// skipped with a warning rather than typed, since the wasm-level tuple
// type is rarely what JS callers actually see.
// [link]
var TSD_SKIP_EXPORTS = [];

// Remembers the values of these settings, and makes them accessible
// through getCompilerSetting and emscripten_get_compiler_setting.
// To see what is retained, look for compilerSettings in the generated code.
Expand Down
18 changes: 18 additions & 0 deletions test/other/test_emit_tsd_multivalue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <emscripten.h>

struct Pair { int a; int b; };

// Returning a small struct by value with the experimental multi-value ABI
// causes clang to emit a wasm function with two return values (i32, i32).
// This mirrors the shape that higher-level toolchains (e.g. wasm-bindgen)
// produce when lowering compound types to (ptr, len) pairs.
EMSCRIPTEN_KEEPALIVE struct Pair make_pair(int a, int b) {
struct Pair p = {a, b};
return p;
}

EMSCRIPTEN_KEEPALIVE int add(int a, int b) {
return a + b;
}

int main() {}
41 changes: 41 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -3803,6 +3803,47 @@ def test_emit_tsd_wasm_only(self):
expected = 'Wasm only output is not compatible with --emit-tsd'
self.assert_fail([EMCC, test_file('other/test_emit_tsd.c'), '--emit-tsd', 'test_emit_tsd_wasm_only.d.ts', '-o', 'out.wasm'], expected)

def _emit_tsd_multivalue_cmd(self, out_basename, extra=None):
return [EMCC, test_file('other/test_emit_tsd_multivalue.c'),
'-mmultivalue', '-Xclang', '-target-abi', '-Xclang', 'experimental-mv',
'--emit-tsd', f'{out_basename}.d.ts',
'-sMODULARIZE', '-sEXPORT_ES6', '-sEXPORT_KEEPALIVE',
'-o', f'{out_basename}.mjs'] + (extra or []) + self.get_cflags()

def test_emit_tsd_multivalue_skipped(self):
# Listing a multi-value-return export in TSD_SKIP_EXPORTS produces a
# valid .d.ts with that export omitted and no warning.
proc = self.run_process(self._emit_tsd_multivalue_cmd(
'test_emit_tsd_mv_skip', extra=['-sTSD_SKIP_EXPORTS=make_pair']), stderr=PIPE)
actual = read_file('test_emit_tsd_mv_skip.d.ts')
self.assertNotContained('make_pair', actual)
self.assertContained('_add(_0: number, _1: number): number;', actual)
self.assertNotContained('TSD_SKIP_EXPORTS', proc.stderr)

def test_emit_tsd_multivalue_unhandled(self):
# A multi-value-return export not on the skip list is dropped from
# the .d.ts with a warning rather than crashing the build.
cmd = self._emit_tsd_multivalue_cmd('test_emit_tsd_mv_warn') + ['-Wno-error=emcc']
proc = self.run_process(cmd, stderr=PIPE)
actual = read_file('test_emit_tsd_mv_warn.d.ts')
self.assertNotContained('make_pair', actual)
self.assertContained('_add(_0: number, _1: number): number;', actual)
self.assertContained('make_pair', proc.stderr)
self.assertContained('TSD_SKIP_EXPORTS', proc.stderr)

def test_emit_tsd_skip_exports_unknown(self):
# Skip-list entries that don't match any export are silently ignored
# (toolchains may pass conservative lists).
proc = self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
'--emit-tsd', 'test_emit_tsd_unknown_skip.d.ts',
'-sMODULARIZE', '-sEXPORT_ES6',
'-sTSD_SKIP_EXPORTS=does_not_exist',
'-o', 'test_emit_tsd_unknown_skip.mjs'] +
self.get_cflags(), stderr=PIPE)
actual = read_file('test_emit_tsd_unknown_skip.d.ts')
self.assertContained('_fooInt', actual)
self.assertNotContained('does_not_exist', proc.stderr)

@requires_dev_dependency('typescript')
def test_emit_tsd_heap(self):
self.run_process([EMCC, test_file('other/test_emit_tsd.c'),
Expand Down
19 changes: 16 additions & 3 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,23 +686,36 @@ def create_tsd(metadata, embind_tsd):
out += create_tsd_exported_runtime_methods(metadata)
# Manually generate definitions for any Wasm function exports.
out += 'interface WasmModule {\n'
tsd_skip_exports = set(settings.TSD_SKIP_EXPORTS)
for name, functype in metadata.function_exports.items():
mangled = asmjs_mangle(name)
should_export = settings.EXPORT_KEEPALIVE and mangled in settings.EXPORTED_FUNCTIONS
if not should_export:
continue
# The toolchain that produced this wasm may own the user-facing JS
# type for this export via a --js-library wrapper. Skip it and let
# downstream .d.ts merging supply the real type.
if name in tsd_skip_exports or mangled in tsd_skip_exports:
continue
if len(functype.returns) > 1:
# Multi-value returns are valid wasm (e.g. wasm-bindgen lowers Rust
# `String` to a (ptr, len) pair) but the raw tuple is rarely what JS
# callers see. If the toolchain knew about this export it would
# have listed it in TSD_SKIP_EXPORTS; warn rather than guess a type.
diagnostics.warning('emcc', 'skipping wasm export "%s" from --emit-tsd: '
'multi-value return; add to TSD_SKIP_EXPORTS to silence',
name)
continue
arguments = []
for index, type in enumerate(functype.params):
arguments.append(f"_{index}: {type_to_ts_type(type)}")
out += f' {mangled}({", ".join(arguments)}): '
assert len(functype.returns) <= 1, 'One return type only supported'
if functype.returns:
ret_ts_type = type_to_ts_type(functype.returns[0])
else:
ret_ts_type = 'void'
if settings.ASYNCIFY == 2 and any(fnmatch.fnmatch(name, pat) for pat in settings.ASYNCIFY_EXPORTS):
ret_ts_type = f'Promise<{ret_ts_type}>'
out += f'{ret_ts_type};\n'
out += f' {mangled}({", ".join(arguments)}): {ret_ts_type};\n'
out += '}\n'
out += f'\n{embind_tsd}'
# Combine all the various exports.
Expand Down