@@ -340,53 +340,44 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo<Value>& args) { |
340 | 340 |
|
341 | 341 | size_t length = source->Length(); |
342 | 342 | size_t utf8_length = 0; |
343 | | - bool is_one_byte = source->IsOneByte(); |
344 | | - |
345 | | - if (is_one_byte) { |
346 | | - // One-byte string (Latin1) - copy to buffer first, then process |
347 | | - MaybeStackBuffer<uint8_t, MAX_SIZE_FOR_STACK_ALLOC> latin1_buffer(length); |
348 | | - source->WriteOneByteV2(isolate, 0, length, latin1_buffer.out()); |
349 | | - |
350 | | - auto data = reinterpret_cast<const char*>(latin1_buffer.out()); |
351 | | - |
352 | | - // Check if it's pure ASCII - if so, we can just copy |
353 | | - simdutf::result result = simdutf::validate_ascii_with_errors(data, length); |
354 | | - if (result.error == simdutf::SUCCESS) { |
355 | | - // Pure ASCII - direct copy |
356 | | - std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( |
357 | | - isolate, length, BackingStoreInitializationMode::kUninitialized); |
358 | | - CHECK(bs); |
359 | | - memcpy(bs->Data(), data, length); |
360 | | - Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(bs)); |
361 | | - args.GetReturnValue().Set(Uint8Array::New(ab, 0, length)); |
362 | | - return; |
363 | | - } |
364 | 343 |
|
365 | | - // Latin1 with non-ASCII characters - need conversion |
366 | | - utf8_length = simdutf::utf8_length_from_latin1(data, length); |
367 | | - std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( |
368 | | - isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); |
369 | | - CHECK(bs); |
370 | | - [[maybe_unused]] size_t written = simdutf::convert_latin1_to_utf8( |
371 | | - data, length, static_cast<char*>(bs->Data())); |
372 | | - DCHECK_EQ(written, utf8_length); |
373 | | - Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(bs)); |
374 | | - args.GetReturnValue().Set(Uint8Array::New(ab, 0, utf8_length)); |
375 | | - return; |
| 344 | + // Inspect the string's flat content directly to determine the encoding and |
| 345 | + // the exact UTF-8 output size, without copying it out of the V8 heap. |
| 346 | + // |
| 347 | + // v8::String::ValueView holds a DisallowGarbageCollection scope, so it must |
| 348 | + // be released before allocating the backing store below. Flattening is cached |
| 349 | + // on the string, so re-acquiring the view for the conversion pass is cheap. |
| 350 | + bool is_one_byte; |
| 351 | + bool is_ascii = false; |
| 352 | + bool is_well_formed = true; |
| 353 | + { |
| 354 | + v8::String::ValueView view(isolate, source); |
| 355 | + is_one_byte = view.is_one_byte(); |
| 356 | + if (is_one_byte) { |
| 357 | + auto data = reinterpret_cast<const char*>(view.data8()); |
| 358 | + is_ascii = simdutf::validate_ascii_with_errors(data, length).error == |
| 359 | + simdutf::SUCCESS; |
| 360 | + utf8_length = |
| 361 | + is_ascii ? length : simdutf::utf8_length_from_latin1(data, length); |
| 362 | + } else { |
| 363 | + auto data = reinterpret_cast<const char16_t*>(view.data16()); |
| 364 | + is_well_formed = |
| 365 | + simdutf::validate_utf16_with_errors(data, length).error == |
| 366 | + simdutf::SUCCESS; |
| 367 | + if (is_well_formed) { |
| 368 | + utf8_length = simdutf::utf8_length_from_utf16(data, length); |
| 369 | + } |
| 370 | + } |
376 | 371 | } |
377 | 372 |
|
378 | | - // Two-byte string (UTF-16) - copy to buffer first |
379 | | - MaybeStackBuffer<uint16_t, MAX_SIZE_FOR_STACK_ALLOC> utf16_buffer(length); |
380 | | - source->WriteV2(isolate, 0, length, utf16_buffer.out()); |
381 | | - |
382 | | - auto data = reinterpret_cast<char16_t*>(utf16_buffer.out()); |
383 | | - |
384 | | - // Check for unpaired surrogates |
385 | | - simdutf::result validation_result = |
386 | | - simdutf::validate_utf16_with_errors(data, length); |
| 373 | + // Rare path: two-byte string with unpaired surrogates. Copy into a mutable |
| 374 | + // buffer, make it well-formed, then encode. |
| 375 | + if (!is_well_formed) { |
| 376 | + MaybeStackBuffer<uint16_t, MAX_SIZE_FOR_STACK_ALLOC> utf16_buffer(length); |
| 377 | + source->WriteV2(isolate, 0, length, utf16_buffer.out()); |
| 378 | + auto data = reinterpret_cast<char16_t*>(utf16_buffer.out()); |
| 379 | + simdutf::to_well_formed_utf16(data, length, data); |
387 | 380 |
|
388 | | - if (validation_result.error == simdutf::SUCCESS) { |
389 | | - // Valid UTF-16 - use the fast path |
390 | 381 | utf8_length = simdutf::utf8_length_from_utf16(data, length); |
391 | 382 | std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( |
392 | 383 | isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); |
@@ -399,16 +390,30 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo<Value>& args) { |
399 | 390 | return; |
400 | 391 | } |
401 | 392 |
|
402 | | - // Invalid UTF-16 with unpaired surrogates - convert to well-formed in place |
403 | | - simdutf::to_well_formed_utf16(data, length, data); |
404 | | - |
405 | | - utf8_length = simdutf::utf8_length_from_utf16(data, length); |
| 393 | + // Common path: allocate the exact-size output, then re-acquire the flat |
| 394 | + // content and encode directly into the backing store. |
406 | 395 | std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore( |
407 | 396 | isolate, utf8_length, BackingStoreInitializationMode::kUninitialized); |
408 | 397 | CHECK(bs); |
409 | | - [[maybe_unused]] size_t written = simdutf::convert_utf16_to_utf8( |
410 | | - data, length, static_cast<char*>(bs->Data())); |
411 | | - DCHECK_EQ(written, utf8_length); |
| 398 | + char* out = static_cast<char*>(bs->Data()); |
| 399 | + { |
| 400 | + v8::String::ValueView view(isolate, source); |
| 401 | + if (is_one_byte) { |
| 402 | + auto data = reinterpret_cast<const char*>(view.data8()); |
| 403 | + if (is_ascii) { |
| 404 | + memcpy(out, data, length); |
| 405 | + } else { |
| 406 | + [[maybe_unused]] size_t written = |
| 407 | + simdutf::convert_latin1_to_utf8(data, length, out); |
| 408 | + DCHECK_EQ(written, utf8_length); |
| 409 | + } |
| 410 | + } else { |
| 411 | + auto data = reinterpret_cast<const char16_t*>(view.data16()); |
| 412 | + [[maybe_unused]] size_t written = |
| 413 | + simdutf::convert_utf16_to_utf8(data, length, out); |
| 414 | + DCHECK_EQ(written, utf8_length); |
| 415 | + } |
| 416 | + } |
412 | 417 | Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(bs)); |
413 | 418 | args.GetReturnValue().Set(Uint8Array::New(ab, 0, utf8_length)); |
414 | 419 | } |
|