diff options
author | 2023-02-05 07:07:35 -0800 | |
---|---|---|
committer | 2023-02-05 07:07:35 -0800 | |
commit | ca2e708be11e9481938965ee92acc38c01a7726a (patch) | |
tree | 5af19d1f7de4807d333334f804d286f230612164 | |
parent | 864913684558466f8517de9d474180a6bc19915c (diff) | |
download | bun-ca2e708be11e9481938965ee92acc38c01a7726a.tar.gz bun-ca2e708be11e9481938965ee92acc38c01a7726a.tar.zst bun-ca2e708be11e9481938965ee92acc38c01a7726a.zip |
speed it up a little more
-rw-r--r-- | src/bun.js/bindings/JSBuffer.cpp | 141 | ||||
-rw-r--r-- | src/bun.js/webcore/encoding.zig | 72 | ||||
-rw-r--r-- | src/string_immutable.zig | 64 | ||||
-rw-r--r-- | test/bun.js/text-decoder.test.js | 132 |
4 files changed, 290 insertions, 119 deletions
diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index a6c735af7..2ce07617a 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -1350,6 +1350,73 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGl return JSC::JSValue::encode(castedThis); } +static inline JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSUint8Array* castedThis, size_t offset, size_t length, WebCore::BufferEncodingType encoding) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (UNLIKELY(length == 0)) { + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); + } + + JSC::EncodedJSValue ret = 0; + + switch (encoding) { + case WebCore::BufferEncodingType::latin1: { + LChar* data = nullptr; + auto str = String::createUninitialized(length, data); + memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + case WebCore::BufferEncodingType::ucs2: + case WebCore::BufferEncodingType::utf16le: { + UChar* data = nullptr; + size_t u16length = length / 2; + if (u16length == 0) { + return JSC::JSValue::encode(JSC::jsEmptyString(vm)); + } else { + auto str = String::createUninitialized(u16length, data); + // always zero out the last byte of the string incase the buffer is not a multiple of 2 + data[u16length - 1] = 0; + memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + break; + } + + case WebCore::BufferEncodingType::ascii: { + // ascii: we always know the length + // so we might as well allocate upfront + LChar* data = nullptr; + auto str = String::createUninitialized(length, data); + Bun__encoding__writeLatin1(castedThis->typedVector() + offset, length, data, length, static_cast<uint8_t>(encoding)); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + case WebCore::BufferEncodingType::buffer: + case WebCore::BufferEncodingType::utf8: + case WebCore::BufferEncodingType::base64: + case WebCore::BufferEncodingType::base64url: + case WebCore::BufferEncodingType::hex: { + ret = Bun__encoding__toString(castedThis->typedVector() + offset, length, lexicalGlobalObject, static_cast<uint8_t>(encoding)); + break; + } + default: { + throwTypeError(lexicalGlobalObject, scope, "Unsupported encoding? This shouldn't happen"_s); + break; + } + } + + JSC::JSValue retValue = JSC::JSValue::decode(ret); + if (UNLIKELY(!retValue.isString())) { + scope.throwException(lexicalGlobalObject, retValue); + return JSC::JSValue::encode(jsUndefined()); + } + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(retValue)); +} + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis) { auto& vm = JSC::getVM(lexicalGlobalObject); @@ -1361,8 +1428,6 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (length == 0) return JSC::JSValue::encode(JSC::jsEmptyString(vm)); - auto scope = DECLARE_THROW_SCOPE(vm); - switch (callFrame->argumentCount()) { case 0: { break; @@ -1374,6 +1439,8 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (arg1.value().isString()) { std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, arg1.value()); if (!encoded) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); return JSC::JSValue::encode(jsUndefined()); } @@ -1389,6 +1456,8 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS JSC::JSValue arg2 = callFrame->uncheckedArgument(1); int32_t ioffset = arg2.toInt32(lexicalGlobalObject); if (ioffset < 0) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(lexicalGlobalObject, scope, "Offset must be a positive integer"_s); return JSC::JSValue::encode(jsUndefined()); } @@ -1406,61 +1475,33 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS length -= std::min(offset, length); - if (UNLIKELY(length == 0)) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); - } + return jsBufferToString(vm, lexicalGlobalObject, castedThis, offset, length, encoding); +} - JSC::EncodedJSValue ret = 0; +// DOMJIT makes it slower! TODO: investigate why +// JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsBufferPrototypeToStringWithoutTypeChecks, JSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::JSUint8Array* thisValue, JSC::JSString* encodingValue)); - switch (encoding) { - case WebCore::BufferEncodingType::latin1: { - LChar* data = nullptr; - auto str = String::createUninitialized(length, data); - memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); - ret = JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); - break; - } +// JSC_DEFINE_JIT_OPERATION(jsBufferPrototypeToStringWithoutTypeChecks, JSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSUint8Array* thisValue, JSString* encodingValue)) +// { +// VM& vm = JSC::getVM(lexicalGlobalObject); +// IGNORE_WARNINGS_BEGIN("frame-address") +// CallFrame* callFrame = DECLARE_CALL_FRAME(vm); +// IGNORE_WARNINGS_END +// JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); - case WebCore::BufferEncodingType::ucs2: - case WebCore::BufferEncodingType::utf16le: { - UChar* data = nullptr; - size_t u16length = length / 2; - if (u16length == 0) { - ret = JSC::JSValue::encode(JSC::jsEmptyString(vm)); - } else { - auto str = String::createUninitialized(u16length, data); - // always zero out the last byte of the string incase the buffer is not a multiple of 2 - data[u16length - 1] = 0; - memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); - ret = JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); - } +// std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, encodingValue); +// if (!encoded) { +// auto scope = DECLARE_THROW_SCOPE(vm); - break; - } +// throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); +// return {}; +// } - case WebCore::BufferEncodingType::buffer: - case WebCore::BufferEncodingType::utf8: - case WebCore::BufferEncodingType::ascii: - case WebCore::BufferEncodingType::base64: - case WebCore::BufferEncodingType::base64url: - case WebCore::BufferEncodingType::hex: { - ret = Bun__encoding__toString(castedThis->typedVector() + offset, length, lexicalGlobalObject, static_cast<uint8_t>(encoding)); - break; - } - default: { - throwTypeError(lexicalGlobalObject, scope, "Unsupported encoding? This shouldn't happen"_s); - break; - } - } +// auto encoding = encoded.value(); - JSC::JSValue retValue = JSC::JSValue::decode(ret); - if (UNLIKELY(!retValue.isString())) { - scope.throwException(lexicalGlobalObject, retValue); - return JSC::JSValue::encode(jsUndefined()); - } +// return JSValue::decode(jsBufferToString(vm, lexicalGlobalObject, thisValue, 0, thisValue->byteLength(), encoding)); +// } - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(retValue)); -} static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis) { auto& vm = JSC::getVM(lexicalGlobalObject); diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index 3172966ef..8a6e3224b 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -795,43 +795,20 @@ pub const Encoder = struct { switch (comptime encoding) { .ascii => { - var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); - var complete = to; - var remain = input; - - if (comptime bun.Environment.enableSIMD) { - const vector_size = 16; - // https://zig.godbolt.org/z/qezsY8T3W - var remain_in_u64 = remain[0 .. remain.len - (remain.len % vector_size)]; - var to_in_u64 = to[0 .. to.len - (to.len % vector_size)]; - var remain_as_u64 = std.mem.bytesAsSlice(u64, remain_in_u64); - var to_as_u64 = std.mem.bytesAsSlice(u64, to_in_u64); - const inner_vector_size = vector_size / 8; - const end_vector_len = @min(remain_as_u64.len, to_as_u64.len); - remain_as_u64 = remain_as_u64[0..end_vector_len]; - to_as_u64 = to_as_u64[0..end_vector_len]; - const end_ptr = remain_as_u64.ptr + remain_as_u64.len; - // using the pointer instead of the length is super important for the codegen - while (end_ptr != remain_as_u64.ptr) { - const buf = @as(@Vector(inner_vector_size, u64), remain_as_u64[0..inner_vector_size].*); - const mask = @splat(inner_vector_size, @as(u64, 0x7f7f7f7f7f7f7f7f)); - to_as_u64[0..inner_vector_size].* = buf & mask; - - remain_as_u64 = remain_as_u64[inner_vector_size..]; - to_as_u64 = to_as_u64[inner_vector_size..]; - } - remain = remain[remain_in_u64.len..]; - to = to[to_in_u64.len..]; + if (bun.simdutf.validate.ascii(input)) { + return ZigString.init(input).toValueGC(global); } - const end_ptr = to.ptr + to.len; - while (to.ptr != end_ptr) { - to[0] = @as(u8, @truncate(u7, remain[0])); - to = to[1..]; - remain = remain[1..]; + if (input.len < 512) { + var buf: [512]u8 = undefined; + var to = buf[0..input.len]; + strings.copyLatin1IntoASCII(to, input); + return ZigString.init(to).toValueGC(global); } - return ZigString.init(complete).toExternalValue(global); + var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); + strings.copyLatin1IntoASCII(to, input); + return ZigString.init(to).toExternalValue(global); }, .latin1 => { var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); @@ -884,7 +861,7 @@ pub const Encoder = struct { } } - pub fn writeU8(input: [*]const u8, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { + pub fn writeU8(input: [*]const u8, len: usize, to_ptr: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { if (len == 0 or to_len == 0) return 0; @@ -898,39 +875,42 @@ pub const Encoder = struct { switch (comptime encoding) { JSC.Node.Encoding.buffer => { const written = @min(len, to_len); - @memcpy(to, input, written); + @memcpy(to_ptr, input, written); return @intCast(i64, written); }, .latin1, .ascii => { const written = @min(len, to_len); - @memcpy(to, input, written); - // Hoping this gets auto vectorized - for (to[0..written]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); + var to = to_ptr[0..written]; + var remain = input[0..written]; + + if (bun.simdutf.validate.ascii(remain)) { + @memcpy(to.ptr, remain.ptr, written); + } else { + strings.copyLatin1IntoASCII(to, remain); } return @intCast(i64, written); }, .utf8 => { // need to encode - return @intCast(i64, strings.copyLatin1IntoUTF8(to[0..to_len], []const u8, input[0..len]).written); + return @intCast(i64, strings.copyLatin1IntoUTF8(to_ptr[0..to_len], []const u8, input[0..len]).written); }, // encode latin1 into UTF16 JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { if (to_len < 2) return 0; - if (std.mem.isAligned(@ptrToInt(to), @alignOf([*]u16))) { + if (std.mem.isAligned(@ptrToInt(to_ptr), @alignOf([*]u16))) { var buf = input[0..len]; - var output = @ptrCast([*]u16, @alignCast(@alignOf(u16), to))[0 .. to_len / 2]; + var output = @ptrCast([*]u16, @alignCast(@alignOf(u16), to_ptr))[0 .. to_len / 2]; var written = strings.copyLatin1IntoUTF16([]u16, output, []const u8, buf).written; return written * 2; } else { var buf = input[0..len]; - var output = @ptrCast([*]align(1) u16, to)[0 .. to_len / 2]; + var output = @ptrCast([*]align(1) u16, to_ptr)[0 .. to_len / 2]; var written = strings.copyLatin1IntoUTF16([]align(1) u16, output, []const u8, buf).written; return written * 2; @@ -938,7 +918,7 @@ pub const Encoder = struct { }, JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to[0..to_len], u8, input[0..len])); + return @intCast(i64, strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len])); }, JSC.Node.Encoding.base64url => { @@ -952,12 +932,12 @@ pub const Encoder = struct { slice = slice[0 .. slice.len - 1]; } - const wrote = bun.base64.decodeURLSafe(to[0..to_len], slice).written; + const wrote = bun.base64.decodeURLSafe(to_ptr[0..to_len], slice).written; return @intCast(i64, wrote); }, JSC.Node.Encoding.base64 => { - return @intCast(i64, bun.base64.decode(to[0..to_len], input[0..len]).written); + return @intCast(i64, bun.base64.decode(to_ptr[0..to_len], input[0..len]).written); }, // else => return 0, } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 68c22b773..63dd70090 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -1012,6 +1012,53 @@ pub inline fn copyU16IntoU8(output_: []u8, comptime InputType: type, input_: Inp const strings = @This(); +pub fn copyLatin1IntoASCII(dest: []u8, src: []const u8) void { + var remain = src; + var to = dest; + + const non_ascii_offset = strings.firstNonASCII(remain) orelse @truncate(u32, remain.len); + if (non_ascii_offset > 0) { + @memcpy(to.ptr, remain.ptr, non_ascii_offset); + remain = remain[non_ascii_offset..]; + to = to[non_ascii_offset..]; + + // ascii fast path + if (remain.len == 0) { + return; + } + } + + if (to.len >= 16 and bun.Environment.enableSIMD) { + const vector_size = 16; + // https://zig.godbolt.org/z/qezsY8T3W + var remain_in_u64 = remain[0 .. remain.len - (remain.len % vector_size)]; + var to_in_u64 = to[0 .. to.len - (to.len % vector_size)]; + var remain_as_u64 = std.mem.bytesAsSlice(u64, remain_in_u64); + var to_as_u64 = std.mem.bytesAsSlice(u64, to_in_u64); + const end_vector_len = @min(remain_as_u64.len, to_as_u64.len); + remain_as_u64 = remain_as_u64[0..end_vector_len]; + to_as_u64 = to_as_u64[0..end_vector_len]; + const end_ptr = remain_as_u64.ptr + remain_as_u64.len; + // using the pointer instead of the length is super important for the codegen + while (end_ptr != remain_as_u64.ptr) { + const buf = remain_as_u64[0]; + // this gets auto-vectorized + const mask = @as(u64, 0x7f7f7f7f7f7f7f7f); + to_as_u64[0] = buf & mask; + + remain_as_u64 = remain_as_u64[1..]; + to_as_u64 = to_as_u64[1..]; + } + remain = remain[remain_in_u64.len..]; + to = to[to_in_u64.len..]; + } + + for (to) |*to_byte| { + to_byte.* = @as(u8, @truncate(u7, remain[0])); + remain = remain[1..]; + } +} + /// Convert a UTF-8 string to a UTF-16 string IF there are any non-ascii characters /// If there are no non-ascii characters, this returns null /// This is intended to be used for strings that go to JavaScript @@ -1023,28 +1070,23 @@ pub fn toUTF16Alloc(allocator: std.mem.Allocator, bytes: []const u8, comptime fa if (bytes.len == 0) return &[_]u16{}; use_simdutf: { - const validated = bun.simdutf.validate.with_errors.ascii(bytes); - if (validated.status == .success) + if (bun.simdutf.validate.ascii(bytes)) return null; - const offset = @truncate(u32, validated.count); - - const trimmed = bun.simdutf.trim.utf8(bytes[offset..]); + const trimmed = bun.simdutf.trim.utf8(bytes); if (trimmed.len == 0) break :use_simdutf; const out_length = bun.simdutf.length.utf16.from.utf8.le(trimmed); - if (out_length != trimmed.len) + if (out_length == 0) break :use_simdutf; - var out = try allocator.alloc(u16, out_length + offset); + var out = try allocator.alloc(u16, out_length); log("toUTF16 {d} UTF8 -> {d} UTF16", .{ bytes.len, out_length }); - if (offset > 0) - strings.copyU8IntoU16(out[0..offset], bytes[0..offset]); - const result = bun.simdutf.convert.utf8.to.utf16.with_errors.le(trimmed, out[offset..]); + const result = bun.simdutf.convert.utf8.to.utf16.with_errors.le(trimmed, out); switch (result.status) { .success => { return out; @@ -1055,7 +1097,7 @@ pub fn toUTF16Alloc(allocator: std.mem.Allocator, bytes: []const u8, comptime fa return error.InvalidByteSequence; } - first_non_ascii = @truncate(u32, result.count) + offset; + first_non_ascii = @truncate(u32, result.count); output_ = std.ArrayList(u16){ .items = out[0..first_non_ascii.?], .capacity = out.len, diff --git a/test/bun.js/text-decoder.test.js b/test/bun.js/text-decoder.test.js index be3f8421b..da0497464 100644 --- a/test/bun.js/text-decoder.test.js +++ b/test/bun.js/text-decoder.test.js @@ -42,7 +42,118 @@ describe("TextDecoder", () => { gcTrace(true); expect(decoder.decode(new Uint8Array([0x41, 0x42, 0x43]))).toBe("ABC"); gcTrace(true); - const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + + // hit the SIMD code path + const result = [ + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + ]; gcTrace(true); expect(decoder.decode(Uint8Array.from(result))).toBe(String.fromCharCode(...result)); gcTrace(true); @@ -51,19 +162,16 @@ describe("TextDecoder", () => { it("should decode unicode text", () => { const decoder = new TextDecoder(); gcTrace(true); - var text = `❤️ Red Heart`; - - const bytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116]; - const decoded = decoder.decode(Uint8Array.from(bytes)); - expect(decoder.encoding).toBe("utf-8"); + const inputBytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116]; + for (var repeat = 1; repeat < 100; repeat++) { + var text = `❤️ Red Heart`.repeat(repeat); - gcTrace(true); - - for (let i = 0; i < text.length; i++) { - expect(decoded.charCodeAt(i)).toBe(text.charCodeAt(i)); + var bytes = Array.from({ length: repeat }, () => inputBytes).flat(); + var decoded = decoder.decode(Uint8Array.from(bytes)); + expect(decoder.encoding).toBe("utf-8"); + expect(decoded).toBe(text); + gcTrace(true); } - expect(decoded).toHaveLength(text.length); - gcTrace(true); }); describe("typedArrays", () => { |