diff options
Diffstat (limited to 'core/runtime/ops.rs')
-rw-r--r-- | core/runtime/ops.rs | 634 |
1 files changed, 0 insertions, 634 deletions
diff --git a/core/runtime/ops.rs b/core/runtime/ops.rs deleted file mode 100644 index 5ecab5edf..000000000 --- a/core/runtime/ops.rs +++ /dev/null @@ -1,634 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::ops::*; -use crate::OpResult; -use crate::PromiseId; -use anyhow::Error; -use futures::future::Either; -use futures::future::Future; -use futures::future::FutureExt; -use futures::task::noop_waker_ref; -use std::borrow::Cow; -use std::cell::RefCell; -use std::future::ready; -use std::mem::MaybeUninit; -use std::option::Option; -use std::task::Context; -use std::task::Poll; - -#[inline] -pub fn queue_fast_async_op<R: serde::Serialize + 'static>( - ctx: &OpCtx, - promise_id: PromiseId, - op: impl Future<Output = Result<R, Error>> + 'static, -) { - let get_class = { - let state = RefCell::borrow(&ctx.state); - state.tracker.track_async(ctx.id); - state.get_error_class_fn - }; - let fut = op.map(|result| crate::_ops::to_op_result(get_class, result)); - ctx - .context_state - .borrow_mut() - .pending_ops - .spawn(OpCall::new(ctx, promise_id, fut)); -} - -#[inline] -pub fn map_async_op1<R: serde::Serialize + 'static>( - ctx: &OpCtx, - op: impl Future<Output = Result<R, Error>> + 'static, -) -> impl Future<Output = OpResult> { - let get_class = { - let state = RefCell::borrow(&ctx.state); - state.tracker.track_async(ctx.id); - state.get_error_class_fn - }; - - op.map(|res| crate::_ops::to_op_result(get_class, res)) -} - -#[inline] -pub fn map_async_op2<R: serde::Serialize + 'static>( - ctx: &OpCtx, - op: impl Future<Output = R> + 'static, -) -> impl Future<Output = OpResult> { - let state = RefCell::borrow(&ctx.state); - state.tracker.track_async(ctx.id); - - op.map(|res| OpResult::Ok(res.into())) -} - -#[inline] -pub fn map_async_op3<R: serde::Serialize + 'static>( - ctx: &OpCtx, - op: Result<impl Future<Output = Result<R, Error>> + 'static, Error>, -) -> impl Future<Output = OpResult> { - let get_class = { - let state = RefCell::borrow(&ctx.state); - state.tracker.track_async(ctx.id); - state.get_error_class_fn - }; - - match op { - Err(err) => { - Either::Left(ready(OpResult::Err(OpError::new(get_class, err)))) - } - Ok(fut) => { - Either::Right(fut.map(|res| crate::_ops::to_op_result(get_class, res))) - } - } -} - -#[inline] -pub fn map_async_op4<R: serde::Serialize + 'static>( - ctx: &OpCtx, - op: Result<impl Future<Output = R> + 'static, Error>, -) -> impl Future<Output = OpResult> { - let get_class = { - let state = RefCell::borrow(&ctx.state); - state.tracker.track_async(ctx.id); - state.get_error_class_fn - }; - - match op { - Err(err) => { - Either::Left(ready(OpResult::Err(OpError::new(get_class, err)))) - } - Ok(fut) => Either::Right(fut.map(|r| OpResult::Ok(r.into()))), - } -} - -pub fn queue_async_op<'s>( - ctx: &OpCtx, - scope: &'s mut v8::HandleScope, - deferred: bool, - promise_id: PromiseId, - op: impl Future<Output = OpResult> + 'static, -) -> Option<v8::Local<'s, v8::Value>> { - // An op's realm (as given by `OpCtx::realm_idx`) must match the realm in - // which it is invoked. Otherwise, we might have cross-realm object exposure. - // deno_core doesn't currently support such exposure, even though embedders - // can cause them, so we panic in debug mode (since the check is expensive). - // TODO(mmastrac): Restore this - // debug_assert_eq!( - // runtime_state.borrow().context(ctx.realm_idx as usize, scope), - // Some(scope.get_current_context()) - // ); - - let id = ctx.id; - - // TODO(mmastrac): We have to poll every future here because that assumption is baked into a large number - // of ops. If we can figure out a way around this, we can remove this call to boxed_local and save a malloc per future. - let mut pinned = op.map(move |res| (promise_id, id, res)).boxed_local(); - - match pinned.poll_unpin(&mut Context::from_waker(noop_waker_ref())) { - Poll::Pending => {} - Poll::Ready(mut res) => { - if deferred { - ctx.context_state.borrow_mut().pending_ops.spawn(ready(res)); - return None; - } else { - ctx.state.borrow_mut().tracker.track_async_completed(ctx.id); - return Some(res.2.to_v8(scope).unwrap()); - } - } - } - - ctx.context_state.borrow_mut().pending_ops.spawn(pinned); - None -} - -macro_rules! try_number { - ($n:ident $type:ident $is:ident) => { - if $n.$is() { - // SAFETY: v8 handles can be transmuted - let n: &v8::Uint32 = unsafe { std::mem::transmute($n) }; - return n.value() as _; - } - }; -} - -pub fn to_u32(number: &v8::Value) -> u32 { - try_number!(number Uint32 is_uint32); - try_number!(number Int32 is_int32); - try_number!(number Number is_number); - if number.is_big_int() { - // SAFETY: v8 handles can be transmuted - let n: &v8::BigInt = unsafe { std::mem::transmute(number) }; - return n.u64_value().0 as _; - } - 0 -} - -pub fn to_i32(number: &v8::Value) -> i32 { - try_number!(number Uint32 is_uint32); - try_number!(number Int32 is_int32); - try_number!(number Number is_number); - if number.is_big_int() { - // SAFETY: v8 handles can be transmuted - let n: &v8::BigInt = unsafe { std::mem::transmute(number) }; - return n.i64_value().0 as _; - } - 0 -} - -#[allow(unused)] -pub fn to_u64(number: &v8::Value) -> u32 { - try_number!(number Uint32 is_uint32); - try_number!(number Int32 is_int32); - try_number!(number Number is_number); - if number.is_big_int() { - // SAFETY: v8 handles can be transmuted - let n: &v8::BigInt = unsafe { std::mem::transmute(number) }; - return n.u64_value().0 as _; - } - 0 -} - -#[allow(unused)] -pub fn to_i64(number: &v8::Value) -> i32 { - try_number!(number Uint32 is_uint32); - try_number!(number Int32 is_int32); - try_number!(number Number is_number); - if number.is_big_int() { - // SAFETY: v8 handles can be transmuted - let n: &v8::BigInt = unsafe { std::mem::transmute(number) }; - return n.i64_value().0 as _; - } - 0 -} - -/// 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: &mut v8::fast_api::FastApiOneByteString, - buffer: &'a mut [MaybeUninit<u8>; N], -) -> Cow<'a, str> { - let input_buf = string.as_bytes(); - 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: &mut 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) - } -} - -/// Converts a [`v8::String`] to either an owned string, or a borrowed string, depending on whether it fits into the -/// provided buffer. -#[inline(always)] -pub fn to_str<'a, const N: usize>( - scope: &mut v8::Isolate, - string: &v8::Value, - buffer: &'a mut [MaybeUninit<u8>; N], -) -> Cow<'a, str> { - if !string.is_string() { - return Cow::Borrowed(""); - } - - // SAFETY: We checked is_string above - let string: &v8::String = unsafe { std::mem::transmute(string) }; - - string.to_rust_cow_lossy(scope, buffer) -} - -#[cfg(test)] -mod tests { - use crate::error::generic_error; - use crate::error::AnyError; - use crate::error::JsError; - use crate::FastString; - use crate::JsRuntime; - use crate::RuntimeOptions; - use deno_ops::op2; - use std::borrow::Cow; - use std::cell::Cell; - - crate::extension!( - testing, - ops = [ - op_test_fail, - op_test_add, - op_test_add_option, - op_test_result_void_switch, - op_test_result_void_ok, - op_test_result_void_err, - op_test_result_primitive_ok, - op_test_result_primitive_err, - op_test_string_owned, - op_test_string_ref, - op_test_string_cow, - op_test_string_roundtrip_char, - op_test_string_return, - op_test_string_option_return, - op_test_string_roundtrip, - op_test_generics<String>, - ] - ); - - thread_local! { - static FAIL: Cell<bool> = Cell::new(false) - } - - #[op2(core, fast)] - pub fn op_test_fail() { - FAIL.with(|b| b.set(true)) - } - - /// Run a test for a single op. - fn run_test2(repeat: usize, op: &str, test: &str) -> Result<(), AnyError> { - let mut runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![testing::init_ops_and_esm()], - ..Default::default() - }); - runtime - .execute_script( - "", - FastString::Owned( - format!( - r" - const {{ op_test_fail, {op} }} = Deno.core.ensureFastOps(); - function assert(b) {{ - if (!b) {{ - op_test_fail(); - }} - }} - " - ) - .into(), - ), - ) - .unwrap(); - FAIL.with(|b| b.set(false)); - runtime.execute_script( - "", - FastString::Owned( - format!( - r" - for (let __index__ = 0; __index__ < {repeat}; __index__++) {{ - {test} - }} - " - ) - .into(), - ), - )?; - if FAIL.with(|b| b.get()) { - Err(generic_error(format!("{op} test failed ({test})"))) - } else { - Ok(()) - } - } - - #[tokio::test(flavor = "current_thread")] - pub async fn test_op_fail() { - assert!(run_test2(1, "", "assert(false)").is_err()); - } - - #[op2(core, fast)] - pub fn op_test_add(a: u32, b: u32) -> u32 { - a + b - } - - #[tokio::test(flavor = "current_thread")] - pub async fn test_op_add() -> Result<(), Box<dyn std::error::Error>> { - Ok(run_test2( - 10000, - "op_test_add", - "assert(op_test_add(1, 11) == 12)", - )?) - } - - #[op2(core)] - pub fn op_test_add_option(a: u32, b: Option<u32>) -> u32 { - a + b.unwrap_or(100) - } - - #[tokio::test(flavor = "current_thread")] - pub async fn test_op_add_option() -> Result<(), Box<dyn std::error::Error>> { - // This isn't fast, so we don't repeat it - run_test2( - 1, - "op_test_add_option", - "assert(op_test_add_option(1, 11) == 12)", - )?; - run_test2( - 1, - "op_test_add_option", - "assert(op_test_add_option(1, null) == 101)", - )?; - Ok(()) - } - - thread_local! { - static RETURN_COUNT: Cell<usize> = Cell::new(0); - } - - #[op2(core, fast)] - pub fn op_test_result_void_switch() -> Result<(), AnyError> { - let count = RETURN_COUNT.with(|count| { - let new = count.get() + 1; - count.set(new); - new - }); - if count > 5000 { - Err(generic_error("failed!!!")) - } else { - Ok(()) - } - } - - #[op2(core, fast)] - pub fn op_test_result_void_err() -> Result<(), AnyError> { - Err(generic_error("failed!!!")) - } - - #[op2(core, fast)] - pub fn op_test_result_void_ok() -> Result<(), AnyError> { - Ok(()) - } - - #[tokio::test(flavor = "current_thread")] - pub async fn test_op_result_void() -> Result<(), Box<dyn std::error::Error>> { - // Test the non-switching kinds - run_test2( - 10000, - "op_test_result_void_err", - "try { op_test_result_void_err(); assert(false) } catch (e) {}", - )?; - run_test2(10000, "op_test_result_void_ok", "op_test_result_void_ok()")?; - Ok(()) - } - - #[tokio::test(flavor = "current_thread")] - pub async fn test_op_result_void_switch( - ) -> Result<(), Box<dyn std::error::Error>> { - RETURN_COUNT.with(|count| count.set(0)); - let err = run_test2( - 10000, - "op_test_result_void_switch", - "op_test_result_void_switch();", - ) - .expect_err("Expected this to fail"); - let js_err = err.downcast::<JsError>().unwrap(); - assert_eq!(js_err.message, Some("failed!!!".into())); - assert_eq!(RETURN_COUNT.with(|count| count.get()), 5001); - Ok(()) - } - - #[op2(core, fast)] - pub fn op_test_result_primitive_err() -> Result<u32, AnyError> { - Err(generic_error("failed!!!")) - } - - #[op2(core, fast)] - pub fn op_test_result_primitive_ok() -> Result<u32, AnyError> { - Ok(123) - } - - #[tokio::test] - pub async fn test_op_result_primitive( - ) -> Result<(), Box<dyn std::error::Error>> { - run_test2( - 10000, - "op_test_result_primitive_err", - "try { op_test_result_primitive_err(); assert(false) } catch (e) {}", - )?; - run_test2( - 10000, - "op_test_result_primitive_ok", - "op_test_result_primitive_ok()", - )?; - Ok(()) - } - - #[op2(core, fast)] - pub fn op_test_string_owned(#[string] s: String) -> u32 { - s.len() as _ - } - - #[op2(core, fast)] - pub fn op_test_string_ref(#[string] s: &str) -> u32 { - s.len() as _ - } - - #[op2(core, fast)] - pub fn op_test_string_cow(#[string] s: Cow<str>) -> u32 { - s.len() as _ - } - - #[op2(core, fast)] - pub fn op_test_string_roundtrip_char(#[string] s: Cow<str>) -> u32 { - s.chars().next().unwrap() as u32 - } - - #[tokio::test] - pub async fn test_op_strings() -> Result<(), Box<dyn std::error::Error>> { - for op in [ - "op_test_string_owned", - "op_test_string_cow", - "op_test_string_ref", - ] { - for (len, str) in [ - // ASCII - (3, "'abc'"), - // Latin-1 (one byte but two UTF-8 chars) - (2, "'\\u00a0'"), - // ASCII - (10000, "'a'.repeat(10000)"), - // Latin-1 - (20000, "'\\u00a0'.repeat(10000)"), - // 4-byte UTF-8 emoji (1F995 = 🦕) - (40000, "'\\u{1F995}'.repeat(10000)"), - ] { - let test = format!("assert({op}({str}) == {len})"); - run_test2(10000, op, &test)?; - } - } - - // Ensure that we're correctly encoding UTF-8 - run_test2( - 10000, - "op_test_string_roundtrip_char", - "assert(op_test_string_roundtrip_char('\\u00a0') == 0xa0)", - )?; - run_test2( - 10000, - "op_test_string_roundtrip_char", - "assert(op_test_string_roundtrip_char('\\u00ff') == 0xff)", - )?; - run_test2( - 10000, - "op_test_string_roundtrip_char", - "assert(op_test_string_roundtrip_char('\\u0080') == 0x80)", - )?; - run_test2( - 10000, - "op_test_string_roundtrip_char", - "assert(op_test_string_roundtrip_char('\\u0100') == 0x100)", - )?; - Ok(()) - } - - #[op2(core)] - #[string] - pub fn op_test_string_return( - #[string] a: Cow<str>, - #[string] b: Cow<str>, - ) -> String { - (a + b).to_string() - } - - #[op2(core)] - #[string] - pub fn op_test_string_option_return( - #[string] a: Cow<str>, - #[string] b: Cow<str>, - ) -> Option<String> { - if a == "none" { - return None; - } - Some((a + b).to_string()) - } - - #[op2(core)] - #[string] - pub fn op_test_string_roundtrip(#[string] s: String) -> String { - s - } - - #[tokio::test] - pub async fn test_op_string_returns() -> Result<(), Box<dyn std::error::Error>> - { - run_test2( - 1, - "op_test_string_return", - "assert(op_test_string_return('a', 'b') == 'ab')", - )?; - run_test2( - 1, - "op_test_string_option_return", - "assert(op_test_string_option_return('a', 'b') == 'ab')", - )?; - run_test2( - 1, - "op_test_string_option_return", - "assert(op_test_string_option_return('none', 'b') == null)", - )?; - run_test2( - 1, - "op_test_string_roundtrip", - "assert(op_test_string_roundtrip('\\u0080\\u00a0\\u00ff') == '\\u0080\\u00a0\\u00ff')", - )?; - Ok(()) - } - - // We don't actually test this one -- we just want it to compile - #[op2(core, fast)] - pub fn op_test_generics<T: Clone>() {} -} |