aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-14 01:31:56 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-03-14 01:31:56 -0700
commit4ab4ae77ac5ab51e0cc028c02d80613c817b8825 (patch)
treed97614937c01afbd586f5d1bb5f49f842cfb91cc /src
parent4638871050f8658f1a0b7b0bab40cedf90cc932f (diff)
downloadbun-4ab4ae77ac5ab51e0cc028c02d80613c817b8825.tar.gz
bun-4ab4ae77ac5ab51e0cc028c02d80613c817b8825.tar.zst
bun-4ab4ae77ac5ab51e0cc028c02d80613c817b8825.zip
Reference-counting for `Blob` and `Headers`
Diffstat (limited to 'src')
-rw-r--r--src/global.zig1
-rw-r--r--src/javascript/jsc/api/html_rewriter.zig29
-rw-r--r--src/javascript/jsc/base.zig50
-rw-r--r--src/javascript/jsc/bindings/ZigSourceProvider.cpp3
-rw-r--r--src/javascript/jsc/bindings/bindings.cpp27
-rw-r--r--src/javascript/jsc/bindings/bindings.zig27
-rw-r--r--src/javascript/jsc/bindings/headers-cpp.h2
-rw-r--r--src/javascript/jsc/bindings/headers.h3
-rw-r--r--src/javascript/jsc/bindings/headers.zig1
-rw-r--r--src/javascript/jsc/javascript.zig214
-rw-r--r--src/javascript/jsc/webcore.zig118
-rw-r--r--src/javascript/jsc/webcore/response.zig515
-rw-r--r--src/ref_count.zig82
-rw-r--r--src/string_immutable.zig25
14 files changed, 742 insertions, 355 deletions
diff --git a/src/global.zig b/src/global.zig
index e2e99bab8..1e16db216 100644
--- a/src/global.zig
+++ b/src/global.zig
@@ -83,6 +83,7 @@ pub const PathString = StringTypes.PathString;
pub const HashedString = StringTypes.HashedString;
pub const strings = @import("string_immutable.zig");
pub const MutableString = @import("string_mutable.zig").MutableString;
+pub const RefCount = @import("./ref_count.zig").RefCount;
pub inline fn constStrToU8(s: []const u8) []u8 {
return @intToPtr([*]u8, @ptrToInt(s.ptr))[0..s.len];
diff --git a/src/javascript/jsc/api/html_rewriter.zig b/src/javascript/jsc/api/html_rewriter.zig
index 255ad14ea..e8728a46d 100644
--- a/src/javascript/jsc/api/html_rewriter.zig
+++ b/src/javascript/jsc/api/html_rewriter.zig
@@ -232,7 +232,6 @@ pub const HTMLRewriter = struct {
pub fn init(context: LOLHTMLContext, global: *JSGlobalObject, original: *Response, builder: *LOLHTML.HTMLRewriter.Builder) JSValue {
var result = bun.default_allocator.create(Response) catch unreachable;
-
var sink = bun.default_allocator.create(BufferOutputSink) catch unreachable;
sink.* = BufferOutputSink{
.global = global,
@@ -282,16 +281,23 @@ pub const HTMLRewriter = struct {
},
},
};
+ {
+ var input = original.body.value.use();
- sink.rewriter.write(original.body.slice()) catch {
- sink.deinit();
- bun.default_allocator.destroy(result);
+ defer input.detach();
+ sink.rewriter.write(input.sharedView()) catch {
+ sink.deinit();
+ bun.default_allocator.destroy(result);
- return throwLOLHTMLError(global);
- };
+ return throwLOLHTMLError(global);
+ };
+ }
// Hold off on cloning until we're actually done.
- result.body.init = original.body.init.clone(bun.default_allocator);
+ result.body.init.headers = original.body.init.headers;
+ result.body.init.method = original.body.init.method;
+ result.body.init.status_code = original.body.init.status_code;
+
result.url = bun.default_allocator.dupe(u8, original.url) catch unreachable;
result.status_text = bun.default_allocator.dupe(u8, original.status_text) catch unreachable;
@@ -474,7 +480,6 @@ fn HandlerCallback(
var zig_element = bun.default_allocator.create(ZigType) catch unreachable;
@field(zig_element, field_name) = value;
// At the end of this scope, the value is no longer valid
-
var args = [1]JSC.C.JSObjectRef{
ZigType.Class.make(this.global.ref(), zig_element),
};
@@ -497,14 +502,10 @@ fn HandlerCallback(
var promise = promise_ orelse JSC.JSInternalPromise.resolvedPromise(this.global, result);
promise_ = promise;
+ JavaScript.VirtualMachine.vm.event_loop.waitForPromise(promise);
switch (promise.status(this.global.vm())) {
- JSC.JSPromise.Status.Pending => {
- while (promise.status(this.global.vm()) == .Pending) {
- JavaScript.VirtualMachine.vm.tick();
- }
- result = promise.result(this.global.vm());
- },
+ JSC.JSPromise.Status.Pending => unreachable,
JSC.JSPromise.Status.Rejected => {
JavaScript.VirtualMachine.vm.defaultErrorHandler(promise.result(this.global.vm()), null);
@field(zig_element, field_name) = null;
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index 5d1dbcc31..6fa5bc511 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -21,7 +21,7 @@ const Response = WebCore.Response;
const Request = WebCore.Request;
const Router = @import("./api/router.zig");
const FetchEvent = WebCore.FetchEvent;
-const Headers = WebCore.Headers;
+const Headers = WebCore.Headers.RefCountedHeaders;
const Body = WebCore.Body;
const TaggedPointerTypes = @import("../../tagged_pointer.zig");
@@ -1742,6 +1742,35 @@ pub const ArrayBuffer = extern struct {
));
}
+ pub fn toJSWithContext(
+ this: ArrayBuffer,
+ ctx: JSC.C.JSContextRef,
+ deallocator: *anyopaque,
+ callback: JSC.C.JSTypedArrayBytesDeallocator,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.JSValue {
+ if (this.typed_array_type == .ArrayBuffer) {
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArrayBufferWithBytesNoCopy(
+ ctx,
+ this.ptr,
+ this.byte_len,
+ callback,
+ deallocator,
+ exception,
+ ));
+ }
+
+ return JSC.JSValue.fromRef(JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(
+ ctx,
+ this.typed_array_type.toC(),
+ this.ptr,
+ this.byte_len,
+ callback,
+ deallocator,
+ exception,
+ ));
+ }
+
pub const fromArrayBuffer = fromTypedArray;
pub inline fn slice(this: *const @This()) []u8 {
@@ -1749,15 +1778,15 @@ pub const ArrayBuffer = extern struct {
}
pub inline fn asU16(this: *const @This()) []u16 {
- const sliced = this.slice();
- const len = if (sliced.len > 0) sliced.len / 2 else 0;
- return @ptrCast([*]u16, @alignCast(@alignOf(@TypeOf(this.ptr)), this.ptr))[0..len];
+ return std.mem.bytesAsSlice(u16, @alignCast(@alignOf([*]u16), this.ptr[this.offset..this.byte_len]));
+ }
+
+ pub inline fn asU16Unaligned(this: *const @This()) []align(1) u16 {
+ return std.mem.bytesAsSlice(u16, @alignCast(@alignOf([*]align(1) u16), this.ptr[this.offset..this.byte_len]));
}
pub inline fn asU32(this: *const @This()) []u32 {
- const sliced = this.slice();
- const len = if (sliced.len > 0) sliced.len / 4 else 0;
- return @ptrCast([*]u32, @alignCast(@alignOf(@TypeOf(this.ptr)), this.ptr))[0..len];
+ return std.mem.bytesAsSlice(u32, @alignCast(@alignOf([*]u32), this.ptr)[this.offset..this.byte_len]);
}
};
@@ -1846,6 +1875,13 @@ export fn MarkedArrayBuffer_deallocator(bytes_: *anyopaque, _: *anyopaque) void
// mimalloc knows the size of things
// but we don't
mimalloc.mi_free(bytes_);
+
+pub export fn BlobArrayBuffer_deallocator(_: *anyopaque, blob: *anyopaque) void {
+ // zig's memory allocator interface won't work here
+ // mimalloc knows the size of things
+ // but we don't
+ var store = bun.cast(*JSC.WebCore.Blob.Store, blob);
+ store.deref();
}
pub fn castObj(obj: js.JSObjectRef, comptime Type: type) *Type {
diff --git a/src/javascript/jsc/bindings/ZigSourceProvider.cpp b/src/javascript/jsc/bindings/ZigSourceProvider.cpp
index c7394f1cb..de46bde77 100644
--- a/src/javascript/jsc/bindings/ZigSourceProvider.cpp
+++ b/src/javascript/jsc/bindings/ZigSourceProvider.cpp
@@ -35,7 +35,8 @@ Ref<SourceProvider> SourceProvider::create(ResolvedSource resolvedSource)
if (allocator) {
Ref<WTF::ExternalStringImpl> stringImpl_ = WTF::ExternalStringImpl::create(
resolvedSource.source_code.ptr, resolvedSource.source_code.len,
- [allocator](WTF::ExternalStringImpl* str, void* ptr, unsigned int len) {
+ nullptr,
+ [allocator](void* str, void* ptr, unsigned int len) {
ZigString__free((const unsigned char*)ptr, len, allocator);
});
return adoptRef(*new SourceProvider(
diff --git a/src/javascript/jsc/bindings/bindings.cpp b/src/javascript/jsc/bindings/bindings.cpp
index bfdb24600..e9ee4b380 100644
--- a/src/javascript/jsc/bindings/bindings.cpp
+++ b/src/javascript/jsc/bindings/bindings.cpp
@@ -740,7 +740,7 @@ JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlobalObject*
return JSC::JSValue::encode(JSC::JSValue(JSC::jsString(arg1->vm(), str)));
}
-void free_global_string(WTF::ExternalStringImpl* str, void* ptr, unsigned len)
+void free_global_string(void* str, void* ptr, unsigned len)
{
ZigString__free_global(reinterpret_cast<const unsigned char*>(ptr), len);
}
@@ -749,7 +749,7 @@ JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t len, JSC__JSG
{
return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
global->vm(),
- ExternalStringImpl::create(reinterpret_cast<const UChar*>(arg0), len, free_global_string))));
+ ExternalStringImpl::create(reinterpret_cast<const UChar*>(arg0), len, nullptr, free_global_string))));
}
// This must be a globally allocated string
JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObject* arg1)
@@ -758,12 +758,12 @@ JSC__JSValue ZigString__toExternalValue(const ZigString* arg0, JSC__JSGlobalObje
if (Zig::isTaggedUTF16Ptr(str.ptr)) {
return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
arg1->vm(),
- ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, free_global_string))));
+ ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, nullptr, free_global_string))));
}
return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
arg1->vm(),
- ExternalStringImpl::create(Zig::untag(str.ptr), str.len, free_global_string))));
+ ExternalStringImpl::create(Zig::untag(str.ptr), str.len, nullptr, free_global_string))));
}
JSC__JSValue ZigString__toValueGC(const ZigString* arg0, JSC__JSGlobalObject* arg1)
@@ -792,6 +792,21 @@ void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, JSC__JSGl
arg1->len = str.length();
}
+JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (*ArgFn3)(void* arg0, void* arg1, size_t arg2))
+{
+ ZigString str
+ = *arg0;
+ if (Zig::isTaggedUTF16Ptr(str.ptr)) {
+ return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
+ arg1->vm(),
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, arg2, ArgFn3)))));
+ } else {
+ return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
+ arg1->vm(),
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, arg2, ArgFn3)))));
+ }
+}
+
JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__JSGlobalObject* arg1, void (*ArgFn2)(void* arg2, void* arg0, size_t arg1))
{
@@ -800,11 +815,11 @@ JSC__JSValue ZigString__toExternalValueWithCallback(const ZigString* arg0, JSC__
if (Zig::isTaggedUTF16Ptr(str.ptr)) {
return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
arg1->vm(),
- WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, ArgFn2)))));
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const UChar*>(Zig::untag(str.ptr)), str.len, nullptr, ArgFn2)))));
} else {
return JSC::JSValue::encode(JSC::JSValue(JSC::jsOwnedString(
arg1->vm(),
- WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, ArgFn2)))));
+ WTF::String(ExternalStringImpl::create(reinterpret_cast<const LChar*>(Zig::untag(str.ptr)), str.len, nullptr, ArgFn2)))));
}
}
diff --git a/src/javascript/jsc/bindings/bindings.zig b/src/javascript/jsc/bindings/bindings.zig
index f33d82ad6..25adec479 100644
--- a/src/javascript/jsc/bindings/bindings.zig
+++ b/src/javascript/jsc/bindings/bindings.zig
@@ -169,6 +169,10 @@ pub const ZigString = extern struct {
return ZigString{ .ptr = slice_.ptr, .len = slice_.len };
}
+ pub fn from(slice_: JSC.C.JSValueRef, ctx: JSC.C.JSContextRef) ZigString {
+ return JSC.JSValue.fromRef(slice_).getZigString(ctx.ptr());
+ }
+
pub fn toBase64DataURL(this: ZigString, allocator: std.mem.Allocator) ![]const u8 {
const slice_ = this.slice();
const size = std.base64.standard.Encoder.calcSize(slice_.len);
@@ -242,6 +246,10 @@ pub const ZigString = extern struct {
return untagged(this.ptr)[0..@minimum(this.len, std.math.maxInt(u32))];
}
+ pub fn dupe(this: ZigString, allocator: std.mem.Allocator) ![]const u8 {
+ return try allocator.dupe(u8, this.slice());
+ }
+
pub fn toSlice(this: ZigString, allocator: std.mem.Allocator) Slice {
if (this.len == 0)
return Slice{ .ptr = "", .len = 0, .allocator = allocator, .allocated = false };
@@ -300,6 +308,15 @@ pub const ZigString = extern struct {
return shim.cppFn("toExternalValueWithCallback", .{ this, global, callback });
}
+ pub fn external(
+ this: *const ZigString,
+ global: *JSGlobalObject,
+ ctx: ?*anyopaque,
+ callback: fn (ctx: ?*anyopaque, ptr: ?*anyopaque, len: usize) callconv(.C) void,
+ ) JSValue {
+ return shim.cppFn("external", .{ this, global, ctx, callback });
+ }
+
pub fn to16BitValue(this: *const ZigString, global: *JSGlobalObject) JSValue {
return shim.cppFn("to16BitValue", .{ this, global });
}
@@ -329,15 +346,7 @@ pub const ZigString = extern struct {
return shim.cppFn("toErrorInstance", .{ this, global });
}
- pub const Extern = [_][]const u8{
- "toValue",
- "toExternalValue",
- "to16BitValue",
- "toValueGC",
- "toErrorInstance",
- "toExternalU16",
- "toExternalValueWithCallback",
- };
+ pub const Extern = [_][]const u8{ "toValue", "toExternalValue", "to16BitValue", "toValueGC", "toErrorInstance", "toExternalU16", "toExternalValueWithCallback", "external" };
};
pub const SystemError = extern struct {
diff --git a/src/javascript/jsc/bindings/headers-cpp.h b/src/javascript/jsc/bindings/headers-cpp.h
index c620b6e98..dccb07f35 100644
--- a/src/javascript/jsc/bindings/headers-cpp.h
+++ b/src/javascript/jsc/bindings/headers-cpp.h
@@ -1,4 +1,4 @@
-//-- AUTOGENERATED FILE -- 1647165586
+//-- AUTOGENERATED FILE -- 1647230769
// clang-format off
#pragma once
diff --git a/src/javascript/jsc/bindings/headers.h b/src/javascript/jsc/bindings/headers.h
index f63a87319..afe6eb575 100644
--- a/src/javascript/jsc/bindings/headers.h
+++ b/src/javascript/jsc/bindings/headers.h
@@ -1,5 +1,5 @@
// clang-format: off
-//-- AUTOGENERATED FILE -- 1647165586
+//-- AUTOGENERATED FILE -- 1647230769
#pragma once
#include <stddef.h>
@@ -246,6 +246,7 @@ CPP_DECL size_t JSC__JSObject__getArrayLength(JSC__JSObject* arg0);
CPP_DECL JSC__JSValue JSC__JSObject__getDirect(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, const ZigString* arg2);
CPP_DECL JSC__JSValue JSC__JSObject__getIndex(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2);
CPP_DECL void JSC__JSObject__putRecord(JSC__JSObject* arg0, JSC__JSGlobalObject* arg1, ZigString* arg2, ZigString* arg3, size_t arg4);
+CPP_DECL JSC__JSValue ZigString__external(const ZigString* arg0, JSC__JSGlobalObject* arg1, void* arg2, void (* ArgFn3)(void* arg0, void* arg1, size_t arg2));
CPP_DECL JSC__JSValue ZigString__to16BitValue(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue ZigString__toErrorInstance(const ZigString* arg0, JSC__JSGlobalObject* arg1);
CPP_DECL JSC__JSValue ZigString__toExternalU16(const uint16_t* arg0, size_t arg1, JSC__JSGlobalObject* arg2);
diff --git a/src/javascript/jsc/bindings/headers.zig b/src/javascript/jsc/bindings/headers.zig
index 0932fb5f5..3729be6b9 100644
--- a/src/javascript/jsc/bindings/headers.zig
+++ b/src/javascript/jsc/bindings/headers.zig
@@ -136,6 +136,7 @@ pub extern fn JSC__JSObject__getArrayLength(arg0: [*c]JSC__JSObject) usize;
pub extern fn JSC__JSObject__getDirect(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]const ZigString) JSC__JSValue;
pub extern fn JSC__JSObject__getIndex(JSValue0: JSC__JSValue, arg1: [*c]JSC__JSGlobalObject, arg2: u32) JSC__JSValue;
pub extern fn JSC__JSObject__putRecord(arg0: [*c]JSC__JSObject, arg1: [*c]JSC__JSGlobalObject, arg2: [*c]ZigString, arg3: [*c]ZigString, arg4: usize) void;
+pub extern fn ZigString__external(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject, arg2: ?*anyopaque, ArgFn3: ?fn (?*anyopaque, ?*anyopaque, usize) callconv(.C) void) JSC__JSValue;
pub extern fn ZigString__to16BitValue(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toErrorInstance(arg0: [*c]const ZigString, arg1: [*c]JSC__JSGlobalObject) JSC__JSValue;
pub extern fn ZigString__toExternalU16(arg0: [*c]const u16, arg1: usize, arg2: [*c]JSC__JSGlobalObject) JSC__JSValue;
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 60ba111de..0eedd8266 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -93,10 +93,10 @@ pub const GlobalClasses = [_]type{
Bun.Class,
Fetch.Class,
js_ast.Macro.JSNode.BunJSXCallbackFunction,
- Performance.Class,
+ WebCore.Performance.Class,
- Crypto.Class,
- Crypto.Prototype,
+ WebCore.Crypto.Class,
+ WebCore.Crypto.Prototype,
WebCore.TextEncoder.Constructor.Class,
WebCore.TextDecoder.Constructor.Class,
@@ -107,89 +107,10 @@ pub const GlobalClasses = [_]type{
// The last item in this array becomes "process.env"
Bun.EnvironmentVariables.Class,
};
-const UUID = @import("./uuid.zig");
const Blob = @import("../../blob.zig");
pub const Buffer = MarkedArrayBuffer;
const Lock = @import("../../lock.zig").Lock;
-pub const Crypto = struct {
- pub const Class = NewClass(void, .{ .name = "crypto" }, .{
- .getRandomValues = .{
- .rfn = getRandomValues,
- },
- .randomUUID = .{
- .rfn = randomUUID,
- },
- }, .{});
- pub const Prototype = NewClass(
- void,
- .{ .name = "Crypto" },
- .{
- .call = .{
- .rfn = call,
- },
- },
- .{},
- );
-
- pub fn getRandomValues(
- // this
- _: void,
- ctx: js.JSContextRef,
- // function
- _: js.JSObjectRef,
- // thisObject
- _: js.JSObjectRef,
- arguments: []const js.JSValueRef,
- exception: js.ExceptionRef,
- ) js.JSValueRef {
- if (arguments.len == 0) {
- JSError(getAllocator(ctx), "Expected typed array but received nothing", .{}, ctx, exception);
- return JSValue.jsUndefined().asObjectRef();
- }
- var array_buffer = MarkedArrayBuffer.fromJS(ctx.ptr(), JSValue.fromRef(arguments[0]), exception) orelse {
- JSError(getAllocator(ctx), "Expected typed array", .{}, ctx, exception);
- return JSValue.jsUndefined().asObjectRef();
- };
- var slice = array_buffer.slice();
- if (slice.len > 0)
- std.crypto.random.bytes(slice);
-
- return arguments[0];
- }
-
- pub fn call(
- // this
- _: void,
- _: js.JSContextRef,
- // function
- _: js.JSObjectRef,
- // thisObject
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- _: js.ExceptionRef,
- ) js.JSValueRef {
- return JSValue.jsUndefined().asObjectRef();
- }
-
- pub fn randomUUID(
- // this
- _: void,
- ctx: js.JSContextRef,
- // function
- _: js.JSObjectRef,
- // thisObject
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- _: js.ExceptionRef,
- ) js.JSValueRef {
- var uuid = UUID.init();
- var out: [128]u8 = undefined;
- var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable;
- return ZigString.init(str).toValueGC(ctx.ptr()).asObjectRef();
- }
-};
-
pub const Bun = struct {
threadlocal var css_imports_list_strings: [512]ZigString = undefined;
threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
@@ -865,9 +786,7 @@ pub const Bun = struct {
.rfn = Router.match,
.ts = Router.match_type_definition,
},
- .__debug__doSegfault = .{
- .rfn = Bun.__debug__doSegfault,
- },
+
.sleepSync = .{
.rfn = sleepSync,
},
@@ -999,6 +918,9 @@ pub const Bun = struct {
.get = getTOMLObject,
.ts = d.ts{ .name = "TOML", .@"return" = "TOML.prototype" },
},
+ .unsafe = .{
+ .get = getUnsafe,
+ },
},
);
@@ -1022,20 +944,69 @@ pub const Bun = struct {
return js.JSObjectMake(ctx, TOML.Class.get().?[0], null);
}
- // For testing the segfault handler
- pub fn __debug__doSegfault(
+ pub fn getUnsafe(
_: void,
ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- _ = ctx;
- const Reporter = @import("../../report.zig");
- Reporter.globalError(error.SegfaultTest);
+ return js.JSObjectMake(ctx, Unsafe.Class.get().?[0], null);
}
+ pub const Unsafe = struct {
+ pub const Class = NewClass(
+ void,
+ .{ .name = "Unsafe", .read_only = true },
+ .{
+ .segfault = .{
+ .rfn = __debug__doSegfault,
+ },
+ .arrayBufferToString = .{
+ .rfn = arrayBufferToString,
+ },
+ },
+ .{},
+ );
+
+ // For testing the segfault handler
+ pub fn __debug__doSegfault(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ _: []const js.JSValueRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ _ = ctx;
+ const Reporter = @import("../../report.zig");
+ Reporter.globalError(error.SegfaultTest);
+ }
+
+ pub fn arrayBufferToString(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSObjectRef,
+ args: []const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) js.JSValueRef {
+ const array_buffer = JSC.ArrayBuffer.fromTypedArray(ctx, JSC.JSValue.fromRef(args[0]), exception);
+ switch (array_buffer.typed_array_type) {
+ .Uint16Array, .Int16Array => {
+ var zig_str = ZigString.init("");
+ zig_str.ptr = @ptrCast([*]const u8, @alignCast(@alignOf([*]align(1) const u16), array_buffer.ptr));
+ zig_str.len = array_buffer.len;
+ zig_str.markUTF16();
+ return ZigString.toValue(&zig_str, ctx.ptr()).asObjectRef();
+ },
+ else => {
+ return ZigString.init(array_buffer.slice()).toValue(ctx.ptr()).asObjectRef();
+ },
+ }
+ }
+ };
+
// pub const Lockfile = struct {
// const BunLockfile = @import("../../install/install.zig").Lockfile;
// pub const Class = NewClass(
@@ -1551,42 +1522,6 @@ pub fn OpaqueWrap(comptime Context: type, comptime Function: fn (this: *Context)
}.callback;
}
-pub const Performance = struct {
- pub const Class = NewClass(
- void,
- .{
- .name = "performance",
- .read_only = true,
- },
- .{
- .now = .{
- .rfn = Performance.now,
- },
- },
- .{},
- );
-
- pub fn now(
- _: void,
- ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSObjectRef,
- _: []const js.JSValueRef,
- _: js.ExceptionRef,
- ) js.JSValueRef {
- return js.JSValueMakeNumber(
- ctx,
- @floatCast(
- f64,
- @intToFloat(
- f128,
- VirtualMachine.vm.origin_timer.read(),
- ) / std.time.ns_per_ms,
- ),
- );
- }
-};
-
const bun_file_import_path = "/node_modules.server.bun";
const FetchTasklet = Fetch.FetchTasklet;
@@ -1961,6 +1896,18 @@ pub const VirtualMachine = struct {
}
}
+ pub fn waitForPromise(this: *EventLoop, promise: *JSC.JSInternalPromise) void {
+ var _vm = this.global.vm();
+ switch (promise.status(_vm)) {
+ JSC.JSPromise.Status.Pending => {
+ while (promise.status(_vm) == .Pending) {
+ this.tick();
+ }
+ },
+ else => {},
+ }
+ }
+
pub fn waitForTasks(this: *EventLoop) void {
this.tick();
while (this.pending_tasks_count.load(.Monotonic) > 0) {
@@ -3098,7 +3045,7 @@ pub const VirtualMachine = struct {
}
}
- fn remapZigException(
+ pub fn remapZigException(
this: *VirtualMachine,
exception: *ZigException,
error_instance: JSValue,
@@ -3442,9 +3389,12 @@ pub const EventListenerMixin = struct {
for (listeners.items) |listener_ref| {
fetch_args[0] = FetchEvent.Class.make(vm.global.ref(), fetch_event);
+ vm.tick();
var result = js.JSObjectCallAsFunctionReturnValue(vm.global.ref(), listener_ref, null, 1, &fetch_args);
- var promise = JSPromise.resolvedPromise(vm.global, result);
- vm.waitForTasks();
+ vm.tick();
+ var promise = JSInternalPromise.resolvedPromise(vm.global, result);
+
+ vm.event_loop.waitForPromise(promise);
if (fetch_event.rejected) return;
diff --git a/src/javascript/jsc/webcore.zig b/src/javascript/jsc/webcore.zig
index 488bcae0e..1a25c658d 100644
--- a/src/javascript/jsc/webcore.zig
+++ b/src/javascript/jsc/webcore.zig
@@ -1,2 +1,120 @@
pub usingnamespace @import("./webcore/response.zig");
pub usingnamespace @import("./webcore/encoding.zig");
+const JSC = @import("../../jsc.zig");
+const std = @import("std");
+
+pub const Crypto = struct {
+ const UUID = @import("./uuid.zig");
+
+ pub const Class = JSC.NewClass(void, .{ .name = "crypto" }, .{
+ .getRandomValues = .{
+ .rfn = getRandomValues,
+ },
+ .randomUUID = .{
+ .rfn = randomUUID,
+ },
+ }, .{});
+ pub const Prototype = JSC.NewClass(
+ void,
+ .{ .name = "Crypto" },
+ .{
+ .call = .{
+ .rfn = call,
+ },
+ },
+ .{},
+ );
+
+ pub fn getRandomValues(
+ // this
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ arguments: []const JSC.C.JSValueRef,
+ exception: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ if (arguments.len == 0) {
+ JSC.JSError(JSC.getAllocator(ctx), "Expected typed array but received nothing", .{}, ctx, exception);
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ }
+ var array_buffer = JSC.MarkedArrayBuffer.fromJS(ctx.ptr(), JSC.JSValue.fromRef(arguments[0]), exception) orelse {
+ JSC.JSError(JSC.getAllocator(ctx), "Expected typed array", .{}, ctx, exception);
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ };
+ var slice = array_buffer.slice();
+ if (slice.len > 0)
+ std.crypto.random.bytes(slice);
+
+ return arguments[0];
+ }
+
+ pub fn call(
+ // this
+ _: void,
+ _: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ _: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ return JSC.JSValue.jsUndefined().asObjectRef();
+ }
+
+ pub fn randomUUID(
+ // this
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ // function
+ _: JSC.C.JSObjectRef,
+ // thisObject
+ _: JSC.C.JSObjectRef,
+ _: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ var uuid = UUID.init();
+ var out: [128]u8 = undefined;
+ var str = std.fmt.bufPrint(&out, "{s}", .{uuid}) catch unreachable;
+ return JSC.ZigString.init(str).toValueGC(ctx.ptr()).asObjectRef();
+ }
+};
+
+pub const Performance = struct {
+ pub const Class = JSC.NewClass(
+ void,
+ .{
+ .name = "performance",
+ .read_only = true,
+ },
+ .{
+ .now = .{
+ .rfn = Performance.now,
+ },
+ },
+ .{},
+ );
+
+ pub fn now(
+ _: void,
+ ctx: JSC.C.JSContextRef,
+ _: JSC.C.JSObjectRef,
+ _: JSC.C.JSObjectRef,
+ _: []const JSC.C.JSValueRef,
+ _: JSC.C.ExceptionRef,
+ ) JSC.C.JSValueRef {
+ return JSC.C.JSValueMakeNumber(
+ ctx,
+ @floatCast(
+ f64,
+ @intToFloat(
+ f128,
+ JSC.VirtualMachine.vm.origin_timer.read(),
+ ) / std.time.ns_per_ms,
+ ),
+ );
+ }
+};
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index 6449cb173..0880c78e8 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -47,6 +47,7 @@ pub const Response = struct {
.{ .name = "Response" },
.{
.@"constructor" = constructor,
+ .@"finalize" = finalize,
.@"text" = .{
.rfn = Response.getText,
.ts = d.ts{},
@@ -202,12 +203,10 @@ pub const Response = struct {
_: js.ExceptionRef,
) js.JSValueRef {
if (this.body.init.headers == null) {
- var headers = getAllocator(ctx).create(Headers) catch unreachable;
- headers.* = Headers.empty(getAllocator(ctx));
- this.body.init.headers = headers;
+ this.body.init.headers = Headers.RefCountedHeaders.init(Headers.empty(getAllocator(ctx)), getAllocator(ctx)) catch unreachable;
}
- return Headers.Class.make(ctx, this.body.init.headers.?);
+ return Headers.Class.make(ctx, this.body.init.headers.?.getRef());
}
pub fn doClone(
@@ -270,7 +269,9 @@ pub const Response = struct {
}
pub fn mimeType(response: *const Response, request_ctx_: ?*const RequestContext) string {
- if (response.body.init.headers) |headers| {
+ if (response.body.init.headers) |headers_ref| {
+ var headers = headers_ref.get();
+ defer headers_ref.deref();
// Remember, we always lowercase it
// hopefully doesn't matter here tho
if (headers.getHeaderIndex("content-type")) |content_type| {
@@ -429,6 +430,8 @@ pub const Fetch = struct {
reject: js.JSObjectRef = null,
context: FetchTaskletContext = undefined,
+ blob_store: ?*Blob.Store = null,
+
const Pool = ObjectPool(FetchTasklet, init, true, 32);
const BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8);
pub const FetchTaskletContext = struct {
@@ -516,6 +519,9 @@ pub const Fetch = struct {
};
pub fn onReject(this: *FetchTasklet) JSValue {
+ if (this.blob_store) |store| {
+ store.deref();
+ }
const fetch_error = std.fmt.allocPrint(
default_allocator,
"fetch() failed – {s}\nurl: \"{s}\"",
@@ -530,12 +536,11 @@ pub const Fetch = struct {
pub fn onResolve(this: *FetchTasklet) JSValue {
var allocator = default_allocator;
var http_response = this.http.response.?;
- var response_headers = Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable;
- response_headers.guard = .immutable;
var response = allocator.create(Response) catch unreachable;
var duped = allocator.dupe(u8, this.http.response_buffer.toOwnedSlice()) catch unreachable;
- var headers = allocator.create(Headers) catch unreachable;
- headers.* = response_headers;
+ if (this.blob_store) |store| {
+ store.deref();
+ }
response.* = Response{
.allocator = allocator,
.url = allocator.dupe(u8, this.http.url.href) catch unreachable,
@@ -543,7 +548,10 @@ pub const Fetch = struct {
.redirected = this.http.redirect_count > 0,
.body = .{
.init = .{
- .headers = headers,
+ .headers = Headers.RefCountedHeaders.init(
+ Headers.fromPicoHeaders(allocator, http_response.headers) catch unreachable,
+ allocator,
+ ) catch unreachable,
.status_code = @truncate(u16, http_response.status_code),
},
.value = .{
@@ -562,11 +570,13 @@ pub const Fetch = struct {
headers_buf: string,
request_body: ?*MutableString,
timeout: usize,
+ request_body_store: ?*Blob.Store,
) !*FetchTasklet.Pool.Node {
var linked_list = FetchTasklet.Pool.get(allocator);
linked_list.data.javascript_vm = VirtualMachine.vm;
linked_list.data.empty_request_body = MutableString.init(allocator, 0) catch unreachable;
linked_list.data.pooled_body = BodyPool.get(allocator);
+ linked_list.data.blob_store = request_body_store;
linked_list.data.http = try HTTPClient.AsyncHTTP.init(
allocator,
method,
@@ -592,8 +602,9 @@ pub const Fetch = struct {
headers_buf: string,
request_body: ?*MutableString,
timeout: usize,
+ request_body_store: ?*Blob.Store,
) !*FetchTasklet.Pool.Node {
- var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout);
+ var node = try get(allocator, method, url, headers, headers_buf, request_body, timeout, request_body_store);
node.data.promise = JSInternalPromise.create(global);
node.data.global_this = global;
@@ -633,6 +644,7 @@ pub const Fetch = struct {
var args = JSC.Node.ArgumentsSlice.from(arguments);
var url: ZigURL = undefined;
var first_arg = args.nextEat().?;
+ var blob_store: ?*Blob.Store = null;
if (first_arg.isString()) {
var url_zig_str = ZigString.init("");
JSValue.fromRef(arguments[0]).toZigString(&url_zig_str, globalThis);
@@ -662,8 +674,8 @@ pub const Fetch = struct {
if (options.get(ctx.ptr(), "headers")) |headers_| {
var headers2: Headers = undefined;
- if (headers_.as(Headers)) |headers__| {
- headers__.clone(&headers2) catch unreachable;
+ if (headers_.as(Headers.RefCountedHeaders)) |headers__| {
+ headers__.leak().clone(&headers2) catch unreachable;
headers = headers2;
} else if (Headers.JS.headersInit(ctx, headers_.asObjectRef()) catch null) |headers__| {
headers__.clone(&headers2) catch unreachable;
@@ -672,23 +684,39 @@ pub const Fetch = struct {
}
if (options.get(ctx.ptr(), "body")) |body__| {
- if (Blob.fromJS(ctx.ptr(), body__)) |new_blob| {
- body = MutableString{ .list = new_blob.asArrayList(), .allocator = bun.default_allocator };
+ 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;
+ // transfer is unnecessary here because this is a new slice
+ //new_blob.transfer();
} else |_| {}
}
}
} else if (Request.Class.loaded and first_arg.as(Request) != null) {
var request = first_arg.as(Request).?;
- url = ZigURL.parse(request.url.slice());
+ url = ZigURL.parse(request.url.dupe(getAllocator(ctx)) catch unreachable);
method = request.method;
if (request.headers) |head| {
- headers = head.*;
+ var for_clone: Headers = undefined;
+ head.leak().clone(&for_clone) catch unreachable;
+ headers = for_clone;
}
var blob = request.body.use();
+ // TODO: make RequestBody _NOT_ a MutableString
body = MutableString{
- .list = blob.asArrayList(),
+ .list = std.ArrayListUnmanaged(u8){
+ .items = bun.constStrToU8(blob.sharedView()),
+ .capacity = bun.constStrToU8(blob.sharedView()).len,
+ },
.allocator = blob.allocator orelse bun.default_allocator,
};
+ blob_store = blob.store;
} else {
const fetch_error = fetch_type_error_strings.get(js.JSValueGetType(ctx, arguments[0]));
return JSPromise.rejectedPromiseValue(globalThis, ZigString.init(fetch_error).toErrorInstance(globalThis)).asRef();
@@ -729,6 +757,7 @@ pub const Fetch = struct {
header_buf,
request_body,
std.time.ns_per_hour,
+ blob_store,
) catch unreachable;
queued.data.this_object = js.JSObjectMake(ctx, null, JSPrivateDataPtr.from(&queued.data.context).ptr());
js.JSValueProtect(ctx, queued.data.this_object);
@@ -751,6 +780,8 @@ pub const Headers = struct {
used: u32 = 0,
guard: Guard = Guard.none,
+ pub const RefCountedHeaders = bun.RefCount(Headers, true);
+
pub fn deinit(
headers: *Headers,
) void {
@@ -774,24 +805,25 @@ pub const Headers = struct {
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/get
pub fn get(
- this: *Headers,
+ ref: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
arguments: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSValueRef {
+ var this = ref.leak();
if (arguments.len == 0 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) {
return js.JSValueMakeNull(ctx);
}
- const key_len = js.JSStringGetUTF8CString(arguments[0], &header_kv_buf, header_kv_buf.len);
- const key = header_kv_buf[0 .. key_len - 1];
- if (this.getHeaderIndex(key)) |index| {
- var str = this.asStr(this.entries.items(.value)[index]);
- var ref = js.JSStringCreateWithUTF8CString(str.ptr);
- defer js.JSStringRelease(ref);
- return js.JSValueMakeString(ctx, ref);
+ const key = ZigString.from(arguments[0], ctx);
+ if (key.is16Bit())
+ return js.JSValueMakeNull(ctx);
+
+ if (this.getHeaderIndex(key.slice())) |index| {
+ return ZigString.init(this.asStr(this.entries.items(.value)[index]))
+ .toValue(ctx.ptr()).asObjectRef();
} else {
return js.JSValueMakeNull(ctx);
}
@@ -801,13 +833,14 @@ pub const Headers = struct {
// > The difference between set() and Headers.append is that if the specified header already exists and accepts multiple values
// > set() overwrites the existing value with the new one, whereas Headers.append appends the new value to the end of the set of values.
pub fn set(
- this: *Headers,
+ ref: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
arguments: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSValueRef {
+ var this = ref.leak();
if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) {
return js.JSValueMakeUndefined(ctx);
}
@@ -818,13 +851,14 @@ pub const Headers = struct {
// https://developer.mozilla.org/en-US/docs/Web/API/Headers/append
pub fn append(
- this: *Headers,
+ ref: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
arguments: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSValueRef {
+ var this = ref.leak();
if (this.guard == .request or arguments.len < 2 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string) or !js.JSValueIsString(ctx, arguments[1])) {
return js.JSValueMakeUndefined(ctx);
}
@@ -833,13 +867,14 @@ pub const Headers = struct {
return js.JSValueMakeUndefined(ctx);
}
pub fn delete(
- this: *Headers,
+ ref: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
arguments: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSValueRef {
+ var this = ref.leak();
if (this.guard == .request or arguments.len < 1 or !js.JSValueIsString(ctx, arguments[0]) or js.JSStringIsEqual(arguments[0], Properties.Refs.empty_string)) {
return js.JSValueMakeUndefined(ctx);
}
@@ -854,7 +889,7 @@ pub const Headers = struct {
return js.JSValueMakeUndefined(ctx);
}
pub fn entries(
- _: *Headers,
+ _: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
@@ -865,7 +900,7 @@ pub const Headers = struct {
return js.JSValueMakeNull(ctx);
}
pub fn keys(
- _: *Headers,
+ _: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
@@ -876,7 +911,7 @@ pub const Headers = struct {
return js.JSValueMakeNull(ctx);
}
pub fn values(
- _: *Headers,
+ _: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
@@ -965,33 +1000,43 @@ pub const Headers = struct {
arguments: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSObjectRef {
- var headers = getAllocator(ctx).create(Headers) catch unreachable;
+ var headers = getAllocator(ctx).create(RefCountedHeaders) catch unreachable;
if (arguments.len > 0 and js.JSValueIsObjectOfClass(ctx, arguments[0], Headers.Class.get().*)) {
- var other = castObj(arguments[0], Headers);
- other.clone(headers) catch unreachable;
+ var other = castObj(arguments[0], RefCountedHeaders).leak();
+ other.clone(&headers.value) catch unreachable;
+ headers.count = 1;
+ headers.allocator = getAllocator(ctx);
} else if (arguments.len == 1 and js.JSValueIsObject(ctx, arguments[0])) {
- headers.* = (JS.headersInit(ctx, arguments[0]) catch unreachable) orelse Headers{
- .entries = @TypeOf(headers.entries){},
- .buf = @TypeOf(headers.buf){},
- .used = 0,
+ headers.* = .{
+ .value = (JS.headersInit(ctx, arguments[0]) catch unreachable) orelse Headers{
+ .entries = .{},
+ .buf = .{},
+ .used = 0,
+ .allocator = getAllocator(ctx),
+ .guard = Guard.none,
+ },
.allocator = getAllocator(ctx),
- .guard = Guard.none,
+ .count = 1,
};
} else {
- headers.* = Headers.empty(getAllocator(ctx));
+ headers.* = .{
+ .value = Headers.empty(getAllocator(ctx)),
+ .allocator = getAllocator(ctx),
+ .count = 1,
+ };
}
return Headers.Class.make(ctx, headers);
}
pub fn finalize(
- this: *Headers,
+ this: *RefCountedHeaders,
) void {
- this.deinit();
+ this.deref();
}
};
pub const Class = NewClass(
- Headers,
+ RefCountedHeaders,
.{
.name = "Headers",
.read_only = true,
@@ -1247,13 +1292,14 @@ pub const Headers = struct {
}
pub fn toJSON(
- this: *Headers,
+ ref: *RefCountedHeaders,
ctx: js.JSContextRef,
_: js.JSObjectRef,
_: js.JSObjectRef,
_: []const js.JSValueRef,
_: js.ExceptionRef,
) js.JSValueRef {
+ var this = ref.leak();
const slice = this.entries.slice();
const keys = slice.items(.name);
const values = slice.items(.value);
@@ -1352,27 +1398,92 @@ pub const Headers = struct {
};
pub const Blob = struct {
- size: usize = 0,
- ptr: ?[*]u8 = null,
- cap: usize = 0,
- ptr_allocator: ?std.mem.Allocator = null,
+ size: u32 = 0,
+ offset: u32 = 0,
allocator: ?std.mem.Allocator = null,
-
+ store: ?*Store = null,
content_type: string = "",
content_type_allocated: bool = false,
- detached: bool = false,
globalThis: *JSGlobalObject,
- pub fn asArrayList(this: *const Blob) std.ArrayListUnmanaged(u8) {
- if (this.ptr == null or this.size == 0)
- return .{};
+ pub const Store = struct {
+ ptr: [*]u8 = undefined,
+ len: u32 = 0,
+ ref_count: u32 = 0,
+ cap: u32 = 0,
+ allocator: std.mem.Allocator,
- return .{
- .items = this.ptr.?[0..this.size],
- .capacity = this.cap,
- };
- }
+ pub inline fn ref(this: *Store) void {
+ this.ref_count += 1;
+ }
+
+ pub fn init(bytes: []u8, allocator: std.mem.Allocator) !*Store {
+ var store = try allocator.create(Store);
+ store.* = .{
+ .ptr = bytes.ptr,
+ .len = @truncate(u32, bytes.len),
+ .ref_count = 1,
+ .cap = @truncate(u32, bytes.len),
+ .allocator = allocator,
+ };
+ return store;
+ }
+
+ pub fn external(_: ?*anyopaque, ptr: ?*anyopaque, _: usize) callconv(.C) void {
+ if (ptr == null) return;
+ var this = bun.cast(*Store, ptr);
+ this.deref();
+ }
+
+ pub fn fromArrayList(list: std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator) !*Store {
+ var store = try allocator.create(Store);
+ store.* = .{
+ .ptr = list.items.ptr,
+ .len = @truncate(u32, list.items.len),
+ .ref_count = 1,
+ .cap = @truncate(u32, list.capacity),
+ .allocator = allocator,
+ };
+ return store;
+ }
+
+ pub fn leakSlice(this: *const Store) []const u8 {
+ return this.ptr[0..this.len];
+ }
+
+ pub fn slice(this: *Store) []u8 {
+ this.ref_count += 1;
+ return this.leakSlice();
+ }
+
+ pub fn isOnlyOneRef(this: *const Store) bool {
+ return this.ref_count <= 1;
+ }
+
+ pub fn deref(this: *Store) void {
+ this.ref_count -= 1;
+ if (this.ref_count == 0) {
+ var allocated_slice = this.ptr[0..this.cap];
+ var allocator = this.allocator;
+ allocator.free(allocated_slice);
+ allocator.destroy(this);
+ }
+ }
+
+ pub fn asArrayList(this: *Store) std.ArrayListUnmanaged(u8) {
+ this.ref_count += 1;
+
+ return this.asArrayListLeak();
+ }
+
+ pub fn asArrayListLeak(this: *const Store) std.ArrayListUnmanaged(u8) {
+ return .{
+ .items = this.ptr[0..this.len],
+ .capacity = this.cap,
+ };
+ }
+ };
pub const Class = NewClass(
Blob,
@@ -1519,11 +1630,9 @@ pub const Blob = struct {
const len = @intCast(u32, @maximum(relativeEnd - relativeStart, 0));
- var blob = Blob.init(
- getAllocator(ctx).dupe(u8, this.sharedView()[@intCast(usize, relativeStart)..][0..len]) catch unreachable,
- getAllocator(ctx),
- ctx.ptr(),
- );
+ var blob = this.dupe();
+ blob.offset = @intCast(u32, relativeStart);
+ blob.size = len;
blob.content_type = content_type;
blob.content_type_allocated = content_type.len > 0;
@@ -1590,7 +1699,7 @@ pub const Blob = struct {
blob = Blob.init(empty, getAllocator(ctx), ctx.ptr());
},
else => {
- blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0])) catch {
+ blob = fromJS(ctx.ptr(), JSValue.fromRef(args[0]), false) catch {
JSC.JSError(getAllocator(ctx), "out of memory :(", .{}, ctx, exception);
return null;
};
@@ -1634,13 +1743,10 @@ pub const Blob = struct {
pub fn init(bytes: []u8, allocator: std.mem.Allocator, globalThis: *JSGlobalObject) Blob {
return Blob{
- .size = bytes.len,
- .ptr = bytes.ptr,
- .cap = bytes.len,
- .ptr_allocator = allocator,
+ .size = @truncate(u32, bytes.len),
+ .store = Blob.Store.init(bytes, allocator) catch unreachable,
.allocator = null,
.content_type = "",
- .detached = false,
.globalThis = globalThis,
};
}
@@ -1648,39 +1754,38 @@ pub const Blob = struct {
pub fn initEmpty(globalThis: *JSGlobalObject) Blob {
return Blob{
.size = 0,
- .ptr = undefined,
- .cap = 0,
- .ptr_allocator = null,
+ .store = null,
.allocator = null,
.content_type = "",
- .detached = false,
.globalThis = globalThis,
};
}
+ // Transferring doesn't change the reference count
+ // It is a move
+ inline fn transfer(this: *Blob) void {
+ this.store = null;
+ }
+
pub fn detach(this: *Blob) void {
- var blob = this.*;
- if (blob.ptr_allocator) |alloc| {
- alloc.free(blob.sharedView());
- this.ptr_allocator = null;
- this.ptr = null;
- this.size = 0;
- this.cap = 0;
+ if (this.store) |store| {
+ store.deref();
+ this.store = null;
}
-
- this.detached = true;
}
- pub fn dupe(this: *const Blob, allocator: std.mem.Allocator) !Blob {
- var data = try allocator.dupe(u8, this.sharedView());
+ /// This does not duplicate
+ /// This creates a new view
+ /// and increment the reference count
+ pub fn dupe(this: *const Blob) Blob {
+ if (this.store) |store| {
+ store.ref();
+ }
+
return Blob{
- .size = data.len,
- .ptr = data.ptr,
- .cap = data.len,
- .ptr_allocator = allocator,
- .allocator = allocator,
+ .store = this.store,
+ .size = this.size,
.content_type = this.content_type,
- .detached = false,
.globalThis = this.globalThis,
};
}
@@ -1697,8 +1802,13 @@ pub const Blob = struct {
}
pub fn sharedView(this: *const Blob) []const u8 {
- if (this.size == 0 or this.ptr == null) return "";
- return this.ptr.?[0..this.size];
+ if (this.size == 0 or this.store == null) return "";
+ return this.store.?.leakSlice()[this.offset..][0..this.size];
+ }
+
+ pub fn view(this: *const Blob) []const u8 {
+ if (this.size == 0 or this.store == null) return "";
+ return this.store.?.slice()[this.offset..][0..this.size];
}
pub const Lifetime = enum {
@@ -1708,7 +1818,8 @@ pub const Blob = struct {
};
pub fn toString(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
- var view_ = this.sharedView();
+ var view_: []const u8 =
+ this.sharedView();
if (view_.len == 0)
return ZigString.Empty.toValue(global);
@@ -1717,6 +1828,10 @@ pub const Blob = struct {
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();
+ }
+
return ZigString.toExternalU16(external.ptr, external.len, global);
}
@@ -1725,15 +1840,13 @@ pub const Blob = struct {
return ZigString.init(buf).toValueGC(global);
},
.transfer => {
- this.detached = true;
- this.ptr_allocator = null;
- this.ptr = null;
- this.size = 0;
- this.cap = 0;
- return ZigString.init(buf).toExternalValue(global);
+ var store = this.store.?;
+ this.transfer();
+ return ZigString.init(buf).external(global, store, Store.external);
},
.share => {
- return ZigString.init(buf).toValue(global);
+ this.store.?.ref();
+ return ZigString.init(buf).external(global, this.store.?, Store.external);
},
}
}
@@ -1752,7 +1865,11 @@ pub const Blob = struct {
return ZigString.toExternalU16(external.ptr, external.len, global).parseJSON(global);
}
- return ZigString.init(buf).toValue(global).parseJSON(global);
+ return ZigString.init(buf).external(
+ global,
+ this.store.?,
+ Store.external,
+ ).parseJSON(global);
}
pub fn toArrayBuffer(this: *Blob, global: *JSGlobalObject, comptime lifetime: Lifetime) JSValue {
var view_ = this.sharedView();
@@ -1768,38 +1885,69 @@ pub const Blob = struct {
return JSC.ArrayBuffer.fromBytes(clone, .ArrayBuffer).toJS(global.ref(), null);
},
.share => {
- return JSC.ArrayBuffer.fromBytes(view_.ptr, .ArrayBuffer).toJS(global.ref(), null);
+ this.store.?.ref();
+ return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext(
+ global.ref(),
+ this.store.?,
+ JSC.BlobArrayBuffer_deallocator,
+ null,
+ );
},
.transfer => {
- this.detached = true;
- this.ptr_allocator = null;
- this.ptr = null;
- this.size = 0;
- this.cap = 0;
- return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJS(global.ref(), null);
+ var store = this.store.?;
+ this.transfer();
+ return JSC.ArrayBuffer.fromBytes(bun.constStrToU8(view_), .ArrayBuffer).toJSWithContext(
+ global.ref(),
+ store,
+ JSC.BlobArrayBuffer_deallocator,
+ null,
+ );
},
}
}
- pub fn fromJS(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ pub inline fn fromJS(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob {
+ if (comptime move) {
+ return fromJSMove(global, arg);
+ } else {
+ return fromJSClone(global, arg);
+ }
+ }
+
+ pub inline fn fromJSMove(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ return fromJSWithoutDeferGC(global, arg, true);
+ }
+
+ pub inline fn fromJSClone(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ return fromJSWithoutDeferGC(global, arg, false);
+ }
+
+ fn fromJSMovable(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob {
+ const FromJSFunction = if (comptime move)
+ fromJSMove
+ else
+ fromJSClone;
const DeferCtx = struct {
- args: std.meta.ArgsTuple(@TypeOf(Blob.fromJSWithoutDeferGC)),
+ args: std.meta.ArgsTuple(@TypeOf(FromJSFunction)),
ret: anyerror!Blob = undefined,
pub fn run(ctx: ?*anyopaque) callconv(.C) void {
var that = bun.cast(*@This(), ctx.?);
- that.ret = @call(.{}, Blob.fromJSWithoutDeferGC, that.args);
+ that.ret = @call(.{}, FromJSFunction, that.args);
}
};
var ctx = DeferCtx{
- .args = .{ global, arg },
+ .args = .{
+ global,
+ arg,
+ },
.ret = undefined,
};
JSC.VirtualMachine.vm.global.vm().deferGC(&ctx, DeferCtx.run);
return ctx.ret;
}
- fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue) anyerror!Blob {
+ fn fromJSWithoutDeferGC(global: *JSGlobalObject, arg: JSValue, comptime move: bool) anyerror!Blob {
var current = arg;
if (current.isUndefinedOrNull()) {
return Blob{ .globalThis = global };
@@ -1814,19 +1962,13 @@ pub const Blob = struct {
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;
}
- return Blob{
- .size = sliced.len,
- .ptr = bun.constStrToU8(sliced.ptr[0..sliced.len]).ptr,
- .cap = sliced.len,
- .ptr_allocator = bun.default_allocator,
- .allocator = null,
- .globalThis = global,
- };
+ return Blob.init(bun.constStrToU8(sliced.slice()), bun.default_allocator, global);
},
JSC.JSValue.JSType.ArrayBuffer,
@@ -1844,14 +1986,7 @@ pub const Blob = struct {
JSC.JSValue.JSType.DataView,
=> {
var buf = try bun.default_allocator.dupe(u8, current.asArrayBuffer(global).?.slice());
- return Blob{
- .size = buf.len,
- .ptr = buf.ptr,
- .cap = buf.len,
- .ptr_allocator = bun.default_allocator,
- .allocator = null,
- .globalThis = global,
- };
+ return Blob.init(buf, bun.default_allocator, global);
},
else => {
@@ -1860,7 +1995,13 @@ pub const Blob = struct {
switch (data.tag()) {
.Blob => {
var blob: *Blob = data.as(Blob);
- return try blob.dupe(bun.default_allocator);
+ if (comptime move) {
+ var _blob = blob.*;
+ blob.transfer();
+ return _blob;
+ } else {
+ return blob.dupe();
+ }
},
else => return Blob.initEmpty(global),
@@ -2007,14 +2148,7 @@ pub const Blob = struct {
}
var joined = try joiner.done(bun.default_allocator);
- return Blob{
- .ptr_allocator = bun.default_allocator,
- .allocator = null,
- .size = joined.len,
- .ptr = joined.ptr,
- .cap = joined.len,
- .globalThis = global,
- };
+ return Blob.init(joined, bun.default_allocator, global);
}
};
@@ -2038,7 +2172,7 @@ pub const Body = struct {
pub fn clone(this: Body, allocator: std.mem.Allocator) Body {
return Body{
.init = this.init.clone(allocator),
- .value = this.value.clone(allocator) catch Value.empty,
+ .value = this.value.clone(allocator),
};
}
@@ -2054,7 +2188,7 @@ pub const Body = struct {
if (this.init.headers) |headers| {
try formatter.writeIndent(Writer, writer);
try writer.writeAll("headers: ");
- try headers.writeFormat(formatter, writer, comptime enable_ansi_colors);
+ try headers.leak().writeFormat(formatter, writer, comptime enable_ansi_colors);
try writer.writeAll("\n");
}
@@ -2065,14 +2199,14 @@ pub const Body = struct {
pub fn deinit(this: *Body, _: std.mem.Allocator) void {
if (this.init.headers) |headers| {
- headers.deinit();
+ headers.deref();
}
this.value.deinit();
}
pub const Init = struct {
- headers: ?*Headers = null,
+ headers: ?*Headers.RefCountedHeaders = null,
status_code: u16,
method: Method = Method.GET,
@@ -2080,9 +2214,11 @@ pub const Body = struct {
var that = this;
var headers = this.headers;
if (headers) |head| {
- headers.?.allocator = allocator;
- var new_headers = allocator.create(Headers) catch unreachable;
- head.clone(new_headers) catch unreachable;
+ headers.?.value.allocator = allocator;
+ var new_headers = allocator.create(Headers.RefCountedHeaders) catch unreachable;
+ new_headers.allocator = allocator;
+ new_headers.count = 1;
+ head.leak().clone(&new_headers.value) catch unreachable;
that.headers = new_headers;
}
@@ -2106,8 +2242,7 @@ pub const Body = struct {
switch (js.JSValueGetType(ctx, header_prop)) {
js.JSType.kJSTypeObject => {
if (try Headers.JS.headersInit(ctx, header_prop)) |headers| {
- result.headers = try allocator.create(Headers);
- result.headers.?.* = headers;
+ result.headers = try Headers.RefCountedHeaders.init(headers, allocator);
}
},
else => {},
@@ -2194,9 +2329,9 @@ pub const Body = struct {
}
}
- pub fn clone(this: Value, allocator: std.mem.Allocator) !Value {
+ pub fn clone(this: Value, _: std.mem.Allocator) Value {
if (this == .Blob) {
- return Value{ .Blob = try this.Blob.dupe(allocator) };
+ return Value{ .Blob = this.Blob.dupe() };
}
return Value{ .Empty = .{} };
@@ -2266,7 +2401,7 @@ pub const Body = struct {
}
body.value = .{
- .Blob = Blob.fromJS(ctx.ptr(), value) catch {
+ .Blob = Blob.fromJS(ctx.ptr(), value, true) catch {
JSC.JSError(allocator, "Out of memory", .{}, ctx, exception);
return body;
},
@@ -2279,25 +2414,25 @@ pub const Body = struct {
// https://developer.mozilla.org/en-US/docs/Web/API/Request
pub const Request = struct {
url: ZigString = ZigString.Empty,
- headers: ?*Headers = null,
+ headers: ?*Headers.RefCountedHeaders = null,
body: Body.Value = Body.Value{ .Empty = .{} },
method: Method = Method.GET,
pub fn fromRequestContext(ctx: *RequestContext) !Request {
- var headers_ = bun.default_allocator.create(Headers) catch unreachable;
var req = Request{
.url = ZigString.init(std.mem.span(ctx.getFullURL())),
.body = Body.Value.empty,
.method = ctx.method,
- .headers = headers_,
+ .headers = try Headers.RefCountedHeaders.init(Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable, bun.default_allocator),
};
- headers_.* = Headers.fromRequestCtx(bun.default_allocator, ctx) catch unreachable;
req.url.mark();
return req;
}
pub fn mimeType(this: *const Request) string {
- if (this.headers) |headers| {
+ if (this.headers) |headers_ref| {
+ var headers = headers_ref.get();
+ defer headers_ref.deref();
// Remember, we always lowercase it
// hopefully doesn't matter here tho
if (headers.getHeaderIndex("content-type")) |content_type| {
@@ -2413,21 +2548,7 @@ pub const Request = struct {
) js.JSValueRef {
return js.JSValueMakeString(ctx, ZigString.init("").toValueGC(ctx.ptr()).asRef());
}
- pub fn getHeaders(
- this: *Request,
- ctx: js.JSContextRef,
- _: js.JSObjectRef,
- _: js.JSStringRef,
- _: js.ExceptionRef,
- ) js.JSValueRef {
- if (this.headers == null) {
- var headers = getAllocator(ctx).create(Headers) catch unreachable;
- headers.* = Headers.empty(getAllocator(ctx));
- this.headers = headers;
- }
- return Headers.Class.make(ctx, this.headers.?);
- }
pub fn getIntegrity(
_: *Request,
ctx: js.JSContextRef,
@@ -2468,6 +2589,10 @@ pub const Request = struct {
}
pub fn finalize(this: *Request) void {
+ if (this.headers) |headers| {
+ headers.deref();
+ }
+
bun.default_allocator.destroy(this);
}
@@ -2487,7 +2612,8 @@ pub const Request = struct {
_: js.JSStringRef,
_: js.ExceptionRef,
) js.JSValueRef {
- if (this.headers) |headers| {
+ if (this.headers) |headers_ref| {
+ var headers = headers_ref.leak();
if (headers.getHeaderIndex("referrer")) |i| {
return ZigString.init(headers.asStr(headers.entries.get(i).value)).toValueGC(ctx.ptr()).asObjectRef();
}
@@ -2536,7 +2662,7 @@ pub const Request = struct {
}
if (JSC.JSValue.fromRef(arguments[1]).get(ctx.ptr(), "body")) |body_| {
- if (Blob.fromJS(ctx.ptr(), body_) catch null) |blob| {
+ if (Blob.fromJS(ctx.ptr(), body_, true) catch null) |blob| {
request.body = Body.Value{ .Blob = blob };
}
}
@@ -2575,14 +2701,28 @@ pub const Request = struct {
return Request.Class.make(ctx, cloned);
}
+ pub fn getHeaders(
+ this: *Request,
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ if (this.headers == null) {
+ this.headers = Headers.RefCountedHeaders.init(Headers.empty(bun.default_allocator), bun.default_allocator) catch unreachable;
+ }
+
+ return Headers.Class.make(ctx, this.headers.?.getRef());
+ }
+
pub fn cloneInto(this: *const Request, req: *Request, allocator: std.mem.Allocator) void {
req.* = Request{
- .body = this.body.clone(allocator) catch unreachable,
+ .body = this.body.clone(allocator),
.url = ZigString.init(allocator.dupe(u8, this.url.slice()) catch unreachable),
};
if (this.headers) |head| {
- var new_headers = allocator.create(Headers) catch unreachable;
- head.clone(new_headers) catch unreachable;
+ var new_headers = Headers.RefCountedHeaders.init(undefined, allocator) catch unreachable;
+ head.leak().clone(&new_headers.value) catch unreachable;
req.headers = new_headers;
}
}
@@ -2642,6 +2782,7 @@ fn BlobInterface(comptime Type: type) type {
var blob = this.body.use();
var ptr = getAllocator(ctx).create(Blob) catch unreachable;
ptr.* = blob;
+ blob.allocator = getAllocator(ctx);
return JSC.JSPromise.resolvedPromiseValue(ctx.ptr(), JSValue.fromRef(Blob.Class.make(ctx, ptr))).asObjectRef();
}
};
@@ -2751,14 +2892,9 @@ pub const FetchEvent = struct {
}
if (this.pending_promise) |promise| {
- var status = promise.status(globalThis.vm());
-
- while (status == .Pending) {
- VirtualMachine.vm.tick();
- status = promise.status(globalThis.vm());
- }
+ VirtualMachine.vm.event_loop.waitForPromise(promise);
- switch (status) {
+ switch (promise.status(ctx.ptr().vm())) {
.Fulfilled => {},
else => {
this.rejected = true;
@@ -2811,7 +2947,9 @@ pub const FetchEvent = struct {
defer this.pending_promise = null;
var needs_mime_type = true;
var content_length: ?usize = null;
- if (response.body.init.headers) |headers| {
+ if (response.body.init.headers) |headers_ref| {
+ var headers = headers_ref.get();
+ defer headers_ref.deref();
this.request_context.clearHeaders() catch {};
var i: usize = 0;
while (i < headers.entries.len) : (i += 1) {
@@ -2826,6 +2964,15 @@ pub const FetchEvent = struct {
continue;
}
+ // Some headers need to be managed by bun
+ if (strings.eqlComptime(name, "transfer-encoding") or
+ strings.eqlComptime(name, "content-encoding") or
+ strings.eqlComptime(name, "strict-transport-security") or
+ strings.eqlComptime(name, "content-security-policy"))
+ {
+ continue;
+ }
+
this.request_context.appendHeaderSlow(
name,
headers.asStr(header.value),
@@ -2838,7 +2985,7 @@ pub const FetchEvent = struct {
}
var blob = response.body.value.use();
- // defer blob.deinit();
+ defer blob.deinit();
const content_length_ = content_length orelse blob.size;
diff --git a/src/ref_count.zig b/src/ref_count.zig
new file mode 100644
index 000000000..2c72aaeb5
--- /dev/null
+++ b/src/ref_count.zig
@@ -0,0 +1,82 @@
+const std = @import("std");
+
+pub const RefCountedSlice = struct {};
+
+pub fn RefCount(comptime Type: type, comptime deinit_on_zero: bool) type {
+ return struct {
+ const AllocatorType = if (deinit_on_zero) std.mem.Allocator else void;
+
+ value: Type,
+ count: i32 = 1,
+ allocator: AllocatorType = undefined,
+
+ pub inline fn ref(this: *@This()) void {
+ this.count += 1;
+ }
+
+ /// Create a new reference counted value.
+ pub inline fn init(
+ value: Type,
+ allocator: std.mem.Allocator,
+ ) !*@This() {
+ var ptr = try allocator.create(@This());
+ ptr.create(value, allocator);
+ return ptr;
+ }
+
+ /// Get the value & increment the reference count.
+ pub inline fn get(this: *@This()) *Type {
+ std.debug.assert(this.count >= 0);
+
+ this.count += 1;
+ return this.leak();
+ }
+
+ /// Get the value without incrementing the reference count.
+ pub inline fn leak(this: *@This()) *Type {
+ return &this.value;
+ }
+
+ pub inline fn getRef(this: *@This()) *@This() {
+ this.count += 1;
+ return this;
+ }
+
+ pub inline fn create(
+ this: *@This(),
+ value: Type,
+ allocator: AllocatorType,
+ ) void {
+ this.* = .{
+ .value = value,
+ .allocator = allocator,
+ .count = 1,
+ };
+ }
+
+ pub inline fn deinit(this: *@This()) void {
+ if (comptime @hasDecl(Type, "deinit")) {
+ this.value.deinit();
+ }
+
+ if (comptime deinit_on_zero) {
+ var allocator = this.allocator;
+ allocator.destroy(this);
+ }
+ }
+
+ pub inline fn deref(this: *@This()) void {
+ this.count -= 1;
+
+ std.debug.assert(this.count >= 0);
+
+ if (comptime deinit_on_zero) {
+ if (this.count <= 0) {
+ this.deinit();
+ }
+ }
+ }
+
+ pub const Type = Type;
+ };
+}
diff --git a/src/string_immutable.zig b/src/string_immutable.zig
index a5e3e7104..7e9895a42 100644
--- a/src/string_immutable.zig
+++ b/src/string_immutable.zig
@@ -709,6 +709,31 @@ pub inline fn copyU8IntoU16(output_: []u16, input_: []const u8) void {
}
}
+pub inline fn appendUTF8MachineWordToUTF16MachineWordUnaligned(comptime alignment: u21, output: *align(alignment) [@sizeOf(usize) / 2]u16, input: *const [@sizeOf(usize) / 2]u8) void {
+ comptime var i: usize = 0;
+ inline while (i < @sizeOf(usize) / 2) : (i += 1) {
+ output[i] = input[i];
+ }
+}
+
+pub fn copyU8IntoU16WithAlignment(comptime alignment: u21, output_: []align(alignment) u16, input_: []const u8) void {
+ var output = output_;
+ var input = input_;
+ const word = @sizeOf(usize) / 2;
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(input.len <= output.len);
+ }
+ while (input.len >= word) {
+ appendUTF8MachineWordToUTF16MachineWordUnaligned(alignment, output[0..word], input[0..word]);
+ output = output[word..];
+ input = input[word..];
+ }
+
+ for (input) |c, i| {
+ output[i] = c;
+ }
+}
+
// pub inline fn copy(output_: []u8, input_: []const u8) void {
// var output = output_;
// var input = input_;