Skip to content

Commit e4e65b3

Browse files
committed
v8: use fast static strings for known strings
1 parent a4bac58 commit e4e65b3

6 files changed

Lines changed: 68 additions & 128 deletions

File tree

crates/core/src/host/v8/de.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use super::error::{exception_already_thrown, ExcResult, ExceptionThrown, ExceptionValue, Throwable, TypeError};
44
use super::from_value::{cast, FromValue};
5-
use super::key_cache::{get_or_create_key_cache, KeyCache};
5+
use super::string_const::{TAG, VALUE};
66
use core::fmt;
77
use core::iter::{repeat_n, RepeatN};
88
use core::marker::PhantomData;
@@ -19,9 +19,7 @@ pub(super) fn deserialize_js_seed<'de, T: DeserializeSeed<'de>>(
1919
val: Local<'_, Value>,
2020
seed: T,
2121
) -> ExcResult<T::Output> {
22-
let key_cache = get_or_create_key_cache(scope);
23-
let key_cache = &mut *key_cache.borrow_mut();
24-
let de = Deserializer::new(scope, val, key_cache);
22+
let de = Deserializer::new(scope, val);
2523
seed.deserialize(de).map_err(|e| e.throw(scope))
2624
}
2725

@@ -41,13 +39,9 @@ struct Deserializer<'this, 'scope, 'isolate> {
4139

4240
impl<'this, 'scope, 'isolate> Deserializer<'this, 'scope, 'isolate> {
4341
/// Creates a new deserializer from `input` in `scope`.
44-
fn new(
45-
scope: &'this mut PinScope<'scope, 'isolate>,
46-
input: Local<'_, Value>,
47-
key_cache: &'this mut KeyCache,
48-
) -> Self {
42+
fn new(scope: &'this mut PinScope<'scope, 'isolate>, input: Local<'_, Value>) -> Self {
4943
let input = Local::new(scope, input);
50-
let common = DeserializerCommon { scope, key_cache };
44+
let common = DeserializerCommon { scope };
5145
Deserializer { input, common }
5246
}
5347
}
@@ -58,16 +52,11 @@ impl<'this, 'scope, 'isolate> Deserializer<'this, 'scope, 'isolate> {
5852
struct DeserializerCommon<'this, 'scope, 'isolate> {
5953
/// The scope of values to deserialize.
6054
scope: &'this mut PinScope<'scope, 'isolate>,
61-
/// A cache for frequently used strings.
62-
key_cache: &'this mut KeyCache,
6355
}
6456

6557
impl<'scope, 'isolate> DeserializerCommon<'_, 'scope, 'isolate> {
6658
fn reborrow(&mut self) -> DeserializerCommon<'_, 'scope, 'isolate> {
67-
DeserializerCommon {
68-
scope: self.scope,
69-
key_cache: self.key_cache,
70-
}
59+
DeserializerCommon { scope: self.scope }
7160
}
7261
}
7362

@@ -165,7 +154,7 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco
165154

166155
// We expect a canonical representation of a sum value in JS to be
167156
// `{ tag: "foo", value: a_value_for_foo }`.
168-
let tag_field = self.common.key_cache.tag(scope);
157+
let tag_field = TAG.string(scope);
169158
let object = cast!(scope, self.input, Object, "object for sum type `{}`", sum_name)?;
170159

171160
// Extract the `tag` field. It needs to contain a string.
@@ -175,7 +164,7 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco
175164
let tag = cast!(scope, tag, v8::String, "string for sum tag of `{}`", sum_name)?;
176165

177166
// Extract the `value` field.
178-
let value_field = self.common.key_cache.value(scope);
167+
let value_field = VALUE.string(scope);
179168
let value = object
180169
.get(scope, value_field.into())
181170
.ok_or_else(exception_already_thrown)?;

crates/core/src/host/v8/key_cache.rs

Lines changed: 0 additions & 67 deletions
This file was deleted.

crates/core/src/host/v8/mod.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use crate::host::wasm_common::module_host_actor::{
1010
};
1111
use crate::host::wasm_common::{RowIters, TimingSpanSet};
1212
use crate::host::wasmtime::{epoch_ticker, ticks_in_duration, EPOCH_TICKS_PER_SECOND};
13-
use crate::host::ArgsTuple;
14-
use crate::{host::Scheduler, module_host_context::ModuleCreationContext, replica_context::ReplicaContext};
13+
use crate::host::{ArgsTuple, Scheduler};
14+
use crate::{module_host_context::ModuleCreationContext, replica_context::ReplicaContext};
1515
use core::ffi::c_void;
1616
use core::sync::atomic::{AtomicBool, Ordering};
1717
use core::time::Duration;
@@ -22,7 +22,6 @@ use error::{
2222
TerminationError, Throwable,
2323
};
2424
use from_value::cast;
25-
use key_cache::get_or_create_key_cache;
2625
use ser::serialize_to_js;
2726
use spacetimedb_client_api_messages::energy::ReducerBudget;
2827
use spacetimedb_datastore::locking_tx_datastore::MutTxId;
@@ -31,6 +30,7 @@ use spacetimedb_lib::{ConnectionId, Identity, RawModuleDef, Timestamp};
3130
use spacetimedb_schema::auto_migrate::MigrationPolicy;
3231
use std::sync::{Arc, LazyLock};
3332
use std::time::Instant;
33+
use string_const::str_from_ident;
3434
use syscall::register_host_funs;
3535
use v8::{
3636
scope, Context, ContextScope, Function, Isolate, IsolateHandle, Local, Object, OwnedIsolate, PinScope, Value,
@@ -39,8 +39,8 @@ use v8::{
3939
mod de;
4040
mod error;
4141
mod from_value;
42-
mod key_cache;
4342
mod ser;
43+
mod string_const;
4444
mod syscall;
4545
mod to_value;
4646

@@ -540,9 +540,7 @@ fn call_call_reducer(
540540
timestamp: i64,
541541
reducer_args: &ArgsTuple,
542542
) -> anyhow::Result<Result<(), Box<str>>> {
543-
// Get a cached version of the `__call_reducer__` property.
544-
let key_cache = get_or_create_key_cache(scope);
545-
let call_reducer_key = key_cache.borrow_mut().call_reducer(scope);
543+
let call_reducer_key = str_from_ident!(__call_reducer__).string(scope);
546544

547545
catch_exception(scope, |scope| {
548546
// Serialize the arguments.
@@ -588,9 +586,7 @@ fn extract_description(program: &str) -> Result<RawModuleDef, DescribeError> {
588586

589587
// Calls the `__describe_module__` function on the global proxy object to extract a [`RawModuleDef`].
590588
fn call_describe_module(scope: &mut PinScope<'_, '_>) -> anyhow::Result<RawModuleDef> {
591-
// Get a cached version of the `__describe_module__` property.
592-
let key_cache = get_or_create_key_cache(scope);
593-
let describe_module_key = key_cache.borrow_mut().describe_module(scope);
589+
let describe_module_key = str_from_ident!(__describe_module__).string(scope);
594590

595591
catch_exception(scope, |scope| {
596592
// Get the function on the global proxy object and convert to a function.

crates/core/src/host/v8/ser.rs

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use super::de::intern_field_name;
44
use super::error::{exception_already_thrown, ExcResult, ExceptionThrown, RangeError, Throwable, TypeError};
5-
use super::key_cache::{get_or_create_key_cache, KeyCache};
5+
use super::string_const::{TAG, VALUE};
66
use super::syscall::FnRet;
77
use super::to_value::ToValue;
88
use derive_more::From;
@@ -15,32 +15,20 @@ use v8::{Array, ArrayBuffer, IntegrityLevel, Local, Object, PinScope, Uint8Array
1515

1616
/// Serializes `value` into a V8 into `scope`.
1717
pub(super) fn serialize_to_js<'scope>(scope: &PinScope<'scope, '_>, value: &impl Serialize) -> FnRet<'scope> {
18-
let key_cache = get_or_create_key_cache(scope);
19-
let key_cache = &mut *key_cache.borrow_mut();
20-
value
21-
.serialize(Serializer::new(scope, key_cache))
22-
.map_err(|e| e.throw(scope))
18+
value.serialize(Serializer::new(scope)).map_err(|e| e.throw(scope))
2319
}
2420

2521
/// Deserializes to V8 values.
22+
#[derive(Copy, Clone)]
2623
struct Serializer<'this, 'scope, 'isolate> {
2724
/// The scope to serialize values into.
2825
scope: &'this PinScope<'scope, 'isolate>,
29-
/// A cache for frequently used strings.
30-
key_cache: &'this mut KeyCache,
3126
}
3227

3328
impl<'this, 'scope, 'isolate> Serializer<'this, 'scope, 'isolate> {
3429
/// Creates a new serializer into `scope`.
35-
pub fn new(scope: &'this PinScope<'scope, 'isolate>, key_cache: &'this mut KeyCache) -> Self {
36-
Self { scope, key_cache }
37-
}
38-
39-
fn reborrow(&mut self) -> Serializer<'_, 'scope, 'isolate> {
40-
Serializer {
41-
scope: self.scope,
42-
key_cache: self.key_cache,
43-
}
30+
pub fn new(scope: &'this PinScope<'scope, 'isolate>) -> Self {
31+
Self { scope }
4432
}
4533
}
4634

@@ -158,22 +146,19 @@ impl<'this, 'scope, 'isolate> ser::Serializer for Serializer<'this, 'scope, 'iso
158146
}
159147

160148
fn serialize_variant<T: Serialize + ?Sized>(
161-
mut self,
149+
self,
162150
tag: u8,
163151
var_name: Option<&str>,
164152
value: &T,
165153
) -> Result<Self::Ok, Self::Error> {
166154
// Serialize the payload.
167-
let value_value: Local<'scope, Value> = value.serialize(self.reborrow())?;
155+
let value_value: Local<'scope, Value> = value.serialize(self)?;
168156
// Figure out the tag.
169157
let tag_value: Local<'scope, Value> = intern_field_name(self.scope, var_name, tag as usize).into();
170158
let values = [tag_value, value_value];
171159

172160
// The property keys are always `"tag"` an `"value"`.
173-
let names = [
174-
self.key_cache.tag(self.scope).into(),
175-
self.key_cache.value(self.scope).into(),
176-
];
161+
let names = [TAG.string(self.scope).into(), VALUE.string(self.scope).into()];
177162

178163
// Stitch together the object.
179164
let prototype = v8::null(self.scope).into();
@@ -196,7 +181,7 @@ impl<'scope> ser::SerializeArray for SerializeArray<'_, 'scope, '_> {
196181

197182
fn serialize_element<T: Serialize + ?Sized>(&mut self, elem: &T) -> Result<(), Self::Error> {
198183
// Serialize the current `elem`ent.
199-
let value = elem.serialize(self.inner.reborrow())?;
184+
let value = elem.serialize(self.inner)?;
200185

201186
// Set the value to the array slot at `index`.
202187
let index = self.next_index;
@@ -243,7 +228,7 @@ impl<'scope> ser::SerializeNamedProduct for SerializeNamedProduct<'_, 'scope, '_
243228
elem: &T,
244229
) -> Result<(), Self::Error> {
245230
// Serialize the field value.
246-
let value = elem.serialize(self.inner.reborrow())?;
231+
let value = elem.serialize(self.inner)?;
247232

248233
// Figure out the object property to use.
249234
let scope = self.inner.scope;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use v8::{Local, OneByteConst, PinScope, String};
2+
3+
/// A string known at compile time to be ASCII.
4+
pub(super) struct StringConst(OneByteConst);
5+
6+
impl StringConst {
7+
/// Returns a new string that is known to be ASCII and static.
8+
pub(super) const fn new(string: &'static str) -> Self {
9+
Self(String::create_external_onebyte_const(string.as_bytes()))
10+
}
11+
12+
/// Converts the string to a V8 string.
13+
pub(super) fn string<'scope>(&'static self, scope: &PinScope<'scope, '_>) -> Local<'scope, String> {
14+
String::new_from_onebyte_const(scope, &self.0)
15+
.expect("`create_external_onebyte_const` should've asserted `.len() < kMaxLength`")
16+
}
17+
}
18+
19+
/// Converts an identifier to a compile-time ASCII string.
20+
#[macro_export]
21+
macro_rules! str_from_ident {
22+
($ident:ident) => {{
23+
const STR: &$crate::host::v8::string_const::StringConst =
24+
&$crate::host::v8::string_const::StringConst::new(stringify!($ident));
25+
STR
26+
}};
27+
}
28+
pub(super) use str_from_ident;
29+
30+
/// The `tag` property of a sum object in JS.
31+
pub(super) const TAG: &StringConst = str_from_ident!(tag);
32+
/// The `value` property of a sum object in JS.
33+
pub(super) const VALUE: &StringConst = str_from_ident!(value);

crates/core/src/host/v8/syscall.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
use super::de::{deserialize_js, scratch_buf, v8_interned_string};
2-
use super::error::ExcResult;
1+
use super::de::{deserialize_js, scratch_buf};
2+
use super::error::{ExcResult, ExceptionThrown};
33
use super::ser::serialize_to_js;
4-
use super::{env_on_isolate, exception_already_thrown};
4+
use super::string_const::{str_from_ident, StringConst};
5+
use super::{
6+
env_on_isolate, exception_already_thrown, global, BufferTooSmall, CodeError, JsInstanceEnv, JsStackTrace,
7+
TerminationError, Throwable,
8+
};
59
use crate::database_logger::{LogLevel, Record};
610
use crate::error::NodesError;
711
use crate::host::instance_env::InstanceEnv;
8-
use crate::host::v8::error::ExceptionThrown;
9-
use crate::host::v8::{global, BufferTooSmall, CodeError, JsInstanceEnv, JsStackTrace, TerminationError, Throwable};
1012
use crate::host::wasm_common::instrumentation::span;
1113
use crate::host::wasm_common::{err_to_errno_and_log, RowIterIdx, TimingSpan, TimingSpanIdx};
1214
use crate::host::AbiCall;
@@ -19,7 +21,9 @@ use v8::{Function, FunctionCallbackArguments, Local, PinScope, Value};
1921
pub(super) fn register_host_funs(scope: &mut PinScope<'_, '_>) -> ExcResult<()> {
2022
macro_rules! register {
2123
($wrapper:ident, $abi_call:expr, $fun:ident) => {
22-
register_host_fun(scope, stringify!($fun), |s, a| $wrapper($abi_call, s, a, $fun))?;
24+
register_host_fun(scope, str_from_ident!($fun), |s, a| {
25+
$wrapper($abi_call, s, a, $fun)
26+
})?;
2327
};
2428
}
2529

@@ -73,10 +77,10 @@ pub(super) type FnRet<'scope> = ExcResult<Local<'scope, Value>>;
7377
/// where the function has `name` and `body`
7478
fn register_host_fun(
7579
scope: &mut PinScope<'_, '_>,
76-
name: &str,
80+
name: &'static StringConst,
7781
body: impl Copy + for<'scope> Fn(&mut PinScope<'scope, '_>, FunctionCallbackArguments<'scope>) -> FnRet<'scope>,
7882
) -> ExcResult<()> {
79-
let name = v8_interned_string(scope, name).into();
83+
let name = name.string(scope).into();
8084
let fun = Function::new(scope, adapt_fun(body))
8185
.ok_or_else(exception_already_thrown)?
8286
.into();

0 commit comments

Comments
 (0)