climate->arguments->add([ 'optimize' => [ 'prefix' => 'O', 'longPrefix' => 'optimize', 'description' => 'Set the optimization level of the gcc compiler to 0 by default', 'required' => false, 'castTo' => 'int', 'defaultValue' => 0, ], 'output' => [ 'prefix' => 'o', 'longPrefix' => 'output', 'description' => 'Output file', ], 'help' => [ 'prefix' => 'h', 'longPrefix' => 'help', 'description' => 'Show help', 'noValue' => true, ], 'profile' => [ 'longPrefix' => 'profile', 'description' => 'Enable performance profiling', 'required' => false, 'noValue' => true, ], 'noLiteralStrings' => [ 'longPrefix' => 'no-literal-strings', 'description' => 'Disable literal strings optimization', 'required' => false, 'noValue' => true, ], ]); $this->preprocessArgvAdvanced(); $this->climate->arguments->parse(); $this->optimizeLevel = $this->climate->arguments->get('optimize'); $this->internalFunctions = array_flip(get_defined_functions()['internal']); if ($this->climate->arguments->defined('help')) { $this->showUsage(); exit(0); } } public function showUsage(): void { $climate = $this->climate; $climate->bold()->green('PHP AOT Compiler v1.0.0'); $climate->br(); $climate->bold('USAGE:'); $climate->tab()->out('./bin/compiler.php [options]'); $climate->br(); $climate->bold('ARGUMENTS:'); $climate->tab()->out(' Input PHP file/directory to compile'); $climate->br(); $climate->bold('OPTIONS:'); $climate->tab()->out('-O Optimization level (0-3, default: 0)'); $climate->tab()->out('-p, --profile Enable performance profiling'); $climate->tab()->out('-o, --output Output binary name (default: input basename)'); $climate->tab()->out('-v, --verbose Verbose output'); $climate->tab()->out('-h, --help Show this help message'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); $climate->br(); $climate->bold('EXAMPLES:'); $climate->tab()->out('./bin/compiler.php examples/hello.php'); $climate->tab()->out('./bin/compiler.php examples/bench.php -O2'); $climate->tab()->out('./bin/compiler.php examples/bench.php -O2 -p'); $climate->tab()->out('./bin/compiler.php examples/app.php -O3 -o myapp -v'); $climate->br(); } public function convert(string $file): string { $phpCode = $this->loadFile($file); while (true) { try { $cppCode = $this->doConvert($phpCode); $info = pathinfo($file); $cppFile = $this->buildDir . '/' . $this->removeCommonPrefix($this->buildDir, $info['dirname'] . '/' . $info['filename'] . '.cc'); $this->save($cppCode, $cppFile); return $cppFile; } catch (RedoException $e) { continue; } } } protected function doConvert(string $phpCode): string { $this->climate->info('convert: ' . $this->file); $ast = $this->parser->parse($phpCode); $traverser = new NodeTraverser(); $traverser->addVisitor(new Visitor()); $stmts = $traverser->traverse($ast); $this->resetFile(); $this->resetNamespace(); $cppCode = ''; foreach ($stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_Declare': $this->parseDeclare($v); break; case 'Stmt_Namespace': $cppCode .= $this->parseNamespaceDef($v); break; case 'Stmt_Class': $cppCode .= $this->parseClass($v); break; case 'Stmt_Use': $cppCode .= $this->parseUse($v) . PHP_EOL; break; case 'Stmt_Function': $cppCode .= $this->parseFunction($v) . PHP_EOL; break; case 'Stmt_Const': $this->parseConstDef($v) . PHP_EOL; break; default: abort($v); } } // include + extern global vars + function impl return $this->genIncludeHeaderFiles() . $cppCode; } public function preprocessArgvAdvanced(): void { global $argv; $processed = [$argv[0]]; for ($i = 1; $i < count($argv); $i++) { $arg = $argv[$i]; if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { $option = $matches[1]; $value = $matches[2]; $processed[] = "-{$option}"; $processed[] = $value; } elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { $options = str_split($matches[1]); foreach ($options as $opt) { $processed[] = "-{$opt}"; } } else { $processed[] = $arg; } } $argv = $processed; } public function genExternGlobalVars(string $file): void { $lines[] = '#include '; $lines[] = PHP_EOL; foreach ($this->globalVars as $name => $type) { $lines[] = 'extern ' . self::TYPE_VAR . ' ' . $name . ';'; } $literalStringsCount = count($this->literalStrings); $lines[] = 'extern php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; $code = implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; $this->writeFile($file, $code); } public function genGlobalVars(string $file): void { $code = $this->genIncludeHeaderFiles(); $lines = []; // 全局变量只能是 var 类型 foreach ($this->globalVars as $name => $type) { $lines[] = self::TYPE_VAR . ' ' . $name . ';'; } $code .= implode(PHP_EOL, $lines) . PHP_EOL; $code .= PHP_EOL; $literalStringsCount = count($this->literalStrings); $code .= 'php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '] = {' . PHP_EOL; $this->indentLevel++; foreach ($this->literalStrings as $str => $index) { $code .= $this->getIndent() . 'php::String{ZEND_STRL("' . $this->escapeString($str) . '"), true},' . PHP_EOL; } $this->indentLevel--; $code .= '};' . PHP_EOL; // 生成常量表 $this->indentLevel++; foreach ($this->nativeConstants as $name => $constant) { $code .= $constant->type . ' ' . $name . ';' . PHP_EOL; } $this->indentLevel--; $code .= PHP_EOL; $this->indentLevel++; $lines = []; foreach ($this->nativeConstants as $name => $constant) { $lines[] = $this->getIndent() . $name . ' = ' . $constant->value . ';'; // 注册到 PHP $lines[] = $this->getIndent() . 'php::define("' . $name . '", ' . $name . ');'; } $this->indentLevel--; $code .= $this->genFunction(self::PREFIX . 'init_constant_vars', 'void', [], $lines); // 生成全局变量 $code .= PHP_EOL; $this->indentLevel++; $lines = []; foreach ($this->globalVars as $name => $type) { $lines[] = $this->getIndent() . $name . ' = php::global("' . $name . '");'; } $this->indentLevel--; $code .= $this->genFunction(self::PREFIX . 'init_global_vars', 'void', [], $lines); // 销毁全局变量 $code .= PHP_EOL; $this->indentLevel++; $lines = []; foreach ($this->globalVars as $name => $type) { $lines[] = $this->getIndent() . $name . '.unset();'; } foreach ($this->nativeConstants as $name => $constant) { if ($constant->type !== self::TYPE_VAR) { continue; } $lines[] = $this->getIndent() . $name . '.unset();'; } $this->indentLevel--; $code .= $this->genFunction(self::PREFIX . 'unset_global_vars', 'void', [], $lines); $this->writeFile($file, $code); $this->formatCppCode($file); } public function compileFile($file): void { $cmd = $this->cppCompiler . ' -c ' . $file . ' -o ' . $file . '.o '; $this->addCompilationOption($cmd); $this->climate->comment($cmd); shell_exec($cmd); } public function compileBinary(string $targetFile, array $objectFiles): void { if ($this->climate->arguments->defined('output')) { $targetFile = $this->climate->arguments->get('output'); } $objectList = implode(' ', $objectFiles); $linkCmd = $this->cppCompiler . ' ' . $objectList . ' -o ' . $targetFile . ' ' . $this->parseLdflags() . $this->parseLibs(); $this->addCompilationOption($linkCmd); $this->climate->comment($linkCmd); shell_exec($linkCmd); } public function genFunctionDeclaration(string $file): void { $code = '#include ' . PHP_EOL; /** * @var FunctionDef $func */ foreach ($this->nativeFunctions as $name => $func) { $code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '('; $argInfoList = $func->argInfoList; if ($argInfoList) { $list = []; foreach ($argInfoList as $argInfo) { $arg = $argInfo->type . ' ' . $argInfo->name; if ($argInfo->default) { $arg .= ' = ' . $argInfo->default; } $list[] = $arg; } $code .= implode(', ', $list); } $code .= ');' . PHP_EOL; } $code .= PHP_EOL; foreach ($this->nativeConstants as $name => $constant) { $code .= 'extern ' . $constant->type . ' ' . $name . ';' . PHP_EOL; } $this->writeFile($file, $code); } protected function getMethodName(Node\Stmt\ClassMethod $v): string { return strtolower($this->parseIdentifier($v->name)); } protected function parseClass(Node\Stmt\Class_ $class): string { $this->class = $this->parseIdentifier($class->name); if (!$this->stubFileIncluded) { shell_exec('php ' . $this->rootPath . '/bin/gen_stub.php -f' . $this->file); $stubFilenameWithoutExtension = str_replace([".stub.php", '.php'], "", $this->file); $this->localHeaders[] = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); $this->stubFileIncluded = true; } $this->classDef = new ClassDef(); $this->classDef->name = $this->class; $methodCodes = []; foreach ($class->stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_ClassConst': $this->parseClassConstDef($v); break; case 'Stmt_Property': $this->parsePropertyDef($v); break; case 'Stmt_ClassMethod': $this->parseClassMethod($v, $methodCodes); break; default: abort($v); } } $code = $this->genZendClass($methodCodes); $this->class = ''; return $code; } public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string { $basename = basename($stubFilenameWithoutExtension); $absPath = $this->getIncludeDir() . "/{$basename}_arginfo.h"; if ($relative) { return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); } else { return $absPath; } } protected function genZendClass($methodCodes): string { $code = ''; $classDef = $this->classDef; foreach ($classDef->methods as $method) { $code .= $methodCodes[$method->name] . PHP_EOL; } return $code; } private function genClassNative(): string { $code = 'class ' . $this->class . ' { '; $publicMethods = []; $protectedMethods = []; $privateMethods = []; $publicConstants = []; $protectedConstants = []; $privateConstants = []; $publicProperties = []; $protectedProperties = []; $privateProperties = []; foreach ($this->classDef->constants as $const) { if ($const->flags & Modifiers::PUBLIC) { $publicConstants[] = $const; } if ($const->flags & Modifiers::PROTECTED) { $protectedConstants[] = $const; } if ($const->flags & Modifiers::PRIVATE) { $privateConstants[] = $const; } } foreach ($this->classDef->methods as $method) { if ($method->flags & Modifiers::PUBLIC) { $publicMethods[] = $method; } if ($method->flags & Modifiers::PROTECTED) { $protectedMethods[] = $method; } if ($method->flags & Modifiers::PRIVATE) { $privateMethods[] = $method; } } foreach ($this->classDef->properties as $property) { if ($property->flags & Modifiers::PUBLIC) { $publicProperties[] = $property; } if ($property->flags & Modifiers::PROTECTED) { $protectedProperties[] = $property; } if ($property->flags & Modifiers::PRIVATE) { $privateProperties[] = $property; } } if ($privateConstants) { $code .= 'private:' . PHP_EOL; $code .= $this->genClassConstantList($privateConstants); } if ($protectedConstants) { $code .= 'protected:' . PHP_EOL; $code .= $this->genClassConstantList($protectedConstants); } if ($publicConstants) { $code .= 'public:' . PHP_EOL; $code .= $this->genClassConstantList($publicConstants); } if ($privateProperties) { $code .= 'private:' . PHP_EOL; $code .= $this->genClassPropertyList($privateProperties); } if ($protectedProperties) { $code .= 'protected:' . PHP_EOL; $code .= $this->genClassPropertyList($protectedProperties); } if ($publicProperties) { $code .= 'public:' . PHP_EOL; $code .= $this->genClassPropertyList($publicProperties); } $code .= '};' . PHP_EOL . PHP_EOL; return $code; } public function genIncludeHeaderFiles(): string { $headers = array_merge($this->globalHeaders, $this->localHeaders); $lines = []; foreach ($headers as $header) { $lines[] = '#include <' . $header . '>'; } return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; } /** * @param array $list */ protected function genClassConstantList(array $list): string { $code = ''; foreach ($list as $const) { $code .= $this->getIndent() . $this->genClassConstant($const); } return $code; } protected function genClassConstant(ConstantDef $const): string { return 'static const ' . $const->type . ' ' . $const->name . ';' . PHP_EOL; } /** * @param array $list */ protected function genClassPropertyList(array $list): string { $code = ''; foreach ($list as $prop) { $code .= $this->getIndent() . $this->genClassProperty($prop); } return $code; } protected function genClassProperty(PropertyDef $prop): string { $code = $prop->type . ' ' . $prop->name; if ($prop->default) { $code .= ' = ' . $prop->default; } return $code . ';' . PHP_EOL; } protected function genFunction(string $name, string $returnType, array $args = [], array $lines = []): string { $_args = []; foreach ($args as $arg => $type) { $_args[] = $type . ' ' . $arg; } $code = $returnType . ' ' . $name . '(' . implode(', ', $_args) . ') {' . PHP_EOL; $code .= implode(PHP_EOL, $lines) . PHP_EOL; $code .= '}' . PHP_EOL; return $code; } protected function parseClassConstDef(Node\Stmt\ClassConst $v): void { $flags = $v->flags; $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; foreach ($v->consts as $const) { $constInfo = new ConstantDef($this->parseIdentifier($const->name), $flags, $type, $this->parseIdentifier($const->value)); $this->classDef->constants[] = $constInfo; } } protected function parsePropertyDef(Node\Stmt\Property $v): void { $flags = $v->flags; $type = $v->type ? $this->getTypeFromZendType($this->parseIdentifier($v->type)) : self::TYPE_VAR; foreach ($v->props as $prop) { $propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type); if ($prop->default) { $propDef->default = $this->parseIdentifier($prop->default); } $this->classDef->properties[] = $propDef; } } private function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void { $name = $this->getMethodName($v); $flags = $v->flags; $methodDef = new MethodDef($name, $flags); $this->classDef->methods[] = $methodDef; $methodCodes[$name] = $this->parseFunction($v); } }