diff --git a/bench/snippets/byteLength.mjs b/bench/snippets/byteLength.mjs new file mode 100644 index 00000000000000..810bf487fd572b --- /dev/null +++ b/bench/snippets/byteLength.mjs @@ -0,0 +1,27 @@ +import { Buffer } from "node:buffer"; +import { bench, run } from "../runner.mjs"; + +const variations = [ + ["latin1", "hello world"], + ["utf16", "hello emoji 🤔"], +]; + +for (const [label, string] of variations) { + const big = Buffer.alloc(1000000, string).toString(); + const small = Buffer.from(string).toString(); + const substring = big.slice(0, big.length - 2); + + bench(`${substring.length}`, () => { + return Buffer.byteLength(substring, "utf8"); + }); + + bench(`${small.length}`, () => { + return Buffer.byteLength(small); + }); + + bench(`${big.length}`, () => { + return Buffer.byteLength(big); + }); +} + +await run(); diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 1335e7b7dcbe49..b540da63177c18 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -5239,10 +5239,17 @@ pub const ServerWebSocket = struct { return globalThis.throw("sendText expects a string", .{}); } - var string_slice = message_value.toSlice(globalThis, bun.default_allocator); - defer string_slice.deinit(); + var js_string = message_value.toString(globalThis); + if (globalThis.hasException()) { + return .zero; + } + const view = js_string.view(globalThis); + const slice = view.toSlice(bun.default_allocator); + defer slice.deinit(); - const buffer = string_slice.slice(); + defer js_string.ensureStillAlive(); + + const buffer = slice.slice(); switch (this.websocket().send(buffer, .text, compress, true)) { .backpressure => { log("sendText() backpressure ({d} bytes string)", .{buffer.len}); diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index 502e8cf796ca7d..5e1062a2bbff41 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -428,7 +428,7 @@ JSC_DEFINE_HOST_FUNCTION(functionBunEscapeHTML, (JSC::JSGlobalObject * lexicalGl if (string->length() == 0) RELEASE_AND_RETURN(scope, JSValue::encode(string)); - auto resolvedString = string->value(lexicalGlobalObject); + auto resolvedString = string->view(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); JSC::EncodedJSValue encodedInput = JSValue::encode(string); diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index ef55090712803b..0476e797bba369 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -113,7 +113,7 @@ namespace Bun { // Use a JSString* here to avoid unnecessarily joining the rope string. // If we're only getting the length property, it won't join the rope string. -std::optional byteLength(JSC::JSString* str, WebCore::BufferEncodingType encoding) +std::optional byteLength(JSC::JSString* str, JSC::JSGlobalObject* lexicalGlobalObject, WebCore::BufferEncodingType encoding) { if (str->length() == 0) return 0; @@ -135,7 +135,7 @@ std::optional byteLength(JSC::JSString* str, WebCore::BufferEncodingType case WebCore::BufferEncodingType::base64: case WebCore::BufferEncodingType::base64url: { int64_t length = str->length(); - const auto& view = str->tryGetValue(true); + const auto view = str->view(lexicalGlobalObject); if (UNLIKELY(view->isNull())) { return std::nullopt; } @@ -167,7 +167,7 @@ std::optional byteLength(JSC::JSString* str, WebCore::BufferEncodingType } case WebCore::BufferEncodingType::utf8: { - const auto& view = str->tryGetValue(true); + const auto view = str->view(lexicalGlobalObject); if (UNLIKELY(view->isNull())) { return std::nullopt; } @@ -657,7 +657,7 @@ static inline JSC::EncodedJSValue jsBufferByteLengthFromStringAndEncoding(JSC::J return {}; } - if (auto length = Bun::byteLength(str, encoding)) { + if (auto length = Bun::byteLength(str, lexicalGlobalObject, encoding)) { return JSValue::encode(jsNumber(*length)); } if (!scope.exception()) { diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 226ed64f8f6d4e..909e8dc1e20a5a 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1915,12 +1915,18 @@ pub const JSString = extern struct { return shim.cppFn("toZigString", .{ this, global, zig_str }); } + pub fn ensureStillAlive(this: *JSString) void { + std.mem.doNotOptimizeAway(this); + } + pub fn getZigString(this: *JSString, global: *JSGlobalObject) JSC.ZigString { var out = JSC.ZigString.init(""); this.toZigString(global, &out); return out; } + pub const view = getZigString; + // doesn't always allocate pub fn toSlice( this: *JSString, diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index be8ff4b90bc12d..e27d4159feea6e 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -785,18 +785,18 @@ static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3 return false; } - String roped = str->tryGetValue(lexicalGlobalObject); - if (UNLIKELY(!roped)) { + const auto roped = str->view(lexicalGlobalObject); + if (UNLIKELY(roped->isNull())) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Out of memory :("_s)); return false; } - if (roped.is8Bit() && roped.containsOnlyASCII()) { - CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast(roped.span8().data()), roped.length(), transientOrStatic)); - } else if (!roped.is8Bit()) { - CHECK_BIND(sqlite3_bind_text16(stmt, i, roped.span16().data(), roped.length() * 2, transientOrStatic)); + if (roped->is8Bit() && roped->containsOnlyASCII()) { + CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast(roped->span8().data()), roped->length(), transientOrStatic)); + } else if (!roped->is8Bit()) { + CHECK_BIND(sqlite3_bind_text16(stmt, i, roped->span16().data(), roped->length() * 2, transientOrStatic)); } else { - auto utf8 = roped.utf8(); + auto utf8 = roped->utf8(); CHECK_BIND(sqlite3_bind_text(stmt, i, utf8.data(), utf8.length(), SQLITE_TRANSIENT)); } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 8c7f348dfbf4b0..0d437358eab230 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -1790,16 +1790,23 @@ pub fn NewJSSink(comptime SinkType: type, comptime name_: []const u8) type { return globalThis.throwValue(JSC.toTypeError(.ERR_INVALID_ARG_TYPE, "write() expects a string, ArrayBufferView, or ArrayBuffer", .{}, globalThis)); } - const str = arg.getZigString(globalThis); - if (str.len == 0) { + const str = arg.toString(globalThis); + if (globalThis.hasException()) { + return .zero; + } + + const view = str.view(globalThis); + + if (view.isEmpty()) { return JSC.JSValue.jsNumber(0); } - if (str.is16Bit()) { - return this.sink.writeUTF16(.{ .temporary = bun.ByteList.initConst(std.mem.sliceAsBytes(str.utf16SliceAligned())) }).toJS(globalThis); + defer str.ensureStillAlive(); + if (view.is16Bit()) { + return this.sink.writeUTF16(.{ .temporary = bun.ByteList.initConst(std.mem.sliceAsBytes(view.utf16SliceAligned())) }).toJS(globalThis); } - return this.sink.writeLatin1(.{ .temporary = bun.ByteList.initConst(str.slice()) }).toJS(globalThis); + return this.sink.writeLatin1(.{ .temporary = bun.ByteList.initConst(view.slice()) }).toJS(globalThis); } pub fn writeUTF8(globalThis: *JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue { @@ -1827,16 +1834,22 @@ pub fn NewJSSink(comptime SinkType: type, comptime name_: []const u8) type { const arg = args[0]; - const str = arg.getZigString(globalThis); - if (str.len == 0) { + const str = arg.toString(globalThis); + if (globalThis.hasException()) { + return .zero; + } + + const view = str.view(globalThis); + if (view.isEmpty()) { return JSC.JSValue.jsNumber(0); } + defer str.ensureStillAlive(); if (str.is16Bit()) { - return this.sink.writeUTF16(.{ .temporary = str.utf16SliceAligned() }).toJS(globalThis); + return this.sink.writeUTF16(.{ .temporary = view.utf16SliceAligned() }).toJS(globalThis); } - return this.sink.writeLatin1(.{ .temporary = str.slice() }).toJS(globalThis); + return this.sink.writeLatin1(.{ .temporary = view.slice() }).toJS(globalThis); } pub fn close(globalThis: *JSGlobalObject, sink_ptr: ?*anyopaque) callconv(.C) JSValue {