preserve runtime integer division semantics

master
韩天峰 1 day ago
parent 10f16726e0
commit 34e229e30b
  1. 7
      src/CompilerBase.php
  2. 2
      src/Optimizer/SsaTypeOptimizer.php
  3. 10
      src/Parser/BinaryOpTrait.php
  4. 50
      tests/compiler/operator/runtime-int-division.phpt

@ -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) {

@ -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;
}

@ -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 . '))';
}

@ -0,0 +1,50 @@
--TEST--
Runtime int division preserves PHP exact, fractional, overflow and zero-divisor semantics
--FILE--
<?php
declare(strict_types=1);
function divide(int $left, int $right): mixed
{
return $left / $right;
}
function divide_assign(int $left, int $right): mixed
{
$result = $left;
$result /= $right;
return $result;
}
function main(): void
{
var_dump(divide(12, 3));
var_dump(divide(10, 4));
var_dump(divide(PHP_INT_MIN, -1));
var_dump(divide_assign(12, 3));
var_dump(divide_assign(10, 4));
var_dump(divide_assign(PHP_INT_MIN, -1));
try {
divide(10, 0);
} catch (DivisionByZeroError $error) {
echo "divide by zero\n";
}
try {
divide_assign(10, 0);
} catch (DivisionByZeroError $error) {
echo "divide assign by zero\n";
}
}
?>
--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
Loading…
Cancel
Save