diff options
author | 2023-07-17 04:15:13 -0700 | |
---|---|---|
committer | 2023-07-17 04:15:13 -0700 | |
commit | 36a25c358044b0c9a56a06d8246ae2b5098b3ae4 (patch) | |
tree | a869888ad4d39e9a5a6dc0b9f7d5e8b1c7528691 | |
parent | 13b5d9d4de4e9fe897d373a6ed7fdf2c9884c71f (diff) | |
download | bun-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.mjs | 136 | ||||
-rw-r--r-- | bench/snippets/response-json.mjs | 123 | ||||
-rw-r--r-- | src/bun.js/api/JSTranspiler.zig | 18 | ||||
-rw-r--r-- | src/bun.js/base.zig | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/BunString.cpp | 15 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 70 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.cpp | 4 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 6 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/headers.zig | 2 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 7 | ||||
-rw-r--r-- | src/bun.js/test/pretty_format.zig | 6 | ||||
-rw-r--r-- | src/bun.js/webcore/blob.zig | 4 | ||||
-rw-r--r-- | src/bun.js/webcore/response.zig | 21 | ||||
-rw-r--r-- | src/string.zig | 43 |
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), |