Skip to content

Commit cfc090d

Browse files
committed
v8: use fast static strings for known strings
1 parent 7079a34 commit cfc090d

7 files changed

Lines changed: 75 additions & 132 deletions

File tree

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

Lines changed: 7 additions & 14 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,9 +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(scope: &'this mut PinScope<'scope, 'isolate>, input: Local<'_, Value>, key_cache: &'this mut KeyCache) -> Self {
42+
fn new(scope: &'this mut PinScope<'scope, 'isolate>, input: Local<'_, Value>) -> Self {
4543
let input = Local::new(scope, input);
46-
let common = DeserializerCommon { scope, key_cache };
44+
let common = DeserializerCommon { scope };
4745
Deserializer { input, common }
4846
}
4947
}
@@ -54,16 +52,11 @@ impl<'this, 'scope, 'isolate> Deserializer<'this, 'scope, 'isolate> {
5452
struct DeserializerCommon<'this, 'scope, 'isolate> {
5553
/// The scope of values to deserialize.
5654
scope: &'this mut PinScope<'scope, 'isolate>,
57-
/// A cache for frequently used strings.
58-
key_cache: &'this mut KeyCache,
5955
}
6056

6157
impl<'scope, 'isolate> DeserializerCommon<'_, 'scope, 'isolate> {
6258
fn reborrow(&mut self) -> DeserializerCommon<'_, 'scope, 'isolate> {
63-
DeserializerCommon {
64-
scope: self.scope,
65-
key_cache: self.key_cache,
66-
}
59+
DeserializerCommon { scope: self.scope }
6760
}
6861
}
6962

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

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

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

173166
// Extract the `value` field.
174-
let value_field = self.common.key_cache.value(scope);
167+
let value_field = VALUE.string(scope);
175168
let value = object
176169
.get(scope, value_field.into())
177170
.ok_or_else(exception_already_thrown)?;

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ pub(super) trait FromValue: Sized {
1717
macro_rules! impl_from_value {
1818
($ty:ty, ($val:ident, $scope:ident) => $logic:expr) => {
1919
impl FromValue for $ty {
20-
fn from_value<'scope>(
21-
$val: Local<'_, Value>,
22-
$scope: &PinScope<'scope, '_>,
23-
) -> ValueResult<'scope, Self> {
20+
fn from_value<'scope>($val: Local<'_, Value>, $scope: &PinScope<'scope, '_>) -> ValueResult<'scope, Self> {
2421
$logic
2522
}
2623
}

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: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,24 @@ use super::module_common::{build_common_module_from_raw, run_describer, ModuleCo
44
use super::module_host::{CallReducerParams, DynModule, Module, ModuleInfo, ModuleInstance, ModuleRuntime};
55
use super::UpdateDatabaseResult;
66
use crate::host::instance_env::{ChunkPool, InstanceEnv};
7-
use crate::host::v8::error::{BufferTooSmall, JsStackTrace};
8-
use crate::host::v8::syscall::{register_host_funs, FnRet};
97
use crate::host::wasm_common::instrumentation::CallTimes;
108
use crate::host::wasm_common::module_host_actor::{
119
DescribeError, EnergyStats, ExecuteResult, ExecutionTimings, InstanceCommon, ReducerOp,
1210
};
1311
use crate::host::wasm_common::{RowIters, TimingSpanSet};
1412
use crate::host::wasmtime::{epoch_ticker, ticks_in_duration, EPOCH_TICKS_PER_SECOND};
15-
use crate::host::ArgsTuple;
16-
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};
1715
use core::ffi::c_void;
1816
use core::sync::atomic::{AtomicBool, Ordering};
1917
use core::time::Duration;
2018
use core::{iter, ptr, str};
2119
use de::deserialize_js;
22-
use error::{catch_exception, exception_already_thrown, log_traceback, CodeError, TerminationError, Throwable};
20+
use error::{
21+
catch_exception, exception_already_thrown, log_traceback, BufferTooSmall, CodeError, JsStackTrace,
22+
TerminationError, Throwable,
23+
};
2324
use from_value::cast;
24-
use key_cache::get_or_create_key_cache;
2525
use ser::serialize_to_js;
2626
use spacetimedb_client_api_messages::energy::ReducerBudget;
2727
use spacetimedb_datastore::locking_tx_datastore::MutTxId;
@@ -30,15 +30,17 @@ use spacetimedb_lib::{ConnectionId, Identity, RawModuleDef, Timestamp};
3030
use spacetimedb_schema::auto_migrate::MigrationPolicy;
3131
use std::sync::{Arc, LazyLock};
3232
use std::time::Instant;
33+
use string_const::str_from_ident;
34+
use syscall::{register_host_funs, FnRet};
3335
use v8::{
3436
scope, Context, ContextScope, Function, Isolate, IsolateHandle, Local, Object, OwnedIsolate, PinScope, Value,
3537
};
3638

3739
mod de;
3840
mod error;
3941
mod from_value;
40-
mod key_cache;
4142
mod ser;
43+
mod string_const;
4244
mod syscall;
4345
mod to_value;
4446

@@ -532,9 +534,7 @@ fn call_call_reducer(
532534
timestamp: i64,
533535
reducer_args: &ArgsTuple,
534536
) -> anyhow::Result<Result<(), Box<str>>> {
535-
// Get a cached version of the `__call_reducer__` property.
536-
let key_cache = get_or_create_key_cache(scope);
537-
let call_reducer_key = key_cache.borrow_mut().call_reducer(scope);
537+
let call_reducer_key = str_from_ident!(__call_reducer__).string(scope);
538538

539539
catch_exception(scope, |scope| {
540540
// Serialize the arguments.
@@ -580,9 +580,7 @@ fn extract_description(program: &str) -> Result<RawModuleDef, DescribeError> {
580580

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

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

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

Lines changed: 11 additions & 26 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

@@ -56,7 +44,7 @@ enum Error {
5644
}
5745

5846
impl<'scope> Throwable<'scope> for Error {
59-
fn throw(self, scope: & PinScope<'scope, '_>) -> ExceptionThrown {
47+
fn throw(self, scope: &PinScope<'scope, '_>) -> ExceptionThrown {
6048
match self {
6149
Self::StringTooLarge(len) => {
6250
RangeError(format!("`{len}` bytes is too large to be a JS string")).throw(scope)
@@ -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)