Skip to content

Commit 83f45a2

Browse files
committed
more fast strings
1 parent 172f597 commit 83f45a2

File tree

2 files changed

+61
-119
lines changed

2 files changed

+61
-119
lines changed

core/runtime/ops.rs

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -150,97 +150,6 @@ pub fn to_external_option(external: &v8::Value) -> Option<*mut c_void> {
150150
}
151151
}
152152

153-
/// Expands `inbuf` to `outbuf`, assuming that `outbuf` has at least 2x `input_length`.
154-
#[inline(always)]
155-
unsafe fn latin1_to_utf8(
156-
input_length: usize,
157-
inbuf: *const u8,
158-
outbuf: *mut u8,
159-
) -> usize {
160-
let mut output = 0;
161-
let mut input = 0;
162-
while input < input_length {
163-
let char = *(inbuf.add(input));
164-
if char < 0x80 {
165-
*(outbuf.add(output)) = char;
166-
output += 1;
167-
} else {
168-
// Top two bits
169-
*(outbuf.add(output)) = (char >> 6) | 0b1100_0000;
170-
// Bottom six bits
171-
*(outbuf.add(output + 1)) = (char & 0b0011_1111) | 0b1000_0000;
172-
output += 2;
173-
}
174-
input += 1;
175-
}
176-
output
177-
}
178-
179-
/// Converts a [`v8::fast_api::FastApiOneByteString`] to either an owned string, or a borrowed string, depending on whether it fits into the
180-
/// provided buffer.
181-
pub fn to_str_ptr<'a, const N: usize>(
182-
string: &'a mut v8::fast_api::FastApiOneByteString,
183-
buffer: &'a mut [MaybeUninit<u8>; N],
184-
) -> Cow<'a, str> {
185-
let input_buf = string.as_bytes();
186-
187-
// Per benchmarking results, it's faster to do this check than to copy latin-1 -> utf8
188-
if input_buf.is_ascii() {
189-
// SAFETY: We just checked that it was ASCII
190-
return Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(input_buf) });
191-
}
192-
193-
let input_len = input_buf.len();
194-
let output_len = buffer.len();
195-
196-
// We know that this string is full of either one or two-byte UTF-8 chars, so if it's < 1/2 of N we
197-
// can skip the ASCII check and just start copying.
198-
if input_len < N / 2 {
199-
debug_assert!(output_len >= input_len * 2);
200-
let buffer = buffer.as_mut_ptr() as *mut u8;
201-
202-
let written =
203-
// SAFETY: We checked that buffer is at least 2x the size of input_buf
204-
unsafe { latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), buffer) };
205-
206-
debug_assert!(written <= output_len);
207-
208-
let slice = std::ptr::slice_from_raw_parts(buffer, written);
209-
// SAFETY: We know it's valid UTF-8, so make a string
210-
Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(&*slice) })
211-
} else {
212-
// TODO(mmastrac): We could be smarter here about not allocating
213-
Cow::Owned(to_string_ptr(string))
214-
}
215-
}
216-
217-
/// Converts a [`v8::fast_api::FastApiOneByteString`] to an owned string. May over-allocate to avoid
218-
/// re-allocation.
219-
pub fn to_string_ptr(string: &v8::fast_api::FastApiOneByteString) -> String {
220-
let input_buf = string.as_bytes();
221-
let capacity = input_buf.len() * 2;
222-
223-
// SAFETY: We're allocating a buffer of 2x the input size, writing valid UTF-8, then turning that into a string
224-
unsafe {
225-
// Create an uninitialized buffer of `capacity` bytes. We need to be careful here to avoid
226-
// accidentally creating a slice of u8 which would be invalid.
227-
let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
228-
let out = std::alloc::alloc(layout);
229-
230-
let written = latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), out);
231-
232-
debug_assert!(written <= capacity);
233-
// We know it's valid UTF-8, so make a string
234-
String::from_raw_parts(out, written, capacity)
235-
}
236-
}
237-
238-
pub fn to_cow_byte_ptr(
239-
string: &v8::fast_api::FastApiOneByteString,
240-
) -> Cow<[u8]> {
241-
string.as_bytes().into()
242-
}
243-
244153
/// Converts a [`v8::Value`] to an owned string.
245154
#[inline(always)]
246155
pub fn to_string(scope: &mut v8::Isolate, string: &v8::Value) -> String {

ops/op2/dispatch_fast.rs

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -97,18 +97,29 @@ impl FastSignature {
9797
&self,
9898
generator_state: &mut GeneratorState,
9999
) -> Result<Vec<TokenStream>, V8SignatureMappingError> {
100+
// Collect virtual arguments in a deferred list that we compute at the very end. This allows us to borrow
101+
// the scope/opstate in the intermediate stages.
100102
let mut call_args = vec![];
103+
let mut deferred = vec![];
104+
101105
for arg in &self.args {
102106
match arg {
103-
FastArg::Actual { arg, name_out, .. }
104-
| FastArg::Virtual { name_out, arg } => call_args.push(
107+
FastArg::Actual { arg, name_out, .. } => call_args.push(
108+
map_v8_fastcall_arg_to_arg(generator_state, name_out, arg).map_err(
109+
|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()),
110+
)?,
111+
),
112+
FastArg::Virtual { name_out, arg } => deferred.push(
105113
map_v8_fastcall_arg_to_arg(generator_state, name_out, arg).map_err(
106114
|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()),
107115
)?,
108116
),
109117
FastArg::CallbackOptions | FastArg::PromiseId => {}
110118
}
111119
}
120+
121+
call_args.extend(deferred);
122+
112123
Ok(call_args)
113124
}
114125

@@ -313,6 +324,14 @@ pub(crate) fn get_fast_signature(
313324
}))
314325
}
315326

327+
fn create_isolate(generator_state: &mut GeneratorState) -> TokenStream {
328+
generator_state.needs_fast_api_callback_options = true;
329+
gs_quote!(generator_state(fast_api_callback_options) => {
330+
// SAFETY: This is using an &FastApiCallbackOptions inside a fast call.
331+
unsafe { &mut *#fast_api_callback_options.isolate };
332+
})
333+
}
334+
316335
fn create_scope(generator_state: &mut GeneratorState) -> TokenStream {
317336
generator_state.needs_fast_api_callback_options = true;
318337
gs_quote!(generator_state(fast_api_callback_options) => {
@@ -478,6 +497,11 @@ pub(crate) fn generate_dispatch_fast(
478497
gs_quote!(generator_state(scope) => {
479498
let mut #scope = #create_scope;
480499
})
500+
} else if generator_state.needs_isolate {
501+
let create_isolate = create_isolate(generator_state);
502+
gs_quote!(generator_state(scope) => {
503+
let mut #scope = #create_isolate;
504+
})
481505
} else {
482506
quote!()
483507
};
@@ -590,6 +614,7 @@ fn map_v8_fastcall_arg_to_arg(
590614
opctx,
591615
js_runtime_state,
592616
scope,
617+
needs_isolate,
593618
needs_scope,
594619
needs_opctx,
595620
needs_fast_api_callback_options,
@@ -658,9 +683,9 @@ fn map_v8_fastcall_arg_to_arg(
658683
fast_api_typed_array_to_buffer(arg_ident, arg_ident, *buffer)?
659684
}
660685
Arg::Special(Special::Isolate) => {
661-
*needs_fast_api_callback_options = true;
662-
gs_quote!(generator_state(fast_api_callback_options) => {
663-
let #arg_ident = #fast_api_callback_options.isolate;
686+
*needs_isolate = true;
687+
gs_quote!(generator_state(scope) => {
688+
let #arg_ident = &mut *#scope;
664689
})
665690
}
666691
Arg::Ref(RefType::Ref, Special::OpState) => {
@@ -720,22 +745,32 @@ fn map_v8_fastcall_arg_to_arg(
720745
}
721746
}
722747
Arg::String(Strings::RefStr) => {
723-
quote! {
748+
*needs_isolate = true;
749+
gs_quote!(generator_state(scope) => {
724750
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; deno_core::_ops::STRING_STACK_BUFFER_SIZE] = [::std::mem::MaybeUninit::uninit(); deno_core::_ops::STRING_STACK_BUFFER_SIZE];
725-
let #arg_ident = &deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
726-
}
751+
let #arg_ident = &deno_core::_ops::to_str(&mut *#scope, &*#arg_ident, &mut #arg_temp);
752+
})
727753
}
728754
Arg::String(Strings::String) => {
729-
quote!(let #arg_ident = deno_core::_ops::to_string_ptr(unsafe { &mut *#arg_ident });)
755+
*needs_isolate = true;
756+
quote!(let #arg_ident = deno_core::_ops::to_string(&mut *#scope, &*#arg_ident);)
730757
}
731758
Arg::String(Strings::CowStr) => {
732-
quote! {
759+
*needs_isolate = true;
760+
gs_quote!(generator_state(scope) => {
733761
let mut #arg_temp: [::std::mem::MaybeUninit<u8>; deno_core::_ops::STRING_STACK_BUFFER_SIZE] = [::std::mem::MaybeUninit::uninit(); deno_core::_ops::STRING_STACK_BUFFER_SIZE];
734-
let #arg_ident = deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
735-
}
762+
let #arg_ident = deno_core::_ops::to_str(&mut *#scope, &*#arg_ident, &mut #arg_temp);
763+
})
736764
}
737765
Arg::String(Strings::CowByte) => {
738-
quote!(let #arg_ident = deno_core::_ops::to_cow_byte_ptr(unsafe { &mut *#arg_ident });)
766+
*needs_isolate = true;
767+
let throw_exception =
768+
throw_type_error(generator_state, "expected one byte string");
769+
gs_quote!(generator_state(scope) => {
770+
let Ok(#arg_ident) = deno_core::_ops::to_cow_one_byte(&mut *#scope, &*#arg_ident) else {
771+
#throw_exception
772+
};
773+
})
739774
}
740775
Arg::V8Local(v8)
741776
| Arg::OptionV8Local(v8)
@@ -755,13 +790,11 @@ fn map_v8_fastcall_arg_to_arg(
755790
let ty =
756791
syn::parse_str::<syn::Path>(ty).expect("Failed to reparse state type");
757792

758-
*needs_fast_api_callback_options = true;
793+
*needs_isolate = true;
759794
let throw_exception =
760795
throw_type_error(generator_state, format!("expected {ty:?}"));
761-
gs_quote!(generator_state(fast_api_callback_options) => {
762-
// SAFETY: Isolate is valid if this function is being called.
763-
let isolate = unsafe { &mut *#fast_api_callback_options.isolate };
764-
let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(isolate, #arg_ident) else {
796+
gs_quote!(generator_state(scope) => {
797+
let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(&mut *#scope, #arg_ident) else {
765798
#throw_exception
766799
};
767800
let #arg_ident = &*#arg_ident;
@@ -851,7 +884,6 @@ fn map_arg_to_v8_fastcall_type(
851884
// Other types + ref types are not handled
852885
Arg::OptionNumeric(..)
853886
| Arg::Option(_)
854-
| Arg::OptionString(_)
855887
| Arg::OptionBuffer(..)
856888
| Arg::SerdeV8(_)
857889
| Arg::FromV8(_)
@@ -885,15 +917,16 @@ fn map_arg_to_v8_fastcall_type(
885917
) => V8FastCallType::F64,
886918
Arg::Numeric(NumericArg::f32, _) => V8FastCallType::F32,
887919
Arg::Numeric(NumericArg::f64, _) => V8FastCallType::F64,
888-
// Ref strings that are one byte internally may be passed as a SeqOneByteString,
889-
// which gives us a FastApiOneByteString.
890-
Arg::String(Strings::RefStr) => V8FastCallType::SeqOneByteString,
891-
// Owned strings can be fast, but we'll have to copy them.
892-
Arg::String(Strings::String) => V8FastCallType::SeqOneByteString,
893-
// Cow strings can be fast, but may require copying
894-
Arg::String(Strings::CowStr) => V8FastCallType::SeqOneByteString,
895-
// Cow byte strings can be fast and don't require copying
896-
Arg::String(Strings::CowByte) => V8FastCallType::SeqOneByteString,
920+
// Strings are passed as v8::Value, because SeqOneByteString is too
921+
// restrictive in what values are eligible for fastcalls.
922+
Arg::OptionString(Strings::RefStr) => return Ok(None),
923+
Arg::OptionString(Strings::String) => return Ok(None),
924+
Arg::OptionString(Strings::CowStr) => return Ok(None),
925+
Arg::OptionString(Strings::CowByte) => return Ok(None),
926+
Arg::String(Strings::RefStr) => V8FastCallType::V8Value,
927+
Arg::String(Strings::String) => V8FastCallType::V8Value,
928+
Arg::String(Strings::CowStr) => V8FastCallType::V8Value,
929+
Arg::String(Strings::CowByte) => V8FastCallType::V8Value,
897930
Arg::External(..) => V8FastCallType::Pointer,
898931
Arg::CppGcResource(..) => V8FastCallType::V8Value,
899932
Arg::OptionCppGcResource(..) => V8FastCallType::V8Value,

0 commit comments

Comments
 (0)