Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c19d83e
Implement method coverage
sferik Sep 14, 2026
c7cf3b8
Do not leave a method coverage counter pending when a define_method c…
sferik Sep 14, 2026
90d08e4
Count method coverage for the initialize of Java subclasses
sferik Sep 14, 2026
34ecb4a
Only build the parser's per-line coverage array when lines are being …
sferik Sep 14, 2026
032c5fc
Use a plain list for a file's method coverage entries
sferik Sep 14, 2026
86e4423
Do the method coverage hand-off from one place per interpreted method…
sferik Sep 15, 2026
15e85ab
Rewrite the method coverage comments in plain, compact English
sferik Sep 15, 2026
664db68
Count method coverage for methods defined by an eval
sferik Sep 16, 2026
107c4ad
Set the line and source span of blocks that follow command arguments
sferik Sep 16, 2026
8ba51d8
Encode the operand of the method coverage instruction
sferik Sep 16, 2026
533a68a
Report whether a line was actually counted
sferik Sep 16, 2026
9f9b455
Keep the unknown span marker out of method coverage keys
sferik Sep 16, 2026
caf18c6
Key a method coverage entry by the name it was defined under
sferik Sep 16, 2026
9894de3
Detach method coverage counters when measurement stops
sferik Sep 16, 2026
b9ee86c
Let the super-forwarding scan skip the method coverage instructions
sferik Sep 16, 2026
a12ddd6
Clear a method coverage counter atomically
sferik Sep 16, 2026
102a76f
Build the coverage result outside the CoverageData lock
sferik Sep 16, 2026
f2756ad
Keep method registration off the lock when methods are not measured
sferik Sep 16, 2026
3f3421c
Read and clear the coverage counts in one step
sferik Sep 16, 2026
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
Prev Previous commit
Next Next commit
Do the method coverage hand-off from one place per interpreted method…
… class

MRI counts a method call from the method entry stored in the VM frame,
so no call path there needs to know about coverage. JRuby frames carry
only the implementation class and name, so the entry's own call methods
are the only place that can pass its counter to the body.

Each arity overload of InterpretedIRMethod and MixedModeIRMethod already
repeated the same preamble (debug output, JIT promotion). Fold that and
the coverage hand-off into a single prepareCall per class, so the
hand-off is invoked from one place instead of once per overload.
  • Loading branch information
sferik committed Sep 15, 2026
commit 86e4423b887b287e1b00651f1b2b7b85b173b27b
Original file line number Diff line number Diff line change
Expand Up @@ -73,118 +73,78 @@ protected void printMethodIR() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args,
Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, args, block);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name,
IRubyObject[] args) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, args, Block.NULL_BLOCK);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, block);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, Block.NULL_BLOCK);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, block);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, Block.NULL_BLOCK);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, arg1, block);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, arg1, Block.NULL_BLOCK);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1, IRubyObject arg2, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, arg1, arg2, block);
}

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1, IRubyObject arg2) {
prepareCall(context);
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, arg1, arg2, Block.NULL_BLOCK);
}

/**
* Work done before every call: debug output, JIT promotion, and the hand-off of this entry's Coverage counter.
*/
private void prepareCall(ThreadContext context) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

if (callCount >= 0) promoteToFullBuild(context);

ensureInstrsReady();
return Interpreter.INTERPRET_METHOD(context, method, clazz, self, name, arg0, arg1, arg2, Block.NULL_BLOCK);
}

protected void doDebug() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,7 @@ protected void printMethodIR() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args,
Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
prepareCall(context);

DynamicMethod jittedMethod = actualMethod;
if (jittedMethod != null) {
Expand Down Expand Up @@ -132,12 +127,7 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
prepareCall(context);

DynamicMethod jittedMethod = actualMethod;
if (jittedMethod != null) {
Expand Down Expand Up @@ -170,12 +160,7 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
prepareCall(context);

DynamicMethod jittedMethod = actualMethod;
if (jittedMethod != null) {
Expand Down Expand Up @@ -208,12 +193,7 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
prepareCall(context);

DynamicMethod jittedMethod = actualMethod;
if (jittedMethod != null) {
Expand Down Expand Up @@ -246,12 +226,7 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i
@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0,
IRubyObject arg1, IRubyObject arg2, Block block) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
prepareCall(context);

DynamicMethod jittedMethod = actualMethod;
if (jittedMethod != null) {
Expand Down Expand Up @@ -302,6 +277,18 @@ public SplitSuperState<?> splitSuperCall(ThreadContext context, IRubyObject self
return super.splitSuperCall(context, self, clazz, name, args, block, ic);
}

/**
* Work done before every call: debug output, the JIT attempt, and the hand-off of this entry's Coverage counter.
*/
private void prepareCall(ThreadContext context) {
prepareMethodCoverage(context);
if (IRRuntimeHelpers.isDebug()) doDebug();

// try jit before checking actualMethod, so we use jitted version immediately if
// it's ready
if (callCount >= 0) tryJit(context, this, false);
}

private void doDebug() {
// FIXME: This is printing out IRScope CFG but JIT may be active and it might
// not reflect
Expand Down