Skip to content

Commit

Permalink
more fast strings
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacasonato committed Oct 25, 2024
1 parent 172f597 commit 83f45a2
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 119 deletions.
91 changes: 0 additions & 91 deletions core/runtime/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,97 +150,6 @@ pub fn to_external_option(external: &v8::Value) -> Option<*mut c_void> {
}
}

/// Expands `inbuf` to `outbuf`, assuming that `outbuf` has at least 2x `input_length`.
#[inline(always)]
unsafe fn latin1_to_utf8(
input_length: usize,
inbuf: *const u8,
outbuf: *mut u8,
) -> usize {
let mut output = 0;
let mut input = 0;
while input < input_length {
let char = *(inbuf.add(input));
if char < 0x80 {
*(outbuf.add(output)) = char;
output += 1;
} else {
// Top two bits
*(outbuf.add(output)) = (char >> 6) | 0b1100_0000;
// Bottom six bits
*(outbuf.add(output + 1)) = (char & 0b0011_1111) | 0b1000_0000;
output += 2;
}
input += 1;
}
output
}

/// Converts a [`v8::fast_api::FastApiOneByteString`] to either an owned string, or a borrowed string, depending on whether it fits into the
/// provided buffer.
pub fn to_str_ptr<'a, const N: usize>(
string: &'a mut v8::fast_api::FastApiOneByteString,
buffer: &'a mut [MaybeUninit<u8>; N],
) -> Cow<'a, str> {
let input_buf = string.as_bytes();

// Per benchmarking results, it's faster to do this check than to copy latin-1 -> utf8
if input_buf.is_ascii() {
// SAFETY: We just checked that it was ASCII
return Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(input_buf) });
}

let input_len = input_buf.len();
let output_len = buffer.len();

// 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
// can skip the ASCII check and just start copying.
if input_len < N / 2 {
debug_assert!(output_len >= input_len * 2);
let buffer = buffer.as_mut_ptr() as *mut u8;

let written =
// SAFETY: We checked that buffer is at least 2x the size of input_buf
unsafe { latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), buffer) };

debug_assert!(written <= output_len);

let slice = std::ptr::slice_from_raw_parts(buffer, written);
// SAFETY: We know it's valid UTF-8, so make a string
Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(&*slice) })
} else {
// TODO(mmastrac): We could be smarter here about not allocating
Cow::Owned(to_string_ptr(string))
}
}

/// Converts a [`v8::fast_api::FastApiOneByteString`] to an owned string. May over-allocate to avoid
/// re-allocation.
pub fn to_string_ptr(string: &v8::fast_api::FastApiOneByteString) -> String {
let input_buf = string.as_bytes();
let capacity = input_buf.len() * 2;

// SAFETY: We're allocating a buffer of 2x the input size, writing valid UTF-8, then turning that into a string
unsafe {
// Create an uninitialized buffer of `capacity` bytes. We need to be careful here to avoid
// accidentally creating a slice of u8 which would be invalid.
let layout = std::alloc::Layout::from_size_align(capacity, 1).unwrap();
let out = std::alloc::alloc(layout);

let written = latin1_to_utf8(input_buf.len(), input_buf.as_ptr(), out);

debug_assert!(written <= capacity);
// We know it's valid UTF-8, so make a string
String::from_raw_parts(out, written, capacity)
}
}

pub fn to_cow_byte_ptr(
string: &v8::fast_api::FastApiOneByteString,
) -> Cow<[u8]> {
string.as_bytes().into()
}

/// Converts a [`v8::Value`] to an owned string.
#[inline(always)]
pub fn to_string(scope: &mut v8::Isolate, string: &v8::Value) -> String {
Expand Down
89 changes: 61 additions & 28 deletions ops/op2/dispatch_fast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,29 @@ impl FastSignature {
&self,
generator_state: &mut GeneratorState,
) -> Result<Vec<TokenStream>, V8SignatureMappingError> {
// Collect virtual arguments in a deferred list that we compute at the very end. This allows us to borrow
// the scope/opstate in the intermediate stages.
let mut call_args = vec![];
let mut deferred = vec![];

for arg in &self.args {
match arg {
FastArg::Actual { arg, name_out, .. }
| FastArg::Virtual { name_out, arg } => call_args.push(
FastArg::Actual { arg, name_out, .. } => call_args.push(
map_v8_fastcall_arg_to_arg(generator_state, name_out, arg).map_err(
|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()),
)?,
),
FastArg::Virtual { name_out, arg } => deferred.push(
map_v8_fastcall_arg_to_arg(generator_state, name_out, arg).map_err(
|s| V8SignatureMappingError::NoArgMapping(s, arg.clone()),
)?,
),
FastArg::CallbackOptions | FastArg::PromiseId => {}
}
}

call_args.extend(deferred);

Ok(call_args)
}

Expand Down Expand Up @@ -313,6 +324,14 @@ pub(crate) fn get_fast_signature(
}))
}

fn create_isolate(generator_state: &mut GeneratorState) -> TokenStream {
generator_state.needs_fast_api_callback_options = true;
gs_quote!(generator_state(fast_api_callback_options) => {
// SAFETY: This is using an &FastApiCallbackOptions inside a fast call.
unsafe { &mut *#fast_api_callback_options.isolate };
})
}

fn create_scope(generator_state: &mut GeneratorState) -> TokenStream {
generator_state.needs_fast_api_callback_options = true;
gs_quote!(generator_state(fast_api_callback_options) => {
Expand Down Expand Up @@ -478,6 +497,11 @@ pub(crate) fn generate_dispatch_fast(
gs_quote!(generator_state(scope) => {
let mut #scope = #create_scope;
})
} else if generator_state.needs_isolate {
let create_isolate = create_isolate(generator_state);
gs_quote!(generator_state(scope) => {
let mut #scope = #create_isolate;
})
} else {
quote!()
};
Expand Down Expand Up @@ -590,6 +614,7 @@ fn map_v8_fastcall_arg_to_arg(
opctx,
js_runtime_state,
scope,
needs_isolate,
needs_scope,
needs_opctx,
needs_fast_api_callback_options,
Expand Down Expand Up @@ -658,9 +683,9 @@ fn map_v8_fastcall_arg_to_arg(
fast_api_typed_array_to_buffer(arg_ident, arg_ident, *buffer)?
}
Arg::Special(Special::Isolate) => {
*needs_fast_api_callback_options = true;
gs_quote!(generator_state(fast_api_callback_options) => {
let #arg_ident = #fast_api_callback_options.isolate;
*needs_isolate = true;
gs_quote!(generator_state(scope) => {
let #arg_ident = &mut *#scope;
})
}
Arg::Ref(RefType::Ref, Special::OpState) => {
Expand Down Expand Up @@ -720,22 +745,32 @@ fn map_v8_fastcall_arg_to_arg(
}
}
Arg::String(Strings::RefStr) => {
quote! {
*needs_isolate = true;
gs_quote!(generator_state(scope) => {
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];
let #arg_ident = &deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
}
let #arg_ident = &deno_core::_ops::to_str(&mut *#scope, &*#arg_ident, &mut #arg_temp);
})
}
Arg::String(Strings::String) => {
quote!(let #arg_ident = deno_core::_ops::to_string_ptr(unsafe { &mut *#arg_ident });)
*needs_isolate = true;
quote!(let #arg_ident = deno_core::_ops::to_string(&mut *#scope, &*#arg_ident);)
}
Arg::String(Strings::CowStr) => {
quote! {
*needs_isolate = true;
gs_quote!(generator_state(scope) => {
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];
let #arg_ident = deno_core::_ops::to_str_ptr(unsafe { &mut *#arg_ident }, &mut #arg_temp);
}
let #arg_ident = deno_core::_ops::to_str(&mut *#scope, &*#arg_ident, &mut #arg_temp);
})
}
Arg::String(Strings::CowByte) => {
quote!(let #arg_ident = deno_core::_ops::to_cow_byte_ptr(unsafe { &mut *#arg_ident });)
*needs_isolate = true;
let throw_exception =
throw_type_error(generator_state, "expected one byte string");
gs_quote!(generator_state(scope) => {
let Ok(#arg_ident) = deno_core::_ops::to_cow_one_byte(&mut *#scope, &*#arg_ident) else {
#throw_exception
};
})
}
Arg::V8Local(v8)
| Arg::OptionV8Local(v8)
Expand All @@ -755,13 +790,11 @@ fn map_v8_fastcall_arg_to_arg(
let ty =
syn::parse_str::<syn::Path>(ty).expect("Failed to reparse state type");

*needs_fast_api_callback_options = true;
*needs_isolate = true;
let throw_exception =
throw_type_error(generator_state, format!("expected {ty:?}"));
gs_quote!(generator_state(fast_api_callback_options) => {
// SAFETY: Isolate is valid if this function is being called.
let isolate = unsafe { &mut *#fast_api_callback_options.isolate };
let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(isolate, #arg_ident) else {
gs_quote!(generator_state(scope) => {
let Some(#arg_ident) = deno_core::_ops::try_unwrap_cppgc_object::<#ty>(&mut *#scope, #arg_ident) else {
#throw_exception
};
let #arg_ident = &*#arg_ident;
Expand Down Expand Up @@ -851,7 +884,6 @@ fn map_arg_to_v8_fastcall_type(
// Other types + ref types are not handled
Arg::OptionNumeric(..)
| Arg::Option(_)
| Arg::OptionString(_)
| Arg::OptionBuffer(..)
| Arg::SerdeV8(_)
| Arg::FromV8(_)
Expand Down Expand Up @@ -885,15 +917,16 @@ fn map_arg_to_v8_fastcall_type(
) => V8FastCallType::F64,
Arg::Numeric(NumericArg::f32, _) => V8FastCallType::F32,
Arg::Numeric(NumericArg::f64, _) => V8FastCallType::F64,
// Ref strings that are one byte internally may be passed as a SeqOneByteString,
// which gives us a FastApiOneByteString.
Arg::String(Strings::RefStr) => V8FastCallType::SeqOneByteString,
// Owned strings can be fast, but we'll have to copy them.
Arg::String(Strings::String) => V8FastCallType::SeqOneByteString,
// Cow strings can be fast, but may require copying
Arg::String(Strings::CowStr) => V8FastCallType::SeqOneByteString,
// Cow byte strings can be fast and don't require copying
Arg::String(Strings::CowByte) => V8FastCallType::SeqOneByteString,
// Strings are passed as v8::Value, because SeqOneByteString is too
// restrictive in what values are eligible for fastcalls.
Arg::OptionString(Strings::RefStr) => return Ok(None),
Arg::OptionString(Strings::String) => return Ok(None),
Arg::OptionString(Strings::CowStr) => return Ok(None),
Arg::OptionString(Strings::CowByte) => return Ok(None),
Arg::String(Strings::RefStr) => V8FastCallType::V8Value,
Arg::String(Strings::String) => V8FastCallType::V8Value,
Arg::String(Strings::CowStr) => V8FastCallType::V8Value,
Arg::String(Strings::CowByte) => V8FastCallType::V8Value,
Arg::External(..) => V8FastCallType::Pointer,
Arg::CppGcResource(..) => V8FastCallType::V8Value,
Arg::OptionCppGcResource(..) => V8FastCallType::V8Value,
Expand Down

0 comments on commit 83f45a2

Please sign in to comment.