Skip to content
Closed
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
4 changes: 2 additions & 2 deletions ext/filter/JitFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -1259,9 +1259,9 @@ private static function stringToInt64(Context $context, Value $strPtr): Value
$nullEnd = $context->getTypeFromString('int8*')->constNull();
$context->builder->store($nullEnd, $endPtrSlot);
$i32 = $context->getTypeFromString('int32');
$parsed = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$parsed = $context->builder->call(
$context->lookupFunction('strtol'),
$charPtr,
$endPtrSlot,
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/JitChr.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,9 @@ private static function stringPtrToLong(Context $context, Value $strPtr): Value
$charPtr = $context->builder->structGep($strPtr, $map['value']);
$endPtrSlot = $context->builder->alloca($context->getTypeFromString('int8*'), 1, 'chr_strtol_end');
$context->builder->store($context->getTypeFromString('int8*')->constNull(), $endPtrSlot);
$raw = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$raw = $context->builder->call(
$context->lookupFunction('strtol'),
$charPtr,
$endPtrSlot,
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/JitImageTypeArg.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@ private static function stringPtrToLong(Context $context, Value $strPtr): Value
$charPtr = $context->builder->structGep($strPtr, $map['value']);
$endPtrSlot = $context->builder->alloca($context->getTypeFromString('int8*'), 1, 'imgext_strtol_end');
$context->builder->store($context->getTypeFromString('int8*')->constNull(), $endPtrSlot);
$raw = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$raw = $context->builder->call(
$context->lookupFunction('strtol'),
$charPtr,
$endPtrSlot,
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/JitIntdiv.php
Original file line number Diff line number Diff line change
Expand Up @@ -689,9 +689,9 @@ private static function stringPtrToLong(Context $context, Value $strPtr): Value
$charPtr = $context->builder->structGep($strPtr, $map['value']);
$endPtrSlot = $context->builder->alloca($context->getTypeFromString('int8*'), 1, 'intdiv_strtol_end');
$context->builder->store($context->getTypeFromString('int8*')->constNull(), $endPtrSlot);
$raw = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$raw = $context->builder->call(
$context->lookupFunction('strtol'),
$charPtr,
$endPtrSlot,
Expand Down
8 changes: 4 additions & 4 deletions ext/standard/JitSessionStorageKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ private static function emitSessionWireLoadFromPath(Context $context, Value $pat
$context->builder->positionAtEnd($bbSNum);
$numStart = $context->builder->inBoundsGEP($afterPipe, $i64->constInt(2, false));
$endPtr = $context->builder->alloca($i8p);
$valLen = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$valLen = $context->builder->call(
$context->lookupFunction('strtol'),
$numStart,
$endPtr,
Expand Down Expand Up @@ -431,9 +431,9 @@ private static function emitSessionWireLoadFromPath(Context $context, Value $pat
$context->builder->positionAtEnd($bbINum);
$iStart = $context->builder->inBoundsGEP($afterPipe, $i64->constInt(2, false));
$iEndPtr = $context->builder->alloca($i8p);
$iVal = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$iVal = $context->builder->call(
$context->lookupFunction('strtol'),
$iStart,
$iEndPtr,
Expand Down
4 changes: 2 additions & 2 deletions ext/standard/JitSleep.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,9 @@ private static function stringPtrToLong(Context $context, Value $strPtr): Value
$charPtr = $context->builder->structGep($strPtr, $map['value']);
$endPtrSlot = $context->builder->alloca($context->getTypeFromString('int8*'), 1, 'sleep_strtol_end');
$context->builder->store($context->getTypeFromString('int8*')->constNull(), $endPtrSlot);
$raw = // strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
// strtol(3) via LibcExtern::ensureStrtolDecl after always-on drop (#31988).
LibcExtern::ensureStrtolDecl($context);
$context->builder->call(
$raw = $context->builder->call(
$context->lookupFunction('strtol'),
$charPtr,
$endPtrSlot,
Expand Down
157 changes: 131 additions & 26 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -7985,6 +7985,15 @@ private function compileBlockInternal(
if ($this->context->coalesceAssignTargets->contains($operand)) {
continue;
}
$slot = $block->slotForOperand($operand);
// Folded enum cases must wait until DECLARE_ENUM (classes are hoisted first, #31967).
if (
null !== $slot
&& isset($block->constants[$slot])
&& VM\Variable::TYPE_ENUM_CASE === $block->constants[$slot]->type
) {
continue;
}
$this->context->makeVariableFromOp($func, $basicBlock, $block, $operand);
}
$blockKey = spl_object_id($block);
Expand Down Expand Up @@ -12322,7 +12331,15 @@ private function compileBlockInternal(
$this->context->scope->classId,
$nameOp->value
);
$savedEnumInsert = JIT\BasicBlockHelper::tryGetInsertBlock($this->context);
$this->context->type->object->finishEnumClass($this->context->scope->classId);
if (null !== $savedEnumInsert) {
JIT\BasicBlockHelper::restoreInsertBlock($this->context, $savedEnumInsert);
}
$this->context->type->object->flushPendingEnumCaseClassConsts(strtolower($nameOp->value));
if (null !== $savedEnumInsert) {
JIT\BasicBlockHelper::restoreInsertBlock($this->context, $savedEnumInsert);
}
$this->context->popScope();
break;
case OpCode::TYPE_DECLARE_CLASS:
Expand Down Expand Up @@ -12395,6 +12412,14 @@ private function compileBlockInternal(
if (AttributeClassRegistry::isRegisteredAttributeClass($op->attributeEntries)) {
$this->context->type->object->markAttributeClass($nameOp->value);
}
// Register implements before compileClass so `const Y = self::X` can
// resolve interface constants (zend_constants.c / #31967).
if ([] !== $op->classImplements) {
$this->context->type->object->setClassInterfaces(
$nameOp->value,
$op->classImplements
);
}
$this->compileClass($op->block1, $this->context->scope->classId);
if ($parentOp instanceof Operand\Literal) {
$this->context->type->object->inheritReadonlyFromParent(
Expand All @@ -12410,12 +12435,6 @@ private function compileBlockInternal(
strtolower(ltrim($parentOp->value, '\\'))
);
}
if ([] !== $op->classImplements) {
$this->context->type->object->setClassInterfaces(
$nameOp->value,
$op->classImplements
);
}
$this->context->type->object->inheritInterfaceConstants(
$this->context->scope->classId,
$nameOp->value
Expand Down Expand Up @@ -21458,7 +21477,7 @@ private function initJitStaticCall(Block $block, int $classOpIdx, int $nameOpIdx

return;
}
// Runtime variable classname: resolve via emitResolveClassId guards (#30059).
// Runtime `$obj::method()` / `$className::method()` — dispatch by class_id (#31967).
$classVar = $this->context->getVariableFromOp($classOp);
if (
JIT\Variable::TYPE_OBJECT !== $classVar->type
Expand All @@ -21473,7 +21492,27 @@ private function initJitStaticCall(Block $block, int $classOpIdx, int $nameOpIdx

return;
}
throw new \LogicException('Static call class must be a literal');
$methodLc = strtolower($nameOp->value);
$candidates = $this->buildRuntimeStaticMethodCandidatesByClassId($methodLc);
if ([] === $candidates) {
throw new \LogicException('Static call class must be a literal');
}
$classIdVal = JIT\ClassConstFetchHelper::emitResolveClassId(
$this->context->type->object,
$block,
$classVar,
$classOp
);
$this->context->scope->toCall = new JIT\Call\RuntimeIndirectStaticMethodCall(
$methodLc,
$candidates,
$block,
false,
$classIdVal
);
$this->context->scope->args = [];

return;
}
$selfScope = 'self' === strtolower((string) $classOp->value);
$staticScope = 'static' === strtolower((string) $classOp->value);
Expand Down Expand Up @@ -22913,11 +22952,23 @@ private function jitClassConstDefineValue(
!isset($block->constants[$op->arg2])
|| $block->constants[$op->arg2]->is(VM\Variable::TYPE_NULL)
) {
$vm = new VM($this->context->runtime->vmContext);
$className = $this->context->type->object->classNameForId($classId);
$rootBlock = $this->context->jitFunctionRootBlock ?? $this->context->jitEnclosingBlock;
VM\ClassConstMaterializer::seedReferencedClasses($vm, $rootBlock, $block, $op->arg2);
$value = VM\ClassConstMaterializer::materializeSlot($vm, $block, $op->arg2, $className);
// Resolve from JIT class/enum maps first. AOT DECLARE_* does not seed VM
// ClassEntry tables the materializer walks for `self::X` / `E::Case` (#31967).
if (null !== $this->tryResolveEnumCaseClassConstInit($block, $op->arg2)) {
$value = new VM\Variable();
$value->null();
} else {
$inherited = $this->tryResolveInheritedClassConstInit($block, $op->arg2, $classId);
if (null !== $inherited) {
$value = $inherited;
} else {
$vm = new VM($this->context->runtime->vmContext);
$className = $this->context->type->object->classNameForId($classId);
$rootBlock = $this->context->jitFunctionRootBlock ?? $this->context->jitEnclosingBlock;
VM\ClassConstMaterializer::seedReferencedClasses($vm, $rootBlock, $block, $op->arg2);
$value = VM\ClassConstMaterializer::materializeSlot($vm, $block, $op->arg2, $className);
}
}
} else {
$value = $block->constants[$op->arg2];
}
Expand Down Expand Up @@ -23796,19 +23847,11 @@ private function ensureJitGlobal(string $name): Variable
/**
* Resolve `public const X = SomeEnum::Case` when VM materialization lacks the enum (#4445).
*
* @return array{0: int, 1: string}|null enum class id + case key (lowercase)
* @return array{0: int, 1: string}|null enum class id + case-sensitive key (#25910)
*/
private function tryResolveEnumCaseClassConstInit(Block $block, int $valueSlot): ?array
{
$fetchOp = null;
foreach ($block->opCodes as $initOp) {
if (OpCode::TYPE_DECLARE_CLASS_CONST === $initOp->type && $valueSlot === $initOp->arg2) {
break;
}
if (OpCode::TYPE_CLASS_CONST_FETCH === $initOp->type) {
$fetchOp = $initOp;
}
}
$fetchOp = $this->classConstFetchOpForInitSlot($block, $valueSlot);
if (null === $fetchOp) {
return null;
}
Expand All @@ -23817,12 +23860,74 @@ private function tryResolveEnumCaseClassConstInit(Block $block, int $valueSlot):
if (!$enumClassOp instanceof Operand\Literal || !$caseOp instanceof Operand\Literal) {
return null;
}
$enumClassId = $this->context->type->object->lookup(strtolower($enumClassOp->value));
if (!$this->context->type->object->isEnumClassId($enumClassId)) {
$enumLc = strtolower(ltrim((string) $enumClassOp->value, '\\'));
if (!$this->context->type->object->isEnumClassLc($enumLc)) {
return null;
}
$enumClassId = $this->context->type->object->lookup($enumLc);

return [$enumClassId, \PHPCompiler\ClassConstName::key((string) $caseOp->value)];
}

/**
* `const Y = self::X` when X is inherited from an interface already compiled in JIT (#31967).
*
* php-src: Zend/zend_constants.c — zend_get_class_constant_ex walks interfaces.
*/
private function tryResolveInheritedClassConstInit(Block $block, int $valueSlot, int $classId): ?VM\Variable
{
$fetchOp = $this->classConstFetchOpForInitSlot($block, $valueSlot);
if (null === $fetchOp) {
return null;
}
$classOp = $block->getOperand($fetchOp->arg2);
$constOp = $block->getOperand($fetchOp->arg3);
if (!$classOp instanceof Operand\Literal || !$constOp instanceof Operand\Literal) {
return null;
}
$object = $this->context->type->object;
$className = (string) $classOp->value;
$lc = strtolower(ltrim($className, '\\'));
$fromId = $classId;
if ('parent' === $lc) {
$parentLc = $object->parentClassLc($object->classNameForId($classId));
if (null === $parentLc || !$object->hasDeclaredClass($parentLc)) {
return null;
}
$fromId = $object->lookup($parentLc);
} elseif ('self' !== $lc && 'static' !== $lc) {
if (!$object->hasDeclaredClass($lc)) {
return null;
}
$fromId = $object->lookup($lc);
}
$constKey = \PHPCompiler\ClassConstName::key((string) $constOp->value);
$holdingId = $object->resolveClassConstHoldingId($fromId, $constKey);
if (null === $holdingId) {
return null;
}

return $object->vmScalarFromClassConst($holdingId, $constKey);
}

private function classConstFetchOpForInitSlot(Block $block, int $valueSlot): ?OpCode
{
$matched = null;
$lastFetch = null;
foreach ($block->opCodes as $initOp) {
if (OpCode::TYPE_DECLARE_CLASS_CONST === $initOp->type && $valueSlot === $initOp->arg2) {
break;
}
if (OpCode::TYPE_CLASS_CONST_FETCH !== $initOp->type) {
continue;
}
$lastFetch = $initOp;
if ($valueSlot === $initOp->arg1) {
$matched = $initOp;
}
}

return [$enumClassId, strtolower($caseOp->value)];
return $matched ?? $lastFetch;
}

private function ensureJitFunctionStatic(string $storageKey): Variable
Expand Down
31 changes: 20 additions & 11 deletions lib/JIT/BackedEnumFromJit.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,31 @@ public static function emitFromFunction(
);

$restore = $context->builder->getInsertBlock();
$savedLowering = $context->loweringLlvmFunction;
$entry = $fn->appendBasicBlock('entry');
$context->builder->positionAtEnd($entry);
$arg = $fn->getParam(0);

// String from() uses parentFunction() for normalize blocks; if loweringLlvmFunction
// is parked on __init__/main, those blocks land in the wrong function (#31967).
if ('string' === $backedType) {
self::emitStringBackedBody($context, $object, $classId, $className, $caseKeys, $arg, $isTry);
} elseif ('int' === $backedType) {
self::emitIntBackedBody($context, $object, $classId, $className, $caseKeys, $arg, $isTry);
} else {
throw new \LogicException('Unsupported enum backing type for JIT from(): '.$backedType);
$context->loweringLlvmFunction = $fn;
}
$arg = $fn->getParam(0);

if (null !== $restore) {
$context->builder->positionAtEnd($restore);
} else {
$context->builder->clearInsertionPosition();
try {
if ('string' === $backedType) {
self::emitStringBackedBody($context, $object, $classId, $className, $caseKeys, $arg, $isTry);
} elseif ('int' === $backedType) {
self::emitIntBackedBody($context, $object, $classId, $className, $caseKeys, $arg, $isTry);
} else {
throw new \LogicException('Unsupported enum backing type for JIT from(): '.$backedType);
}
} finally {
$context->loweringLlvmFunction = $savedLowering;
if (null !== $restore) {
$context->builder->positionAtEnd($restore);
} else {
$context->builder->clearInsertionPosition();
}
}
}

Expand Down
25 changes: 21 additions & 4 deletions lib/JIT/Builtin/Type/ObjectStaticPropertyLlvm.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPCompiler\JIT\Builtin\ErrorRaise;
use PHPCompiler\JIT\Builtin\TypeErrorRaise;
use PHPCompiler\JIT\Context;
use PHPCompiler\JIT\HashTableWriteLlvm;
use PHPCompiler\JIT\JitNestedHelperCoerce;
use PHPCompiler\JIT\JitStringArg;
use PHPCompiler\JIT\JitStringCompare;
Expand Down Expand Up @@ -522,6 +523,25 @@ private static function storeValueBox(Object_ $object, Value $global, Variable $
return;
}

if (0 !== ($value->type & Variable::IS_NATIVE_ARRAY)) {
HashTableWriteLlvm::promoteNativeArrayVariableToHashtable($context, $value);
}

if (Variable::TYPE_NULL === $value->type && null !== $value->value) {
$llvmTy = $context->getStringFromType($value->value->typeOf());
if (str_contains($llvmTy, '__hashtable__')) {
$value->type = Variable::TYPE_HASHTABLE;
} elseif (str_contains($llvmTy, '__value__')) {
$ptr = Variable::KIND_VARIABLE === $value->kind
? JitValueBox::pointer($context, $value->value)
: $value->value;
$context->builder->store($ptr, $global);
$value->addref();

return;
}
}

$heapVal = $context->memory->malloc($valueType);
$heapPtr = $context->builder->pointerCast($heapVal, $valuePtrTy);
$valueMap = $context->structFieldMap['__value__'];
Expand Down Expand Up @@ -581,10 +601,7 @@ private static function storeValueBox(Object_ $object, Value $global, Variable $
$context->helper->loadValue($value)
);
} else {
throw new \LogicException(
'JIT static property boxed store does not support value type '
.Variable::getStringType($value->type)
);
JitValueBox::assignToPointer($context, $heapPtr, $value);
}

$context->builder->store($heapPtr, $global);
Expand Down
Loading