Skip to content

Link, don't just compile, when probing compiler builtins#1052

Closed
hsbt wants to merge 1 commit into
masterfrom
parser-builtin-try-link
Closed

Link, don't just compile, when probing compiler builtins#1052
hsbt wants to merge 1 commit into
masterfrom
parser-builtin-try-link

Conversation

@hsbt

@hsbt hsbt commented Jul 13, 2026

Copy link
Copy Markdown
Member

Building the parser C extension against an official Ruby 3.3 mswin binary (x64-mswin64_140) fails at link time with an unresolved __builtin_clzll:

parser.obj : error LNK2019: unresolved external symbol __builtin_clzll referenced in function json_parse_number
parser.so : fatal error LNK1120: 1 unresolved externals

The root cause is in ext/json/ext/parser/extconf.rb. have_builtin_func probes the builtin with try_compile, which only compiles and never links. On MSVC __builtin_clzll does not exist, so int main() { __builtin_clzll(0); return 0; } merely triggers C4013 ('__builtin_clzll' undefined; assuming extern returning int), a warning by default. The compile succeeds, HAVE_BUILTIN___BUILTIN_CLZLL gets defined, and ffc_nlz_int64 in ext/json/ext/vendor/fast_float_parser.h selects the __builtin_clzll branch, which then fails to link.

The fix is to use try_link instead of try_compile in have_builtin_func. On GCC/Clang the builtin is resolved inline with no external symbol, so linking succeeds and the fast path is kept exactly as before. On MSVC there is no such symbol, so linking fails and json correctly falls back. This is the right answer on every toolchain and does not depend on any warning-as-error flag.

Ruby 3.4+ mswin happens to mask this because ruby/ruby 091c7d4a54 promotes C4013 to an error via -we4013, so the old try_compile probe already fails there. Ruby 3.3 mswin (and any config where C4013 stays a warning) still hits the false positive, and json's CI apparently runs a Ruby that already carries -we4013, which is why it slipped through. RubyInstaller (mingw/gcc) is unaffected because gcc really provides the builtin.

Verification, cl.exe on x64-mswin64_140, using the exact probe source:

compile-only (try_compile) exit = 0   # false positive
link        (try_link)     exit = 2   # LNK2019, correct

With the fix, ruby extconf.rb reports checking for __builtin_clzll()... no and the parser builds and links cleanly, with no __builtin_clzll symbol left in parser.obj.

An alternative, or additional belt-and-suspenders guard, would be to gate the __builtin_clzll branch in fast_float_parser.h on defined(__GNUC__) || defined(__clang__). The try_link change alone fully resolves the issue, so I kept the change minimal and did not touch the header. Adding mswin Ruby 3.3 to the CI matrix would catch this class of regression, but I left that as a maintainer call rather than editing CI here.

have_builtin_func used try_compile, so on MSVC a bare implicit
declaration of __builtin_clzll passes (C4013 is only a warning unless
promoted to an error), HAVE_BUILTIN___BUILTIN_CLZLL gets defined, and
the parser fails to link with LNK2019 for __builtin_clzll. try_link
resolves the builtin inline on GCC/Clang and fails on MSVC, giving the
correct answer on every toolchain regardless of warning flags.
Copilot AI review requested due to automatic review settings July 13, 2026 01:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a Windows (mswin/MSVC) build failure for the JSON parser C extension by making the builtin-function probe actually link, preventing a compile-only false positive that later becomes an unresolved external at link time.

Changes:

  • Switch the have_builtin_func probe from try_compile to try_link so the check validates linkability on MSVC.
  • Ensure HAVE_BUILTIN___BUILTIN_CLZLL is only defined when the builtin is truly usable, allowing correct fallback selection.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@nobu

nobu commented Jul 13, 2026

Copy link
Copy Markdown
Member

Since 13 years ago, Ruby's config.h defines HAVE_BUILTIN___BUILTIN_CLZLL.
Is the check necessary?

@byroot

byroot commented Jul 13, 2026

Copy link
Copy Markdown
Member

Right, let's use HAVE_BUILTIN___BUILTIN_CLZLL

@byroot byroot closed this in #1053 Jul 13, 2026
matzbot pushed a commit to ruby/ruby that referenced this pull request Jul 13, 2026
@byroot

byroot commented Jul 13, 2026

Copy link
Copy Markdown
Member

2.21.1 is out with the fix.

@hsbt

hsbt commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

👌 Thanks!

pull Bot pushed a commit to Mattlk13/json that referenced this pull request Jul 13, 2026
We don't need to perform out own checks.

Fix: ruby#1052
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.

4 participants