rw_deno_core/
ops.rs

1// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2
3use crate::error::AnyError;
4use crate::error::GetErrorClassFn;
5use crate::gotham_state::GothamState;
6use crate::io::ResourceTable;
7use crate::ops_metrics::OpMetricsFn;
8use crate::runtime::JsRuntimeState;
9use crate::runtime::OpDriverImpl;
10use crate::FeatureChecker;
11use crate::OpDecl;
12use futures::task::AtomicWaker;
13use std::cell::RefCell;
14use std::cell::UnsafeCell;
15use std::ops::Deref;
16use std::ops::DerefMut;
17use std::ptr::NonNull;
18use std::rc::Rc;
19use std::sync::Arc;
20use v8::fast_api::CFunctionInfo;
21use v8::fast_api::CTypeInfo;
22use v8::Isolate;
23
24pub type PromiseId = i32;
25pub type OpId = u16;
26
27#[cfg(debug_assertions)]
28thread_local! {
29  static CURRENT_OP: std::cell::Cell<Option<&'static OpDecl>> = None.into();
30}
31
32#[cfg(debug_assertions)]
33pub struct ReentrancyGuard {}
34
35#[cfg(debug_assertions)]
36impl Drop for ReentrancyGuard {
37  fn drop(&mut self) {
38    CURRENT_OP.with(|f| f.set(None));
39  }
40}
41
42/// Creates an op re-entrancy check for the given [`OpDecl`].
43#[cfg(debug_assertions)]
44#[doc(hidden)]
45pub fn reentrancy_check(decl: &'static OpDecl) -> Option<ReentrancyGuard> {
46  if decl.is_reentrant {
47    return None;
48  }
49
50  let current = CURRENT_OP.with(|f| f.get());
51  if let Some(current) = current {
52    panic!("op {} was not marked as #[op2(reentrant)], but re-entrantly invoked op {}", current.name, decl.name);
53  }
54  CURRENT_OP.with(|f| f.set(Some(decl)));
55  Some(ReentrancyGuard {})
56}
57
58#[derive(Clone, Copy)]
59pub struct OpMetadata {
60  /// A description of the op for use in sanitizer output.
61  pub sanitizer_details: Option<&'static str>,
62  /// The fix for the issue described in `sanitizer_details`.
63  pub sanitizer_fix: Option<&'static str>,
64}
65
66impl OpMetadata {
67  pub const fn default() -> Self {
68    Self {
69      sanitizer_details: None,
70      sanitizer_fix: None,
71    }
72  }
73}
74
75/// Per-op context.
76///
77// Note: We don't worry too much about the size of this struct because it's allocated once per realm, and is
78// stored in a contiguous array.
79pub struct OpCtx {
80  /// The id for this op. Will be identical across realms.
81  pub id: OpId,
82
83  /// A stashed Isolate that ops can make use of. This is a raw isolate pointer, and as such, is
84  /// extremely dangerous to use.
85  pub isolate: *mut Isolate,
86
87  #[doc(hidden)]
88  pub state: Rc<RefCell<OpState>>,
89  #[doc(hidden)]
90  pub get_error_class_fn: GetErrorClassFn,
91
92  pub(crate) decl: OpDecl,
93  pub(crate) fast_fn_c_info: Option<NonNull<v8::fast_api::CFunctionInfo>>,
94  pub(crate) metrics_fn: Option<OpMetricsFn>,
95  /// If the last fast op failed, stores the error to be picked up by the slow op.
96  pub(crate) last_fast_error: UnsafeCell<Option<AnyError>>,
97
98  op_driver: Rc<OpDriverImpl>,
99  runtime_state: Rc<JsRuntimeState>,
100}
101
102impl OpCtx {
103  #[allow(clippy::too_many_arguments)]
104  pub(crate) fn new(
105    id: OpId,
106    isolate: *mut Isolate,
107    op_driver: Rc<OpDriverImpl>,
108    decl: OpDecl,
109    state: Rc<RefCell<OpState>>,
110    runtime_state: Rc<JsRuntimeState>,
111    get_error_class_fn: GetErrorClassFn,
112    metrics_fn: Option<OpMetricsFn>,
113  ) -> Self {
114    let mut fast_fn_c_info = None;
115
116    // If we want metrics for this function, create the fastcall `CFunctionInfo` from the metrics
117    // `FastFunction`. For some extremely fast ops, the parameter list may change for the metrics
118    // version and require a slightly different set of arguments (for example, it may need the fastcall
119    // callback information to get the `OpCtx`).
120    let fast_fn = if metrics_fn.is_some() {
121      &decl.fast_fn_with_metrics
122    } else {
123      &decl.fast_fn
124    };
125
126    if let Some(fast_fn) = fast_fn {
127      let args = CTypeInfo::new_from_slice(fast_fn.args);
128      let ret = CTypeInfo::new(fast_fn.return_type);
129
130      // SAFETY: all arguments are coming from the trait and they have
131      // static lifetime
132      let c_fn = unsafe {
133        CFunctionInfo::new(
134          args.as_ptr(),
135          fast_fn.args.len(),
136          ret.as_ptr(),
137          fast_fn.repr,
138        )
139      };
140      fast_fn_c_info = Some(c_fn);
141    }
142
143    Self {
144      id,
145      state,
146      get_error_class_fn,
147      runtime_state,
148      decl,
149      op_driver,
150      fast_fn_c_info,
151      last_fast_error: UnsafeCell::new(None),
152      isolate,
153      metrics_fn,
154    }
155  }
156
157  #[inline(always)]
158  pub const fn decl(&self) -> &OpDecl {
159    &self.decl
160  }
161
162  #[inline(always)]
163  pub const fn metrics_enabled(&self) -> bool {
164    self.metrics_fn.is_some()
165  }
166
167  /// Generates four external references for each op. If an op does not have a fastcall, it generates
168  /// "null" slots to avoid changing the size of the external references array.
169  pub const fn external_references(&self) -> [v8::ExternalReference; 4] {
170    extern "C" fn placeholder() {}
171
172    let ctx_ptr = v8::ExternalReference {
173      pointer: self as *const OpCtx as _,
174    };
175    let null = v8::ExternalReference {
176      pointer: placeholder as _,
177    };
178
179    if self.metrics_enabled() {
180      let slow_fn = v8::ExternalReference {
181        function: self.decl.slow_fn_with_metrics,
182      };
183      if let (Some(fast_fn), Some(fast_fn_c_info)) =
184        (&self.decl.fast_fn_with_metrics, &self.fast_fn_c_info)
185      {
186        let fast_fn = v8::ExternalReference {
187          pointer: fast_fn.function as _,
188        };
189        let fast_info = v8::ExternalReference {
190          pointer: fast_fn_c_info.as_ptr() as _,
191        };
192        [ctx_ptr, slow_fn, fast_fn, fast_info]
193      } else {
194        [ctx_ptr, slow_fn, null, null]
195      }
196    } else {
197      let slow_fn = v8::ExternalReference {
198        function: self.decl.slow_fn,
199      };
200      if let (Some(fast_fn), Some(fast_fn_c_info)) =
201        (&self.decl.fast_fn, &self.fast_fn_c_info)
202      {
203        let fast_fn = v8::ExternalReference {
204          pointer: fast_fn.function as _,
205        };
206        let fast_info = v8::ExternalReference {
207          pointer: fast_fn_c_info.as_ptr() as _,
208        };
209        [ctx_ptr, slow_fn, fast_fn, fast_info]
210      } else {
211        [ctx_ptr, slow_fn, null, null]
212      }
213    }
214  }
215
216  /// This takes the last error from an [`OpCtx`], assuming that no other code anywhere
217  /// can hold a `&mut` to the last_fast_error field.
218  ///
219  /// # Safety
220  ///
221  /// Must only be called from op implementations.
222  #[inline(always)]
223  pub unsafe fn unsafely_take_last_error_for_ops_only(
224    &self,
225  ) -> Option<AnyError> {
226    let opt_mut = &mut *self.last_fast_error.get();
227    opt_mut.take()
228  }
229
230  /// This set the last error for an [`OpCtx`], assuming that no other code anywhere
231  /// can hold a `&mut` to the last_fast_error field.
232  ///
233  /// # Safety
234  ///
235  /// Must only be called from op implementations.
236  #[inline(always)]
237  pub unsafe fn unsafely_set_last_error_for_ops_only(&self, error: AnyError) {
238    let opt_mut = &mut *self.last_fast_error.get();
239    *opt_mut = Some(error);
240  }
241
242  pub(crate) fn op_driver(&self) -> &OpDriverImpl {
243    &self.op_driver
244  }
245
246  /// Get the [`JsRuntimeState`] for this op.
247  pub(crate) fn runtime_state(&self) -> &JsRuntimeState {
248    &self.runtime_state
249  }
250
251  pub fn metadata(&self) -> OpMetadata {
252    self.decl.metadata
253  }
254}
255
256/// Maintains the resources and ops inside a JS runtime.
257pub struct OpState {
258  pub resource_table: ResourceTable,
259  pub(crate) gotham_state: GothamState,
260  pub waker: Arc<AtomicWaker>,
261  pub feature_checker: Arc<FeatureChecker>,
262}
263
264impl OpState {
265  pub fn new(maybe_feature_checker: Option<Arc<FeatureChecker>>) -> OpState {
266    OpState {
267      resource_table: Default::default(),
268      gotham_state: Default::default(),
269      waker: Arc::new(AtomicWaker::new()),
270      feature_checker: maybe_feature_checker.unwrap_or_default(),
271    }
272  }
273
274  /// Clear all user-provided resources and state.
275  pub(crate) fn clear(&mut self) {
276    std::mem::take(&mut self.gotham_state);
277    std::mem::take(&mut self.resource_table);
278  }
279}
280
281impl Deref for OpState {
282  type Target = GothamState;
283
284  fn deref(&self) -> &Self::Target {
285    &self.gotham_state
286  }
287}
288
289impl DerefMut for OpState {
290  fn deref_mut(&mut self) -> &mut Self::Target {
291    &mut self.gotham_state
292  }
293}