From 39e6c4bfb1f3fb96bba47e3eec8e6451038a3d22 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 21 Feb 2026 23:22:25 +0900 Subject: feat: implement validation phase --- src/WebAssembly/Validation/ValidationStack.php | 154 +++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/WebAssembly/Validation/ValidationStack.php (limited to 'src/WebAssembly/Validation/ValidationStack.php') diff --git a/src/WebAssembly/Validation/ValidationStack.php b/src/WebAssembly/Validation/ValidationStack.php new file mode 100644 index 0000000..53b47a7 --- /dev/null +++ b/src/WebAssembly/Validation/ValidationStack.php @@ -0,0 +1,154 @@ + + */ + private array $values = []; + + /** + * @var list + */ + private array $controls = []; + + public function __construct( + ) { + } + + public function pushVal(?ValType $type): void + { + $this->values[] = $type; + } + + /** + * @param list $types + */ + public function pushVals(array $types): void + { + foreach ($types as $type) { + $this->pushVal($type); + } + } + + public function popVal(): ?ValType + { + // Check stack underflow. + $currentCtrl = $this->getCurrentControl(); + if (count($this->values) === $currentCtrl->height) { + // If the current control frame is marked as unreachable, the stack behaves polymorphically. + if ($currentCtrl->unreachable) { + return null; + } + throw new ValidationFailureException('Value stack underflow'); + } + + return array_pop($this->values); + } + + public function popValOf(?ValType $expect): ?ValType + { + $actual = $this->popVal(); + if ($actual !== $expect && $actual !== null && $expect !== null) { + throw new ValidationFailureException('Type mismatch'); + } + return $actual; + } + + /** + * @param list $types + * @return list + */ + public function popVals(array $types): array + { + $popped = []; + foreach (array_reverse($types) as $type) { + $popped[] = $this->popValOf($type); + } + return array_reverse($popped); + } + + /** + * @param class-string $opcode + * @param list $in + * @param list $out + */ + public function pushCtrl(string $opcode, array $in, array $out): void + { + $ctrl = new ControlFrame($opcode, $in, $out, count($this->values), false); + $this->controls[] = $ctrl; + $this->pushVals($in); + } + + public function popCtrl(): ControlFrame + { + if ($this->controlDepth() === 0) { + throw new ValidationFailureException('Control stack underflow'); + } + $ctrl = $this->getCurrentControl(); + $this->popVals($ctrl->endTypes); + if (count($this->values) !== $ctrl->height) { + throw new ValidationFailureException('Invalid stack height'); + } + array_pop($this->controls); + return $ctrl; + } + + /** + * @return list + */ + public function labelTypes(ControlFrame $ctrl): array + { + return $ctrl->opcode === Instrs\Control\Loop::class ? $ctrl->startTypes : $ctrl->endTypes; + } + + public function unreachable(): void + { + $currentCtrl = $this->getCurrentControl(); + $this->values = array_slice($this->values, 0, $currentCtrl->height); + assert(count($this->controls) !== 0); + array_pop($this->controls); + $this->controls[] = new ControlFrame( + $currentCtrl->opcode, + $currentCtrl->startTypes, + $currentCtrl->endTypes, + $currentCtrl->height, + true, + ); + } + + public function controlDepth(): int + { + return count($this->controls); + } + + public function getControl(int $index): ?ControlFrame + { + $absIndex = count($this->controls) - 1 - $index; + if ($absIndex < 0) { + return null; + } + return $this->controls[$absIndex] ?? null; + } + + public function getCurrentControl(): ControlFrame + { + if ($this->controlDepth() === 0) { + throw new ValidationFailureException('No control frame available'); + } + assert(count($this->controls) !== 0); + return $this->controls[array_key_last($this->controls)]; + } +} -- cgit v1.3-1-g0d28