Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ PHP NEWS
. Fixed bug GH-24438 (Preserve indirect types in JIT side traces). (aasul)
. Fixed bug GH-24577 (CE-cache flag on interned class name visible before
map_ptr offset). (prateekbhujel)
. Fixed tracing JIT losing register-allocated CVs when an interrupt is
handled at the end of a loop trace. (Levi Morrison)

- PDO:
. Fixed invalid ATTR_ORACLE_NULLS values being accepted and truncated.
Expand Down
17 changes: 16 additions & 1 deletion ext/opcache/jit/zend_jit_trace.c
Original file line number Diff line number Diff line change
Expand Up @@ -3486,6 +3486,20 @@ static int zend_jit_trace_stack_needs_deoptimization(zend_jit_trace_stack *stack
return 0;
}

/* While a trace is being compiled, a register-allocated variable is tracked by
* its IR reference only (STACK_REG() is still ZREG_NONE). */
static bool zend_jit_trace_stack_has_unstored_refs(zend_jit_trace_stack *stack, uint32_t stack_size)
{
uint32_t i;

for (i = 0; i < stack_size; i++) {
if (STACK_REF(stack, i) && !(STACK_FLAGS(stack, i) & (ZREG_LOAD|ZREG_STORE))) {
return 1;
}
}
return 0;
}

static int zend_jit_trace_exit_needs_deoptimization(uint32_t trace_num, uint32_t exit_num)
{
const zend_op *opline = zend_jit_traces[trace_num].exit_info[exit_num].opline;
Expand Down Expand Up @@ -7160,7 +7174,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
if (trace_buffer->stop != ZEND_JIT_TRACE_STOP_RECURSIVE_RET) {
if (!(t->flags & ZEND_JIT_TRACE_USES_INITIAL_IP)
|| (ra
&& zend_jit_trace_stack_needs_deoptimization(stack, op_array->last_var + op_array->T))) {
&& (zend_jit_trace_stack_needs_deoptimization(stack, op_array->last_var + op_array->T)
|| zend_jit_trace_stack_has_unstored_refs(stack, op_array->last_var + op_array->T)))) {
/* Deoptimize to the first instruction of the loop */
uint32_t exit_point = zend_jit_trace_get_exit_point(trace_buffer[1].opline, ZEND_JIT_EXIT_TO_VM);

Expand Down
38 changes: 38 additions & 0 deletions ext/opcache/tests/jit/loop_vm_interrupt_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
Tracing JIT: register-allocated loop variable must survive a VM interrupt raised in the loop body
--DESCRIPTION--
Comparing two VmInterruptComparable objects sets EG(vm_interrupt) without a
PHP function call, so the JIT only notices the interrupt at the end of the
loop. Before handing control to the VM there, it has to write $s back to
memory, or the loop continues with a stale value.

This only shows up if $s is kept in a callee-saved register across the
comparison, which is why it's an int: x86_64 has no callee-saved floating
point registers, so a float would always be spilled to memory first.
--EXTENSIONS--
opcache
zend_test
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=64M
opcache.jit_hot_loop=1
--FILE--
<?php
function sum(array $objs, VmInterruptComparable $t): int {
$s = 0;
foreach ($objs as $o) {
$s += 3;
// Only VmInterruptComparable's compare handler raises the interrupt.
$r = $o == $t;
}
return $s;
}

$objs = array_fill(0, 200, new stdClass);
$objs[150] = new VmInterruptComparable(0);
var_dump(sum($objs, new VmInterruptComparable(0)));
?>
--EXPECT--
int(600)
45 changes: 45 additions & 0 deletions ext/zend_test/object_handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,46 @@ ZEND_METHOD(NumericCastableNoOperations, __construct)
ZVAL_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), n);
}

/* Comparing two VmInterruptComparable objects raises EG(vm_interrupt) without a
* function call. Used by ext/opcache/tests/jit/loop_vm_interrupt_001.phpt. */
static zend_class_entry *vm_interrupt_comparable_ce;
static zend_object_handlers vm_interrupt_comparable_object_handlers;

static zend_object* vm_interrupt_comparable_object_create_ex(zend_class_entry* ce, zend_long l) {
zend_object *obj = zend_objects_new(ce);
object_properties_init(obj, ce);
obj->handlers = &vm_interrupt_comparable_object_handlers;
ZVAL_LONG(OBJ_PROP_NUM(obj, 0), l);
return obj;
}

static zend_object *vm_interrupt_comparable_object_create(zend_class_entry *ce)
{
return vm_interrupt_comparable_object_create_ex(ce, 0);
}

static int vm_interrupt_comparable_compare(zval *op1, zval *op2)
{
ZEND_COMPARE_OBJECTS_FALLBACK(op1, op2);

zend_atomic_bool_store_ex(&EG(vm_interrupt), true);

return ZEND_THREEWAY_COMPARE(
Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op1), 0)),
Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op2), 0)));
}

ZEND_METHOD(VmInterruptComparable, __construct)
{
zend_long l;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(l)
ZEND_PARSE_PARAMETERS_END();

ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l);
}

static zend_class_entry *dimension_handlers_no_ArrayAccess_ce;
static zend_object_handlers dimension_handlers_no_ArrayAccess_object_handlers;

Expand Down Expand Up @@ -302,6 +342,11 @@ void zend_test_object_handlers_init(void)
memcpy(&numeric_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
numeric_castable_no_operation_object_handlers.cast_object = numeric_castable_no_operation_cast_object;

vm_interrupt_comparable_ce = register_class_VmInterruptComparable();
vm_interrupt_comparable_ce->create_object = vm_interrupt_comparable_object_create;
memcpy(&vm_interrupt_comparable_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
vm_interrupt_comparable_object_handlers.compare = vm_interrupt_comparable_compare;

dimension_handlers_no_ArrayAccess_ce = register_class_DimensionHandlersNoArrayAccess();
dimension_handlers_no_ArrayAccess_ce->create_object = dimension_handlers_no_ArrayAccess_object_create;
memcpy(&dimension_handlers_no_ArrayAccess_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
Expand Down
5 changes: 5 additions & 0 deletions ext/zend_test/object_handlers.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ final class NumericCastableNoOperations {
public function __construct(int|float $val) {}
}

final class VmInterruptComparable {
private int $val;
public function __construct(int $val) {}
}

class DimensionHandlersNoArrayAccess {
public bool $read = false;
public bool $write = false;
Expand Down
26 changes: 25 additions & 1 deletion ext/zend_test/object_handlers_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading