diff --git a/src/fast_api.rs b/src/fast_api.rs index 3ca7bbaf..d4db8b87 100644 --- a/src/fast_api.rs +++ b/src/fast_api.rs @@ -91,6 +91,7 @@ pub enum CType { Float32, Float64, V8Value, + SeqOneByteString, // https://github.com/v8/v8/blob/492a32943bc34a527f42df2ae15a77154b16cc84/include/v8-fast-api-calls.h#L264-L267 // kCallbackOptionsType is not part of the Type enum // because it is only used internally. Use value 255 that is larger @@ -110,6 +111,7 @@ pub enum Type { Float32, Float64, V8Value, + SeqOneByteString, CallbackOptions, Sequence(CType), TypedArray(CType), @@ -128,6 +130,7 @@ impl From<&Type> for CType { Type::Float32 => CType::Float32, Type::Float64 => CType::Float64, Type::V8Value => CType::V8Value, + Type::SeqOneByteString => CType::SeqOneByteString, Type::CallbackOptions => CType::CallbackOptions, Type::Sequence(ty) => *ty, Type::TypedArray(ty) => *ty, @@ -204,6 +207,25 @@ pub struct FastApiTypedArray { data: *mut T, } +#[repr(C)] +pub struct FastApiOneByteString { + data: *const u8, + pub length: usize, +} + +impl FastApiOneByteString { + #[inline(always)] + pub fn as_str(&self) -> &str { + // SAFETY: The string is guaranteed to be valid UTF-8. + unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + self.data, + self.length, + )) + } + } +} + impl FastApiTypedArray { #[inline(always)] pub fn get(&self, index: usize) -> T { diff --git a/tests/test_api.rs b/tests/test_api.rs index 8f1530f2..1b281e26 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -8603,3 +8603,73 @@ fn test_detach_key() { assert!(buffer.was_detached()); } } + +#[test] +fn test_fast_calls_onebytestring() { + static mut WHO: &str = "none"; + fn fast_fn( + _recv: v8::Local, + data: *const fast_api::FastApiOneByteString, + ) -> u32 { + unsafe { WHO = "fast" }; + let data = unsafe { &*data }.as_str(); + assert_eq!("hello", data); + data.len() as u32 + } + + pub struct FastTest; + impl fast_api::FastFunction for FastTest { + fn args(&self) -> &'static [fast_api::Type] { + &[fast_api::Type::V8Value, fast_api::Type::SeqOneByteString] + } + + fn return_type(&self) -> fast_api::CType { + fast_api::CType::Uint32 + } + + fn function(&self) -> *const c_void { + fast_fn as _ + } + } + + fn slow_fn( + _: &mut v8::HandleScope, + _: v8::FunctionCallbackArguments, + _: v8::ReturnValue, + ) { + unsafe { WHO = "slow" }; + } + + let _setup_guard = setup(); + let isolate = &mut v8::Isolate::new(Default::default()); + let scope = &mut v8::HandleScope::new(isolate); + let context = v8::Context::new(scope); + let scope = &mut v8::ContextScope::new(scope, context); + + let global = context.global(scope); + + let template = + v8::FunctionTemplate::builder(slow_fn).build_fast(scope, &FastTest, None); + + let name = v8::String::new(scope, "func").unwrap(); + let value = template.get_function(scope).unwrap(); + global.set(scope, name.into(), value.into()).unwrap(); + let source = r#" + function f(data) { return func(data); } + %PrepareFunctionForOptimization(f); + const str = "hello"; + f(str); +"#; + eval(scope, source).unwrap(); + assert_eq!("slow", unsafe { WHO }); + + let source = r#" + %OptimizeFunctionOnNextCall(f); + const result = f(str); + if (result != 5) { + throw new Error("wrong result"); + } + "#; + eval(scope, source).unwrap(); + assert_eq!("fast", unsafe { WHO }); +}