Skip to content

JIT: switch on constant null takes default instead of case 0 (opcache.jit=function, optimization_level=0) #24391

Open

Description

@waroad

With the function JIT and the optimizer disabled, switch (null) with enough integer cases jumps to default instead of taking case 0.

<?php
function f() {
    switch (null) {
        case 0: return 1;
        case 1: return 2;
        case 2: return 3;
        case 3: return 4;
        case 4: return 5;
        default: return 6;
    }
}
var_dump(f());
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d opcache.jit=function -d opcache.optimization_level=0 input.php
int(6)

Expected int(1), since null == 0 is true. The interpreter with the same settings but opcache.jit=0 prints int(1).

Found at c65321c and it still reproduces at 5ccc56b. The debug/sanitizer build and the release build both print int(6) with exit code 0, no assertion or crash. With the default opcache.optimization_level the output is int(1), and with 3 cases instead of 5 it is also right (no jump table is emitted).

This looks like the IS_CONST branch of zend_jit_switch in ext/opcache/jit/zend_jit_ir.c: for a constant of the wrong type for SWITCH_LONG/SWITCH_STRING it goes to the default target, while the VM handler continues at opline+1 (the IS_EQUAL chain).

The tracing JIT trips over the same branch. Here switch (0) compiles to SWITCH_STRING with an int constant, the VM continues at opline+1, and the JIT's default target does not match the trace:

<?php
function f() {
    switch (0) {
        case "aa": $b=1;break;
        case "bb": $b=2; break;
        case "cc": $b=3; break;
        default: $b=6;
    }
    return $b;
}
for ($i=0;$i<80;$i++) $r=f(); echo json_encode($r),"\n";
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 -d opcache.jit_buffer_size=64M -d opcache.jit=tracing -d opcache.optimization_level=0 input.php
php: ext/opcache/jit/zend_jit_ir.c:16424: zend_jit_switch: Assertion `target == next_opline' failed.

That is the debug build at c65321c (exit 134; with opcache.jit=0 it prints 6). I did not run this one on a release build or at 5ccc56b.

The trigger is contrived (a literal constant as the switch subject, optimizer off); it is not from a real program.

Found with an LLM-based testing tool; I used Claude to reduce it and look for the cause.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions