From 9aa8e8825f4c7fc05dd366cfa521d47af813bfd7 Mon Sep 17 00:00:00 2001
From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com>
Date: Mon, 4 Aug 2025 15:52:09 +0200
Subject: [PATCH 01/27] Remove zend_jit_vm_kind (#19299)

JIT used to not have a compile-time dependency on VM kind, such that a single
build of opcache could work with different VM kinds at runtime. This has been broken
over time and would be difficult to restore. Additionally, as opcache is now
built-in, this would not be useful anymore.

Remove the zend_jit_vm_kind variable.
---
 ext/opcache/jit/zend_jit.c    | 14 ++++--------
 ext/opcache/jit/zend_jit_ir.c | 42 +++++++++++++++++------------------
 2 files changed, 25 insertions(+), 31 deletions(-)

diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c
index eadd72ff4c800..e743532a69337 100644
--- a/ext/opcache/jit/zend_jit.c
+++ b/ext/opcache/jit/zend_jit.c
@@ -77,7 +77,6 @@ int zend_jit_profile_counter_rid = -1;
 int16_t zend_jit_hot_counters[ZEND_HOT_COUNTERS_COUNT];
 
 const zend_op *zend_jit_halt_op = NULL;
-static int zend_jit_vm_kind = 0;
 #ifdef HAVE_PTHREAD_JIT_WRITE_PROTECT_NP
 static int zend_write_protect = 1;
 #endif
@@ -3707,19 +3706,14 @@ void zend_jit_init(void)
 #endif
 }
 
+#if ZEND_VM_KIND != ZEND_VM_KIND_CALL && ZEND_VM_KIND != ZEND_VM_KIND_HYBRID
+# error JIT is compatible only with CALL and HYBRID VM
+#endif
+
 int zend_jit_check_support(void)
 {
 	int i;
 
-	zend_jit_vm_kind = zend_vm_kind();
-	if (zend_jit_vm_kind != ZEND_VM_KIND_CALL &&
-	    zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
-		zend_error(E_WARNING, "JIT is compatible only with CALL and HYBRID VM. JIT disabled.");
-		JIT_G(enabled) = 0;
-		JIT_G(on) = 0;
-		return FAILURE;
-	}
-
 	if (zend_execute_ex != execute_ex) {
 		if (zend_dtrace_enabled) {
 			zend_error(E_WARNING, "JIT is incompatible with DTrace. JIT disabled.");
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 2a2c6c79f17f9..16f3de648a2f1 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -1926,7 +1926,7 @@ static void zend_jit_vm_leave(zend_jit_ctx *jit, ir_ref to_opline)
 
 static int zend_jit_exception_handler_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		zend_vm_opcode_handler_func_t handler = (zend_vm_opcode_handler_func_t)zend_get_opcode_handler_func(EG(exception_op));
 
 		ir_CALL(IR_VOID, ir_CONST_FUNC(handler));
@@ -2051,7 +2051,7 @@ static int zend_jit_leave_function_handler_stub(zend_jit_ctx *jit)
 
 	ir_IF_FALSE(if_top);
 
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_nested_func_helper), call_info);
 		jit_STORE_IP(jit,
 			ir_LOAD_A(jit_EX(opline)));
@@ -2064,7 +2064,7 @@ static int zend_jit_leave_function_handler_stub(zend_jit_ctx *jit)
 
 	ir_IF_TRUE(if_top);
 
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_leave_top_func_helper), call_info);
 		ir_TAILCALL(IR_VOID, ir_LOAD_A(jit_IP(jit)));
 	} else if (GCC_GLOBAL_REGS) {
@@ -2248,7 +2248,7 @@ static int zend_jit_leave_throw_stub(zend_jit_ctx *jit)
 
 static int zend_jit_hybrid_runtime_jit_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID) {
 		return 0;
 	}
 
@@ -2261,7 +2261,7 @@ static int zend_jit_hybrid_profile_jit_stub(zend_jit_ctx *jit)
 {
 	ir_ref addr, func, run_time_cache, jit_extension;
 
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID) {
 		return 0;
 	}
 
@@ -2317,7 +2317,7 @@ static int _zend_jit_hybrid_hot_counter_stub(zend_jit_ctx *jit, uint32_t cost)
 
 static int zend_jit_hybrid_func_hot_counter_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
 		return 0;
 	}
 
@@ -2327,7 +2327,7 @@ static int zend_jit_hybrid_func_hot_counter_stub(zend_jit_ctx *jit)
 
 static int zend_jit_hybrid_loop_hot_counter_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
 		return 0;
 	}
 
@@ -2389,7 +2389,7 @@ static int _zend_jit_hybrid_trace_counter_stub(zend_jit_ctx *jit, uint32_t cost)
 
 static int zend_jit_hybrid_func_trace_counter_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !JIT_G(hot_func)) {
 		return 0;
 	}
 
@@ -2399,7 +2399,7 @@ static int zend_jit_hybrid_func_trace_counter_stub(zend_jit_ctx *jit)
 
 static int zend_jit_hybrid_ret_trace_counter_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_return)) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !JIT_G(hot_return)) {
 		return 0;
 	}
 
@@ -2409,7 +2409,7 @@ static int zend_jit_hybrid_ret_trace_counter_stub(zend_jit_ctx *jit)
 
 static int zend_jit_hybrid_loop_trace_counter_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
+	if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !JIT_G(hot_loop)) {
 		return 0;
 	}
 
@@ -2419,7 +2419,7 @@ static int zend_jit_hybrid_loop_trace_counter_stub(zend_jit_ctx *jit)
 
 static int zend_jit_trace_halt_stub(zend_jit_ctx *jit)
 {
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		ir_TAILCALL(IR_VOID, ir_CONST_FC_FUNC(zend_jit_halt_op->handler));
 	} else if (GCC_GLOBAL_REGS) {
 		jit_STORE_IP(jit, IR_NULL);
@@ -2676,7 +2676,7 @@ static void zend_jit_init_ctx(zend_jit_ctx *jit, uint32_t flags)
 	jit->ctx.fixed_regset = (1<<ZREG_FP) | (1<<ZREG_IP);
 	if (!(flags & IR_FUNCTION)) {
 		jit->ctx.flags |= IR_NO_STACK_COMBINE;
-		if (zend_jit_vm_kind == ZEND_VM_KIND_CALL) {
+		if (ZEND_VM_KIND == ZEND_VM_KIND_CALL) {
 			jit->ctx.flags |= IR_FUNCTION;
 			/* Stack must be 16 byte aligned */
 			/* TODO: select stack size ??? */
@@ -3146,7 +3146,7 @@ static void zend_jit_calc_trace_prologue_size(void)
 	void *entry;
 	size_t size;
 
-	zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
+	zend_jit_init_ctx(jit, (ZEND_VM_KIND == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
 
 	if (!GCC_GLOBAL_REGS) {
 		ir_ref execute_data_ref = ir_PARAM(IR_ADDR, "execute_data", 1);
@@ -3333,7 +3333,7 @@ static void zend_jit_setup(bool reattached)
 #endif
 
 #if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		zend_jit_set_sp_adj_vm(); // set zend_jit_hybrid_vm_sp_adj
 	}
 #endif
@@ -4073,7 +4073,7 @@ static int zend_jit_tail_handler(zend_jit_ctx *jit, const zend_op *opline)
 	zend_basic_block *bb;
 
 	zend_jit_set_ip(jit, opline);
-	if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+	if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 		if (opline->opcode == ZEND_DO_UCALL ||
 		    opline->opcode == ZEND_DO_FCALL_BY_NAME ||
 		    opline->opcode == ZEND_DO_FCALL ||
@@ -10984,7 +10984,7 @@ static int zend_jit_leave_func(zend_jit_ctx         *jit,
 
 			if (may_be_top_frame) {
 				// TODO: try to avoid this check ???
-				if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+				if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 #if 0
 					/* this check should be handled by the following OPLINE guard */
 					|	cmp IP, zend_jit_halt_op
@@ -16566,7 +16566,7 @@ static int zend_jit_start(zend_jit_ctx *jit, const zend_op_array *op_array, zend
 	int i, count;
 	zend_basic_block *bb;
 
-	zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : (IR_START_BR_TARGET|IR_ENTRY_BR_TARGET));
+	zend_jit_init_ctx(jit, (ZEND_VM_KIND == ZEND_VM_KIND_CALL) ? 0 : (IR_START_BR_TARGET|IR_ENTRY_BR_TARGET));
 
 	jit->ctx.spill_base = ZREG_FP;
 
@@ -16644,7 +16644,7 @@ static zend_vm_opcode_handler_t zend_jit_finish(zend_jit_ctx *jit)
 
 //					ir_mem_unprotect(entry, size);
 					if (!(jit->ctx.flags & IR_FUNCTION)
-					 && zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+					 && ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 #if !defined(ZEND_WIN32) && !defined(IR_TARGET_AARCH64)
 						sp_offset = zend_jit_hybrid_vm_sp_adj;
 #else
@@ -17012,7 +17012,7 @@ static int zend_jit_trace_handler(zend_jit_ctx *jit, const zend_op_array *op_arr
 		    opline->opcode == ZEND_RETURN_BY_REF ||
 		    opline->opcode == ZEND_GENERATOR_CREATE) {
 
-			if (zend_jit_vm_kind == ZEND_VM_KIND_HYBRID) {
+			if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) {
 				if (trace->op != ZEND_JIT_TRACE_END ||
 				    (trace->stop != ZEND_JIT_TRACE_STOP_RETURN &&
 				     trace->stop < ZEND_JIT_TRACE_STOP_INTERPRETER)) {
@@ -17126,7 +17126,7 @@ static int zend_jit_deoptimizer_start(zend_jit_ctx        *jit,
                                       uint32_t             trace_num,
                                       uint32_t             exit_num)
 {
-	zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
+	zend_jit_init_ctx(jit, (ZEND_VM_KIND == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
 
 	jit->ctx.spill_base = ZREG_FP;
 
@@ -17147,7 +17147,7 @@ static int zend_jit_trace_start(zend_jit_ctx        *jit,
                                 zend_jit_trace_info *parent,
                                 uint32_t             exit_num)
 {
-	zend_jit_init_ctx(jit, (zend_jit_vm_kind == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
+	zend_jit_init_ctx(jit, (ZEND_VM_KIND == ZEND_VM_KIND_CALL) ? 0 : IR_START_BR_TARGET);
 
 	jit->ctx.spill_base = ZREG_FP;
 

From 4e219242713058509e0da00ee75c6923e5544eb9 Mon Sep 17 00:00:00 2001
From: Frederik Milling Pytlick <frederikpyt@protonmail.com>
Date: Mon, 10 Mar 2025 11:15:52 +0100
Subject: [PATCH 02/27] Fix GH-17951: Addition of max_memory_limit INI

Closes GH-18011
---
 UPGRADING                                 |  5 +++
 main/main.c                               | 52 ++++++++++++++++++++++-
 main/php_globals.h                        |  1 +
 php.ini-development                       |  1 +
 php.ini-production                        |  1 +
 tests/basic/gh17951_ini_parse_1.phpt      | 15 +++++++
 tests/basic/gh17951_ini_parse_2.phpt      | 15 +++++++
 tests/basic/gh17951_ini_parse_3.phpt      | 15 +++++++
 tests/basic/gh17951_ini_parse_4.phpt      | 15 +++++++
 tests/basic/gh17951_ini_parse_5.phpt      | 15 +++++++
 tests/basic/gh17951_runtime_change_1.phpt | 14 ++++++
 tests/basic/gh17951_runtime_change_2.phpt | 14 ++++++
 tests/basic/gh17951_runtime_change_3.phpt | 14 ++++++
 tests/basic/gh17951_runtime_change_4.phpt | 14 ++++++
 tests/basic/gh17951_runtime_change_5.phpt | 19 +++++++++
 15 files changed, 209 insertions(+), 1 deletion(-)
 create mode 100644 tests/basic/gh17951_ini_parse_1.phpt
 create mode 100644 tests/basic/gh17951_ini_parse_2.phpt
 create mode 100644 tests/basic/gh17951_ini_parse_3.phpt
 create mode 100644 tests/basic/gh17951_ini_parse_4.phpt
 create mode 100644 tests/basic/gh17951_ini_parse_5.phpt
 create mode 100644 tests/basic/gh17951_runtime_change_1.phpt
 create mode 100644 tests/basic/gh17951_runtime_change_2.phpt
 create mode 100644 tests/basic/gh17951_runtime_change_3.phpt
 create mode 100644 tests/basic/gh17951_runtime_change_4.phpt
 create mode 100644 tests/basic/gh17951_runtime_change_5.phpt

diff --git a/UPGRADING b/UPGRADING
index 65e164fe01a07..f3a7494d3c839 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -629,6 +629,11 @@ PHP 8.5 UPGRADE NOTES
   . Added fatal_error_backtraces to control whether fatal errors should include
     a backtrace.
     RFC: https://wiki.php.net/rfc/error_backtraces_v2
+  . Added startup-only max_memory_limit INI setting to control the maximum
+    memory_limit that may be configured at startup or runtime. Exceeding this
+    value emits a warning, unless set to -1, and sets memory_limit to the
+    current max_memory_limit instead.
+    ML discussion: https://externals.io/message/127108
 
 - Opcache:
   . Added opcache.file_cache_read_only to support a read-only
diff --git a/main/main.c b/main/main.c
index 04715d1916eec..7e4925da6852d 100644
--- a/main/main.c
+++ b/main/main.c
@@ -332,6 +332,33 @@ static PHP_INI_MH(OnChangeMemoryLimit)
 	} else {
 		value = Z_L(1)<<30;		/* effectively, no limit */
 	}
+
+	/* If max_memory_limit is not set to unlimited, verify change */
+	if (PG(max_memory_limit) != -1) {
+		if (value == -1) {
+			zend_error(
+				E_WARNING,
+				"Failed to set memory_limit to unlimited. memory_limit (currently: " ZEND_LONG_FMT " bytes) cannot be set to unlimited if max_memory_limit (" ZEND_LONG_FMT " bytes) is not unlimited",
+				PG(memory_limit),
+				PG(max_memory_limit)
+			);
+
+			return FAILURE;
+		}
+
+		if (value > PG(max_memory_limit)) {
+			zend_error(
+				E_WARNING,
+				"Failed to set memory_limit to %zd bytes. memory_limit (currently: " ZEND_LONG_FMT " bytes) cannot exceed max_memory_limit (" ZEND_LONG_FMT " bytes)",
+				value,
+				PG(memory_limit),
+				PG(max_memory_limit)
+			);
+
+			return FAILURE;
+		}
+	}
+
 	if (zend_set_memory_limit(value) == FAILURE) {
 		/* When the memory limit is reset to the original level during deactivation, we may be
 		 * using more memory than the original limit while shutdown is still in progress.
@@ -347,6 +374,26 @@ static PHP_INI_MH(OnChangeMemoryLimit)
 }
 /* }}} */
 
+static PHP_INI_MH(OnChangeMaxMemoryLimit)
+{
+	size_t value;
+	if (new_value) {
+		value = zend_ini_parse_uquantity_warn(new_value, entry->name);
+	} else {
+		value = Z_L(1) << 30; /* effectively, no limit */
+	}
+
+	if (zend_set_memory_limit(value) == FAILURE) {
+		zend_error(E_ERROR, "Failed to set memory limit to %zd bytes (Current memory usage is %zd bytes)", value, zend_memory_usage(true));
+		return FAILURE;
+	}
+
+	PG(memory_limit) = value;
+	PG(max_memory_limit) = value;
+
+	return SUCCESS;
+}
+
 /* {{{ PHP_INI_MH */
 static PHP_INI_MH(OnSetLogFilter)
 {
@@ -810,7 +857,10 @@ PHP_INI_BEGIN()
 	STD_PHP_INI_BOOLEAN("mail.mixed_lf_and_crlf",			"0",		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateBool,			mail_mixed_lf_and_crlf,			php_core_globals,	core_globals)
 	STD_PHP_INI_ENTRY("mail.log",					NULL,		PHP_INI_SYSTEM|PHP_INI_PERDIR,		OnUpdateMailLog,			mail_log,			php_core_globals,	core_globals)
 	PHP_INI_ENTRY("browscap",					NULL,		PHP_INI_SYSTEM,		OnChangeBrowscap)
-	PHP_INI_ENTRY("memory_limit",				"128M",		PHP_INI_ALL,		OnChangeMemoryLimit)
+
+	PHP_INI_ENTRY("max_memory_limit",		"-1",		PHP_INI_SYSTEM,		OnChangeMaxMemoryLimit)
+	PHP_INI_ENTRY("memory_limit",			"128M",		PHP_INI_ALL,		OnChangeMemoryLimit)
+
 	PHP_INI_ENTRY("precision",					"14",		PHP_INI_ALL,		OnSetPrecision)
 	PHP_INI_ENTRY("sendmail_from",				NULL,		PHP_INI_ALL,		NULL)
 	PHP_INI_ENTRY("sendmail_path",	DEFAULT_SENDMAIL_PATH,	PHP_INI_SYSTEM,		NULL)
diff --git a/main/php_globals.h b/main/php_globals.h
index ab7a9a00b2f1d..94e5aa44fc59f 100644
--- a/main/php_globals.h
+++ b/main/php_globals.h
@@ -72,6 +72,7 @@ struct _php_core_globals {
 	zend_long serialize_precision;
 
 	zend_long memory_limit;
+	zend_long max_memory_limit;
 	zend_long max_input_time;
 
 	char *error_log;
diff --git a/php.ini-development b/php.ini-development
index 2760f167ba4ea..2d3c8bf32a5d9 100644
--- a/php.ini-development
+++ b/php.ini-development
@@ -436,6 +436,7 @@ max_input_time = 60
 ; Maximum amount of memory a script may consume
 ; https://php.net/memory-limit
 memory_limit = 128M
+max_memory_limit = -1
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Error handling and logging ;
diff --git a/php.ini-production b/php.ini-production
index e028990f304cd..13d3f66c93c4f 100644
--- a/php.ini-production
+++ b/php.ini-production
@@ -438,6 +438,7 @@ max_input_time = 60
 ; Maximum amount of memory a script may consume
 ; https://php.net/memory-limit
 memory_limit = 128M
+max_memory_limit = -1
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ; Error handling and logging ;
diff --git a/tests/basic/gh17951_ini_parse_1.phpt b/tests/basic/gh17951_ini_parse_1.phpt
new file mode 100644
index 0000000000000..fe0dea138e4b2
--- /dev/null
+++ b/tests/basic/gh17951_ini_parse_1.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-17951 INI Parse 1
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=-1
+--FILE--
+<?php
+echo ini_get('max_memory_limit') . PHP_EOL;
+echo ini_get('memory_limit') . PHP_EOL;
+?>
+--EXPECT--
+-1
+128M
diff --git a/tests/basic/gh17951_ini_parse_2.phpt b/tests/basic/gh17951_ini_parse_2.phpt
new file mode 100644
index 0000000000000..e0f290f4c2cf0
--- /dev/null
+++ b/tests/basic/gh17951_ini_parse_2.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-17951 INI Parse 2
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=-1
+max_memory_limit=-1
+--FILE--
+<?php
+echo ini_get('max_memory_limit') . PHP_EOL;
+echo ini_get('memory_limit') . PHP_EOL;
+?>
+--EXPECT--
+-1
+-1
diff --git a/tests/basic/gh17951_ini_parse_3.phpt b/tests/basic/gh17951_ini_parse_3.phpt
new file mode 100644
index 0000000000000..5057671418621
--- /dev/null
+++ b/tests/basic/gh17951_ini_parse_3.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-17951 INI Parse 3
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=256M
+--FILE--
+<?php
+echo ini_get('max_memory_limit') . PHP_EOL;
+echo ini_get('memory_limit') . PHP_EOL;
+?>
+--EXPECT--
+256M
+128M
diff --git a/tests/basic/gh17951_ini_parse_4.phpt b/tests/basic/gh17951_ini_parse_4.phpt
new file mode 100644
index 0000000000000..f29207183a1cf
--- /dev/null
+++ b/tests/basic/gh17951_ini_parse_4.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-17951 INI Parse 4
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=-1
+max_memory_limit=128M
+--FILE--
+<?php
+echo ini_get('max_memory_limit') . PHP_EOL;
+echo ini_get('memory_limit') . PHP_EOL;
+--EXPECTF--
+Warning: Failed to set memory_limit to unlimited. memory_limit (currently: %d bytes) cannot be set to unlimited if max_memory_limit (%d bytes) is not unlimited in %s
+128M
+128M
diff --git a/tests/basic/gh17951_ini_parse_5.phpt b/tests/basic/gh17951_ini_parse_5.phpt
new file mode 100644
index 0000000000000..ee028caf9a351
--- /dev/null
+++ b/tests/basic/gh17951_ini_parse_5.phpt
@@ -0,0 +1,15 @@
+--TEST--
+GH-17951 INI Parse 5
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=256M
+max_memory_limit=128M
+--FILE--
+<?php
+echo ini_get('max_memory_limit') . PHP_EOL;
+echo ini_get('memory_limit') . PHP_EOL;
+--EXPECTF--
+Warning: Failed to set memory_limit to %d bytes. memory_limit (currently: %d bytes) cannot exceed max_memory_limit (%d bytes) in %s
+128M
+128M
diff --git a/tests/basic/gh17951_runtime_change_1.phpt b/tests/basic/gh17951_runtime_change_1.phpt
new file mode 100644
index 0000000000000..12b7b746f2b7a
--- /dev/null
+++ b/tests/basic/gh17951_runtime_change_1.phpt
@@ -0,0 +1,14 @@
+--TEST--
+GH-17951 Runtime Change 1
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=512M
+--FILE--
+<?php
+ini_set('memory_limit', '256M');
+echo ini_get('memory_limit') . PHP_EOL;
+?>
+--EXPECT--
+256M
diff --git a/tests/basic/gh17951_runtime_change_2.phpt b/tests/basic/gh17951_runtime_change_2.phpt
new file mode 100644
index 0000000000000..8bcc5ea65dd5c
--- /dev/null
+++ b/tests/basic/gh17951_runtime_change_2.phpt
@@ -0,0 +1,14 @@
+--TEST--
+GH-17951 Runtime Change 2
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=512M
+--FILE--
+<?php
+ini_set('memory_limit', '512M');
+echo ini_get('memory_limit') . PHP_EOL;
+?>
+--EXPECT--
+512M
diff --git a/tests/basic/gh17951_runtime_change_3.phpt b/tests/basic/gh17951_runtime_change_3.phpt
new file mode 100644
index 0000000000000..d37c9da9b8ed4
--- /dev/null
+++ b/tests/basic/gh17951_runtime_change_3.phpt
@@ -0,0 +1,14 @@
+--TEST--
+GH-17951 Runtime Change 3
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=512M
+--FILE--
+<?php
+ini_set('memory_limit', '1024M');
+echo ini_get('memory_limit');
+--EXPECTF--
+Warning: Failed to set memory_limit to %d bytes. memory_limit (currently: %d bytes) cannot exceed max_memory_limit (%d bytes) in %s
+128M
diff --git a/tests/basic/gh17951_runtime_change_4.phpt b/tests/basic/gh17951_runtime_change_4.phpt
new file mode 100644
index 0000000000000..0cbe3bb5c1040
--- /dev/null
+++ b/tests/basic/gh17951_runtime_change_4.phpt
@@ -0,0 +1,14 @@
+--TEST--
+GH-17951 Runtime Change 4
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=512M
+--FILE--
+<?php
+ini_set('memory_limit', '-1');
+echo ini_get('memory_limit');
+--EXPECTF--
+Warning: Failed to set memory_limit to unlimited. memory_limit (currently: %d bytes) cannot be set to unlimited if max_memory_limit (%d bytes) is not unlimited in %s
+128M
diff --git a/tests/basic/gh17951_runtime_change_5.phpt b/tests/basic/gh17951_runtime_change_5.phpt
new file mode 100644
index 0000000000000..f5f5afd601476
--- /dev/null
+++ b/tests/basic/gh17951_runtime_change_5.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-17951 Runtime Change 5
+--CREDITS--
+Frederik Milling Pytlick (frederikpyt@protonmail.com)
+--INI--
+memory_limit=128M
+max_memory_limit=512M
+--FILE--
+<?php
+var_dump(ini_set('max_memory_limit', '128M'));
+var_dump(ini_set('max_memory_limit', '256M'));
+var_dump(ini_set('max_memory_limit', '512M'));
+var_dump(ini_set('max_memory_limit', '-1'));
+?>
+--EXPECT--
+bool(false)
+bool(false)
+bool(false)
+bool(false)

From cd80ed6f7bcec1bd96708cba172255856f13a3be Mon Sep 17 00:00:00 2001
From: Ilija Tovilo <ilija.tovilo@me.com>
Date: Mon, 4 Aug 2025 15:58:21 +0200
Subject: [PATCH 03/27] Implement changes to GH-17951 according to ML
 discussion

---
 Zend/zend_ini.c                           | 20 ++++++++++++---
 main/main.c                               | 31 ++++++++---------------
 tests/basic/gh17951_ini_parse_4.phpt      |  4 +--
 tests/basic/gh17951_ini_parse_5.phpt      |  5 ++--
 tests/basic/gh17951_runtime_change_3.phpt |  5 ++--
 tests/basic/gh17951_runtime_change_4.phpt |  6 ++---
 6 files changed, 37 insertions(+), 34 deletions(-)

diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c
index 199ebfb2e9b45..689e76794df34 100644
--- a/Zend/zend_ini.c
+++ b/Zend/zend_ini.c
@@ -248,10 +248,16 @@ ZEND_API zend_result zend_register_ini_entries_ex(const zend_ini_entry_def *ini_
 			zend_unregister_ini_entries_ex(module_number, module_type);
 			return FAILURE;
 		}
+
+		zend_string *prev_value = p->value;
+
 		if (((default_value = zend_get_configuration_directive(p->name)) != NULL) &&
 		    (!p->on_modify || p->on_modify(p, Z_STR_P(default_value), p->mh_arg1, p->mh_arg2, p->mh_arg3, ZEND_INI_STAGE_STARTUP) == SUCCESS)) {
 
-			p->value = zend_new_interned_string(zend_string_copy(Z_STR_P(default_value)));
+			/* Skip assigning the value if the handler has already done so. */
+			if (p->value == prev_value) {
+				p->value = zend_new_interned_string(zend_string_copy(Z_STR_P(default_value)));
+			}
 		} else {
 			p->value = ini_entry->value ?
 				zend_string_init_interned(ini_entry->value, ini_entry->value_length, 1) : NULL;
@@ -389,14 +395,20 @@ ZEND_API zend_result zend_alter_ini_entry_ex(zend_string *name, zend_string *new
 		zend_hash_add_ptr(EG(modified_ini_directives), ini_entry->name, ini_entry);
 	}
 
+	zend_string *prev_value = ini_entry->value;
 	duplicate = zend_string_copy(new_value);
 
 	if (!ini_entry->on_modify
 		|| ini_entry->on_modify(ini_entry, duplicate, ini_entry->mh_arg1, ini_entry->mh_arg2, ini_entry->mh_arg3, stage) == SUCCESS) {
-		if (modified && ini_entry->orig_value != ini_entry->value) { /* we already changed the value, free the changed value */
-			zend_string_release(ini_entry->value);
+		if (modified && ini_entry->orig_value != prev_value) { /* we already changed the value, free the changed value */
+			zend_string_release(prev_value);
+		}
+		/* Skip assigning the value if the handler has already done so. */
+		if (ini_entry->value == prev_value) {
+			ini_entry->value = duplicate;
+		} else {
+			zend_string_release(duplicate);
 		}
-		ini_entry->value = duplicate;
 	} else {
 		zend_string_release(duplicate);
 		return FAILURE;
diff --git a/main/main.c b/main/main.c
index 7e4925da6852d..1cff1cee27a40 100644
--- a/main/main.c
+++ b/main/main.c
@@ -333,30 +333,19 @@ static PHP_INI_MH(OnChangeMemoryLimit)
 		value = Z_L(1)<<30;		/* effectively, no limit */
 	}
 
-	/* If max_memory_limit is not set to unlimited, verify change */
-	if (PG(max_memory_limit) != -1) {
-		if (value == -1) {
-			zend_error(
-				E_WARNING,
-				"Failed to set memory_limit to unlimited. memory_limit (currently: " ZEND_LONG_FMT " bytes) cannot be set to unlimited if max_memory_limit (" ZEND_LONG_FMT " bytes) is not unlimited",
-				PG(memory_limit),
-				PG(max_memory_limit)
-			);
-
-			return FAILURE;
+	/* If memory_limit exceeds max_memory_limit, warn and set to max_memory_limit instead. */
+	if (value > PG(max_memory_limit)) {
+		if (value != -1) {
+			zend_error(E_WARNING,
+				"Failed to set memory_limit to %zd bytes. Setting to max_memory_limit instead (currently: " ZEND_LONG_FMT " bytes)",
+				value, PG(max_memory_limit));
 		}
 
-		if (value > PG(max_memory_limit)) {
-			zend_error(
-				E_WARNING,
-				"Failed to set memory_limit to %zd bytes. memory_limit (currently: " ZEND_LONG_FMT " bytes) cannot exceed max_memory_limit (" ZEND_LONG_FMT " bytes)",
-				value,
-				PG(memory_limit),
-				PG(max_memory_limit)
-			);
+		zend_ini_entry *max_mem_limit_ini = zend_hash_str_find_ptr(EG(ini_directives), ZEND_STRL("max_memory_limit"));
+		entry->value = zend_string_copy(max_mem_limit_ini->value);
+		PG(memory_limit) = PG(max_memory_limit);
 
-			return FAILURE;
-		}
+		return SUCCESS;
 	}
 
 	if (zend_set_memory_limit(value) == FAILURE) {
diff --git a/tests/basic/gh17951_ini_parse_4.phpt b/tests/basic/gh17951_ini_parse_4.phpt
index f29207183a1cf..5690a133d7755 100644
--- a/tests/basic/gh17951_ini_parse_4.phpt
+++ b/tests/basic/gh17951_ini_parse_4.phpt
@@ -9,7 +9,7 @@ max_memory_limit=128M
 <?php
 echo ini_get('max_memory_limit') . PHP_EOL;
 echo ini_get('memory_limit') . PHP_EOL;
---EXPECTF--
-Warning: Failed to set memory_limit to unlimited. memory_limit (currently: %d bytes) cannot be set to unlimited if max_memory_limit (%d bytes) is not unlimited in %s
+?>
+--EXPECT--
 128M
 128M
diff --git a/tests/basic/gh17951_ini_parse_5.phpt b/tests/basic/gh17951_ini_parse_5.phpt
index ee028caf9a351..20e414526000a 100644
--- a/tests/basic/gh17951_ini_parse_5.phpt
+++ b/tests/basic/gh17951_ini_parse_5.phpt
@@ -9,7 +9,8 @@ max_memory_limit=128M
 <?php
 echo ini_get('max_memory_limit') . PHP_EOL;
 echo ini_get('memory_limit') . PHP_EOL;
---EXPECTF--
-Warning: Failed to set memory_limit to %d bytes. memory_limit (currently: %d bytes) cannot exceed max_memory_limit (%d bytes) in %s
+?>
+--EXPECT--
+Warning: Failed to set memory_limit to 268435456 bytes. Setting to max_memory_limit instead (currently: 134217728 bytes) in Unknown on line 0
 128M
 128M
diff --git a/tests/basic/gh17951_runtime_change_3.phpt b/tests/basic/gh17951_runtime_change_3.phpt
index d37c9da9b8ed4..975caad4ff0bf 100644
--- a/tests/basic/gh17951_runtime_change_3.phpt
+++ b/tests/basic/gh17951_runtime_change_3.phpt
@@ -9,6 +9,7 @@ max_memory_limit=512M
 <?php
 ini_set('memory_limit', '1024M');
 echo ini_get('memory_limit');
+?>
 --EXPECTF--
-Warning: Failed to set memory_limit to %d bytes. memory_limit (currently: %d bytes) cannot exceed max_memory_limit (%d bytes) in %s
-128M
+Warning: Failed to set memory_limit to 1073741824 bytes. Setting to max_memory_limit instead (currently: 536870912 bytes) in %s on line %d
+512M
diff --git a/tests/basic/gh17951_runtime_change_4.phpt b/tests/basic/gh17951_runtime_change_4.phpt
index 0cbe3bb5c1040..3b27e2c91a9ab 100644
--- a/tests/basic/gh17951_runtime_change_4.phpt
+++ b/tests/basic/gh17951_runtime_change_4.phpt
@@ -9,6 +9,6 @@ max_memory_limit=512M
 <?php
 ini_set('memory_limit', '-1');
 echo ini_get('memory_limit');
---EXPECTF--
-Warning: Failed to set memory_limit to unlimited. memory_limit (currently: %d bytes) cannot be set to unlimited if max_memory_limit (%d bytes) is not unlimited in %s
-128M
+?>
+--EXPECT--
+512M

From 433c00b34847ff18a339b90277fc076ad4e31148 Mon Sep 17 00:00:00 2001
From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com>
Date: Mon, 4 Aug 2025 16:15:52 +0200
Subject: [PATCH 04/27] [skip ci] tree: Fix various typos (#19366)

---
 ext/gettext/gettext.c             | 2 +-
 ext/opcache/shared_alloc_mmap.c   | 4 ++--
 ext/pdo_mysql/php_pdo_mysql_int.h | 2 +-
 sapi/phpdbg/phpdbg_watch.c        | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/ext/gettext/gettext.c b/ext/gettext/gettext.c
index 27f0dfa26da70..6badea4a17698 100644
--- a/ext/gettext/gettext.c
+++ b/ext/gettext/gettext.c
@@ -194,7 +194,7 @@ PHP_FUNCTION(bindtextdomain)
 		btd_result = bindtextdomain(ZSTR_VAL(domain), NULL);
 		if (btd_result == NULL) {
 			/* POSIX-compliant implementations can return
-			 * NULL if an error occured. On musl you will
+			 * NULL if an error occurred. On musl you will
 			 * also get NULL if the domain is not yet
 			 * bound, because musl has no default directory
 			 * to return in that case. */
diff --git a/ext/opcache/shared_alloc_mmap.c b/ext/opcache/shared_alloc_mmap.c
index 18c7532478f48..cf5dbb29ebfd9 100644
--- a/ext/opcache/shared_alloc_mmap.c
+++ b/ext/opcache/shared_alloc_mmap.c
@@ -52,7 +52,7 @@
 #endif
 
 #if defined(HAVE_JIT) && (defined(__linux__) || defined(__FreeBSD__)) && (defined(__x86_64__) || defined (__aarch64__)) && !defined(__SANITIZE_ADDRESS__)
-static void *find_prefered_mmap_base(size_t requested_size)
+static void *find_preferred_mmap_base(size_t requested_size)
 {
 	size_t huge_page_size = 2 * 1024 * 1024;
 	uintptr_t last_free_addr = huge_page_size;
@@ -204,7 +204,7 @@ static int create_segments(size_t requested_size, zend_shared_segment ***shared_
 	void *hint;
 	if (JIT_G(enabled) && JIT_G(buffer_size)
 			&& zend_jit_check_support() == SUCCESS) {
-		hint = find_prefered_mmap_base(requested_size);
+		hint = find_preferred_mmap_base(requested_size);
 	} else {
 		/* Do not use a hint if JIT is not enabled, as this profits only JIT and
 		 * this is potentially unsafe when the only suitable candidate is just
diff --git a/ext/pdo_mysql/php_pdo_mysql_int.h b/ext/pdo_mysql/php_pdo_mysql_int.h
index ae77193afcfa9..ec49d6c311c34 100644
--- a/ext/pdo_mysql/php_pdo_mysql_int.h
+++ b/ext/pdo_mysql/php_pdo_mysql_int.h
@@ -78,7 +78,7 @@ ZEND_BEGIN_MODULE_GLOBALS(pdo_mysql)
 	/* dummy member so we get at least one member in the struct
 	 * and avoids build errors.
 	 */
-	void *dummymemmber;
+	void *dummymember;
 #endif
 ZEND_END_MODULE_GLOBALS(pdo_mysql)
 
diff --git a/sapi/phpdbg/phpdbg_watch.c b/sapi/phpdbg/phpdbg_watch.c
index 4478dca0516ba..14496d865ead4 100644
--- a/sapi/phpdbg/phpdbg_watch.c
+++ b/sapi/phpdbg/phpdbg_watch.c
@@ -1481,7 +1481,7 @@ void phpdbg_setup_watchpoints(void) {
 #ifdef HAVE_USERFAULTFD_WRITEFAULT
 	int flags = O_CLOEXEC;
 #ifdef UFFD_USER_MODE_ONLY
-	// unpriviliged userfaultfd are disabled by default,
+	// unprivileged userfaultfd are disabled by default,
 	// with this flag it allows ranges from the user space
 	// being reported.
 	flags |= UFFD_USER_MODE_ONLY;

From ac1cd9c26ef90fd2512587c185db2964bc060f77 Mon Sep 17 00:00:00 2001
From: Dmitry Stogov <dmitry@php.net>
Date: Mon, 4 Aug 2025 17:26:24 +0300
Subject: [PATCH 05/27] Update IR

IR commit: 6e2aea0ebfef2c741ebec30c57aa492df0d4e319
---
 ext/opcache/jit/ir/ir.c            |  15 +-
 ext/opcache/jit/ir/ir.h            |  17 +-
 ext/opcache/jit/ir/ir_aarch64.dasc |  94 +++++++++--
 ext/opcache/jit/ir/ir_builder.h    |   2 +
 ext/opcache/jit/ir/ir_cfg.c        | 247 +++++++++++++++++++++--------
 ext/opcache/jit/ir/ir_check.c      |   9 +-
 ext/opcache/jit/ir/ir_emit.c       |   4 +-
 ext/opcache/jit/ir/ir_fold.h       |   4 +-
 ext/opcache/jit/ir/ir_gcm.c        | 199 +++++++++++++++++++++--
 ext/opcache/jit/ir/ir_private.h    |   5 +-
 ext/opcache/jit/ir/ir_save.c       |  11 +-
 ext/opcache/jit/ir/ir_sccp.c       |  38 ++++-
 ext/opcache/jit/ir/ir_x86.dasc     | 167 +++++++++++++++++--
 13 files changed, 685 insertions(+), 127 deletions(-)

diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c
index a9f55cc0e466c..97d32680e4fdd 100644
--- a/ext/opcache/jit/ir/ir.c
+++ b/ext/opcache/jit/ir/ir.c
@@ -172,7 +172,7 @@ void ir_print_const(const ir_ctx *ctx, const ir_insn *insn, FILE *f, bool quoted
 			} else if (insn->val.c == '\0') {
 				fprintf(f, "'\\0'");
 			} else {
-				fprintf(f, "%u", insn->val.c);
+				fprintf(f, "%u", (unsigned char)insn->val.c);
 			}
 			break;
 		case IR_I8:
@@ -247,6 +247,7 @@ void ir_print_const(const ir_ctx *ctx, const ir_insn *insn, FILE *f, bool quoted
 #define ir_op_flag_S1X1    (ir_op_flag_S | 1 | (2 << IR_OP_FLAG_OPERANDS_SHIFT))
 #define ir_op_flag_S2      (ir_op_flag_S | 2 | (2 << IR_OP_FLAG_OPERANDS_SHIFT))
 #define ir_op_flag_S2X1    (ir_op_flag_S | 2 | (3 << IR_OP_FLAG_OPERANDS_SHIFT))
+#define ir_op_flag_S3      (ir_op_flag_S | 3 | (3 << IR_OP_FLAG_OPERANDS_SHIFT))
 #define ir_op_flag_SN      (ir_op_flag_S | IR_OP_FLAG_VAR_INPUTS)
 #define ir_op_flag_E       (IR_OP_FLAG_CONTROL|IR_OP_FLAG_BB_END)
 #define ir_op_flag_E1      (ir_op_flag_E | 1 | (1 << IR_OP_FLAG_OPERANDS_SHIFT))
@@ -803,7 +804,9 @@ ir_ref ir_proto(ir_ctx *ctx, uint8_t flags, ir_type ret_type, uint32_t params_co
 	proto->flags = flags;
 	proto->ret_type = ret_type;
 	proto->params_count = params_count;
-	memcpy(proto->param_types, param_types, params_count);
+	if (params_count) {
+		memcpy(proto->param_types, param_types, params_count);
+	}
 	return ir_strl(ctx, (const char *)proto, offsetof(ir_proto_t, param_types) + params_count);
 }
 
@@ -1080,7 +1083,7 @@ ir_ref ir_emit_N(ir_ctx *ctx, uint32_t opt, int32_t count)
 	ir_ref *p, ref = ctx->insns_count;
 	ir_insn *insn;
 
-	IR_ASSERT(count >= 0);
+	IR_ASSERT(count >= 0 && count < 65536);
 	while (UNEXPECTED(ref + count/4 >= ctx->insns_limit)) {
 		ir_grow_top(ctx);
 	}
@@ -2973,6 +2976,12 @@ void _ir_CASE_VAL(ir_ctx *ctx, ir_ref switch_ref, ir_ref val)
 	ctx->control = ir_emit2(ctx, IR_CASE_VAL, switch_ref, val);
 }
 
+void _ir_CASE_RANGE(ir_ctx *ctx, ir_ref switch_ref, ir_ref v1, ir_ref v2)
+{
+	IR_ASSERT(!ctx->control);
+	ctx->control = ir_emit3(ctx, IR_CASE_RANGE, switch_ref, v1, v2);
+}
+
 void _ir_CASE_DEFAULT(ir_ctx *ctx, ir_ref switch_ref)
 {
 	IR_ASSERT(!ctx->control);
diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h
index ec5e57129c9ab..9575348ff5450 100644
--- a/ext/opcache/jit/ir/ir.h
+++ b/ext/opcache/jit/ir/ir.h
@@ -359,6 +359,7 @@ typedef enum _ir_type {
 	_(IF_TRUE,      S1X1, src, prb, ___) /* IF TRUE proj.               */ \
 	_(IF_FALSE,     S1X1, src, prb, ___) /* IF FALSE proj.              */ \
 	_(CASE_VAL,     S2X1, src, def, prb) /* switch proj.                */ \
+	_(CASE_RANGE,   S3,   src, def, def) /* switch proj.                */ \
 	_(CASE_DEFAULT, S1X1, src, prb, ___) /* switch proj.                */ \
 	_(MERGE,        SN,   src, src, src) /* control merge               */ \
 	_(LOOP_BEGIN,   SN,   src, src, src) /* loop start                  */ \
@@ -854,6 +855,9 @@ void ir_gdb_unregister_all(void);
 bool ir_gdb_present(void);
 
 /* IR load API (implementation in ir_load.c) */
+#define IR_RESOLVE_SYM_ADD_THUNK (1<<0)
+#define IR_RESOLVE_SYM_SILENT    (1<<1)
+
 struct _ir_loader {
 	uint32_t default_func_flags;
 	bool (*init_module)       (ir_loader *loader, const char *name, const char *filename, const char *target);
@@ -870,7 +874,7 @@ struct _ir_loader {
 	bool (*sym_data_end)      (ir_loader *loader, uint32_t flags);
 	bool (*func_init)         (ir_loader *loader, ir_ctx *ctx, const char *name);
 	bool (*func_process)      (ir_loader *loader, ir_ctx *ctx, const char *name);
-	void*(*resolve_sym_name)  (ir_loader *loader, const char *name, bool add_thunk);
+	void*(*resolve_sym_name)  (ir_loader *loader, const char *name, uint32_t flags);
 	bool (*has_sym)           (ir_loader *loader, const char *name);
 	bool (*add_sym)           (ir_loader *loader, const char *name, void *addr);
 };
@@ -884,11 +888,12 @@ int ir_load_llvm_bitcode(ir_loader *loader, const char *filename);
 int ir_load_llvm_asm(ir_loader *loader, const char *filename);
 
 /* IR save API (implementation in ir_save.c) */
-#define IR_SAVE_CFG       (1<<0) /* add info about CFG */
-#define IR_SAVE_CFG_MAP   (1<<1) /* add info about CFG block assignment */
-#define IR_SAVE_USE_LISTS (1<<2) /* add info about def->use lists */
-#define IR_SAVE_RULES     (1<<3) /* add info about selected code-generation rules */
-#define IR_SAVE_REGS      (1<<4) /* add info about selected registers */
+#define IR_SAVE_CFG        (1<<0) /* add info about CFG */
+#define IR_SAVE_CFG_MAP    (1<<1) /* add info about CFG block assignment */
+#define IR_SAVE_USE_LISTS  (1<<2) /* add info about def->use lists */
+#define IR_SAVE_RULES      (1<<3) /* add info about selected code-generation rules */
+#define IR_SAVE_REGS       (1<<4) /* add info about selected registers */
+#define IR_SAVE_SAFE_NAMES (1<<5) /* add '@' prefix to symbol names */
 
 void ir_print_proto(const ir_ctx *ctx, ir_ref proto, FILE *f);
 void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f);
diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc
index 772eea7a5d78a..476529555e93e 100644
--- a/ext/opcache/jit/ir/ir_aarch64.dasc
+++ b/ext/opcache/jit/ir/ir_aarch64.dasc
@@ -996,6 +996,7 @@ binop_fp:
 		case IR_IF_TRUE:
 		case IR_IF_FALSE:
 		case IR_CASE_VAL:
+		case IR_CASE_RANGE:
 		case IR_CASE_DEFAULT:
 		case IR_MERGE:
 		case IR_LOOP_BEGIN:
@@ -4366,11 +4367,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	ir_backend_data *data = ctx->data;
 	dasm_State **Dst = &data->dasm_state;
 	ir_type type = insn->type;
-	ir_reg def_reg = ctx->regs[def][0];
+	ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
 	ir_reg op2_reg = ctx->regs[def][2];
 	ir_reg tmp_reg = ctx->regs[def][3];
 	int32_t offset;
 
+	if (ctx->use_lists[def].count == 1) {
+		/* dead load */
+		return;
+	}
 	IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE);
 	if (op2_reg != IR_REG_NONE) {
 		if (IR_REG_SPILLED(op2_reg)) {
@@ -4394,11 +4399,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	ir_backend_data *data = ctx->data;
 	dasm_State **Dst = &data->dasm_state;
 	ir_type type = insn->type;
-	ir_reg def_reg = ctx->regs[def][0];
+	ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
 	ir_reg op2_reg = ctx->regs[def][2];
 	ir_reg tmp_reg = ctx->regs[def][3];
 	int32_t offset;
 
+	if (ctx->use_lists[def].count == 1) {
+		/* dead load */
+		return;
+	}
 	IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE);
 	if (op2_reg != IR_REG_NONE) {
 		if (IR_REG_SPILLED(op2_reg)) {
@@ -4465,6 +4474,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 	int count = 0;
 	ir_val min, max;
 	ir_reg op1_reg, op2_reg, tmp_reg;
+	bool has_case_range = 0;
 
 	type = ctx->ir_base[insn->op2].type;
 	if (IR_IS_TYPE_SIGNED(type)) {
@@ -4493,6 +4503,22 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 				max.u64 = (int64_t)IR_MAX(max.u64, val->val.u64);
 			}
 			count++;
+		} else if (use_insn->op == IR_CASE_RANGE) {
+			has_case_range = 1;
+			val = &ctx->ir_base[use_insn->op2];
+			IR_ASSERT(!IR_IS_SYM_CONST(val->op));
+			ir_insn *val2 = &ctx->ir_base[use_insn->op3];
+			IR_ASSERT(!IR_IS_SYM_CONST(val2->op));
+			if (IR_IS_TYPE_SIGNED(type)) {
+				IR_ASSERT(IR_IS_TYPE_SIGNED(val->type));
+				min.i64 = IR_MIN(min.i64, val->val.i64);
+				max.i64 = IR_MAX(max.i64, val2->val.i64);
+			} else {
+				IR_ASSERT(!IR_IS_TYPE_SIGNED(val->type));
+				min.u64 = (int64_t)IR_MIN(min.u64, val->val.u64);
+				max.u64 = (int64_t)IR_MAX(max.u64, val2->val.u64);
+			}
+			count++;
 		} else {
 			IR_ASSERT(use_insn->op == IR_CASE_DEFAULT);
 			default_label = ir_skip_empty_target_blocks(ctx, use_block);
@@ -4510,7 +4536,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 	}
 
 	/* Generate a table jmp or a sequence of calls */
-	if (count > 2 && (max.i64-min.i64) < count * 8) {
+	if (!has_case_range && count > 2 && (max.i64-min.i64) < count * 8) {
 		int *labels = ir_mem_malloc(sizeof(int) * (max.i64 - min.i64 + 1));
 
 		for (i = 0; i <= (max.i64 - min.i64); i++) {
@@ -4615,6 +4641,38 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 
 				}
 				|	beq =>label
+			} else if (use_insn->op == IR_CASE_RANGE) {
+				val = &ctx->ir_base[use_insn->op2];
+				IR_ASSERT(!IR_IS_SYM_CONST(val->op));
+				label = ir_skip_empty_target_blocks(ctx, use_block);
+				if (aarch64_may_encode_imm12(val->val.i64)) {
+					|	ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i64
+				} else {
+					ir_emit_load_imm_int(ctx, type, tmp_reg, val->val.i64);
+					|	ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg
+
+				}
+				if (IR_IS_TYPE_SIGNED(type)) {
+					|	blt >1
+				} else {
+					|	blo >1
+				}
+				val = &ctx->ir_base[use_insn->op3];
+				IR_ASSERT(!IR_IS_SYM_CONST(val->op));
+				label = ir_skip_empty_target_blocks(ctx, use_block);
+				if (aarch64_may_encode_imm12(val->val.i64)) {
+					|	ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i64
+				} else {
+					ir_emit_load_imm_int(ctx, type, tmp_reg, val->val.i64);
+					|	ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg
+
+				}
+				if (IR_IS_TYPE_SIGNED(type)) {
+					|	ble =>label
+				} else {
+					|	bls =>label
+				}
+				|1:
 			}
 		}
 		if (default_label) {
@@ -4935,6 +4993,28 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 		return;
 	}
 
+	/* Move op2 to a tmp register before epilogue if it's in
+	 * used_preserved_regs, because it will be overridden. */
+
+	ir_reg op2_reg = IR_REG_NONE;
+	if (!IR_IS_CONST_REF(insn->op2)) {
+		op2_reg = ctx->regs[def][2];
+		IR_ASSERT(op2_reg != IR_REG_NONE);
+
+		if (IR_REG_SPILLED(op2_reg)) {
+			op2_reg = IR_REG_INT_TMP;
+			ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2);
+		} else if (IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, IR_REG_NUM(op2_reg))) {
+			ir_reg orig_op2_reg = op2_reg;
+			op2_reg = IR_REG_INT_TMP;
+
+			ir_type type = ctx->ir_base[insn->op2].type;
+			| ASM_REG_REG_OP mov, type, op2_reg, IR_REG_NUM(orig_op2_reg)
+		} else {
+			op2_reg = IR_REG_NUM(op2_reg);
+		}
+	}
+
 	ir_emit_epilogue(ctx);
 
 	if (IR_IS_CONST_REF(insn->op2)) {
@@ -4947,13 +5027,8 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 			|	br Rx(IR_REG_INT_TMP)
 		}
     } else {
-		ir_reg op2_reg = ctx->regs[def][2];
-
 		IR_ASSERT(op2_reg != IR_REG_NONE);
-		if (IR_REG_SPILLED(op2_reg)) {
-			op2_reg = IR_REG_NUM(op2_reg);
-			ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2);
-		}
+		IR_ASSERT(!IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, op2_reg));
 		|	br Rx(op2_reg)
     }
 }
@@ -5590,6 +5665,7 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx)
 				case IR_IF_TRUE:
 				case IR_IF_FALSE:
 				case IR_CASE_VAL:
+				case IR_CASE_RANGE:
 				case IR_CASE_DEFAULT:
 				case IR_MERGE:
 				case IR_LOOP_BEGIN:
diff --git a/ext/opcache/jit/ir/ir_builder.h b/ext/opcache/jit/ir/ir_builder.h
index 4e4ea53683afb..ba1924f8d65e5 100644
--- a/ext/opcache/jit/ir/ir_builder.h
+++ b/ext/opcache/jit/ir/ir_builder.h
@@ -603,6 +603,7 @@ extern "C" {
 #define ir_LOOP_END()                     _ir_LOOP_END(_ir_CTX)
 #define ir_SWITCH(_val)                   _ir_SWITCH(_ir_CTX, (_val))
 #define ir_CASE_VAL(_switch, _val)        _ir_CASE_VAL(_ir_CTX, (_switch), (_val))
+#define ir_CASE_RANGE(_switch, _v1, _v2)  _ir_CASE_RANGE(_ir_CTX, (_switch), (_v1), (_v2))
 #define ir_CASE_DEFAULT(_switch)          _ir_CASE_DEFAULT(_ir_CTX, (_switch))
 #define ir_RETURN(_val)                   _ir_RETURN(_ir_CTX, (_val))
 #define ir_IJMP(_addr)                    _ir_IJMP(_ir_CTX, (_addr))
@@ -682,6 +683,7 @@ ir_ref _ir_TLS(ir_ctx *ctx, ir_ref index, ir_ref offset);
 void   _ir_UNREACHABLE(ir_ctx *ctx);
 ir_ref _ir_SWITCH(ir_ctx *ctx, ir_ref val);
 void   _ir_CASE_VAL(ir_ctx *ctx, ir_ref switch_ref, ir_ref val);
+void   _ir_CASE_RANGE(ir_ctx *ctx, ir_ref switch_ref, ir_ref v1, ir_ref v2);
 void   _ir_CASE_DEFAULT(ir_ctx *ctx, ir_ref switch_ref);
 void   _ir_RETURN(ir_ctx *ctx, ir_ref val);
 void   _ir_IJMP(ir_ctx *ctx, ir_ref addr);
diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c
index 01532c8ea3e30..13d66a7130283 100644
--- a/ext/opcache/jit/ir/ir_cfg.c
+++ b/ext/opcache/jit/ir/ir_cfg.c
@@ -244,7 +244,6 @@ int ir_build_cfg(ir_ctx *ctx)
 		_blocks[start] = b;
 		_blocks[end] = b;
 		IR_ASSERT(IR_IS_BB_START(insn->op));
-		IR_ASSERT(end > start);
 		bb->start = start;
 		bb->end = end;
 		bb->successors = count;
@@ -583,7 +582,6 @@ static int ir_remove_unreachable_blocks(ir_ctx *ctx)
 	return 1;
 }
 
-#if 0
 static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b)
 {
 	uint32_t i, *p;
@@ -607,34 +605,42 @@ static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b)
 
 /* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by
  * Cooper, Harvey and Kennedy. */
-int ir_build_dominators_tree(ir_ctx *ctx)
+static int ir_build_dominators_tree_slow(ir_ctx *ctx)
 {
 	uint32_t blocks_count, b, postnum;
 	ir_block *blocks, *bb;
 	uint32_t *edges;
 	bool changed;
 
+	blocks = ctx->cfg_blocks;
+	edges  = ctx->cfg_edges;
+	blocks_count = ctx->cfg_blocks_count;
+
+	/* Clear the dominators tree */
+	for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) {
+		bb->idom = 0;
+		bb->dom_depth = 0;
+		bb->dom_child = 0;
+		bb->dom_next_child = 0;
+	}
+
 	ctx->flags2 &= ~IR_NO_LOOPS;
 
 	postnum = 1;
 	compute_postnum(ctx, &postnum, 1);
 
-	/* Find immediate dominators */
-	blocks = ctx->cfg_blocks;
-	edges  = ctx->cfg_edges;
-	blocks_count = ctx->cfg_blocks_count;
+	/* Find immediate dominators by iterative fixed-point algorithm */
 	blocks[1].idom = 1;
 	do {
 		changed = 0;
 		/* Iterating in Reverse Post Order */
 		for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) {
 			IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE));
+			IR_ASSERT(bb->predecessors_count > 0);
 			if (bb->predecessors_count == 1) {
 				uint32_t pred_b = edges[bb->predecessors];
 
-				if (blocks[pred_b].idom <= 0) {
-					//IR_ASSERT("Wrong blocks order: BB is before its single predecessor");
-				} else if (bb->idom != pred_b) {
+				if (blocks[pred_b].idom > 0 && bb->idom != pred_b) {
 					bb->idom = pred_b;
 					changed = 1;
 				}
@@ -680,39 +686,53 @@ int ir_build_dominators_tree(ir_ctx *ctx)
 			}
 		}
 	} while (changed);
+
+	/* Build dominators tree */
 	blocks[1].idom = 0;
 	blocks[1].dom_depth = 0;
+	for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) {
+		uint32_t idom = bb->idom;
+		ir_block *idom_bb = &blocks[idom];
+
+		bb->dom_depth = 0;
+		/* Sort by block number to traverse children in pre-order */
+		if (idom_bb->dom_child == 0) {
+			idom_bb->dom_child = b;
+		} else if (b < idom_bb->dom_child) {
+			bb->dom_next_child = idom_bb->dom_child;
+			idom_bb->dom_child = b;
+		} else {
+			int child = idom_bb->dom_child;
+			ir_block *child_bb = &blocks[child];
 
-	/* Construct dominators tree */
+			while (child_bb->dom_next_child > 0 && b > child_bb->dom_next_child) {
+				child = child_bb->dom_next_child;
+				child_bb = &blocks[child];
+			}
+			bb->dom_next_child = child_bb->dom_next_child;
+			child_bb->dom_next_child = b;
+		}
+	}
+
+	/* Recalculate dom_depth for all blocks */
 	for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) {
-		IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE));
-		if (bb->idom > 0) {
-			ir_block *idom_bb = &blocks[bb->idom];
-
-			bb->dom_depth = idom_bb->dom_depth + 1;
-			/* Sort by block number to traverse children in pre-order */
-			if (idom_bb->dom_child == 0) {
-				idom_bb->dom_child = b;
-			} else if (b < idom_bb->dom_child) {
-				bb->dom_next_child = idom_bb->dom_child;
-				idom_bb->dom_child = b;
+		uint32_t idom = bb->idom;
+		uint32_t dom_depth = 0;
+		while (idom) {
+			dom_depth++;
+			if (blocks[idom].dom_depth > 0) {
+				dom_depth += blocks[idom].dom_depth;
+				break;
 			} else {
-				int child = idom_bb->dom_child;
-				ir_block *child_bb = &blocks[child];
-
-				while (child_bb->dom_next_child > 0 && b > child_bb->dom_next_child) {
-					child = child_bb->dom_next_child;
-					child_bb = &blocks[child];
-				}
-				bb->dom_next_child = child_bb->dom_next_child;
-				child_bb->dom_next_child = b;
+				idom = blocks[idom].idom;
 			}
 		}
+		bb->dom_depth = dom_depth;
 	}
 
 	return 1;
 }
-#else
+
 /* A single pass modification of "A Simple, Fast Dominance Algorithm" by
  * Cooper, Harvey and Kennedy, that relays on IR block ordering.
  * It may fallback to the general slow fixed-point algorithm.  */
@@ -747,7 +767,11 @@ int ir_build_dominators_tree(ir_ctx *ctx)
 		if (UNEXPECTED(idom >= b)) {
 			/* In rare cases, LOOP_BEGIN.op1 may be a back-edge. Skip back-edges. */
 			ctx->flags2 &= ~IR_NO_LOOPS;
-			IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor");
+//			IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor");
+			if (UNEXPECTED(k <= 1)) {
+				ir_list_free(&worklist);
+				return ir_build_dominators_tree_slow(ctx);
+			}
 			ir_list_push(&worklist, idom);
 			while (1) {
 				k--;
@@ -942,7 +966,6 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx)
 
 	return 1;
 }
-#endif
 
 static bool ir_dominates(const ir_block *blocks, uint32_t b1, uint32_t b2)
 {
@@ -958,7 +981,7 @@ static bool ir_dominates(const ir_block *blocks, uint32_t b1, uint32_t b2)
 
 int ir_find_loops(ir_ctx *ctx)
 {
-	uint32_t i, j, n, count;
+	uint32_t b, j, n, count;
 	uint32_t *entry_times, *exit_times, *sorted_blocks, time = 1;
 	ir_block *blocks = ctx->cfg_blocks;
 	uint32_t *edges = ctx->cfg_edges;
@@ -983,13 +1006,13 @@ int ir_find_loops(ir_ctx *ctx)
 		int child;
 
 next:
-		i = ir_worklist_peek(&work);
-		if (!entry_times[i]) {
-			entry_times[i] = time++;
+		b = ir_worklist_peek(&work);
+		if (!entry_times[b]) {
+			entry_times[b] = time++;
 		}
 
-		/* Visit blocks immediately dominated by i. */
-		bb = &blocks[i];
+		/* Visit blocks immediately dominated by "b". */
+		bb = &blocks[b];
 		for (child = bb->dom_child; child > 0; child = blocks[child].dom_next_child) {
 			if (ir_worklist_push(&work, child)) {
 				goto next;
@@ -999,17 +1022,17 @@ int ir_find_loops(ir_ctx *ctx)
 		/* Visit join edges. */
 		if (bb->successors_count) {
 			uint32_t *p = edges + bb->successors;
-			for (j = 0; j < bb->successors_count; j++,p++) {
+			for (j = 0; j < bb->successors_count; j++, p++) {
 				uint32_t succ = *p;
 
-				if (blocks[succ].idom == i) {
+				if (blocks[succ].idom == b) {
 					continue;
 				} else if (ir_worklist_push(&work, succ)) {
 					goto next;
 				}
 			}
 		}
-		exit_times[i] = time++;
+		exit_times[b] = time++;
 		ir_worklist_pop(&work);
 	}
 
@@ -1018,7 +1041,7 @@ int ir_find_loops(ir_ctx *ctx)
 	j = 1;
 	n = 2;
 	while (j != n) {
-		i = j;
+		uint32_t i = j;
 		j = n;
 		for (; i < j; i++) {
 			int child;
@@ -1030,9 +1053,82 @@ int ir_find_loops(ir_ctx *ctx)
 	count = n;
 
 	/* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs". */
+	uint32_t prev_dom_depth = blocks[sorted_blocks[n - 1]].dom_depth;
+	uint32_t prev_irreducible = 0;
 	while (n > 1) {
-		i = sorted_blocks[--n];
-		ir_block *bb = &blocks[i];
+		b = sorted_blocks[--n];
+		ir_block *bb = &blocks[b];
+
+		IR_ASSERT(bb->dom_depth <= prev_dom_depth);
+		if (UNEXPECTED(prev_irreducible) && bb->dom_depth != prev_dom_depth) {
+			/* process delyed irreducible loops */
+			do {
+				b = sorted_blocks[prev_irreducible];
+				bb = &blocks[b];
+				if ((bb->flags & IR_BB_IRREDUCIBLE_LOOP) && !bb->loop_depth) {
+					/* process irreducible loop */
+					uint32_t hdr = b;
+
+					bb->loop_depth = 1;
+					if (ctx->ir_base[bb->start].op == IR_MERGE) {
+						ctx->ir_base[bb->start].op = IR_LOOP_BEGIN;
+					}
+
+					/* find the closing edge(s) of the irreucible loop */
+					IR_ASSERT(bb->predecessors_count > 1);
+					uint32_t *p = &edges[bb->predecessors];
+					j = bb->predecessors_count;
+					do {
+						uint32_t pred = *p;
+
+						if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) {
+							if (!ir_worklist_len(&work)) {
+								ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work)));
+							}
+							blocks[pred].loop_header = 0; /* support for merged loops */
+							ir_worklist_push(&work, pred);
+						}
+						p++;
+					} while (--j);
+					if (ir_worklist_len(&work) == 0) continue;
+
+					/* collect members of the irreducible loop */
+					while (ir_worklist_len(&work)) {
+						b = ir_worklist_pop(&work);
+						if (b != hdr) {
+							ir_block *bb = &blocks[b];
+							bb->loop_header = hdr;
+							if (bb->predecessors_count) {
+								uint32_t *p = &edges[bb->predecessors];
+								uint32_t n = bb->predecessors_count;
+								do {
+									uint32_t pred = *p;
+									while (blocks[pred].loop_header > 0) {
+										pred = blocks[pred].loop_header;
+									}
+									if (pred != hdr) {
+										if (entry_times[pred] > entry_times[hdr] && exit_times[pred] < exit_times[hdr]) {
+											/* "pred" is a descendant of "hdr" */
+											ir_worklist_push(&work, pred);
+										} else {
+											/* another entry to the irreducible loop */
+											bb->flags |= IR_BB_IRREDUCIBLE_LOOP;
+											if (ctx->ir_base[bb->start].op == IR_MERGE) {
+												ctx->ir_base[bb->start].op = IR_LOOP_BEGIN;
+											}
+										}
+									}
+									p++;
+								} while (--n);
+							}
+						}
+					}
+				}
+			} while (--prev_irreducible != n);
+			prev_irreducible = 0;
+			b = sorted_blocks[n];
+			bb = &blocks[b];
+		}
 
 		if (bb->predecessors_count > 1) {
 			bool irreducible = 0;
@@ -1047,7 +1143,7 @@ int ir_find_loops(ir_ctx *ctx)
 				if (bb->idom != pred) {
 					/* In a loop back-edge (back-join edge), the successor dominates
 					   the predecessor.  */
-					if (ir_dominates(blocks, i, pred)) {
+					if (ir_dominates(blocks, b, pred)) {
 						if (!ir_worklist_len(&work)) {
 							ir_bitset_clear(work.visited, ir_bitset_len(ir_worklist_capasity(&work)));
 						}
@@ -1056,8 +1152,9 @@ int ir_find_loops(ir_ctx *ctx)
 					} else {
 						/* Otherwise it's a cross-join edge.  See if it's a branch
 						   to an ancestor on the DJ spanning tree.  */
-						if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) {
+						if (entry_times[pred] > entry_times[b] && exit_times[pred] < exit_times[b]) {
 							irreducible = 1;
+							break;
 						}
 					}
 				}
@@ -1065,46 +1162,56 @@ int ir_find_loops(ir_ctx *ctx)
 			} while (--j);
 
 			if (UNEXPECTED(irreducible)) {
-				// TODO: Support for irreducible loops ???
-				bb->flags |= IR_BB_IRREDUCIBLE_LOOP;
-				ctx->flags2 |= IR_IRREDUCIBLE_CFG;
-				while (ir_worklist_len(&work)) {
-					ir_worklist_pop(&work);
+				bb->flags |= IR_BB_LOOP_HEADER | IR_BB_IRREDUCIBLE_LOOP;
+				ctx->flags2 |= IR_CFG_HAS_LOOPS | IR_IRREDUCIBLE_CFG;
+				/* Remember the position of the first irreducible loop to process all the irreducible loops
+				 * after the reducible loops with the same dominator tree depth
+				 */
+				if (!prev_irreducible) {
+					prev_irreducible = n;
+					prev_dom_depth = bb->dom_depth;
 				}
+				ir_list_clear(&work.l);
 			} else if (ir_worklist_len(&work)) {
+				/* collect members of the reducible loop */
+				uint32_t hdr = b;
+
 				bb->flags |= IR_BB_LOOP_HEADER;
 				ctx->flags2 |= IR_CFG_HAS_LOOPS;
 				bb->loop_depth = 1;
+				if (ctx->ir_base[bb->start].op == IR_MERGE) {
+					ctx->ir_base[bb->start].op = IR_LOOP_BEGIN;
+				}
 				while (ir_worklist_len(&work)) {
-					j = ir_worklist_pop(&work);
-					while (blocks[j].loop_header > 0) {
-						j = blocks[j].loop_header;
-					}
-					if (j != i) {
-						ir_block *bb = &blocks[j];
-						if (bb->idom == 0 && j != 1) {
-							/* Ignore blocks that are unreachable or only abnormally reachable. */
-							continue;
-						}
-						bb->loop_header = i;
+					b = ir_worklist_pop(&work);
+					if (b != hdr) {
+						ir_block *bb = &blocks[b];
+						bb->loop_header = hdr;
 						if (bb->predecessors_count) {
 							uint32_t *p = &edges[bb->predecessors];
-							j = bb->predecessors_count;
+							uint32_t n = bb->predecessors_count;
 							do {
-								ir_worklist_push(&work, *p);
+								uint32_t pred = *p;
+								while (blocks[pred].loop_header > 0) {
+									pred = blocks[pred].loop_header;
+								}
+								if (pred != hdr) {
+									ir_worklist_push(&work, pred);
+								}
 								p++;
-							} while (--j);
+							} while (--n);
 						}
 					}
 				}
 			}
 		}
 	}
+	IR_ASSERT(!prev_irreducible);
 
 	if (ctx->flags2 & IR_CFG_HAS_LOOPS) {
 		for (n = 1; n < count; n++) {
-			i = sorted_blocks[n];
-			ir_block *bb = &blocks[i];
+			b = sorted_blocks[n];
+			ir_block *bb = &blocks[b];
 			if (bb->loop_header > 0) {
 				ir_block *loop = &blocks[bb->loop_header];
 				uint32_t loop_depth = loop->loop_depth;
@@ -1389,7 +1496,7 @@ static int ir_schedule_blocks_bottom_up(ir_ctx *ctx)
 						goto restart;
 					}
 				} else if (b != predecessor && ctx->cfg_blocks[predecessor].loop_header != b) {
-					ir_dump_cfg(ctx, stderr);
+					/* not a loop back-edge */
 					IR_ASSERT(b == predecessor || ctx->cfg_blocks[predecessor].loop_header == b);
 				}
 			}
diff --git a/ext/opcache/jit/ir/ir_check.c b/ext/opcache/jit/ir/ir_check.c
index f12b4776fa1e0..a791baef5db92 100644
--- a/ext/opcache/jit/ir/ir_check.c
+++ b/ext/opcache/jit/ir/ir_check.c
@@ -213,13 +213,18 @@ bool ir_check(const ir_ctx *ctx)
 									ok = 0;
 								}
 							}
-							break;
-						case IR_OPND_CONTROL_DEP:
 							if ((ctx->flags2 & IR_LINEAR)
 							 && use >= i
 							 && !(insn->op == IR_LOOP_BEGIN)) {
 								fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use);
 								ok = 0;
+							}
+							break;
+						case IR_OPND_CONTROL_DEP:
+							if ((ctx->flags2 & IR_LINEAR)
+							 && use >= i) {
+								fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use);
+								ok = 0;
 							} else if (insn->op == IR_PHI) {
 								ir_insn *merge_insn = &ctx->ir_base[insn->op1];
 								if (merge_insn->op != IR_MERGE && merge_insn->op != IR_LOOP_BEGIN) {
diff --git a/ext/opcache/jit/ir/ir_emit.c b/ext/opcache/jit/ir/ir_emit.c
index c82655daf48db..fab9f56228d80 100644
--- a/ext/opcache/jit/ir/ir_emit.c
+++ b/ext/opcache/jit/ir/ir_emit.c
@@ -309,7 +309,7 @@ static void* ir_sym_addr(ir_ctx *ctx, const ir_insn *addr_insn)
 {
 	const char *name = ir_get_str(ctx, addr_insn->val.name);
 	void *addr = (ctx->loader && ctx->loader->resolve_sym_name) ?
-		ctx->loader->resolve_sym_name(ctx->loader, name, 0) :
+		ctx->loader->resolve_sym_name(ctx->loader, name, IR_RESOLVE_SYM_SILENT) :
 		ir_resolve_sym_name(name);
 
 	return addr;
@@ -320,7 +320,7 @@ static void* ir_sym_val(ir_ctx *ctx, const ir_insn *addr_insn)
 {
 	const char *name = ir_get_str(ctx, addr_insn->val.name);
 	void *addr = (ctx->loader && ctx->loader->resolve_sym_name) ?
-		ctx->loader->resolve_sym_name(ctx->loader, name, addr_insn->op == IR_FUNC) :
+		ctx->loader->resolve_sym_name(ctx->loader, name, addr_insn->op == IR_FUNC ? IR_RESOLVE_SYM_ADD_THUNK : 0) :
 		ir_resolve_sym_name(name);
 
 	IR_ASSERT(addr);
diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h
index 88539e52ab085..90112214d0c8b 100644
--- a/ext/opcache/jit/ir/ir_fold.h
+++ b/ext/opcache/jit/ir/ir_fold.h
@@ -1909,7 +1909,9 @@ IR_FOLD(SUB(_, SUB))
 IR_FOLD(SUB(ADD, ADD))
 {
 	if (IR_IS_TYPE_INT(IR_OPT_TYPE(opt))) {
-		if (op1_insn->op1 == op2_insn->op1) {
+		if (op1 == op2) {
+			IR_FOLD_CONST_U(0);
+		} else if (op1_insn->op1 == op2_insn->op1) {
 			/* (a + b) - (a + c) => b - c */
 			op1 = op1_insn->op2;
 			op2 = op2_insn->op2;
diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c
index 8bd6be5d10aa0..4d518d20079fb 100644
--- a/ext/opcache/jit/ir/ir_gcm.c
+++ b/ext/opcache/jit/ir/ir_gcm.c
@@ -785,6 +785,139 @@ IR_ALWAYS_INLINE ir_ref ir_count_constant(ir_ref *_xlat, ir_ref ref)
 	return 0;
 }
 
+IR_ALWAYS_INLINE bool ir_is_good_bb_order(ir_ctx *ctx, uint32_t b, ir_block *bb, ir_ref start)
+{
+	ir_insn	*insn = &ctx->ir_base[start];
+	uint32_t n = insn->inputs_count;
+	ir_ref *p = insn->ops + 1;
+
+	if (n == 1) {
+		return *p < start;
+	} else {
+		IR_ASSERT(n > 1);
+		for (; n > 0; p++, n--) {
+			ir_ref input = *p;
+			if (input < start) {
+				/* ordered */
+			} else if ((bb->flags & IR_BB_LOOP_HEADER)
+			  && (ctx->cfg_map[input] == b || ctx->cfg_blocks[ctx->cfg_map[input]].loop_header == b)) {
+				/* back-edge of reducible loop */
+			} else if ((bb->flags & IR_BB_IRREDUCIBLE_LOOP)
+			  && (ctx->cfg_blocks[ctx->cfg_map[input]].loop_header == ctx->cfg_blocks[b].loop_header)) {
+				/* closing edge of irreducible loop */
+			} else {
+				return 0;
+			}
+		}
+		return 1;
+	}
+}
+
+static IR_NEVER_INLINE void ir_fix_bb_order(ir_ctx *ctx, ir_ref *_prev, ir_ref *_next)
+{
+	uint32_t b, succ, count, *q, *xlat;
+	ir_block *bb;
+	ir_ref ref, n, prev;
+	ir_worklist worklist;
+	ir_block *new_blocks;
+
+#if 0
+	for (b = 1, bb = ctx->cfg_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) {
+		if (!ir_is_good_bb_order(ctx, b, bb, bb->start)) {
+			goto fix;
+		}
+	}
+	return;
+
+fix:
+#endif
+	count = ctx->cfg_blocks_count + 1;
+	new_blocks = ir_mem_malloc(count * sizeof(ir_block));
+	xlat = ir_mem_malloc(count * sizeof(uint32_t));
+	ir_worklist_init(&worklist, count);
+	ir_worklist_push(&worklist, 1);
+	while (ir_worklist_len(&worklist) != 0) {
+next:
+		b = ir_worklist_peek(&worklist);
+		bb = &ctx->cfg_blocks[b];
+		n = bb->successors_count;
+		if (n == 1) {
+			succ = ctx->cfg_edges[bb->successors];
+			if (ir_worklist_push(&worklist, succ)) {
+				goto next;
+			}
+		} else if (n > 1) {
+			uint32_t best = 0;
+			uint32_t best_loop_depth = 0;
+
+			q = ctx->cfg_edges + bb->successors + n;
+			do {
+				q--;
+				succ = *q;
+				if (ir_bitset_in(worklist.visited, succ)) {
+					/* already processed */
+				} else if ((ctx->cfg_blocks[succ].flags & IR_BB_LOOP_HEADER)
+				  && (succ == b || ctx->cfg_blocks[b].loop_header == succ)) {
+					/* back-edge of reducible loop */
+				} else if ((ctx->cfg_blocks[succ].flags & IR_BB_IRREDUCIBLE_LOOP)
+				  && (ctx->cfg_blocks[succ].loop_header == ctx->cfg_blocks[b].loop_header)) {
+					/* closing edge of irreducible loop */
+				} else if (!best) {
+					best = succ;
+					best_loop_depth = ctx->cfg_blocks[best].loop_depth;
+				} else if (ctx->cfg_blocks[succ].loop_depth < best_loop_depth) {
+					/* prefer deeper loop */
+					best = succ;
+					best_loop_depth = ctx->cfg_blocks[best].loop_depth;
+				}
+				n--;
+			} while (n > 0);
+			if (best) {
+				ir_worklist_push(&worklist, best);
+				goto next;
+			}
+		}
+		ir_worklist_pop(&worklist);
+		count--;
+		new_blocks[count] = *bb;
+		xlat[b] = count;
+	}
+	IR_ASSERT(count == 1);
+	xlat[0] = 0;
+	ir_worklist_free(&worklist);
+
+	prev = 0;
+	for (b = 1, bb = new_blocks + 1; b <= ctx->cfg_blocks_count; b++, bb++) {
+		bb->idom = xlat[bb->idom];
+		bb->loop_header = xlat[bb->loop_header];
+		n = bb->successors_count;
+		if (n > 0) {
+			for (q = ctx->cfg_edges + bb->successors; n > 0; q++, n--) {
+				*q = xlat[*q];
+			}
+		}
+		n = bb->predecessors_count;
+		if (n > 0) {
+			for (q = ctx->cfg_edges + bb->predecessors; n > 0; q++, n--) {
+				*q = xlat[*q];
+			}
+		}
+		_next[prev] = bb->start;
+		_prev[bb->start] = prev;
+		prev = bb->end;
+	}
+	_next[0] = 0;
+	_next[prev] = 0;
+
+	for (ref = 2; ref < ctx->insns_count; ref++) {
+		ctx->cfg_map[ref] = xlat[ctx->cfg_map[ref]];
+	}
+	ir_mem_free(xlat);
+
+	ir_mem_free(ctx->cfg_blocks);
+	ctx->cfg_blocks = new_blocks;
+}
+
 int ir_schedule(ir_ctx *ctx)
 {
 	ir_ctx new_ctx;
@@ -800,6 +933,7 @@ int ir_schedule(ir_ctx *ctx)
 	ir_block *bb;
 	ir_insn *insn, *new_insn;
 	ir_use_list *lists, *use_list, *new_list;
+	bool bad_bb_order = 0;
 
 	/* Create a double-linked list of nodes ordered by BB, respecting BB->start and BB->end */
 	IR_ASSERT(_blocks[1] == 1);
@@ -818,27 +952,61 @@ int ir_schedule(ir_ctx *ctx)
 		} else if (b > prev_b) {
 			bb = &ctx->cfg_blocks[b];
 			if (i == bb->start) {
-				IR_ASSERT(bb->end > bb->start);
-				prev_b = b;
-				prev_b_end = bb->end;
-				_prev[bb->end] = 0;
-				/* add to the end of the list */
-				_next[j] = i;
-				_prev[i] = j;
-				j = i;
-			} else {
-				IR_ASSERT(i != bb->end);
+				if (bb->end > bb->start) {
+					prev_b = b;
+					prev_b_end = bb->end;
+					/* add to the end of the list */
+					_next[j] = i;
+					_prev[i] = j;
+					j = i;
+				} else {
+					prev_b = 0;
+					prev_b_end = 0;
+					k = bb->end;
+					while (_blocks[_prev[k]] == b) {
+						k = _prev[k];
+					}
+					/* insert before "k" */
+					_prev[i] = _prev[k];
+					_next[i] = k;
+					_next[_prev[k]] = i;
+					_prev[k] = i;
+				}
+				if (!ir_is_good_bb_order(ctx, b, bb, i)) {
+					bad_bb_order = 1;
+				}
+			} else if (i != bb->end) {
 				/* move down late (see the following loop) */
 				_next[i] = _move_down;
 				_move_down = i;
+			} else {
+				prev_b = 0;
+				prev_b_end = 0;
+				if (bb->start > bb->end) {
+					/* add to the end of the list */
+					_next[j] = i;
+					_prev[i] = j;
+					j = i;
+				} else {
+					k = bb->start;
+					while (_blocks[_next[k]] == b) {
+						k = _next[k];
+					}
+					/* insert after "k" */
+					_next[i] = _next[k];
+					_prev[i] = k;
+					_prev[_next[k]] = i;
+					_next[k] = i;
+				}
 			}
 		} else if (b) {
 			bb = &ctx->cfg_blocks[b];
 			IR_ASSERT(i != bb->start);
-			if (_prev[bb->end]) {
+			if (i > bb->end) {
 				/* move up, insert before the end of the already scheduled BB */
 				k = bb->end;
 			} else {
+				IR_ASSERT(i > bb->start);
 				/* move up, insert at the end of the block */
 				k = ctx->cfg_blocks[b + 1].start;
 			}
@@ -883,6 +1051,10 @@ int ir_schedule(ir_ctx *ctx)
 	}
 #endif
 
+	if (bad_bb_order) {
+		ir_fix_bb_order(ctx, _prev, _next);
+	}
+
 	_xlat = ir_mem_calloc((ctx->consts_count + ctx->insns_count), sizeof(ir_ref));
 	_xlat += ctx->consts_count;
 	_xlat[IR_TRUE] = IR_TRUE;
@@ -904,6 +1076,11 @@ int ir_schedule(ir_ctx *ctx)
 		if (insn->op == IR_CASE_VAL) {
 			IR_ASSERT(insn->op2 < IR_TRUE);
 			consts_count += ir_count_constant(_xlat, insn->op2);
+		} else if (insn->op == IR_CASE_RANGE) {
+			IR_ASSERT(insn->op2 < IR_TRUE);
+			consts_count += ir_count_constant(_xlat, insn->op2);
+			IR_ASSERT(insn->op3 < IR_TRUE);
+			consts_count += ir_count_constant(_xlat, insn->op3);
 		}
 		n = insn->inputs_count;
 		insns_count += ir_insn_inputs_to_len(n);
diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h
index 69a0101d24ee2..369b4c34e3793 100644
--- a/ext/opcache/jit/ir/ir_private.h
+++ b/ext/opcache/jit/ir/ir_private.h
@@ -9,6 +9,7 @@
 #define IR_PRIVATE_H
 #include <string.h>
 #include <stdlib.h>
+#include <stddef.h>
 
 #ifdef IR_DEBUG
 # include <assert.h>
@@ -62,7 +63,7 @@
 #define IR_MAX(a, b)          (((a) > (b)) ? (a) : (b))
 #define IR_MIN(a, b)          (((a) < (b)) ? (a) : (b))
 
-#define IR_IS_POWER_OF_TWO(x) (!((x) & ((x) - 1)))
+#define IR_IS_POWER_OF_TWO(x) ((x) && (!((x) & ((x) - 1))))
 
 #define IR_LOG2(x) ir_ntzl(x)
 
@@ -257,7 +258,7 @@ IR_ALWAYS_INLINE void* ir_arena_alloc(ir_arena **arena_ptr, size_t size)
 	ir_arena *arena = *arena_ptr;
 	char *ptr = (char*)IR_ALIGNED_SIZE((uintptr_t)arena->ptr, 8);
 
-	if (EXPECTED(size <= (size_t)(arena->end - ptr))) {
+	if (EXPECTED((ptrdiff_t)size <= (ptrdiff_t)(arena->end - ptr))) {
 		arena->ptr = ptr + size;
 	} else {
 		size_t arena_size =
diff --git a/ext/opcache/jit/ir/ir_save.c b/ext/opcache/jit/ir/ir_save.c
index b12cc267af607..ea787f162ec1f 100644
--- a/ext/opcache/jit/ir/ir_save.c
+++ b/ext/opcache/jit/ir/ir_save.c
@@ -97,10 +97,14 @@ void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f)
 	for (i = IR_UNUSED + 1, insn = ctx->ir_base - i; i < ctx->consts_count; i++, insn--) {
 		fprintf(f, "\t%s c_%d = ", ir_type_cname[insn->type], i);
 		if (insn->op == IR_FUNC) {
-			fprintf(f, "func %s", ir_get_str(ctx, insn->val.name));
+			fprintf(f, "func %s%s",
+				(save_flags & IR_SAVE_SAFE_NAMES) ? "@" : "",
+				ir_get_str(ctx, insn->val.name));
 			ir_print_proto(ctx, insn->proto, f);
 		} else if (insn->op == IR_SYM) {
-			fprintf(f, "sym(%s)", ir_get_str(ctx, insn->val.name));
+			fprintf(f, "sym(%s%s)",
+				(save_flags & IR_SAVE_SAFE_NAMES) ? "@" : "",
+				ir_get_str(ctx, insn->val.name));
 		} else if (insn->op == IR_FUNC_ADDR) {
 			fprintf(f, "func *");
 			ir_print_const(ctx, insn, f, true);
@@ -140,6 +144,9 @@ void ir_save(const ir_ctx *ctx, uint32_t save_flags, FILE *f)
 					fprintf(f, ", loop=BB%d(%d)", bb->loop_header, bb->loop_depth);
 				}
 			}
+			if (bb->flags & IR_BB_IRREDUCIBLE_LOOP) {
+				fprintf(f, ", IRREDUCIBLE");
+			}
 			if (bb->predecessors_count) {
 				uint32_t i;
 
diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c
index 2e006516df818..ae0eebadee54a 100644
--- a/ext/opcache/jit/ir/ir_sccp.c
+++ b/ext/opcache/jit/ir/ir_sccp.c
@@ -458,6 +458,22 @@ static bool ir_sccp_is_equal(ir_ctx *ctx, ir_insn *_values, ir_ref a, ir_ref b)
 	return v1->val.u64 == v2->val.u64;
 }
 
+static bool ir_sccp_in_range(ir_ctx *ctx, ir_insn *_values, ir_ref a, ir_ref b, ir_ref c)
+{
+	ir_insn *v1 = IR_IS_CONST_REF(a) ? &ctx->ir_base[a] : &_values[a];
+	ir_insn *v2 = IR_IS_CONST_REF(b) ? &ctx->ir_base[b] : &_values[b];
+	ir_insn *v3 = IR_IS_CONST_REF(c) ? &ctx->ir_base[c] : &_values[c];
+
+	IR_ASSERT(!IR_IS_SYM_CONST(v1->op));
+	IR_ASSERT(!IR_IS_SYM_CONST(v2->op));
+	IR_ASSERT(!IR_IS_SYM_CONST(v3->op));
+	if (IR_IS_TYPE_SIGNED(v1->type)) {
+		return v1->val.i64 >= v2->val.i64 && v1->val.i64 <= v3->val.i64;
+	} else {
+		return v1->val.u64 >= v2->val.u64 && v1->val.u64 <= v3->val.u64;
+    }
+}
+
 #ifdef IR_SCCP_TRACE
 static void ir_sccp_trace_val(ir_ctx *ctx, ir_insn *_values, ir_ref i)
 {
@@ -676,6 +692,11 @@ static IR_NEVER_INLINE void ir_sccp_analyze(ir_ctx *ctx, ir_insn *_values, ir_bi
 							}
 						} else if (use_insn->op == IR_CASE_DEFAULT) {
 							use_case = use;
+						} else if (use_insn->op == IR_CASE_RANGE) {
+							if (ir_sccp_in_range(ctx, _values, insn->op2, use_insn->op2, use_insn->op3)) {
+								use_case = use;
+								break;
+							}
 						}
 					}
 					if (use_case) {
@@ -1732,7 +1753,20 @@ static ir_ref ir_promote_i2i(ir_ctx *ctx, ir_type type, ir_ref ref, ir_ref use,
 	ir_ref *p, n, input;
 
 	if (IR_IS_CONST_REF(ref)) {
-		return ir_const(ctx, insn->val, type);
+		ir_val val;
+
+		switch (type) {
+			case IR_I8:  val.i64 = insn->val.i8; break;
+			case IR_U8:  val.u64 = insn->val.u8; break;
+			case IR_I16: val.i64 = insn->val.i16; break;
+			case IR_U16: val.u64 = insn->val.u16; break;
+			case IR_I32: val.i64 = insn->val.i32; break;
+			case IR_U32: val.u64 = insn->val.u32; break;
+			case IR_CHAR:val.i64 = insn->val.i8; break;
+			case IR_BOOL:val.u64 = insn->val.u8 != 0; break;
+			default: IR_ASSERT(0); val.u64 = 0;
+		}
+		return ir_const(ctx, val, type);
 	} else {
 		ir_bitqueue_add(worklist, ref);
 		switch (insn->op) {
@@ -2391,7 +2425,7 @@ static bool ir_try_remove_empty_diamond(ir_ctx *ctx, ir_ref ref, ir_insn *insn,
 			}
 			start_ref = end->op1;
 			start = &ctx->ir_base[start_ref];
-			if (start->op != IR_CASE_VAL && start->op != IR_CASE_DEFAULT) {
+			if (start->op != IR_CASE_VAL && start->op != IR_CASE_RANGE && start->op != IR_CASE_DEFAULT) {
 				return 0;
 			}
 			if (ctx->use_lists[start_ref].count != 1) {
diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc
index 76602c2b4bcf5..d9967d24dbea2 100644
--- a/ext/opcache/jit/ir/ir_x86.dasc
+++ b/ext/opcache/jit/ir/ir_x86.dasc
@@ -1569,6 +1569,20 @@ op2_const:
 			constraints->tmp_regs[0] = IR_TMP_REG(1, IR_ADDR, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
 			n = 1;
 			break;
+		case IR_SSE_SQRT:
+		case IR_SSE_RINT:
+		case IR_SSE_FLOOR:
+		case IR_SSE_CEIL:
+		case IR_SSE_TRUNC:
+		case IR_SSE_NEARBYINT:
+			insn = &ctx->ir_base[ref];
+			flags = IR_USE_MUST_BE_IN_REG | IR_OP3_MUST_BE_IN_REG;
+			if (IR_IS_CONST_REF(insn->op3)) {
+				const ir_insn *val_insn = &ctx->ir_base[insn->op3];
+				constraints->tmp_regs[n] = IR_TMP_REG(3, val_insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
+				n = 1;
+			}
+			break;
 	}
 	constraints->tmps_count = n;
 
@@ -2630,6 +2644,7 @@ store_int:
 		case IR_IF_TRUE:
 		case IR_IF_FALSE:
 		case IR_CASE_VAL:
+		case IR_CASE_RANGE:
 		case IR_CASE_DEFAULT:
 		case IR_MERGE:
 		case IR_LOOP_BEGIN:
@@ -6868,7 +6883,24 @@ static void ir_emit_return_fp(ir_ctx *ctx, ir_ref ref, ir_insn *insn)
 	ir_backend_data *data = ctx->data;
 	dasm_State **Dst = &data->dasm_state;
 
-	if (op2_reg == IR_REG_NONE || IR_REG_SPILLED(op2_reg)) {
+	if (IR_IS_CONST_REF(insn->op2)) {
+		ir_insn *value = &ctx->ir_base[insn->op2];
+
+		if ((type == IR_FLOAT && value->val.f == 0.0) || (type == IR_DOUBLE && value->val.d == 0.0)) {
+			|	fldz
+		} else if ((type == IR_FLOAT && value->val.f == 1.0) || (type == IR_DOUBLE && value->val.d == 1.0)) {
+			|	fld1
+		} else {
+			int label = ir_const_label(ctx, insn->op2);
+
+			if (type == IR_DOUBLE) {
+				|	fld qword [=>label]
+			} else {
+				IR_ASSERT(type == IR_FLOAT);
+				|	fld dword [=>label]
+			}
+		}
+	} else if (op2_reg == IR_REG_NONE || IR_REG_SPILLED(op2_reg)) {
 		ir_reg fp;
 		int32_t offset = ir_ref_spill_slot_offset(ctx, insn->op2, &fp);
 
@@ -8442,11 +8474,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	ir_backend_data *data = ctx->data;
 	dasm_State **Dst = &data->dasm_state;
 	ir_type type = insn->type;
-	ir_reg def_reg = ctx->regs[def][0];
+	ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
 	ir_reg op2_reg = ctx->regs[def][2];
 	ir_reg tmp_reg = ctx->regs[def][3];
 	int32_t offset;
 
+	if (ctx->use_lists[def].count == 1) {
+		/* dead load */
+		return;
+	}
 	IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE);
 	if (op2_reg != IR_REG_NONE) {
 		if (IR_REG_SPILLED(op2_reg)) {
@@ -8471,11 +8507,15 @@ static void ir_emit_va_arg(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 	ir_backend_data *data = ctx->data;
 	dasm_State **Dst = &data->dasm_state;
 	ir_type type = insn->type;
-	ir_reg def_reg = ctx->regs[def][0];
+	ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
 	ir_reg op2_reg = ctx->regs[def][2];
 	ir_reg tmp_reg = ctx->regs[def][3];
 	int32_t offset;
 
+	if (ctx->use_lists[def].count == 1) {
+		/* dead load */
+		return;
+	}
 	IR_ASSERT(def_reg != IR_REG_NONE&& tmp_reg != IR_REG_NONE);
 	if (op2_reg != IR_REG_NONE) {
 		if (IR_REG_SPILLED(op2_reg)) {
@@ -8541,6 +8581,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 	ir_val min, max;
 	ir_reg op2_reg = ctx->regs[def][2];
 	ir_reg tmp_reg = ctx->regs[def][3];
+	bool has_case_range = 0;
 
 	type = ctx->ir_base[insn->op2].type;
 	IR_ASSERT(tmp_reg != IR_REG_NONE);
@@ -8570,6 +8611,21 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 				max.u64 = (int64_t)IR_MAX(max.u64, val->val.u64);
 			}
 			count++;
+		} else if (use_insn->op == IR_CASE_RANGE) {
+			has_case_range = 1;
+			val = &ctx->ir_base[use_insn->op2];
+			IR_ASSERT(!IR_IS_SYM_CONST(val->op));
+			ir_insn *val2 = &ctx->ir_base[use_insn->op3];
+			IR_ASSERT(!IR_IS_SYM_CONST(val2->op));
+			if (IR_IS_TYPE_SIGNED(type)) {
+				IR_ASSERT(IR_IS_TYPE_SIGNED(val->type));
+				min.i64 = IR_MIN(min.i64, val->val.i64);
+				max.i64 = IR_MAX(max.i64, val2->val.i64);
+			} else {
+				IR_ASSERT(!IR_IS_TYPE_SIGNED(val->type));
+				min.u64 = (int64_t)IR_MIN(min.u64, val->val.u64);
+				max.u64 = (int64_t)IR_MAX(max.u64, val2->val.u64);
+			}
 		} else {
 			IR_ASSERT(use_insn->op == IR_CASE_DEFAULT);
 			default_label = ir_skip_empty_target_blocks(ctx, use_block);
@@ -8583,7 +8639,7 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 	}
 
 	/* Generate a table jmp or a seqence of calls */
-	if (count > 2 && (max.i64-min.i64) < count * 8) {
+	if (!has_case_range && count > 2 && (max.i64-min.i64) < count * 8) {
 		int *labels = ir_mem_malloc(sizeof(int) * (size_t)(max.i64 - min.i64 + 1));
 
 		for (i = 0; i <= (max.i64 - min.i64); i++) {
@@ -8747,6 +8803,42 @@ static void ir_emit_switch(ir_ctx *ctx, uint32_t b, ir_ref def, ir_insn *insn)
 |.endif
 				}
 				|	je =>label
+			} else if (use_insn->op == IR_CASE_RANGE) {
+				val = &ctx->ir_base[use_insn->op2];
+				IR_ASSERT(!IR_IS_SYM_CONST(val->op));
+				label = ir_skip_empty_target_blocks(ctx, use_block);
+				if (IR_IS_32BIT(type, val->val)) {
+					|	ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i32
+				} else {
+					IR_ASSERT(sizeof(void*) == 8);
+|.if X64
+					|	mov64 Ra(tmp_reg), val->val.i64
+					|	ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg
+|.endif
+				}
+				if (IR_IS_TYPE_SIGNED(type)) {
+					|	jl >1
+				} else {
+					|	jb >1
+				}
+				val = &ctx->ir_base[use_insn->op3];
+				IR_ASSERT(!IR_IS_SYM_CONST(val->op3));
+				label = ir_skip_empty_target_blocks(ctx, use_block);
+				if (IR_IS_32BIT(type, val->val)) {
+					|	ASM_REG_IMM_OP cmp, type, op2_reg, val->val.i32
+				} else {
+					IR_ASSERT(sizeof(void*) == 8);
+|.if X64
+					|	mov64 Ra(tmp_reg), val->val.i64
+					|	ASM_REG_REG_OP cmp, type, op2_reg, tmp_reg
+|.endif
+				}
+				if (IR_IS_TYPE_SIGNED(type)) {
+					|	jle =>label
+				} else {
+					|	jbe =>label
+				}
+				|1:
 			}
 		}
 		if (default_label) {
@@ -9221,6 +9313,58 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 		return;
 	}
 
+	/* Move op2 to a tmp register before epilogue if it's in
+	 * used_preserved_regs, because it will be overridden. */
+
+	ir_reg op2_reg = IR_REG_NONE;
+	ir_mem mem = IR_MEM_B(IR_REG_NONE);
+	if (!IR_IS_CONST_REF(insn->op2)) {
+		op2_reg = ctx->regs[def][2];
+
+		ir_regset preserved_regs = (ir_regset)ctx->used_preserved_regs | IR_REGSET(IR_REG_STACK_POINTER);
+		if (ctx->flags & IR_USE_FRAME_POINTER) {
+			preserved_regs |= IR_REGSET(IR_REG_FRAME_POINTER);
+		}
+
+		bool is_spill_slot = op2_reg != IR_REG_NONE
+			&& IR_REG_SPILLED(op2_reg)
+			&& ctx->vregs[insn->op2];
+
+		if (op2_reg != IR_REG_NONE && !is_spill_slot) {
+			if (IR_REGSET_IN(preserved_regs, IR_REG_NUM(op2_reg))) {
+				ir_ref orig_op2_reg = op2_reg;
+				op2_reg = IR_REG_RAX;
+
+				if (IR_REG_SPILLED(orig_op2_reg)) {
+					ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2);
+				} else {
+					ir_type type = ctx->ir_base[insn->op2].type;
+					| ASM_REG_REG_OP mov, type, op2_reg, IR_REG_NUM(orig_op2_reg)
+				}
+			} else {
+				op2_reg = IR_REG_NUM(op2_reg);
+			}
+		} else {
+			if (ir_rule(ctx, insn->op2) & IR_FUSED) {
+				IR_ASSERT(op2_reg == IR_REG_NONE);
+				mem = ir_fuse_load(ctx, def, insn->op2);
+			} else {
+				mem = ir_ref_spill_slot(ctx, insn->op2);
+			}
+			ir_reg base = IR_MEM_BASE(mem);
+			ir_reg index = IR_MEM_INDEX(mem);
+			if ((base != IR_REG_NONE && IR_REGSET_IN(preserved_regs, base)) ||
+					(index != IR_REG_NONE && IR_REGSET_IN(preserved_regs, index))) {
+				op2_reg = IR_REG_RAX;
+
+				ir_type type = ctx->ir_base[insn->op2].type;
+				ir_emit_load_mem_int(ctx, type, op2_reg, mem);
+			} else {
+				op2_reg = IR_REG_NONE;
+			}
+		}
+	}
+
 	ir_emit_epilogue(ctx);
 
 	if (IR_IS_CONST_REF(insn->op2)) {
@@ -9246,22 +9390,10 @@ static void ir_emit_tailcall(ir_ctx *ctx, ir_ref def, ir_insn *insn)
 |.endif
 		}
     } else {
-		ir_reg op2_reg = ctx->regs[def][2];
-
 		if (op2_reg != IR_REG_NONE) {
-			if (IR_REG_SPILLED(op2_reg)) {
-				op2_reg = IR_REG_NUM(op2_reg);
-				ir_emit_load(ctx, IR_ADDR, op2_reg, insn->op2);
-			}
+			IR_ASSERT(!IR_REGSET_IN((ir_regset)ctx->used_preserved_regs, op2_reg));
 			|	jmp Ra(op2_reg)
 		} else {
-			ir_mem mem;
-
-			if (ir_rule(ctx, insn->op2) & IR_FUSED) {
-				mem = ir_fuse_load(ctx, def, insn->op2);
-			} else {
-				mem = ir_ref_spill_slot(ctx, insn->op2);
-			}
 			|	ASM_TMEM_OP jmp, aword, mem
 		}
     }
@@ -10314,6 +10446,7 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx)
 				case IR_IF_TRUE:
 				case IR_IF_FALSE:
 				case IR_CASE_VAL:
+				case IR_CASE_RANGE:
 				case IR_CASE_DEFAULT:
 				case IR_MERGE:
 				case IR_LOOP_BEGIN:

From 17b8706bf62a65c5e2e0690f7c18807a8553962f Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 11:56:39 +0100
Subject: [PATCH 06/27] streams: add const specifier

---
 ext/standard/streamsfuncs.c          | 17 ++++++++---------
 main/php_streams.h                   |  2 +-
 main/streams/filter.c                |  2 +-
 main/streams/php_stream_context.h    |  2 +-
 main/streams/php_stream_filter_api.h |  2 +-
 main/streams/streams.c               | 19 ++++++++++---------
 6 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index bf7cbc9fa4623..a9a024b1df0f3 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -568,13 +568,13 @@ PHP_FUNCTION(stream_get_meta_data)
 /* {{{ Retrieves list of registered socket transports */
 PHP_FUNCTION(stream_get_transports)
 {
-	HashTable *stream_xport_hash;
-	zend_string *stream_xport;
 
 	ZEND_PARSE_PARAMETERS_NONE();
 
-	stream_xport_hash = php_stream_xport_get_hash();
 	array_init(return_value);
+
+	const HashTable *stream_xport_hash = php_stream_xport_get_hash();
+	zend_string *stream_xport;
 	ZEND_HASH_MAP_FOREACH_STR_KEY(stream_xport_hash, stream_xport) {
 		add_next_index_str(return_value, zend_string_copy(stream_xport));
 	} ZEND_HASH_FOREACH_END();
@@ -584,13 +584,12 @@ PHP_FUNCTION(stream_get_transports)
 /* {{{ Retrieves list of registered stream wrappers */
 PHP_FUNCTION(stream_get_wrappers)
 {
-	HashTable *url_stream_wrappers_hash;
-	zend_string *stream_protocol;
-
 	ZEND_PARSE_PARAMETERS_NONE();
 
-	url_stream_wrappers_hash = php_stream_get_url_stream_wrappers_hash();
 	array_init(return_value);
+
+	const HashTable *url_stream_wrappers_hash = php_stream_get_url_stream_wrappers_hash();
+	zend_string *stream_protocol;
 	ZEND_HASH_MAP_FOREACH_STR_KEY(url_stream_wrappers_hash, stream_protocol) {
 		if (stream_protocol) {
 			add_next_index_str(return_value, zend_string_copy(stream_protocol));
@@ -882,7 +881,7 @@ static void user_space_stream_notifier_dtor(php_stream_notifier *notifier)
 	notifier->ptr = NULL;
 }
 
-static zend_result parse_context_options(php_stream_context *context, HashTable *options)
+static zend_result parse_context_options(php_stream_context *context, const HashTable *options)
 {
 	zval *wval, *oval;
 	zend_string *wkey, *okey;
@@ -906,7 +905,7 @@ static zend_result parse_context_options(php_stream_context *context, HashTable
 	return SUCCESS;
 }
 
-static zend_result parse_context_params(php_stream_context *context, HashTable *params)
+static zend_result parse_context_params(php_stream_context *context, const HashTable *params)
 {
 	zval *tmp;
 
diff --git a/main/php_streams.h b/main/php_streams.h
index 81fba301c6834..ca6172e764bd4 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -361,7 +361,7 @@ PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence);
 #define php_stream_rewind(stream)	_php_stream_seek((stream), 0L, SEEK_SET)
 #define php_stream_seek(stream, offset, whence)	_php_stream_seek((stream), (offset), (whence))
 
-PHPAPI zend_off_t _php_stream_tell(php_stream *stream);
+PHPAPI zend_off_t _php_stream_tell(const php_stream *stream);
 #define php_stream_tell(stream)	_php_stream_tell((stream))
 
 PHPAPI ssize_t _php_stream_read(php_stream *stream, char *buf, size_t count);
diff --git a/main/streams/filter.c b/main/streams/filter.c
index abfc5c26ae12d..a68da91c4851e 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -68,7 +68,7 @@ PHPAPI int php_stream_filter_register_factory_volatile(zend_string *filterpatter
 
 /* Buckets */
 
-PHPAPI php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent)
+PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent)
 {
 	int is_persistent = php_stream_is_persistent(stream);
 	php_stream_bucket *bucket;
diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h
index b983bbb10efe2..56574704704cb 100644
--- a/main/streams/php_stream_context.h
+++ b/main/streams/php_stream_context.h
@@ -59,7 +59,7 @@ BEGIN_EXTERN_C()
 PHPAPI int php_le_stream_context(void);
 PHPAPI void php_stream_context_free(php_stream_context *context);
 PHPAPI php_stream_context *php_stream_context_alloc(void);
-PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
+PHPAPI zval *php_stream_context_get_option(const php_stream_context *context,
 		const char *wrappername, const char *optionname);
 PHPAPI void php_stream_context_set_option(php_stream_context *context,
 		const char *wrappername, const char *optionname, zval *optionvalue);
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index ee6ab75f38c23..3144697455a08 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -63,7 +63,7 @@ typedef enum {
 
 /* Buckets API. */
 BEGIN_EXTERN_C()
-PHPAPI php_stream_bucket *php_stream_bucket_new(php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent);
+PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent);
 PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **left, php_stream_bucket **right, size_t length);
 PHPAPI void php_stream_bucket_delref(php_stream_bucket *bucket);
 #define php_stream_bucket_addref(bucket)	(bucket)->refcount++
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 2eef790863f61..4488aba59b2ee 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1041,12 +1041,13 @@ PHPAPI char *_php_stream_get_line(php_stream *stream, char *buf, size_t maxlen,
 #define STREAM_BUFFERED_AMOUNT(stream) \
 	((size_t)(((stream)->writepos) - (stream)->readpos))
 
-static const char *_php_stream_search_delim(php_stream *stream,
-											size_t maxlen,
-											size_t skiplen,
-											const char *delim, /* non-empty! */
-											size_t delim_len)
-{
+static const char *_php_stream_search_delim(
+	const php_stream *stream,
+	size_t maxlen,
+	size_t skiplen,
+	const char *delim, /* non-empty! */
+	size_t delim_len
+) {
 	size_t	seek_len;
 
 	/* set the maximum number of bytes we're allowed to read from buffer */
@@ -1340,7 +1341,7 @@ PHPAPI ssize_t _php_stream_printf(php_stream *stream, const char *fmt, ...)
 	return count;
 }
 
-PHPAPI zend_off_t _php_stream_tell(php_stream *stream)
+PHPAPI zend_off_t _php_stream_tell(const php_stream *stream)
 {
 	return stream->position;
 }
@@ -1975,7 +1976,7 @@ PHPAPI zend_result php_unregister_url_stream_wrapper_volatile(zend_string *proto
 /* {{{ php_stream_locate_url_wrapper */
 PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const char **path_for_open, int options)
 {
-	HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
+	const HashTable *wrapper_hash = (FG(stream_wrappers) ? FG(stream_wrappers) : &url_stream_wrappers_hash);
 	php_stream_wrapper *wrapper = NULL;
 	const char *p, *protocol = NULL;
 	size_t n = 0;
@@ -2416,7 +2417,7 @@ PHPAPI void php_stream_notification_free(php_stream_notifier *notifier)
 	efree(notifier);
 }
 
-PHPAPI zval *php_stream_context_get_option(php_stream_context *context,
+PHPAPI zval *php_stream_context_get_option(const php_stream_context *context,
 		const char *wrappername, const char *optionname)
 {
 	zval *wrapperhash;

From b35dbe474ee114ebca3e58b8351a150b2a788bc6 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 11:57:11 +0100
Subject: [PATCH 07/27] streams: reduce scope of variables

---
 ext/standard/streamsfuncs.c |  6 ++----
 main/streams/streams.c      | 13 ++++++-------
 2 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index a9a024b1df0f3..1070e3be5d428 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -99,7 +99,6 @@ PHP_FUNCTION(stream_socket_client)
 	zval *zerrno = NULL, *zerrstr = NULL, *zcontext = NULL;
 	double timeout;
 	bool timeout_is_null = 1;
-	php_timeout_ull conv;
 	struct timeval tv;
 	char *hashkey = NULL;
 	php_stream *stream = NULL;
@@ -138,7 +137,7 @@ PHP_FUNCTION(stream_socket_client)
 	if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) {
 		tv_pointer = NULL;
 	} else {
-		conv = (php_timeout_ull) (timeout * 1000000.0);
+		php_timeout_ull conv = (php_timeout_ull) (timeout * 1000000.0);
 #ifdef PHP_WIN32
 		tv.tv_sec = (long)(conv / 1000000);
 		tv.tv_usec = (long)(conv % 1000000);
@@ -262,7 +261,6 @@ PHP_FUNCTION(stream_socket_accept)
 	bool timeout_is_null = 1;
 	zval *zpeername = NULL;
 	zend_string *peername = NULL;
-	php_timeout_ull conv;
 	struct timeval tv;
 	php_stream *stream = NULL, *clistream = NULL;
 	zend_string *errstr = NULL;
@@ -286,7 +284,7 @@ PHP_FUNCTION(stream_socket_accept)
 	if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) {
 		tv_pointer = NULL;
 	} else {
-		conv = (php_timeout_ull) (timeout * 1000000.0);
+		php_timeout_ull conv = (php_timeout_ull) (timeout * 1000000.0);
 #ifdef PHP_WIN32
 		tv.tv_sec = (long)(conv / 1000000);
 		tv.tv_usec = (long)(conv % 1000000);
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 4488aba59b2ee..7fe2ef215b80d 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -880,7 +880,7 @@ PHPAPI int _php_stream_stat(php_stream *stream, php_stream_statbuf *ssb)
 PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf)
 {
 	size_t avail;
-	const char *cr, *lf, *eol = NULL;
+	const char *eol = NULL;
 	const char *readptr;
 
 	if (!buf) {
@@ -893,8 +893,8 @@ PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf)
 
 	/* Look for EOL */
 	if (stream->flags & PHP_STREAM_FLAG_DETECT_EOL) {
-		cr = memchr(readptr, '\r', avail);
-		lf = memchr(readptr, '\n', avail);
+		const char *cr = memchr(readptr, '\r', avail);
+		const char *lf = memchr(readptr, '\n', avail);
 
 		if (cr && lf != cr + 1 && !(lf && lf < cr)) {
 			/* mac */
@@ -1218,16 +1218,15 @@ static ssize_t _php_stream_write_filtered(php_stream *stream, const char *buf, s
 	size_t consumed = 0;
 	php_stream_bucket *bucket;
 	php_stream_bucket_brigade brig_in = { NULL, NULL }, brig_out = { NULL, NULL };
-	php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out, *brig_swap;
+	php_stream_bucket_brigade *brig_inp = &brig_in, *brig_outp = &brig_out;
 	php_stream_filter_status_t status = PSFS_ERR_FATAL;
-	php_stream_filter *filter;
 
 	if (buf) {
 		bucket = php_stream_bucket_new(stream, (char *)buf, count, 0, 0);
 		php_stream_bucket_append(&brig_in, bucket);
 	}
 
-	for (filter = stream->writefilters.head; filter; filter = filter->next) {
+	for (php_stream_filter *filter = stream->writefilters.head; filter; filter = filter->next) {
 		/* for our return value, we are interested in the number of bytes consumed from
 		 * the first filter in the chain */
 		status = filter->fops->filter(stream, filter, brig_inp, brig_outp,
@@ -1239,7 +1238,7 @@ static ssize_t _php_stream_write_filtered(php_stream *stream, const char *buf, s
 		/* brig_out becomes brig_in.
 		 * brig_in will always be empty here, as the filter MUST attach any un-consumed buckets
 		 * to its own brigade */
-		brig_swap = brig_inp;
+		php_stream_bucket_brigade *brig_swap = brig_inp;
 		brig_inp = brig_outp;
 		brig_outp = brig_swap;
 		memset(brig_outp, 0, sizeof(*brig_outp));

From b3551cc31f6b168ecdebf74b0222ed8c0def5e62 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:02:49 +0100
Subject: [PATCH 08/27] streams: use type bool instead of type int

---
 ext/standard/streamsfuncs.c          |  2 +-
 main/php_streams.h                   |  2 +-
 main/streams/filter.c                | 20 ++++++++++----------
 main/streams/php_stream_filter_api.h |  8 ++++----
 main/streams/streams.c               | 12 ++++++------
 5 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 1070e3be5d428..2754545149070 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -1215,7 +1215,7 @@ PHP_FUNCTION(stream_context_create)
 /* }}} */
 
 /* {{{ streams filter functions */
-static void apply_filter_to_stream(int append, INTERNAL_FUNCTION_PARAMETERS)
+static void apply_filter_to_stream(bool append, INTERNAL_FUNCTION_PARAMETERS)
 {
 	php_stream *stream;
 	char *filtername;
diff --git a/main/php_streams.h b/main/php_streams.h
index ca6172e764bd4..0391a2b7a649f 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -531,7 +531,7 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
 
 /* read all data from stream and put into a buffer. Caller must free buffer
  * when done. */
-PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int persistent STREAMS_DC);
+PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool persistent STREAMS_DC);
 #define php_stream_copy_to_mem(src, maxlen, persistent) _php_stream_copy_to_mem((src), (maxlen), (persistent) STREAMS_CC)
 
 /* output all data from a stream */
diff --git a/main/streams/filter.c b/main/streams/filter.c
index a68da91c4851e..304eac0a155f5 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -43,9 +43,9 @@ PHPAPI HashTable *_php_get_stream_filters_hash(void)
 PHPAPI int php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory)
 {
 	int ret;
-	zend_string *str = zend_string_init_interned(filterpattern, strlen(filterpattern), 1);
+	zend_string *str = zend_string_init_interned(filterpattern, strlen(filterpattern), true);
 	ret = zend_hash_add_ptr(&stream_filters_hash, str, (void*)factory) ? SUCCESS : FAILURE;
-	zend_string_release_ex(str, 1);
+	zend_string_release_ex(str, true);
 	return ret;
 }
 
@@ -70,7 +70,7 @@ PHPAPI int php_stream_filter_register_factory_volatile(zend_string *filterpatter
 
 PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *buf, size_t buflen, uint8_t own_buf, uint8_t buf_persistent)
 {
-	int is_persistent = php_stream_is_persistent(stream);
+	bool is_persistent = php_stream_is_persistent(stream);
 	php_stream_bucket *bucket;
 
 	bucket = (php_stream_bucket*)pemalloc(sizeof(php_stream_bucket), is_persistent);
@@ -78,10 +78,10 @@ PHPAPI php_stream_bucket *php_stream_bucket_new(const php_stream *stream, char *
 
 	if (is_persistent && !buf_persistent) {
 		/* all data in a persistent bucket must also be persistent */
-		bucket->buf = pemalloc(buflen, 1);
+		bucket->buf = pemalloc(buflen, true);
 		memcpy(bucket->buf, buf, buflen);
 		bucket->buflen = buflen;
-		bucket->own_buf = 1;
+		bucket->own_buf = true;
 	} else {
 		bucket->buf = buf;
 		bucket->buflen = buflen;
@@ -118,7 +118,7 @@ PHPAPI php_stream_bucket *php_stream_bucket_make_writeable(php_stream_bucket *bu
 	memcpy(retval->buf, bucket->buf, retval->buflen);
 
 	retval->refcount = 1;
-	retval->own_buf = 1;
+	retval->own_buf = true;
 
 	php_stream_bucket_delref(bucket);
 
@@ -134,14 +134,14 @@ PHPAPI int php_stream_bucket_split(php_stream_bucket *in, php_stream_bucket **le
 	(*left)->buflen = length;
 	memcpy((*left)->buf, in->buf, length);
 	(*left)->refcount = 1;
-	(*left)->own_buf = 1;
+	(*left)->own_buf = true;
 	(*left)->is_persistent = in->is_persistent;
 
 	(*right)->buflen = in->buflen - length;
 	(*right)->buf = pemalloc((*right)->buflen, in->is_persistent);
 	memcpy((*right)->buf, in->buf + length, (*right)->buflen);
 	(*right)->refcount = 1;
-	(*right)->own_buf = 1;
+	(*right)->own_buf = true;
 	(*right)->is_persistent = in->is_persistent;
 
 	return SUCCESS;
@@ -395,7 +395,7 @@ PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream
 	}
 }
 
-PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
+PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, bool finish)
 {
 	php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;
 	php_stream_bucket *bucket;
@@ -480,7 +480,7 @@ PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish)
 	return SUCCESS;
 }
 
-PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor)
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor)
 {
 	if (filter->prev) {
 		filter->prev->next = filter->next;
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index 3144697455a08..9d9ed24f861dc 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -44,8 +44,8 @@ struct _php_stream_bucket {
 	char *buf;
 	size_t buflen;
 	/* if non-zero, buf should be pefreed when the bucket is destroyed */
-	uint8_t own_buf;
-	uint8_t is_persistent;
+	bool own_buf;
+	bool is_persistent;
 
 	/* destroy this struct when refcount falls to zero */
 	int refcount;
@@ -124,8 +124,8 @@ PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_strea
 PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, int finish);
-PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, int call_dtor);
+PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, bool finish);
+PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor);
 PHPAPI void php_stream_filter_free(php_stream_filter *filter);
 PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, void *abstract, uint8_t persistent STREAMS_DC);
 END_EXTERN_C()
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 7fe2ef215b80d..45bfaecf45eeb 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1529,7 +1529,7 @@ PHPAPI ssize_t _php_stream_passthru(php_stream * stream STREAMS_DC)
 }
 
 
-PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, int persistent STREAMS_DC)
+PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool persistent STREAMS_DC)
 {
 	ssize_t ret = 0;
 	char *ptr;
@@ -2023,16 +2023,16 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const
 		php_stream_wrapper *plain_files_wrapper = (php_stream_wrapper*)&php_plain_files_wrapper;
 
 		if (protocol) {
-			int localhost = 0;
+			bool localhost = false;
 
 			if (!strncasecmp(path, "file://localhost/", 17)) {
-				localhost = 1;
+				localhost = true;
 			}
 
 #ifdef PHP_WIN32
-			if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':')	{
+			if (!localhost && path[n+3] != '\0' && path[n+3] != '/' && path[n+4] != ':')	{
 #else
-			if (localhost == 0 && path[n+3] != '\0' && path[n+3] != '/') {
+			if (!localhost && path[n+3] != '\0' && path[n+3] != '/') {
 #endif
 				if (options & REPORT_ERRORS) {
 					php_error_docref(NULL, E_WARNING, "Remote host file access not supported, %s", path);
@@ -2043,7 +2043,7 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const
 			if (path_for_open) {
 				/* skip past protocol and :/, but handle windows correctly */
 				*path_for_open = (char*)path + n + 1;
-				if (localhost == 1) {
+				if (localhost) {
 					(*path_for_open) += 11;
 				}
 				while (*(++*path_for_open)=='/') {

From 841afdc4866b9a1f30681bddc90aa97c0bda8e23 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:08:04 +0100
Subject: [PATCH 09/27] streams: use type zend_result instead of type int

---
 ext/pgsql/pgsql.c                    |  2 +-
 ext/sockets/sockets.c                |  2 +-
 main/php_streams.h                   |  4 +--
 main/streams/cast.c                  |  6 ++--
 main/streams/filter.c                | 14 ++++-----
 main/streams/php_stream_filter_api.h | 12 +++----
 main/streams/streams.c               |  2 +-
 main/streams/userspace.c             | 47 ++++++++++------------------
 8 files changed, 38 insertions(+), 51 deletions(-)

diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
index d6a9e26503601..c697776dade64 100644
--- a/ext/pgsql/pgsql.c
+++ b/ext/pgsql/pgsql.c
@@ -6388,7 +6388,7 @@ PHP_FUNCTION(pg_socket_poll)
 		Z_PARAM_LONG(ts)
 	ZEND_PARSE_PARAMETERS_END();
 
-	if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void **)&socket, 0)) {
+	if (UNEXPECTED(php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void **)&socket, 0) == FAILURE)) {
 		zend_argument_type_error(1, "invalid resource socket");
 		RETURN_THROWS();
 	}
diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c
index 3ed7d17bba217..3cae726838a64 100644
--- a/ext/sockets/sockets.c
+++ b/ext/sockets/sockets.c
@@ -2585,7 +2585,7 @@ PHP_FUNCTION(socket_import_stream)
 	ZEND_PARSE_PARAMETERS_END();
 	php_stream_from_zval(stream, zstream);
 
-	if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1)) {
+	if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void**)&socket, 1) == FAILURE) {
 		/* error supposedly already shown */
 		RETURN_FALSE;
 	}
diff --git a/main/php_streams.h b/main/php_streams.h
index 0391a2b7a649f..d3a6660c6ca7c 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -561,7 +561,7 @@ END_EXTERN_C()
 #define PHP_STREAM_CAST_INTERNAL	0x20000000	/* stream cast for internal use */
 #define PHP_STREAM_CAST_MASK		(PHP_STREAM_CAST_TRY_HARD | PHP_STREAM_CAST_RELEASE | PHP_STREAM_CAST_INTERNAL)
 BEGIN_EXTERN_C()
-PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err);
+PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err);
 END_EXTERN_C()
 /* use this to check if a stream can be cast into another form */
 #define php_stream_can_cast(stream, as)	_php_stream_cast((stream), (as), NULL, 0)
@@ -626,7 +626,7 @@ END_EXTERN_C()
 /* this flag is only used by include/require functions */
 #define STREAM_OPEN_FOR_ZEND_STREAM     0x00010000
 
-int php_init_stream_wrappers(int module_number);
+zend_result php_init_stream_wrappers(int module_number);
 void php_shutdown_stream_wrappers(int module_number);
 void php_shutdown_stream_hashes(void);
 PHP_RSHUTDOWN_FUNCTION(streams);
diff --git a/main/streams/cast.c b/main/streams/cast.c
index 6e5c63fb3b292..5c78b560945eb 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -191,7 +191,7 @@ void php_stream_mode_sanitize_fdopen_fopencookie(php_stream *stream, char *resul
 /* }}} */
 
 /* {{{ php_stream_cast */
-PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err)
+PHPAPI zend_result _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err)
 {
 	int flags = castas & PHP_STREAM_CAST_MASK;
 	castas &= ~PHP_STREAM_CAST_MASK;
@@ -273,12 +273,12 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
 
 			newstream = php_stream_fopen_tmpfile();
 			if (newstream) {
-				int retcopy = php_stream_copy_to_stream_ex(stream, newstream, PHP_STREAM_COPY_ALL, NULL);
+				zend_result retcopy = php_stream_copy_to_stream_ex(stream, newstream, PHP_STREAM_COPY_ALL, NULL);
 
 				if (retcopy != SUCCESS) {
 					php_stream_close(newstream);
 				} else {
-					int retcast = php_stream_cast(newstream, castas | flags, (void **)ret, show_err);
+					zend_result retcast = php_stream_cast(newstream, castas | flags, (void **)ret, show_err);
 
 					if (retcast == SUCCESS) {
 						rewind(*(FILE**)ret);
diff --git a/main/streams/filter.c b/main/streams/filter.c
index 304eac0a155f5..2a48fae79f04c 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -40,22 +40,22 @@ PHPAPI HashTable *_php_get_stream_filters_hash(void)
 }
 
 /* API for registering GLOBAL filters */
-PHPAPI int php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory)
+PHPAPI zend_result php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory)
 {
-	int ret;
+	zend_result ret;
 	zend_string *str = zend_string_init_interned(filterpattern, strlen(filterpattern), true);
 	ret = zend_hash_add_ptr(&stream_filters_hash, str, (void*)factory) ? SUCCESS : FAILURE;
 	zend_string_release_ex(str, true);
 	return ret;
 }
 
-PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern)
+PHPAPI zend_result php_stream_filter_unregister_factory(const char *filterpattern)
 {
 	return zend_hash_str_del(&stream_filters_hash, filterpattern, strlen(filterpattern));
 }
 
 /* API for registering VOLATILE wrappers */
-PHPAPI int php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory)
+PHPAPI zend_result php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory)
 {
 	if (!FG(stream_filters)) {
 		ALLOC_HASHTABLE(FG(stream_filters));
@@ -282,7 +282,7 @@ PHPAPI void php_stream_filter_free(php_stream_filter *filter)
 	pefree(filter, filter->is_persistent);
 }
 
-PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
+PHPAPI zend_result php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
 {
 	filter->next = chain->head;
 	filter->prev = NULL;
@@ -303,7 +303,7 @@ PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_strea
 	php_stream_filter_prepend_ex(chain, filter);
 }
 
-PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
+PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
 {
 	php_stream *stream = chain->stream;
 
@@ -395,7 +395,7 @@ PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream
 	}
 }
 
-PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, bool finish)
+PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish)
 {
 	php_stream_bucket_brigade brig_a = { NULL, NULL }, brig_b = { NULL, NULL }, *inp = &brig_a, *outp = &brig_b, *brig_temp;
 	php_stream_bucket *bucket;
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index 9d9ed24f861dc..8e697c2ad0194 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -121,10 +121,10 @@ struct _php_stream_filter {
 /* stack filter onto a stream */
 BEGIN_EXTERN_C()
 PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI int php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
+PHPAPI zend_result php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI int php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI int _php_stream_filter_flush(php_stream_filter *filter, bool finish);
+PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
+PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish);
 PHPAPI php_stream_filter *php_stream_filter_remove(php_stream_filter *filter, bool call_dtor);
 PHPAPI void php_stream_filter_free(php_stream_filter *filter);
 PHPAPI php_stream_filter *_php_stream_filter_alloc(const php_stream_filter_ops *fops, void *abstract, uint8_t persistent STREAMS_DC);
@@ -142,8 +142,8 @@ typedef struct _php_stream_filter_factory {
 } php_stream_filter_factory;
 
 BEGIN_EXTERN_C()
-PHPAPI int php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory);
-PHPAPI int php_stream_filter_unregister_factory(const char *filterpattern);
-PHPAPI int php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory);
+PHPAPI zend_result php_stream_filter_register_factory(const char *filterpattern, const php_stream_filter_factory *factory);
+PHPAPI zend_result php_stream_filter_unregister_factory(const char *filterpattern);
+PHPAPI zend_result php_stream_filter_register_factory_volatile(zend_string *filterpattern, const php_stream_filter_factory *factory);
 PHPAPI php_stream_filter *php_stream_filter_create(const char *filtername, zval *filterparams, uint8_t persistent);
 END_EXTERN_C()
diff --git a/main/streams/streams.c b/main/streams/streams.c
index 45bfaecf45eeb..fddca6f84a94e 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1869,7 +1869,7 @@ void php_shutdown_stream_hashes(void)
 	}
 }
 
-int php_init_stream_wrappers(int module_number)
+zend_result php_init_stream_wrappers(int module_number)
 {
 	le_stream = zend_register_list_destructors_ex(stream_resource_regular_dtor, NULL, "stream", module_number);
 	le_pstream = zend_register_list_destructors_ex(NULL, stream_resource_persistent_dtor, "persistent stream", module_number);
diff --git a/main/streams/userspace.c b/main/streams/userspace.c
index 0bb80acf6b7c2..baf6c96605726 100644
--- a/main/streams/userspace.c
+++ b/main/streams/userspace.c
@@ -297,7 +297,6 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
 	php_userstream_data_t *us;
 	zval zretval, zfuncname;
 	zval args[4];
-	int call_result;
 	php_stream *stream = NULL;
 	bool old_in_user_include;
 
@@ -340,6 +339,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char *
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_OPEN);
 
+	zend_result call_result;
 	zend_try {
 		call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 4, args);
 	} zend_catch {
@@ -398,7 +398,6 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
 	php_userstream_data_t *us;
 	zval zretval, zfuncname;
 	zval args[2];
-	int call_result;
 	php_stream *stream = NULL;
 
 	/* Try to catch bad usage without preventing flexibility */
@@ -426,7 +425,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_DIR_OPEN);
 
-	call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 2, args);
+	zend_result call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 2, args);
 
 	if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) {
 		/* the stream is now open! */
@@ -564,7 +563,6 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
 {
 	zval func_name;
 	zval retval;
-	int call_result;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 	zval args[1];
 	ssize_t didwrite;
@@ -575,7 +573,7 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_
 
 	ZVAL_STRINGL(&args[0], (char*)buf, count);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
 	zval_ptr_dtor(&args[0]);
 	zval_ptr_dtor(&func_name);
 
@@ -614,7 +612,6 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
 	zval func_name;
 	zval retval;
 	zval args[1];
-	int call_result;
 	size_t didread = 0;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 
@@ -624,7 +621,7 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count
 
 	ZVAL_LONG(&args[0], count);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
 
 	zval_ptr_dtor(&args[0]);
 	zval_ptr_dtor(&func_name);
@@ -714,14 +711,13 @@ static int php_userstreamop_flush(php_stream *stream)
 {
 	zval func_name;
 	zval retval;
-	int call_result;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 
 	assert(us != NULL);
 
 	ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
 
 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval))
 		call_result = 0;
@@ -738,7 +734,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
 {
 	zval func_name;
 	zval retval;
-	int call_result, ret;
+	int ret;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 	zval args[2];
 
@@ -749,7 +745,7 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
 	ZVAL_LONG(&args[0], offset);
 	ZVAL_LONG(&args[1], whence);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args);
 
 	zval_ptr_dtor(&args[0]);
 	zval_ptr_dtor(&args[1]);
@@ -838,13 +834,12 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
 {
 	zval func_name;
 	zval retval;
-	int call_result;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 	int ret = -1;
 
 	ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
 
 	if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) {
 		statbuf_from_array(&retval, ssb);
@@ -866,7 +861,7 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
 static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) {
 	zval func_name;
 	zval retval;
-	int call_result;
+	zend_result call_result;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 	int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL;
 	zval args[3];
@@ -1034,7 +1029,6 @@ static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[1];
-	int call_result;
 	zval object;
 	int ret = 0;
 
@@ -1049,7 +1043,7 @@ static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_UNLINK);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 1, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 1, args);
 
 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
 		ret = (Z_TYPE(zretval) == IS_TRUE);
@@ -1073,7 +1067,6 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[2];
-	int call_result;
 	zval object;
 	int ret = 0;
 
@@ -1089,7 +1082,7 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_RENAME);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
 
 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
 		ret = (Z_TYPE(zretval) == IS_TRUE);
@@ -1114,7 +1107,6 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[3];
-	int call_result;
 	zval object;
 	int ret = 0;
 
@@ -1131,7 +1123,7 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_MKDIR);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
 
 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
 		ret = (Z_TYPE(zretval) == IS_TRUE);
@@ -1157,7 +1149,6 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[2];
-	int call_result;
 	zval object;
 	int ret = 0;
 
@@ -1173,7 +1164,7 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url,
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_RMDIR);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
 
 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
 		ret = (Z_TYPE(zretval) == IS_TRUE);
@@ -1198,7 +1189,6 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[3];
-	int call_result;
 	zval object;
 	int ret = 0;
 
@@ -1239,7 +1229,7 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_METADATA);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args);
 
 	if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) {
 		ret = Z_TYPE(zretval) == IS_TRUE;
@@ -1266,7 +1256,6 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
 	struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract;
 	zval zfuncname, zretval;
 	zval args[2];
-	int call_result;
 	zval object;
 	int ret = -1;
 
@@ -1282,7 +1271,7 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
 
 	ZVAL_STRING(&zfuncname, USERSTREAM_STATURL);
 
-	call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
+	zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args);
 
 	if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) {
 		/* We got the info we needed */
@@ -1311,7 +1300,6 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co
 {
 	zval func_name;
 	zval retval;
-	int call_result;
 	size_t didread = 0;
 	php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract;
 	php_stream_dirent *ent = (php_stream_dirent*)buf;
@@ -1322,7 +1310,7 @@ static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t co
 
 	ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1);
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
 
 	if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
 		convert_to_string(&retval);
@@ -1387,7 +1375,6 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
 	zval retval;
 	zval args[1];
 	php_stream * intstream = NULL;
-	int call_result;
 	int ret = FAILURE;
 	/* If we are checking if the stream can cast, no return pointer is provided, so do not emit errors */
 	bool report_errors = retptr;
@@ -1403,7 +1390,7 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr)
 		break;
 	}
 
-	call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
+	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args);
 
 	do {
 		if (call_result == FAILURE) {

From c9836b03ceff5bf5d8c8cd9e1aaa337ae622d5e5 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:09:49 +0100
Subject: [PATCH 10/27] streams: use type size_t instead of type unsigned int

---
 main/streams/streams.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/main/streams/streams.c b/main/streams/streams.c
index fddca6f84a94e..0e1e26831960a 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1903,11 +1903,9 @@ void php_shutdown_stream_wrappers(int module_number)
 /* Validate protocol scheme names during registration
  * Must conform to /^[a-zA-Z0-9+.-]+$/
  */
-static inline zend_result php_stream_wrapper_scheme_validate(const char *protocol, unsigned int protocol_len)
+static inline zend_result php_stream_wrapper_scheme_validate(const char *protocol, size_t protocol_len)
 {
-	unsigned int i;
-
-	for(i = 0; i < protocol_len; i++) {
+	for (size_t i = 0; i < protocol_len; i++) {
 		if (!isalnum((int)protocol[i]) &&
 			protocol[i] != '+' &&
 			protocol[i] != '-' &&

From 67d6160462a3a7ad7dedffda44b0770f62d6a7de Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 14:27:31 +0100
Subject: [PATCH 11/27] streams: use int type instead of long

The flags variable is passed to the filter virtual function that requires an int
---
 main/streams/filter.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main/streams/filter.c b/main/streams/filter.c
index 2a48fae79f04c..0a698737b6de3 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -403,7 +403,7 @@ PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool fini
 	php_stream_filter *current;
 	php_stream *stream;
 	size_t flushed_size = 0;
-	long flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC);
+	int flags = (finish ? PSFS_FLAG_FLUSH_CLOSE : PSFS_FLAG_FLUSH_INC);
 
 	if (!filter->chain || !filter->chain->stream) {
 		/* Filter is not attached to a chain, or chain is somehow not part of a stream */

From 02af9acfca4bc5f223209b0180d865f004d89521 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:20:51 +0100
Subject: [PATCH 12/27] streams: use precomputed persistant variable

---
 main/streams/streams.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/main/streams/streams.c b/main/streams/streams.c
index 0e1e26831960a..b872cbaedd3d1 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -2260,7 +2260,7 @@ PHPAPI php_stream *_php_stream_open_wrapper_ex(const char *path, const char *mod
 
 		/* if the caller asked for a persistent stream but the wrapper did not
 		 * return one, force an error here */
-		if (stream && (options & STREAM_OPEN_PERSISTENT) && !stream->is_persistent) {
+		if (stream && persistent && !stream->is_persistent) {
 			php_stream_wrapper_log_error(wrapper, options & ~REPORT_ERRORS,
 					"wrapper does not support persistent streams");
 			php_stream_close(stream);

From 4b6ebcc6e9cf12ba8fe92532372361560e0a3a3b Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:06:48 +0100
Subject: [PATCH 13/27] streams: remove confusing step variable

---
 main/streams/streams.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/main/streams/streams.c b/main/streams/streams.c
index b872cbaedd3d1..37eb253a1e5c9 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -1534,8 +1534,6 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool
 	ssize_t ret = 0;
 	char *ptr;
 	size_t len = 0, buflen;
-	int step = CHUNK_SIZE;
-	int min_room = CHUNK_SIZE / 4;
 	php_stream_statbuf ssbuf;
 	zend_string *result;
 
@@ -1578,20 +1576,21 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool
 	 * we can.  Note that the stream may be filtered, in which case the stat
 	 * result may be inaccurate, as the filter may inflate or deflate the
 	 * number of bytes that we can read.  In order to avoid an upsize followed
-	 * by a downsize of the buffer, overestimate by the step size (which is
+	 * by a downsize of the buffer, overestimate by the CHUNK_SIZE size (which is
 	 * 8K).  */
 	if (php_stream_stat(src, &ssbuf) == 0 && ssbuf.sb.st_size > 0) {
-		buflen = MAX(ssbuf.sb.st_size - src->position, 0) + step;
+		buflen = MAX(ssbuf.sb.st_size - src->position, 0) + CHUNK_SIZE;
 		if (maxlen > 0 && buflen > maxlen) {
 			buflen = maxlen;
 		}
 	} else {
-		buflen = step;
+		buflen = CHUNK_SIZE;
 	}
 
 	result = zend_string_alloc(buflen, persistent);
 	ptr = ZSTR_VAL(result);
 
+	const int min_room = CHUNK_SIZE / 4;
 	// TODO: Propagate error?
 	while ((ret = php_stream_read(src, ptr, buflen - len)) > 0) {
 		len += ret;
@@ -1599,10 +1598,10 @@ PHPAPI zend_string *_php_stream_copy_to_mem(php_stream *src, size_t maxlen, bool
 			if (maxlen == len) {
 				break;
 			}
-			if (maxlen > 0 && buflen + step > maxlen) {
+			if (maxlen > 0 && buflen + CHUNK_SIZE > maxlen) {
 				buflen = maxlen;
 			} else {
-				buflen += step;
+				buflen += CHUNK_SIZE;
 			}
 			result = zend_string_extend(result, buflen, persistent);
 			ptr = ZSTR_VAL(result) + len;

From fb2585f4e23485bfab840617334f2f3391bc8288 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:21:52 +0100
Subject: [PATCH 14/27] streams: use -1 directly instead of FAILURE macro

The result of the functions returning those globals is never checked anyway
---
 main/streams/streams.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/main/streams/streams.c b/main/streams/streams.c
index 37eb253a1e5c9..42e08e910771d 100644
--- a/main/streams/streams.c
+++ b/main/streams/streams.c
@@ -35,9 +35,9 @@
 /* {{{ resource and registration code */
 /* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */
 static HashTable url_stream_wrappers_hash;
-static int le_stream = FAILURE; /* true global */
-static int le_pstream = FAILURE; /* true global */
-static int le_stream_filter = FAILURE; /* true global */
+static int le_stream = -1; /* true global */
+static int le_pstream = -1; /* true global */
+static int le_stream_filter = -1; /* true global */
 
 PHPAPI int php_file_le_stream(void)
 {

From 0d0f774a6ad4dc168dc1a235819809cbcd64b473 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:27:37 +0100
Subject: [PATCH 15/27] streams: use call in if statement directly

---
 ext/standard/streamsfuncs.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 2754545149070..96903f48abb72 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -482,7 +482,6 @@ PHP_FUNCTION(stream_copy_to_stream)
 	zend_long maxlen, pos = 0;
 	bool maxlen_is_null = 1;
 	size_t len;
-	int ret;
 
 	ZEND_PARSE_PARAMETERS_START(2, 4)
 		PHP_Z_PARAM_STREAM(src)
@@ -501,9 +500,7 @@ PHP_FUNCTION(stream_copy_to_stream)
 		RETURN_FALSE;
 	}
 
-	ret = php_stream_copy_to_stream_ex(src, dest, maxlen, &len);
-
-	if (ret != SUCCESS) {
+	if (php_stream_copy_to_stream_ex(src, dest, maxlen, &len) != SUCCESS) {
 		RETURN_FALSE;
 	}
 	RETURN_LONG(len);

From c50a715a5c609d584d85023a1bda711d511707c0 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 12:29:38 +0100
Subject: [PATCH 16/27] streams: remove useless casts

---
 ext/standard/streamsfuncs.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 96903f48abb72..07f4c1b31e2de 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -529,9 +529,9 @@ PHP_FUNCTION(stream_get_meta_data)
 		add_assoc_zval(return_value, "wrapper_data", &stream->wrapperdata);
 	}
 	if (stream->wrapper) {
-		add_assoc_string(return_value, "wrapper_type", (char *)stream->wrapper->wops->label);
+		add_assoc_string(return_value, "wrapper_type", stream->wrapper->wops->label);
 	}
-	add_assoc_string(return_value, "stream_type", (char *)stream->ops->label);
+	add_assoc_string(return_value, "stream_type", stream->ops->label);
 
 	add_assoc_string(return_value, "mode", stream->mode);
 
@@ -543,7 +543,7 @@ PHP_FUNCTION(stream_get_meta_data)
 		array_init(newval);
 
 		for (filter = stream->filterhead; filter != NULL; filter = filter->next) {
-			add_next_index_string(newval, (char *)filter->fops->label);
+			add_next_index_string(newval, filter->fops->label);
 		}
 
 		add_assoc_zval(return_value, "filters", newval);

From b46c5e618679fba5d131d55f3a56e2b6413c0507 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 13:13:14 +0100
Subject: [PATCH 17/27] streams: refactor implementation of stream_select()

---
 ext/standard/streamsfuncs.c | 26 ++++++++------------------
 1 file changed, 8 insertions(+), 18 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 07f4c1b31e2de..912cf6503bfd9 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -595,17 +595,13 @@ PHP_FUNCTION(stream_get_wrappers)
 /* }}} */
 
 /* {{{ stream_select related functions */
-static int stream_array_to_fd_set(zval *stream_array, fd_set *fds, php_socket_t *max_fd)
+static int stream_array_to_fd_set(const HashTable *stream_array, fd_set *fds, php_socket_t *max_fd)
 {
 	zval *elem;
 	php_stream *stream;
 	int cnt = 0;
 
-	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
-		return 0;
-	}
-
-	ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(stream_array), elem) {
+	ZEND_HASH_FOREACH_VAL(stream_array, elem) {
 		/* Temporary int fd is needed for the STREAM data type on windows, passing this_fd directly to php_stream_cast()
 			would eventually bring a wrong result on x64. php_stream_cast() casts to int internally, and this will leave
 			the higher bits of a SOCKET variable uninitialized on systems with little endian. */
@@ -634,7 +630,7 @@ static int stream_array_to_fd_set(zval *stream_array, fd_set *fds, php_socket_t
 	return cnt ? 1 : 0;
 }
 
-static int stream_array_from_fd_set(zval *stream_array, fd_set *fds)
+static int stream_array_from_fd_set(zval *stream_array, const fd_set *fds)
 {
 	zval *elem, *dest_elem;
 	HashTable *ht;
@@ -643,9 +639,7 @@ static int stream_array_from_fd_set(zval *stream_array, fd_set *fds)
 	zend_string *key;
 	zend_ulong num_ind;
 
-	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
-		return 0;
-	}
+	ZEND_ASSERT(Z_TYPE_P(stream_array) == IS_ARRAY);
 	ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array)));
 
 	ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) {
@@ -671,7 +665,6 @@ static int stream_array_from_fd_set(zval *stream_array, fd_set *fds)
 
 				zval_add_ref(dest_elem);
 				ret++;
-				continue;
 			}
 		}
 	} ZEND_HASH_FOREACH_END();
@@ -692,9 +685,7 @@ static int stream_array_emulate_read_fd_set(zval *stream_array)
 	zend_ulong num_ind;
 	zend_string *key;
 
-	if (Z_TYPE_P(stream_array) != IS_ARRAY) {
-		return 0;
-	}
+	ZEND_ASSERT(Z_TYPE_P(stream_array) == IS_ARRAY);
 	ht = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(stream_array)));
 
 	ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(stream_array), num_ind, key, elem) {
@@ -717,7 +708,6 @@ static int stream_array_emulate_read_fd_set(zval *stream_array)
 			}
 			zval_add_ref(dest_elem);
 			ret++;
-			continue;
 		}
 	} ZEND_HASH_FOREACH_END();
 
@@ -760,21 +750,21 @@ PHP_FUNCTION(stream_select)
 	FD_ZERO(&efds);
 
 	if (r_array != NULL) {
-		set_count = stream_array_to_fd_set(r_array, &rfds, &max_fd);
+		set_count = stream_array_to_fd_set(Z_ARR_P(r_array), &rfds, &max_fd);
 		if (set_count > max_set_count)
 			max_set_count = set_count;
 		sets += set_count;
 	}
 
 	if (w_array != NULL) {
-		set_count = stream_array_to_fd_set(w_array, &wfds, &max_fd);
+		set_count = stream_array_to_fd_set(Z_ARR_P(w_array), &wfds, &max_fd);
 		if (set_count > max_set_count)
 			max_set_count = set_count;
 		sets += set_count;
 	}
 
 	if (e_array != NULL) {
-		set_count = stream_array_to_fd_set(e_array, &efds, &max_fd);
+		set_count = stream_array_to_fd_set(Z_ARR_P(e_array), &efds, &max_fd);
 		if (set_count > max_set_count)
 			max_set_count = set_count;
 		sets += set_count;

From cd13ba73e2c99eeb4d412182506b7ea04931c899 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 13:23:37 +0100
Subject: [PATCH 18/27] streams: use RETURN_BOOL() when possible

---
 ext/standard/streamsfuncs.c | 51 ++++++++-----------------------------
 1 file changed, 11 insertions(+), 40 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index 912cf6503bfd9..cc7fc0406555e 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -1362,11 +1362,7 @@ PHP_FUNCTION(stream_set_blocking)
 		Z_PARAM_BOOL(block)
 	ZEND_PARSE_PARAMETERS_END();
 
-	if (php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL) == -1) {
-		RETURN_FALSE;
-	}
-
-	RETURN_TRUE;
+	RETURN_BOOL(-1 != php_stream_set_option(stream, PHP_STREAM_OPTION_BLOCKING, block, NULL));
 }
 
 /* }}} */
@@ -1407,11 +1403,7 @@ PHP_FUNCTION(stream_set_timeout)
 	}
 #endif
 
-	if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t)) {
-		RETURN_TRUE;
-	}
-
-	RETURN_FALSE;
+	RETURN_BOOL(PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &t));
 }
 #endif /* HAVE_SYS_TIME_H || defined(PHP_WIN32) */
 /* }}} */
@@ -1587,11 +1579,7 @@ PHP_FUNCTION(stream_is_local)
 		wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, 0);
 	}
 
-	if (!wrapper) {
-		RETURN_FALSE;
-	}
-
-	RETURN_BOOL(wrapper->is_url==0);
+	RETURN_BOOL(wrapper && wrapper->is_url == 0);
 }
 /* }}} */
 
@@ -1604,11 +1592,7 @@ PHP_FUNCTION(stream_supports_lock)
 		PHP_Z_PARAM_STREAM(stream)
 	ZEND_PARSE_PARAMETERS_END();
 
-	if (!php_stream_supports_lock(stream)) {
-		RETURN_FALSE;
-	}
-
-	RETURN_TRUE;
+	RETURN_BOOL(php_stream_supports_lock(stream));
 }
 
 /* {{{ Check if a stream is a TTY. */
@@ -1635,15 +1619,13 @@ PHP_FUNCTION(stream_isatty)
 
 #ifdef PHP_WIN32
 	/* Check if the Windows standard handle is redirected to file */
-	RETVAL_BOOL(php_win32_console_fileno_is_console(fileno));
+	RETURN_BOOL(php_win32_console_fileno_is_console(fileno));
 #elif defined(HAVE_UNISTD_H)
 	/* Check if the file descriptor identifier is a terminal */
-	RETVAL_BOOL(isatty(fileno));
+	RETURN_BOOL(isatty(fileno));
 #else
-	{
-		zend_stat_t stat = {0};
-		RETVAL_BOOL(zend_fstat(fileno, &stat) == 0 && (stat.st_mode & /*S_IFMT*/0170000) == /*S_IFCHR*/0020000);
-	}
+	zend_stat_t stat = {0};
+	RETURN_BOOL(zend_fstat(fileno, &stat) == 0 && (stat.st_mode & /*S_IFMT*/0170000) == /*S_IFCHR*/0020000);
 #endif
 }
 
@@ -1689,21 +1671,10 @@ PHP_FUNCTION(sapi_windows_vt100_support)
 
 	if (enable_is_null) {
 		/* Check if the Windows standard handle has VT100 control codes enabled */
-		if (php_win32_console_fileno_has_vt100(fileno)) {
-			RETURN_TRUE;
-		}
-		else {
-			RETURN_FALSE;
-		}
-	}
-	else {
+		RETURN_BOOL(php_win32_console_fileno_has_vt100(fileno));
+	} else {
 		/* Enable/disable VT100 control codes support for the specified Windows standard handle */
-		if (php_win32_console_fileno_set_vt100(fileno, enable ? TRUE : FALSE)) {
-			RETURN_TRUE;
-		}
-		else {
-			RETURN_FALSE;
-		}
+		RETURN_BOOL(php_win32_console_fileno_set_vt100(fileno, enable ? TRUE : FALSE));
 	}
 }
 #endif

From fa85b38560a36f4f9781a64753b809387cdaa6c6 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 14:29:54 +0100
Subject: [PATCH 19/27] streams: voidify php_stream_filter_prepend_ex()

This only ever returned SUCCESS
---
 ext/standard/streamsfuncs.c          | 25 ++++++++++++-------------
 main/streams/filter.c                |  4 +---
 main/streams/php_stream_filter_api.h |  2 +-
 3 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c
index cc7fc0406555e..506ce0dafed8b 100644
--- a/ext/standard/streamsfuncs.c
+++ b/ext/standard/streamsfuncs.c
@@ -1210,7 +1210,6 @@ static void apply_filter_to_stream(bool append, INTERNAL_FUNCTION_PARAMETERS)
 	zend_long read_write = 0;
 	zval *filterparams = NULL;
 	php_stream_filter *filter = NULL;
-	int ret;
 
 	ZEND_PARSE_PARAMETERS_START(2, 4)
 		PHP_Z_PARAM_STREAM(stream)
@@ -1241,13 +1240,13 @@ static void apply_filter_to_stream(bool append, INTERNAL_FUNCTION_PARAMETERS)
 		}
 
 		if (append) {
-			ret = php_stream_filter_append_ex(&stream->readfilters, filter);
+			zend_result ret = php_stream_filter_append_ex(&stream->readfilters, filter);
+			if (ret != SUCCESS) {
+				php_stream_filter_remove(filter, 1);
+				RETURN_FALSE;
+			}
 		} else {
-			ret = php_stream_filter_prepend_ex(&stream->readfilters, filter);
-		}
-		if (ret != SUCCESS) {
-			php_stream_filter_remove(filter, 1);
-			RETURN_FALSE;
+			php_stream_filter_prepend_ex(&stream->readfilters, filter);
 		}
 	}
 
@@ -1258,13 +1257,13 @@ static void apply_filter_to_stream(bool append, INTERNAL_FUNCTION_PARAMETERS)
 		}
 
 		if (append) {
-			ret = php_stream_filter_append_ex(&stream->writefilters, filter);
+			zend_result ret = php_stream_filter_append_ex(&stream->writefilters, filter);
+			if (ret != SUCCESS) {
+				php_stream_filter_remove(filter, 1);
+				RETURN_FALSE;
+			}
 		} else {
-			ret = php_stream_filter_prepend_ex(&stream->writefilters, filter);
-		}
-		if (ret != SUCCESS) {
-			php_stream_filter_remove(filter, 1);
-			RETURN_FALSE;
+			php_stream_filter_prepend_ex(&stream->writefilters, filter);
 		}
 	}
 
diff --git a/main/streams/filter.c b/main/streams/filter.c
index 0a698737b6de3..0f0e650046585 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -282,7 +282,7 @@ PHPAPI void php_stream_filter_free(php_stream_filter *filter)
 	pefree(filter, filter->is_persistent);
 }
 
-PHPAPI zend_result php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
+PHPAPI void php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter)
 {
 	filter->next = chain->head;
 	filter->prev = NULL;
@@ -294,8 +294,6 @@ PHPAPI zend_result php_stream_filter_prepend_ex(php_stream_filter_chain *chain,
 	}
 	chain->head = filter;
 	filter->chain = chain;
-
-	return SUCCESS;
 }
 
 PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter)
diff --git a/main/streams/php_stream_filter_api.h b/main/streams/php_stream_filter_api.h
index 8e697c2ad0194..e224b85b2d9a2 100644
--- a/main/streams/php_stream_filter_api.h
+++ b/main/streams/php_stream_filter_api.h
@@ -121,7 +121,7 @@ struct _php_stream_filter {
 /* stack filter onto a stream */
 BEGIN_EXTERN_C()
 PHPAPI void _php_stream_filter_prepend(php_stream_filter_chain *chain, php_stream_filter *filter);
-PHPAPI zend_result php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
+PHPAPI void php_stream_filter_prepend_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI void _php_stream_filter_append(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI zend_result php_stream_filter_append_ex(php_stream_filter_chain *chain, php_stream_filter *filter);
 PHPAPI zend_result _php_stream_filter_flush(php_stream_filter *filter, bool finish);

From 93ec0ac0f3f309e3b72f07cda6701beee5fe4338 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 14:42:53 +0100
Subject: [PATCH 20/27] streams: drop unused includes

---
 main/streams/filter.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/main/streams/filter.c b/main/streams/filter.c
index 0f0e650046585..beaba503cbe16 100644
--- a/main/streams/filter.c
+++ b/main/streams/filter.c
@@ -15,10 +15,8 @@
  */
 
 #include "php.h"
-#include "php_globals.h"
 #include "php_network.h"
-#include "php_open_temporary_file.h"
-#include "ext/standard/file.h"
+#include "ext/standard/file.h"  /* For FG(stream_filters) */
 #include <stddef.h>
 #include <fcntl.h>
 

From 051414bec6bdc43b8345e317b680d2d063b28067 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 14:52:32 +0100
Subject: [PATCH 21/27] streams: use an enum for return type of
 _php_stream_make_seekable()

---
 main/php_streams.h  | 13 ++++++++-----
 main/streams/cast.c |  2 +-
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/main/php_streams.h b/main/php_streams.h
index d3a6660c6ca7c..1c52539cfcaee 100644
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -646,15 +646,18 @@ PHPAPI const char *php_stream_locate_eol(php_stream *stream, zend_string *buf);
 /* pushes an error message onto the stack for a wrapper instance */
 PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, int options, const char *fmt, ...) PHP_ATTRIBUTE_FORMAT(printf, 3, 4);
 
-#define PHP_STREAM_UNCHANGED	0 /* orig stream was seekable anyway */
-#define PHP_STREAM_RELEASED		1 /* newstream should be used; origstream is no longer valid */
-#define PHP_STREAM_FAILED		2 /* an error occurred while attempting conversion */
-#define PHP_STREAM_CRITICAL		3 /* an error occurred; origstream is in an unknown state; you should close origstream */
+typedef enum {
+	PHP_STREAM_UNCHANGED = 0, /* orig stream was seekable anyway */
+	PHP_STREAM_RELEASED = 1, /* newstream should be used; origstream is no longer valid */
+	PHP_STREAM_FAILED = 2, /* an error occurred while attempting conversion */
+	PHP_STREAM_CRITICAL = 3, /* an error occurred; origstream is in an unknown state; you should close origstream */
+} php_stream_make_seekable_status;
+
 #define PHP_STREAM_NO_PREFERENCE	0
 #define PHP_STREAM_PREFER_STDIO		1
 #define PHP_STREAM_FORCE_CONVERSION	2
 /* DO NOT call this on streams that are referenced by resources! */
-PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC);
+PHPAPI php_stream_make_seekable_status _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC);
 #define php_stream_make_seekable(origstream, newstream, flags)	_php_stream_make_seekable((origstream), (newstream), (flags) STREAMS_CC)
 
 /* Give other modules access to the url_stream_wrappers_hash and stream_filters_hash */
diff --git a/main/streams/cast.c b/main/streams/cast.c
index 5c78b560945eb..4b7183024571b 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -370,7 +370,7 @@ PHPAPI FILE * _php_stream_open_wrapper_as_file(char *path, char *mode, int optio
 /* }}} */
 
 /* {{{ php_stream_make_seekable */
-PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC)
+PHPAPI php_stream_make_seekable_status _php_stream_make_seekable(php_stream *origstream, php_stream **newstream, int flags STREAMS_DC)
 {
 	if (newstream == NULL) {
 		return PHP_STREAM_FAILED;

From a02b2b8eb6e26514bf69e5284950164f8e43d0e3 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 15:12:01 +0100
Subject: [PATCH 22/27] streams: use %zu printf specifier for size_t

Rather than casting to zend_long
---
 main/streams/xp_socket.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index f843fe83e27ff..fd063260296b9 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -107,8 +107,8 @@ static ssize_t php_sockop_write(php_stream *stream, const char *buf, size_t coun
 		if (!(stream->flags & PHP_STREAM_FLAG_SUPPRESS_ERRORS)) {
 			estr = php_socket_strerror(err, NULL, 0);
 			php_error_docref(NULL, E_NOTICE,
-				"Send of " ZEND_LONG_FMT " bytes failed with errno=%d %s",
-				(zend_long)count, err, estr);
+				"Send of %zu bytes failed with errno=%d %s",
+				count, err, estr);
 			efree(estr);
 		}
 	}

From 9e334af6e4e790cc11b4a0dc68f740d9a5b9add0 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 15:14:23 +0100
Subject: [PATCH 23/27] streams: use type php_socket_t instead of type int

---
 main/streams/xp_socket.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/main/streams/xp_socket.c b/main/streams/xp_socket.c
index fd063260296b9..ae8cf24c39221 100644
--- a/main/streams/xp_socket.c
+++ b/main/streams/xp_socket.c
@@ -857,7 +857,6 @@ static inline int php_tcp_sockop_connect(php_stream *stream, php_netstream_data_
 static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t *sock,
 		php_stream_xport_param *xparam STREAMS_DC)
 {
-	int clisock;
 	bool nodelay = 0;
 	zval *tmpzval = NULL;
 
@@ -869,7 +868,7 @@ static inline int php_tcp_sockop_accept(php_stream *stream, php_netstream_data_t
 		nodelay = 1;
 	}
 
-	clisock = php_network_accept_incoming(sock->socket,
+	php_socket_t clisock = php_network_accept_incoming(sock->socket,
 		xparam->want_textaddr ? &xparam->outputs.textaddr : NULL,
 		xparam->want_addr ? &xparam->outputs.addr : NULL,
 		xparam->want_addr ? &xparam->outputs.addrlen : NULL,

From 4d5bdef21a96cd22d84956abca57e13a6f4467b4 Mon Sep 17 00:00:00 2001
From: Gina Peter Banyard <girgias@php.net>
Date: Sat, 12 Jul 2025 15:26:49 +0100
Subject: [PATCH 24/27] streams: refactor statbuf_from_array()

---
 main/streams/userspace.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/main/streams/userspace.c b/main/streams/userspace.c
index baf6c96605726..a25900eda3054 100644
--- a/main/streams/userspace.c
+++ b/main/streams/userspace.c
@@ -794,12 +794,12 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when
 
 /* parse the return value from one of the stat functions and store the
  * relevant fields into the statbuf provided */
-static void statbuf_from_array(zval *array, php_stream_statbuf *ssb)
+static void statbuf_from_array(const HashTable *array, php_stream_statbuf *ssb)
 {
 	zval *elem;
 
 #define STAT_PROP_ENTRY_EX(name, name2)                        \
-	if (NULL != (elem = zend_hash_str_find(Z_ARRVAL_P(array), #name, sizeof(#name)-1))) {     \
+	if (NULL != (elem = zend_hash_str_find(array, #name, sizeof(#name)-1))) {     \
 		ssb->sb.st_##name2 = zval_get_long(elem);                                                      \
 	}
 
@@ -842,7 +842,7 @@ static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb)
 	zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL);
 
 	if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) {
-		statbuf_from_array(&retval, ssb);
+		statbuf_from_array(Z_ARR(retval), ssb);
 		ret = 0;
 	} else {
 		if (call_result == FAILURE) {
@@ -1275,7 +1275,7 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i
 
 	if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) {
 		/* We got the info we needed */
-		statbuf_from_array(&zretval, ssb);
+		statbuf_from_array(Z_ARR(zretval), ssb);
 		ret = 0;
 	} else {
 		if (call_result == FAILURE) {

From 9d29283392191f660ad767b246d0f4e064b5d174 Mon Sep 17 00:00:00 2001
From: Arndt Kaiser <madmajestro@gmail.com>
Date: Fri, 1 Aug 2025 19:51:09 +0200
Subject: [PATCH 25/27] Fix filtering of INI directives to respect leading
 whitespaces

Directives are now correctly filtered out if the line in the php.ini
file begins with whitespace characters.

Closes GH-19348
---
 build/Makefile.global | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/build/Makefile.global b/build/Makefile.global
index ec19efcbc5894..49d4f8bcb3cfe 100644
--- a/build/Makefile.global
+++ b/build/Makefile.global
@@ -90,7 +90,7 @@ PHP_TEST_SHARED_EXTENSIONS =  ` \
 			. $$i; $(top_srcdir)/build/shtool echo -n -- " -d zend_extension=$(top_builddir)/modules/$$dlname"; \
 		done; \
 	fi`
-PHP_DEPRECATED_DIRECTIVES_REGEX = '^(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*='
+PHP_DEPRECATED_DIRECTIVES_REGEX = '^[\t\ ]*(magic_quotes_(gpc|runtime|sybase)?|(zend_)?extension(_debug)?(_ts)?)[\t\ ]*='
 
 test: all
 	@if test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \

From e9e432a35d8327d0bac84216d1b8afddae3c535f Mon Sep 17 00:00:00 2001
From: Ilija Tovilo <ilija.tovilo@me.com>
Date: Mon, 4 Aug 2025 16:16:49 +0200
Subject: [PATCH 26/27] Upgrade to macOS 14 and backport necessary changes

---
 .github/actions/brew/action.yml               | 26 ++++++-------
 .github/actions/configure-macos/action.yml    | 37 ++++++++++---------
 .../actions/verify-generated-files/action.yml |  2 +-
 .github/workflows/push.yml                    |  4 +-
 4 files changed, 33 insertions(+), 36 deletions(-)

diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml
index 5cd8d33cfa6b7..fa2acd58d2e57 100644
--- a/.github/actions/brew/action.yml
+++ b/.github/actions/brew/action.yml
@@ -5,31 +5,27 @@ runs:
     - shell: bash
       run: |
         set -x
+
+        # Patch brew to overwrite always
+        formula_installer="$(brew --repo)"/Library/Homebrew/formula_installer.rb
+        code=" keg.link\(verbose: verbose\?"
+        sudo sed -Ei '' "s/$code.*/$code, overwrite: true\)/" "$formula_installer"
+
+        # Some packages exist on x86 but not arm, or vice versa.
+        # Install them with reinstall to avoid warnings.
+        brew reinstall autoconf webp tidy-html5 libzip libsodium icu4c
         brew install \
-          pkg-config \
-          autoconf \
           bison \
           re2c
         brew install \
-          openssl@1.1 \
-          curl \
+          aspell \
           krb5 \
           bzip2 \
           enchant \
           libffi \
-          libpng \
-          webp \
-          freetype \
           intltool \
           libiconv \
-          zlib \
           t1lib \
-          gd \
-          libzip \
-          gmp \
-          tidy-html5 \
           libxml2 \
           libjpeg \
-          libxslt \
-          postgresql
-        brew link gettext --force
+          libxslt
diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml
index 87627d4f84848..071e31708c21b 100644
--- a/.github/actions/configure-macos/action.yml
+++ b/.github/actions/configure-macos/action.yml
@@ -10,15 +10,15 @@ runs:
       run: |
         set -x
         BREW_OPT="$(brew --prefix)"/opt
-        export PATH="/usr/local/opt/bison/bin:$PATH"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/curl/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/krb5/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libffi/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxml2/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig"
-        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c@74/lib/pkgconfig"
+        export PATH="$BREW_OPT/bison/bin:$PATH"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@1.1/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/krb5/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxslt/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/zlib/lib/pkgconfig"
+        export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/icu4c/lib/pkgconfig"
         sed -i -e 's/Requires.private:.*//g' "$BREW_OPT/curl/lib/pkgconfig/libcurl.pc"
         ./buildconf --force
         ./configure \
@@ -28,8 +28,8 @@ runs:
           --enable-fpm \
           --with-pdo-mysql=mysqlnd \
           --with-mysqli=mysqlnd \
-          --with-pgsql=/usr/local/opt/libpq \
-          --with-pdo-pgsql=/usr/local/opt/libpq \
+          --with-pgsql="$BREW_OPT"/libpq \
+          --with-pdo-pgsql="$BREW_OPT"/libpq \
           --with-pdo-sqlite \
           --without-pear \
           --enable-gd \
@@ -42,30 +42,31 @@ runs:
           --enable-soap \
           --enable-xmlreader \
           --with-xsl \
-          --with-tidy=/usr/local/opt/tidy-html5 \
+          --with-tidy="$BREW_OPT"/tidy-html5 \
           --with-libxml \
           --enable-sysvsem \
           --enable-sysvshm \
           --enable-shmop \
           --enable-pcntl \
-          --with-readline=/usr/local/opt/readline \
+          --with-readline="$BREW_OPT"/readline \
           --enable-mbstring \
           --with-curl \
-          --with-gettext=/usr/local/opt/gettext \
+          --with-gettext="$BREW_OPT"/gettext \
           --enable-sockets \
-          --with-bz2=/usr/local/opt/bzip2 \
+          --with-bz2="$BREW_OPT"/bzip2 \
           --with-openssl \
-          --with-gmp=/usr/local/opt/gmp \
-          --with-iconv=/usr/local/opt/libiconv \
+          --with-gmp="$BREW_OPT"/gmp \
+          --with-iconv="$BREW_OPT"/libiconv \
           --enable-bcmath \
           --enable-calendar \
           --enable-ftp \
-          --with-pspell=/usr/local/opt/aspell \
+          --with-pspell="$BREW_OPT"/aspell \
           --with-kerberos \
           --enable-sysvmsg \
           --with-ffi \
           --enable-zend-test \
           --enable-dl-test=shared \
+          --enable-intl \
           --with-mhash \
           --with-sodium \
           --enable-dba \
diff --git a/.github/actions/verify-generated-files/action.yml b/.github/actions/verify-generated-files/action.yml
index 4bd838f631728..4d26e5f250274 100644
--- a/.github/actions/verify-generated-files/action.yml
+++ b/.github/actions/verify-generated-files/action.yml
@@ -5,7 +5,7 @@ runs:
     - shell: bash
       run: |
         set -x
-        [[ "$OSTYPE" == "darwin"* ]] && export PATH="/usr/local/opt/bison/bin:$PATH"
+        [[ "$OSTYPE" == "darwin"* ]] && export PATH="$(brew --prefix)/opt/bison/bin:$PATH"
         scripts/dev/genfiles
         Zend/zend_vm_gen.php
         build/gen_stub.php -f
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index 5353ef7d0ea44..5c1057dd9164b 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -113,7 +113,7 @@ jobs:
         uses: ./.github/actions/verify-generated-files
   MACOS_DEBUG_NTS:
     if: github.repository == 'php/php-src' || github.event_name == 'pull_request'
-    runs-on: macos-13
+    runs-on: macos-14
     steps:
       - name: git checkout
         uses: actions/checkout@v4
@@ -130,7 +130,7 @@ jobs:
           configurationParameters: --enable-debug --disable-zts
       - name: make
         run: |-
-          export PATH="/usr/local/opt/bison/bin:$PATH"
+          export PATH="$(brew --prefix)/opt/bison/bin:$PATH"
           make -j$(sysctl -n hw.logicalcpu) >/dev/null
       - name: make install
         run: sudo make install

From f077c9d233f4fe6096a83cd87a36db96b64012b4 Mon Sep 17 00:00:00 2001
From: Ilija Tovilo <ilija.tovilo@me.com>
Date: Mon, 4 Aug 2025 23:10:41 +0200
Subject: [PATCH 27/27] [skip ci] Add zend_jit_arm64.c to gitignore

This is not generated in CI since moving to macOS 14, which is arm based.
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index b8eb03412fa61..49acc9f2e1788 100644
--- a/.gitignore
+++ b/.gitignore
@@ -169,6 +169,7 @@ php
 
 # Miscellaneous extensions files
 /ext/opcache/jit/zend_jit_x86.c
+/ext/opcache/jit/zend_jit_arm64.c
 /ext/opcache/minilua
 
 # Generated by `cd ext/name && phpize && ./configure`
