aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-17 04:15:13 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-17 04:15:13 -0700
commit36a25c358044b0c9a56a06d8246ae2b5098b3ae4 (patch)
treea869888ad4d39e9a5a6dc0b9f7d5e8b1c7528691
parent13b5d9d4de4e9fe897d373a6ed7fdf2c9884c71f (diff)
downloadbun-36a25c358044b0c9a56a06d8246ae2b5098b3ae4.tar.gz
bun-36a25c358044b0c9a56a06d8246ae2b5098b3ae4.tar.zst
bun-36a25c358044b0c9a56a06d8246ae2b5098b3ae4.zip
Fix memory leak in `await new Response(latin1String).arrayBuffer()` and `await Response.json(obj).json()` (#3656)
❯ mem bun --smol response-arrayBuffer.mjs cpu: Apple M1 Max runtime: bun 0.6.15 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------------------------------------------- ----------------------------- new Response().arrayBuffer() (new string each call, latin1) 12.9 µs/iter (625 ns … 4.18 ms) 1 µs 567.17 µs 711.79 µs new Response().arrayBuffer() (new string each call, utf16) 12.85 µs/iter (1.67 µs … 1.56 ms) 2.17 µs 462.75 µs 621.13 µs new Response().arrayBuffer() (existing string, latin1) 6.53 µs/iter (6.21 µs … 7.07 µs) 6.64 µs 7.07 µs 7.07 µs Peak memory usage: 49 MB bun on  jarred/memory-leak-fix took 2s ❯ mem bun response-arrayBuffer.mjs cpu: Apple M1 Max runtime: bun 0.6.15 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------------------------------------------- ----------------------------- new Response().arrayBuffer() (new string each call, latin1) 1.2 µs/iter (1.04 µs … 1.42 µs) 1.22 µs 1.42 µs 1.42 µs new Response().arrayBuffer() (new string each call, utf16) 2.74 µs/iter (2.42 µs … 6.37 µs) 2.68 µs 6.37 µs 6.37 µs new Response().arrayBuffer() (existing string, latin1) 746.37 ns/iter (643.82 ns … 1.04 µs) 776.11 ns 1.04 µs 1.04 µs Peak memory usage: 104 MB bun on  jarred/memory-leak-fix took 2s ❯ mem ~/.bun/bin/bun response-arrayBuffer.mjs cpu: Apple M1 Max runtime: bun 0.6.15 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------------------------------------------- ----------------------------- new Response().arrayBuffer() (new string each call, latin1) 1.69 µs/iter (1.56 µs … 2.1 µs) 1.73 µs 2.1 µs 2.1 µs new Response().arrayBuffer() (new string each call, utf16) 2.65 µs/iter (2.47 µs … 3.17 µs) 2.69 µs 3.17 µs 3.17 µs new Response().arrayBuffer() (existing string, latin1) 667.67 ns/iter (547.67 ns … 1.28 µs) 694.21 ns 1.28 µs 1.28 µs Peak memory usage: 2735 MB bun on  jarred/memory-leak-fix took 2s ❯ mem ~/.bun/bin/bun --smol response-arrayBuffer.mjs cpu: Apple M1 Max runtime: bun 0.6.15 (arm64-darwin) benchmark time (avg) (min … max) p75 p99 p995 --------------------------------------------------------------------------------------------------- ----------------------------- new Response().arrayBuffer() (new string each call, latin1) 13.51 µs/iter (541 ns … 3.2 ms) 1.92 µs 553.42 µs 709.92 µs new Response().arrayBuffer() (new string each call, utf16) 13.07 µs/iter (1.71 µs … 3.43 ms) 2.13 µs 451.21 µs 651.67 µs new Response().arrayBuffer() (existing string, latin1) 6.25 µs/iter (5.79 µs … 6.81 µs) 6.4 µs 6.81 µs 6.81 µs Peak memory usage: 292 MB Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--bench/snippets/response-arrayBuffer.mjs136
-rw-r--r--bench/snippets/response-json.mjs123
-rw-r--r--src/bun.js/api/JSTranspiler.zig18
-rw-r--r--src/bun.js/base.zig4
-rw-r--r--src/bun.js/bindings/BunString.cpp15
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp70
-rw-r--r--src/bun.js/bindings/bindings.cpp4
-rw-r--r--src/bun.js/bindings/bindings.zig2
-rw-r--r--src/bun.js/bindings/exports.zig6
-rw-r--r--src/bun.js/bindings/headers.h2
-rw-r--r--src/bun.js/bindings/headers.zig2
-rw-r--r--src/bun.js/javascript.zig7
-rw-r--r--src/bun.js/test/pretty_format.zig6
-rw-r--r--src/bun.js/webcore/blob.zig4
-rw-r--r--src/bun.js/webcore/response.zig21
-rw-r--r--src/string.zig43
16 files changed, 361 insertions, 102 deletions
diff --git a/bench/snippets/response-arrayBuffer.mjs b/bench/snippets/response-arrayBuffer.mjs
new file mode 100644
index 000000000..a3b1f0a73
--- /dev/null
+++ b/bench/snippets/response-arrayBuffer.mjs
@@ -0,0 +1,136 @@
+// This snippet mostly exists to reproduce a memory leak
+//
+import { bench, run } from "mitata";
+
+const obj = {
+ "id": 1296269,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
+ "name": "Hello-World",
+ "full_name": "octocat/Hello-World",
+ "owner": {
+ "login": "octocat",
+ "id": 1,
+ "node_id": "MDQ6VXNlcjE=",
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false,
+ },
+ "private": false,
+ "html_url": "https://github.com/octocat/Hello-World",
+ "description": "This your first repo!",
+ "fork": false,
+ "url": "https://api.github.com/repos/octocat/Hello-World",
+ "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
+ "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
+ "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
+ "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
+ "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
+ "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
+ "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
+ "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
+ "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
+ "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
+ "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
+ "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
+ "events_url": "https://api.github.com/repos/octocat/Hello-World/events",
+ "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
+ "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
+ "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
+ "git_url": "git:github.com/octocat/Hello-World.git",
+ "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
+ "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
+ "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
+ "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
+ "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
+ "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
+ "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
+ "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
+ "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
+ "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
+ "ssh_url": "git@github.com:octocat/Hello-World.git",
+ "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
+ "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
+ "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
+ "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
+ "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
+ "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
+ "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
+ "clone_url": "https://github.com/octocat/Hello-World.git",
+ "mirror_url": "git:git.example.com/octocat/Hello-World",
+ "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
+ "svn_url": "https://svn.github.com/octocat/Hello-World",
+ "homepage": "https://github.com",
+ "language": null,
+ "forks_count": 9,
+ "stargazers_count": 80,
+ "watchers_count": 80,
+ "size": 108,
+ "default_branch": "master",
+ "open_issues_count": 0,
+ "is_template": false,
+ "topics": ["octocat", "atom", "electron", "api"],
+ "has_issues": true,
+ "has_projects": true,
+ "has_wiki": true,
+ "has_pages": false,
+ "has_downloads": true,
+ "has_discussions": false,
+ "archived": false,
+ "disabled": false,
+ "visibility": "public",
+ "pushed_at": "2011-01-26T19:06:43Z",
+ "created_at": "2011-01-26T19:01:12Z",
+ "updated_at": "2011-01-26T19:14:43Z",
+ "permissions": {
+ "admin": false,
+ "push": false,
+ "pull": true,
+ },
+ "security_and_analysis": {
+ "advanced_security": {
+ "status": "enabled",
+ },
+ "secret_scanning": {
+ "status": "enabled",
+ },
+ "secret_scanning_push_protection": {
+ "status": "disabled",
+ },
+ },
+};
+
+// Force the string to be 8bit
+const str = String.fromCharCode(
+ ...JSON.stringify(obj)
+ .split("")
+ .map(a => a.charCodeAt(0)),
+);
+var i = 0;
+
+bench("new Response().arrayBuffer() (new string each call, latin1)", async () => {
+ return await new Response(str + i++).arrayBuffer();
+});
+
+bench("new Response().arrayBuffer() (new string each call, utf16)", async () => {
+ return await new Response(str + i++ + "😊").arrayBuffer();
+});
+
+bench("new Response().arrayBuffer() (existing string, latin1)", async () => {
+ return await new Response(str).arrayBuffer();
+});
+
+await run();
diff --git a/bench/snippets/response-json.mjs b/bench/snippets/response-json.mjs
new file mode 100644
index 000000000..dd28203f0
--- /dev/null
+++ b/bench/snippets/response-json.mjs
@@ -0,0 +1,123 @@
+// This snippet mostly exists to reproduce a memory leak
+import { bench, run } from "mitata";
+
+const obj = {
+ "id": 1296269,
+ "node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
+ "name": "Hello-World",
+ "full_name": "octocat/Hello-World",
+ "owner": {
+ "login": "octocat",
+ "id": 1,
+ "node_id": "MDQ6VXNlcjE=",
+ "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/octocat",
+ "html_url": "https://github.com/octocat",
+ "followers_url": "https://api.github.com/users/octocat/followers",
+ "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+ "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+ "organizations_url": "https://api.github.com/users/octocat/orgs",
+ "repos_url": "https://api.github.com/users/octocat/repos",
+ "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/octocat/received_events",
+ "type": "User",
+ "site_admin": false,
+ },
+ "private": false,
+ "html_url": "https://github.com/octocat/Hello-World",
+ "description": "This your first repo!",
+ "fork": false,
+ "url": "https://api.github.com/repos/octocat/Hello-World",
+ "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
+ "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
+ "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
+ "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
+ "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
+ "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
+ "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
+ "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
+ "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
+ "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
+ "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
+ "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
+ "events_url": "https://api.github.com/repos/octocat/Hello-World/events",
+ "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
+ "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
+ "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
+ "git_url": "git:github.com/octocat/Hello-World.git",
+ "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
+ "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
+ "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
+ "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
+ "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
+ "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
+ "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
+ "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
+ "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
+ "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
+ "ssh_url": "git@github.com:octocat/Hello-World.git",
+ "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
+ "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
+ "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
+ "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
+ "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
+ "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
+ "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
+ "clone_url": "https://github.com/octocat/Hello-World.git",
+ "mirror_url": "git:git.example.com/octocat/Hello-World",
+ "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
+ "svn_url": "https://svn.github.com/octocat/Hello-World",
+ "homepage": "https://github.com",
+ "language": null,
+ "forks_count": 9,
+ "stargazers_count": 80,
+ "watchers_count": 80,
+ "size": 108,
+ "default_branch": "master",
+ "open_issues_count": 0,
+ "is_template": false,
+ "topics": ["octocat", "atom", "electron", "api"],
+ "has_issues": true,
+ "has_projects": true,
+ "has_wiki": true,
+ "has_pages": false,
+ "has_downloads": true,
+ "has_discussions": false,
+ "archived": false,
+ "disabled": false,
+ "visibility": "public",
+ "pushed_at": "2011-01-26T19:06:43Z",
+ "created_at": "2011-01-26T19:01:12Z",
+ "updated_at": "2011-01-26T19:14:43Z",
+ "permissions": {
+ "admin": false,
+ "push": false,
+ "pull": true,
+ },
+ "security_and_analysis": {
+ "advanced_security": {
+ "status": "enabled",
+ },
+ "secret_scanning": {
+ "status": "enabled",
+ },
+ "secret_scanning_push_protection": {
+ "status": "disabled",
+ },
+ },
+};
+
+bench("Response.json(obj)", async () => {
+ return Response.json(obj);
+});
+
+bench("Response.json(obj).json()", async () => {
+ return await Response.json(obj).json();
+});
+
+await run();
diff --git a/src/bun.js/api/JSTranspiler.zig b/src/bun.js/api/JSTranspiler.zig
index 308738abf..95b0eeaae 100644
--- a/src/bun.js/api/JSTranspiler.zig
+++ b/src/bun.js/api/JSTranspiler.zig
@@ -442,7 +442,8 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
tsconfig: {
if (tsconfig.isUndefinedOrNull()) break :tsconfig;
const kind = tsconfig.jsType();
- var out = JSC.ZigString.init("");
+ var out = bun.String.empty;
+ defer out.deref();
if (kind.isArray()) {
JSC.throwInvalidArguments("tsconfig must be a string or object", .{}, globalObject, exception);
@@ -452,11 +453,11 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
if (!kind.isStringLike()) {
tsconfig.jsonStringify(globalThis, 0, &out);
} else {
- tsconfig.toZigString(&out, globalThis);
+ out = tsconfig.toBunString(globalThis);
}
- if (out.len == 0) break :tsconfig;
- transpiler.tsconfig_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+ if (out.isEmpty()) break :tsconfig;
+ transpiler.tsconfig_buf = out.toOwnedSlice(allocator) catch @panic("OOM");
// TODO: JSC -> Ast conversion
if (TSConfigJSON.parse(
@@ -490,16 +491,17 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
return transpiler;
}
- var out: ZigString = ZigString.init("");
+ var out = bun.String.empty;
+ defer out.deref();
// TODO: write a converter between JSC types and Bun AST types
if (is_object) {
macros.jsonStringify(globalThis, 0, &out);
} else {
- macros.toZigString(&out, globalThis);
+ out = macros.toBunString(globalThis);
}
- if (out.len == 0) break :macros;
- transpiler.macros_buf = std.fmt.allocPrint(allocator, "{}", .{out}) catch unreachable;
+ if (out.isEmpty()) break :macros;
+ transpiler.macros_buf = out.toOwnedSlice(allocator) catch @panic("OOM");
const source = logger.Source.initPathString("macros.json", transpiler.macros_buf);
const json = (VirtualMachine.get().bundler.resolver.caches.json.parseJSON(
&transpiler.log,
diff --git a/src/bun.js/base.zig b/src/bun.js/base.zig
index 4a1249b5d..535c08395 100644
--- a/src/bun.js/base.zig
+++ b/src/bun.js/base.zig
@@ -1854,6 +1854,8 @@ pub const ArrayBuffer = extern struct {
));
}
+ const log = Output.scoped(.ArrayBuffer, false);
+
pub fn toJS(this: ArrayBuffer, ctx: JSC.C.JSContextRef, exception: JSC.C.ExceptionRef) JSC.JSValue {
if (!this.value.isEmpty()) {
return this.value;
@@ -1861,6 +1863,8 @@ pub const ArrayBuffer = extern struct {
// If it's not a mimalloc heap buffer, we're not going to call a deallocator
if (this.len > 0 and !bun.Mimalloc.mi_is_in_heap_region(this.ptr)) {
+ log("toJS but will never free: {d} bytes", .{this.len});
+
if (this.typed_array_type == .ArrayBuffer) {
return JSC.JSValue.fromRef(JSC.C.JSObjectMakeArrayBufferWithBytesNoCopy(
ctx,
diff --git a/src/bun.js/bindings/BunString.cpp b/src/bun.js/bindings/BunString.cpp
index 21541d711..0676dce19 100644
--- a/src/bun.js/bindings/BunString.cpp
+++ b/src/bun.js/bindings/BunString.cpp
@@ -5,6 +5,8 @@
#include "simdutf.h"
#include "wtf/text/ExternalStringImpl.h"
#include "GCDefferalContext.h"
+#include <JavaScriptCore/JSONObject.h>
+
using namespace JSC;
extern "C" bool Bun__WTFStringImpl__hasPrefix(const WTF::StringImpl* impl, const char* bytes, size_t length)
@@ -231,6 +233,19 @@ extern "C" BunString BunString__createExternal(const char* bytes, size_t length,
return { BunStringTag::WTFStringImpl, { .wtf = &impl.leakRef() } };
}
+extern "C" EncodedJSValue BunString__toJSON(
+ JSC::JSGlobalObject* globalObject,
+ BunString* bunString)
+{
+ JSC::JSValue result = JSC::JSONParse(globalObject, Bun::toWTFString(*bunString));
+
+ if (!result) {
+ result = JSC::JSValue(JSC::createSyntaxError(globalObject, "Failed to parse JSON"_s));
+ }
+
+ return JSC::JSValue::encode(result);
+}
+
extern "C" EncodedJSValue BunString__createArray(
JSC::JSGlobalObject* globalObject,
const BunString* ptr, size_t length)
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 7c0e1668b..a5c96ee08 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -249,76 +249,6 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
#endif
JSC::Options::useSetMethods() = true;
- /*
- * The Magic "Use Less RAM" button
- */
- // # With /= 16,184
- // cpu: Apple M1 Max
- // runtime: bun 0.6.10 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 953.74 ns/iter (842.34 ns … 1.11 µs) 1 µs 1.11 µs 1.11 µs
- // crypto.createHash("sha256") 704.35 ns/iter (594.42 ns … 912.4 ns) 754.89 ns 912.4 ns 912.4 ns
- // crypto.createHash("sha1") 608.53 ns/iter (488.66 ns … 724.54 ns) 637.75 ns 724.54 ns 724.54 ns
- // Peak memory usage: 59 MB
- // # With /= 8096
- // cpu: Apple M1 Max
- // runtime: bun 0.6.10 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 821.71 ns/iter (716.88 ns … 1.01 µs) 859.07 ns 1.01 µs 1.01 µs
- // crypto.createHash("sha256") 534.89 ns/iter (439.35 ns … 737.17 ns) 576.15 ns 633.4 ns 737.17 ns
- // crypto.createHash("sha1") 529.1 ns/iter (432.46 ns … 766.94 ns) 566.07 ns 653.13 ns 766.94 ns
- // Peak memory usage: 60 MB
- // # With /= 1024
- // cpu: Apple M1 Max
- // runtime: bun 0.6.10 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 751.13 ns/iter (703.45 ns … 947.73 ns) 754.54 ns 947.73 ns 947.73 ns
- // crypto.createHash("sha256") 501.47 ns/iter (467.19 ns … 580.84 ns) 505.66 ns 572.42 ns 580.84 ns
- // crypto.createHash("sha1") 482.16 ns/iter (452.7 ns … 570.29 ns) 482.74 ns 565.52 ns 570.29 ns
- // Peak memory usage: 169 MB
- // # With /= 256
- // cpu: Apple M1 Max
- // runtime: bun 0.6.10 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 745.25 ns/iter (705.91 ns … 863.73 ns) 747.8 ns 863.73 ns 863.73 ns
- // crypto.createHash("sha256") 506.94 ns/iter (459.27 ns … 692.93 ns) 514.95 ns 611.72 ns 692.93 ns
- // crypto.createHash("sha1") 486.19 ns/iter (456.13 ns … 749.72 ns) 487.5 ns 578.84 ns 749.72 ns
- // Peak memory usage: 270 MB
- // # Unset
- // cpu: Apple M1 Max
- // runtime: bun 0.6.10 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 752.96 ns/iter (705.88 ns … 954.63 ns) 758.78 ns 954.63 ns 954.63 ns
- // crypto.createHash("sha256") 503.72 ns/iter (466.7 ns … 602.83 ns) 507.22 ns 593.72 ns 602.83 ns
- // crypto.createHash("sha1") 484.25 ns/iter (454.55 ns … 555.84 ns) 487.96 ns 553.41 ns 555.84 ns
- // Peak memory usage: 273 MB
- // # Node.js, for comparison
- // cpu: Apple M1 Max
- // runtime: node v20.1.0 (arm64-darwin)
- // benchmark time (avg) (min … max) p75 p99 p995
- // ------------------------------------------------------------------- -----------------------------
- // crypto.createHash("sha512") 1.82 µs/iter (1.77 µs … 2.17 µs) 1.84 µs 2.17 µs 2.17 µs
- // crypto.createHash("sha256") 964 ns/iter (946.02 ns … 1.1 µs) 962.95 ns 1.1 µs 1.1 µs
- // crypto.createHash("sha1") 985.26 ns/iter (956.7 ns … 1.12 µs) 1 µs 1.12 µs 1.12 µs
- // Peak memory usage: 56 MB
- size_t ramSize = WTF::ramSize();
-
- // We originally went with a hardcoded /= 1024 here
- // But if you don't have much memory, that becomes a problem.
- // Instead, we do 65%
- double ramSizeDouble = static_cast<double>(ramSize);
- ramSizeDouble *= 0.65;
- ramSize = static_cast<size_t>(ramSizeDouble);
-
- if (ramSize > 0) {
- JSC::Options::forceRAMSize() = ramSize;
- }
-
if (LIKELY(envc > 0)) {
while (envc--) {
const char* env = (const char*)envp[envc];
diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp
index d311072e4..552da4009 100644
--- a/src/bun.js/bindings/bindings.cpp
+++ b/src/bun.js/bindings/bindings.cpp
@@ -1542,11 +1542,11 @@ void JSC__JSFunction__optimizeSoon(JSC__JSValue JSValue0)
}
void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2,
- ZigString* arg3)
+ BunString* arg3)
{
JSC::JSValue value = JSC::JSValue::decode(JSValue0);
WTF::String str = JSC::JSONStringify(arg1, value, (unsigned)arg2);
- *arg3 = Zig::toZigString(str);
+ *arg3 = Bun::toStringRef(str);
}
unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0)
{
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 7e3fa6d8e..a28bb4297 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -4164,7 +4164,7 @@ pub const JSValue = enum(JSValueReprInt) {
return cppFn("toString", .{ this, globalThis });
}
- pub fn jsonStringify(this: JSValue, globalThis: *JSGlobalObject, indent: u32, out: *ZigString) void {
+ pub fn jsonStringify(this: JSValue, globalThis: *JSGlobalObject, indent: u32, out: *bun.String) void {
return cppFn("jsonStringify", .{ this, globalThis, indent, out });
}
diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig
index 958a7ff20..8fed85c4a 100644
--- a/src/bun.js/bindings/exports.zig
+++ b/src/bun.js/bindings/exports.zig
@@ -2463,9 +2463,11 @@ pub const ZigConsoleClient = struct {
writer.writeAll("{}");
},
.JSON => {
- var str = ZigString.init("");
+ var str = bun.String.empty;
+ defer str.deref();
+
value.jsonStringify(this.globalThis, this.indent, &str);
- this.addForNewLine(str.len);
+ this.addForNewLine(str.length());
if (jsType == JSValue.JSType.JSDate) {
// in the code for printing dates, it never exceeds this amount
var iso_string_buf: [36]u8 = undefined;
diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h
index 8a03d5233..206a7e27c 100644
--- a/src/bun.js/bindings/headers.h
+++ b/src/bun.js/bindings/headers.h
@@ -377,7 +377,7 @@ CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromChar(unsigned char arg0);
CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromDouble(double arg0);
CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromInt64(int64_t arg0);
CPP_DECL JSC__JSValue JSC__JSValue__jsNumberFromU16(uint16_t arg0);
-CPP_DECL void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2, ZigString* arg3);
+CPP_DECL void JSC__JSValue__jsonStringify(JSC__JSValue JSValue0, JSC__JSGlobalObject* arg1, uint32_t arg2, BunString* arg3);
CPP_DECL JSC__JSValue JSC__JSValue__jsTDZValue();
CPP_DECL unsigned char JSC__JSValue__jsType(JSC__JSValue JSValue0);
CPP_DECL JSC__JSValue JSC__JSValue__jsUndefined();
diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig
index 343939cc2..155b7cce4 100644
--- a/src/bun.js/bindings/headers.zig
+++ b/src/bun.js/bindings/headers.zig
@@ -277,7 +277,7 @@ pub extern fn JSC__JSValue__jsNumberFromChar(arg0: u8) JSC__JSValue;
pub extern fn JSC__JSValue__jsNumberFromDouble(arg0: f64) JSC__JSValue;
pub extern fn JSC__JSValue__jsNumberFromInt64(arg0: i64) JSC__JSValue;
pub extern fn JSC__JSValue__jsNumberFromU16(arg0: u16) JSC__JSValue;
-pub extern fn JSC__JSValue__jsonStringify(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u32, arg3: [*c]ZigString) void;
+pub extern fn JSC__JSValue__jsonStringify(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: u32, arg3: [*c]BunString) void;
pub extern fn JSC__JSValue__jsTDZValue(...) JSC__JSValue;
pub extern fn JSC__JSValue__jsType(JSValue0: JSC__JSValue) u8;
pub extern fn JSC__JSValue__jsUndefined(...) JSC__JSValue;
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index cfa791d63..2e6d61952 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -2431,9 +2431,10 @@ pub const VirtualMachine = struct {
add_extra_line = true;
}
} else if (kind.isObject() or kind.isArray()) {
- var zig_str = ZigString.init("");
- value.jsonStringify(this.global, 2, &zig_str);
- try writer.print(comptime Output.prettyFmt(" {s}<d>: <r>{s}<r>\n", allow_ansi_color), .{ field, zig_str });
+ var bun_str = bun.String.empty;
+ defer bun_str.deref();
+ value.jsonStringify(this.global, 2, &bun_str);
+ try writer.print(comptime Output.prettyFmt(" {s}<d>: <r>{any}<r>\n", allow_ansi_color), .{ field, bun_str });
add_extra_line = true;
}
}
diff --git a/src/bun.js/test/pretty_format.zig b/src/bun.js/test/pretty_format.zig
index a6c6aa631..e907dd711 100644
--- a/src/bun.js/test/pretty_format.zig
+++ b/src/bun.js/test/pretty_format.zig
@@ -1403,9 +1403,11 @@ pub const JestPrettyFormat = struct {
writer.writeAll("\n");
},
.JSON => {
- var str = ZigString.init("");
+ var str = bun.String.empty;
+ defer str.deref();
+
value.jsonStringify(this.globalThis, this.indent, &str);
- this.addForNewLine(str.len);
+ this.addForNewLine(str.length());
if (jsType == JSValue.JSType.JSDate) {
// in the code for printing dates, it never exceeds this amount
var iso_string_buf: [36]u8 = undefined;
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 0d6dcbc26..7e0b7f24b 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -3798,7 +3798,7 @@ pub const AnyBlob = union(enum) {
return JSValue.jsNull();
}
- return str.toJS(global).parseJSON(global);
+ return str.toJSForParseJSON(global);
},
}
}
@@ -3867,7 +3867,7 @@ pub const AnyBlob = union(enum) {
this.* = .{ .Blob = .{} };
defer str.deref();
- const out_bytes = str.toUTF8(bun.default_allocator);
+ const out_bytes = str.toUTF8WithoutRef(bun.default_allocator);
if (out_bytes.isAllocated()) {
const value = JSC.ArrayBuffer.fromBytes(
@constCast(out_bytes.slice()),
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig
index e888ffa5a..f27e7f9aa 100644
--- a/src/bun.js/webcore/response.zig
+++ b/src/bun.js/webcore/response.zig
@@ -387,22 +387,23 @@ pub const Response = struct {
const json_value = args.nextEat() orelse JSC.JSValue.zero;
if (@intFromEnum(json_value) != 0) {
- var zig_str = JSC.ZigString.init("");
+ var str = bun.String.empty;
// calling JSON.stringify on an empty string adds extra quotes
// so this is correct
- json_value.jsonStringify(globalThis.ptr(), 0, &zig_str);
+ json_value.jsonStringify(globalThis, 0, &str);
- if (zig_str.len > 0) {
- const allocator = getAllocator(globalThis);
- var zig_str_slice = zig_str.toSlice(allocator);
-
- if (zig_str_slice.isAllocated()) {
+ if (!str.isEmpty()) {
+ if (str.value.WTFStringImpl.toUTF8IfNeeded(bun.default_allocator)) |bytes| {
+ defer str.deref();
response.body.value = .{
- .Blob = Blob.initWithAllASCII(zig_str_slice.mut(), allocator, globalThis.ptr(), false),
+ .InternalBlob = InternalBlob{
+ .bytes = std.ArrayList(u8).fromOwnedSlice(bun.default_allocator, @constCast(bytes.slice())),
+ .was_string = true,
+ },
};
} else {
- response.body.value = .{
- .Blob = Blob.initWithAllASCII(allocator.dupe(u8, zig_str_slice.slice()) catch unreachable, allocator, globalThis.ptr(), true),
+ response.body.value = Body.Value{
+ .WTFStringImpl = str.value.WTFStringImpl,
};
}
}
diff --git a/src/string.zig b/src/string.zig
index 5f107197f..26cd86d8c 100644
--- a/src/string.zig
+++ b/src/string.zig
@@ -128,6 +128,22 @@ pub const WTFStringImplStruct = extern struct {
return .{};
}
+ pub fn toUTF8WithoutRef(this: WTFStringImpl, allocator: std.mem.Allocator) ZigString.Slice {
+ if (this.is8Bit()) {
+ if (bun.strings.toUTF8FromLatin1(allocator, this.latin1Slice()) catch null) |utf8| {
+ return ZigString.Slice.init(allocator, utf8.items);
+ }
+
+ return ZigString.Slice.fromUTF8NeverFree(this.latin1Slice());
+ }
+
+ if (bun.strings.toUTF8Alloc(allocator, this.utf16Slice()) catch null) |utf8| {
+ return ZigString.Slice.init(allocator, utf8);
+ }
+
+ return .{};
+ }
+
pub fn toUTF8IfNeeded(this: WTFStringImpl, allocator: std.mem.Allocator) ?ZigString.Slice {
if (this.is8Bit()) {
if (bun.strings.toUTF8FromLatin1(allocator, this.latin1Slice()) catch null) |utf8| {
@@ -533,6 +549,16 @@ pub const String = extern struct {
return false;
}
+ extern fn BunString__toJSON(
+ globalObject: *bun.JSC.JSGlobalObject,
+ this: *String,
+ ) JSC.JSValue;
+
+ pub fn toJSForParseJSON(self: *String, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding(@src());
+ return BunString__toJSON(globalObject, self);
+ }
+
pub fn encodeInto(self: String, out: []u8, comptime enc: JSC.Node.Encoding) !usize {
if (self.isUTF16()) {
return JSC.WebCore.Encoder.encodeIntoFrom16(self.utf16(), out, enc, true);
@@ -581,6 +607,23 @@ pub const String = extern struct {
return ZigString.Slice.empty;
}
+ /// This is the same as toUTF8, but it doesn't increment the reference count for latin1 strings
+ pub fn toUTF8WithoutRef(this: String, allocator: std.mem.Allocator) ZigString.Slice {
+ if (this.tag == .WTFStringImpl) {
+ return this.value.WTFStringImpl.toUTF8WithoutRef(allocator);
+ }
+
+ if (this.tag == .ZigString) {
+ return this.value.ZigString.toSlice(allocator);
+ }
+
+ if (this.tag == .StaticZigString) {
+ return ZigString.Slice.fromUTF8NeverFree(this.value.StaticZigString.slice());
+ }
+
+ return ZigString.Slice.empty;
+ }
+
pub fn toSlice(this: String, allocator: std.mem.Allocator) SliceWithUnderlyingString {
return SliceWithUnderlyingString{
.utf8 = this.toUTF8(allocator),