Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Segfault with hook "simple get" cache slot and minimal JIT #15834

Open
nielsdos opened this issue Sep 11, 2024 · 0 comments · May be fixed by #17909
Open

Segfault with hook "simple get" cache slot and minimal JIT #15834

nielsdos opened this issue Sep 11, 2024 · 0 comments · May be fixed by #17909

Comments

@nielsdos
Copy link
Member

Description

Originally posted in #15819 (comment)

The following code:

<?php
class A {
    private $_prop;
    public $prop {
        get => $this->_prop;
    }
}
for ($i=0;$i<2;$i++)
    echo (new A)->prop;

using opcache.jit=1101

Resulted in this output:

Segfault

But I expected this output instead:

No segfault

I analysed this and this is a different bug related to a cache slot optimization.
As far as I understand, this happens for when the cache slot satisfies the ZEND_IS_PROPERTY_HOOK_SIMPLE_GET condition. Then we set up a function call frame and re-enter the VM to execute the hook function:

php-src/Zend/zend_vm_def.h

Lines 2094 to 2126 in 7c2204c

} else if (EXPECTED(ZEND_IS_PROPERTY_HOOK_SIMPLE_GET(prop_offset))) {
zend_function *hook = prop_info->hooks[ZEND_PROPERTY_HOOK_GET];
ZEND_ASSERT(hook->type == ZEND_USER_FUNCTION);
ZEND_ASSERT(RUN_TIME_CACHE(&hook->op_array));
uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_HAS_THIS;
if (OP1_TYPE & IS_CV) {
GC_ADDREF(zobj);
}
if (OP1_TYPE & (IS_CV|IS_VAR|IS_TMP_VAR)) {
call_info |= ZEND_CALL_RELEASE_THIS;
}
zend_execute_data *call = zend_vm_stack_push_call_frame(call_info, hook, 0, zobj);
call->prev_execute_data = execute_data;
call->call = NULL;
call->return_value = EX_VAR(opline->result.var);
call->run_time_cache = RUN_TIME_CACHE(&hook->op_array);
execute_data = call;
EG(current_execute_data) = execute_data;
zend_init_cvs(0, hook->op_array.last_var EXECUTE_DATA_CC);
#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID))
opline = hook->op_array.opcodes;
#else
EX(opline) = hook->op_array.opcodes;
#endif
LOAD_OPLINE_EX();
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_BEGIN(execute_data);
ZEND_VM_ENTER_EX();
}

This seems incompatible with how the minimal JIT works, getting the property will be skipped.
Indeed if we get rid of ZEND_SET_PROPERTY_HOOK_SIMPLE_GET in zend_object_handlers.c or go to a higher optimization level the problem disappears. I'm not sure yet how to solve that.

PHP Version

master

Operating System

Linux

nielsdos added a commit to nielsdos/php-src that referenced this issue Feb 23, 2025
…al JIT

The FETCH_OBJ_R VM handler has an optimization that directly enters into
a hook if it is a simpler getter hook. This is not compatible with the
minimal JIT because the minimal JIT will try to continue executing the
opcodes after the FETCH_OBJ_R.
To solve this, we check whether the opcode is still the expected one
after the execution of the VM handler. If it is not, we know that we are
going to execute a simple hook. In that case, exit to the VM.
nielsdos added a commit to nielsdos/php-src that referenced this issue Feb 27, 2025
…al JIT

The FETCH_OBJ_R VM handler has an optimization that directly enters into
a hook if it is a simpler getter hook. This is not compatible with the
minimal JIT because the minimal JIT will try to continue executing the
opcodes after the FETCH_OBJ_R.
To solve this, we check whether the opcode is still the expected one
after the execution of the VM handler. If it is not, we know that we are
going to execute a simple hook. In that case, exit to the VM.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant