From 021710861e2d8847f86d7749633c7eca39958e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Sat, 27 Jun 2026 14:39:19 +0300 Subject: [PATCH] buffer: add fast api for isUtf8 and isAscii MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gürgün Dayıoğlu --- src/node_buffer.cc | 73 ++++++++++++++++--- .../test-buffer-isutf8-isascii-fast.js | 34 +++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 test/parallel/test-buffer-isutf8-isascii-fast.js diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 9adb02517efc42..5e31f78b35b29e 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1360,19 +1360,52 @@ void FastSwap64(Local receiver, static CFunction fast_swap64(CFunction::Make(FastSwap64)); +static bool ValidateUtf8(Local value, bool* was_detached) { + ArrayBufferViewContents abv(value); + *was_detached = abv.WasDetached(); + return !*was_detached && simdutf::validate_utf8(abv.data(), abv.length()); +} + static void IsUtf8(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsTypedArray() || args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); - ArrayBufferViewContents abv(args[0]); - if (abv.WasDetached()) { + bool was_detached; + const bool result = ValidateUtf8(args[0], &was_detached); + if (was_detached) { return node::THROW_ERR_INVALID_STATE( env, "Cannot validate on a detached buffer"); } - args.GetReturnValue().Set(simdutf::validate_utf8(abv.data(), abv.length())); + args.GetReturnValue().Set(result); +} + +static bool FastIsUtf8(Local receiver, + Local value, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + TRACK_V8_FAST_API_CALL("buffer.isUtf8"); + HandleScope scope(options.isolate); + + bool was_detached; + const bool result = ValidateUtf8(value, &was_detached); + if (was_detached) { + node::THROW_ERR_INVALID_STATE(options.isolate, + "Cannot validate on a detached buffer"); + return false; + } + return result; +} + +static CFunction fast_is_utf8(CFunction::Make(FastIsUtf8)); + +static bool ValidateAscii(Local value, bool* was_detached) { + ArrayBufferViewContents abv(value); + *was_detached = abv.WasDetached(); + return !*was_detached && + !simdutf::validate_ascii_with_errors(abv.data(), abv.length()).error; } static void IsAscii(const FunctionCallbackInfo& args) { @@ -1380,17 +1413,36 @@ static void IsAscii(const FunctionCallbackInfo& args) { CHECK_EQ(args.Length(), 1); CHECK(args[0]->IsTypedArray() || args[0]->IsArrayBuffer() || args[0]->IsSharedArrayBuffer()); - ArrayBufferViewContents abv(args[0]); - if (abv.WasDetached()) { + bool was_detached; + const bool result = ValidateAscii(args[0], &was_detached); + if (was_detached) { return node::THROW_ERR_INVALID_STATE( env, "Cannot validate on a detached buffer"); } - args.GetReturnValue().Set( - !simdutf::validate_ascii_with_errors(abv.data(), abv.length()).error); + args.GetReturnValue().Set(result); +} + +static bool FastIsAscii(Local receiver, + Local value, + // NOLINTNEXTLINE(runtime/references) + FastApiCallbackOptions& options) { + TRACK_V8_FAST_API_CALL("buffer.isAscii"); + HandleScope scope(options.isolate); + + bool was_detached; + const bool result = ValidateAscii(value, &was_detached); + if (was_detached) { + node::THROW_ERR_INVALID_STATE(options.isolate, + "Cannot validate on a detached buffer"); + return false; + } + return result; } +static CFunction fast_is_ascii(CFunction::Make(FastIsAscii)); + void SetBufferPrototype(const FunctionCallbackInfo& args) { Realm* realm = Realm::GetCurrent(args); @@ -1762,8 +1814,9 @@ void Initialize(Local target, SetFastMethod(context, target, "swap32", Swap32, &fast_swap32); SetFastMethod(context, target, "swap64", Swap64, &fast_swap64); - SetMethodNoSideEffect(context, target, "isUtf8", IsUtf8); - SetMethodNoSideEffect(context, target, "isAscii", IsAscii); + SetFastMethodNoSideEffect(context, target, "isUtf8", IsUtf8, &fast_is_utf8); + SetFastMethodNoSideEffect( + context, target, "isAscii", IsAscii, &fast_is_ascii); target ->Set(context, @@ -1836,7 +1889,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(fast_swap64); registry->Register(IsUtf8); + registry->Register(fast_is_utf8); registry->Register(IsAscii); + registry->Register(fast_is_ascii); registry->Register(StringSlice); registry->Register(StringSlice); diff --git a/test/parallel/test-buffer-isutf8-isascii-fast.js b/test/parallel/test-buffer-isutf8-isascii-fast.js new file mode 100644 index 00000000000000..fb2b57025c5272 --- /dev/null +++ b/test/parallel/test-buffer-isutf8-isascii-fast.js @@ -0,0 +1,34 @@ +// Flags: --expose-internals --no-warnings --allow-natives-syntax +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { Buffer, isAscii, isUtf8 } = require('buffer'); + +const ascii = Buffer.from('hello'); +const utf8 = Buffer.from('hello \xc4\x9f'); + +function testFastIsAscii() { + assert.strictEqual(isAscii(ascii), true); +} + +function testFastIsUtf8() { + assert.strictEqual(isUtf8(utf8), true); +} + +eval('%PrepareFunctionForOptimization(isAscii)'); +testFastIsAscii(); +eval('%OptimizeFunctionOnNextCall(isAscii)'); +testFastIsAscii(); + +eval('%PrepareFunctionForOptimization(isUtf8)'); +testFastIsUtf8(); +eval('%OptimizeFunctionOnNextCall(isUtf8)'); +testFastIsUtf8(); + +if (common.isDebug) { + const { internalBinding } = require('internal/test/binding'); + const { getV8FastApiCallCount } = internalBinding('debug'); + assert.strictEqual(getV8FastApiCallCount('buffer.isAscii'), 1); + assert.strictEqual(getV8FastApiCallCount('buffer.isUtf8'), 1); +}