Skip to content

fix(compiler): convert float operands of %, <<, >>, &, | and ^ to int - #141

Open
Giandonn wants to merge 1 commit into
swoole:masterfrom
Giandonn:fix/bitwise-float-operands
Open

Giandonn wants to merge 1 commit into
swoole:masterfrom
Giandonn:fix/bitwise-float-operands

Conversation

@Giandonn

@Giandonn Giandonn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #140

PHP converts a float operand of %, <<, >>, &, | and ^ to int and always yields an int. TypePHP inferred float for these expressions and emitted the raw C++ operator, which does not exist for double, so the generated C++ did not compile.

The change

  • Type inference (CompilerBase): with a float operand, %, <<, >>, &, | and ^ produce int.
  • Codegen (BinaryOpTrait): a bitwise or shift operator with a float operand goes through the php::Var operator, so Zend applies its own float-to-int conversion (deprecation of a lossy conversion, 0 for NAN/INF/out of range, ArithmeticError for a negative shift). This is placed before the existing Int → Float promotion, which would otherwise round a large int operand (PHP_INT_MAX ^ 1.0 must stay 9223372036854775806). % already went through php::fn::mod.
  • Compound assignment (AssignOpTrait): %=, &=, |=, ^=, <<=, >>= with a float operand on an int local take the same route. On a native float local the result would have to become an int, which a native local cannot do (the documented "reassigning a native local to an incompatible type" rule), so that is now a clear compile-time error — Cannot apply %= to a native float variable: PHP converts the result to int — instead of invalid C++.

Verified on a binary

PHP 8.4.26 ZTS with embed, GCC 11, Linux x64. 20 cases compared against PHP (6.0 & 3.0, -1.0 & 255.0, 1.5 & 1.0, 1e20 & 1.0, NAN & 1.0, INF & 1.0, 2.0 | PHP_INT_MAX, PHP_INT_MAX ^ 1.0, PHP_INT_MIN ^ 0.0, 1.0 << 3.0, 1.0 << 64.0, 1.0 << -1.0, -8.0 >> 1, 7.5 % 2, 7.5 % 0, $x &= 3.0, $x %= 2.5, $x <<= 1.0, and literals):

master this PR
program with the 20 cases does not compile (11 C++ errors) identical to PHP in all 20
new tests/compiler/operator/float-bitwise-operands.phpt FAIL (invalid operands ... operator&) PASS
new FloatLocalIntOnlyAssignOpTest FAIL PASS
tests/compiler (full) — 1248 passed, 0 failed, 27 skipped
PHPUnit (full) — 2409 tests OK, 4 skipped

Found with a differential fuzzer; the same session found #137.

PHP converts a float operand of these operators to int and always yields an
int. TypePHP inferred float for them and emitted the raw C++ operator, which
does not exist for double, so ordinary PHP code failed in the C++ compiler:

    function f(float $a, float $b): int { return $a & $b; }
    // error: invalid operands of types php::Float and php::Float
    //        to binary operator&

The same happened for |, ^, << and >>, for an int and a float operand, and
for the compound forms on an int local ($x &= 3.0, $x %= 2.5). The type of
`$x = $float % $int` was also inferred as float (var_dump printed float(1)
where PHP prints int(1)).

- Type inference: with a float operand, %, <<, >>, &, | and ^ produce int.
- Codegen: a bitwise or shift operator with a float operand goes through the
  php::Var operator, so Zend applies its own float-to-int conversion (the
  deprecation of a lossy conversion, 0 for NAN/INF/out of range, and the
  ArithmeticError of a negative shift). This happens before the Int -> Float
  promotion, which would round a large int operand (PHP_INT_MAX ^ 1.0).
- An int local's compound %=, &=, |=, ^=, <<= or >>= with a float operand
  takes the same route. On a native float local the result would have to
  become an int, which a native local cannot do; that is now a compile-time
  error instead of invalid C++.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bitwise and shift operators with a float operand emit invalid C++

1 participant