@@ -145,12 +145,49 @@ class ExternString: public ResourceType { |
145 | 145 | size_t length_; |
146 | 146 | }; |
147 | 147 |
|
| 148 | +typedef ExternString<String::ExternalOneByteStringResource, char> |
| 149 | + ExternOneByteString; |
| 150 | +typedef ExternString<String::ExternalStringResource, uint16_t> |
| 151 | + ExternTwoByteString; |
| 152 | + |
| 153 | +template <typename EncodeFn> |
| 154 | +static MaybeLocal<Value> EncodeOneByteString(Isolate* isolate, |
| 155 | + size_t length, |
| 156 | + EncodeFn encode) { |
| 157 | + // 512B stack threshold: covers common small outputs (hex SHA-256/512, UUIDs). |
| 158 | + // Larger thresholds were benchmarked |
| 159 | + MaybeStackBuffer<char, 512> buf(length); |
| 160 | + encode(buf.out()); |
| 161 | + // Copy stack-backed data, but release heap-backed storage to V8. |
| 162 | + if (buf.IsAllocated()) { |
| 163 | + char* data = buf.out(); |
| 164 | + buf.Release(); |
| 165 | + return ExternOneByteString::New(isolate, data, length); |
| 166 | + } |
| 167 | + return String::NewFromOneByte(isolate, |
| 168 | + reinterpret_cast<const uint8_t*>(buf.out()), |
| 169 | + v8::NewStringType::kNormal, |
| 170 | + static_cast<int>(length)); |
| 171 | +} |
148 | 172 |
|
149 | | -typedef ExternString<String::ExternalOneByteStringResource, |
150 | | - char> ExternOneByteString; |
151 | | -typedef ExternString<String::ExternalStringResource, |
152 | | - uint16_t> ExternTwoByteString; |
153 | | - |
| 173 | +template <typename EncodeFn> |
| 174 | +static MaybeLocal<Value> EncodeTwoByteString(Isolate* isolate, |
| 175 | + size_t char_length, |
| 176 | + EncodeFn encode) { |
| 177 | + // 256 uint16_t = 512 bytes on the stack, matching the one-byte |
| 178 | + MaybeStackBuffer<uint16_t, 256> buf(char_length); |
| 179 | + encode(buf.out()); |
| 180 | + // Copy stack-backed data, but release heap-backed storage to V8. |
| 181 | + if (buf.IsAllocated()) { |
| 182 | + uint16_t* data = buf.out(); |
| 183 | + buf.Release(); |
| 184 | + return ExternTwoByteString::New(isolate, data, char_length); |
| 185 | + } |
| 186 | + return String::NewFromTwoByte(isolate, |
| 187 | + buf.out(), |
| 188 | + v8::NewStringType::kNormal, |
| 189 | + static_cast<int>(char_length)); |
| 190 | +} |
154 | 191 |
|
155 | 192 | template <> |
156 | 193 | MaybeLocal<Value> ExternOneByteString::NewExternal( |
@@ -527,13 +564,10 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, |
527 | 564 | buflen = keep_buflen_in_range(buflen); |
528 | 565 | if (simdutf::validate_ascii_with_errors(buf, buflen).error) { |
529 | 566 | // The input contains non-ASCII bytes. |
530 | | - char* out = node::UncheckedMalloc(buflen); |
531 | | - if (out == nullptr) { |
532 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
533 | | - return MaybeLocal<Value>(); |
534 | | - } |
535 | | - nbytes::ForceAscii(buf, out, buflen); |
536 | | - return ExternOneByteString::New(isolate, out, buflen); |
| 567 | + |
| 568 | + return EncodeOneByteString(isolate, buflen, [buf, buflen](char* dst) { |
| 569 | + nbytes::ForceAscii(buf, dst, buflen); |
| 570 | + }); |
537 | 571 | } else { |
538 | 572 | return ExternOneByteString::NewFromCopy(isolate, buf, buflen); |
539 | 573 | } |
@@ -557,14 +591,12 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, |
557 | 591 | isolate->ThrowException(ERR_STRING_TOO_LONG(isolate)); |
558 | 592 | return MaybeLocal<Value>(); |
559 | 593 | } |
560 | | - uint16_t* dst = node::UncheckedMalloc<uint16_t>(u16size); |
561 | | - if (u16size != 0 && dst == nullptr) { |
562 | | - THROW_ERR_MEMORY_ALLOCATION_FAILED(isolate); |
563 | | - return MaybeLocal<Value>(); |
564 | | - } |
565 | | - size_t utf16len = simdutf::convert_valid_utf8_to_utf16( |
566 | | - buf, buflen, reinterpret_cast<char16_t*>(dst)); |
567 | | - return ExternTwoByteString::New(isolate, dst, utf16len); |
| 594 | + return EncodeTwoByteString( |
| 595 | + isolate, u16size, [buf, buflen, u16size](uint16_t* dst) { |
| 596 | + size_t written = simdutf::convert_valid_utf8_to_utf16( |
| 597 | + buf, buflen, reinterpret_cast<char16_t*>(dst)); |
| 598 | + CHECK_EQ(written, u16size); |
| 599 | + }); |
568 | 600 | } |
569 | 601 |
|
570 | 602 | val = |
@@ -583,77 +615,52 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, |
583 | 615 | case BASE64: { |
584 | 616 | buflen = keep_buflen_in_range(buflen); |
585 | 617 | size_t dlen = simdutf::base64_length_from_binary(buflen); |
586 | | - char* dst = node::UncheckedMalloc(dlen); |
587 | | - if (dst == nullptr) { |
588 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
589 | | - return MaybeLocal<Value>(); |
590 | | - } |
591 | | - |
592 | | - size_t written = simdutf::binary_to_base64(buf, buflen, dst); |
593 | | - CHECK_EQ(written, dlen); |
594 | | - |
595 | | - return ExternOneByteString::New(isolate, dst, dlen); |
| 618 | + return EncodeOneByteString(isolate, dlen, [buf, buflen, dlen](char* dst) { |
| 619 | + size_t written = simdutf::binary_to_base64(buf, buflen, dst); |
| 620 | + CHECK_EQ(written, dlen); |
| 621 | + }); |
596 | 622 | } |
597 | 623 |
|
598 | 624 | case BASE64URL: { |
599 | 625 | buflen = keep_buflen_in_range(buflen); |
600 | 626 | size_t dlen = |
601 | 627 | simdutf::base64_length_from_binary(buflen, simdutf::base64_url); |
602 | | - char* dst = node::UncheckedMalloc(dlen); |
603 | | - if (dst == nullptr) { |
604 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
605 | | - return MaybeLocal<Value>(); |
606 | | - } |
607 | | - |
608 | | - size_t written = |
609 | | - simdutf::binary_to_base64(buf, buflen, dst, simdutf::base64_url); |
610 | | - CHECK_EQ(written, dlen); |
611 | | - |
612 | | - return ExternOneByteString::New(isolate, dst, dlen); |
| 628 | + return EncodeOneByteString(isolate, dlen, [buf, buflen, dlen](char* dst) { |
| 629 | + size_t written = |
| 630 | + simdutf::binary_to_base64(buf, buflen, dst, simdutf::base64_url); |
| 631 | + CHECK_EQ(written, dlen); |
| 632 | + }); |
613 | 633 | } |
614 | 634 |
|
615 | 635 | case HEX: { |
616 | 636 | buflen = keep_buflen_in_range(buflen); |
617 | 637 | size_t dlen = buflen * 2; |
618 | | - char* dst = node::UncheckedMalloc(dlen); |
619 | | - if (dst == nullptr) { |
620 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
621 | | - return MaybeLocal<Value>(); |
622 | | - } |
623 | | - size_t written = nbytes::HexEncode(buf, buflen, dst, dlen); |
624 | | - CHECK_EQ(written, dlen); |
625 | | - |
626 | | - return ExternOneByteString::New(isolate, dst, dlen); |
| 638 | + return EncodeOneByteString(isolate, dlen, [buf, buflen, dlen](char* dst) { |
| 639 | + size_t written = nbytes::HexEncode(buf, buflen, dst, dlen); |
| 640 | + CHECK_EQ(written, dlen); |
| 641 | + }); |
627 | 642 | } |
628 | 643 |
|
629 | 644 | case UCS2: { |
630 | 645 | buflen = keep_buflen_in_range(buflen); |
631 | 646 | size_t str_len = buflen / 2; |
632 | 647 | if constexpr (IsBigEndian()) { |
633 | | - uint16_t* dst = node::UncheckedMalloc<uint16_t>(str_len); |
634 | | - if (str_len != 0 && dst == nullptr) { |
635 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
636 | | - return MaybeLocal<Value>(); |
637 | | - } |
638 | | - for (size_t i = 0, k = 0; k < str_len; i += 2, k += 1) { |
639 | | - // The input is in *little endian*, because that's what Node.js |
640 | | - // expects, so the high byte comes after the low byte. |
641 | | - const uint8_t hi = static_cast<uint8_t>(buf[i + 1]); |
642 | | - const uint8_t lo = static_cast<uint8_t>(buf[i + 0]); |
643 | | - dst[k] = static_cast<uint16_t>(hi) << 8 | lo; |
644 | | - } |
645 | | - return ExternTwoByteString::New(isolate, dst, str_len); |
| 648 | + return EncodeTwoByteString( |
| 649 | + isolate, str_len, [buf, str_len](uint16_t* dst) { |
| 650 | + for (size_t i = 0, k = 0; k < str_len; i += 2, k += 1) { |
| 651 | + // The input is in *little endian*, because that's what Node.js |
| 652 | + // expects, so the high byte comes after the low byte. |
| 653 | + const uint8_t hi = static_cast<uint8_t>(buf[i + 1]); |
| 654 | + const uint8_t lo = static_cast<uint8_t>(buf[i + 0]); |
| 655 | + dst[k] = static_cast<uint16_t>(hi) << 8 | lo; |
| 656 | + } |
| 657 | + }); |
646 | 658 | } |
647 | 659 | if (reinterpret_cast<uintptr_t>(buf) % 2 != 0) { |
648 | | - // Unaligned data still means we can't directly pass it to V8. |
649 | | - char* dst = node::UncheckedMalloc(buflen); |
650 | | - if (dst == nullptr) { |
651 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
652 | | - return MaybeLocal<Value>(); |
653 | | - } |
654 | | - memcpy(dst, buf, buflen); |
655 | | - return ExternTwoByteString::New( |
656 | | - isolate, reinterpret_cast<uint16_t*>(dst), str_len); |
| 660 | + return EncodeTwoByteString( |
| 661 | + isolate, str_len, [buf, buflen](uint16_t* dst) { |
| 662 | + memcpy(dst, buf, buflen); |
| 663 | + }); |
657 | 664 | } |
658 | 665 | return ExternTwoByteString::NewFromCopy( |
659 | 666 | isolate, reinterpret_cast<const uint16_t*>(buf), str_len); |
@@ -675,15 +682,11 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, |
675 | 682 | // https://nodejs.org/api/buffer.html regarding Node's "ucs2" |
676 | 683 | // encoding specification |
677 | 684 | if constexpr (IsBigEndian()) { |
678 | | - uint16_t* dst = node::UncheckedMalloc<uint16_t>(buflen); |
679 | | - if (dst == nullptr) { |
680 | | - isolate->ThrowException(node::ERR_MEMORY_ALLOCATION_FAILED(isolate)); |
681 | | - return MaybeLocal<Value>(); |
682 | | - } |
683 | | - size_t nbytes = buflen * sizeof(uint16_t); |
684 | | - memcpy(dst, buf, nbytes); |
685 | | - CHECK(nbytes::SwapBytes16(reinterpret_cast<char*>(dst), nbytes)); |
686 | | - return ExternTwoByteString::New(isolate, dst, buflen); |
| 685 | + return EncodeTwoByteString(isolate, buflen, [buf, buflen](uint16_t* dst) { |
| 686 | + size_t nbytes = buflen * sizeof(uint16_t); |
| 687 | + memcpy(dst, buf, nbytes); |
| 688 | + CHECK(nbytes::SwapBytes16(reinterpret_cast<char*>(dst), nbytes)); |
| 689 | + }); |
687 | 690 | } else { |
688 | 691 | return ExternTwoByteString::NewFromCopy(isolate, buf, buflen); |
689 | 692 | } |
|