TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
410 lines
15 KiB
410 lines
15 KiB
<?php
|
|
|
|
namespace TypePhp\Build;
|
|
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use ReflectionFunction;
|
|
use TypePhp\Analysis\CompilationStatistics;
|
|
use TypePhp\Type;
|
|
|
|
/**
|
|
* Converts generic compiler usage statistics into extension-level Nano needs.
|
|
*
|
|
* Source ownership remains php-nano metadata. This module only identifies
|
|
* extension names and computes their dependency closure.
|
|
*/
|
|
final class NanoExtensionSelector
|
|
{
|
|
/** @var array<string, string> */
|
|
private const array EXTENSION_NAMES = [
|
|
'standard' => 'standard',
|
|
'date' => 'date',
|
|
'hash' => 'hash',
|
|
'json' => 'json',
|
|
'pcre' => 'pcre',
|
|
'random' => 'random',
|
|
'reflection' => 'reflection',
|
|
'spl' => 'spl',
|
|
'filter' => 'filter',
|
|
'bcmath' => 'bcmath',
|
|
];
|
|
|
|
/** @var array<string, list<string>> */
|
|
private const array DEPENDENCIES = [
|
|
'spl' => ['json'],
|
|
];
|
|
|
|
/** @var array<string, string> */
|
|
private const array FUNCTION_OVERRIDES = [
|
|
'std::bigint' => 'bcmath',
|
|
'std::bigfloat' => 'bcmath',
|
|
'std::decimal' => 'bcmath',
|
|
];
|
|
|
|
/** @var array<string, string> */
|
|
private const array STANDARD_DIRECT_FEATURES = [
|
|
'count' => 'standard.array',
|
|
'sizeof' => 'standard.array',
|
|
'in_array' => 'standard.array',
|
|
'array_search' => 'standard.array',
|
|
'array_key_first' => 'standard.array',
|
|
'array_key_last' => 'standard.array',
|
|
'strlen' => 'standard.string',
|
|
'trim' => 'standard.string',
|
|
'ltrim' => 'standard.string',
|
|
'rtrim' => 'standard.string',
|
|
'strtolower' => 'standard.string',
|
|
'strtoupper' => 'standard.string',
|
|
'lcfirst' => 'standard.string',
|
|
'ucfirst' => 'standard.string',
|
|
'ucwords' => 'standard.string',
|
|
'explode' => 'standard.string',
|
|
'implode' => 'standard.string',
|
|
'strpos' => 'standard.string',
|
|
'stripos' => 'standard.string',
|
|
'strrpos' => 'standard.string',
|
|
'strstr' => 'standard.string',
|
|
'stristr' => 'standard.string',
|
|
'substr' => 'standard.string',
|
|
'dirname' => 'standard.string',
|
|
'basename' => 'standard.string',
|
|
'is_dir' => 'standard.filesystem',
|
|
'is_file' => 'standard.filesystem',
|
|
'file_exists' => 'standard.filesystem',
|
|
'realpath' => 'standard.filesystem',
|
|
'round' => 'standard.math',
|
|
'md5' => 'standard.hash',
|
|
'sha1' => 'standard.hash',
|
|
'version_compare' => 'standard.misc',
|
|
'print_r' => 'standard.misc',
|
|
'uniqid' => 'standard.misc',
|
|
'parse_str' => 'standard.misc',
|
|
'class_exists' => 'standard.core',
|
|
'interface_exists' => 'standard.core',
|
|
'trait_exists' => 'standard.core',
|
|
'enum_exists' => 'standard.core',
|
|
'function_exists' => 'standard.core',
|
|
'method_exists' => 'standard.core',
|
|
'property_exists' => 'standard.core',
|
|
'is_a' => 'standard.core',
|
|
'is_subclass_of' => 'standard.core',
|
|
'defined' => 'standard.core',
|
|
'define' => 'standard.core',
|
|
'get_parent_class' => 'standard.core',
|
|
];
|
|
|
|
/** @var array<string, string> */
|
|
private const array STANDARD_FUNCTION_FEATURES = [
|
|
'compact' => 'standard.array',
|
|
'current' => 'standard.array',
|
|
'end' => 'standard.array',
|
|
'key' => 'standard.array',
|
|
'max' => 'standard.array',
|
|
'min' => 'standard.array',
|
|
'next' => 'standard.array',
|
|
'pos' => 'standard.array',
|
|
'prev' => 'standard.array',
|
|
'range' => 'standard.array',
|
|
'reset' => 'standard.array',
|
|
'sort' => 'standard.array',
|
|
'rsort' => 'standard.array',
|
|
'asort' => 'standard.array',
|
|
'arsort' => 'standard.array',
|
|
'ksort' => 'standard.array',
|
|
'krsort' => 'standard.array',
|
|
'usort' => 'standard.array',
|
|
'uasort' => 'standard.array',
|
|
'uksort' => 'standard.array',
|
|
'base64_encode' => 'standard.encoding',
|
|
'base64_decode' => 'standard.encoding',
|
|
'htmlspecialchars' => 'standard.encoding',
|
|
'htmlspecialchars_decode' => 'standard.encoding',
|
|
'html_entity_decode' => 'standard.encoding',
|
|
'htmlentities' => 'standard.encoding',
|
|
'get_html_translation_table' => 'standard.encoding',
|
|
'parse_url' => 'standard.encoding',
|
|
'urlencode' => 'standard.encoding',
|
|
'urldecode' => 'standard.encoding',
|
|
'rawurlencode' => 'standard.encoding',
|
|
'rawurldecode' => 'standard.encoding',
|
|
'chdir' => 'standard.filesystem',
|
|
'chroot' => 'standard.filesystem',
|
|
'closedir' => 'standard.filesystem',
|
|
'copy' => 'standard.filesystem',
|
|
'disk_free_space' => 'standard.filesystem',
|
|
'disk_total_space' => 'standard.filesystem',
|
|
'getcwd' => 'standard.filesystem',
|
|
'glob' => 'standard.filesystem',
|
|
'mkdir' => 'standard.filesystem',
|
|
'opendir' => 'standard.filesystem',
|
|
'pathinfo' => 'standard.filesystem',
|
|
'readdir' => 'standard.filesystem',
|
|
'rename' => 'standard.filesystem',
|
|
'rewinddir' => 'standard.filesystem',
|
|
'rmdir' => 'standard.filesystem',
|
|
'scandir' => 'standard.filesystem',
|
|
'stat' => 'standard.filesystem',
|
|
'lstat' => 'standard.filesystem',
|
|
'tempnam' => 'standard.filesystem',
|
|
'tmpfile' => 'standard.filesystem',
|
|
'unlink' => 'standard.filesystem',
|
|
'fprintf' => 'standard.format',
|
|
'printf' => 'standard.format',
|
|
'scanf' => 'standard.format',
|
|
'sprintf' => 'standard.format',
|
|
'sscanf' => 'standard.format',
|
|
'vfprintf' => 'standard.format',
|
|
'vprintf' => 'standard.format',
|
|
'vsprintf' => 'standard.format',
|
|
'md5' => 'standard.hash',
|
|
'md5_file' => 'standard.hash',
|
|
'sha1' => 'standard.hash',
|
|
'sha1_file' => 'standard.hash',
|
|
'phpcredits' => 'standard.info',
|
|
'phpinfo' => 'standard.info',
|
|
'phpversion' => 'standard.info',
|
|
'php_ini_loaded_file' => 'standard.info',
|
|
'php_ini_scanned_files' => 'standard.info',
|
|
'php_sapi_name' => 'standard.info',
|
|
'php_uname' => 'standard.info',
|
|
'abs' => 'standard.math',
|
|
'ceil' => 'standard.math',
|
|
'floor' => 'standard.math',
|
|
'fmod' => 'standard.math',
|
|
'fdiv' => 'standard.math',
|
|
'fpow' => 'standard.math',
|
|
'intdiv' => 'standard.math',
|
|
'number_format' => 'standard.math',
|
|
'pi' => 'standard.math',
|
|
'pow' => 'standard.math',
|
|
'sqrt' => 'standard.math',
|
|
'hrtime' => 'standard.misc',
|
|
'microtime' => 'standard.misc',
|
|
'gettimeofday' => 'standard.misc',
|
|
'sleep' => 'standard.misc',
|
|
'usleep' => 'standard.misc',
|
|
'time_nanosleep' => 'standard.misc',
|
|
'time_sleep_until' => 'standard.misc',
|
|
'uniqid' => 'standard.misc',
|
|
'version_compare' => 'standard.misc',
|
|
'bin2hex' => 'standard.string',
|
|
'chr' => 'standard.string',
|
|
'explode' => 'standard.string',
|
|
'hex2bin' => 'standard.string',
|
|
'implode' => 'standard.string',
|
|
'join' => 'standard.string',
|
|
'nl2br' => 'standard.string',
|
|
'ord' => 'standard.string',
|
|
'parse_str' => 'standard.string',
|
|
'strrev' => 'standard.string',
|
|
'wordwrap' => 'standard.string',
|
|
'boolval' => 'standard.type',
|
|
'doubleval' => 'standard.type',
|
|
'floatval' => 'standard.type',
|
|
'get_debug_type' => 'standard.type',
|
|
'gettype' => 'standard.type',
|
|
'intval' => 'standard.type',
|
|
'settype' => 'standard.type',
|
|
'strval' => 'standard.type',
|
|
'debug_zval_dump' => 'standard.var',
|
|
'memory_get_peak_usage' => 'standard.var',
|
|
'memory_get_usage' => 'standard.var',
|
|
'memory_reset_peak_usage' => 'standard.var',
|
|
'print_r' => 'standard.var',
|
|
'serialize' => 'standard.var',
|
|
'unserialize' => 'standard.var',
|
|
'var_dump' => 'standard.var',
|
|
'var_export' => 'standard.var',
|
|
'constant' => 'standard.core',
|
|
'ini_alter' => 'standard.core',
|
|
'ini_get' => 'standard.core',
|
|
'ini_get_all' => 'standard.core',
|
|
'ini_parse_quantity' => 'standard.core',
|
|
'ini_restore' => 'standard.core',
|
|
'ini_set' => 'standard.core',
|
|
];
|
|
|
|
/** @var array<string, string> */
|
|
private const array STANDARD_CLASS_FEATURES = [
|
|
'__php_incomplete_class' => 'standard.var',
|
|
'roundingmode' => 'standard.math',
|
|
'sortdirection' => 'standard.array',
|
|
];
|
|
|
|
/**
|
|
* @param list<string> $availableExtensions
|
|
*/
|
|
public function select(
|
|
CompilationStatistics $statistics,
|
|
?array $availableExtensions = null,
|
|
): NanoExtensionSelection {
|
|
$availableExtensions ??= array_values(self::EXTENSION_NAMES);
|
|
$available = [];
|
|
foreach ($availableExtensions as $extension) {
|
|
$available[strtolower($extension)] = true;
|
|
}
|
|
ksort($available, SORT_STRING);
|
|
|
|
$dynamic = array_keys($statistics->get(CompilationStatistics::DYNAMIC_CAPABILITIES));
|
|
if ($dynamic !== []) {
|
|
// Dynamic calls can only imply the built-in Nano surface. Optional
|
|
// php-src and PIE extensions must still be selected by a statically
|
|
// observed symbol (or, in the future, explicit project metadata).
|
|
// Otherwise adding an extension to php-nano silently bloats every
|
|
// program that contains any dynamic call.
|
|
$builtIn = array_intersect_key($available, self::EXTENSION_NAMES);
|
|
return new NanoExtensionSelection(
|
|
array_keys($builtIn),
|
|
true,
|
|
[],
|
|
array_map(
|
|
static fn(string $capability): string => "dynamic capability: {$capability}",
|
|
$dynamic,
|
|
),
|
|
);
|
|
}
|
|
|
|
$selected = [];
|
|
$features = [];
|
|
foreach (array_keys($statistics->get(CompilationStatistics::RUNTIME_FUNCTIONS)) as $function) {
|
|
$owner = self::FUNCTION_OVERRIDES[strtolower($function)]
|
|
?? $this->functionExtension($function);
|
|
if ($owner === 'standard') {
|
|
$feature = $this->standardFeature($function);
|
|
if ($feature === null) {
|
|
$selected['standard'] = true;
|
|
} else {
|
|
$features[$feature] = true;
|
|
}
|
|
continue;
|
|
}
|
|
if ($owner !== null && isset($available[$owner])) {
|
|
$selected[$owner] = true;
|
|
}
|
|
}
|
|
foreach (array_keys($statistics->get(CompilationStatistics::DIRECT_FUNCTIONS)) as $function) {
|
|
$feature = $this->standardFeature($function);
|
|
if ($feature !== null) {
|
|
$features[$feature] = true;
|
|
continue;
|
|
}
|
|
$owner = $this->functionExtension($function);
|
|
if ($owner === 'date') {
|
|
// Common date helpers are coupled to timelib and the DateTime
|
|
// implementation, so date remains one composition unit.
|
|
$selected['date'] = true;
|
|
continue;
|
|
}
|
|
if ($owner === 'standard') {
|
|
$selected['standard'] = true;
|
|
continue;
|
|
}
|
|
if ($owner !== null && isset($available[$owner])) {
|
|
$selected[$owner] = true;
|
|
}
|
|
}
|
|
foreach (array_keys($statistics->get(CompilationStatistics::FUNCTIONS)) as $function) {
|
|
$owner = self::FUNCTION_OVERRIDES[strtolower($function)] ?? null;
|
|
if ($owner !== null && isset($available[$owner])) {
|
|
$selected[$owner] = true;
|
|
}
|
|
}
|
|
foreach (array_keys($statistics->get(CompilationStatistics::CLASSES)) as $class) {
|
|
$owner = $this->classExtension($class);
|
|
if ($owner === 'standard') {
|
|
$feature = self::STANDARD_CLASS_FEATURES[strtolower(ltrim($class, '\\'))] ?? null;
|
|
if ($feature === null) {
|
|
$selected['standard'] = true;
|
|
} else {
|
|
$features[$feature] = true;
|
|
}
|
|
continue;
|
|
}
|
|
if ($owner !== null && isset($available[$owner])) {
|
|
$selected[$owner] = true;
|
|
}
|
|
}
|
|
foreach ([Type::BIGINT, Type::BIGFLOAT, Type::DECIMAL] as $type) {
|
|
if ($statistics->has(CompilationStatistics::TYPES, $type)
|
|
&& isset($available['bcmath'])
|
|
) {
|
|
$selected['bcmath'] = true;
|
|
}
|
|
}
|
|
|
|
$pending = array_keys($selected);
|
|
while (($extension = array_pop($pending)) !== null) {
|
|
foreach (self::DEPENDENCIES[$extension] ?? [] as $dependency) {
|
|
if (isset($available[$dependency]) && !isset($selected[$dependency])) {
|
|
$selected[$dependency] = true;
|
|
$pending[] = $dependency;
|
|
}
|
|
}
|
|
}
|
|
ksort($selected, SORT_STRING);
|
|
ksort($features, SORT_STRING);
|
|
return new NanoExtensionSelection(array_keys($selected), false, array_keys($features));
|
|
}
|
|
|
|
private function standardFeature(string $function): ?string
|
|
{
|
|
$function = strtolower($function);
|
|
$feature = self::STANDARD_DIRECT_FEATURES[$function]
|
|
?? self::STANDARD_FUNCTION_FEATURES[$function]
|
|
?? null;
|
|
if ($feature !== null) {
|
|
return $feature;
|
|
}
|
|
if (str_starts_with($function, 'array_')) {
|
|
return 'standard.array';
|
|
}
|
|
if (str_starts_with($function, 'str_') || str_starts_with($function, 'substr_')) {
|
|
return 'standard.string';
|
|
}
|
|
if (str_starts_with($function, 'file') || str_starts_with($function, 'stream_')) {
|
|
return 'standard.filesystem';
|
|
}
|
|
if (str_starts_with($function, 'is_')) {
|
|
return in_array($function, ['is_dir', 'is_file', 'is_link', 'is_readable', 'is_writable', 'is_writeable', 'is_executable'], true)
|
|
? 'standard.filesystem'
|
|
: 'standard.type';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function functionExtension(string $name): ?string
|
|
{
|
|
if (!function_exists($name)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return $this->normalizeExtension((new ReflectionFunction($name))->getExtensionName());
|
|
} catch (ReflectionException) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function classExtension(string $name): ?string
|
|
{
|
|
if (!class_exists($name, false) && !interface_exists($name, false)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return $this->normalizeExtension((new ReflectionClass($name))->getExtensionName());
|
|
} catch (ReflectionException) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function normalizeExtension(string|false $extension): ?string
|
|
{
|
|
if (!is_string($extension)) {
|
|
return null;
|
|
}
|
|
$extension = strtolower($extension);
|
|
return preg_match('/^[a-z][a-z0-9_]*$/', $extension) === 1
|
|
? $extension
|
|
: null;
|
|
}
|
|
}
|
|
|