Skip to content

Commit 51aafbc

Browse files
authored
ConstantArrayType: reduce duplicate work in checkOurKeys() (#6661)
2 parents 681809d + ff74248 commit 51aafbc

3 files changed

Lines changed: 58 additions & 18 deletions

File tree

‎src/Turbo/TurboExtensionEnabler.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
final class TurboExtensionEnabler
3434
{
3535

36-
public const EXPECTED_EXTENSION_VERSION = '12424ee';
36+
public const EXPECTED_EXTENSION_VERSION = '6e457b8';
3737

3838
private static bool $active = false;
3939

‎src/Type/Constant/ConstantArrayType.php‎

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
use function pow;
9090
use function range;
9191
use function sort;
92+
use function spl_object_id;
9293
use function sprintf;
9394
use function str_contains;
9495
use function strtolower;
@@ -699,6 +700,10 @@ public function accepts(Type $type, bool $strictTypes): AcceptsResult
699700
private function checkOurKeys(Type $type, bool $strictTypes): AcceptsResult
700701
{
701702
$result = AcceptsResult::createYes();
703+
704+
// offsets sharing the same value types (e.g. a type alias used for many keys) are compared once
705+
/** @var array<string, array{Type, AcceptsResult}> $acceptsValueCache */
706+
$acceptsValueCache = [];
702707
foreach ($this->keyTypes as $i => $keyType) {
703708
$valueType = $this->valueTypes[$i];
704709
$hasOffsetValueType = $type->hasOffsetValueType($keyType);
@@ -718,23 +723,29 @@ private function checkOurKeys(Type $type, bool $strictTypes): AcceptsResult
718723

719724
$result = $result->and($hasOffset);
720725
$otherValueType = $type->getOffsetValueType($keyType);
721-
$verbosity = null;
722-
$acceptsValue = $valueType->accepts($otherValueType, $strictTypes)->decorateReasons(
723-
static function (string $reason) use ($keyType, $valueType, &$verbosity, $otherValueType) {
724-
$verbosity ??= VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType);
726+
$acceptsValueCacheKey = spl_object_id($valueType) . '-' . spl_object_id($otherValueType);
727+
if (isset($acceptsValueCache[$acceptsValueCacheKey])) {
728+
$acceptsValue = $acceptsValueCache[$acceptsValueCacheKey][1];
729+
} else {
730+
$acceptsValue = $valueType->accepts($otherValueType, $strictTypes);
731+
// $otherValueType is kept alive so that its id is not reused
732+
$acceptsValueCache[$acceptsValueCacheKey] = [$otherValueType, $acceptsValue];
733+
}
734+
if ($acceptsValue->yes() && count($acceptsValue->reasons) === 0) {
735+
continue;
736+
}
725737

726-
return sprintf(
727-
'Offset %s (%s) does not accept type %s: %s',
728-
$keyType->describe(VerbosityLevel::precise()),
729-
$valueType->describe($verbosity),
730-
$otherValueType->describe($verbosity),
731-
$reason,
732-
);
733-
},
738+
$verbosity = VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType);
739+
$acceptsValue = $acceptsValue->decorateReasons(
740+
static fn (string $reason) => sprintf(
741+
'Offset %s (%s) does not accept type %s: %s',
742+
$keyType->describe(VerbosityLevel::precise()),
743+
$valueType->describe($verbosity),
744+
$otherValueType->describe($verbosity),
745+
$reason,
746+
),
734747
);
735748
if (!$acceptsValue->yes() && count($acceptsValue->reasons) === 0 && $type->isConstantArray()->yes()) {
736-
$verbosity ??= VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType);
737-
738749
$acceptsValue = new AcceptsResult($acceptsValue->result, [
739750
sprintf(
740751
'Offset %s (%s) does not accept type %s.',

‎turbo-ext/src/ConstantArrayType.cpp‎

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,6 +1658,12 @@ class ConstantArrayType
16581658
zval *k = keyTypes();
16591659
zval *v = k != NULL ? valueTypes() : NULL;
16601660
if (UNEXPECTED(result.isUndef() || v == NULL)) return zv::Val();
1661+
/* $acceptsValueCache = [] — keyed by both object handles in one
1662+
* integer instead of the twin's 'id-id' string */
1663+
zval acceptsValueCacheZv;
1664+
array_init(&acceptsValueCacheZv);
1665+
zv::Val acceptsValueCacheHolder = zv::Val::adopt(acceptsValueCacheZv);
1666+
HashTable *acceptsValueCache = Z_ARRVAL(acceptsValueCacheZv);
16611667
for (zv::ArrayEntry entry : zv::ArrRef(k)) {
16621668
zend_long i = (zend_long) entry.indexKey();
16631669
zval *keyType = entry.value().deref().raw();
@@ -1702,11 +1708,34 @@ class ConstantArrayType
17021708
if (UNEXPECTED(result.isUndef())) return zv::Val();
17031709
zv::Val otherValueType = callType(Z_OBJ_P(type), PT_LC("getoffsetvaluetype"), 1, keyType);
17041710
if (UNEXPECTED(otherValueType.isUndef())) return zv::Val();
1711+
zend_ulong acceptsValueCacheKey = ((zend_ulong) Z_OBJ_HANDLE_P(valueType) << 32) | (zend_ulong) Z_OBJ_HANDLE_P(otherValueType.raw());
1712+
zv::Val acceptsValue;
1713+
zval *acceptsValueCached = zend_hash_index_find(acceptsValueCache, acceptsValueCacheKey);
1714+
if (acceptsValueCached != NULL) {
1715+
acceptsValue = zv::Val::copyOf(zv::Ref(zend_hash_index_find(Z_ARRVAL_P(acceptsValueCached), 1)));
1716+
} else {
1717+
zv::Args args{otherValueType.raw(), strictTypes};
1718+
acceptsValue = pt_type_op(Z_OBJ_P(valueType), PT_OP_ACCEPTS, 2, args);
1719+
if (UNEXPECTED(acceptsValue.isUndef())) return zv::Val();
1720+
/* $otherValueType is kept alive so that its id is not reused */
1721+
zval pair;
1722+
array_init_size(&pair, 2);
1723+
Z_TRY_ADDREF_P(otherValueType.raw());
1724+
add_next_index_zval(&pair, otherValueType.raw());
1725+
Z_TRY_ADDREF_P(acceptsValue.raw());
1726+
add_next_index_zval(&pair, acceptsValue.raw());
1727+
zend_hash_index_update(acceptsValueCache, acceptsValueCacheKey, &pair);
1728+
}
1729+
/* $acceptsValue->yes() && count($acceptsValue->reasons) === 0: continue */
1730+
zend_long acceptsValueBefore = pt_type_result_trinary(acceptsValue.raw());
1731+
if (UNEXPECTED(acceptsValueBefore < 0)) return zv::Val();
1732+
if (acceptsValueBefore == PT_TRI_YES) {
1733+
zv::Val acceptsValueReasons = resultReasons(acceptsValue.raw());
1734+
if (UNEXPECTED(acceptsValueReasons.isUndef())) return zv::Val();
1735+
if (arrayCount(acceptsValueReasons.raw()) == 0) continue;
1736+
}
17051737
zv::Val verbosity = pt_type_verbosity_recommended(valueType, otherValueType.raw());
17061738
if (UNEXPECTED(verbosity.isUndef())) return zv::Val();
1707-
zv::Args args{otherValueType.raw(), strictTypes};
1708-
zv::Val acceptsValue = pt_type_op(Z_OBJ_P(valueType), PT_OP_ACCEPTS, 2, args);
1709-
if (UNEXPECTED(acceptsValue.isUndef())) return zv::Val();
17101739
zv::Val captured = quadOf(keyType, valueType, verbosity.raw(), otherValueType.raw());
17111740
acceptsValue = decorateReasons(acceptsValue.raw(), offsetReasonCallback, captured.raw(), NULL);
17121741
if (UNEXPECTED(acceptsValue.isUndef())) return zv::Val();

0 commit comments

Comments
 (0)