aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-17 01:28:36 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-17 01:28:36 -0700
commit3e68ead0fa837ef7912a47b3972bacde3379e319 (patch)
tree9e192e4eb080ab7938202352761528b007ce37b6
parent74f4c8bcab092d97628e996dc89c81f76114ba37 (diff)
downloadbun-3e68ead0fa837ef7912a47b3972bacde3379e319.tar.gz
bun-3e68ead0fa837ef7912a47b3972bacde3379e319.tar.zst
bun-3e68ead0fa837ef7912a47b3972bacde3379e319.zip
optimize blob.text()
-rw-r--r--src/javascript/jsc/webcore/response.zig268
1 files changed, 185 insertions, 83 deletions
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index 15b540b3a..12fcd83e1 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -690,17 +690,21 @@ pub const Fetch = struct {
if (options.get(ctx.ptr(), "body")) |body__| {
if (Blob.fromJS(ctx.ptr(), body__, true)) |new_blob| {
- body = MutableString{
- .list = std.ArrayListUnmanaged(u8){
- .items = bun.constStrToU8(new_blob.sharedView()),
- .capacity = new_blob.size,
- },
- .allocator = bun.default_allocator,
- };
- blob_store = new_blob.store;
+ if (new_blob.size > 0) {
+ body = MutableString{
+ .list = std.ArrayListUnmanaged(u8){
+ .items = bun.constStrToU8(new_blob.sharedView()),
+ .capacity = new_blob.size,
+ },
+ .allocator = bun.default_allocator,
+ };
+ blob_store = new_blob.store;
+ }
// transfer is unnecessary here because this is a new slice
//new_blob.transfer();
- } else |_| {}
+ } else |_| {
+ return JSPromise.rejectedPromiseValue(globalThis, ZigString.init("fetch() received invalid body").toErrorInstance(globalThis)).asRef();
+ }
}
}
} else if (Request.Class.loaded and first_arg.as(Request) != null) {
@@ -1395,6 +1399,10 @@ pub const Blob = struct {
content_type: string = "",
content_type_allocated: bool = false,
+ /// JavaScriptCore strings are either latin1 or UTF-16
+ /// When UTF-16, they're nearly always due to non-ascii characters
+ is_all_ascii: ?bool = null,
+
globalThis: *JSGlobalObject,
pub const Store = struct {
@@ -1403,6 +1411,7 @@ pub const Blob = struct {
ref_count: u32 = 0,
cap: u32 = 0,
allocator: std.mem.Allocator,
+ is_all_ascii: ?bool = null,
pub inline fn ref(this: *Store) void {
this.ref_count += 1;
@@ -1420,7 +1429,7 @@ pub const Blob = struct {
return store;
}
- pub fn external(_: ?*anyopaque, ptr: ?*anyopaque, _: usize) callconv(.C) void {
+ pub fn external(ptr: ?*anyopaque, _: ?*anyopaque, _: usize) callconv(.C) void {
if (ptr == null) return;
var this = bun.cast(*Store, ptr);
this.deref();
@@ -1620,6 +1629,8 @@ pub const Blob = struct {
const len = @intCast(u32, @maximum(relativeEnd - relativeStart, 0));
+ // This copies over the is_all_ascii flag
+ // which is okay because this will only be a <= slice
var blob = this.dupe();
blob.offset = @intCast(u32, relativeStart);
blob.size = len;
@@ -1689,7 +1700,11 @@ pub const Blob = struct {
blob = Blob.init(empty, getAllocator(ctx), ctx.ptr());
},
else => {
- blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0]), false) catch {
+ blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0]), false) catch |err| {
+ if (err == error.InvalidArguments) {
+ JSC.JSError(getAllocator(ctx), "new Blob() expects an Array", .{}, ctx, exception);
+ return null;
+ }
JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception);
return null;
};
@@ -1731,6 +1746,19 @@ pub const Blob = struct {
this.deinit();
}
+ pub fn initWithAllASCII(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject, is_all_ascii: bool) Blob {
+ var store = Blob.Store.init(bytes, allocator) catch unreachable;
+ store.is_all_ascii = is_all_ascii;
+ return Blob{
+ .size = @truncate(u32, bytes.len),
+ .store = store,
+ .allocator = null,
+ .content_type = "",
+ .globalThis = globalThis,
+ .is_all_ascii = is_all_ascii,
+ };
+ }
+
pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob {
return Blob{
.size = @truncate(u32, bytes.len),
@@ -1772,23 +1800,16 @@ pub const Blob = struct {
store.ref();
}
- return Blob{
- .store = this.store,
- .size = this.size,
- .content_type = this.content_type,
- .globalThis = this.globalThis,
- };
+ return this.*;
}
pub fn deinit(this: *Blob) void {
- var blob = this.*;
+ this.detach();
if (this.allocator) |alloc| {
this.allocator = null;
alloc.destroy(this);
}
-
- blob.detach();
}
pub fn sharedView(this: *const Blob) []const u8 {
@@ -1801,6 +1822,26 @@ pub const Blob = struct {
return this.store.?.slice()[this.offset..][0..this.size];
}
+ pub fn setASCIIFlagIfNeeded(this: *Blob, buf: []const u8) bool {
+ var store = this.store orelse return true;
+
+ if (this.is_all_ascii != null)
+ return this.is_all_ascii.?;
+
+ const sync_with_store = this.offset == 0 and this.size == store.len;
+ if (sync_with_store) {
+ this.is_all_ascii = store.is_all_ascii;
+ }
+
+ this.is_all_ascii = this.is_all_ascii orelse strings.isAllASCII(buf);
+
+ if (sync_with_store) {
+ store.is_all_ascii = this.is_all_ascii;
+ }
+
+ return this.is_all_ascii.?;
+ }
+
pub const Lifetime = enum {
clone,
transfer,
@@ -1816,18 +1857,30 @@ pub const Blob = struct {
// TODO: use the index to make this one pass instead of two passes
var buf = view_;
- if (!strings.isAllASCII(buf)) {
- var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{};
- if (lifetime == .transfer) {
- this.detach();
+ const is_all_ascii = this.setASCIIFlagIfNeeded(buf);
+
+ if (!is_all_ascii) {
+ if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) |external| {
+ if (lifetime == .transfer) {
+ this.detach();
+ }
+
+ return ZigString.toExternalU16(external.ptr, external.len, global);
}
- return ZigString.toExternalU16(external.ptr, external.len, global);
+ // if we get here, it means we were wrong
+ // but it generally shouldn't happen
+ this.is_all_ascii = true;
+
+ if (comptime Environment.allow_assert) {
+ unreachable;
+ }
}
switch (comptime lifetime) {
.clone => {
- return ZigString.init(buf).toValueGC(global);
+ this.store.?.ref();
+ return ZigString.init(buf).external(global, this.store.?, Store.external);
},
.transfer => {
var store = this.store.?;
@@ -1849,10 +1902,21 @@ pub const Blob = struct {
// TODO: use the index to make this one pass instead of two passes
var buf = view_;
- if (!strings.isAllASCII(buf)) {
- var external = (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) orelse &[_]u16{};
- defer bun.default_allocator.free(external);
- return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global);
+
+ const is_all_ascii = this.setASCIIFlagIfNeeded(buf);
+
+ if (!is_all_ascii) {
+ if (strings.toUTF16Alloc(bun.default_allocator, buf, false) catch null) |external| {
+ return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global);
+ }
+
+ // if we get here, it means we were wrong
+ // but it generally shouldn't happen
+ this.is_all_ascii = true;
+
+ if (comptime Environment.allow_assert) {
+ unreachable;
+ }
}
return ZigString.init(buf).toValue(
@@ -1941,61 +2005,85 @@ pub const Blob = struct {
return Blob{ .globalThis = global };
}
- // Fast path: one item, we don't need to join
+ var top_value = current;
+ var might_only_be_one_thing = false;
switch (current.jsTypeLoose()) {
- .Cell,
- .NumberObject,
- JSC.JSValue.JSType.String,
- JSC.JSValue.JSType.StringObject,
- JSC.JSValue.JSType.DerivedStringObject,
- => {
- var sliced = current.toSlice(global, bun.default_allocator);
-
- if (!sliced.allocated) {
- sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr);
- sliced.allocated = true;
+ .Array, .DerivedArray => {
+ var top_iter = JSC.JSArrayIterator.init(current, global);
+ might_only_be_one_thing = top_iter.len == 1;
+ if (top_iter.len == 0) {
+ return Blob{ .globalThis = global };
+ }
+ if (might_only_be_one_thing) {
+ top_value = top_iter.next().?;
}
-
- return Blob.init(bun.constStrToU8(sliced.slice()), bun.default_allocator, global);
},
-
- JSC.JSValue.JSType.ArrayBuffer,
- JSC.JSValue.JSType.Int8Array,
- JSC.JSValue.JSType.Uint8Array,
- JSC.JSValue.JSType.Uint8ClampedArray,
- JSC.JSValue.JSType.Int16Array,
- JSC.JSValue.JSType.Uint16Array,
- JSC.JSValue.JSType.Int32Array,
- JSC.JSValue.JSType.Uint32Array,
- JSC.JSValue.JSType.Float32Array,
- JSC.JSValue.JSType.Float64Array,
- JSC.JSValue.JSType.BigInt64Array,
- JSC.JSValue.JSType.BigUint64Array,
- JSC.JSValue.JSType.DataView,
- => {
- var buf = try bun.default_allocator.dupe(u8, current.asArrayBuffer(global).?.slice());
- return Blob.init(buf, bun.default_allocator, global);
+ else => {
+ might_only_be_one_thing = true;
+ if (comptime !move) {
+ return error.InvalidArguments;
+ }
},
+ }
- else => {
- if (JSC.C.JSObjectGetPrivate(current.asObjectRef())) |priv| {
- var data = JSC.JSPrivateDataPtr.from(priv);
- switch (data.tag()) {
- .Blob => {
- var blob: *Blob = data.as(Blob);
- if (comptime move) {
- var _blob = blob.*;
- blob.transfer();
- return _blob;
- } else {
- return blob.dupe();
- }
- },
+ if (might_only_be_one_thing or !move) {
- else => return Blob.initEmpty(global),
+ // Fast path: one item, we don't need to join
+ switch (top_value.jsTypeLoose()) {
+ .Cell,
+ .NumberObject,
+ JSC.JSValue.JSType.String,
+ JSC.JSValue.JSType.StringObject,
+ JSC.JSValue.JSType.DerivedStringObject,
+ => {
+ var sliced = top_value.toSlice(global, bun.default_allocator);
+ const is_all_ascii = !sliced.allocated;
+ if (!sliced.allocated) {
+ sliced.ptr = @ptrCast([*]const u8, (try bun.default_allocator.dupe(u8, sliced.slice())).ptr);
+ sliced.allocated = true;
}
- }
- },
+
+ return Blob.initWithAllASCII(bun.constStrToU8(sliced.slice()), bun.default_allocator, global, is_all_ascii);
+ },
+
+ JSC.JSValue.JSType.ArrayBuffer,
+ JSC.JSValue.JSType.Int8Array,
+ JSC.JSValue.JSType.Uint8Array,
+ JSC.JSValue.JSType.Uint8ClampedArray,
+ JSC.JSValue.JSType.Int16Array,
+ JSC.JSValue.JSType.Uint16Array,
+ JSC.JSValue.JSType.Int32Array,
+ JSC.JSValue.JSType.Uint32Array,
+ JSC.JSValue.JSType.Float32Array,
+ JSC.JSValue.JSType.Float64Array,
+ JSC.JSValue.JSType.BigInt64Array,
+ JSC.JSValue.JSType.BigUint64Array,
+ JSC.JSValue.JSType.DataView,
+ => {
+ var buf = try bun.default_allocator.dupe(u8, top_value.asArrayBuffer(global).?.slice());
+ return Blob.init(buf, bun.default_allocator, global);
+ },
+
+ else => {
+ if (JSC.C.JSObjectGetPrivate(top_value.asObjectRef())) |priv| {
+ var data = JSC.JSPrivateDataPtr.from(priv);
+ switch (data.tag()) {
+ .Blob => {
+ var blob: *Blob = data.as(Blob);
+ if (comptime move) {
+ var _blob = blob.*;
+ blob.transfer();
+ return _blob;
+ } else {
+ return blob.dupe();
+ }
+ },
+
+ else => return Blob.initEmpty(global),
+ }
+ }
+ },
+ }
}
var stack_allocator = std.heap.stackFallback(1024, bun.default_allocator);
@@ -2131,7 +2219,6 @@ pub const Blob = struct {
}
},
}
-
current = stack.popOrNull() orelse break;
}
@@ -2393,7 +2480,12 @@ pub const Body = struct {
}
body.value = .{
- .Blob = Blob.fromJS(ctx.ptr(), value, true) catch {
+ .Blob = Blob.fromJS(ctx.ptr(), value, true) catch |err| {
+ if (err == error.InvalidArguments) {
+ JSC.JSError(allocator, "Expected an Array", .{}, ctx, exception);
+ return body;
+ }
+
JSC.JSError(allocator, "Out of memory", .{}, ctx, exception);
return body;
},
@@ -2637,7 +2729,7 @@ pub const Request = struct {
ctx: js.JSContextRef,
_: js.JSObjectRef,
arguments: []const js.JSValueRef,
- _: js.ExceptionRef,
+ exception: js.ExceptionRef,
) js.JSObjectRef {
var request = Request{};
@@ -2655,8 +2747,18 @@ pub const Request = struct {
}
if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| {
- if (Blob.fromJS(ctx.ptr(), body_, true) catch null) |blob| {
- request.body = Body.Value{ .Blob = blob };
+ if (Blob.fromJS(ctx.ptr(), body_, true)) |blob| {
+ if (blob.size > 0) {
+ request.body = Body.Value{ .Blob = blob };
+ }
+ } else |err| {
+ if (err == error.InvalidArguments) {
+ JSC.JSError(getAllocator(ctx), "Expected an Array", .{}, ctx, exception);
+ return null;
+ }
+
+ JSC.JSError(getAllocator(ctx), "Invalid Body", .{}, ctx, exception);
+ return null;
}
}
},