Skip to content

fix(build): don't force -fuse-ld=lld on macOS#30871

Open
shguddn8591 wants to merge 7 commits into
oven-sh:mainfrom
shguddn8591:claude/fix-macos-lld-linker
Open

fix(build): don't force -fuse-ld=lld on macOS#30871
shguddn8591 wants to merge 7 commits into
oven-sh:mainfrom
shguddn8591:claude/fix-macos-lld-linker

Conversation

@shguddn8591

Copy link
Copy Markdown

What does this PR do?

Prevents the build system from forcing -fuse-ld=lld on macOS.

On macOS, Homebrew's llvm@21 clang++ does not support the -fuse-ld=lld flag, which caused the following error when running bun run rust:check or building debug binaries:

clang++: error: invalid linker name in argument '-fuse-ld=lld'

This change makes the LLD linker flag only apply to non-Windows, non-Darwin platforms.

How did you verify your code works?

  • Reproduced the linker error on macOS with llvm@21 installed.
  • Identified the root cause in scripts/build/rust.ts:489.
  • Applied the fix to skip -fuse-ld=lld on Darwin.
  • Confirmed the change is minimal and only affects the Rust flag generation logic.
  • The fix follows the existing pattern used for other platform-specific linker decisions in the same file.

Closes #30870

shguddn8591 and others added 3 commits May 16, 2026 00:04
## Summary
Refactored `FFICallbackFunctionWrapper` to inherit from `WTF::ThreadSafeRefCounted` for safe cross-thread memory management.
Guarded `JSFunction` execution in `FFI_Callback_threadsafe_call` by taking a protected `Ref` to prevent use-after-free crashes when JSC garbage collection runs concurrently with native thread callbacks.
Fixed a debug-build assertion failure (`isASCII`) in `initializeInternalModuleFromDisk` caused by non-ASCII characters in `BUN_DYNAMIC_JS_LOAD_PATH` by using `WTF::String::fromUTF8`  instead of `ASCIILiteral`.
Added a high-stress test case simulating native thread callback execution with forced `Bun.gc(true)` to ensure thread safety.

## Test plan
`bun bd test test/js/bun/ffi/cc.test.ts` passes reliably under high callback volume and forced garbage collection.
Homebrew's llvm@21 clang++ does not support -fuse-ld=lld on macOS.
This caused 'invalid linker name' errors when running rust:check or debug builds.

Only apply the LLD flag on non-Windows, non-Darwin platforms.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Refactors FFI callback handling to a threadsafe refcounted wrapper capturing execution context, adds a skipped threadsafe JSCallback test exercising pthread callbacks with interleaved GC, omits the lld linker flag for Darwin in the Rust build, fixes an internal module path UTF-8 construction, and replaces AGENTS.md content with a reference to CLAUDE.md.

Changes

FFI Callback Thread-Safety

Layer / File(s) Summary
Thread-safe wrapper refactor and context capture
src/jsc/bindings/JSFFIFunction.cpp
FFICallbackFunctionWrapper becomes WTF::ThreadSafeRefCounted<FFICallbackFunctionWrapper> with a create() factory and contextIdentifier() accessor. Bun__createFFICallbackFunction uses leakRef(). FFI_Callback_threadsafe_call captures the wrapper's context identifier and holds a Ref-protected wrapper across the async task boundary.
Threadsafe callback test with GC interleaving
test/js/bun/ffi/cc.test.ts
Adds a skipped test that compiles an inline pthread C harness, creates a JSCallback with threadsafe: true, spawns a detached native thread invoking the callback repeatedly, and runs GC/yield loops on the JS side while asserting ordered invocation count.

Build Flags and Internal Module Loading

Layer / File(s) Summary
Darwin linker exclusion and module path string fix
scripts/build/rust.ts, src/jsc/bindings/InternalModuleRegistry.cpp
Rust build now excludes Darwin targets from emitting -Clink-arg=-fuse-ld=lld. InternalModuleRegistry now constructs BUN_DYNAMIC_JS_LOAD_PATH prefix using WTF::String::fromUTF8 instead of ASCIILiteral::fromLiteralUnsafe.

Developer Documentation

Layer / File(s) Summary
AGENTS.md reference to CLAUDE.md
AGENTS.md
AGENTS.md content replaced with a single-line reference to CLAUDE.md (previous detailed guide content removed).
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR contains multiple changes beyond the scope of issue #30870: AGENTS.md documentation update, FFICallbackFunctionWrapper refactoring in JSFFIFunction.cpp, InternalModuleRegistry.cpp UTF-8 conversion, and new FFI callback threading tests. These are unrelated to the macOS linker flag fix. Separate the macOS linker fix (scripts/build/rust.ts) into its own PR, and create a separate PR for the FFI threading safety improvements and related test changes to maintain clear change scope.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: preventing -fuse-ld=lld from being forced on macOS, which directly addresses the primary objective of the pull request.
Description check ✅ Passed The description follows the required template with both 'What does this PR do?' and 'How did you verify your code works?' sections completed with comprehensive details about the fix and verification steps.
Linked Issues check ✅ Passed The main code change in scripts/build/rust.ts prevents -fuse-ld=lld from being forced on macOS/Darwin, directly addressing issue #30870's root cause and expected behavior. However, the PR includes additional unrelated changes to other files (FFI, tests, AGENTS.md) that go beyond fixing the linked issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Jarred-Sumner Jarred-Sumner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The fix looks real but can you undo the AGENTS.md change? Most of the coding products follow symlinks.

@shguddn8591

Copy link
Copy Markdown
Author

@Jarred-Sumner I have fulfilled your request. Sorry for the late correction.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/build/rust.ts (1)

484-491: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Update comment to document Darwin exclusion.

The comment at lines 484-488 explains why Windows is excluded from the -fuse-ld=lld flag, but the code now also excludes Darwin (macOS). The comment should be updated to document both exclusions for future maintainability.

📝 Proposed comment update
   // Not on Windows: the per-target linker there is `link.exe` / `lld-link.exe`
   // (see `CARGO_TARGET_*_LINKER` below), which take `/X` args, not the GCC/clang
   // `-fuse-ld=`. RUSTFLAGS only reach *target* crates when `--target` is given,
   // and the `bun_bin` staticlib has no link step, so it's normally dead — but
   // if a target cdylib ever appears it'd fail with "could not open '-fuse-ld=lld'".
+  // Not on Darwin: macOS uses the system ld64 linker by default, and Homebrew's
+  // clang++ (llvm@21+) rejects the `-fuse-ld=lld` flag.
   if (!cfg.windows && !cfg.darwin) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build/rust.ts` around lines 484 - 491, The comment explaining why we
avoid adding `-fuse-ld=lld` only mentions Windows; update it to also document
the Darwin exclusion by referencing the `cfg.windows` and `cfg.darwin` checks
around the `rustflags.push(`-Clink-arg=-fuse-ld=lld`)` call so future readers
understand both Windows and macOS are excluded (mentioning that macOS's
linker/requirements differ and thus we skip adding `-fuse-ld=lld` on Darwin in
the same way we skip it on Windows).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/jsc/bindings/JSFFIFunction.cpp`:
- Around line 56-61: The constructor FFICallbackFunctionWrapper currently
dereferences globalObject->scriptExecutionContext() when initializing
m_contextIdentifier; add a defensive null check (or assert) for
globalObject->scriptExecutionContext() before using it so we don't crash when it
is nullptr—e.g., in the FFICallbackFunctionWrapper(JSC::JSFunction* function,
Zig::GlobalObject* globalObject) initializer or body, verify
scriptExecutionContext() is non-null and only call identifier() when valid (or
set m_contextIdentifier to a safe default/invalid value and log/assert) to match
the null-checking pattern used elsewhere (see uses in JSAbortSignal.cpp,
JSCallbackData.cpp, JSPerformanceObserver.cpp).

---

Outside diff comments:
In `@scripts/build/rust.ts`:
- Around line 484-491: The comment explaining why we avoid adding `-fuse-ld=lld`
only mentions Windows; update it to also document the Darwin exclusion by
referencing the `cfg.windows` and `cfg.darwin` checks around the
`rustflags.push(`-Clink-arg=-fuse-ld=lld`)` call so future readers understand
both Windows and macOS are excluded (mentioning that macOS's linker/requirements
differ and thus we skip adding `-fuse-ld=lld` on Darwin in the same way we skip
it on Windows).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8985a2a1-65fc-4bbd-ab8b-a1e6cec7835f

📥 Commits

Reviewing files that changed from the base of the PR and between ad68af8 and 08d9cb2.

📒 Files selected for processing (6)
  • AGENTS.md
  • AGENTS.md
  • scripts/build/rust.ts
  • src/jsc/bindings/InternalModuleRegistry.cpp
  • src/jsc/bindings/JSFFIFunction.cpp
  • test/js/bun/ffi/cc.test.ts

Comment on lines 56 to 61
FFICallbackFunctionWrapper(JSC::JSFunction* function, Zig::GlobalObject* globalObject)
: m_function(globalObject->vm(), function)
, globalObject(globalObject->vm(), globalObject)
, m_contextIdentifier(globalObject->scriptExecutionContext()->identifier())
{
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if scriptExecutionContext() can return nullptr in GlobalObject implementations
rg -nC3 'scriptExecutionContext\(\)' --type cpp | head -80

Repository: oven-sh/bun

Length of output: 5768


🏁 Script executed:

# Find Zig::GlobalObject implementation and scriptExecutionContext() definition
fd -t f 'GlobalObject\|Zig' --type h --type cpp | head -30

Repository: oven-sh/bun

Length of output: 224


🏁 Script executed:

# Search for Zig::GlobalObject class definition
rg -n 'class\s+GlobalObject' --type cpp --type h | head -20

Repository: oven-sh/bun

Length of output: 1263


🏁 Script executed:

# Look for scriptExecutionContext() implementation in GlobalObject
rg -nB2 -A5 'GlobalObject.*scriptExecutionContext\(\)' --type cpp --type h

Repository: oven-sh/bun

Length of output: 17952


🏁 Script executed:

# Check what calls FFICallbackFunctionWrapper constructor
rg -n 'FFICallbackFunctionWrapper' --type cpp | head -20

Repository: oven-sh/bun

Length of output: 2756


Add null check for scriptExecutionContext() on line 59.

The constructor dereferences globalObject->scriptExecutionContext()->identifier() without null checking. The method returns a raw pointer that can be nullptr, as evidenced by the widespread null-checking pattern throughout the codebase (JSAbortSignal.cpp, JSCallbackData.cpp, JSPerformanceObserver.cpp, and others all defensively check this pointer). Add a null check or assert to prevent a potential crash during FFICallbackFunctionWrapper construction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/jsc/bindings/JSFFIFunction.cpp` around lines 56 - 61, The constructor
FFICallbackFunctionWrapper currently dereferences
globalObject->scriptExecutionContext() when initializing m_contextIdentifier;
add a defensive null check (or assert) for
globalObject->scriptExecutionContext() before using it so we don't crash when it
is nullptr—e.g., in the FFICallbackFunctionWrapper(JSC::JSFunction* function,
Zig::GlobalObject* globalObject) initializer or body, verify
scriptExecutionContext() is non-null and only call identifier() when valid (or
set m_contextIdentifier to a safe default/invalid value and log/assert) to match
the null-checking pattern used elsewhere (see uses in JSAbortSignal.cpp,
JSCallbackData.cpp, JSPerformanceObserver.cpp).

@shguddn8591 shguddn8591 requested a review from Jarred-Sumner May 18, 2026 03:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS: bun run rust:check fails with "invalid linker name in argument '-fuse-ld=lld'"

2 participants