From b8b520104d921935215747bc4fb774b7bc3a1ee9 Mon Sep 17 00:00:00 2001 From: Ben Creech Date: Tue, 23 Dec 2025 20:26:41 -0500 Subject: [PATCH] Reduce calls to get array length --- src/py_mini_racer/_abstract_context.py | 4 +++ src/py_mini_racer/_context.py | 9 +++++ src/py_mini_racer/_dll.py | 3 ++ src/py_mini_racer/_objects.py | 5 ++- src/v8_py_frontend/context.cc | 23 ++++++++++++ src/v8_py_frontend/context.h | 2 ++ src/v8_py_frontend/exports.cc | 11 ++++++ src/v8_py_frontend/exports.h | 10 ++++++ src/v8_py_frontend/object_manipulator.cc | 45 +++++++++++++++++++++++- src/v8_py_frontend/object_manipulator.h | 3 ++ 10 files changed, 113 insertions(+), 2 deletions(-) diff --git a/src/py_mini_racer/_abstract_context.py b/src/py_mini_racer/_abstract_context.py index 8bd924f7..b22d0777 100644 --- a/src/py_mini_racer/_abstract_context.py +++ b/src/py_mini_racer/_abstract_context.py @@ -78,6 +78,10 @@ def array_insert( ) -> None: pass + @abstractmethod + def array_push(self, arr: JSArray, new_val: PythonJSConvertedTypes) -> None: + pass + @abstractmethod def call_function( self, diff --git a/src/py_mini_racer/_context.py b/src/py_mini_racer/_context.py index 88f8042c..1894ffc4 100644 --- a/src/py_mini_racer/_context.py +++ b/src/py_mini_racer/_context.py @@ -199,6 +199,15 @@ def array_insert( ) ).to_python_or_raise() + def array_push(self, arr: JSArray, new_val: PythonJSConvertedTypes) -> None: + arr_handle = python_to_value_handle(self, arr) + new_val_handle = python_to_value_handle(self, new_val) + + # Convert the value just to convert any exceptions (and GC the result) + self._wrap_raw_handle( + self._get_dll().mr_array_push(self._ctx, arr_handle.raw, new_val_handle.raw) + ).to_python_or_raise() + def call_function( self, func: JSFunction, diff --git a/src/py_mini_racer/_dll.py b/src/py_mini_racer/_dll.py index 30b89ded..ae3bf254 100644 --- a/src/py_mini_racer/_dll.py +++ b/src/py_mini_racer/_dll.py @@ -134,6 +134,9 @@ def _build_dll_handle(dll_path: Path) -> ctypes.CDLL: # noqa: PLR0915 ] handle.mr_splice_array.restype = RawValueHandle + handle.mr_array_push.argtypes = [ctypes.c_uint64, RawValueHandle, RawValueHandle] + handle.mr_array_push.restype = RawValueHandle + handle.mr_call_function.argtypes = [ ctypes.c_uint64, RawValueHandle, diff --git a/src/py_mini_racer/_objects.py b/src/py_mini_racer/_objects.py index 4b081f1a..98d7528e 100644 --- a/src/py_mini_racer/_objects.py +++ b/src/py_mini_racer/_objects.py @@ -132,7 +132,10 @@ def insert(self, index: int, new_obj: PythonJSConvertedTypes) -> None: def __iter__(self) -> Iterator[PythonJSConvertedTypes]: for i in range(len(self)): - yield self[i] + yield self._ctx.get_object_item(self, i) + + def append(self, value: PythonJSConvertedTypes) -> None: + self._ctx.array_push(self, value) class JSFunctionImpl(JSMappedObjectImpl, JSFunction): diff --git a/src/v8_py_frontend/context.cc b/src/v8_py_frontend/context.cc index a9e2d8b8..ab4d892f 100644 --- a/src/v8_py_frontend/context.cc +++ b/src/v8_py_frontend/context.cc @@ -277,6 +277,29 @@ auto Context::SpliceArray(BinaryValueHandle* obj_handle, .get()); } +auto Context::ArrayPush(BinaryValueHandle* obj_handle, + BinaryValueHandle* new_val_handle) + -> BinaryValueHandle* { + auto obj_hc = MakeHandleConverter(obj_handle, "Bad handle: obj"); + if (!obj_hc) { + return obj_hc.GetErrorHandle(); + } + + auto new_val_hc = MakeHandleConverter(new_val_handle, "Bad handle: new_val"); + if (!new_val_hc) { + return new_val_hc.GetErrorHandle(); + } + + return bv_registry_.Remember( + isolate_manager_ + .Run([this, obj_ptr = obj_hc.GetPtr(), + new_val_ptr = new_val_hc.GetPtr()](v8::Isolate* isolate) { + return object_manipulator_.Push(isolate, obj_ptr.get(), + new_val_ptr.get()); + }) + .get()); +} + void Context::FreeBinaryValue(BinaryValueHandle* val) { bv_registry_.Forget(val); } diff --git a/src/v8_py_frontend/context.h b/src/v8_py_frontend/context.h index 67a64502..327231b5 100644 --- a/src/v8_py_frontend/context.h +++ b/src/v8_py_frontend/context.h @@ -60,6 +60,8 @@ class Context { int32_t start, int32_t delete_count, BinaryValueHandle* new_val_handle) -> BinaryValueHandle*; + auto ArrayPush(BinaryValueHandle* obj_handle, + BinaryValueHandle* new_val_handle) -> BinaryValueHandle*; auto CallFunction(BinaryValueHandle* func_handle, BinaryValueHandle* this_handle, BinaryValueHandle* argv_handle, diff --git a/src/v8_py_frontend/exports.cc b/src/v8_py_frontend/exports.cc index 0c424383..d761ee1c 100644 --- a/src/v8_py_frontend/exports.cc +++ b/src/v8_py_frontend/exports.cc @@ -249,6 +249,17 @@ LIB_EXPORT auto mr_splice_array(uint64_t context_id, new_val_handle); } +LIB_EXPORT auto mr_array_push(uint64_t context_id, + MiniRacer::BinaryValueHandle* array_handle, + MiniRacer::BinaryValueHandle* new_val_handle) + -> MiniRacer::BinaryValueHandle* { + auto context = GetContext(context_id); + if (!context) { + return nullptr; + } + return context->ArrayPush(array_handle, new_val_handle); +} + LIB_EXPORT auto mr_call_function(uint64_t context_id, MiniRacer::BinaryValueHandle* func_handle, MiniRacer::BinaryValueHandle* this_handle, diff --git a/src/v8_py_frontend/exports.h b/src/v8_py_frontend/exports.h index f6b45d0a..de59d16a 100644 --- a/src/v8_py_frontend/exports.h +++ b/src/v8_py_frontend/exports.h @@ -197,6 +197,16 @@ LIB_EXPORT auto mr_splice_array(uint64_t context_id, MiniRacer::BinaryValueHandle* new_val_handle) -> MiniRacer::BinaryValueHandle*; +/** Call JavaScript `Array.prototype.push(array, new_val)`. + * + * The result of the operation (passed into the callback) is an exception + * in case of failure. + **/ +LIB_EXPORT auto mr_array_push(uint64_t context_id, + MiniRacer::BinaryValueHandle* array_handle, + MiniRacer::BinaryValueHandle* new_val_handle) + -> MiniRacer::BinaryValueHandle*; + /** Cancel the given asynchronous task. * * (Such tasks are started by mr_eval, mr_call_function, mr_heap_stats, and diff --git a/src/v8_py_frontend/object_manipulator.cc b/src/v8_py_frontend/object_manipulator.cc index 8f043c83..2c77a60b 100644 --- a/src/v8_py_frontend/object_manipulator.cc +++ b/src/v8_py_frontend/object_manipulator.cc @@ -137,7 +137,7 @@ auto ObjectManipulator::Splice(v8::Isolate* isolate, } if (!splice_val->IsFunction()) { - return bv_factory_->New("splice method is not a function", + return bv_factory_->New("splice member is not a function", type_execute_exception); } @@ -163,6 +163,49 @@ auto ObjectManipulator::Splice(v8::Isolate* isolate, return bv_factory_->New(local_context, maybe_value.ToLocalChecked()); } +auto ObjectManipulator::Push(v8::Isolate* isolate, + BinaryValue* obj_ptr, + BinaryValue* new_val_ptr) -> BinaryValue::Ptr { + const v8::Isolate::Scope isolate_scope(isolate); + const v8::HandleScope handle_scope(isolate); + const v8::Local local_context = context_->Get()->Get(isolate); + const v8::Context::Scope context_scope(local_context); + + const v8::Local local_obj_val = obj_ptr->ToValue(local_context); + const v8::Local local_obj = local_obj_val.As(); + + // Array.prototype.push doesn't exist in C++ in V8. We have to find the JS + // function and call it: + const v8::Local push_name = + v8::String::NewFromUtf8Literal(isolate, "push"); + + v8::Local push_val; + if (!local_obj->Get(local_context, push_name).ToLocal(&push_val)) { + return bv_factory_->New("no push method on object", type_execute_exception); + } + + if (!push_val->IsFunction()) { + return bv_factory_->New("push member is not a function", + type_execute_exception); + } + + const v8::Local push_func = push_val.As(); + + const v8::TryCatch trycatch(isolate); + + std::vector> argv = { + new_val_ptr->ToValue(local_context)}; + + v8::MaybeLocal maybe_value = push_func->Call( + local_context, local_obj, static_cast(argv.size()), argv.data()); + if (maybe_value.IsEmpty()) { + return bv_factory_->New(local_context, trycatch.Message(), + trycatch.Exception(), type_execute_exception); + } + + return bv_factory_->New(local_context, maybe_value.ToLocalChecked()); +} + auto ObjectManipulator::Call(v8::Isolate* isolate, BinaryValue* func_ptr, BinaryValue* this_ptr, diff --git a/src/v8_py_frontend/object_manipulator.h b/src/v8_py_frontend/object_manipulator.h index 528595dd..031fe92d 100644 --- a/src/v8_py_frontend/object_manipulator.h +++ b/src/v8_py_frontend/object_manipulator.h @@ -38,6 +38,9 @@ class ObjectManipulator { int32_t start, int32_t delete_count, BinaryValue* new_val_ptr) -> BinaryValue::Ptr; + auto Push(v8::Isolate* isolate, + BinaryValue* obj_ptr, + BinaryValue* new_val_ptr) -> BinaryValue::Ptr; auto Call(v8::Isolate* isolate, BinaryValue* func_ptr, BinaryValue* this_ptr,