Skip to content

Commit 6bba813

Browse files
committed
Share the observed keys of whole-variable writes and walk the smaller set
Every write replacing a whole variable observes and kills the read keys of all its slots, and compileAccesses() copied them into each such write: quadratic in the writes and reads of a variable. They are now built once per variable and shared. Removing them, the markers of a variable and the observed keys from the live set walks the smaller of the two sets, like the per-slot markers already did. Amp-Thread-ID: https://ampcode.com/threads/T-01a1ad6c-dec2-72ff-bbe8-5b3b62746c94
1 parent 543dd29 commit 6bba813

3 files changed

Lines changed: 217 additions & 108 deletions

File tree

‎src/Analyser/VariableLivenessResolver.php‎

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
use PHPStan\Type\ObjectType;
1313
use PHPStan\Type\Type;
1414
use Throwable;
15+
use function array_diff_key;
16+
use function array_fill_keys;
17+
use function array_key_exists;
1518
use function array_keys;
1619
use function array_pop;
1720
use function array_reverse;
@@ -89,6 +92,9 @@ final class VariableLivenessResolver
8992
/** @var array<string, array<string, array<string, int>>> variable name => replaced slot => marker => id of the write leaving it */
9093
private array $markersBySlot = [];
9194

95+
/** @var array<string, array<string, int>> variable name => marker => id of the write leaving it */
96+
private array $markersByName = [];
97+
9298
/** @var array<int, true> */
9399
private array $overwrittenIds = [];
94100

@@ -264,9 +270,7 @@ private function liveBefore(?VariableFlow $flow, array $next, VariableFlowContex
264270
$this->observeOverwrite($item, $next);
265271
}
266272
}
267-
foreach (array_keys($this->killedKeys[$id] ?? []) as $key) {
268-
unset($next[$key]);
269-
}
273+
$next = self::withoutKeys($next, $this->killedKeys[$id] ?? []);
270274
$next = $this->killOverwriteMarkers($flow->write, $next);
271275
if (isset($this->overwriteMarkers[$id])) {
272276
$next[$this->overwriteMarkers[$id]] = true;
@@ -532,6 +536,14 @@ private function compileAccesses(): void
532536
$this->nameKeys[$name][$key] = true;
533537
}
534538
}
539+
// the keys of every slot, shared by all writes of the whole variable
540+
// instead of copied into each of them
541+
$allKeys = [];
542+
foreach ($keysBySlot as $keys) {
543+
$allKeys += $keys;
544+
}
545+
$offsetKeys = array_diff_key($allKeys, $keysBySlot['container'] ?? []);
546+
$allKills = array_fill_keys(array_keys($allKeys), true);
535547
foreach ($accesses as $access) {
536548
$write = $access->write;
537549
if ($write === null) {
@@ -540,25 +552,21 @@ private function compileAccesses(): void
540552
$id = $write->getId();
541553
$offset = $write->getOffset();
542554
if ($write->isOffsetWrite() && $offset !== null) {
543-
$slot = self::offsetKey($offset);
544-
$selectedKeys = [$slot => $keysBySlot[$slot] ?? []];
555+
$keys = $keysBySlot[self::offsetKey($offset)] ?? [];
556+
$kills = $write->replacesOffset();
545557
} else {
546-
$selectedKeys = $keysBySlot;
547-
if ($write->isOffsetWrite()) {
548-
unset($selectedKeys['container']);
549-
}
558+
$keys = $write->isOffsetWrite() ? $offsetKeys : $allKeys;
559+
$kills = !$write->isOffsetWrite();
550560
}
551-
$kills = !$write->isOffsetWrite() || ($offset !== null && $write->replacesOffset());
552-
foreach ($selectedKeys as $keys) {
553-
foreach ($keys as $key => $targetId) {
554-
$this->observedKeys[$id][$key] = $targetId;
555-
if (!$kills) {
556-
continue;
557-
}
558-
559-
$this->killedKeys[$id][$key] = true;
560-
}
561+
if ($keys === []) {
562+
continue;
561563
}
564+
$this->observedKeys[$id] = isset($this->observedKeys[$id]) ? $this->observedKeys[$id] + $keys : $keys;
565+
if (!$kills) {
566+
continue;
567+
}
568+
$killedKeys = $keys === $allKeys ? $allKills : array_fill_keys(array_keys($keys), true);
569+
$this->killedKeys[$id] = isset($this->killedKeys[$id]) ? $this->killedKeys[$id] + $killedKeys : $killedKeys;
562570
}
563571
$this->compileOverwrites($name, $accesses);
564572
}
@@ -597,6 +605,11 @@ private function compileOverwrites(string $name, array $accesses): void
597605
}
598606

599607
$this->markersBySlot[$name] = $markersBySlot;
608+
$markersOfName = [];
609+
foreach ($markersBySlot as $markers) {
610+
$markersOfName += $markers;
611+
}
612+
$this->markersByName[$name] = $markersOfName;
600613
}
601614

602615
/** The slot a write replaces: '' for the whole variable, null when it keeps the old value. */
@@ -616,7 +629,19 @@ private static function replacedSlot(VariableWrite $write): ?string
616629
/** @param array<string, true> $next */
617630
private function observeWrite(int $id, array $next): void
618631
{
619-
foreach ($this->observedKeys[$id] ?? [] as $key => $targetId) {
632+
$observedKeys = $this->observedKeys[$id] ?? [];
633+
// walk the smaller of the two sets
634+
if (count($observedKeys) > count($next)) {
635+
$live = [];
636+
foreach (array_keys($next) as $key) {
637+
if (!array_key_exists($key, $observedKeys)) {
638+
continue;
639+
}
640+
$live[$key] = $observedKeys[$key];
641+
}
642+
$observedKeys = $live;
643+
}
644+
foreach ($observedKeys as $key => $targetId) {
620645
if (!isset($next[$key])) {
621646
continue;
622647
}
@@ -689,23 +714,34 @@ private function killOverwriteMarkers(VariableWrite $write, array $next): array
689714
if ($slot === null) {
690715
return $next;
691716
}
692-
foreach ($markersBySlot as $markerSlot => $markers) {
693-
if ($slot !== '' && $markerSlot !== $slot) {
694-
continue;
717+
if ($slot === '') {
718+
return self::withoutKeys($next, $this->markersByName[$write->getVariableName()]);
719+
}
720+
721+
return self::withoutKeys($next, $markersBySlot[$slot] ?? []);
722+
}
723+
724+
/**
725+
* Walks the smaller of the two sets.
726+
*
727+
* @param array<string, true> $next
728+
* @param array<string, mixed> $keys
729+
* @return array<string, true>
730+
*/
731+
private static function withoutKeys(array $next, array $keys): array
732+
{
733+
if (count($keys) <= count($next)) {
734+
foreach (array_keys($keys) as $key) {
735+
unset($next[$key]);
695736
}
696-
// walk the smaller of the two sets
697-
if (count($markers) <= count($next)) {
698-
foreach (array_keys($markers) as $marker) {
699-
unset($next[$marker]);
700-
}
737+
738+
return $next;
739+
}
740+
foreach (array_keys($next) as $key) {
741+
if (!array_key_exists($key, $keys)) {
701742
continue;
702743
}
703-
foreach (array_keys($next) as $key) {
704-
if (!isset($markers[$key])) {
705-
continue;
706-
}
707-
unset($next[$key]);
708-
}
744+
unset($next[$key]);
709745
}
710746

711747
return $next;

0 commit comments

Comments
 (0)