diff --git a/doc/api/v8.md b/doc/api/v8.md index 670283e17f5d80..a2c5aaba868f6f 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -1304,6 +1304,26 @@ setTimeout(() => { }, 1000); ``` +## `v8.isOneByteRepresentation(content)` + + + +* `content` {string} +* Returns: {boolean} + +Returns `true` if `content` uses one byte as the underlying representation Otherwise, returns `false`. + +```js +const { isOneByteRepresentation } = require('node:v8'); + +isOneByteRepresentation('hello world'); +// Returns: true +isOneByteRepresentation('你好'); +// Returns: false +``` + [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm [Hook Callbacks]: #hook-callbacks [V8]: https://developers.google.com/v8/ diff --git a/lib/v8.js b/lib/v8.js index 7a8979887bab49..381aabfcbfafab 100644 --- a/lib/v8.js +++ b/lib/v8.js @@ -104,6 +104,7 @@ const binding = internalBinding('v8'); const { cachedDataVersionTag, setFlagsFromString: _setFlagsFromString, + isStringOneByteRepresentation: _isStringOneByteRepresentation, updateHeapStatisticsBuffer, updateHeapSpaceStatisticsBuffer, updateHeapCodeStatisticsBuffer, @@ -155,6 +156,17 @@ function setFlagsFromString(flags) { _setFlagsFromString(flags); } +/** + * Return whether this string uses one byte as underlying representation or not. + * @param {string} content + * @returns {boolean} + */ +function isStringOneByteRepresentation(content) { + validateString(content, 'content'); + return _isStringOneByteRepresentation(content); +} + + /** * Gets the current V8 heap statistics. * @returns {{ @@ -439,4 +451,5 @@ module.exports = { startupSnapshot, setHeapSnapshotNearHeapLimit, GCProfiler, + isStringOneByteRepresentation, }; diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 8d49a119c21832..bb007dbdcce486 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -12,6 +12,9 @@ namespace node { using CFunctionCallbackWithOneByteString = uint32_t (*)(v8::Local, const v8::FastOneByteString&); + +using CFunctionCallbackReturnBool = bool (*)(v8::Local unused, + v8::Local receiver); using CFunctionCallback = void (*)(v8::Local unused, v8::Local receiver); using CFunctionCallbackReturnDouble = @@ -90,6 +93,7 @@ class ExternalReferenceRegistry { #define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \ V(CFunctionCallback) \ V(CFunctionCallbackWithOneByteString) \ + V(CFunctionCallbackReturnBool) \ V(CFunctionCallbackReturnDouble) \ V(CFunctionCallbackReturnInt32) \ V(CFunctionCallbackValueReturnDouble) \ diff --git a/src/node_v8.cc b/src/node_v8.cc index a7f0ba7973498e..7bcd9d8ad4c9d2 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -32,6 +32,7 @@ namespace node { namespace v8_utils { using v8::Array; +using v8::CFunction; using v8::Context; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; @@ -238,6 +239,25 @@ void SetFlagsFromString(const FunctionCallbackInfo& args) { V8::SetFlagsFromString(*flags, static_cast(flags.length())); } + +static void IsStringOneByteRepresentation( + const FunctionCallbackInfo& args) { + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsString()); + bool is_one_byte = args[0].As()->IsOneByte(); + args.GetReturnValue().Set(is_one_byte); +} + +static bool FastIsStringOneByteRepresentation(Local receiver, + const Local target) { + CHECK(target->IsString()); + return target.As()->IsOneByte(); +} + +CFunction fast_is_string_one_byte_representation_( + CFunction::Make(FastIsStringOneByteRepresentation)); + + static const char* GetGCTypeName(v8::GCType gc_type) { switch (gc_type) { case v8::GCType::kGCTypeScavenge: @@ -479,6 +499,13 @@ void Initialize(Local target, // Export symbols used by v8.setFlagsFromString() SetMethod(context, target, "setFlagsFromString", SetFlagsFromString); + // Export symbols used by v8.isStringOneByteRepresentation() + SetFastMethodNoSideEffect(context, + target, + "isStringOneByteRepresentation", + IsStringOneByteRepresentation, + &fast_is_string_one_byte_representation_); + // GCProfiler Local t = NewFunctionTemplate(env->isolate(), GCProfiler::New); @@ -498,6 +525,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) { registry->Register(GCProfiler::New); registry->Register(GCProfiler::Start); registry->Register(GCProfiler::Stop); + registry->Register(IsStringOneByteRepresentation); + registry->Register(FastIsStringOneByteRepresentation); + registry->Register(fast_is_string_one_byte_representation_.GetTypeInfo()); } } // namespace v8_utils diff --git a/test/parallel/test-v8-string-is-one-byte-representation.js b/test/parallel/test-v8-string-is-one-byte-representation.js new file mode 100644 index 00000000000000..0403299c01015f --- /dev/null +++ b/test/parallel/test-v8-string-is-one-byte-representation.js @@ -0,0 +1,37 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const assert = require('assert'); +const { isStringOneByteRepresentation } = require('v8'); + +[ + undefined, + null, + false, + 5n, + 5, + Symbol(), + () => {}, + {}, +].forEach((value) => { + assert.throws( + () => { isStringOneByteRepresentation(value); }, + /The "content" argument must be of type string/ + ); +}); + +{ + const latin1String = 'hello world!'; + // Run this inside a for loop to trigger the fast API + for (let i = 0; i < 10_000; i++) { + assert.strictEqual(isStringOneByteRepresentation(latin1String), true); + } +} + +{ + const utf16String = '你好😀😃'; + // Run this inside a for loop to trigger the fast API + for (let i = 0; i < 10_000; i++) { + assert.strictEqual(isStringOneByteRepresentation(utf16String), false); + } +}