diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index f2189e34..ae51f0cc 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -24,6 +24,7 @@ tys! { BOOLEAN FUNCTION CLOSURE + CACHED_STRING STRING REF REFMUT @@ -58,6 +59,7 @@ pub enum Descriptor { RefMut(Box), Slice(Box), Vector(Box), + CachedString, String, Anyref, Enum { hole: u32 }, @@ -127,6 +129,7 @@ impl Descriptor { SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data, clamped))), VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data, clamped))), OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data, clamped))), + CACHED_STRING => Descriptor::CachedString, STRING => Descriptor::String, ANYREF => Descriptor::Anyref, ENUM => Descriptor::Enum { hole: get(data) }, diff --git a/crates/cli-support/src/js/outgoing.rs b/crates/cli-support/src/js/outgoing.rs index c0e2abe0..f38da33f 100644 --- a/crates/cli-support/src/js/outgoing.rs +++ b/crates/cli-support/src/js/outgoing.rs @@ -130,6 +130,33 @@ impl<'a, 'b> Outgoing<'a, 'b> { Ok(format!("v{}", i)) } + NonstandardOutgoing::CachedString { + offset, + length, + owned, + } => { + let ptr = self.arg(*offset); + let len = self.arg(*length); + let tmp = self.js.tmp(); + + self.js.typescript_required("string"); + self.cx.expose_get_object(); + self.cx.expose_get_string_from_wasm()?; + + self.js.prelude(&format!( + "const v{tmp} = {ptr} === 0 ? getObject({len}) : getStringFromWasm({ptr}, {len});", + tmp = tmp, + ptr = ptr, + len = len, + )); + + if *owned { + self.prelude_free_cached_string(&ptr, &len)?; + } + + Ok(format!("v{}", tmp)) + } + NonstandardOutgoing::StackClosure { a, b, @@ -305,6 +332,35 @@ impl<'a, 'b> Outgoing<'a, 'b> { self.js.prelude("}"); Ok(format!("v{}", i)) } + + NonstandardOutgoing::OptionCachedString { + offset, + length, + owned, + } => { + let ptr = self.arg(*offset); + let len = self.arg(*length); + let tmp = self.js.tmp(); + + self.js.typescript_optional("string"); + self.cx.expose_get_object(); + self.cx.expose_get_string_from_wasm()?; + + self.js.prelude(&format!("let v{};", tmp)); + + self.js.prelude(&format!( + "if ({ptr} === 0) {{ if ({len} !== 0) {{ v{tmp} = getObject({len}); }} }} else {{ v{tmp} = getStringFromWasm({ptr}, {len}); }}", + tmp = tmp, + ptr = ptr, + len = len, + )); + + if *owned { + self.prelude_free_cached_string(&ptr, &len)?; + } + + Ok(format!("v{}", tmp)) + } } } @@ -408,4 +464,15 @@ impl<'a, 'b> Outgoing<'a, 'b> { )); self.cx.require_internal_export("__wbindgen_free") } + + fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> { + self.js.prelude(&format!( + "if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len} * {size}); }}", + ptr = ptr, + len = len, + size = VectorKind::String.size(), + )); + + self.cx.require_internal_export("__wbindgen_free") + } } diff --git a/crates/cli-support/src/webidl/outgoing.rs b/crates/cli-support/src/webidl/outgoing.rs index 59b284af..38e4696f 100644 --- a/crates/cli-support/src/webidl/outgoing.rs +++ b/crates/cli-support/src/webidl/outgoing.rs @@ -60,6 +60,13 @@ pub enum NonstandardOutgoing { kind: VectorKind, }, + /// + CachedString { + offset: u32, + length: u32, + owned: bool, + }, + /// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here /// aren't supported by WebIDL bindings yet. View64 { @@ -81,6 +88,13 @@ pub enum NonstandardOutgoing { kind: VectorKind, }, + /// + OptionCachedString { + offset: u32, + length: u32, + owned: bool, + }, + /// An optional slice of data is being passed into JS. /// /// TODO: with some cleverness this could probably use `AllocCopy`. @@ -240,6 +254,17 @@ impl OutgoingBuilder<'_> { Descriptor::Ref(d) => self.process_ref(false, d)?, Descriptor::RefMut(d) => self.process_ref(true, d)?, + Descriptor::CachedString => { + let offset = self.push_wasm(ValType::I32); + let length = self.push_wasm(ValType::I32); + self.webidl.push(ast::WebidlScalarType::Any); + self.bindings.push(NonstandardOutgoing::CachedString { + offset, + length, + owned: true, + }) + } + Descriptor::Vector(_) | Descriptor::String => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( @@ -281,6 +306,16 @@ impl OutgoingBuilder<'_> { self.bindings .push(NonstandardOutgoing::BorrowedAnyref { idx }); } + Descriptor::CachedString => { + let offset = self.push_wasm(ValType::I32); + let length = self.push_wasm(ValType::I32); + self.webidl.push(ast::WebidlScalarType::DomString); + self.bindings.push(NonstandardOutgoing::CachedString { + offset, + length, + owned: false, + }) + } Descriptor::Slice(_) | Descriptor::String => { use wasm_webidl_bindings::ast::WebidlScalarType::*; @@ -422,6 +457,18 @@ impl OutgoingBuilder<'_> { } Descriptor::Ref(d) => self.process_option_ref(false, d)?, Descriptor::RefMut(d) => self.process_option_ref(true, d)?, + + Descriptor::CachedString => { + let offset = self.push_wasm(ValType::I32); + let length = self.push_wasm(ValType::I32); + self.webidl.push(ast::WebidlScalarType::DomString); + self.bindings.push(NonstandardOutgoing::OptionCachedString { + offset, + length, + owned: true, + }) + } + Descriptor::String | Descriptor::Vector(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( @@ -455,6 +502,16 @@ impl OutgoingBuilder<'_> { self.bindings .push(NonstandardOutgoing::BorrowedAnyref { idx }); } + Descriptor::CachedString => { + let offset = self.push_wasm(ValType::I32); + let length = self.push_wasm(ValType::I32); + self.webidl.push(ast::WebidlScalarType::DomString); + self.bindings.push(NonstandardOutgoing::OptionCachedString { + offset, + length, + owned: false, + }) + } Descriptor::String | Descriptor::Slice(_) => { let kind = arg.vector_kind().ok_or_else(|| { format_err!( diff --git a/src/cache/intern.rs b/src/cache/intern.rs index ac3fb6f8..f5ff6ced 100644 --- a/src/cache/intern.rs +++ b/src/cache/intern.rs @@ -33,15 +33,16 @@ cfg_if! { cache.find(|p| p.key == key).map(|x| &x.value) } - pub(crate) fn get_str(s: &str) -> JsValue { + pub(crate) fn get_str(s: &str) -> Option { CACHE.with(|cache| { let mut cache = cache.entries.borrow_mut(); if let Some(value) = get_js_string(&mut cache, s) { - value.clone() + // This is safe because the cache values are never removed + Some(value._unsafe_clone()) } else { - JsValue::from(s) + None } }) } diff --git a/src/convert/slices.rs b/src/convert/slices.rs index f0939462..bf41a33d 100644 --- a/src/convert/slices.rs +++ b/src/convert/slices.rs @@ -124,6 +124,29 @@ vectors! { u8 i8 u16 i16 u32 i32 u64 i64 usize isize f32 f64 } + +cfg_if! { + if #[cfg(feature = "enable-interning")] { + #[inline] + fn get_cached_str(x: &str) -> WasmSlice { + if let Some(x) = crate::cache::intern::get_str(x) { + // This uses 0 for the ptr as an indication that it is a JsValue and not a str + WasmSlice { ptr: 0, len: x.into_abi() } + + } else { + x.into_bytes().into_abi() + } + } + + } else { + #[inline] + fn get_cached_str(x: &str) -> WasmSlice { + x.into_bytes().into_abi() + } + } +} + + if_std! { impl IntoWasmAbi for Vec where Box<[T]>: IntoWasmAbi { type Abi = as IntoWasmAbi>::Abi; @@ -149,41 +172,20 @@ if_std! { fn is_none(abi: &WasmSlice) -> bool { abi.ptr == 0 } } - cfg_if! { - if #[cfg(feature = "enable-interning")] { - impl IntoWasmAbi for String { - type Abi = ::Abi; + impl IntoWasmAbi for String { + type Abi = as IntoWasmAbi>::Abi; - #[inline] - fn into_abi(self) -> Self::Abi { - crate::cache::intern::get_str(&self).into_abi() - } - } - - impl OptionIntoWasmAbi for String { - #[inline] - fn none() -> Self::Abi { - JsValue::UNDEFINED.into_abi() - } - } - - } else { - impl IntoWasmAbi for String { - type Abi = as IntoWasmAbi>::Abi; - - #[inline] - fn into_abi(self) -> Self::Abi { - self.into_bytes().into_abi() - } - } - - impl OptionIntoWasmAbi for String { - #[inline] - fn none() -> Self::Abi { null_slice() } - } + #[inline] + fn into_abi(self) -> Self::Abi { + get_cached_str(&self) } } + impl OptionIntoWasmAbi for String { + #[inline] + fn none() -> Self::Abi { null_slice() } + } + impl FromWasmAbi for String { type Abi = as FromWasmAbi>::Abi; @@ -198,41 +200,19 @@ if_std! { } } +impl<'a> IntoWasmAbi for &'a str { + type Abi = <&'a [u8] as IntoWasmAbi>::Abi; -cfg_if! { - if #[cfg(feature = "enable-interning")] { - impl<'a> IntoWasmAbi for &'a str { - type Abi = ::Abi; - - #[inline] - fn into_abi(self) -> Self::Abi { - crate::cache::intern::get_str(self).into_abi() - } - } - - impl<'a> OptionIntoWasmAbi for &'a str { - #[inline] - fn none() -> Self::Abi { - JsValue::UNDEFINED.into_abi() - } - } - - } else { - impl<'a> IntoWasmAbi for &'a str { - type Abi = <&'a [u8] as IntoWasmAbi>::Abi; - - #[inline] - fn into_abi(self) -> Self::Abi { - self.as_bytes().into_abi() - } - } - - impl<'a> OptionIntoWasmAbi for &'a str { - fn none() -> Self::Abi { null_slice() } - } + #[inline] + fn into_abi(self) -> Self::Abi { + get_cached_str(self) } } +impl<'a> OptionIntoWasmAbi for &'a str { + fn none() -> Self::Abi { null_slice() } +} + impl RefFromWasmAbi for str { type Abi = <[u8] as RefFromWasmAbi>::Abi; type Anchor = Box; diff --git a/src/describe.rs b/src/describe.rs index f737aa99..b6b325ed 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -29,6 +29,7 @@ tys! { BOOLEAN FUNCTION CLOSURE + CACHED_STRING STRING REF REFMUT @@ -75,7 +76,7 @@ simple! { f64 => F64 bool => BOOLEAN char => CHAR - str => if cfg!(feature = "enable-interning") { ANYREF } else { STRING } + str => if cfg!(feature = "enable-interning") { CACHED_STRING } else { STRING } JsValue => ANYREF } @@ -116,7 +117,7 @@ if_std! { use std::prelude::v1::*; impl WasmDescribe for String { - fn describe() { inform(if cfg!(feature = "enable-interning") { ANYREF } else { STRING }) } + fn describe() { inform(if cfg!(feature = "enable-interning") { CACHED_STRING } else { STRING }) } } impl WasmDescribe for Box<[T]> { diff --git a/src/lib.rs b/src/lib.rs index a65a0930..151fd6c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,6 +125,11 @@ impl JsValue { } } + #[inline] + fn _unsafe_clone(&self) -> JsValue { + Self::_new(self.idx) + } + /// Creates a new JS value which is a string. /// /// The utf-8 string provided is copied to the JS heap and the string will