Skip to content

Commit

Permalink
Avoid resolving substrings in bun:sqlite and Buffer.byteLength (#16092)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner authored Jan 1, 2025
1 parent 30008ed commit 1919165
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 24 deletions.
27 changes: 27 additions & 0 deletions bench/snippets/byteLength.mjs
Original file line number Diff line number Diff line change
@@ -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();
13 changes: 10 additions & 3 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down
2 changes: 1 addition & 1 deletion src/bun.js/bindings/BunObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/bun.js/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<double> byteLength(JSC::JSString* str, WebCore::BufferEncodingType encoding)
std::optional<double> byteLength(JSC::JSString* str, JSC::JSGlobalObject* lexicalGlobalObject, WebCore::BufferEncodingType encoding)
{
if (str->length() == 0)
return 0;
Expand All @@ -135,7 +135,7 @@ std::optional<double> 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;
}
Expand Down Expand Up @@ -167,7 +167,7 @@ std::optional<double> 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;
}
Expand Down Expand Up @@ -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()) {
Expand Down
6 changes: 6 additions & 0 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 7 additions & 7 deletions src/bun.js/bindings/sqlite/JSSQLStatement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char*>(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<const char*>(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));
}

Expand Down
31 changes: 22 additions & 9 deletions src/bun.js/webcore/streams.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 1919165

Please sign in to comment.