packages/deriver/src/Deriver/Evaluator/Execution/State.php

1<?php
2
3declare(strict_types=1);
4
5namespace Deriver\Deriver\Evaluator\Execution;
6
7use Deriver\Deriver\Evaluator\Execution\Control\Handler;
8use Deriver\Deriver\Evaluator\Execution\Control\IteratorCursor;
9use Deriver\Deriver\Evaluator\Execution\Memory\Location;
10use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
11
12/**
13 * One correlated execution path; forks copy state without sharing mutable memory.
14 * @visibility namespace
15 * @visibility Deriver\Deriver\Execution
16 */
17final class State
18{
19    /**
20     * @var array<string, \Deriver\Deriver\Evaluator\Term> SSA expression results.
21     */
22    public array $registers = [];
23    /**
24     * @var array<string, string> Register definitions in the derivation graph.
25     */
26    public array $producers = [];
27    /**
28     * @var array<string, Location> Address registers.
29     */
30    public array $addresses = [];
31    /**
32     * @var array<string, Offset\Address> Raw offset chains retained until their storage operation.
33     */
34    public array $offsets = [];
35    /**
36     * @var array<string, Call\Preparation\Target> Signatures captured before evaluating arguments.
37     */
38    public array $callTargets = [];
39    /**
40     * @var array<string, Transfer\PropertySlot> Property metadata by address register.
41     */
42    public array $properties = [];
43    /**
44     * @var array<string, Location> Local variable bindings.
45     */
46    public array $locals = [];
47    /**
48     * @var array<string, bool> Correlated branch decisions.
49     */
50    public array $guard = [];
51    /**
52     * @var array<string, array{min: int|float|null, max: int|float|null, equal: \Deriver\Deriver\Evaluator\Term|null, excluded: list<\Deriver\Deriver\Evaluator\Term>}> Input constraints.
53     */
54    public array $constraints = [];
55    /**
56     * @var list<Handler> Active exception regions.
57     */
58    public array $handlers = [];
59    /**
60     * @var array<string, IteratorCursor> Active foreach cursors.
61     */
62    public array $iterators = [];
63    /**
64     * @var array<int, int> Loop header visits.
65     */
66    public array $visits = [];
67    /**
68     * @var list<string> Derivation node identifiers.
69     */
70    public array $evidence = [];
71    /**
72     * @var list<string> Definitions that determine this path's reachability.
73     */
74    public array $controls = [];
75    /**
76     * @var array<int, array<string, \Deriver\Deriver\Evaluator\Term>> Loop entry storage.
77     */
78    public array $loopEntries = [];
79    /**
80     * Current CFG block.
81     */
82    public int $block = 0;
83    /**
84     * Previously completed block for Phi selection.
85     */
86    public int $previous = -1;
87    /**
88     * Current completion.
89     */
90    public Completion\Completion $completion;
91    /**
92     * Whether this execution path has reached the query's observation point.
93     */
94    public bool $observed = false;
95    /**
96     * @var array<int, true> Batch observations already reached on this execution path
97     */
98    public array $observedQueries = [];
99    /**
100     * Cause of unknown writes to this frame's symbol table, including future bindings.
101     */
102    public ?string $unknownLocals = null;
103    /**
104     * Late static binding class.
105     */
106    public string $lateStaticClass = '';
107
108    /**
109     * @var array<int, array<string, \Deriver\Deriver\Evaluator\Term>> Widened loop header approximations.
110     */
111    public array $approximations = [];
112    /**
113     * @var array<int, string> Alias and iterator structure at a widened header.
114     */
115    public array $loopStructures = [];
116    /**
117     * @var array<int, array<string, bool>> Guards established outside each loop.
118     */
119    public array $loopGuards = [];
120    /**
121     * Loop header whose body has reached a checked post-fixpoint.
122     */
123    public ?int $stableHeader = null;
124
125    /**
126     * @param Memory $memory Path-specific memory
127     */
128    public function __construct(public Memory $memory = new Memory())
129    {
130        $this->completion = new Completion\Completion();
131    }
132
133    /**
134     * Forks a correlated path with independent subsequent writes.
135     * @return self Forked state
136     */
137    public function fork(): self
138    {
139        $fork = clone $this;
140        $fork->memory = clone $this->memory;
141        return $fork;
142    }
143
144    /**
145     * Resolves a local binding, allocating an uninitialized cell when needed.
146     * @param string $name Variable name
147     * @return Location Address with its local binding identity
148     */
149    public function local(string $name): Location
150    {
151        if (!isset($this->locals[$name])) {
152            $initial = in_array($name, ['_GET', '_POST', '_COOKIE', '_SERVER', '_ENV', '_REQUEST', '_FILES', '_SESSION'], true) ? new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::External, 'superglobal:' . $name, attributes: ['type' => 'array', 'stability' => 'request', 'elementType' => in_array($name, ['_GET', '_POST', '_COOKIE', '_REQUEST'], true) ? 'string|array|null' : 'mixed']) : new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Uninitialized);
153            if ($this->unknownLocals !== null) {
154                $initial = \Deriver\Deriver\Evaluator\Term::opaque($this->unknownLocals);
155            }
156            $this->locals[$name] = $this->memory->allocate($initial);
157        }
158        $location = $this->locals[$name];
159        return new Location($location->root, $location->path, $name, $location->unknown);
160    }
161
162    /**
163     * Rebinds a PHP reference without retargeting other aliases of the old cell.
164     * @param Location $destination Left address
165     * @param Location $source Right address
166     */
167    public function alias(Location $destination, Location $source): void
168    {
169        if ($source->unknown) {
170            $this->unknownAlias($destination, $source);
171            return;
172        }
173        $cell = $this->memory->reference($source);
174        if ($destination->local !== '' && $destination->path === []) {
175            if (str_starts_with($destination->root, 'global:') && $destination->root !== $cell) {
176                $this->memory->writePath($destination->root, [], new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Cell, $cell), true);
177            }
178            $this->locals[$destination->local] = new Location($cell);
179            return;
180        }
181        $this->memory->writePath($destination->root, $destination->path, new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Cell, $cell), true);
182    }
183
184    /**
185     * Binds a reference to an unknown slot of known storage so that writes through either side invalidate that storage.
186     * @param Location $destination Left address
187     * @param Location $source Unknown slot, whose root is the storage it may belong to
188     */
189    public function unknownAlias(Location $destination, Location $source): void
190    {
191        if ($destination->local !== '' && $destination->path === [] && !str_starts_with($destination->root, 'global:')) {
192            $this->locals[$destination->local] = new Location($source->root, $source->path, $destination->local, true);
193            return;
194        }
195        $this->memory->write($source, \Deriver\Deriver\Evaluator\Term::opaque('UNKNOWN_REFERENCE'));
196        $this->memory->write($destination, \Deriver\Deriver\Evaluator\Term::opaque('UNKNOWN_REFERENCE'));
197    }
198
199    /**
200     * Returns a register while keeping absent computation distinct from null.
201     * @param string $register SSA register
202     * @return \Deriver\Deriver\Evaluator\Term Available value or an explicit residual
203     */
204    public function value(string $register): \Deriver\Deriver\Evaluator\Term
205    {
206        return $this->memory->dereference($this->registers[$register] ?? \Deriver\Deriver\Evaluator\Term::opaque('UNCOMPUTED_REGISTER'));
207    }
208
209    /**
210     * Produces the observable local state.
211     * @return array<string, \Deriver\Deriver\Evaluator\Term> Materialized local values
212     */
213    public function snapshot(): array
214    {
215        $values = [];
216        foreach ($this->locals as $name => $location) {
217            $values[$name] = $this->memory->materialize($this->memory->read($location));
218        }
219        ksort($values);
220        return $values;
221    }
222}
223