RFR: 8360389: Support printing from C2 compiled code

Benoît Maillard bmaillard at openjdk.org
Mon Aug 18 07:07:00 UTC 2025


This PR adds support for printf-style debugging from C2 compiled code. This is implemented as a runtime call to a C++ method that prints the values of the nodes passed as arguments. The runtime C++ method is implemented with the help of variadic templates, as it is expected to support various combinations of argument types.

## Usage

Suppose we have this piece of Java code, that simply computes an arithmetic operation, and
that we compile `square` with C2.

class Square {
    static int square(int a) {
        return a * a;
    }

    public static void main(String[] args) {
        square(9);
    }
}


We would like to inspect the node that contains the value returned in this method.
We can add a call to `Compile::make_debug_print` and pass a message, the IGVN instance, as well as the node(s) that we would like to inspect.

```c++
void Compile::return_values(JVMState* jvms) {
  GraphKit kit(jvms);
  Node* ret = new ReturnNode(TypeFunc::Parms,
                             kit.control(),
                             kit.i_o(),
                             kit.reset_memory(),
                             kit.frameptr(),
                             kit.returnadr());
  // Add zero or 1 return values
  int ret_size = tf()->range()->cnt() - TypeFunc::Parms;

  
  Node* return_value;
  if (ret_size > 0) {
    kit.inc_sp(-ret_size);  // pop the return value(s)
    kit.sync_jvms();
    return_value = kit.argument(0);
    ret->add_req(return_value);

    // <-------------------- Simply insert this
    C->make_debug_print_new<jint>("return:", initial_gvn(), return_value);
  }
  
  // bind it to root
  root()->add_req(ret);
  record_for_igvn(ret);
  initial_gvn()->transform(ret);
}


We can then call run our code with `-XX:CompileCommand="compileonly,Square::square`
and we get the following output:


return:
int 81


This case is of course trivial, but more useful examples are shown later.

## Implementation
## Implementation

The debugging capability is implemented as a runtime call to a C++ printing method. For this, `Compile::make_debug_print` inserts a `CallLeafNode` into the graph and rewires control flow as needed.

The actual printing is handled by the `SharedRuntime::debug_print` method, written with a variadic template to support various argument type combinations. A pointer to this runtime method is obtained at compile time and is passed to the `CallLeafNode` constructor.

The first argument to the runtime printing method is the printing message, a static string encoded as a `ConP` node in the graph. The remaining arguments are the actual nodes being printed.

A key part is wiring the new call into the existing control-flow graph:
- A call node needs a control input and also at least one control usage (otherwise it will be removed).
- We wish the debug print call to be as "close as possible" in the CFG to the nodes being printed, by attaching it at the most appropriate control point.
- To do this, we:
    - Follow the data dependencies of each argument node until reaching control-flow nodes, collecting these as candidates.
    - Pick the candidate that is reachable from all other candidates in the CFG (i.e., all other candidates are predecessors of the one we pick).
- We then rewire the control users of the selected candidate node to use the new call node as their control input. This ensures the call node has a control usage and is not deleted as dead code.

<img width="424" height="257" alt="image" src="https://github.com/user-attachments/assets/228a46ee-7149-4a78-a310-89e3168eb1d1" />

## More examples
Here are other examples that showcase uses of this debug feature at various stages
in the compilation pipeline. Some of them are inspired by issues I worked on recently.

### Identity optimization
#### Modified C2 code
In `ConvD2LNode::Identity`:
```c++
Node* ConvD2LNode::Identity(PhaseGVN* phase) {
  // Remove ConvD2L->ConvL2D->ConvD2L sequences.
  if(in(1)->Opcode() == Op_ConvL2D && in(1)->in(1)->Opcode() == Op_ConvD2L) {
    Node* identity = in(1)->in(1);
    phase->C->make_debug_print_new<jlong>("value: ", phase, identity, phase->C->top());
    return identity;
  }

  return this;
}


#### Java program

class TestSimpleD2L {
    int instanceCount;

    void test(long d) {
        int i = 1;
        int j = 1;
        while (++i < 37) {
            d = i;
            for (; 8 > j; ++j) {
                instanceCount = (int) d;
                d = instanceCount;
            }
        }
    }
    void main(String[] strArr) {
        for (int i = 0; i < 50_000; ++i) {
            test(1);
        }
    }
}



java -XX:CompileCommand=quiet -XX:CompileCommand="compileonly,TestSimpleD2L::test" -XX:-TieredCompilation -Xbatch -Xcomp TestSimpleD2L.java


### Selecting nodes by ID between two optimization phases 
#### Modified C2 code
In `Compile::Optimize`
```c++
  // Set loop opts counter
  if((_loop_opts_cnt > 0) && (has_loops() || has_split_ifs())) {
    {
      TracePhase tp(_t_idealLoop);
      PhaseIdealLoop::optimize(igvn, LoopOptsDefault);
      _loop_opts_cnt--;
      if (major_progress()) print_method(PHASE_PHASEIDEALLOOP1, 2);
      if (failing())  return;
    }

    // <----------------- START
    Node* parm0 = C->root()->find(98);
    Node* parm1 = C->root()->find(108);
    C->make_debug_print_new<jint, jint>("hello there: ", &igvn, parm0, parm1);
    // <----------------- END


#### Java program

public class Loop {
    static int foo(int x) {
        return x % 17;
    }

    static int test(int iterations) {
        int x = 1;
        int y = iterations + 20;
        for (int i = 0; i < iterations; i++) {
            x += foo(x);

            // to print result of foo: id = 98
            // to print x + foo(x):    id = 108
        }
        
        return x + y;
    }

    public static void main(String[] args) {
        int res = test(100);
        System.out.println(res);
    }
}




java -XX:CompileCommand=quiet -XX:CompileCommand="compileonly,Loop::test" -XX:-TieredCompilation -Xcomp -Xbatch -XX:-Inline Loop.java


### Ideal optimization
#### Modified C2 code
In `ModINode::Ideal`:
```c++
  // Check for useless control input
  // Check for excluding mod-zero case
  if (in(0) && (ti->_hi < 0 || ti->_lo > 0)) {
    set_req(0, nullptr);        // Yank control input
    phase->C->make_debug_print_new<jint>("ModI: ", phase, this);
    return this;
  }


#### Java program

public class TestModControlFoldedAfterCCP {
    static void test() {
        int i22, i24 = -1191, i28;
        int iArr1[] = new int[1];
        for (int i = 1;i < 100; i++) {
            for (int j = 4; j > i; j--) {
                i22 = i24;

                // divisor is either -1191 or -13957
                iArr1[0] = 5 % i22;
            }
            for (i28 = i; i28 < 2; ++i28) {
                i24 = -13957;
            }
        }
    }

    public static void main(String[] args) {
        test();
    }
}



java -XX:CompileCommand=quiet -XX:CompileCommand="compileonly,TestModControlFoldedAfterCCP::test" -XX:-TieredCompilation -Xcomp -Xbatch -XX:-Inline TestModControlFoldedAfterCCP.java


## Testing
- [x] [GitHub Actions](https://github.com/benoitmaillard/jdk/actions?query=branch%3AJDK-8360389)
- [x] tier1-3, plus some internal testing

Thank you for reviewing!

-------------

Commit messages:
 - Fix style and add comments
 - Use format consistent with fieldDescriptor::print_on_for
 - Fix bad format specifier
 - Clean up includes
 - Use platform-dependent macros for printing primitives types
 - Remove old versions and rename new one
 - Add null check when finding control candidates
 - Remove unnecessary additions to worklist
 - Fix bug caused by bad rewiring with nodes that have several control inputs
 - Remove --i in DUIterator use
 - ... and 31 more: https://git.openjdk.org/jdk/compare/c70258ca...d7641ea7

Changes: https://git.openjdk.org/jdk/pull/26475/files
  Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=26475&range=00
  Issue: https://bugs.openjdk.org/browse/JDK-8360389
  Stats: 290 lines in 6 files changed: 290 ins; 0 del; 0 mod
  Patch: https://git.openjdk.org/jdk/pull/26475.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/26475/head:pull/26475

PR: https://git.openjdk.org/jdk/pull/26475


More information about the hotspot-dev mailing list