Commit 87d728d2489 for php

commit 87d728d24895068a7e886ad04368447797399c7f
Author: Lazizbek Ergashev <lazerg2@gmail.com>
Date:   Fri Oct 2 19:20:51 2026 +0500

    Fix GH-24063: SCCP merges 0.0 and -0.0 into a single constant (#24064)

    SCCP joins phi values with zend_is_identical(), which compares doubles with ==, so 0.0 and -0.0 count as the same value. A ternary like $n < 0 ? -0.0 : 0.0 then gets folded to whichever constant the first branch held, and the condition disappears. The same thing happens for constant arrays and for elements of partial arrays.

    The join now compares doubles bitwise (like compact_literals.c already does), recursing into arrays, and falls back to zend_is_identical() for everything else.

    Fixes GH-24063

diff --git a/NEWS b/NEWS
index 7b24c94899c..99df5910c8b 100644
--- a/NEWS
+++ b/NEWS
@@ -108,6 +108,8 @@ PHP                                                                        NEWS
     (David Carlier)
   . Fix multiple incorrect DCE due to unsound escape analysis. (ndossche,
     arnaud-lb)
+  . Fixed bug GH-24063 (OPcache optimizer folds `$cond ? -0.0 : 0.0` into a
+    single `-0.0` constant). (lazerg)

 - OpenSSL:
   . Fixed stream_socket_enable_crypto() leaving the socket non-blocking
diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
index 24b63a9cf40..ca5184d8d5a 100644
--- a/Zend/Optimizer/sccp.c
+++ b/Zend/Optimizer/sccp.c
@@ -1946,6 +1946,28 @@ static void sccp_mark_feasible_successors(
 	scdf_mark_edge_feasible(scdf, block_num, block->successors[s]);
 }

+/* Unlike zend_is_identical(), this does not treat 0.0 and -0.0 as the same value.
+ * Returns int to be usable as compare_func_t. */
+static int sccp_values_differ(const void *p1, const void *p2)
+{
+	const zval *a = p1;
+	const zval *b = p2;
+
+	if (Z_TYPE_P(a) != Z_TYPE_P(b)) {
+		return 1;
+	}
+	if (Z_TYPE_P(a) == IS_DOUBLE) {
+		return memcmp(&Z_DVAL_P(a), &Z_DVAL_P(b), sizeof(double)) != 0;
+	}
+	if (Z_TYPE_P(a) == IS_ARRAY) {
+		return Z_ARRVAL_P(a) != Z_ARRVAL_P(b)
+			&& zend_hash_compare(Z_ARRVAL_P(a), Z_ARRVAL_P(b), sccp_values_differ, 1) != 0;
+	}
+	ZEND_ASSERT(IS_PARTIAL_ARRAY(a) || IS_PARTIAL_OBJECT(a)
+		|| ((1 << Z_TYPE_P(a)) & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_BOOL|MAY_BE_LONG|MAY_BE_STRING)));
+	return !zend_is_identical(a, b);
+}
+
 static void join_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2)
 {
 	zend_ulong index;
@@ -1958,7 +1980,7 @@ static void join_hash_tables(HashTable *ret, HashTable *ht1, HashTable *ht2)
 		} else {
 			val2 = zend_hash_index_find(ht2, index);
 		}
-		if (val2 && zend_is_identical(val1, val2)) {
+		if (val2 && !sccp_values_differ(val1, val2)) {
 			if (key) {
 				val1 = zend_hash_add_new(ret, key, val1);
 			} else {
@@ -2026,7 +2048,7 @@ static void join_phi_values(zval *a, zval *b, bool escape) {
 			zval_ptr_dtor_nogc(a);
 			MAKE_BOT(a);
 		}
-	} else if (!zend_is_identical(a, b)) {
+	} else if (sccp_values_differ(a, b)) {
 		if (join_partial_arrays(a, b) == FAILURE) {
 			zval_ptr_dtor_nogc(a);
 			MAKE_BOT(a);
diff --git a/ext/opcache/tests/opt/gh24063.phpt b/ext/opcache/tests/opt/gh24063.phpt
new file mode 100644
index 00000000000..36f3ddbdca1
--- /dev/null
+++ b/ext/opcache/tests/opt/gh24063.phpt
@@ -0,0 +1,46 @@
+--TEST--
+GH-24063 (SCCP merges 0.0 and -0.0 into a single constant)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+--FILE--
+<?php
+
+function scalar(float $n): float
+{
+    $r = $n < 0 ? -0.0 : 0.0;
+    return $r;
+}
+
+function arr(float $n): array
+{
+    $r = $n < 0 ? [-0.0] : [0.0];
+    return $r;
+}
+
+function partial(float $n, $x): float
+{
+    $a = ['x' => $x];
+    if ($n < 0) {
+        $a['k'] = -0.0;
+    } else {
+        $a['k'] = 0.0;
+    }
+    return $a['k'];
+}
+
+var_dump(scalar(1.0), scalar(-1.0));
+var_dump(arr(1.0)[0], arr(-1.0)[0]);
+var_dump(partial(1.0, 1), partial(-1.0, 1));
+
+?>
+--EXPECT--
+float(0)
+float(-0)
+float(0)
+float(-0)
+float(0)
+float(-0)