/**
* Compiles the method
*
* @param CompilationContext $compilationContext
* @return null
* @throws CompilerException
*/
public function compile(CompilationContext $compilationContext)
{
/**
* Set the method currently being compiled
*/
$compilationContext->currentMethod = $this;
/**
* Initialize the method warm-up to null
*/
$compilationContext->methodWarmUp = null;
/**
* Assign pre-made compilation passses
*/
$localContext = $this->localContext;
$typeInference = $this->typeInference;
$callGathererPass = $this->callGathererPass;
/**
* Every method has its own symbol table
*/
$symbolTable = new SymbolTable($compilationContext);
if ($localContext) {
$symbolTable->setLocalContext($localContext);
}
/**
* Parameters has an additional extra mutation
*/
$parameters = $this->parameters;
if ($localContext) {
if (is_object($parameters)) {
foreach ($parameters->getParameters() as $parameter) {
$localContext->increaseMutations($parameter['name']);
}
}
}
/**
* Initialization of parameters happens in a fictitious external branch
*/
$branch = new Branch();
$branch->setType(Branch::TYPE_EXTERNAL);
/**
* BranchManager helps to create graphs of conditional/loop/root/jump branches
*/
$branchManager = new BranchManager();
$branchManager->addBranch($branch);
/**
* Cache Manager manages function calls, method calls and class entries caches
*/
$cacheManager = new CacheManager();
$cacheManager->setGatherer($callGathererPass);
$compilationContext->branchManager = $branchManager;
$compilationContext->cacheManager = $cacheManager;
$compilationContext->typeInference = $typeInference;
$compilationContext->symbolTable = $symbolTable;
$oldCodePrinter = $compilationContext->codePrinter;
/**
* Change the code printer to a single method instance
*/
$codePrinter = new CodePrinter();
$compilationContext->codePrinter = $codePrinter;
/**
* Set an empty function cache
*/
$compilationContext->functionCache = null;
/**
* Reset try/catch and loop counter
*/
$compilationContext->insideCycle = 0;
$compilationContext->insideTryCatch = 0;
$compilationContext->currentTryCatch = 0;
if (is_object($parameters)) {
/**
* Round 1. Create variables in parameters in the symbol table
*/
$classCastChecks = array();
$substituteVars = array();
foreach ($parameters->getParameters() as $parameter) {
/**
* Change dynamic variables to low level types
*/
if ($typeInference) {
if (isset($parameter['data-type'])) {
if ($parameter['data-type'] == 'variable') {
$type = $typeInference->getInferedType($parameter['name']);
if (is_string($type)) {
/* promote polymorphic parameters to low level types */
}
}
} else {
$type = $typeInference->getInferedType($parameter['name']);
if (is_string($type)) {
/* promote polymorphic parameters to low level types */
}
}
}
$symbolParam = null;
if (isset($parameter['data-type'])) {
switch ($parameter['data-type']) {
case 'object':
case 'callable':
case 'resource':
case 'variable':
$symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext);
/* TODO: Move this to the respective backend, which requires refactoring how this works */
if ($compilationContext->backend->isZE3()) {
$symbol->setIsDoublePointer(true);
$substituteVars[$parameter['name']] = $symbolTable->addVariable('variable', $parameter['name'] . '_sub', $compilationContext);
}
break;
default:
$symbol = $symbolTable->addVariable($parameter['data-type'], $parameter['name'], $compilationContext);
$symbolParam = $symbolTable->addVariable('variable', $parameter['name'] . '_param', $compilationContext);
/* TODO: Move this to the respective backend, which requires refactoring how this works */
if ($compilationContext->backend->isZE3()) {
$symbolParam->setIsDoublePointer(true);
}
if ($parameter['data-type'] == 'string' || $parameter['data-type'] == 'array') {
$symbol->setMustInitNull(true);
}
break;
}
} else {
$symbol = $symbolTable->addVariable('variable', $parameter['name'], $compilationContext);
}
/* ZE3 only */
if (isset($substituteVars[$parameter['name']])) {
$substituteVar = $substituteVars[$parameter['name']];
$substituteVar->increaseUses();
}
/**
* Some parameters can be read-only
*/
if (isset($parameter['const']) && $parameter['const']) {
$symbol->setReadOnly(true);
if (is_object($symbolParam)) {
$symbolParam->setReadOnly(true);
}
}
if (is_object($symbolParam)) {
/**
* Parameters are marked as 'external'
*/
$symbolParam->setIsExternal(true);
/**
* Assuming they're initialized
*/
$symbolParam->setIsInitialized(true, $compilationContext, $parameter);
/**
* Initialize auxiliar parameter zvals to null
*/
$symbolParam->setMustInitNull(true);
/**
* Increase uses
*/
$symbolParam->increaseUses();
} else {
if (isset($parameter['default'])) {
if (isset($parameter['data-type'])) {
if ($parameter['data-type'] == 'variable') {
$symbol->setMustInitNull(true);
}
} else {
$symbol->setMustInitNull(true);
}
}
}
/**
* Original node where the variable was declared
*/
$symbol->setOriginal($parameter);
/**
* Parameters are marked as 'external'
*/
$symbol->setIsExternal(true);
/**
* Assuming they're initialized
*/
$symbol->setIsInitialized(true, $compilationContext, $parameter);
/**
* Variables with class/type must be objects across the execution
*/
if (isset($parameter['cast'])) {
$symbol->setDynamicTypes('object');
$symbol->setClassTypes($compilationContext->getFullName($parameter['cast']['value']));
$classCastChecks[] = array($symbol, $parameter);
} else {
if (isset($parameter['data-type'])) {
if ($parameter['data-type'] == 'variable') {
$symbol->setDynamicTypes('undefined');
}
} else {
$symbol->setDynamicTypes('undefined');
}
}
}
}
$compilationContext->backend->onPreCompile($this, $compilationContext);
/**
* Compile the block of statements if any
*/
if (is_object($this->statements)) {
if ($this->hasModifier('static')) {
$compilationContext->staticContext = true;
} else {
$compilationContext->staticContext = false;
}
/**
* Compile the statements block as a 'root' branch
*/
$this->statements->compile($compilationContext, false, Branch::TYPE_ROOT);
}
/**
* Initialize variable default values
*/
$initVarCode = $compilationContext->backend->initializeVariableDefaults($symbolTable->getVariables(), $compilationContext);
/**
* Fetch parameters from vm-top
*/
$initCode = "";
$code = "";
if (is_object($parameters)) {
/**
* Round 2. Fetch the parameters in the method
*/
$params = array();
$requiredParams = array();
$optionalParams = array();
$numberRequiredParams = 0;
$numberOptionalParams = 0;
foreach ($parameters->getParameters() as $parameter) {
if (isset($parameter['data-type'])) {
$dataType = $parameter['data-type'];
} else {
$dataType = 'variable';
}
switch ($dataType) {
case 'object':
case 'callable':
case 'resource':
case 'variable':
if (!$this->isInternal()) {
$params[] = '&' . $parameter['name'];
} else {
$params[] = $parameter['name'];
}
break;
default:
if (!$this->isInternal()) {
$params[] = '&' . $parameter['name'] . '_param';
} else {
$params[] = $parameter['name'] . '_param';
}
break;
}
if (isset($parameter['default'])) {
$optionalParams[] = $parameter;
$numberOptionalParams++;
} else {
$requiredParams[] = $parameter;
$numberRequiredParams++;
}
}
/**
* Pass the write detector to the method statement block to check if the parameter
* variable is modified so as do the proper separation
*/
$parametersToSeparate = array();
if (is_object($this->statements)) {
/**
* If local context is not available
*/
if (!$localContext) {
$writeDetector = new WriteDetector();
}
foreach ($parameters->getParameters() as $parameter) {
if (isset($parameter['data-type'])) {
$dataType = $parameter['data-type'];
} else {
$dataType = 'variable';
}
switch ($dataType) {
case 'variable':
case 'string':
case 'array':
case 'resource':
case 'object':
case 'callable':
$name = $parameter['name'];
if (!$localContext) {
if ($writeDetector->detect($name, $this->statements->getStatements())) {
$parametersToSeparate[$name] = true;
}
} else {
if ($localContext->getNumberOfMutations($name) > 1) {
$parametersToSeparate[$name] = true;
}
}
break;
}
}
}
/**
* Initialize required parameters
*/
foreach ($requiredParams as $parameter) {
if (isset($parameter['mandatory'])) {
$mandatory = $parameter['mandatory'];
} else {
$mandatory = 0;
}
if (isset($parameter['data-type'])) {
$dataType = $parameter['data-type'];
} else {
$dataType = 'variable';
}
if ($dataType != 'variable') {
/**
* Assign value from zval to low level type
*/
if ($mandatory) {
$initCode .= $this->checkStrictType($parameter, $compilationContext);
} else {
$initCode .= $this->assignZvalValue($parameter, $compilationContext);
}
}
switch ($dataType) {
case 'variable':
case 'resource':
case 'object':
case 'callable':
if (isset($parametersToSeparate[$parameter['name']])) {
$symbolTable->mustGrownStack(true);
$initCode .= "\t" . "ZEPHIR_SEPARATE_PARAM(" . $parameter['name'] . ");" . PHP_EOL;
}
break;
}
}
/**
* Initialize optional parameters
*/
foreach ($optionalParams as $parameter) {
if (isset($parameter['mandatory'])) {
$mandatory = $parameter['mandatory'];
} else {
$mandatory = 0;
}
if (isset($parameter['data-type'])) {
$dataType = $parameter['data-type'];
} else {
$dataType = 'variable';
}
switch ($dataType) {
case 'object':
case 'callable':
case 'resource':
case 'variable':
$name = $parameter['name'];
break;
default:
$name = $parameter['name'] . '_param';
break;
}
/**
* Assign the default value according to the variable's type
*/
$targetVar = $compilationContext->symbolTable->getVariableForWrite($name, $compilationContext);
$initCode .= "\t" . $compilationContext->backend->ifVariableValueUndefined($targetVar, $compilationContext, false, false) . PHP_EOL;
if ($compilationContext->backend->isZE3()) {
if ($targetVar->isDoublePointer() && isset($substituteVars[$parameter['name']])) {
$substituteVar = $substituteVars[$parameter['name']];
$initCode .= "\t\t" . $targetVar->getName() . ' = &' . $substituteVar->getName() . ';' . PHP_EOL;
}
}
$initCode .= $this->assignDefaultValue($parameter, $compilationContext);
if (isset($parametersToSeparate[$name]) || $dataType != 'variable') {
$initCode .= "\t" . '} else {' . PHP_EOL;
if (isset($parametersToSeparate[$name])) {
$initCode .= "\t\t" . "ZEPHIR_SEPARATE_PARAM(" . $name . ");" . PHP_EOL;
} else {
if ($mandatory) {
$initCode .= $this->checkStrictType($parameter, $compilationContext, $mandatory);
} else {
$initCode .= "\t" . $this->assignZvalValue($parameter, $compilationContext);
}
}
}
$initCode .= "\t" . '}' . PHP_EOL;
}
/**
* Fetch the parameters to zval pointers
*/
$codePrinter->preOutputBlankLine();
if (!$this->isInternal()) {
$compilationContext->headersManager->add('kernel/memory');
if ($symbolTable->getMustGrownStack()) {
$code .= "\t" . 'zephir_fetch_params(1, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL;
} else {
$code .= "\t" . 'zephir_fetch_params(0, ' . $numberRequiredParams . ', ' . $numberOptionalParams . ', ' . join(', ', $params) . ');' . PHP_EOL;
}
} else {
foreach ($params as $param) {
/* TODO: Migrate all this code to codeprinter, get rid of temp code printer */
$tempCodePrinter = new CodePrinter();
$realCodePrinter = $compilationContext->codePrinter;
$compilationContext->codePrinter = $tempCodePrinter;
$paramVar = $compilationContext->symbolTable->getVariableForRead($param, $compilationContext);
$compilationContext->backend->assignZval($paramVar, $param . '_ext', $compilationContext);
$code .= "\t" . $tempCodePrinter->getOutput() . PHP_EOL;
$compilationContext->codePrinter = $realCodePrinter;
}
}
$code .= PHP_EOL;
}
$code .= $initCode . $initVarCode;
$codePrinter->preOutput($code);
/**
* Fetch used superglobals
*/
foreach ($symbolTable->getVariables() as $name => $variable) {
if ($symbolTable->isSuperGlobal($name)) {
$globalVar = $symbolTable->getVariable($name);
$codePrinter->preOutput("\t" . $compilationContext->backend->fetchGlobal($globalVar, $compilationContext, false));
}
}
/**
* Grow the stack if needed
*/
if ($symbolTable->getMustGrownStack()) {
$compilationContext->headersManager->add('kernel/memory');
$codePrinter->preOutput("\t" . 'ZEPHIR_MM_GROW();');
}
/**
* Check if there are unused variables
*/
$usedVariables = array();
$classDefinition = $this->getClassDefinition();
if ($classDefinition) {
$completeName = $classDefinition->getCompleteName();
} else {
$completeName = '[unknown]';
}
foreach ($symbolTable->getVariables() as $variable) {
if ($variable->getNumberUses() <= 0) {
if ($variable->isExternal() == false) {
$compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal());
continue;
}
$compilationContext->logger->warning('Variable "' . $variable->getName() . '" declared but not used in ' . $completeName . '::' . $this->getName(), "unused-variable-external", $variable->getOriginal());
}
if ($variable->getName() != 'this_ptr' && $variable->getName() != 'return_value' && $variable->getName() != 'return_value_ptr') {
$type = $variable->getType();
if (!isset($usedVariables[$type])) {
$usedVariables[$type] = array();
}
$usedVariables[$type][] = $variable;
}
}
/**
* Check if there are assigned but not used variables
* Warn whenever a variable is unused aside from its declaration.
*/
foreach ($symbolTable->getVariables() as $variable) {
if ($variable->isExternal() == true || $variable->isTemporal()) {
continue;
}
if ($variable->getName() == 'this_ptr' || $variable->getName() == 'return_value' || $variable->getName() == 'return_value_ptr' || $variable->getName() == 'ZEPHIR_LAST_CALL_STATUS') {
continue;
}
if (!$variable->isUsed()) {
$node = $variable->getLastUsedNode();
if (is_array($node)) {
$compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $node);
} else {
$compilationContext->logger->warning('Variable "' . $variable->getName() . '" assigned but not used in ' . $completeName . '::' . $this->getName(), "unused-variable", $variable->getOriginal());
}
}
}
if (count($usedVariables)) {
$codePrinter->preOutputBlankLine();
}
/**
* Generate the variable definition for variables used
*/
$initCode = "\t" . implode(PHP_EOL . "\t", $compilationContext->backend->declareVariables($this, $usedVariables, $compilationContext));
if ($initCode) {
$codePrinter->preOutput($initCode);
}
/**
* Finalize the method compilation
*/
if (is_object($this->statements)) {
/**
* If the last statement is not a 'return' or 'throw' we need to
* restore the memory stack if needed
*/
$lastType = $this->statements->getLastStatementType();
if ($lastType != 'return' && $lastType != 'throw' && !$this->hasChildReturnStatementType($this->statements->getLastStatement())) {
if ($symbolTable->getMustGrownStack()) {
$compilationContext->headersManager->add('kernel/memory');
$codePrinter->output("\t" . 'ZEPHIR_MM_RESTORE();');
}
/**
* If a method has return-type hints we need to ensure the last statement is a 'return' statement
*/
if ($this->hasReturnTypes()) {
throw new CompilerException('Reached end of the method without returning a valid type specified in the return-type hints', $this->expression['return-type']);
}
}
}
$compilationContext->backend->onPostCompile($this, $compilationContext);
/**
* Remove macros that grow/restore the memory frame stack if it wasn't used
*/
$code = $this->removeMemoryStackReferences($symbolTable, $codePrinter->getOutput());
/**
* Restore the compilation context
*/
$oldCodePrinter->output($code);
$compilationContext->codePrinter = $oldCodePrinter;
$compilationContext->branchManager = null;
$compilationContext->cacheManager = null;
$compilationContext->typeInference = null;
$codePrinter->clear();
return null;
}