diff --git a/lib/Compiler.php b/lib/Compiler.php index 10e5025a..e2da6e10 100755 --- a/lib/Compiler.php +++ b/lib/Compiler.php @@ -280,6 +280,8 @@ protected function getOpCodeTypeFromBinaryOp(Op\Expr\BinaryOp $expr): int { return OpCode::TYPE_IDENTICAL; } elseif ($expr instanceof Op\Expr\BinaryOp\NotIdentical) { return OpCode::TYPE_NOT_IDENTICAL; + } elseif ($expr instanceof Op\Expr\BinaryOp\Spaceship) { + return OpCode::TYPE_SPACESHIP; } elseif ($expr instanceof Op\Expr\BinaryOp\Minus) { return OpCode::TYPE_MINUS; } elseif ($expr instanceof Op\Expr\BinaryOp\Mul) { diff --git a/lib/JIT.php b/lib/JIT.php index b178e922..878ef839 100644 --- a/lib/JIT.php +++ b/lib/JIT.php @@ -414,6 +414,7 @@ private function compileBlockInternal( case OpCode::TYPE_NOT_IDENTICAL: case OpCode::TYPE_EQUAL: case OpCode::TYPE_NOT_EQUAL: + case OpCode::TYPE_SPACESHIP: $this->assignOperand( $block->getOperand($op->arg1), $this->context->helper->binaryOp( diff --git a/lib/JIT.pre b/lib/JIT.pre index 24730166..aff76d38 100755 --- a/lib/JIT.pre +++ b/lib/JIT.pre @@ -343,6 +343,7 @@ class JIT { case OpCode::TYPE_NOT_IDENTICAL: case OpCode::TYPE_EQUAL: case OpCode::TYPE_NOT_EQUAL: + case OpCode::TYPE_SPACESHIP: $this->assignOperand( $block->getOperand($op->arg1), $this->context->helper->binaryOp( diff --git a/lib/JIT/Helper.php b/lib/JIT/Helper.php index 46f6a33f..33827dfd 100644 --- a/lib/JIT/Helper.php +++ b/lib/JIT/Helper.php @@ -106,9 +106,17 @@ public function binaryOp(OpCode $opcode, Variable $left, Variable $right): Varia goto return_bool; case OpCode::TYPE_NOT_IDENTICAL: case OpCode::TYPE_NOT_EQUAL: - case OpCode::TYPE_NOT_IDENTICAL: $result = $this->context->builder->fcmp(Builder::REAL_ONE, $leftValue, $rightValue); goto return_bool; + case OpCode::TYPE_SPACESHIP: + $lt = $this->context->builder->fcmp(Builder::REAL_OLT, $leftValue, $rightValue); + $gt = $this->context->builder->fcmp(Builder::REAL_OGT, $leftValue, $rightValue); + $ty = $leftValue->typeOf(); + $negOne = $ty->constInt(-1, true); + $one = $ty->constInt(1, true); + $zero = $ty->constInt(0, false); + $result = $this->context->builder->select($gt, $one, $this->context->builder->select($lt, $negOne, $zero)); + goto return_long; } break; case TYPE_PAIR_NATIVE_LONG_NATIVE_LONG: @@ -434,6 +442,8 @@ public function binaryOp(OpCode $opcode, Variable $left, Variable $right): Varia + + @@ -444,6 +454,16 @@ public function binaryOp(OpCode $opcode, Variable $left, Variable $right): Varia $__right = $this->context->builder->intCast($rightValue, $leftValue->typeOf()); $result = $this->context->builder->icmp(\PHPLLVM\Builder::INT_NE, $leftValue, $__right); goto return_bool; + case OpCode::TYPE_SPACESHIP: + $__right = $this->context->builder->intCast($rightValue, $leftValue->typeOf()); + $lt = $this->context->builder->icmp(\PHPLLVM\Builder::INT_SLT, $leftValue, $__right); + $gt = $this->context->builder->icmp(\PHPLLVM\Builder::INT_SGT, $leftValue, $__right); + $ty = $leftValue->typeOf(); + $negOne = $ty->constInt(-1, true); + $one = $ty->constInt(1, true); + $zero = $ty->constInt(0, false); + $result = $this->context->builder->select($gt, $one, $this->context->builder->select($lt, $negOne, $zero)); + goto return_long; } break; case TYPE_PAIR_NATIVE_LONG_NATIVE_BOOL: diff --git a/lib/JIT/Helper.pre b/lib/JIT/Helper.pre index a23b7f38..b666d6f8 100644 --- a/lib/JIT/Helper.pre +++ b/lib/JIT/Helper.pre @@ -106,6 +106,17 @@ restart: case OpCode::TYPE_NOT_IDENTICAL: $result = $this->context->builder->fcmp(Builder::REAL_ONE, $leftValue, $rightValue); goto return_bool; + case OpCode::TYPE_SPACESHIP: + compile { + if ($leftValue < $rightValue) { + $result = -1; + } elseif ($leftValue > $rightValue) { + $result = 1; + } else { + $result = 0; + } + } + goto return_long; } break; case TYPE_PAIR_NATIVE_LONG_NATIVE_LONG: @@ -183,6 +194,17 @@ restart: $result = $leftValue != $rightValue; } goto return_bool; + case OpCode::TYPE_SPACESHIP: + compile { + if ($leftValue < $rightValue) { + $result = -1; + } elseif ($leftValue > $rightValue) { + $result = 1; + } else { + $result = 0; + } + } + goto return_long; } break; case TYPE_PAIR_NATIVE_LONG_NATIVE_BOOL: diff --git a/lib/OpCode.php b/lib/OpCode.php index a2180e2b..51223835 100755 --- a/lib/OpCode.php +++ b/lib/OpCode.php @@ -70,6 +70,7 @@ class OpCode { const TYPE_POW = 58; const TYPE_NOT_EQUAL = 59; const TYPE_NOT_IDENTICAL = 60; + const TYPE_SPACESHIP = 61; public int $type; public ?int $arg1; diff --git a/lib/VM.php b/lib/VM.php index a342b9af..d358a3a3 100755 --- a/lib/VM.php +++ b/lib/VM.php @@ -109,6 +109,12 @@ public function run(Block $block): int { $arg3 = $frame->scope[$op->arg3]; $arg1->compareOp($op->type, $arg2, $arg3); break; + case OpCode::TYPE_SPACESHIP: + $arg1 = $frame->scope[$op->arg1]; + $arg2 = $frame->scope[$op->arg2]; + $arg3 = $frame->scope[$op->arg3]; + $arg1->spaceshipOp($arg2, $arg3); + break; case OpCode::TYPE_PLUS: case OpCode::TYPE_MINUS: case OpCode::TYPE_MUL: diff --git a/lib/VM/Variable.php b/lib/VM/Variable.php index b7b759f7..3f454147 100755 --- a/lib/VM/Variable.php +++ b/lib/VM/Variable.php @@ -375,6 +375,7 @@ public function equals(Variable $other): bool { } return $this->looseEqual($self, $other); } + throw new \LogicException("Equals comparison between {$self->type} and {$other->type} not implemented"); } private function looseEqual(Variable $self, Variable $other): bool { @@ -471,6 +472,56 @@ private function _compareOp(int $opCode, $left, $right): bool { } } + public function spaceshipOp(Variable $left, Variable $right): void { + $this->reset(); +restart: + switch (type_pair($left->type, $right->type)) { + case TYPE_PAIR_INTEGER_INTEGER: + $this->int($this->_spaceship($left->integer, $right->integer)); + break; + case TYPE_PAIR_INTEGER_FLOAT: + $this->int($this->_spaceship($left->integer, $right->float)); + break; + case TYPE_PAIR_FLOAT_INTEGER: + $this->int($this->_spaceship($left->float, $right->integer)); + break; + case TYPE_PAIR_FLOAT_FLOAT: + $this->int($this->_spaceship($left->float, $right->float)); + break; + case TYPE_PAIR_STRING_STRING: + $cmp = strcmp($left->string, $right->string); + $this->int($cmp < 0 ? -1 : ($cmp > 0 ? 1 : 0)); + break; + case TYPE_PAIR_BOOLEAN_BOOLEAN: + $this->int($this->_spaceship((int) $left->bool, (int) $right->bool)); + break; + case TYPE_PAIR_NULL_NULL: + $this->int(0); + break; + default: + if ($left->type === self::TYPE_INDIRECT) { + $left = $left->indirect; + goto restart; + } elseif ($right->type === self::TYPE_INDIRECT) { + $right = $right->indirect; + goto restart; + } else { + $this->int($this->_spaceship($left->toNumeric(), $right->toNumeric())); + } + } + } + + private function _spaceship($left, $right): int { + if ($left < $right) { + return -1; + } + if ($left > $right) { + return 1; + } + + return 0; + } + public function bitwiseOp(int $opCode, Variable $left, Variable $right): void { $this->reset(); restart: diff --git a/patches/php-types-binaryop-spaceship.patch b/patches/php-types-binaryop-spaceship.patch new file mode 100644 index 00000000..5abdc9fc --- /dev/null +++ b/patches/php-types-binaryop-spaceship.patch @@ -0,0 +1,10 @@ +--- vendor/ircmaxell/php-types/lib/PHPTypes/TypeReconstructor.php ++++ vendor/ircmaxell/php-types/lib/PHPTypes/TypeReconstructor.php +@@ -205,6 +205,7 @@ + case 'Expr_BinaryOp_Mod': + case 'Expr_BinaryOp_ShiftLeft': + case 'Expr_BinaryOp_ShiftRight': ++ case 'Expr_BinaryOp_Spaceship': + case 'Expr_Cast_Int': + case 'Expr_Print': + return [Type::int()]; diff --git a/script/apply-patches.sh b/script/apply-patches.sh index a32acdc3..41c5add8 100755 --- a/script/apply-patches.sh +++ b/script/apply-patches.sh @@ -33,6 +33,7 @@ apply_patch "$PATCH_DIR/php-llvm-x86-posix-fallback.patch" if [[ -d "$ROOT/vendor/ircmaxell/php-types" ]]; then apply_patch "$PATCH_DIR/php-types-binaryop-pow.patch" + apply_patch "$PATCH_DIR/php-types-binaryop-spaceship.patch" apply_patch "$PATCH_DIR/php-types-str-bool-fns.patch" apply_patch "$PATCH_DIR/php-types-dollars-brace.patch" fi diff --git a/test/compliance/cases/language/spaceship_operator.phpt b/test/compliance/cases/language/spaceship_operator.phpt new file mode 100644 index 00000000..e31b773b --- /dev/null +++ b/test/compliance/cases/language/spaceship_operator.phpt @@ -0,0 +1,9 @@ +--TEST-- +Spaceship operator (<=>) for numbers and strings +--FILE-- + 2, 2 <=> 2, 3 <=> 2, "\n"; +echo 'b' <=> 'a', 'a' <=> 'a', 'a' <=> 'b', "\n"; +--EXPECT-- +-101 +10-1 diff --git a/test/compliance/cases/language/spaceship_operator_jit.phpt b/test/compliance/cases/language/spaceship_operator_jit.phpt new file mode 100644 index 00000000..90ee8e7f --- /dev/null +++ b/test/compliance/cases/language/spaceship_operator_jit.phpt @@ -0,0 +1,7 @@ +--TEST-- +Spaceship operator (<=>) under JIT for integers +--FILE-- + 2, 2 <=> 2, 3 <=> 2; +--EXPECT-- +-101