diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 9d946f07..fde10bed 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -3047,6 +3047,13 @@ class CompilerBase implements PropertyAccessContext if ($evaluation !== null && is_float($evaluation['result'])) { return Type::FLOAT; } + // Runtime integer division has a value-dependent PHP + // result: exact quotients are int, fractional quotients + // and PHP_INT_MIN / -1 are float. Keep it boxed when the + // operands are not compile-time constants. + if ($exprType === 'Expr_BinaryOp_Div' && $evaluation === null) { + return Type::VAR; + } } } if ($leftType === Type::INT || $rightType === Type::INT) { diff --git a/src/Optimizer/SsaTypeOptimizer.php b/src/Optimizer/SsaTypeOptimizer.php index e235b0af..f8eb9afb 100644 --- a/src/Optimizer/SsaTypeOptimizer.php +++ b/src/Optimizer/SsaTypeOptimizer.php @@ -318,7 +318,7 @@ trait SsaTypeOptimizer */ protected function exprCanOverflowInt(NodeAbstract $expr): bool { - // Division always produces float in PHP when operands are int + // Runtime division may produce float even when both operands are int. if ($expr instanceof Node\Expr\BinaryOp\Div) { return true; } diff --git a/src/Parser/BinaryOpTrait.php b/src/Parser/BinaryOpTrait.php index c8b5c35c..8933c68a 100644 --- a/src/Parser/BinaryOpTrait.php +++ b/src/Parser/BinaryOpTrait.php @@ -158,20 +158,20 @@ trait BinaryOpTrait } // Declared int parameters use the native Int ABI even in ordinary PHP - // mode. A direct C++ +/−/* would therefore have undefined signed - // overflow, while PHP promotes the result to float. Route dynamic + // mode. Direct C++ +/−/* can overflow, while C++ integer division + // truncates and cannot raise PHP's DivisionByZeroError. Route dynamic // integer arithmetic through the encapsulated Variant operators unless // the user explicitly selected `use native_types`. Fully constant // expressions remain safe to emit directly after the checks above. if (!$this->nativeTypes && $leftType === Type::INT && $rightType === Type::INT - && in_array($op, ['+', '-', '*'], true) + && in_array($op, ['+', '-', '*', '/'], true) && $this->evaluateConstantIntArithmetic($left, $right, $op) === null ) { // Keep the potentially widening result boxed, but pass the native - // RHS directly so PHPX can use its inline checked-int overload - // without constructing and destroying another temporary zval. + // RHS directly so PHPX can use its inline checked arithmetic + // overload without constructing and destroying another zval. return '((php::Var(' . $leftExpr . ')) ' . $op . ' (' . $rightExpr . '))'; } diff --git a/tests/compiler/operator/runtime-int-division.phpt b/tests/compiler/operator/runtime-int-division.phpt new file mode 100644 index 00000000..079adecb --- /dev/null +++ b/tests/compiler/operator/runtime-int-division.phpt @@ -0,0 +1,50 @@ +--TEST-- +Runtime int division preserves PHP exact, fractional, overflow and zero-divisor semantics +--FILE-- + +--EXPECT-- +int(4) +float(2.5) +float(9.223372036854776E+18) +int(4) +float(2.5) +float(9.223372036854776E+18) +divide by zero +divide assign by zero