Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/bun_build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,48 @@ jobs:
if: matrix.safe == 'true'
- run: sed -i 's/max_rss = 7_800_000_000/max_rss = 10_000_000_000/' zig/build.zig
if: matrix.safe == 'true'
- name: Fetch mimalloc
uses: actions/checkout@v4
with:
repository: oven-sh/mimalloc
ref: bun-dev3-v2
path: mimalloc
- name: Splice mimalloc into bootstrap build (linux-musl)
# musl's malloc has a single global rwlock; with N parallel LLVM
# contexts (--llvm-codegen-threads=N) every `operator new` serialises
# on it. mimalloc has per-thread heaps. Compile its unity-build
# static.c (with MI_OVERRIDE so it replaces malloc/free) for the
# target after the host zig exists, then link the object into the
# final cross-compiled zig. Inserted just before the final
# `$ZIG build` (the `cd "$ROOTDIR/zig"` line) so $ZIG is available.
if: contains(matrix.target, 'linux-musl')
run: |
sed -i '/^cd "\$ROOTDIR\/zig"$/i \
$ZIG cc -c "$ROOTDIR/mimalloc/src/static.c" \\\
-I "$ROOTDIR/mimalloc/include" \\\
-target $TARGET -mcpu=$MCPU -O2 -fno-builtin -DNDEBUG -Wno-date-time \\\
-DMI_MALLOC_OVERRIDE=1 -DMI_LIBC_MUSL=1 -DMI_STATIC_LIB \\\
-o "$ROOTDIR/out/mimalloc-$TARGET-$MCPU.o"\
' build
sed -i 's#-Dversion-string="$ZIG_VERSION"#-Dversion-string="$ZIG_VERSION" -Dmimalloc-obj="$ROOTDIR/out/mimalloc-$TARGET-$MCPU.o"#' build
- name: Splice mimalloc into bootstrap build (windows-gnu)
# Windows CRT routes operator new -> HeapAlloc(GetProcessHeap()),
# which is guarded by a single critical section — same parallel-emit
# serialisation as musl. malloc/free can't be statically interposed
# on Windows, but C++ operator new/delete are replaceable per the
# standard, and LLVM's hot allocations go through them. Compile the
# override TU as C++ so the global operators are emitted; mimalloc
# itself is compiled-in via #include of its unity-build static.c.
if: contains(matrix.target, 'windows-gnu')
run: |
sed -i '/^cd "\$ROOTDIR\/zig"$/i \
$ZIG c++ -c "$ROOTDIR/zig/tools/mimalloc_new_delete_override.cpp" \\\
-I "$ROOTDIR/mimalloc/include" -I "$ROOTDIR/mimalloc" \\\
-target $TARGET -mcpu=$MCPU -std=c++17 -O2 -fno-builtin \\\
-DNDEBUG -Wno-date-time -DMI_STATIC_LIB \\\
-o "$ROOTDIR/out/mimalloc-$TARGET-$MCPU.o"\
' build
sed -i 's#-Dversion-string="$ZIG_VERSION"#-Dversion-string="$ZIG_VERSION" -Dmimalloc-obj="$ROOTDIR/out/mimalloc-$TARGET-$MCPU.o"#' build
- run: cat build
- name: Cache host toolchain
uses: actions/cache@v4
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ zig-out/
# Although this was renamed to .zig-cache, let's leave it here for a few
# releases to make it less annoying to work with multiple branches.
zig-cache/
bun-cache/
30 changes: 29 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub fn build(b: *std.Build) !void {
const skip_macos = b.option(bool, "skip-macos", "Main test suite skips targets with macos OS") orelse false;
const skip_linux = b.option(bool, "skip-linux", "Main test suite skips targets with linux OS") orelse false;
const skip_llvm = b.option(bool, "skip-llvm", "Main test suite skips targets that use LLVM backend") orelse false;
const llvm_codegen_threads = b.option(u32, "llvm-codegen-threads", "Number of LLVM codegen threads to use for module tests") orelse 0;

const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;

Expand All @@ -123,6 +124,19 @@ pub fn build(b: *std.Build) !void {
"llvm-has-xtensa",
"Whether LLVM has the experimental target xtensa enabled",
) orelse false;
const llvm_has_polly = b.option(
bool,
"llvm-has-polly",
"Whether LLVM was built with Polly and requires linking it",
) orelse false;
const mimalloc_obj = b.option(
[]const u8,
"mimalloc-obj",
"Path to a mimalloc static.c object built with MI_OVERRIDE; linked " ++
"into the compiler so libc malloc (musl's single-lock allocator " ++
"in static builds) is replaced. LLVM emit at high codegen-thread " ++
"counts otherwise serialises on the malloc lock.",
);
const enable_ios_sdk = b.option(bool, "enable-ios-sdk", "Run tests requiring presence of iOS SDK and frameworks") orelse false;
const enable_macos_sdk = b.option(bool, "enable-macos-sdk", "Run tests requiring presence of macOS SDK and frameworks") orelse enable_ios_sdk;
const enable_symlinks_windows = b.option(bool, "enable-symlinks-windows", "Run tests requiring presence of symlinks on Windows") orelse false;
Expand Down Expand Up @@ -202,6 +216,7 @@ pub fn build(b: *std.Build) !void {
});
exe.pie = pie;
exe.entitlements = entitlements;
if (mimalloc_obj) |p| exe.addObjectFile(.{ .cwd_relative = p });

const use_llvm = b.option(bool, "use-llvm", "Use the llvm backend");
exe.use_llvm = use_llvm;
Expand Down Expand Up @@ -332,6 +347,7 @@ pub fn build(b: *std.Build) !void {
.llvm_has_csky = llvm_has_csky,
.llvm_has_arc = llvm_has_arc,
.llvm_has_xtensa = llvm_has_xtensa,
.llvm_has_polly = llvm_has_polly,
});
}
if (target.result.os.tag == .windows) {
Expand Down Expand Up @@ -461,6 +477,7 @@ pub fn build(b: *std.Build) !void {
.skip_linux = skip_linux,
.skip_llvm = skip_llvm,
.skip_libc = skip_libc,
.llvm_codegen_threads = llvm_codegen_threads,
// 3888779264 was observed on an x86_64-linux-gnu host.
.max_rss = 4000000000,
}));
Expand All @@ -483,6 +500,7 @@ pub fn build(b: *std.Build) !void {
.skip_linux = skip_linux,
.skip_llvm = skip_llvm,
.skip_libc = skip_libc,
.llvm_codegen_threads = llvm_codegen_threads,
}));

test_modules_step.dependOn(tests.addModuleTests(b, .{
Expand Down Expand Up @@ -545,6 +563,7 @@ pub fn build(b: *std.Build) !void {
.skip_linux = skip_linux,
.skip_llvm = skip_llvm,
.skip_libc = skip_libc,
.llvm_codegen_threads = llvm_codegen_threads,
// I observed a value of 5605064704 on the M2 CI.
.max_rss = 6165571174,
}));
Expand Down Expand Up @@ -739,7 +758,7 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu
fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Step.Compile {
const exe = b.addExecutable(.{
.name = "zig",
.max_rss = 10_000_000_000,
.max_rss = 11_000_000_000,
.root_module = addCompilerMod(b, options),
});
exe.stack_size = stack_size;
Expand Down Expand Up @@ -858,6 +877,7 @@ fn addStaticLlvmOptionsToModule(mod: *std.Build.Module, options: struct {
llvm_has_csky: bool,
llvm_has_arc: bool,
llvm_has_xtensa: bool,
llvm_has_polly: bool,
}) !void {
// Adds the Zig C++ sources which both stage1 and stage2 need.
//
Expand Down Expand Up @@ -898,6 +918,10 @@ fn addStaticLlvmOptionsToModule(mod: *std.Build.Module, options: struct {
mod.linkSystemLibrary(lib_name, .{});
};

if (options.llvm_has_polly) for (llvm_libs_polly) |lib_name| {
mod.linkSystemLibrary(lib_name, .{});
};

mod.linkSystemLibrary("z", .{});
mod.linkSystemLibrary("zstd", .{});

Expand Down Expand Up @@ -1419,6 +1443,10 @@ const llvm_libs_xtensa = [_][]const u8{
"LLVMXtensaDesc",
"LLVMXtensaInfo",
};
const llvm_libs_polly = [_][]const u8{
"Polly",
"PollyISL",
};

fn generateLangRef(b: *std.Build) std.Build.LazyPath {
const doctest_exe = b.addExecutable(.{
Expand Down
41 changes: 40 additions & 1 deletion lib/std/Build/Step/Compile.zig
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,16 @@ dead_strip_dylibs: bool = false,

/// Number of threads to use for LLVM backend code generation.
/// 0 means single-threaded (default). > 1 enables parallel codegen.
/// When enabled, outputs multiple .o files: filename.0.o, filename.1.o, etc.
/// When enabled, outputs multiple object files: filename.0.o, filename.1.o, etc.
/// (or .obj on COFF targets).
llvm_codegen_threads: u32 = 0,

/// Skip the relocatable -r merge of partitioned LLVM output. The shard
/// objects are emitted directly to `{emit}.{i}.o` (or `.obj` on COFF) for
/// the downstream linker to consume. Only meaningful when
/// `llvm_codegen_threads > 1`.
llvm_no_merge_shards: bool = false,

/// Skip linker step for build-obj - outputs raw LLVM object file(s).
/// Saves time by avoiding parse/resolve/write cycle.
no_link_obj: bool = false,
Expand Down Expand Up @@ -893,6 +900,35 @@ pub fn getEmittedBin(compile: *Compile) LazyPath {
return compile.getEmittedFileGeneric(&compile.generated_bin);
}

/// Returns the per-shard object paths when `llvm_no_merge_shards` is set.
/// Shard `i` lives at `{dir}/{stem}.{i}{ext}` where `dir` is the emitted-bin
/// directory, `stem` is `out_filename` with the target's object extension
/// stripped, and `ext` is that extension (`.o` for ELF/Mach-O, `.obj` for
/// COFF). The returned slice has `llvm_codegen_threads` entries, allocated
/// from the build arena.
///
/// Intended use: `addObject` is configured with `llvm_codegen_threads > 1`
/// and `llvm_no_merge_shards = true`; the consumer (an executable's link
/// step, or `addInstallFile`) iterates this slice instead of calling
/// `getEmittedBin()` (which points at a stub the compiler deletes).
pub fn getEmittedBinShards(compile: *Compile) []std.Build.LazyPath {
assert(compile.llvm_no_merge_shards);
assert(compile.llvm_codegen_threads > 1);
const b = compile.step.owner;
const dir = compile.getEmittedBinDirectory();
const target = compile.rootModuleTarget();
const obj_ext = target.ofmt.fileExt(target.cpu.arch);
const stem = if (std.mem.endsWith(u8, compile.out_filename, obj_ext))
compile.out_filename[0 .. compile.out_filename.len - obj_ext.len]
else
compile.out_filename;
const out = b.allocator.alloc(std.Build.LazyPath, compile.llvm_codegen_threads) catch @panic("OOM");
for (out, 0..) |*p, i| {
p.* = dir.path(b, b.fmt("{s}.{d}{s}", .{ stem, i, obj_ext }));
}
return out;
}

/// Returns the path to the generated import library.
/// This function can only be called for libraries.
pub fn getEmittedImplib(compile: *Compile) LazyPath {
Expand Down Expand Up @@ -1532,6 +1568,9 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
if (compile.llvm_codegen_threads > 0) {
try zig_args.append(b.fmt("--llvm-codegen-threads={d}", .{compile.llvm_codegen_threads}));
}
if (compile.llvm_no_merge_shards) {
try zig_args.append("--llvm-no-merge-shards");
}
if (compile.no_link_obj) {
try zig_args.append("--no-link");
}
Expand Down
8 changes: 6 additions & 2 deletions lib/std/Thread/Condition.zig
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,14 @@ pub fn broadcast(self: *Condition) void {
self.impl.wake(.all);
}

// FutexImpl is used everywhere, including Windows. WindowsImpl wraps the
// kernel CONDITION_VARIABLE which has no userspace "no waiters" fast-path —
// every wake() is a kernel32 call. Under heavily-signalled condvars (e.g.
// the compiler's per-job work_queue_cond.signal()) this dominates wall time
// at high thread counts. FutexImpl checks `wakeable == 0` in userspace
// first; on Windows the underlying Futex maps to RtlWaitOnAddress (Win8+).
const Impl = if (builtin.single_threaded)
SingleThreadedImpl
else if (builtin.os.tag == .windows)
WindowsImpl
else
FutexImpl;
Comment on lines 116 to 119

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

WindowsImpl looks unreachable after this selection change.

Since Impl no longer selects WindowsImpl, consider removing it (or clearly parking it behind an explicit fallback gate) to reduce maintenance and avoid bit-rot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/std/Thread/Condition.zig` around lines 116 - 119, The conditional alias
Impl now only chooses SingleThreadedImpl or FutexImpl, leaving WindowsImpl
unreachable; remove WindowsImpl or gate it behind an explicit fallback/feature
flag. Update the alias selection logic referencing Impl and the concrete types
SingleThreadedImpl, FutexImpl, and WindowsImpl: either delete the WindowsImpl
definition and any uses, or add a clear conditional branch (e.g., if
(builtin.windows or feature flag) then WindowsImpl else ...) so WindowsImpl is
intentionally reachable and documented. Ensure any tests or references to
WindowsImpl are updated or removed accordingly.


Expand Down
2 changes: 1 addition & 1 deletion lib/std/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11371,7 +11371,7 @@ const private = struct {
extern "c" fn getentropy(buffer: [*]u8, size: usize) c_int;
extern "c" fn arc4random_buf(buf: [*]u8, len: usize) void;

extern "c" fn _msize(?*const anyopaque) usize;
extern "c" fn _msize(?*anyopaque) usize;
extern "c" fn malloc_size(?*const anyopaque) usize;
extern "c" fn malloc_usable_size(?*const anyopaque) usize;
extern "c" fn posix_memalign(memptr: *?*anyopaque, alignment: usize, size: usize) c_int;
Expand Down
8 changes: 6 additions & 2 deletions lib/std/fs/Dir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1309,8 +1309,12 @@ pub fn realpath(self: Dir, pathname: []const u8, out_buffer: []u8) RealPathError
}
if (native_os == .windows) {
if (pathname.len == 1 and pathname[0] == '.') {
const ptr: *[std.fs.max_path_bytes]u8 = out_buffer[0..std.fs.max_path_bytes];
return try std.os.getFdPath(self.fd, ptr);
var buffer: [fs.max_path_bytes]u8 = undefined;
const out_path = try std.os.getFdPath(self.fd, &buffer);
if (out_path.len > out_buffer.len) return error.NameTooLong;
const result = out_buffer[0..out_path.len];
@memcpy(result, out_path);
return result;
}
const pathname_w = try windows.sliceToPrefixedFileW(self.fd, pathname);
return self.realpathW(pathname_w.span(), out_buffer);
Expand Down
4 changes: 2 additions & 2 deletions lib/std/os/linux.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1651,15 +1651,15 @@ fn init_vdso_clock_gettime(clk: clockid_t, ts: *timespec) callconv(.c) usize {
pub fn clock_getres(clk_id: clockid_t, tp: *timespec) usize {
return syscall2(
if (@hasField(SYS, "clock_getres")) .clock_getres else .clock_getres_time64,
@as(usize, @bitCast(@as(isize, clk_id))),
@as(usize, @bitCast(@as(isize, @intFromEnum(clk_id)))),
@intFromPtr(tp),
);
}

pub fn clock_settime(clk_id: clockid_t, tp: *const timespec) usize {
return syscall2(
if (@hasField(SYS, "clock_settime")) .clock_settime else .clock_settime64,
@as(usize, @bitCast(@as(isize, clk_id))),
@as(usize, @bitCast(@as(isize, @intFromEnum(clk_id)))),
@intFromPtr(tp),
);
}
Expand Down
Loading
Loading