diff --git a/build.zig b/build.zig index cea24a8dfe..5602460fba 100644 --- a/build.zig +++ b/build.zig @@ -34,6 +34,7 @@ const BunBuildOptions = struct { enable_asan: bool, enable_fuzzilli: bool, enable_valgrind: bool, + enable_heap_breakdown: bool, use_mimalloc: bool, tracy_callstack_depth: u16, reported_nodejs_version: Version, @@ -84,6 +85,7 @@ const BunBuildOptions = struct { opts.addOption(bool, "enable_asan", this.enable_asan); opts.addOption(bool, "enable_fuzzilli", this.enable_fuzzilli); opts.addOption(bool, "enable_valgrind", this.enable_valgrind); + opts.addOption(bool, "enable_heap_breakdown", this.enable_heap_breakdown); opts.addOption(bool, "use_mimalloc", this.use_mimalloc); opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{f}", .{this.reported_nodejs_version})); opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm); @@ -259,6 +261,7 @@ pub fn build(b: *Build) !void { .enable_asan = b.option(bool, "enable_asan", "Enable asan") orelse false, .enable_fuzzilli = b.option(bool, "enable_fuzzilli", "Enable fuzzilli instrumentation") orelse false, .enable_valgrind = b.option(bool, "enable_valgrind", "Enable valgrind") orelse false, + .enable_heap_breakdown = b.option(bool, "enable_heap_breakdown", "Enable malloc heap breakdown (macOS only)") orelse false, .use_mimalloc = b.option(bool, "use_mimalloc", "Use mimalloc as default allocator") orelse false, .llvm_codegen_threads = b.option(u32, "llvm_codegen_threads", "Number of threads to use for LLVM codegen") orelse 1, }; @@ -494,6 +497,7 @@ fn addMultiCheck( .enable_asan = root_build_options.enable_asan, .enable_valgrind = root_build_options.enable_valgrind, .enable_fuzzilli = root_build_options.enable_fuzzilli, + .enable_heap_breakdown = root_build_options.enable_heap_breakdown, .use_mimalloc = root_build_options.use_mimalloc, .override_no_export_cpp_apis = root_build_options.override_no_export_cpp_apis, }; @@ -609,7 +613,6 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void { obj.no_link_obj = opts.os != .windows and !opts.no_llvm; - if (opts.enable_asan and !enableFastBuild(b)) { if (@hasField(Build.Module, "sanitize_address")) { if (opts.enable_fuzzilli) { diff --git a/cmake/Options.cmake b/cmake/Options.cmake index e54f6db166..766eb69698 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -129,6 +129,13 @@ endif() optionx(ENABLE_FUZZILLI BOOL "If fuzzilli support should be enabled" DEFAULT OFF) +optionx(ENABLE_HEAP_BREAKDOWN BOOL "If malloc heap breakdown support should be enabled (macOS only)" DEFAULT OFF) + +if(ENABLE_HEAP_BREAKDOWN AND NOT APPLE) + message(WARNING "ENABLE_HEAP_BREAKDOWN is only supported on macOS, disabling") + setx(ENABLE_HEAP_BREAKDOWN OFF) +endif() + if(RELEASE AND LINUX AND CI AND NOT ENABLE_ASSERTIONS AND NOT ENABLE_ASAN) set(DEFAULT_LTO ON) else() diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index 74580efaf9..3b44b3ed66 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -697,6 +697,7 @@ register_command( -Denable_asan=$,true,false> -Denable_fuzzilli=$,true,false> -Denable_valgrind=$,true,false> + -Denable_heap_breakdown=$,true,false> -Duse_mimalloc=$,true,false> -Dllvm_codegen_threads=${LLVM_ZIG_CODEGEN_THREADS} -Dversion=${VERSION} diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index aa07c060ec..8d274e49db 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -29,11 +29,11 @@ if(WEBKIT_LOCAL) include_directories( ${WEBKIT_PATH} ${WEBKIT_PATH}/JavaScriptCore/Headers/JavaScriptCore + ${WEBKIT_PATH}/JavaScriptCore/DerivedSources/inspector ${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders + ${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore ${WEBKIT_PATH}/bmalloc/Headers ${WEBKIT_PATH}/WTF/Headers - ${WEBKIT_PATH}/JavaScriptCore/DerivedSources/inspector - ${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore ) endif() diff --git a/package.json b/package.json index 3284a54a31..93b05c42ac 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "build:smol": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=MinSizeRel -B build/release-smol", "build:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DWEBKIT_LOCAL=ON -B build/debug-local", "build:release:local": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DWEBKIT_LOCAL=ON -B build/release-local", + "build:release:local:heap-breakdown": "NO_SCCACHE=1 bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DWEBKIT_LOCAL=ON -DWEBKIT_PATH=$PWD/../WebKit/WebKitBuild/ReleaseHeapBreakdown -DENABLE_HEAP_BREAKDOWN=ON -DENABLE_ASAN=OFF -B build/release-heap-breakdown", "build:release:with_logs": "cmake . -DCMAKE_BUILD_TYPE=Release -DENABLE_LOGS=true -GNinja -Bbuild-release && ninja -Cbuild-release", "build:debug-zig-release": "cmake . -DCMAKE_BUILD_TYPE=Release -DZIG_OPTIMIZE=Debug -GNinja -Bbuild-debug-zig-release && ninja -Cbuild-debug-zig-release", "run:linux": "docker run --rm -v \"$PWD:/root/bun/\" -w /root/bun ghcr.io/oven-sh/bun-development-docker-image", @@ -54,6 +55,7 @@ "jsc:build": "bun ./scripts/build-jsc.ts release", "jsc:build:debug": "bun ./scripts/build-jsc.ts debug", "jsc:build:lto": "bun ./scripts/build-jsc.ts lto", + "jsc:build:heap-breakdown": "bun ./scripts/build-jsc.ts heap-breakdown", "typecheck": "tsc --noEmit && cd test && bun run typecheck", "fmt": "bun run prettier", "fmt:cpp": "bun run clang-format", diff --git a/scripts/build-jsc.ts b/scripts/build-jsc.ts index 4da6617402..d85c2f5f01 100755 --- a/scripts/build-jsc.ts +++ b/scripts/build-jsc.ts @@ -5,12 +5,12 @@ import { arch, platform } from "os"; import { join, resolve } from "path"; // Build configurations -type BuildConfig = "debug" | "release" | "lto"; +type BuildConfig = "debug" | "release" | "lto" | "heap-breakdown"; // Parse command line arguments const args = process.argv.slice(2); const buildConfig: BuildConfig = (args[0] as BuildConfig) || "debug"; -const validConfigs = ["debug", "release", "lto"]; +const validConfigs = ["debug", "release", "lto", "heap-breakdown"]; if (!validConfigs.includes(buildConfig)) { console.error(`Invalid build configuration: ${buildConfig}`); @@ -32,6 +32,7 @@ const WEBKIT_BUILD_DIR = join(WEBKIT_DIR, "WebKitBuild"); const WEBKIT_RELEASE_DIR = join(WEBKIT_BUILD_DIR, "Release"); const WEBKIT_DEBUG_DIR = join(WEBKIT_BUILD_DIR, "Debug"); const WEBKIT_RELEASE_DIR_LTO = join(WEBKIT_BUILD_DIR, "ReleaseLTO"); +const WEBKIT_HEAP_BREAKDOWN_DIR = join(WEBKIT_BUILD_DIR, "ReleaseHeapBreakdown"); // Homebrew prefix detection const HOMEBREW_PREFIX = IS_ARM64 ? "/opt/homebrew/" : "/usr/local/"; @@ -57,6 +58,8 @@ const getBuildDir = (config: BuildConfig) => { return WEBKIT_DEBUG_DIR; case "lto": return WEBKIT_RELEASE_DIR_LTO; + case "heap-breakdown": + return WEBKIT_HEAP_BREAKDOWN_DIR; default: return WEBKIT_RELEASE_DIR; } @@ -122,6 +125,10 @@ const getBuildFlags = (config: BuildConfig) => { flags.push("-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS=-flto=full", "-DCMAKE_CXX_FLAGS=-flto=full"); break; + case "heap-breakdown": + flags.push("-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DENABLE_MALLOC_HEAP_BREAKDOWN=ON"); + break; + default: // release flags.push("-DCMAKE_BUILD_TYPE=RelWithDebInfo"); break; @@ -172,7 +179,7 @@ function runCommand(command: string, args: string[], options: any = {}) { } // Main build function -function buildJSC() { +async function buildJSC() { const buildDir = getBuildDir(buildConfig); const cmakeFlags = getBuildFlags(buildConfig); const env = getBuildEnv(); @@ -198,18 +205,27 @@ function buildJSC() { // Build with CMake console.log("\n🔨 Building JSC..."); - const buildType = buildConfig === "debug" ? "Debug" : buildConfig === "lto" ? "Release" : "RelWithDebInfo"; + const buildType = buildConfig === "debug" ? "Debug" : buildConfig === "lto" ? "Release" : "RelWithDebInfo"; // heap-breakdown uses RelWithDebInfo runCommand("cmake", ["--build", buildDir, "--config", buildType, "--target", "jsc"], { cwd: buildDir, env, }); + // Remove duplicate InspectorProtocolObjects.h to prevent redefinition errors when building Bun + // The file exists in both DerivedSources/inspector and PrivateHeaders/JavaScriptCore, + // and #pragma once doesn't prevent double-inclusion from different paths + const duplicateHeader = join(buildDir, "JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h"); + if (await Bun.file(duplicateHeader).exists()) { + console.log("\n🧹 Removing duplicate InspectorProtocolObjects.h..."); + await Bun.$`rm ${duplicateHeader}`; + } + console.log(`\n✅ JSC build completed successfully!`); console.log(`Build output: ${buildDir}`); } // Entry point if (import.meta.main) { - buildJSC(); + await buildJSC(); } diff --git a/src/bun.js/modules/BunJSCModule.h b/src/bun.js/modules/BunJSCModule.h index 461b04c679..b7d72bad5f 100644 --- a/src/bun.js/modules/BunJSCModule.h +++ b/src/bun.js/modules/BunJSCModule.h @@ -50,7 +50,7 @@ #include #if OS(DARWIN) -#if ASSERT_ENABLED +#if (ASSERT_ENABLED || ENABLE(MALLOC_HEAP_BREAKDOWN)) #if !__has_feature(address_sanitizer) #include #define IS_MALLOC_DEBUGGING_ENABLED 1 @@ -310,13 +310,16 @@ JSC_DEFINE_HOST_FUNCTION(functionMemoryUsageStatistics, Vector> zoneSizes; zoneSizes.reserveInitialCapacity(count); for (unsigned i = 0; i < count; i++) { - auto zone = reinterpret_cast(zones[i]); - if (const char* name = malloc_get_zone_name(zone)) { - malloc_zone_statistics(reinterpret_cast(zones[i]), - &zone_stats); - zoneSizes.append( - std::make_pair(Identifier::fromString(vm, String::fromUTF8(name)), - zone_stats.size_in_use)); + auto* zone = reinterpret_cast(zones[i]); + if (zone) { + if (const char* name = malloc_get_zone_name(zone)) { + auto nameString = String::fromUTF8(name); + malloc_zone_statistics(reinterpret_cast(zones[i]), + &zone_stats); + zoneSizes.append( + std::make_pair(Identifier::fromString(vm, nameString), + zone_stats.size_in_use)); + } } } diff --git a/src/env.zig b/src/env.zig index 528d4ef5a1..0efa3d08a4 100644 --- a/src/env.zig +++ b/src/env.zig @@ -51,6 +51,7 @@ pub const dump_source = isDebug and !isTest; pub const base_path = build_options.base_path; pub const enable_logs = build_options.enable_logs; pub const enable_asan = build_options.enable_asan; +pub const enable_heap_breakdown = build_options.enable_heap_breakdown; pub const enable_fuzzilli = build_options.enable_fuzzilli; pub const codegen_path = build_options.codegen_path; pub const codegen_embed = build_options.codegen_embed; diff --git a/src/heap_breakdown.zig b/src/heap_breakdown.zig index dc20c07efb..63c0ee0acf 100644 --- a/src/heap_breakdown.zig +++ b/src/heap_breakdown.zig @@ -1,6 +1,6 @@ const vm_size_t = usize; -pub const enabled = Environment.allow_assert and Environment.isMac and !Environment.enable_asan; +pub const enabled = Environment.enable_heap_breakdown and Environment.isMac; fn heapLabel(comptime T: type) [:0]const u8 { const base_name = if (comptime bun.meta.hasDecl(T, "heap_label"))