Skip to content

A synchronize { yield } on a boxed Mutex answers each caller's block value - #8463

Merged
matz merged 1 commit into
matz:masterfrom
iberianpig:c12-boxed-mutex-synchronize-result
Oct 11, 2026
Merged

matz merged 1 commit into
matz:masterfrom
iberianpig:c12-boxed-mutex-synchronize-result

Conversation

@iberianpig

@iberianpig iberianpig commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

A method that answers synchronize { yield } on a Mutex read out of a Hash (a boxed Mutex) did not compile when its callers' blocks answer values of different types. The method is inlined at each caller, and the type of the synchronize call on a boxed receiver is the type of its block's last expression, here the yield. The yield took the first caller's block type, so the result slot was declared as that type (an sp_StrStrHash * for a Hash block), and another caller's Boolean was assigned into it: gcc stopped with int-conversion. A Mutex in an instance variable compiles, because Mutex#synchronize on a typed receiver already answers a boxed value.

The yield is now typed poly when it is the value of a synchronize block with a receiver, as it already is when it is the value of a block handed to a method the program defines (the block's value goes into the lock frame's result slot, the same position as a yield at the end of a begin/ensure). Each caller then boxes its own block's value. This applies only where the callers' block values differ in type (yield_value_diverges).

Reproducer

class Box
  def initialize = @locks = Hash.new { |h, k| h[k] = Mutex.new }
  def with_lock = @locks["a"].synchronize { yield }
  def make = with_lock { { "a" => "b" } }
  def check = with_lock { ARGV.empty? }
end
p Box.new.make
p Box.new.check

CRuby 4.0.6 prints {"a" => "b"} and true. master 090969e does not build the C: error: assignment to ‘sp_StrStrHash *’ from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion].

spinel diff from master

On master:

spinel diff: link-error
  program: boxed-mutex-synchronize-yield.rb
  ruby:    exit 0
  spinel:  the C did not build

boxed-mutex-synchronize-yield.rb: In function ‘sp_Box_check’:
boxed-mutex-synchronize-yield.rb:3: error: assignment to ‘sp_StrStrHash *’ from ‘int’ makes pointer from integer without a cast [-Werror=int-conversion]
cc1: some warnings being treated as errors
spinel: C compilation failed

On this branch: spinel diff: same.

Generated C: of the 33 programs in test/, packages/*/test/, benchmark/ and examples/ (outside test/reject/) that call synchronize, none gets different C. optcarrot's C is unchanged.

make gate (on this branch merged with current master)

scale-test: boxed Hash store work at 2x the methods is 1.95x (limit 2.20)
scale-test: boxed-receiver alias work at 2x the writes is 1.86x (limit 2.20)
scale-test: instance_eval forwarding work at 2x the wrappers is 1.71x (limit 2.50)
scale-test: work at 4x the program is 4.93x (linear 4.00, limit 5.20)
scale-test: work at 4x the program, compiled to C, is 6.21x (limit 6.90)
scale-test: call-shape work at 4x the units, compiled to C, is 4.14x (linear 4.00, limit 4.50)
Tests: 6772 pass, 0 fail, 0 error
gate: stamp for tree d0642e86d0ab on master 55aa88e9753a; git commit --amend --no-edit adds the Gate: trailer
gate: ALL GREEN
  • New tests have .expected files that match CRuby 4.0 run with --enable-frozen-string-literal
  • Values past 2^31 are marked # spinel: int64 (the test has none)
  • If optcarrot's generated C changed: callgrind numbers, checksum 59662 (its C is unchanged)
  • Depends on: none

Test: test/mutex_boxed_synchronize_yield_values.rb calls one method that answers synchronize { yield } on a Mutex read out of a Hash from callers whose blocks answer a Hash, a Boolean (true and false), nil, an Integer, a String and the Mutex's own locked?, and the same method's second path through a Mutex in an instance variable; the Integer and the Hash are then used as values. It fails to compile on master and passes with SPINEL_GC_STRESS=1.

Not in this PR: a synchronize block whose last expression is not a yield takes one type per block, as before; only a value shared by several inlined callers needed the change.

Summary by CodeRabbit

  • Bug Fixes
    • Improved type inference for values yielded within receiver-based synchronized calls, helping the analyzer handle these values more accurately.
  • Tests
    • Added coverage for synchronized calls returning hashes, booleans, numbers, strings, and combined values, including cases with empty and nonempty names.

…value

A method that runs its block in synchronize on a Mutex read from a Hash
did not compile when its callers' blocks return different types: the
result slot took the first caller's type. Each inlined caller's value has
to be boxed, as it already is for a block handed to a program method.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gate: green tree d0642e8 master 55aa88e (linux-x86_64 gcc-13.3.0) tests 6772/0
@coderabbitai

coderabbitai Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d1d5dd57-2382-482f-9972-670ea5311df5

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc287c and 69b5ea8.


📒 Files selected for processing (3)
  • src/analyze_infer.c
  • test/mutex_boxed_synchronize_yield_values.rb
  • test/mutex_boxed_synchronize_yield_values.rb.expected

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.



📝 Walkthrough

Walkthrough

Inference now treats a divergent yield value flowing into the block argument of a receiver-bearing synchronize call as TY_POLY. A regression test exercises this case with empty and nonempty names.

Changes

Synchronize yield inference

Layer / File(s) Summary
Inference behavior
src/analyze_infer.c
Inference returns TY_POLY for a divergent yield value flowing into the block argument of a receiver-bearing synchronize call.
Regression test
test/mutex_boxed_synchronize_yield_values.rb, test/mutex_boxed_synchronize_yield_values.rb.expected
The test exercises LockTable methods with empty and nonempty names and specifies ten expected output values.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix


Merge Risk: ⚪ Minimal · up to 69b5e

The inference change is narrowly scoped, and the added regression test covers the reported synchronized-yield behavior. No actionable merge-blocking issue is evident.

Pre-merge checks | Passed 4 | Inconclusive 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage Inconclusive Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 1 files. (2 skipped: 1 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: synchronize { yield } on a boxed Mutex now preserves each caller's block value.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 1 files. (2 skipped: 1 unsupported, 1 too large.)


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions github-actions Bot added the gate: verified The head commit's Gate trailer names the tree its merge with master gives label Oct 11, 2026
@matz
matz merged commit 04cf97f into matz:master Oct 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate: verified The head commit's Gate trailer names the tree its merge with master gives

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants