js_intern_core/
lib.rs

1use std::collections::{HashMap};
2use wasm_bindgen::prelude::*;
3use std::cell::RefCell;
4use std::mem::transmute;
5use std::hash::{Hash};
6
7thread_local!(
8	static FLOAT_CACHE: Cacher<BitwiseFloat> = Cacher::new();
9	static STRING_CACHE: Cacher<&'static str> = Cacher::new();
10	static BOOL_CACHE: Cacher<bool> = Cacher::new(); // TODO: This is a bit overkill.
11	// TODO: Include None. The first thought would be for Option<!> if that compiles with a simple js_intern!(None). wasm-bindgen treats this as undefined rather than null, so then should we.
12);
13
14#[doc(hidden)]
15/// This is a private trait and not meant to be used.
16pub trait CacheJsIntern__ {
17	fn cache_js_intern__(self) -> *mut JsValue;
18}
19
20impl CacheJsIntern__ for f64 {
21	fn cache_js_intern__(self) -> *mut JsValue {
22		FLOAT_CACHE.with(|c| {
23			c.cache(self.into())
24		})
25	}
26}
27
28impl CacheJsIntern__ for &'static str {
29	fn cache_js_intern__(self) -> *mut JsValue {
30		STRING_CACHE.with(|c| {
31			c.cache(self)
32		})
33	}
34}
35
36impl CacheJsIntern__ for bool {
37	fn cache_js_intern__(self) -> *mut JsValue {
38		BOOL_CACHE.with(|c| {
39			c.cache(self)
40		})
41	}
42}
43
44macro_rules! CacheForT64 {
45	($t:ty) => {
46		impl CacheJsIntern__ for $t {
47			fn cache_js_intern__(self) -> *mut JsValue {
48				(self as f64).cache_js_intern__()
49			}
50		}
51	};
52}
53
54CacheForT64!(i8);
55CacheForT64!(i16);
56CacheForT64!(i32);
57CacheForT64!(u8);
58CacheForT64!(u16);
59CacheForT64!(u32);
60CacheForT64!(f32);
61
62struct Cacher<T: Eq + Hash> {
63	inner: RefCell<HashMap<T, *mut JsValue>>
64}
65
66impl<T: Eq + Hash> Cacher<T> {
67	fn new() -> Cacher<T> {
68		Cacher {
69			inner: RefCell::default()
70		}
71	}
72}
73
74// Implementing Drop is probably overkill, since in eg: a browser, there
75// is only one JavaScript engine. But, I can imagine a system that had
76// multiple JavaScript engines. Perhaps one per thread.
77impl<T: Eq + Hash> Drop for Cacher<T> {
78	fn drop(&mut self) {
79		// Ensure we free all the heap allocations from our boxes,
80		// and drop the js values contained in them.
81		for (_key, value) in self.inner.borrow_mut().drain() {
82			unsafe { Box::from_raw(value); }
83		}
84	}
85}
86
87impl<T: Into<JsValue> + Eq + Hash + Copy> Cacher<T> {
88	fn cache(&self, value: T) -> *mut JsValue {
89		let mut map = self.inner.borrow_mut();
90		// Note that if Cacher is ever used outside this crate, we would need to make
91		// this function re-entrant, since T::into<JsValue> could execute arbitrary
92		// code, this could get called by it, and the borrow_mut() would panic.
93		// For now, it's only used on types for which this is not a problem.
94
95		*map.entry(value).or_insert_with(move || {
96			let js_value: JsValue = value.into();
97			Box::into_raw(Box::new(js_value))
98		})
99	}
100}
101
102
103/// For the purposes of this crate, floats are equal if and only if their bit patterns
104/// are equal, since we are only responsible for the idea of caching the transfer of
105/// the float into JavaScript and not what float semantics mean.
106#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
107#[repr(transparent)]
108struct BitwiseFloat(u64);
109
110impl From<BitwiseFloat> for JsValue {
111	fn from(value: BitwiseFloat) -> JsValue {
112		JsValue::from_f64(value.into())
113	}
114}
115
116impl From<f64> for BitwiseFloat {
117	fn from(value: f64) -> Self {
118		unsafe { transmute(value) }
119	}
120}
121
122impl From<BitwiseFloat> for f64 {
123	fn from(value: BitwiseFloat) -> Self {
124		unsafe { transmute(value) }
125	}
126}
127
128/// Stores one copy of each distinct JavaScript primitive.
129/// For example, ```js_intern!("string")``` evaluates to a ```&JsValue``` but only
130/// does the translation from the utf-8 Rust string to the utf-16 JavaScript
131/// string the first time the expression is evaluated. Furthermore, strings
132/// are de-duplicated across the program. So, any time ```js_intern!("string")```
133/// is used in the program, the same instance of the JavaScript string is used.
134///
135/// # Supported types
136/// * ```&'static str``` Eg: ```js_intern!("str")```
137/// * ```f64```, ```f32```, ```u8```, ```u16```, ```u32```, ```i8```, ```i16```, ```i32``` Eg: ```js_intern(1.0)```
138/// * ```bool``` Eg: ```js_intern(true)```
139///
140/// # Warning: This is intended to work for literals only. It may presently work on expressions,
141/// but this is not an intended part of the API and will break in a future release.
142#[macro_export]
143macro_rules! js_intern {
144	($value:expr) => {
145		{
146			use wasm_bindgen::JsValue;
147			use $crate::CacheJsIntern__;
148			thread_local!(
149				static INTERN: *mut JsValue = $value.cache_js_intern__();
150			);
151
152			// A word about the safety here. We are dereferencing a pointer
153			// of type *mut JsValue. At the address of the pointer is a JsValue
154			// instance that is boxed. The JsValue is only freed when the thread
155			// goes out of scope. JsValue does not implement Send, so we know
156			// that the value cannot be used anywhere it's invalid.
157			unsafe { &*INTERN.with(|i| i.clone()) }
158		}
159	};
160}