Repository: denoland/deno
Pull request: perf(napi): misc napi optimizations

before
```
❯ deno run -A perf-turso.js
cpu: Apple M3 Max
runtime: deno 2.4.3+6ae4eda (aarch64-apple-darwin)

benchmark                                            time (avg)             (min … max)       p75       p99      p999
--------------------------------------------------------------------------------------- -----------------------------
Statement.get() with bind parameters [expanded]   4'544 ns/iter   (4'424 ns … 4'801 ns)  4'599 ns  4'801 ns  4'801 ns
Statement.get() with bind parameters [raw]        4'484 ns/iter   (4'409 ns … 4'667 ns)  4'527 ns  4'667 ns  4'667 ns
Statement.run() with bind parameters              5'424 ns/iter   (4'833 ns … 4'518 µs)  5'292 ns  8'042 ns 15'292 ns
```

after
```
❯ devdeno run -A perf-turso.js
cpu: Apple M3 Max
runtime: deno 2.4.3 (aarch64-apple-darwin)

benchmark                                            time (avg)             (min … max)       p75       p99      p999
--------------------------------------------------------------------------------------- -----------------------------
Statement.get() with bind parameters [expanded]   1'141 ns/iter   (1'057 ns … 3'223 ns)  1'126 ns  2'910 ns  3'223 ns
Statement.get() with bind parameters [raw]        1'015 ns/iter     (975 ns … 1'112 ns)  1'017 ns  1'093 ns  1'112 ns
Statement.run() with bind parameters              1'560 ns/iter   (1'295 ns … 8'840 ns)  1'433 ns  8'752 ns  8'840 ns
```

Optimizations are:
- don't log napi entry/exit in release builds (this ends up being very expensive, as it checks whether logging is enabled twice per call into NAPI)
- use internalized strings to speed up property accesses (matches node's behavior as well)
- change order we check value types (matches node now)
- don't unnecessarily validate utf8
- don't get handle scopes when an isolate will do
- reuse handle scope within a function
- use the faster APIs that don't require handle scopes for getting uint32 / int32 values

Changed files:
filename: ext/napi/js_native_api.rs
patch: @@ -599,8 +599,9 @@ fn napi_set_named_property<'s>(
 ) -> napi_status {
   check_arg!(env, value);
   let env_ptr = env as *mut Env;
+  let scope = &mut env.scope();
 
-  let Some(object) = object.and_then(|o| o.to_object(&mut env.scope())) else {
+  let Some(object) = object.and_then(|o| o.to_object(scope)) else {
     return napi_object_expected;
   };
 
@@ -611,10 +612,7 @@ fn napi_set_named_property<'s>(
 
   let value = value.unwrap();
 
-  if !object
-    .set(&mut env.scope(), key.into(), value)
-    .unwrap_or(false)
-  {
+  if !object.set(scope, key.into(), value).unwrap_or(false) {
     return napi_generic_failure;
   }
 
@@ -1559,26 +1557,26 @@ fn node_api_create_syntax_error(
 }
 
 pub fn get_value_type(value: v8::Local<v8::Value>) -> Option<napi_valuetype> {
-  if value.is_undefined() {
-    Some(napi_undefined)
-  } else if value.is_null() {
-    Some(napi_null)
-  } else if value.is_external() {
-    Some(napi_external)
-  } else if value.is_boolean() {
-    Some(napi_boolean)
-  } else if value.is_number() {
+  if value.is_number() {
     Some(napi_number)
   } else if value.is_big_int() {
     Some(napi_bigint)
   } else if value.is_string() {
     Some(napi_string)
-  } else if value.is_symbol() {
-    Some(napi_symbol)
   } else if value.is_function() {
     Some(napi_function)
+  } else if value.is_external() {
+    Some(napi_external)
   } else if value.is_object() {
     Some(napi_object)
+  } else if value.is_boolean() {
+    Some(napi_boolean)
+  } else if value.is_undefined() {
+    Some(napi_undefined)
+  } else if value.is_symbol() {
+    Some(napi_symbol)
+  } else if value.is_null() {
+    Some(napi_null)
   } else {
     None
   }
@@ -1611,7 +1609,7 @@ fn napi_get_undefined(env: *mut Env, result: *mut napi_value) -> napi_status {
   check_arg!(env, result);
 
   unsafe {
-    *result = v8::undefined(&mut env.scope()).into();
+    *result = v8::undefined(env.isolate()).into();
   }
 
   return napi_clear_last_error(env);
@@ -1623,7 +1621,7 @@ fn napi_get_null(env: *mut Env, result: *mut napi_value) -> napi_status {
   check_arg!(env, result);
 
   unsafe {
-    *result = v8::null(&mut env.scope()).into();
+    *result = v8::null(env.isolate()).into();
   }
 
   return napi_clear_last_error(env);
@@ -1892,8 +1890,13 @@ fn napi_get_value_int32(
   check_arg!(env, value);
   check_arg!(env, result);
 
-  let Some(value) = value.unwrap().int32_value(&mut env.scope()) else {
-    return napi_set_last_error(env, napi_number_expected);
+  let value = value.unwrap();
+  let value = if let Ok(value) = value.try_cast::<v8::Int32>() {
+    value.value()
+  } else if let Some(value) = value.int32_value(&mut env.scope()) {
+    value
+  } else {
+    return napi_set_last_error(env_ptr, napi_number_expected);
   };
 
   unsafe {
@@ -1913,12 +1916,18 @@ fn napi_get_value_uint32(
   check_arg!(env, value);
   check_arg!(env, result);
 
-  let Some(value) = value.unwrap().uint32_value(&mut env.scope()) else {
-    return napi_set_last_error(env, napi_number_expected);
+  let value = value.unwrap();
+
+  let num = if let Ok(value) = value.try_cast::<v8::Uint32>() {
+    value.value()
+  } else if let Some(value) = value.uint32_value(&mut env.scope()) {
+    value
+  } else {
+    return napi_set_last_error(env_ptr, napi_number_expected);
   };
 
   unsafe {
-    *result = value;
+    *result = num;
   }
 
   return napi_clear_last_error(env_ptr);
@@ -2379,9 +2388,10 @@ fn unwrap(
   else {
     return napi_invalid_arg;
   };
+  let scope = &mut env.scope();
 
-  let napi_wrap = v8::Local::new(&mut env.scope(), &env.shared().napi_wrap);
-  let Some(val) = obj.get_private(&mut env.scope(), napi_wrap) else {
+  let napi_wrap = v8::Local::new(scope, &env.shared().napi_wrap);
+  let Some(val) = obj.get_private(scope, napi_wrap) else {
     return napi_invalid_arg;
   };
 
@@ -2399,11 +2409,7 @@ fn unwrap(
   }
 
   if !keep {
-    assert!(
-      obj
-        .delete_private(&mut env.scope(), napi_wrap)
-        .unwrap_or(false)
-    );
+    assert!(obj.delete_private(scope, napi_wrap).unwrap_or(false));
     unsafe { Reference::remove(reference) };
   }
 

filename: ext/napi/util.rs
patch: @@ -118,18 +118,18 @@ pub(crate) unsafe fn check_new_from_utf8_len<'s>(
   );
   return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg);
   let string = if len == NAPI_AUTO_LENGTH {
-    let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str();
-    return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
-    result.unwrap()
+    unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_bytes()
   } else {
     let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) };
-    let result = std::str::from_utf8(string);
-    return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
-    result.unwrap()
+    string
   };
   let result = {
     let env = unsafe { &mut *(env as *mut Env) };
-    v8::String::new(&mut env.scope(), string)
+    v8::String::new_from_utf8(
+      &mut env.scope(),
+      string,
+      v8::NewStringType::Internalized,
+    )
   };
   return_error_status_if_false!(env, result.is_some(), napi_generic_failure);
   Ok(result.unwrap())
@@ -248,10 +248,12 @@ macro_rules! napi_wrap {
       #[inline(always)]
       fn inner $( < $( $x ),* > )? ( $env: & $( $lt )? mut Env , $( $ident : $ty ),* ) -> napi_status $body
 
+      #[cfg(debug_assertions)]
       log::trace!("NAPI ENTER: {}", stringify!($name));
 
       let result = inner( env, $( $ident ),* );
 
+      #[cfg(debug_assertions)]
       log::trace!("NAPI EXIT: {} {}", stringify!($name), result);
 
       if let Some(exception) = try_catch.exception() {
@@ -276,10 +278,12 @@ macro_rules! napi_wrap {
       #[inline(always)]
       fn inner $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status $body
 
+      #[cfg(debug_assertions)]
       log::trace!("NAPI ENTER: {}", stringify!($name));
 
       let result = inner( $( $ident ),* );
 
+      #[cfg(debug_assertions)]
       log::trace!("NAPI EXIT: {} {}", stringify!($name), result);
 
       result