-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Add wasm-bindgen support #23493
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add wasm-bindgen support #23493
Changes from all commits
e9bf8a0
135405f
e5b67a7
6732eec
1708fdf
5f3feae
8ff4580
44d0f55
836fbe6
2debab9
ea4eab0
d7faa6b
cb281cb
2843e36
26cd774
cfc9b11
c2dfc91
4196290
8c08d8e
50e8795
09992ae
126f551
c779002
8e5fd0a
feaf9be
50b600f
50c4fe1
24be666
fe3db01
6d1c5ff
9c92122
3be75f2
4e8aab6
20839d5
95c89b5
65fca2a
e40e789
45c8c8f
11855f5
24d681b
2ef1305
dfd65cf
fe99e55
1c74e70
79a4af7
9627ee3
cba2609
d56b37f
64f0dad
e146085
44d3059
1b72851
afebddc
80d0b67
3cd3b31
68eb9b2
2b1c672
d229aaf
e55a8a1
bc3a3f0
bf9d095
6029dd7
c082ea1
78b0a55
1925b05
b009b61
2b591ee
4b27565
a739374
5cc0091
094743a
ebd53fe
076abcb
dc705f4
06cf81a
f19ae01
b804a48
298d746
7898c9d
389f901
aa7bcc2
3f38afd
2ae5656
067c6c8
4661535
13b5ce7
aa92019
d96723a
e9488b9
311eba5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1374,8 +1374,7 @@ def ccshared(src, linkto=None): | |
| cfunc_ptr(); | ||
| return 0; | ||
| } | ||
| ''' % locals(), | ||
| 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', cflags=extra_args) | ||
| ''', 'a: loaded\na: b (prev: (null))\na: c (prev: b)\n', cflags=extra_args) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks like an unrelated cleanup? |
||
|
|
||
| def do_run(self, src, expected_output=None, force_c=False, **kwargs): | ||
| if 'no_build' in kwargs: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| [build] | ||
| target = "wasm32-unknown-emscripten" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| [build] | ||
| target = "wasm32-unknown-emscripten" | ||
| rustflags = [ | ||
| "-Cllvm-args=-enable-emscripten-cxx-exceptions=0", | ||
| "-Cpanic=abort", | ||
| "-Crelocation-model=static", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [package] | ||
| name = "bindgen_integration" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["staticlib"] | ||
|
|
||
| [dependencies] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| use wasm_bindgen::prelude::*; | ||
|
|
||
| #[wasm_bindgen] | ||
| pub fn rs_add(a: i32, b: i32) -> i32 { | ||
| return a + b; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14857,6 +14857,30 @@ def test_rust_integration_basics(self): | |
| }''') | ||
| self.do_runf('main.cpp', 'Hello from rust!', cflags=[lib]) | ||
|
|
||
| @requires_rust | ||
| def test_wasm_bindgen_integration(self): | ||
| copytree(test_file('rust/bindgen_integration'), '.') | ||
| self.run_process(['cargo', 'add', 'wasm-bindgen']) | ||
| self.run_process(['cargo', 'build']) | ||
| lib = 'target/wasm32-unknown-emscripten/debug/libbindgen_integration.a' | ||
| self.assertExists(lib) | ||
|
|
||
| create_file('main.cpp', '') | ||
| create_file('post.js', ''' | ||
| Module.onRuntimeInitialized = () => { | ||
| out(Module.rs_add(17, 25)); | ||
| }; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a single line function here? |
||
| ''') | ||
| emcc_args = [ | ||
| lib, | ||
| '-sWASM_BINDGEN', | ||
| '--post-js', | ||
| 'post.js', | ||
| '-lexports.js', | ||
| ] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this all fit on a single line? Note: you can make |
||
|
|
||
| self.do_runf('main.cpp', '42', cflags=emcc_args) | ||
|
|
||
| def test_relative_em_cache(self): | ||
| with env_modify({'EM_CACHE': 'foo'}): | ||
| self.assert_fail([EMCC, '-c', test_file('hello_world.c')], 'emcc: error: environment variable EM_CACHE must be an absolute path: foo') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -273,12 +273,33 @@ def lld_flags_for_executable(external_symbols): | |
| return cmd | ||
|
|
||
|
|
||
| def lld_flags(args): | ||
| def get_wasm_bindgen_exported_symbols(input_files): | ||
| nm_args = [LLVM_NM, '--defined-only', '--extern-only', '--format=just-symbols', | ||
| '--print-file-name', '--quiet'] | ||
| nm_args += input_files | ||
|
|
||
| result = run_process(nm_args, stdout=subprocess.PIPE) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use |
||
| symbols = [] | ||
| for line in result.stdout.splitlines(): | ||
| path, symbol = line.split() | ||
| # Skip mangled (non-C) symbols | ||
| if symbol.startswith(('_Z', '_R', 'anon.')): | ||
| continue | ||
| symbols.append(symbol) | ||
|
|
||
| return symbols | ||
|
|
||
|
|
||
| def lld_flags(args, linker_inputs=None): | ||
| # lld doesn't currently support --start-group/--end-group since the | ||
| # semantics are more like the windows linker where there is no need for | ||
| # grouping. | ||
| args = [a for a in args if a not in {'--start-group', '--end-group'}] | ||
|
|
||
| if settings.WASM_BINDGEN: | ||
| exported_symbols = get_wasm_bindgen_exported_symbols(linker_inputs) | ||
| args.extend(f'--export-if-defined={e}' for e in exported_symbols) | ||
|
|
||
| # Emscripten currently expects linkable output (SIDE_MODULE/MAIN_MODULE) to | ||
| # include all archive contents. | ||
| if settings.LINKABLE: | ||
|
|
@@ -312,7 +333,7 @@ def lld_flags(args): | |
| return args | ||
|
|
||
|
|
||
| def link_lld(args, target, external_symbols=None): | ||
| def link_lld(args, target, external_symbols=None, linker_inputs=None): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need a default? (i.e. do all callsites pass this arg?) |
||
| # runs lld to link things. | ||
| if not os.path.exists(WASM_LD): | ||
| exit_with_error('linker binary not found in LLVM directory: %s', WASM_LD) | ||
|
|
@@ -321,7 +342,7 @@ def link_lld(args, target, external_symbols=None): | |
| # normal linker flags that are used when building and executable | ||
| if '--relocatable' not in args and '-r' not in args: | ||
| cmd += lld_flags_for_executable(external_symbols) | ||
| cmd += lld_flags(args) | ||
| cmd += lld_flags(args, linker_inputs) | ||
| cmd = get_command_with_possible_response_file(cmd) | ||
| check_call(cmd) | ||
|
|
||
|
|
@@ -1254,6 +1275,34 @@ def run_wasm_opt(infile, outfile=None, args=[], **kwargs): # noqa | |
| return run_binaryen_command('wasm-opt', infile, outfile, args=args, **kwargs) | ||
|
|
||
|
|
||
| def run_wasm_bindgen(infile, outfile=None, args=[], **kwargs): # noqa | ||
| bindgen_out_dir = os.path.join(get_emscripten_temp_dir(), 'bindgen_out') | ||
|
|
||
| wasm_bindgen_bin = shutil.which('wasm-bindgen') | ||
| if not wasm_bindgen_bin: | ||
| exit_with_error('wasm-bindgen executable not found in $PATH') | ||
| cmd = [ | ||
| wasm_bindgen_bin, | ||
| infile, | ||
| '--keep-lld-exports', | ||
| '--keep-debug', | ||
| '--out-dir', | ||
| bindgen_out_dir, | ||
| ] | ||
| check_call(cmd) | ||
|
|
||
| # Don't try to predict the .wasm filename that wasm-bindgen outputs. Instead | ||
| # just grab the .wasm file itself. | ||
| all_output_files = os.listdir(bindgen_out_dir) | ||
| new_wasm_file = [x for x in all_output_files if x.endswith('.wasm')][0] | ||
| if outfile is None: | ||
| outfile = infile | ||
|
|
||
| shutil.copyfile(os.path.join(bindgen_out_dir, new_wasm_file), outfile) | ||
|
|
||
| return os.path.join(bindgen_out_dir, 'library_bindgen.js') | ||
|
|
||
|
|
||
| intermediate_counter = 0 | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -680,7 +680,7 @@ def create_tsd_exported_runtime_methods(metadata): | |
| return utils.read_file(in_temp(f'{file}.d.ts')) | ||
|
|
||
|
|
||
| def create_tsd(metadata, embind_tsd): | ||
| def create_tsd(metadata, embind_tsd, bindgen_tsd=None): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this need a default? (i.e. do all callsites pass this arg?) |
||
| out = '// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.\n' | ||
| if settings.EXPORTED_RUNTIME_METHODS: | ||
| out += create_tsd_exported_runtime_methods(metadata) | ||
|
|
@@ -712,6 +712,10 @@ def create_tsd(metadata, embind_tsd): | |
| # Add in embind definitions. | ||
| if embind_tsd: | ||
| export_interfaces += ' & EmbindModule' | ||
| if settings.WASM_BINDGEN and bindgen_tsd: | ||
| for file_path in bindgen_tsd: | ||
| out += utils.read_file(file_path) | ||
| export_interfaces += ' & BindgenModule' | ||
| out += f'export type MainModule = {export_interfaces};\n' | ||
| if settings.MODULARIZE: | ||
| return_type = 'MainModule' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
| import stat | ||
| import subprocess | ||
| import sys | ||
| from dataclasses import dataclass | ||
| from pathlib import Path | ||
|
|
||
| from . import diagnostics | ||
|
|
@@ -234,3 +235,17 @@ def set_version_globals(): | |
| EMSCRIPTEN_VERSION = read_file(filename).strip().strip('"') | ||
| parts = [int(x) for x in EMSCRIPTEN_VERSION.split('-')[0].split('.')] | ||
| EMSCRIPTEN_VERSION_MAJOR, EMSCRIPTEN_VERSION_MINOR, EMSCRIPTEN_VERSION_TINY = parts | ||
|
|
||
|
|
||
| @dataclass | ||
| class LinkFlag: | ||
| """Used to represent a linker flag. | ||
|
|
||
| The flag value is stored along with a bool that distinguishes input | ||
| files from non-files. | ||
|
|
||
| A list of these is returned by separate_linker_flags. | ||
| """ | ||
|
|
||
| value: str | ||
| is_file: int | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps putting this in I'm still not clear why we need to move this, but trying to understand now.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously it was just defined in emcc.py. Moving it here lets us also use it in link.py. That let's us ensure that all entries to linker_args are LinkFlags and not a combination of LinkFlags and strings. Happy to move it if you'd like. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How/why does WASM_BINDGEN use HEAP_DATA_VIEW?