From e983b1cece7d24f7b4776798916276d30b9e419a Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Wed, 27 May 2020 23:10:09 -0500 Subject: [PATCH 1/2] deps: V8: cherry-pick 0d6debcc5f08 Original commit message: [turbofan] Fixes for integrating the fast C API This commit adds a few fixes neccessary for integrating the fast C API into Blink: - added default constructor for CFunction - removed a bogus template specialization allowing void* params - extended the public Isolate class Bug: chromium:1052746 Change-Id: I4f2ba84299920e2cc9d66ec1ed59302313db6c0b Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2120587 Commit-Queue: Maya Lekova Reviewed-by: Toon Verwaest Reviewed-by: Georg Neis Cr-Commit-Position: refs/heads/master@{#66986} Refs: https://github.com/v8/v8/commit/0d6debcc5f08fe3c41d07686f82c9de05310519f PR-URL: https://github.com/nodejs/node/pull/33600 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- common.gypi | 2 +- deps/v8/include/v8-fast-api-calls.h | 40 ++++++++++++++++------------- deps/v8/include/v8.h | 12 ++++++++- deps/v8/src/api/api.cc | 9 +++++-- deps/v8/test/cctest/test-api.cc | 2 ++ 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/common.gypi b/common.gypi index 9801c3257ca322..76a55b7b628f72 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.17', + 'v8_embedder_string': '-node.18', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-fast-api-calls.h b/deps/v8/include/v8-fast-api-calls.h index bfce66b652dd2d..79a5d4d82a764d 100644 --- a/deps/v8/include/v8-fast-api-calls.h +++ b/deps/v8/include/v8-fast-api-calls.h @@ -183,30 +183,32 @@ class CTypeInfo { kUnwrappedApiObject, }; - enum ArgFlags : char { - None = 0, - IsArrayBit = 1 << 0, // This argument is first in an array of values. + enum class ArgFlags : uint8_t { + kNone = 0, + kIsArrayBit = 1 << 0, // This argument is first in an array of values. }; static CTypeInfo FromWrapperType(const void* wrapper_type_info, - ArgFlags flags = ArgFlags::None) { + ArgFlags flags = ArgFlags::kNone) { uintptr_t wrapper_type_info_ptr = reinterpret_cast(wrapper_type_info); // Check that the lower kIsWrapperTypeBit bits are 0's. CHECK_EQ( wrapper_type_info_ptr & ~(static_cast(~0) << static_cast(kIsWrapperTypeBit)), - 0); + 0u); // TODO(mslekova): Refactor the manual bit manipulations to use // PointerWithPayload instead. - return CTypeInfo(wrapper_type_info_ptr | flags | kIsWrapperTypeBit); + return CTypeInfo(wrapper_type_info_ptr | static_cast(flags) | + kIsWrapperTypeBit); } static constexpr CTypeInfo FromCType(Type ctype, - ArgFlags flags = ArgFlags::None) { + ArgFlags flags = ArgFlags::kNone) { // ctype cannot be Type::kUnwrappedApiObject. return CTypeInfo( - ((static_cast(ctype) << kTypeOffset) & kTypeMask) | flags); + ((static_cast(ctype) << kTypeOffset) & kTypeMask) | + static_cast(flags)); } const void* GetWrapperInfo() const; @@ -218,7 +220,9 @@ class CTypeInfo { return static_cast((payload_ & kTypeMask) >> kTypeOffset); } - constexpr bool IsArray() const { return payload_ & ArgFlags::IsArrayBit; } + constexpr bool IsArray() const { + return payload_ & static_cast(ArgFlags::kIsArrayBit); + } private: explicit constexpr CTypeInfo(uintptr_t payload) : payload_(payload) {} @@ -283,9 +287,6 @@ SUPPORTED_C_TYPES(SPECIALIZE_GET_C_TYPE_FOR) template struct EnableIfHasWrapperTypeInfo {}; -template <> -struct EnableIfHasWrapperTypeInfo {}; - template struct EnableIfHasWrapperTypeInfo::GetTypeInfo(), void())> { @@ -297,7 +298,7 @@ template struct GetCTypePointerImpl { static constexpr CTypeInfo Get() { return CTypeInfo::FromCType(GetCType::Get().GetType(), - CTypeInfo::IsArrayBit); + CTypeInfo::ArgFlags::kIsArrayBit); } }; @@ -321,7 +322,7 @@ struct GetCTypePointerPointerImpl< T, typename EnableIfHasWrapperTypeInfo::type> { static constexpr CTypeInfo Get() { return CTypeInfo::FromWrapperType(WrapperTraits::GetTypeInfo(), - CTypeInfo::IsArrayBit); + CTypeInfo::ArgFlags::kIsArrayBit); } }; @@ -335,11 +336,12 @@ template class CFunctionInfoImpl : public CFunctionInfo { public: CFunctionInfoImpl() - : return_info_(i::GetCType::Get()), + : return_info_(internal::GetCType::Get()), arg_count_(sizeof...(Args)), - arg_info_{i::GetCType::Get()...} { - static_assert(i::GetCType::Get().GetType() == CTypeInfo::Type::kVoid, - "Only void return types are currently supported."); + arg_info_{internal::GetCType::Get()...} { + static_assert( + internal::GetCType::Get().GetType() == CTypeInfo::Type::kVoid, + "Only void return types are currently supported."); } const CTypeInfo& ReturnInfo() const override { return return_info_; } @@ -359,6 +361,8 @@ class CFunctionInfoImpl : public CFunctionInfo { class V8_EXPORT CFunction { public: + constexpr CFunction() : address_(nullptr), type_info_(nullptr) {} + const CTypeInfo& ReturnInfo() const { return type_info_->ReturnInfo(); } const CTypeInfo& ArgumentInfo(unsigned int index) const { diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index 7c6fdb748bf368..da4eb341295671 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -8184,7 +8184,9 @@ class V8_EXPORT Isolate { array_buffer_allocator_shared(), external_references(nullptr), allow_atomics_wait(true), - only_terminate_in_safe_scope(false) {} + only_terminate_in_safe_scope(false), + embedder_wrapper_type_index(-1), + embedder_wrapper_object_index(-1) {} /** * Allows the host application to provide the address of a function that is @@ -8248,6 +8250,14 @@ class V8_EXPORT Isolate { * Termination is postponed when there is no active SafeForTerminationScope. */ bool only_terminate_in_safe_scope; + + /** + * The following parameters describe the offsets for addressing type info + * for wrapped API objects and are used by the fast C API + * (for details see v8-fast-api-calls.h). + */ + int embedder_wrapper_type_index; + int embedder_wrapper_object_index; }; diff --git a/deps/v8/src/api/api.cc b/deps/v8/src/api/api.cc index b2d6db3661a564..915a781f3aeb77 100644 --- a/deps/v8/src/api/api.cc +++ b/deps/v8/src/api/api.cc @@ -1582,8 +1582,9 @@ void FunctionTemplate::SetCallHandler(FunctionCallback callback, data = v8::Undefined(reinterpret_cast(isolate)); } obj->set_data(*Utils::OpenHandle(*data)); - if (c_function != nullptr) { - DCHECK_NOT_NULL(c_function->GetAddress()); + // Blink passes CFunction's constructed with the default constructor + // for non-fast calls, so we should check the address too. + if (c_function != nullptr && c_function->GetAddress()) { i::FunctionTemplateInfo::SetCFunction( isolate, info, i::handle(*FromCData(isolate, c_function->GetAddress()), isolate)); @@ -8333,6 +8334,10 @@ void Isolate::Initialize(Isolate* isolate, } i_isolate->set_only_terminate_in_safe_scope( params.only_terminate_in_safe_scope); + i_isolate->set_embedder_wrapper_type_index( + params.embedder_wrapper_type_index); + i_isolate->set_embedder_wrapper_object_index( + params.embedder_wrapper_object_index); if (!i::V8::GetCurrentPlatform() ->GetForegroundTaskRunner(isolate) diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index 7f4b3002038179..6b7ea85452d2b0 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -27064,6 +27064,8 @@ void SetupTest(v8::Local initial_value, LocalContext* env, v8::Isolate* isolate = CcTest::isolate(); v8::CFunction c_func = v8::CFunction::Make(ApiNumberChecker::CheckArgFast); + CHECK_EQ(c_func.ArgumentInfo(0).GetType(), + v8::CTypeInfo::Type::kUnwrappedApiObject); Local checker_templ = v8::FunctionTemplate::New( isolate, ApiNumberChecker::CheckArgSlow, v8::Local(), From d8eef83757c263672832687ac7667927a7d0c059 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Thu, 28 May 2020 00:10:22 -0500 Subject: [PATCH 2/2] process: use v8 fast api calls for hrtime Refs: https://github.com/nodejs/node/issues/33374 PR-URL: https://github.com/nodejs/node/pull/33600 Reviewed-By: Anna Henningsen Reviewed-By: Matteo Collina --- lib/internal/process/per_thread.js | 9 +- src/api/environment.cc | 2 + src/node_process_methods.cc | 131 +++++++++++++++++++++------- test/cctest/test_base_object_ptr.cc | 52 ++++++----- 4 files changed, 136 insertions(+), 58 deletions(-) diff --git a/lib/internal/process/per_thread.js b/lib/internal/process/per_thread.js index d219cd7298fc15..303e71703d2fe5 100644 --- a/lib/internal/process/per_thread.js +++ b/lib/internal/process/per_thread.js @@ -41,7 +41,6 @@ function assert(x, msg) { function wrapProcessMethods(binding) { const { hrtime: _hrtime, - hrtimeBigInt: _hrtimeBigInt, cpuUsage: _cpuUsage, memoryUsage: _memoryUsage, resourceUsage: _resourceUsage @@ -113,10 +112,10 @@ function wrapProcessMethods(binding) { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. - const hrValues = new Uint32Array(3); + const hrValues = new Uint32Array(_hrtime.buffer); function hrtime(time) { - _hrtime(hrValues); + _hrtime.hrtime(); if (time !== undefined) { if (!ArrayIsArray(time)) { @@ -140,9 +139,9 @@ function wrapProcessMethods(binding) { // Use a BigUint64Array in the closure because this is actually a bit // faster than simply returning a BigInt from C++ in V8 7.1. - const hrBigintValues = new BigUint64Array(1); + const hrBigintValues = new BigUint64Array(_hrtime.buffer, 0, 1); function hrtimeBigInt() { - _hrtimeBigInt(hrBigintValues); + _hrtime.hrtimeBigInt(); return hrBigintValues[0]; } diff --git a/src/api/environment.cc b/src/api/environment.cc index 5349657d4c5a8d..99b6d8556101fd 100644 --- a/src/api/environment.cc +++ b/src/api/environment.cc @@ -209,6 +209,8 @@ void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) { // heap based on the actual physical memory. params->constraints.ConfigureDefaults(total_memory, 0); } + params->embedder_wrapper_object_index = BaseObject::InternalFields::kSlot; + params->embedder_wrapper_type_index = std::numeric_limits::max(); } void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) { diff --git a/src/node_process_methods.cc b/src/node_process_methods.cc index 6013dbb86b72fc..d580f74478cbf7 100644 --- a/src/node_process_methods.cc +++ b/src/node_process_methods.cc @@ -7,6 +7,7 @@ #include "node_process.h" #include "util-inl.h" #include "uv.h" +#include "v8-fast-api-calls.h" #include "v8.h" #include @@ -33,7 +34,7 @@ namespace node { using v8::Array; using v8::ArrayBuffer; -using v8::BigUint64Array; +using v8::BackingStore; using v8::Context; using v8::Float64Array; using v8::FunctionCallbackInfo; @@ -46,7 +47,6 @@ using v8::Number; using v8::Object; using v8::String; using v8::Uint32; -using v8::Uint32Array; using v8::Value; namespace per_process { @@ -131,35 +131,6 @@ static void Cwd(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(cwd); } - -// Hrtime exposes libuv's uv_hrtime() high-resolution timer. - -// This is the legacy version of hrtime before BigInt was introduced in -// JavaScript. -// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, -// so this function instead fills in an Uint32Array with 3 entries, -// to avoid any integer overflow possibility. -// The first two entries contain the second part of the value -// broken into the upper/lower 32 bits to be converted back in JS, -// because there is no Uint64Array in JS. -// The third entry contains the remaining nanosecond part of the value. -static void Hrtime(const FunctionCallbackInfo& args) { - uint64_t t = uv_hrtime(); - - Local ab = args[0].As()->Buffer(); - uint32_t* fields = static_cast(ab->GetBackingStore()->Data()); - - fields[0] = (t / NANOS_PER_SEC) >> 32; - fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; - fields[2] = t % NANOS_PER_SEC; -} - -static void HrtimeBigInt(const FunctionCallbackInfo& args) { - Local ab = args[0].As()->Buffer(); - uint64_t* fields = static_cast(ab->GetBackingStore()->Data()); - fields[0] = uv_hrtime(); -} - static void Kill(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); Local context = env->context(); @@ -452,6 +423,85 @@ static void ReallyExit(const FunctionCallbackInfo& args) { env->Exit(code); } +class FastHrtime : public BaseObject { + public: + static Local New(Environment* env) { + Local otmpl = v8::ObjectTemplate::New(env->isolate()); + otmpl->SetInternalFieldCount(FastHrtime::kInternalFieldCount); + + auto create_func = [env](auto fast_func, auto slow_func) { + auto cfunc = v8::CFunction::Make(fast_func); + return v8::FunctionTemplate::New(env->isolate(), + slow_func, + Local(), + Local(), + 0, + v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasNoSideEffect, + &cfunc); + }; + + otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"), + create_func(FastNumber, SlowNumber)); + otmpl->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "hrtimeBigInt"), + create_func(FastBigInt, SlowBigInt)); + + Local obj = otmpl->NewInstance(env->context()).ToLocalChecked(); + + Local ab = ArrayBuffer::New(env->isolate(), 12); + new FastHrtime(env, obj, ab->GetBackingStore()); + obj->Set( + env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "buffer"), ab) + .ToChecked(); + + return obj; + } + + private: + FastHrtime(Environment* env, + Local object, + std::shared_ptr backing_store) + : BaseObject(env, object), backing_store_(backing_store) {} + + void MemoryInfo(MemoryTracker* tracker) const override {} + + SET_MEMORY_INFO_NAME(FastHrtime) + SET_SELF_SIZE(FastHrtime) + + // This is the legacy version of hrtime before BigInt was introduced in + // JavaScript. + // The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, + // so this function instead fills in an Uint32Array with 3 entries, + // to avoid any integer overflow possibility. + // The first two entries contain the second part of the value + // broken into the upper/lower 32 bits to be converted back in JS, + // because there is no Uint64Array in JS. + // The third entry contains the remaining nanosecond part of the value. + static void FastNumber(FastHrtime* receiver) { + uint64_t t = uv_hrtime(); + uint32_t* fields = static_cast(receiver->backing_store_->Data()); + fields[0] = (t / NANOS_PER_SEC) >> 32; + fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; + fields[2] = t % NANOS_PER_SEC; + } + + static void SlowNumber(const FunctionCallbackInfo& args) { + FastNumber(FromJSObject(args.Holder())); + } + + static void FastBigInt(FastHrtime* receiver) { + uint64_t t = uv_hrtime(); + uint64_t* fields = static_cast(receiver->backing_store_->Data()); + fields[0] = t; + } + + static void SlowBigInt(const FunctionCallbackInfo& args) { + FastBigInt(FromJSObject(args.Holder())); + } + + std::shared_ptr backing_store_; +}; + static void InitializeProcessMethods(Local target, Local unused, Local context, @@ -475,8 +525,6 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "_rawDebug", RawDebug); env->SetMethod(target, "memoryUsage", MemoryUsage); env->SetMethod(target, "cpuUsage", CPUUsage); - env->SetMethod(target, "hrtime", Hrtime); - env->SetMethod(target, "hrtimeBigInt", HrtimeBigInt); env->SetMethod(target, "resourceUsage", ResourceUsage); env->SetMethod(target, "_getActiveRequests", GetActiveRequests); @@ -488,9 +536,26 @@ static void InitializeProcessMethods(Local target, env->SetMethod(target, "reallyExit", ReallyExit); env->SetMethodNoSideEffect(target, "uptime", Uptime); env->SetMethod(target, "patchProcessObject", PatchProcessObject); + + target + ->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "hrtime"), + FastHrtime::New(env)) + .ToChecked(); } } // namespace node +namespace v8 { +template <> +class WrapperTraits { + public: + static const void* GetTypeInfo() { + static const int tag = 0; + return reinterpret_cast(&tag); + } +}; +} // namespace v8 + NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_methods, node::InitializeProcessMethods) diff --git a/test/cctest/test_base_object_ptr.cc b/test/cctest/test_base_object_ptr.cc index 18e27edba8cd53..9006ea2a3ba519 100644 --- a/test/cctest/test_base_object_ptr.cc +++ b/test/cctest/test_base_object_ptr.cc @@ -14,6 +14,10 @@ using v8::Isolate; using v8::Local; using v8::Object; +// Environments may come with existing BaseObject instances. +// This variable offsets the expected BaseObject counts. +static const int BASE_OBJECT_COUNT = 1; + class BaseObjectPtrTest : public EnvironmentTestFixture {}; class DummyBaseObject : public BaseObject { @@ -47,12 +51,12 @@ TEST_F(BaseObjectPtrTest, ScopedDetached) { Env env_{handle_scope, argv}; Environment* env = *env_; - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); { BaseObjectPtr ptr = DummyBaseObject::NewDetached(env); - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); } - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); } TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) { @@ -63,14 +67,14 @@ TEST_F(BaseObjectPtrTest, ScopedDetachedWithWeak) { BaseObjectWeakPtr weak_ptr; - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); { BaseObjectPtr ptr = DummyBaseObject::NewDetached(env); weak_ptr = ptr; - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); } EXPECT_EQ(weak_ptr.get(), nullptr); - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); } TEST_F(BaseObjectPtrTest, Undetached) { @@ -79,12 +83,16 @@ TEST_F(BaseObjectPtrTest, Undetached) { Env env_{handle_scope, argv}; Environment* env = *env_; - node::AddEnvironmentCleanupHook(isolate_, [](void* arg) { - EXPECT_EQ(static_cast(arg)->base_object_count(), 0); - }, env); + node::AddEnvironmentCleanupHook( + isolate_, + [](void* arg) { + EXPECT_EQ(static_cast(arg)->base_object_count(), + BASE_OBJECT_COUNT); + }, + env); BaseObjectPtr ptr = DummyBaseObject::New(env); - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); } TEST_F(BaseObjectPtrTest, GCWeak) { @@ -101,21 +109,21 @@ TEST_F(BaseObjectPtrTest, GCWeak) { weak_ptr = ptr; ptr->MakeWeak(); - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); EXPECT_EQ(weak_ptr.get(), ptr.get()); EXPECT_EQ(weak_ptr->persistent().IsWeak(), false); ptr.reset(); } - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); EXPECT_NE(weak_ptr.get(), nullptr); EXPECT_EQ(weak_ptr->persistent().IsWeak(), true); v8::V8::SetFlagsFromString("--expose-gc"); isolate_->RequestGarbageCollectionForTesting(Isolate::kFullGarbageCollection); - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); EXPECT_EQ(weak_ptr.get(), nullptr); } @@ -126,7 +134,7 @@ TEST_F(BaseObjectPtrTest, Moveable) { Environment* env = *env_; BaseObjectPtr ptr = DummyBaseObject::NewDetached(env); - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); BaseObjectWeakPtr weak_ptr { ptr }; EXPECT_EQ(weak_ptr.get(), ptr.get()); @@ -137,12 +145,12 @@ TEST_F(BaseObjectPtrTest, Moveable) { BaseObjectWeakPtr weak_ptr2 = std::move(weak_ptr); EXPECT_EQ(weak_ptr2.get(), ptr2.get()); EXPECT_EQ(weak_ptr.get(), nullptr); - EXPECT_EQ(env->base_object_count(), 1); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 1); ptr2.reset(); EXPECT_EQ(weak_ptr2.get(), nullptr); - EXPECT_EQ(env->base_object_count(), 0); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT); } TEST_F(BaseObjectPtrTest, NestedClasses) { @@ -163,14 +171,18 @@ TEST_F(BaseObjectPtrTest, NestedClasses) { Env env_{handle_scope, argv}; Environment* env = *env_; - node::AddEnvironmentCleanupHook(isolate_, [](void* arg) { - EXPECT_EQ(static_cast(arg)->base_object_count(), 0); - }, env); + node::AddEnvironmentCleanupHook( + isolate_, + [](void* arg) { + EXPECT_EQ(static_cast(arg)->base_object_count(), + BASE_OBJECT_COUNT); + }, + env); ObjectWithPtr* obj = new ObjectWithPtr(env, DummyBaseObject::MakeJSObject(env)); obj->ptr1 = DummyBaseObject::NewDetached(env); obj->ptr2 = DummyBaseObject::New(env); - EXPECT_EQ(env->base_object_count(), 3); + EXPECT_EQ(env->base_object_count(), BASE_OBJECT_COUNT + 3); }