From 894aff75be5cc5e0f4d151038913ce83d39643e7 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Sat, 10 Dec 2022 09:23:50 -0500 Subject: [PATCH] deps: V8: cherry-pick bc831f8ba33b Original commit message: [fastcall] Implement support for onebyte string arguments This CL adds one byte string specialization support for fast API call arguments. It introduces a kOneByteString variant to CTypeInfo. We see a ~6x improvement in Deno's TextEncoder#encode microbenchmark. Rendered results: https://divy-v8-patches.deno.dev/ Bug: chromium:1052746 Change-Id: I47c3a9e101cd18ddc6ad58f627db3a34231b60f7 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4036884 Reviewed-by: Toon Verwaest Reviewed-by: Maya Lekova Commit-Queue: Maya Lekova Cr-Commit-Position: refs/heads/main@{#84552} Refs: https://github.com/v8/v8/commit/bc831f8ba33b79e2eb670faf1f84c4e39aeb0f9f PR-URL: https://github.com/nodejs/node/pull/45788 Reviewed-By: Anna Henningsen Reviewed-By: Jiawen Geng Reviewed-By: Daeyeon Jeong --- common.gypi | 2 +- deps/v8/include/v8-fast-api-calls.h | 21 ++++- deps/v8/src/codegen/machine-type.h | 1 + .../src/compiler/effect-control-linearizer.cc | 45 ++++++++++ deps/v8/src/compiler/fast-api-calls.cc | 1 + deps/v8/src/compiler/simplified-lowering.cc | 1 + deps/v8/src/d8/d8-test.cc | 47 +++++++++++ .../mjsunit/compiler/fast-api-calls-string.js | 84 +++++++++++++++++++ 8 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 deps/v8/test/mjsunit/compiler/fast-api-calls-string.js diff --git a/common.gypi b/common.gypi index c8ecf1ffae8a9b..5bc3d8666abc85 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.5', + 'v8_embedder_string': '-node.6', ##### 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 1826f133210477..9ea43fe2535397 100644 --- a/deps/v8/include/v8-fast-api-calls.h +++ b/deps/v8/include/v8-fast-api-calls.h @@ -248,6 +248,7 @@ class CTypeInfo { kFloat32, kFloat64, kV8Value, + kSeqOneByteString, kApiObject, // This will be deprecated once all users have // migrated from v8::ApiObject to v8::Local. kAny, // This is added to enable untyped representation of fast @@ -379,6 +380,11 @@ struct FastApiArrayBuffer { size_t byte_length; }; +struct FastOneByteString { + const char* data; + uint32_t length; +}; + class V8_EXPORT CFunctionInfo { public: // Construct a struct to hold a CFunction's type information. @@ -438,6 +444,7 @@ struct AnyCType { const FastApiTypedArray* uint64_ta_value; const FastApiTypedArray* float_ta_value; const FastApiTypedArray* double_ta_value; + const FastOneByteString* string_value; FastApiCallbackOptions* options_value; }; }; @@ -614,7 +621,7 @@ class CFunctionInfoImpl : public CFunctionInfo { kReturnType == CTypeInfo::Type::kFloat32 || kReturnType == CTypeInfo::Type::kFloat64 || kReturnType == CTypeInfo::Type::kAny, - "64-bit int and api object values are not currently " + "64-bit int, string and api object values are not currently " "supported return types."); } @@ -735,6 +742,18 @@ struct TypeInfoHelper { } }; +template <> +struct TypeInfoHelper { + static constexpr CTypeInfo::Flags Flags() { return CTypeInfo::Flags::kNone; } + + static constexpr CTypeInfo::Type Type() { + return CTypeInfo::Type::kSeqOneByteString; + } + static constexpr CTypeInfo::SequenceType SequenceType() { + return CTypeInfo::SequenceType::kScalar; + } +}; + #define STATIC_ASSERT_IMPLIES(COND, ASSERTION, MSG) \ static_assert(((COND) == 0) || (ASSERTION), MSG) diff --git a/deps/v8/src/codegen/machine-type.h b/deps/v8/src/codegen/machine-type.h index 29d7de75838e5e..38834d2394ce0d 100644 --- a/deps/v8/src/codegen/machine-type.h +++ b/deps/v8/src/codegen/machine-type.h @@ -315,6 +315,7 @@ class MachineType { case CTypeInfo::Type::kFloat64: return MachineType::Float64(); case CTypeInfo::Type::kV8Value: + case CTypeInfo::Type::kSeqOneByteString: case CTypeInfo::Type::kApiObject: return MachineType::AnyTagged(); } diff --git a/deps/v8/src/compiler/effect-control-linearizer.cc b/deps/v8/src/compiler/effect-control-linearizer.cc index 055e3484e72d99..66973abcbcf8fa 100644 --- a/deps/v8/src/compiler/effect-control-linearizer.cc +++ b/deps/v8/src/compiler/effect-control-linearizer.cc @@ -5395,6 +5395,50 @@ Node* EffectControlLinearizer::AdaptFastCallArgument( case CTypeInfo::Type::kFloat32: { return __ TruncateFloat64ToFloat32(node); } + case CTypeInfo::Type::kSeqOneByteString: { + // Check that the value is a HeapObject. + Node* value_is_smi = ObjectIsSmi(node); + __ GotoIf(value_is_smi, if_error); + + Node* map = __ LoadField(AccessBuilder::ForMap(), node); + Node* instance_type = + __ LoadField(AccessBuilder::ForMapInstanceType(), map); + + Node* encoding = __ Word32And( + instance_type, + __ Int32Constant(kStringRepresentationAndEncodingMask)); + + Node* is_onebytestring = __ Word32Equal( + encoding, __ Int32Constant(kSeqOneByteStringTag)); + __ GotoIfNot(is_onebytestring, if_error); + + Node* length_in_bytes = + __ LoadField(AccessBuilder::ForStringLength(), node); + Node* data_ptr = __ IntPtrAdd( + node, __ IntPtrConstant(SeqOneByteString::kHeaderSize - + kHeapObjectTag)); + + constexpr int kAlign = alignof(FastOneByteString); + constexpr int kSize = sizeof(FastOneByteString); + static_assert(kSize == sizeof(uintptr_t) + sizeof(size_t), + "The size of " + "FastOneByteString isn't equal to the sum of its " + "expected members."); + Node* stack_slot = __ StackSlot(kSize, kAlign); + + __ Store(StoreRepresentation(MachineType::PointerRepresentation(), + kNoWriteBarrier), + stack_slot, 0, data_ptr); + __ Store(StoreRepresentation(MachineRepresentation::kWord32, + kNoWriteBarrier), + stack_slot, sizeof(size_t), length_in_bytes); + + static_assert(sizeof(uintptr_t) == sizeof(size_t), + "The string length can't " + "fit the PointerRepresentation used to store it."); + + return stack_slot; + } default: { return node; } @@ -5600,6 +5644,7 @@ Node* EffectControlLinearizer::LowerFastApiCall(Node* node) { case CTypeInfo::Type::kFloat64: return ChangeFloat64ToTagged( c_call_result, CheckForMinusZeroMode::kCheckForMinusZero); + case CTypeInfo::Type::kSeqOneByteString: case CTypeInfo::Type::kV8Value: case CTypeInfo::Type::kApiObject: case CTypeInfo::Type::kUint8: diff --git a/deps/v8/src/compiler/fast-api-calls.cc b/deps/v8/src/compiler/fast-api-calls.cc index 4e3f92a99e5c27..4279048ee17f50 100644 --- a/deps/v8/src/compiler/fast-api-calls.cc +++ b/deps/v8/src/compiler/fast-api-calls.cc @@ -29,6 +29,7 @@ ElementsKind GetTypedArrayElementsKind(CTypeInfo::Type type) { case CTypeInfo::Type::kFloat64: return FLOAT64_ELEMENTS; case CTypeInfo::Type::kVoid: + case CTypeInfo::Type::kSeqOneByteString: case CTypeInfo::Type::kBool: case CTypeInfo::Type::kV8Value: case CTypeInfo::Type::kApiObject: diff --git a/deps/v8/src/compiler/simplified-lowering.cc b/deps/v8/src/compiler/simplified-lowering.cc index 17200099caf5af..2d4766380995b6 100644 --- a/deps/v8/src/compiler/simplified-lowering.cc +++ b/deps/v8/src/compiler/simplified-lowering.cc @@ -1961,6 +1961,7 @@ class RepresentationSelector { case CTypeInfo::Type::kFloat64: return UseInfo::CheckedNumberAsFloat64(kDistinguishZeros, feedback); case CTypeInfo::Type::kV8Value: + case CTypeInfo::Type::kSeqOneByteString: case CTypeInfo::Type::kApiObject: return UseInfo::AnyTagged(); } diff --git a/deps/v8/src/d8/d8-test.cc b/deps/v8/src/d8/d8-test.cc index 068a2b2329891c..a4d5abdc442df3 100644 --- a/deps/v8/src/d8/d8-test.cc +++ b/deps/v8/src/d8/d8-test.cc @@ -42,6 +42,43 @@ class FastCApiObject { public: static FastCApiObject& instance(); +#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS + static AnyCType CopyStringFastCallbackPatch(AnyCType receiver, + AnyCType should_fallback, + AnyCType source, AnyCType out, + AnyCType options) { + AnyCType ret; + CopyStringFastCallback(receiver.object_value, should_fallback.bool_value, + *source.string_value, *out.uint8_ta_value, + *options.options_value); + return ret; + } + +#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS + static void CopyStringFastCallback(Local receiver, + bool should_fallback, + const FastOneByteString& source, + const FastApiTypedArray& out, + FastApiCallbackOptions& options) { + FastCApiObject* self = UnwrapObject(receiver); + self->fast_call_count_++; + + if (should_fallback) { + options.fallback = true; + } else { + options.fallback = false; + } + + uint8_t* memory = nullptr; + CHECK(out.getStorageIfAligned(&memory)); + memcpy(memory, source.data, source.length); + } + + static void CopyStringSlowCallback(const FunctionCallbackInfo& args) { + FastCApiObject* self = UnwrapObject(args.This()); + CHECK_SELF_OR_THROW(); + self->slow_call_count_++; + } #ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS static AnyCType AddAllFastCallbackPatch(AnyCType receiver, AnyCType should_fallback, @@ -1084,6 +1121,16 @@ Local Shell::CreateTestFastCApiTemplate(Isolate* isolate) { PerIsolateData::Get(isolate)->SetTestApiObjectCtor(api_obj_ctor); Local signature = Signature::New(isolate, api_obj_ctor); { + CFunction copy_str_func = CFunction::Make( + FastCApiObject::CopyStringFastCallback V8_IF_USE_SIMULATOR( + FastCApiObject::CopyStringFastCallbackPatch)); + api_obj_ctor->PrototypeTemplate()->Set( + isolate, "copy_string", + FunctionTemplate::New(isolate, FastCApiObject::CopyStringSlowCallback, + Local(), signature, 1, + ConstructorBehavior::kThrow, + SideEffectType::kHasSideEffect, ©_str_func)); + CFunction add_all_c_func = CFunction::Make(FastCApiObject::AddAllFastCallback V8_IF_USE_SIMULATOR( FastCApiObject::AddAllFastCallbackPatch)); diff --git a/deps/v8/test/mjsunit/compiler/fast-api-calls-string.js b/deps/v8/test/mjsunit/compiler/fast-api-calls-string.js new file mode 100644 index 00000000000000..70e33c9a2d5f33 --- /dev/null +++ b/deps/v8/test/mjsunit/compiler/fast-api-calls-string.js @@ -0,0 +1,84 @@ +// Copyright 2022 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file excercises one byte string support for fast API calls. + +// Flags: --turbo-fast-api-calls --expose-fast-api --allow-natives-syntax --turbofan +// --always-turbofan is disabled because we rely on particular feedback for +// optimizing to the fastest path. +// Flags: --no-always-turbofan +// The test relies on optimizing/deoptimizing at predictable moments, so +// it's not suitable for deoptimization fuzzing. +// Flags: --deopt-every-n-times=0 + +assertThrows(() => d8.test.FastCAPI()); +const fast_c_api = new d8.test.FastCAPI(); + +function assertSlowCall(input) { + assertEquals(new Uint8Array(input.length), copy_string(false, input)); +} + +function assertFastCall(input) { + const bytes = Uint8Array.from(input, c => c.charCodeAt(0)); + assertEquals(bytes, copy_string(false, input)); +} + +function copy_string(should_fallback = false, input) { + const buffer = new Uint8Array(input.length); + fast_c_api.copy_string(should_fallback, input, buffer); + return buffer; +} + +%PrepareFunctionForOptimization(copy_string); +assertSlowCall('Hello'); +%OptimizeFunctionOnNextCall(copy_string); + +fast_c_api.reset_counts(); +assertFastCall('Hello'); +assertFastCall(''); +assertFastCall(['Hello', 'World'].join('')); +assertOptimized(copy_string); +assertEquals(3, fast_c_api.fast_call_count()); +assertEquals(0, fast_c_api.slow_call_count()); + +// Fall back for twobyte strings. +fast_c_api.reset_counts(); +assertSlowCall('Hello\u{10000}'); +assertSlowCall('नमस्ते'); +assertSlowCall(['नमस्ते', 'World'].join('')); +assertOptimized(copy_string); +assertEquals(0, fast_c_api.fast_call_count()); +assertEquals(3, fast_c_api.slow_call_count()); + +// Fall back for cons strings. +function getTwoByteString() { + return '\u1234t'; +} +function getCons() { + return 'hello' + getTwoByteString() +} + +fast_c_api.reset_counts(); +assertSlowCall(getCons()); +assertOptimized(copy_string); +assertEquals(0, fast_c_api.fast_call_count()); +assertEquals(1, fast_c_api.slow_call_count()); + +// Fall back for sliced strings. +fast_c_api.reset_counts(); +function getSliced() { + return getCons().slice(1); +} +assertSlowCall(getSliced()); +assertOptimized(copy_string); +assertEquals(0, fast_c_api.fast_call_count()); +assertEquals(1, fast_c_api.slow_call_count()); + +// Fall back for SMI and non-string inputs. +fast_c_api.reset_counts(); +assertSlowCall(1); +assertSlowCall({}); +assertSlowCall(new Uint8Array(1)); +assertEquals(0, fast_c_api.fast_call_count()); +assertEquals(3, fast_c_api.slow_call_count());