diff --git a/lib/JIT/Builtin/Refcount.php b/lib/JIT/Builtin/Refcount.php index 72dcb90a543..01db9bb5d20 100644 --- a/lib/JIT/Builtin/Refcount.php +++ b/lib/JIT/Builtin/Refcount.php @@ -182,175 +182,75 @@ public function implement(): void { } private function implementInit(): void { - $fn___c4ca4238a0b923820dcc509a6f75849b = $this->context->lookupFunction('__ref__init'); - $block___c4ca4238a0b923820dcc509a6f75849b = $fn___c4ca4238a0b923820dcc509a6f75849b->appendBasicBlock('main'); - $this->context->builder->positionAtEnd($block___c4ca4238a0b923820dcc509a6f75849b); - $typeinfo = $fn___c4ca4238a0b923820dcc509a6f75849b->getParam(0); - $refVirtual = $fn___c4ca4238a0b923820dcc509a6f75849b->getParam(1); - - $offset = $this->context->structFieldIndex($refVirtual, 'ref'); - $ref = $this->context->builder->load( - $this->context->builder->structGep($refVirtual, $offset) - ); - $structType = $ref->typeOf(); - $offset = $this->context->structFieldMap[$structType->getName()]['refcount']; - - $this->context->builder->insertValue( - $ref, - $structType->getElementAtIndex($offset)->constInt(0, false), - $offset - ); - $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['typeinfo']; - $this->context->builder->insertValue( - $ref, - $typeinfo, - $offset - ); - $this->context->builder->returnVoid(); - - $this->context->builder->clearInsertionPosition(); + // insertValue returns a new aggregate — must store it (#24226: prior codegen + // discarded the SSA value, so refcount/typeinfo never hit memory). + $fn = $this->context->lookupFunction('__ref__init'); + $block = $fn->appendBasicBlock('main'); + $this->context->builder->positionAtEnd($block); + $typeinfo = $fn->getParam(0); + $refVirtual = $fn->getParam(1); + $refField = $this->context->structFieldIndex($refVirtual, 'ref'); + $refPtr = $this->context->builder->structGep($refVirtual, $refField); + $ref = $this->context->builder->load($refPtr); + $structType = $ref->typeOf(); + $rcOff = $this->context->structFieldMap[$structType->getName()]['refcount']; + $tiOff = $this->context->structFieldMap[$structType->getName()]['typeinfo']; + $ref = $this->context->builder->insertValue( + $ref, + $structType->getElementAtIndex($rcOff)->constInt(0, false), + $rcOff + ); + $ref = $this->context->builder->insertValue($ref, $typeinfo, $tiOff); + $this->context->builder->store($ref, $refPtr); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); } private function implementAddref(): void { - $fn___eccbc87e4b5ce2fe28308fd9f2a7baf3 = $this->context->lookupFunction('__ref__addref'); - $block___eccbc87e4b5ce2fe28308fd9f2a7baf3 = $fn___eccbc87e4b5ce2fe28308fd9f2a7baf3->appendBasicBlock('main'); - $this->context->builder->positionAtEnd($block___eccbc87e4b5ce2fe28308fd9f2a7baf3); - $refVirtual = $fn___eccbc87e4b5ce2fe28308fd9f2a7baf3->getParam(0); - - $isNull = $this->context->builder->icmp(\PHPLLVM\Builder::INT_EQ, $refVirtual, $refVirtual->typeOf()->constNull()); - $bool = $this->context->castToBool($isNull); - $prev = $this->context->builder->getInsertBlock(); - $ifBlock = $prev->insertBasicBlock('ifBlock'); - $prev->moveBefore($ifBlock); - - $endBlock[] = $tmp = $ifBlock->insertBasicBlock('endBlock'); - $this->context->builder->branchIf($bool, $ifBlock, $tmp); - - $this->context->builder->positionAtEnd($ifBlock); - { $this->context->builder->returnVoid(); - } - if ($this->context->builder->getInsertBlock()->getTerminator() === null) { - $this->context->builder->branch(end($endBlock)); - } - - $this->context->builder->positionAtEnd(array_pop($endBlock)); - $offset = $this->context->structFieldIndex($refVirtual, 'ref'); - $ref = $this->context->builder->load( - $this->context->builder->structGep($refVirtual, $offset) - ); - $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['typeinfo']; - $typeinfo = $this->context->builder->extractValue($ref, $offset); - $__type = $this->context->getTypeFromString('int32'); - - - $__kind = $__type->getKind(); - $__value = self::TYPE_INFO_REFCOUNTED; - switch ($__kind) { - case \PHPLLVM\Type::KIND_INTEGER: - if (!is_object($__value)) { - $refMask = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - if ($__other_type->getWidth() >= $__type->getWidth()) { - $refMask = $this->context->builder->truncOrBitCast($__value, $__type); - } else { - $refMask = $this->context->builder->zExtOrBitCast($__value, $__type); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - - $refMask = $this->context->builder->fpToSi($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - $refMask = $this->context->builder->ptrToInt($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (int, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - if (!is_object($__value)) { - $refMask = $__type->constReal(self::TYPE_INFO_REFCOUNTED); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - - $refMask = $this->context->builder->siToFp($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_DOUBLE: - $refMask = $this->context->builder->fpCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - if (!is_object($__value)) { - // this is very likely very wrong... - $refMask = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - $refMask = $this->context->builder->intToPtr($__value, $__type); - break; - case \PHPLLVM\Type::KIND_ARRAY: - // $__tmp = $this->context->builder->($__value, $this->context->context->int64Type()); - // $(result) = $this->context->builder->intToPtr($__tmp, $__type); - // break; - case \PHPLLVM\Type::KIND_POINTER: - $refMask = $this->context->builder->pointerCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - default: - throw new \LogicException("Unsupported type cast: " . $__type->toString()); - } - $__right = $this->context->builder->intCast($refMask, $typeinfo->typeOf()); - - - - - $isCounted = $this->context->builder->bitwiseAnd($typeinfo, $__right); - $bool = $this->context->castToBool($isCounted); - $prev = $this->context->builder->getInsertBlock(); - $ifBlock = $prev->insertBasicBlock('ifBlock'); - $prev->moveBefore($ifBlock); - - $endBlock[] = $tmp = $ifBlock->insertBasicBlock('endBlock'); - $this->context->builder->branchIf($bool, $ifBlock, $tmp); - - $this->context->builder->positionAtEnd($ifBlock); - { $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['refcount']; - $current = $this->context->builder->extractValue($ref, $offset); - $current = $this->context->builder->add($current, $current->typeOf()->constInt(1, false)); - $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['refcount']; - $this->context->builder->insertValue( - $ref, - $current, - $offset - ); - } - if ($this->context->builder->getInsertBlock()->getTerminator() === null) { - $this->context->builder->branch(end($endBlock)); - } - - $this->context->builder->positionAtEnd(array_pop($endBlock)); - $this->context->builder->returnVoid(); - - $this->context->builder->clearInsertionPosition(); + // insertValue is pure SSA — store the updated __ref__ or addref is a no-op (#24226). + $fn = $this->context->lookupFunction('__ref__addref'); + $entry = $fn->appendBasicBlock('main'); + $this->context->builder->positionAtEnd($entry); + $refVirtual = $fn->getParam(0); + $i32 = $this->context->getTypeFromString('int32'); + + $isNull = $this->context->builder->icmp( + \PHPLLVM\Builder::INT_EQ, + $refVirtual, + $refVirtual->typeOf()->constNull() + ); + $nullBlock = $fn->appendBasicBlock('addref_null'); + $body = $fn->appendBasicBlock('addref_body'); + $this->context->builder->branchIf($isNull, $nullBlock, $body); + $this->context->builder->positionAtEnd($nullBlock); + $this->context->builder->returnVoid(); + + $this->context->builder->positionAtEnd($body); + $refField = $this->context->structFieldIndex($refVirtual, 'ref'); + $refPtr = $this->context->builder->structGep($refVirtual, $refField); + $ref = $this->context->builder->load($refPtr); + $structName = $ref->typeOf()->getName(); + $tiOff = $this->context->structFieldMap[$structName]['typeinfo']; + $rcOff = $this->context->structFieldMap[$structName]['refcount']; + $typeinfo = $this->context->builder->extractValue($ref, $tiOff); + $refMask = $i32->constInt(self::TYPE_INFO_REFCOUNTED, false); + $isCounted = $this->context->builder->bitwiseAnd($typeinfo, $refMask); + $incBlock = $fn->appendBasicBlock('addref_inc'); + $done = $fn->appendBasicBlock('addref_done'); + $this->context->builder->branchIf( + $this->context->castToBool($isCounted), + $incBlock, + $done + ); + $this->context->builder->positionAtEnd($incBlock); + $current = $this->context->builder->extractValue($ref, $rcOff); + $next = $this->context->builder->add($current, $current->typeOf()->constInt(1, false)); + $ref = $this->context->builder->insertValue($ref, $next, $rcOff); + $this->context->builder->store($ref, $refPtr); + $this->context->builder->branch($done); + $this->context->builder->positionAtEnd($done); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); } private function implementDelref(): void { @@ -377,9 +277,8 @@ private function implementDelref(): void { $this->context->builder->positionAtEnd(array_pop($endBlock)); $offset = $this->context->structFieldIndex($refVirtual, 'ref'); - $ref = $this->context->builder->load( - $this->context->builder->structGep($refVirtual, $offset) - ); + $refPtr = $this->context->builder->structGep($refVirtual, $offset); + $ref = $this->context->builder->load($refPtr); $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['typeinfo']; $typeinfo = $this->context->builder->extractValue($ref, $offset); $__type = $this->context->getTypeFromString('int32'); @@ -479,11 +378,13 @@ private function implementDelref(): void { $current = $this->context->builder->extractValue($ref, $offset); $current = $this->context->builder->sub($current, $current->typeOf()->constInt(1, false)); $offset = $this->context->structFieldMap[$ref->typeOf()->getName()]['refcount']; - $this->context->builder->insertValue( + // Persist decremented count before free-or-keep (#24226). + $ref = $this->context->builder->insertValue( $ref, $current, $offset ); + $this->context->builder->store($ref, $refPtr); $__right = $current->typeOf()->constInt(0, false); diff --git a/lib/JIT/Builtin/Refcount.pre b/lib/JIT/Builtin/Refcount.pre index c9714210877..45a0dc91c94 100755 --- a/lib/JIT/Builtin/Refcount.pre +++ b/lib/JIT/Builtin/Refcount.pre @@ -66,6 +66,9 @@ class Refcount extends Builtin { } private function implementInit(): void { + // NOTE: compile{} expands `$ref.refcount = …` to insertValue without store. + // The generated Refcount.php hand-stores the aggregate (#24226). Re-run the + // .pre expander carefully or keep the .php store fix when regenerating. compile { function __ref__init($typeinfo, $refVirtual) { $ref = $refVirtual->ref; diff --git a/lib/JIT/Builtin/Type/HashTable.php b/lib/JIT/Builtin/Type/HashTable.php index 47336763ec5..78a4326aa58 100644 --- a/lib/JIT/Builtin/Type/HashTable.php +++ b/lib/JIT/Builtin/Type/HashTable.php @@ -1907,6 +1907,14 @@ private function implementValueWriteHashtable(): void $ptrField = $this->context->builder->structGep($value, $map['value']); $htSlot = $this->context->builder->pointerCast($ptrField, $htPtr->pointerType(0)); $this->context->builder->store($hashtable, $htSlot); + // Match writeObject: retain the HT for the value-box owner (#24226 e08_spread). + $this->context->builder->call( + $this->context->lookupFunction('__ref__addref'), + $this->context->builder->pointerCast( + $hashtable, + $this->context->getTypeFromString('__ref__virtual*') + ) + ); $this->context->builder->returnVoid(); $this->context->builder->clearInsertionPosition(); } diff --git a/lib/JIT/Builtin/Type/Value.php b/lib/JIT/Builtin/Type/Value.php index 3c4b13ce92f..466bb336ed3 100644 --- a/lib/JIT/Builtin/Type/Value.php +++ b/lib/JIT/Builtin/Type/Value.php @@ -258,210 +258,72 @@ public function initialize(): void { } protected function implementValueDelref(): void { - $fn___c4ca4238a0b923820dcc509a6f75849b = $this->context->lookupFunction('__value__valueDelref'); - $block___c4ca4238a0b923820dcc509a6f75849b = $fn___c4ca4238a0b923820dcc509a6f75849b->appendBasicBlock('main'); - $this->context->builder->positionAtEnd($block___c4ca4238a0b923820dcc509a6f75849b); - $value = $fn___c4ca4238a0b923820dcc509a6f75849b->getParam(0); - - $offset = $this->context->structFieldIndex($value, 'type'); - $oldType = $this->context->builder->load( - $this->context->builder->structGep($value, $offset) - ); - $__type = $this->context->getTypeFromString('int8'); - $zeroType = $__type->constInt(0, false); - $isNullType = $this->context->builder->icmp(\PHPLLVM\Builder::INT_EQ, $oldType, $zeroType); - $nullDelrefBlock = $fn___c4ca4238a0b923820dcc509a6f75849b->appendBasicBlock('value_delref_null'); - $contDelrefBlock = $fn___c4ca4238a0b923820dcc509a6f75849b->appendBasicBlock('value_delref_cont'); - $this->context->builder->branchIf($isNullType, $nullDelrefBlock, $contDelrefBlock); - $this->context->builder->positionAtEnd($nullDelrefBlock); - $this->context->builder->returnVoid(); - $this->context->builder->positionAtEnd($contDelrefBlock); - - - $__kind = $__type->getKind(); - $__value = Variable::IS_REFCOUNTED; - switch ($__kind) { - case \PHPLLVM\Type::KIND_INTEGER: - if (!is_object($__value)) { - $mask = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - if ($__other_type->getWidth() >= $__type->getWidth()) { - $mask = $this->context->builder->truncOrBitCast($__value, $__type); - } else { - $mask = $this->context->builder->zExtOrBitCast($__value, $__type); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - - $mask = $this->context->builder->fpToSi($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - $mask = $this->context->builder->ptrToInt($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (int, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - if (!is_object($__value)) { - $mask = $__type->constReal(Variable::IS_REFCOUNTED); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - - $mask = $this->context->builder->siToFp($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_DOUBLE: - $mask = $this->context->builder->fpCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - if (!is_object($__value)) { - // this is very likely very wrong... - $mask = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - $mask = $this->context->builder->intToPtr($__value, $__type); - break; - case \PHPLLVM\Type::KIND_ARRAY: - // $__tmp = $this->context->builder->($__value, $this->context->context->int64Type()); - // $(result) = $this->context->builder->intToPtr($__tmp, $__type); - // break; - case \PHPLLVM\Type::KIND_POINTER: - $mask = $this->context->builder->pointerCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - default: - throw new \LogicException("Unsupported type cast: " . $__type->toString()); - } - $__right = $this->context->builder->intCast($mask, $oldType->typeOf()); - - - + // Only string/object/hashtable payloads store a heap pointer in value[]. + // TYPE_VALUE and scalars must not load value[] as __ref__virtual* (#24226). + $fn = $this->context->lookupFunction('__value__valueDelref'); + $entry = $fn->appendBasicBlock('main'); + $this->context->builder->positionAtEnd($entry); + $value = $fn->getParam(0); + $i8 = $this->context->getTypeFromString('int8'); + $typeGep = $this->context->builder->structGep( + $value, + $this->context->structFieldIndex($value, 'type') + ); + $oldType = $this->context->builder->load($typeGep); + $zeroType = $i8->constInt(0, false); + $nullBlock = $fn->appendBasicBlock('value_delref_null'); + $contBlock = $fn->appendBasicBlock('value_delref_cont'); + $doneBlock = $fn->appendBasicBlock('value_delref_done'); + $isNullType = $this->context->builder->icmp(\PHPLLVM\Builder::INT_EQ, $oldType, $zeroType); + $this->context->builder->branchIf($isNullType, $nullBlock, $contBlock); - $isCounted = $this->context->builder->bitwiseAnd($oldType, $__right); - $bool = $this->context->castToBool($isCounted); - $prev = $this->context->builder->getInsertBlock(); - $ifBlock = $prev->insertBasicBlock('ifBlock'); - $prev->moveBefore($ifBlock); - - $endBlock[] = $tmp = $ifBlock->insertBasicBlock('endBlock'); - $this->context->builder->branchIf($bool, $ifBlock, $tmp); - - $this->context->builder->positionAtEnd($ifBlock); - { $offset = $this->context->structFieldIndex($value, 'value'); - $ptr = $this->context->builder->structGep($value, $offset); - $__type = $this->context->getTypeFromString('__ref__virtual*'); - - - $__kind = $__type->getKind(); - $__value = $ptr; - switch ($__kind) { - case \PHPLLVM\Type::KIND_INTEGER: - if (!is_object($__value)) { - $virtual = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - if ($__other_type->getWidth() >= $__type->getWidth()) { - $virtual = $this->context->builder->truncOrBitCast($__value, $__type); - } else { - $virtual = $this->context->builder->zExtOrBitCast($__value, $__type); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - - $virtual = $this->context->builder->fpToSi($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - $virtual = $this->context->builder->ptrToInt($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (int, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_DOUBLE: - if (!is_object($__value)) { - $virtual = $__type->constReal($ptr); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - - $virtual = $this->context->builder->siToFp($__value, $__type); - - break; - case \PHPLLVM\Type::KIND_DOUBLE: - $virtual = $this->context->builder->fpCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - case \PHPLLVM\Type::KIND_ARRAY: - case \PHPLLVM\Type::KIND_POINTER: - if (!is_object($__value)) { - // this is very likely very wrong... - $virtual = $__type->constInt($__value, false); - break; - } - $__other_type = $__value->typeOf(); - switch ($__other_type->getKind()) { - case \PHPLLVM\Type::KIND_INTEGER: - $virtual = $this->context->builder->intToPtr($__value, $__type); - break; - case \PHPLLVM\Type::KIND_ARRAY: - // $__tmp = $this->context->builder->($__value, $this->context->context->int64Type()); - // $(result) = $this->context->builder->intToPtr($__tmp, $__type); - // break; - case \PHPLLVM\Type::KIND_POINTER: - $virtual = $this->context->builder->pointerCast($__value, $__type); - break; - default: - throw new \LogicException("Unknown how to handle type pair (double, " . $__other_type->toString() . ")"); - } - break; - default: - throw new \LogicException("Unsupported type cast: " . $__type->toString()); - } - $this->context->builder->call( - $this->context->lookupFunction('__ref__delref') , - $virtual - - ); - } - if ($this->context->builder->getInsertBlock()->getTerminator() === null) { - $this->context->builder->branch(end($endBlock)); - } - - $this->context->builder->positionAtEnd(array_pop($endBlock)); - $this->context->builder->returnVoid(); - - $this->context->builder->clearInsertionPosition(); + $this->context->builder->positionAtEnd($nullBlock); + $this->context->builder->returnVoid(); + + $this->context->builder->positionAtEnd($contBlock); + $kind = $this->context->builder->and($oldType, $i8->constInt(0x7f, false)); + $isString = $this->context->builder->icmp( + \PHPLLVM\Builder::INT_EQ, + $kind, + $i8->constInt(Variable::TYPE_STRING & 0x7f, false) + ); + $isObject = $this->context->builder->icmp( + \PHPLLVM\Builder::INT_EQ, + $kind, + $i8->constInt(Variable::TYPE_OBJECT & 0x7f, false) + ); + $isHt = $this->context->builder->icmp( + \PHPLLVM\Builder::INT_EQ, + $kind, + $i8->constInt(Variable::TYPE_HASHTABLE & 0x7f, false) + ); + $isPtr = $this->context->builder->bitwiseOr( + $isString, + $this->context->builder->bitwiseOr($isObject, $isHt) + ); + $releaseBlock = $fn->appendBasicBlock('value_delref_release'); + $this->context->builder->branchIf($isPtr, $releaseBlock, $doneBlock); + + $this->context->builder->positionAtEnd($releaseBlock); + $valueField = $this->context->builder->structGep( + $value, + $this->context->structFieldIndex($value, 'value') + ); + $ptrSlot = $this->context->builder->pointerCast( + $valueField, + $this->context->getTypeFromString('__ref__virtual*')->pointerType(0) + ); + $held = $this->context->builder->load($ptrSlot); + $this->context->builder->call( + $this->context->lookupFunction('__ref__delref'), + $held + ); + $this->context->builder->branch($doneBlock); + + $this->context->builder->positionAtEnd($doneBlock); + $this->context->builder->store($zeroType, $typeGep); + $this->context->builder->returnVoid(); + $this->context->builder->clearInsertionPosition(); } protected function implementValueToNumeric(): void { diff --git a/lib/JIT/Builtin/Type/Value.pre b/lib/JIT/Builtin/Type/Value.pre index faca2e60009..c9c762b99f3 100755 --- a/lib/JIT/Builtin/Type/Value.pre +++ b/lib/JIT/Builtin/Type/Value.pre @@ -57,6 +57,8 @@ class Value extends Type { } protected function implementValueDelref(): void { + // Hand-fixed in Value.php (#24226): load heap ptr for string/object/HT only, + // then clear type. compile{} &$value->value cast treated payload storage as __ref__. compile { function __value__valueDelref($value) { $oldType = $value->type; @@ -67,9 +69,10 @@ class Value extends Type { $isCounted = $oldType & $mask; if ($isCounted) { $ptr = &$value->value; - $virtual = (__ref__virtual*) $ptr; - __ref__delref($virtual); + $held = *(__ref__virtual**) $ptr; + __ref__delref($held); } + $value->type = (int8) 0; return; } } diff --git a/lib/JIT/JitValueBox.php b/lib/JIT/JitValueBox.php index 98ae067b5d4..b3eb9ecfc62 100644 --- a/lib/JIT/JitValueBox.php +++ b/lib/JIT/JitValueBox.php @@ -345,7 +345,7 @@ public static function assignToPointer(Context $context, Value $destPtr, Variabl return; case Variable::TYPE_HASHTABLE: $ht = $context->helper->loadValue($value); - $context->refcount->addref($ht); + // writeHashtable addrefs (#24226). $context->builder->call( $context->lookupFunction('__value__writeHashtable'), $destPtr, @@ -356,7 +356,6 @@ public static function assignToPointer(Context $context, Value $destPtr, Variabl } if (ArrayBuiltinHelper::isNativeArray($value->type)) { $ht = ArrayBuiltinHelper::loadHashTable($context, $value); - $context->refcount->addref($ht); $context->builder->call( $context->lookupFunction('__value__writeHashtable'), $destPtr, @@ -428,7 +427,6 @@ public static function promoteNativeLvalueToValueBox(Context $context, Variable break; case Variable::TYPE_HASHTABLE: $ht = $context->helper->loadValue($var); - $context->refcount->addref($ht); $context->builder->call( $context->lookupFunction('__value__writeHashtable'), $ptr, @@ -547,6 +545,7 @@ private static function copyBetweenPointers(Context $context, Value $destPtr, Va $context->lookupFunction('__value__readHashtable'), $srcPtr ); + // writeHashtable addrefs internally (same as writeObject, #24226). $context->builder->call( $context->lookupFunction('__value__writeHashtable'), $destPtr, @@ -563,6 +562,7 @@ private static function copyBetweenPointers(Context $context, Value $destPtr, Va $context->lookupFunction('__value__readObject'), $srcPtr ); + // writeObject addrefs internally (#4096); do not addref here. $context->builder->call( $context->lookupFunction('__value__writeObject'), $destPtr, @@ -721,7 +721,6 @@ public static function valuePtrFromNativeVariable(Context $context, Variable $va ); break; case Variable::TYPE_HASHTABLE: - $context->refcount->addref($native); $context->builder->call( $context->lookupFunction('__value__writeHashtable'), self::pointer($context, $slot), @@ -737,7 +736,6 @@ public static function valuePtrFromNativeVariable(Context $context, Variable $va default: if (ArrayBuiltinHelper::isNativeArray($var->type)) { $ht = ArrayBuiltinHelper::loadHashTable($context, $var); - $context->refcount->addref($ht); $context->builder->call( $context->lookupFunction('__value__writeHashtable'), self::pointer($context, $slot), diff --git a/test/differential/AOT-BASELINE.md b/test/differential/AOT-BASELINE.md index e030df863bf..772823b22d0 100644 --- a/test/differential/AOT-BASELINE.md +++ b/test/differential/AOT-BASELINE.md @@ -43,7 +43,7 @@ corpus*, not the language. | `e16_array_slice` | compiles; runtime still hits `print_r` thin-standalone gap (#23540) — slice itself fixed in #23991 | | `e30_array_lit_dim_assign_shift` | ~~regression / segfault~~ **fixed** in #24055 — dim-write orphan box sync + nested `[$a]` value-box hashtable dispatch | | `e04_usort` | **documented limitation** — array-callable / invokable comparators deferred | -| `e08_spread` | ~~variadic cast crash~~ **fixed** (#23971) — NestedJIT `toCall` isolation, call-unpack without list-isList guard, owned HT copy, runtime `nextFreeElement` on spread loops | +| `e08_spread` | ~~variadic cast crash~~ **fixed** (#23971) — NestedJIT `toCall` isolation, call-unpack without list-isList guard, owned HT copy, runtime `nextFreeElement` on spread loops; ~~intermittent `free(): invalid pointer`~~ **fixed** (#24226) — `__ref__*` stores insertValue results, `valueDelref` loads heap pointers for string/object/HT, `writeHashtable` addrefs like `writeObject` | | ~~`c07_method`~~ | ~~`Missing required argument 1` on a two-argument call whose arity is correct~~ **fixed** — free function after class no longer inherits leftover `scope->className` (#23971) | **`var_dump()` / `print_r()` of non-scalars — one limitation, six cases** diff --git a/test/differential/cases/e08_spread.php b/test/differential/cases/e08_spread.php index 929ffa39fb3..1bc4234b888 100644 --- a/test/differential/cases/e08_spread.php +++ b/test/differential/cases/e08_spread.php @@ -1 +1,3 @@ -