Skip to content

Commit d5d3e46

Browse files
committed
cli-support: Skip generating JS shims for imports when unnecessary
After this change, any import that only takes and returns ABI-safe numbers (signed integers less than 64 bits and unrestricted floating point numbers) will be a direct import, and will not have a little JS shim in the middle. We don't have a great mechanism for testing the generated bindings' contents -- as opposed to its behavior -- but I manually verified that everything here does the Right Thing and doesn't have a JS shim: ```rust \#[wasm_bindgen] extern "C" { fn trivial(); fn incoming_i32() -> i32; fn incoming_f32() -> f32; fn incoming_f64() -> f64; fn outgoing_i32(x: i32); fn outgoing_f32(y: f32); fn outgoing_f64(z: f64); fn many(x: i32, y: f32, z: f64) -> i32; } ``` Furthermore, I verified that when our support for emitting native `anyref` is enabled, then we do not have a JS shim for the following import, but if it is disabled, then we do have a JS shim: ```rust \#[wasm_bindgen] extern "C" { fn works_when_anyref_support_is_enabled(v: JsValue) -> JsValue; } ``` Fixes #1636.
1 parent f2a4694 commit d5d3e46

File tree

5 files changed

+199
-8
lines changed

5 files changed

+199
-8
lines changed

crates/cli-support/src/js/mod.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::descriptor::VectorKind;
22
use crate::intrinsic::Intrinsic;
3+
use crate::webidl;
34
use crate::webidl::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
45
use crate::webidl::{AuxValue, Binding};
56
use crate::webidl::{JsImport, JsImportName, NonstandardWebidlSection, WasmBindgenAux};
@@ -723,6 +724,15 @@ impl<'a> Context<'a> {
723724
self.global("function getObject(idx) { return heap[idx]; }");
724725
}
725726

727+
fn expose_not_defined(&mut self) {
728+
if !self.should_write_global("not_defined") {
729+
return;
730+
}
731+
self.global(
732+
"function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
733+
);
734+
}
735+
726736
fn expose_assert_num(&mut self) {
727737
if !self.should_write_global("assert_num") {
728738
return;
@@ -1971,16 +1981,59 @@ impl<'a> Context<'a> {
19711981
.types
19721982
.get::<ast::WebidlFunction>(binding.webidl_ty)
19731983
.unwrap();
1974-
let mut builder = binding::Builder::new(self);
1975-
builder.catch(catch)?;
1976-
let js = builder.process(&binding, &webidl, false, &None, &mut |cx, prelude, args| {
1977-
cx.invoke_import(&binding, import, bindings, args, variadic, prelude)
1978-
})?;
1979-
let js = format!("function{}", js);
1984+
let js = match import {
1985+
AuxImport::Value(AuxValue::Bare(js))
1986+
if !variadic && !catch && self.import_does_not_require_glue(binding, webidl) =>
1987+
{
1988+
self.expose_not_defined();
1989+
let name = self.import_name(js)?;
1990+
format!(
1991+
"typeof {name} == 'function' ? {name} : notDefined('{name}')",
1992+
name = name,
1993+
)
1994+
}
1995+
_ => {
1996+
let mut builder = binding::Builder::new(self);
1997+
builder.catch(catch)?;
1998+
let js = builder.process(
1999+
&binding,
2000+
&webidl,
2001+
false,
2002+
&None,
2003+
&mut |cx, prelude, args| {
2004+
cx.invoke_import(&binding, import, bindings, args, variadic, prelude)
2005+
},
2006+
)?;
2007+
format!("function{}", js)
2008+
}
2009+
};
19802010
self.wasm_import_definitions.insert(id, js);
19812011
Ok(())
19822012
}
19832013

2014+
fn import_does_not_require_glue(
2015+
&self,
2016+
binding: &Binding,
2017+
webidl: &ast::WebidlFunction,
2018+
) -> bool {
2019+
if !self.config.anyref && binding.contains_anyref(self.module) {
2020+
return false;
2021+
}
2022+
2023+
let wasm_ty = self.module.types.get(binding.wasm_ty);
2024+
webidl.kind == ast::WebidlFunctionKind::Static
2025+
&& webidl::outgoing_do_not_require_glue(
2026+
&binding.outgoing,
2027+
wasm_ty.params(),
2028+
&webidl.params,
2029+
)
2030+
&& webidl::incoming_do_not_require_glue(
2031+
&binding.incoming,
2032+
&webidl.result.into_iter().collect::<Vec<_>>(),
2033+
wasm_ty.results(),
2034+
)
2035+
}
2036+
19842037
/// Generates a JS snippet appropriate for invoking `import`.
19852038
///
19862039
/// This is generating code for `binding` where `bindings` has more type

crates/cli-support/src/webidl/mod.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,17 @@ pub struct Binding {
120120
pub return_via_outptr: Option<Vec<walrus::ValType>>,
121121
}
122122

123+
impl Binding {
124+
/// Does this binding's wasm function signature have any `anyref`s?
125+
pub fn contains_anyref(&self, module: &walrus::Module) -> bool {
126+
let ty = module.types.get(self.wasm_ty);
127+
ty.params()
128+
.iter()
129+
.chain(ty.results())
130+
.any(|ty| *ty == walrus::ValType::Anyref)
131+
}
132+
}
133+
123134
/// A synthetic custom section which is not standardized, never will be, and
124135
/// cannot be serialized or parsed. This is synthesized from all of the
125136
/// compiler-emitted wasm-bindgen sections and then immediately removed to be
@@ -1428,3 +1439,49 @@ fn concatenate_comments(comments: &[&str]) -> String {
14281439
.collect::<Vec<_>>()
14291440
.join("\n")
14301441
}
1442+
1443+
/// Do we need to generate JS glue shims for these incoming bindings?
1444+
pub fn incoming_do_not_require_glue(
1445+
exprs: &[NonstandardIncoming],
1446+
from_webidl_tys: &[ast::WebidlTypeRef],
1447+
to_wasm_tys: &[walrus::ValType],
1448+
) -> bool {
1449+
exprs.len() == from_webidl_tys.len()
1450+
&& exprs.len() == to_wasm_tys.len()
1451+
&& exprs
1452+
.iter()
1453+
.zip(from_webidl_tys)
1454+
.zip(to_wasm_tys)
1455+
.enumerate()
1456+
.all(|(i, ((expr, from_webidl_ty), to_wasm_ty))| match expr {
1457+
NonstandardIncoming::Standard(e) => e.is_expressible_in_js_without_webidl_bindings(
1458+
*from_webidl_ty,
1459+
*to_wasm_ty,
1460+
i as u32,
1461+
),
1462+
_ => false,
1463+
})
1464+
}
1465+
1466+
/// Do we need to generate JS glue shims for these outgoing bindings?
1467+
pub fn outgoing_do_not_require_glue(
1468+
exprs: &[NonstandardOutgoing],
1469+
from_wasm_tys: &[walrus::ValType],
1470+
to_webidl_tys: &[ast::WebidlTypeRef],
1471+
) -> bool {
1472+
exprs.len() == from_wasm_tys.len()
1473+
&& exprs.len() == to_webidl_tys.len()
1474+
&& exprs
1475+
.iter()
1476+
.zip(from_wasm_tys)
1477+
.zip(to_webidl_tys)
1478+
.enumerate()
1479+
.all(|(i, ((expr, from_wasm_ty), to_webidl_ty))| match expr {
1480+
NonstandardOutgoing::Standard(e) => e.is_expressible_in_js_without_webidl_bindings(
1481+
*from_wasm_ty,
1482+
*to_webidl_ty,
1483+
i as u32,
1484+
),
1485+
_ => false,
1486+
})
1487+
}

crates/cli-support/src/webidl/outgoing.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,12 @@ impl OutgoingBuilder<'_> {
200200
Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort),
201201
Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long),
202202
Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong),
203-
Descriptor::F32 => self.standard_as(ValType::F32, ast::WebidlScalarType::Float),
204-
Descriptor::F64 => self.standard_as(ValType::F64, ast::WebidlScalarType::Double),
203+
Descriptor::F32 => {
204+
self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat)
205+
}
206+
Descriptor::F64 => {
207+
self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble)
208+
}
205209
Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long),
206210

207211
Descriptor::Char => {

tests/wasm/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub mod imports;
2929
pub mod js_objects;
3030
pub mod jscast;
3131
pub mod math;
32+
pub mod no_shims;
3233
pub mod node;
3334
pub mod option;
3435
pub mod optional_primitives;

tests/wasm/no_shims.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! A collection of tests to exercise imports where we don't need to generate a
2+
//! JS shim to convert arguments/returns even when Web IDL bindings is not
3+
//! implemented.
4+
5+
use wasm_bindgen::prelude::*;
6+
use wasm_bindgen_test::*;
7+
8+
#[wasm_bindgen(inline_js = "
9+
function assert_eq(a, b) {
10+
if (a !== b) {
11+
throw new Error(`assert_eq failed: ${a} != ${b}`);
12+
}
13+
}
14+
15+
module.exports.trivial = function () {};
16+
17+
module.exports.incoming_i32 = function () { return 0; };
18+
module.exports.incoming_f32 = function () { return 1.5; };
19+
module.exports.incoming_f64 = function () { return 13.37; };
20+
21+
module.exports.outgoing_i32 = function (x) { assert_eq(x, 0); };
22+
module.exports.outgoing_f32 = function (y) { assert_eq(y, 1.5); };
23+
module.exports.outgoing_f64 = function (z) { assert_eq(z, 13.37); };
24+
25+
module.exports.many = function (x, y, z) {
26+
assert_eq(x, 0);
27+
assert_eq(y, 1.5);
28+
assert_eq(z, 13.37);
29+
return 42;
30+
};
31+
32+
module.exports.works_when_anyref_support_is_enabled = function (v) {
33+
assert_eq(v, 'hello');
34+
return v;
35+
};
36+
")]
37+
extern "C" {
38+
fn trivial();
39+
40+
fn incoming_i32() -> i32;
41+
fn incoming_f32() -> f32;
42+
fn incoming_f64() -> f64;
43+
44+
fn outgoing_i32(x: i32);
45+
fn outgoing_f32(y: f32);
46+
fn outgoing_f64(z: f64);
47+
48+
fn many(x: i32, y: f32, z: f64) -> i32;
49+
50+
// Note that this should only skip the JS shim if we have anyref support
51+
// enabled.
52+
fn works_when_anyref_support_is_enabled(v: JsValue) -> JsValue;
53+
}
54+
55+
#[wasm_bindgen_test]
56+
fn no_shims() {
57+
trivial();
58+
59+
let x = incoming_i32();
60+
assert_eq!(x, 0);
61+
let y = incoming_f32();
62+
assert_eq!(y, 1.5);
63+
let z = incoming_f64();
64+
assert_eq!(z, 13.37);
65+
66+
outgoing_i32(x);
67+
outgoing_f32(y);
68+
outgoing_f64(z);
69+
70+
let w = many(x, y, z);
71+
assert_eq!(w, 42);
72+
73+
let v = JsValue::from("hello");
74+
let vv = works_when_anyref_support_is_enabled(v.clone());
75+
assert_eq!(v, vv);
76+
}

0 commit comments

Comments
 (0)