Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions lib/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,15 @@ protected function compileTypeConstrainedVariable(Block $block, Type $type): int
protected function compileParam(Op\Expr\Param $param, Block $block, int $paramIdx): OpCode {
assert(false === $param->byRef);
assert(false === $param->variadic);
assert(null === $param->defaultBlock);
$defaultConst = null;
if (null !== $param->defaultVar) {
$defaultConst = $this->compileOperand($param->defaultVar, $block, true);
}
return new OpCode(
OpCode::TYPE_ARG_RECV,
$this->compileOperand($param->result, $block, false),
$paramIdx
$paramIdx,
$defaultConst
);
}

Expand Down Expand Up @@ -655,8 +659,24 @@ protected function compileOperand(Operand $operand, Block $block, bool $isRead):
return null;
} elseif ($operand instanceof Operand\Literal) {
assert($isRead === true);
$return = new Variable(Variable::mapFromType($operand->type));
switch (Variable::mapFromType($operand->type)) {
$mappedType = null !== $operand->type
? Variable::mapFromType($operand->type)
: Variable::TYPE_UNDEFINED;
if ($mappedType === Variable::TYPE_UNDEFINED) {
if (is_int($operand->value)) {
$mappedType = Variable::TYPE_INTEGER;
} elseif (is_float($operand->value)) {
$mappedType = Variable::TYPE_FLOAT;
} elseif (is_string($operand->value)) {
$mappedType = Variable::TYPE_STRING;
} elseif (is_bool($operand->value)) {
$mappedType = Variable::TYPE_BOOLEAN;
} elseif (null === $operand->value) {
$mappedType = Variable::TYPE_NULL;
}
}
$return = new Variable($mappedType);
switch ($mappedType) {
case Variable::TYPE_STRING:
$return->string($operand->value);
break;
Expand All @@ -669,8 +689,10 @@ protected function compileOperand(Operand $operand, Block $block, bool $isRead):
case Variable::TYPE_BOOLEAN:
$return->bool($operand->value);
break;
case Variable::TYPE_NULL:
break;
default:
throw new \LogicException("Unknown Literal Operand Type: " . $operand->type);
throw new \LogicException('Unknown Literal Operand Type: ' . ($operand->type ?? 'untyped'));
}
return $block->registerConstant($operand, $return);
} elseif ($operand instanceof Operand\Temporary) {
Expand Down
3 changes: 3 additions & 0 deletions lib/Frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class Frame {
public ?Variable $returnVar = null;
public ?Handler $handler = null;

/** When true, finishing this frame resumes the caller instead of ending execution. */
public bool $ephemeral = false;

public function __construct(?Handler $handler, ?Block $block, ?Frame $parent, Variable ...$scope) {
$this->handler = $handler;
$this->block = $block;
Expand Down
41 changes: 40 additions & 1 deletion lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ private function compileBlock(Block $block, ?string $funcName = null): PHPLLVM\V
if ($isVarArgs) {
$this->context->functionProxies[$lcname] = new JIT\Call\Vararg($func, $funcName, count($args));
} else {
$this->context->functionProxies[$lcname] = new JIT\Call\Native($func, $funcName, $args);
$defaultArgs = $this->collectParamDefaults($block);
$this->context->functionProxies[$lcname] = new JIT\Call\Native($func, $funcName, $args, $defaultArgs);
}
}

Expand Down Expand Up @@ -727,4 +728,42 @@ private function jitTypeFromLlvmValue(PHPLLVM\Value $value): int
}
}

/**
* @return array<int, Variable>
*/
private function collectParamDefaults(Block $block): array {
$defaults = [];
foreach ($block->opCodes as $op) {
if ($op->type !== OpCode::TYPE_ARG_RECV || null === $op->arg3) {
continue;
}
if (!isset($block->constants[$op->arg3])) {
continue;
}
$defaults[$op->arg2] = $this->jitVariableFromVmConstant($block->constants[$op->arg3]);
}
return $defaults;
}

private function jitVariableFromVmConstant(VM\Variable $vm): Variable {
switch ($vm->type) {
case VM\Variable::TYPE_INTEGER:
return Variable::fromConstantInt($this->context, $vm->toInt());
case VM\Variable::TYPE_STRING:
$lit = new Operand\Literal($vm->toString());
$lit->type = Type::string();
return Variable::fromLiteral($this->context, $lit);
case VM\Variable::TYPE_FLOAT:
$lit = new Operand\Literal($vm->toFloat());
$lit->type = Type::float();
return Variable::fromLiteral($this->context, $lit);
case VM\Variable::TYPE_BOOLEAN:
$lit = new Operand\Literal($vm->toBool());
$lit->type = Type::bool();
return Variable::fromLiteral($this->context, $lit);
default:
throw new \LogicException('Unsupported default parameter type for JIT');
}
}

}
41 changes: 40 additions & 1 deletion lib/JIT.pre
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ class JIT {
if ($isVarArgs) {
$this->context->functionProxies[$lcname] = new JIT\Call\Vararg($func, $funcName, count($args));
} else {
$this->context->functionProxies[$lcname] = new JIT\Call\Native($func, $funcName, $args);
$defaultArgs = $this->collectParamDefaults($block);
$this->context->functionProxies[$lcname] = new JIT\Call\Native($func, $funcName, $args, $defaultArgs);
}
}

Expand Down Expand Up @@ -609,4 +610,42 @@ class JIT {
}
}

/**
* @return array<int, Variable>
*/
private function collectParamDefaults(Block $block): array {
$defaults = [];
foreach ($block->opCodes as $op) {
if ($op->type !== OpCode::TYPE_ARG_RECV || null === $op->arg3) {
continue;
}
if (!isset($block->constants[$op->arg3])) {
continue;
}
$defaults[$op->arg2] = $this->jitVariableFromVmConstant($block->constants[$op->arg3]);
}
return $defaults;
}

private function jitVariableFromVmConstant(VM\Variable $vm): Variable {
switch ($vm->type) {
case VM\Variable::TYPE_INTEGER:
return Variable::fromConstantInt($this->context, $vm->toInt());
case VM\Variable::TYPE_STRING:
$lit = new Operand\Literal($vm->toString());
$lit->type = Type::string();
return Variable::fromLiteral($this->context, $lit);
case VM\Variable::TYPE_FLOAT:
$lit = new Operand\Literal($vm->toFloat());
$lit->type = Type::float();
return Variable::fromLiteral($this->context, $lit);
case VM\Variable::TYPE_BOOLEAN:
$lit = new Operand\Literal($vm->toBool());
$lit->type = Type::bool();
return Variable::fromLiteral($this->context, $lit);
default:
throw new \LogicException('Unsupported default parameter type for JIT');
}
}

}
16 changes: 14 additions & 2 deletions lib/JIT/Call/Native.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,27 @@ class Native implements Call {
public string $name;
public array $argTypes;

public function __construct(Value $function, string $name, array $argTypes) {
/** @var array<int, Variable> compile-time defaults for optional parameters */
public array $defaultArgs = [];

public function __construct(Value $function, string $name, array $argTypes, array $defaultArgs = []) {
$this->function = $function;
$this->name = $name;
$this->argTypes = $argTypes;
$this->defaultArgs = $defaultArgs;
}

public function call(Context $context, Variable ... $args): Value {
$argValues = [];
foreach ($args as $index => $arg) {
$total = count($this->argTypes);
for ($index = 0; $index < $total; $index++) {
if (isset($args[$index])) {
$arg = $args[$index];
} elseif (isset($this->defaultArgs[$index])) {
$arg = $this->defaultArgs[$index];
} else {
throw new \LogicException("Missing required argument {$index} for {$this->name}()");
}
$argValues[] = $this->compileArg($context, $arg, $index);
}
return $context->builder->call(
Expand Down
16 changes: 14 additions & 2 deletions lib/JIT/Call/Native.pre
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,27 @@ class Native implements Call {
public string $name;
public array $argTypes;

public function __construct(Value $function, string $name, array $argTypes) {
/** @var array<int, Variable> compile-time defaults for optional parameters */
public array $defaultArgs = [];

public function __construct(Value $function, string $name, array $argTypes, array $defaultArgs = []) {
$this->function = $function;
$this->name = $name;
$this->argTypes = $argTypes;
$this->defaultArgs = $defaultArgs;
}

public function call(Context $context, Variable ... $args): Value {
$argValues = [];
foreach ($args as $index => $arg) {
$total = count($this->argTypes);
for ($index = 0; $index < $total; $index++) {
if (isset($args[$index])) {
$arg = $args[$index];
} elseif (isset($this->defaultArgs[$index])) {
$arg = $this->defaultArgs[$index];
} else {
throw new \LogicException("Missing required argument {$index} for {$this->name}()");
}
$argValues[] = $this->compileArg($context, $arg, $index);
}
return $context->builder->call(
Expand Down
14 changes: 12 additions & 2 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,15 @@ public function run(Block $block): int {
case OpCode::TYPE_ARG_RECV:
// Todo: do type checks and transformations
$arg1 = $frame->scope[$op->arg1];
$arg1->copyFrom($frame->calledArgs[$op->arg2]);
break;
if (array_key_exists($op->arg2, $frame->calledArgs)) {
$arg1->copyFrom($frame->calledArgs[$op->arg2]);
break;
}
if (null !== $op->arg3 && isset($frame->block->constants[$op->arg3])) {
$arg1->copyFrom($frame->block->constants[$op->arg3]);
break;
}
throw new \LogicException('Missing required argument ' . $op->arg2);
case OpCode::TYPE_DECLARE_CLASS:
$name = $frame->scope[$op->arg1]->toString();
$lcname = strtolower($name);
Expand Down Expand Up @@ -331,6 +338,9 @@ public function run(Block $block): int {
throw new \LogicException("VM OpCode Not Implemented: " . $op->getType());
}
}
if ($frame->ephemeral) {
goto nextframe;
}
return self::SUCCESS;
}

Expand Down
32 changes: 32 additions & 0 deletions patches/php-cfg-mixed-reserved.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--- a/lib/PHPCfg/Op/Type/Mixed.php
+++ b/lib/PHPCfg/Op/Type/Mixed_.php
@@ -13,5 +13,5 @@ namespace PHPCfg\Op\Type;

use PHPCfg\Op\Type;

-class Mixed extends Type
+class Mixed_ extends Type
{
}
--- a/lib/PHPCfg/Parser.php
+++ b/lib/PHPCfg/Parser.php
@@ -173,7 +173,7 @@ class Parser
return new Op\Type\Literal($type);
}

- return new Op\Type\Mixed();
+ return new Op\Type\Mixed_();
}

private function processType(?Node $type): Op\Type
--- a/lib/PHPCfg/Printer.php
+++ b/lib/PHPCfg/Printer.php
@@ -246,7 +246,7 @@ class Printer
if ($type instanceof Op\Type\Literal) {
return $type->name;
}
- if ($type instanceof Op\Type\Mixed) {
+ if ($type instanceof Op\Type\Mixed_) {
return 'mixed';
}
if ($type instanceof Op\Type\Nullable) {
57 changes: 57 additions & 0 deletions patches/php-types-mixed-reserved.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--- a/lib/PHPTypes/Type.php
+++ b/lib/PHPTypes/Type.php
@@ -293,6 +293,9 @@ class Type
if ($decl instanceof CfgType\Literal) {
return self::fromDecl($decl->name);
}
+ if ($decl instanceof CfgType\Mixed_) {
+ return self::mixed();
+ }

throw new \LogicException('Unsupported declaration type: '.get_class($decl));
}
--- a/lib/PHPTypes/TypeReconstructor.php
+++ b/lib/PHPTypes/TypeReconstructor.php
@@ -393,9 +393,14 @@ class TypeReconstructor
protected function resolveOp_Expr_Param(Operand $var, Op\Expr\Param $op, SplObjectStorage $resolved)
{
$type = $this->resolveOpType($op->declaredType);
- if ($op->defaultVar) {
- if ($op->defaultBlock->children[0]->getType() === 'Expr_ConstFetch' && strtolower($op->defaultBlock->children[0]->name->value) === 'null') {
+ if ($op->defaultVar && $op->defaultBlock && isset($op->defaultBlock->children[0])) {
+ $defaultOp = $op->defaultBlock->children[0];
+ if (
+ method_exists($defaultOp, 'getType')
+ && $defaultOp->getType() === 'Expr_ConstFetch'
+ && strtolower($defaultOp->name->value) === 'null'
+ ) {
$type = (new Type(Type::TYPE_UNION, [$type, Type::null()]))->simplify();
}
}
@@ -541,7 +546,7 @@ class TypeReconstructor
foreach ($this->state->classes as $class) {
foreach ($class->stmts->children as $stmt) {
if ($stmt instanceof Op\Stmt\Property) {
- if ($stmt->declaredType instanceof Op\Type\Mixed) {
+ if ($stmt->declaredType instanceof Op\Type\Mixed_) {
$stmt->type = Type::extractTypeFromComment('var', $stmt->getAttribute('doccomment'));
} else {
$stmt->type = $this->resolveOpType($stmt->declaredType);
@@ -677,7 +682,7 @@ class TypeReconstructor
foreach ($class->stmts->children as $property) {
if ($property instanceof Op\Stmt\Property) {
$property->type = $this->resolveOpType($property->declaredType);
- if ($property->declaredType instanceof Op\Type\Mixed && $property->type) {
+ if ($property->declaredType instanceof Op\Type\Mixed_ && $property->type) {
$property->declaredType = new Op\Type\Literal($property->type->toDecl());
}
}
@@ -694,7 +699,7 @@ class TypeReconstructor

private function resolveOpType(Op\Type $type): Type
{
- if ($type instanceof Op\Type\Mixed) {
+ if ($type instanceof Op\Type\Mixed_) {
return Type::mixed();
}
if ($type instanceof Op\Type\Nullable) {
2 changes: 2 additions & 0 deletions script/apply-patches.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ if [[ -d "$ROOT/vendor/ircmaxell/php-types" ]]; then
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"
apply_patch "$PATCH_DIR/php-types-mixed-reserved.patch"
fi

if [[ -d "$ROOT/vendor/ircmaxell/php-cfg" ]]; then
apply_patch "$PATCH_DIR/php-cfg-dollars-brace.patch"
apply_patch "$PATCH_DIR/php-cfg-mixed-reserved.patch"
fi

if [[ -d "$ROOT/vendor/pre/plugin" ]]; then
Expand Down
14 changes: 14 additions & 0 deletions test/compliance/cases/language/default_params.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Default parameter values and optional arguments
--FILE--
<?php
function f($a = 1, $b = 2) {
return $a + $b;
}
echo f(), "\n";
echo f(10), "\n";
echo f(10, 20), "\n";
--EXPECT--
3
12
30
14 changes: 14 additions & 0 deletions test/compliance/cases/language/default_params_jit.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Default parameter values and optional arguments (JIT)
--FILE--
<?php
function f($a = 1, $b = 2) {
return $a + $b;
}
echo f(), "\n";
echo f(10), "\n";
echo f(10, 20), "\n";
--EXPECT--
3
12
30
Loading