Skip to content

Fix JIT vm_interrupt#21910

Merged
morrisonlevi merged 4 commits intophp:PHP-8.4from
morrisonlevi:observer_jit_vm_interrupt
Apr 30, 2026
Merged

Fix JIT vm_interrupt#21910
morrisonlevi merged 4 commits intophp:PHP-8.4from
morrisonlevi:observer_jit_vm_interrupt

Conversation

@morrisonlevi
Copy link
Copy Markdown
Contributor

@morrisonlevi morrisonlevi commented Apr 29, 2026

We've been observing crashes in our customers' applications at Datadog for months with no clear sign of what is wrong directly from the call stacks. Yesterday we finally got a key insight: it's an interaction with the tracing JIT and VM interrupt. When the interrupt fires, the JIT returns to the VM with a stale caller opline installed on the active callee execute_data, so the interpreter resumes the caller’s call opcode against the wrong frame. It can happen on both x86_64 and aarch64.

The tell-tale sign was that the interpreter was executing one of the fcall family of opcodes where EX(call) is unexpectedly NULL, and this JIT path was the only way that could happen.

This PR contains a reproducer for the issue. It modifies zend_test to set an interrupt function from an observer_begin hoo; this is not strictly necessary, this is just the most reliable way I could make the needed timing to trigger. I've made a jit.Dockerfile to accompany it:

Dockerfile for reproducing the JIT issue
# syntax=docker/dockerfile:1
# Build and run with the fix:
#   docker build -f jit.Dockerfile -t php-src-8.4-jit-repro:fixed .
#   docker run --rm php-src-8.4-jit-repro:fixed
#
# Build and run without the fix:
#   docker build -f jit.Dockerfile --build-arg PHP_COMMIT=45fbe8a35f1cc3fc93f817c00b887b43080efd7a -t php-src-8.4-jit-repro:broken .
#   docker run --rm php-src-8.4-jit-repro:broken
FROM debian:trixie

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
	&& apt-get install -y --no-install-recommends \
		autoconf \
		bison \
		ca-certificates \
		file \
		g++ \
		gcc \
		git \
		libc-dev \
		libxml2-dev \
		make \
		pkg-config \
		re2c \
	&& rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src
ARG PHP_REPO=https://github.com/morrisonlevi/php-src.git
ARG PHP_REF=observer_jit_vm_interrupt
RUN git init php-src \
	&& cd php-src \
	&& git remote add origin "$PHP_REPO" \
	&& git fetch origin "$PHP_REF" \
	&& git merge-base --is-ancestor "45fbe8a35f1cc3fc93f817c00b887b43080efd7a" FETCH_HEAD \
	&& git checkout --detach "45fbe8a35f1cc3fc93f817c00b887b43080efd7a"
WORKDIR /usr/src/php-src

# Pin the checkout so moving refs do not affect the repro.
ARG PHP_COMMIT=b25704e3b63774b730d2fba7b66e167149fd24a6
RUN if [ "$PHP_COMMIT" != "45fbe8a35f1cc3fc93f817c00b887b43080efd7a" ]; then \
		git merge-base --is-ancestor "$PHP_COMMIT" FETCH_HEAD && \
		git checkout --detach "$PHP_COMMIT"; \
	fi

RUN ./buildconf --force
RUN ./configure \
	--prefix=/opt/php-src \
	--disable-all \
	--disable-cgi \
	--disable-phpdbg \
	--enable-cli \
	--enable-opcache \
	--enable-zend-test=shared

ARG MAKE_JOBS
RUN make -j"${MAKE_JOBS:-$(nproc)}"
RUN make install

ENV PATH="/opt/php-src/bin:${PATH}"
ENV TEST_PHP_EXECUTABLE=/opt/php-src/bin/php
ENV TEST_PHP_ARGS="-n -d zend_extension=opcache -d extension=zend_test"

CMD ["php", "run-tests.php", "-q", "ext/zend_test/tests/observer_jit_vm_interrupt.phpt"]

As for the fix itself... I'm less confident in it. Please review it carefully as JIT code can be tricky and I had an AI agent help me with it. I'm targeting 8.4 with this PR.

Copy link
Copy Markdown
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't affect PHP without observer.
So I don't object.
Merge this, if you are sure.

@morrisonlevi morrisonlevi force-pushed the observer_jit_vm_interrupt branch from e28cf6a to b25704e Compare April 30, 2026 16:40
@morrisonlevi morrisonlevi force-pushed the observer_jit_vm_interrupt branch from b25704e to 4e778d6 Compare April 30, 2026 18:10
@morrisonlevi
Copy link
Copy Markdown
Contributor Author

I've done some extra testing. Although I am not too experienced with JIT stuff, I don't seem to have caused any regressions and did fix this new test case, so I will merge.

@morrisonlevi morrisonlevi merged commit 1f50b63 into php:PHP-8.4 Apr 30, 2026
19 checks passed
morrisonlevi added a commit that referenced this pull request Apr 30, 2026
# Via GitHub
* PHP-8.4:
  Fix JIT vm_interrupt (#21910)

# Conflicts:
#	ext/opcache/jit/zend_jit_ir.c
morrisonlevi added a commit that referenced this pull request Apr 30, 2026
* PHP-8.5:
  Fix JIT vm_interrupt (#21910)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants