/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.as.codegen; import static org.apache.flex.abc.ABCConstants.*; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.ABCEmitter; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.MethodBodyInfo; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.PooledValue; import org.apache.flex.abc.visitors.IMethodBodyVisitor; import org.apache.flex.abc.visitors.IMethodVisitor; import org.apache.flex.abc.visitors.IScriptVisitor; import org.apache.flex.abc.visitors.IVisitor; import org.apache.flex.compiler.exceptions.BURMAbortException; import org.apache.flex.compiler.exceptions.CodegenInterruptedException; import org.apache.flex.compiler.exceptions.MissingBuiltinException; import org.apache.flex.compiler.internal.definitions.FunctionDefinition; import org.apache.flex.compiler.internal.definitions.ParameterDefinition; import org.apache.flex.compiler.internal.definitions.TypeDefinitionBase; import org.apache.flex.compiler.internal.embedding.EmbedData; import org.apache.flex.compiler.internal.semantics.SemanticUtils; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.units.EmbedCompilationUnitFactory; import org.apache.flex.compiler.internal.units.requests.ABCBytesRequestResult; import org.apache.flex.compiler.problems.CodegenInternalProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.MissingBuiltinProblem; import org.apache.flex.compiler.problems.NonConstantParamInitializerProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IFileNodeAccumulator; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.tree.as.IParameterNode; import org.apache.flex.compiler.units.requests.IABCBytesRequestResult; import com.google.common.util.concurrent.Futures; /** * ABCGenerator is the public interface to the code generator. */ public class ABCGenerator implements ICodeGenerator { private static boolean DEFINITION_NORMALIZATION_DISABLED = false; @Override public ABCBytesRequestResult generate (String synthetic_name_prefix, IASNode root_node, ICompilerProject project) throws InterruptedException { return generate(null, false, synthetic_name_prefix, root_node, project, DEFINITION_NORMALIZATION_DISABLED, Collections.<String, String>emptyMap()); } @Override public ABCBytesRequestResult generate(ExecutorService executorService, boolean useParallelCodegen, String synthetic_name_prefix, IASNode root_node, ICompilerProject project, boolean inInvisibleCompilationUnit, Map<String, String> encodedDebugFiles) throws InterruptedException { // Set up the global lexical scope. final GlobalLexicalScope global_scope = new GlobalLexicalScope( project, this, synthetic_name_prefix, inInvisibleCompilationUnit, useParallelCodegen, encodedDebugFiles); final ABCEmitter emitter = (ABCEmitter)global_scope.getEmitter(); // CG targets the latest version - these ABCs can be postprocessed to downgrade to previous versions emitter.visit(ABCConstants.VERSION_ABC_MAJOR_FP10, ABCConstants.VERSION_ABC_MINOR_FP10); IScriptVisitor sv = emitter.visitScript(); sv.visit(); MethodInfo init_method = new MethodInfo(); sv.visitInit(init_method); MethodBodyInfo init_body = new MethodBodyInfo(); init_body.setMethodInfo(init_method); IMethodVisitor mv = emitter.visitMethod(init_method); mv.visit(); IMethodBodyVisitor mbv = mv.visitBody(init_body); mbv.visit(); global_scope.traitsVisitor = sv.visitTraits(); global_scope.setMethodInfo(init_method); global_scope.methodBodyVisitor = mbv; global_scope.setInitialControlFlowRegionNode(root_node); // Process global directives. GlobalDirectiveProcessor top_level_processor = new GlobalDirectiveProcessor(executorService, useParallelCodegen, global_scope, emitter); boolean fatal_error_encountered = false; try { top_level_processor.traverse(root_node); } catch (MissingBuiltinException e) { global_scope.addProblem(new MissingBuiltinProblem(root_node, e.getBuiltinName())); fatal_error_encountered = true; } catch (CodegenInterruptedException e) { // Unwrap the InterruptedException and rethrow it. throw e.getException(); } top_level_processor.finish(); byte[] generatedBytes = IABCBytesRequestResult.ZEROBYTES; if ( !fatal_error_encountered ) { // Initialize the init script. InstructionList script_init_insns = new InstructionList(); script_init_insns.addInstruction(OP_getlocal0); script_init_insns.addInstruction(OP_pushscope); script_init_insns.addAll(global_scope.getInitInstructions()); script_init_insns.addAll(top_level_processor.directiveInsns); if ( script_init_insns.canFallThrough() || script_init_insns.hasPendingLabels() ) script_init_insns.addInstruction(OP_returnvoid); // Allocate temps beginning with register 1, // register 0 is reserved for "this" global. global_scope.initializeTempRegisters(1); // Make any vistEnd method calls // that were deferred. // callVisitEnds must be called on the same thread // that original called ABCGenerator.generate ( this method ). global_scope.callVisitEnds(); mbv.visitInstructionList(script_init_insns); mbv.visitEnd(); mv.visitEnd(); global_scope.traitsVisitor.visitEnd(); sv.visitEnd(); try { generatedBytes = emitter.emit(); } catch ( Exception e ) { global_scope.addProblem(new CodegenInternalProblem(root_node, e)); } } Set<EmbedData> embeds = global_scope.getEmbeds(); EmbedCompilationUnitFactory.collectEmbedDatas(project, (IFileNodeAccumulator)root_node, embeds, global_scope.getProblems()); ICompilerProblem[] problemsArray = global_scope.getProblems().toArray(IABCBytesRequestResult.ZEROPROBLEMS); return new ABCBytesRequestResult(generatedBytes, problemsArray, embeds); } /** * Translate an AST into ABC instructions. * @param subtree - the CM subtree. * @param goal_state - the desired goal state. * One of the nonterminal states in CmcEmitter, * or 0 if you're feeling lucky and are willing * to accept whatever instruction sequence the * BURM decides is optimal. * @param scope - the active lexical scope. * @return a list of ABC instructions. */ public InstructionList generateInstructions (IASNode subtree, int goal_state, LexicalScope scope) { return generateInstructions(subtree, goal_state, scope, null); } /** * Translate an AST into ABC instructions. * @param subtree - the CM subtree. * @param goal_state - the desired goal state. * One of the nonterminal states in CmcEmitter, * or 0 if you're feeling lucky and are willing * to accept whatever instruction sequence the * BURM decides is optimal. * @param scope - the active lexical scope. * @param instance_init_insns - a list of instance * initialization instructions collected outside * a constructor body that must be included in the * constructor. * @post if instance_init_insns is not null then the * method will have been processed as and marked * as a constructor. * @return a list of ABC instructions. */ public InstructionList generateInstructions(IASNode subtree, int goal_state, LexicalScope scope, InstructionList instance_init_insns) { CmcEmitter burm = new CmcEmitter(); burm.reducer = new ABCGeneratingReducer(); burm.reducer.setCurrentscope(scope); burm.reducer.setInstanceInitializers(instance_init_insns); try { burm.burm(subtree, goal_state); return ((InstructionList)burm.getResult()); } catch ( Exception cant_reduce) { handleBurmError(burm, subtree, cant_reduce, scope); return new InstructionList(); } } /** * Generate code for a function declaration, and put its initialization code * on the relevant instruction list. * <p> * Post condition: if instance_init_insns is not null then the method will * have been processed as and marked as a constructor. * * @param func the function declaration node. * @param enclosing_scope the lexical scope in which the function was * defined. * @param instance_init_insns a list of instance initialization instructions * collected outside a constructor body that must be included in the * constructor. * @return {@link MethodInfo} created for the function. */ public MethodInfo generateFunction (FunctionNode func, LexicalScope enclosing_scope, InstructionList instance_init_insns, Name alternate_name) { MethodInfo mi = createMethodInfo(enclosing_scope, func, alternate_name); if (mi.isNative()) { generateNativeMethod(func, mi, enclosing_scope); } else { generateMethodBodyForFunction(mi, func, enclosing_scope, instance_init_insns); } func.discardFunctionBody(); return mi; } /** * Generate code for a function declaration, using a background thread * provided by the specified {@link ExecutorService}. * * @param executorService {@link ExecutorService} used to do work in other * threads. * @param func the function declaration node. * @param enclosing_scope the lexical scope in which the function was * defined. * @return {@link GenerateFunctionInParallelResult} which can be used to * wait for code generation of the specified function to complete and to * extract the {@link MethodInfo} created for the specified function. The * {@link MethodInfo} may be extracted immediately after this method * completes ( you don't have to wait for code generation of the specified * function complete ). */ public GenerateFunctionInParallelResult generateFunctionInParallel (ExecutorService executorService, FunctionNode func, LexicalScope enclosing_scope) { MethodInfo mi = createMethodInfo(enclosing_scope, func, null); if (mi.isNative()) { generateNativeMethod(func, mi, enclosing_scope); return new GenerateFunctionInParallelResult(Futures.immediateFuture(null), mi, Collections.<IVisitor>emptyList()); } GenerateFunctionRunnable runnable = new GenerateFunctionRunnable(mi, func, enclosing_scope); Future<?> future = executorService.submit(runnable); return new GenerateFunctionInParallelResult(future, mi, runnable.getDeferredVisitEndsList()); } /** * Helper method used by <code>generateFunction()</code>. * @param func - the function declaration node. * @param mi - the MethodInfo describing the signature * @param enclosing_scope - the lexical scope * in which the handler method is autogenerated. */ void generateNativeMethod(FunctionNode func, MethodInfo mi, LexicalScope enclosing_scope) { enclosing_scope.getMethodBodySemanticChecker().checkNativeMethod(func); // don't need to create a new scope, so just use the enclosing scope // to get a handle to the emitter IMethodVisitor mv = enclosing_scope.getEmitter().visitMethod(mi); // Just visit the method info. Do NOT generate a body // for native methods mv.visit(); // For non native methods, the return type is set by the burm, // but for native types, as the burm isn't run, we need to set // the return type here. String returnType = func.getReturnType(); mi.setReturnType(new Name(returnType)); mv.visitEnd(); } /** * Helper method used by <code>generateFunction()</code> (and also by * <code>generateEventHandler()</code> in MXMLDocumentDirectiveProcessor). * @param mi - the MethodInfo describing the signature * @param node - the FunctionNode or MXMLEventSpecifierNode. * @param enclosing_scope - the lexical scope * in which the handler method is autogenerated. * @param instance_init_insns - a list of instance * initialization instructions collected outside * a constructor body that must be included in the * constructor. * @post if instance_init_insns is not null then the * method will have been processed as and marked * as a constructor. */ public void generateMethodBodyForFunction(MethodInfo mi, IASNode node, LexicalScope enclosing_scope, InstructionList instance_init_insns) { List<IVisitor> deferredVisitEnds = new LinkedList<IVisitor>(); generateMethodBodyForFunction(deferredVisitEnds, mi, node, enclosing_scope, instance_init_insns); for (IVisitor v : deferredVisitEnds) v.visitEnd(); } private void generateMethodBodyForFunction(List<IVisitor> deferredVisitEnds, MethodInfo mi, IASNode node, LexicalScope enclosing_scope, InstructionList instance_init_insns) { assert node != null; final boolean is_constructor = SemanticUtils.isInConstructor(node); // Set up a lexical scope for this function. LexicalScope function_scope = enclosing_scope.pushFrame(); // If instance_init_insns is not null, then the new // scope needs to assume ownership of the initializers' // data, such as hasnext2 instruction initializers. if ( instance_init_insns != null ) { function_scope.transferInitializerData(); } IMethodVisitor mv = function_scope.getEmitter().visitMethod(mi); mv.visit(); MethodBodyInfo mbi = new MethodBodyInfo(); mbi.setMethodInfo(mi); IMethodBodyVisitor mbv = mv.visitBody(mbi); mbv.visit(); function_scope.methodBodyVisitor = mbv; function_scope.traitsVisitor = mbv.visitTraits(); function_scope.setMethodInfo(mi); if ( is_constructor ) function_scope.getMethodBodySemanticChecker().enterConstructor(); InstructionList insns = null; if (node instanceof IFunctionNode) function_scope.setInitialControlFlowRegionNode(((IFunctionNode)node).getScopedNode()); else function_scope.setInitialControlFlowRegionNode((IASNode)node); // If we are passed a single node, generate its instructions insns = generateInstructions((IASNode)node, CmcEmitter.__function_NT, function_scope, instance_init_insns); if ( is_constructor ) function_scope.getMethodBodySemanticChecker().leaveConstructor(); assert insns != null; // Make any vistEnd method calls // that were deferred. // callVisitEnds must be called on the same thread // that started code generation. function_scope.addVisitEndsToList(deferredVisitEnds); mbv.visitInstructionList(insns); deferredVisitEnds.add(function_scope.traitsVisitor); deferredVisitEnds.add(mbv); deferredVisitEnds.add(mv); // Perform semantic checks that require the control flow graph. function_scope.getMethodBodySemanticChecker().checkControlFlow( node, mi, mbi ); } /** * Helper method used by mxml databinding codegen to emit an anonymous * function used by an mxml data binding destination function. Example: * <p> * If the expression node is a.b.c, this method will generate a funtion * whose source would look something like this: * * <pre> * function (arg:*):void { a.b.c = arg; } * </pre> * * @param mi - the MethodInfo describing the signature * @param setterExpression {@link IExpressionNode} that is the destination * expression of a mxml data binding. * @param enclosing_scope {@link LexicalScope} for the class initializer * that encloses the function being generated. */ public void generateMXMLDataBindingSetterFunction (MethodInfo mi, IExpressionNode setterExpression, LexicalScope enclosing_scope) { IMethodVisitor methodVisitor = enclosing_scope.getEmitter().visitMethod(mi); methodVisitor.visit(); MethodBodyInfo methodBodyInfo = new MethodBodyInfo(); methodBodyInfo.setMethodInfo(mi); IMethodBodyVisitor methodBodyVisitor = methodVisitor.visitBody(methodBodyInfo); methodBodyVisitor.visit(); // Set up a lexical scope for this function. LexicalScope function_scope = enclosing_scope.pushFrame(); function_scope.methodBodyVisitor = methodBodyVisitor; function_scope.traitsVisitor = methodBodyVisitor.visitTraits(); function_scope.setMethodInfo(mi); InstructionList functionBody; if (setterExpression instanceof InstructionListNode) functionBody = ((InstructionListNode)setterExpression).getInstructions(); else functionBody = generateInstructions(setterExpression, CmcEmitter.__mxml_data_binding_setter_expression_NT, function_scope, null); functionBody.addInstruction(OP_returnvoid); methodBodyVisitor.visitInstructionList(functionBody); methodBodyVisitor.visitEnd(); methodVisitor.visitEnd(); } /** * Helper method used by databinding codegen to emit an anonymous function * based on a list of {@link IExpressionNode}'s. This method emits a * function that contains code that evaluates each expression in the list * and adds the expressions together with {@link ABCConstants#OP_add}. * * @param mi - the MethodInfo describing the signature * @param nodes - a {@link List} of {@link IExpressionNode}'s to be * codegen'd. * @param enclosing_scope {@link LexicalScope} for the class initializer * that encloses the function being generated. */ public void generateMXMLDataBindingGetterFunction (MethodInfo mi, List<IExpressionNode> nodes, LexicalScope enclosing_scope) { IMethodVisitor methodVisitor = enclosing_scope.getEmitter().visitMethod(mi); methodVisitor.visit(); MethodBodyInfo methodBodyInfo = new MethodBodyInfo(); methodBodyInfo.setMethodInfo(mi); IMethodBodyVisitor methodBodyVisitor = methodVisitor.visitBody(methodBodyInfo); methodBodyVisitor.visit(); // Set up a lexical scope for this function. LexicalScope function_scope = enclosing_scope.pushFrame(); function_scope.methodBodyVisitor = methodBodyVisitor; function_scope.traitsVisitor = methodBodyVisitor.visitTraits(); function_scope.setMethodInfo(mi); InstructionList functionBody = null; // for a list of nodes, generate all their instructions and add the results together. // typically we are doing this to concatenate strings for (IExpressionNode expressionNode : nodes) { InstructionList instructionsForExpression = generateInstructions(expressionNode, CmcEmitter.__expression_NT, function_scope, null); if (functionBody == null) { // First one in the list makes a new IL and puts // instructions into it functionBody = instructionsForExpression; } else { // successive children generate into the same IL, then add the results functionBody.addAll(instructionsForExpression); functionBody.addInstruction(OP_add); } } functionBody.addInstruction(OP_returnvalue); methodBodyVisitor.visitInstructionList(functionBody); function_scope.traitsVisitor.visitEnd(); methodBodyVisitor.visitEnd(); methodVisitor.visitEnd(); } /** * Creates a MethodInfo specifying the signature of a method * declared by a FunctionNode. * @param func - A FunctionNode representing a method declaration. * @return The MethodInfo specifying the signature of the method. */ @Override public MethodInfo createMethodInfo (LexicalScope scope, FunctionNode func, Name alternate_name) { return createMethodInfoWithOptionalDefaultArgumentValues(scope, func, false, alternate_name); } /** ** * Creates a MethodInfo specifying the signature of a method * declared by a FunctionNode, and adds in the information for any * default argument values. * * @param func - A FunctionNode representing a method declaration. * @return The MethodInfo specifying the signature of the method. * * Will generate a compiler problem is the default value is bad */ @Override public MethodInfo createMethodInfoWithDefaultArgumentValues (LexicalScope scope, FunctionNode func) { return createMethodInfoWithOptionalDefaultArgumentValues(scope, func, true, null); } private static MethodInfo createMethodInfoWithOptionalDefaultArgumentValues(LexicalScope scope, FunctionNode func, boolean addDefalutValues, Name alternate_name) { MethodInfo mi = new MethodInfo(); mi.setMethodName(alternate_name != null ? alternate_name.getBaseName() : func.getName()); FunctionDefinition funcDef = func.getDefinition(); // Marshal the function's arguments. ParameterDefinition[] args = funcDef.getParameters(); List<String> param_names = new ArrayList<String>(); ICompilerProject project = scope.getProject(); if ( args.length > 0 ) { Vector<Name> method_args = new Vector<Name>(); for ( ParameterDefinition arg: args) { TypeDefinitionBase arg_type = (TypeDefinitionBase)arg.resolveType(project); Name type_name = arg_type != null ? arg_type.getMName(project) : null; if ( arg.isRest() ) { mi.setFlags( (byte)(mi.getFlags() | ABCConstants.NEED_REST) ); } else { method_args.add(type_name); param_names.add(arg.getBaseName()); } // If appropriate, tell the MethodInfo about the default parameter value if (addDefalutValues && arg.hasDefaultValue()) { Object initValue = arg.resolveInitialValue(project); // init value might resolve to null, if the source code is bad if (initValue == null) { IParameterNode paramNode = arg.getNode(); scope.addProblem(new NonConstantParamInitializerProblem(paramNode.getAssignedValueNode())); // re-write non-constant expression to undefined, so resulting ABC will pass the verifier. initValue = ABCConstants.UNDEFINED_VALUE; } mi.addDefaultValue(new PooledValue(initValue)); } } mi.setParamTypes(method_args); mi.setParamNames(param_names); } // check for native modifier if ( func.getDefinition().isNative() ) { mi.setFlags( (byte)(mi.getFlags() | ABCConstants.NATIVE) ); } // The return type will be set by the BURM. return mi; } /** * Helper method to expose the constant folding code to clients outside of the burm, such * as org.apache.flex.compiler.internal.as.definitions.ConstantDefinition. * @param subtree the tree to generate a constant value for * @param project the project to use to evaluate the tree * @return the constant value for the subtree, or null if a constant value can't be determined */ @Override public IConstantValue generateConstantValue (IASNode subtree, ICompilerProject project) { IConstantValue result = null; LexicalScope scope = new GlobalLexicalScope(project, this); if ( subtree != null ) { try { Object value = reduceSubtree(subtree, scope, CmcEmitter.__constant_value_NT); result = new ConstantValue(value, scope.getProblems()); } catch ( Exception cant_reduce) { // Can't generate a constant value, just return null } } return result; } /** * Reduce an AST to its equivalent ABC structures. * @param subtree - the root of the AST subtree. * May be null, in which case this routine returns null. * @param scope - the active LexicalScope. * @param goal - the BURM's goal state. One of the CmcEmitter.__foo_NT constants. * @return the result of reducing the subtree to the desired goal state, * or null if the input subtree was null. * @throws Exception from the BURM if the computation didn't succeed or was interrupted. */ public Object reduceSubtree (IASNode subtree, LexicalScope scope, int goal) throws Exception { CmcEmitter burm = new CmcEmitter(); burm.reducer = new ABCGeneratingReducer(); burm.reducer.setCurrentscope(scope); burm.burm(subtree, CmcEmitter.__constant_value_NT); return burm.getResult(); } /** * Handle an error from a BURM: emit diagnostics and bump the error count. * @param n - the subtree that was to be reduced. * @param ex - the exception. */ private void handleBurmError(CmcEmitter burm, IASNode n, Exception ex, LexicalScope scope) { if ( ex instanceof CodegenInterruptedException ) { // If the exception is an InterruptedException, do nothing, as not // a real error. The incremental flow kicked in and interrupted // the current work, so just throw away the current work and carry // on our merry way. // No problem should be reported in this case. scope.getProblems().clear(); return; } else if ( ! ( ex instanceof BURMAbortException ) ) { scope.addProblem( new CodegenInternalProblem(n, ex) ); } DumpBURMState.dump(burm, n); } /** * Implementation of {@link Runnable} that is used to code generate a * function on a background thread. */ private class GenerateFunctionRunnable implements Runnable { GenerateFunctionRunnable(MethodInfo methodInfo, FunctionNode func, LexicalScope enclosing_scope) { this.methodInfo = methodInfo; this.functionNode = func; this.enclosingScope = enclosing_scope; this.deferredVisitEnds = new LinkedList<IVisitor>(); } private final MethodInfo methodInfo; private final FunctionNode functionNode; private final LexicalScope enclosingScope; private final List<IVisitor> deferredVisitEnds; @Override public void run() { assert !methodInfo.isNative() : "Native methods should be handled in the main thread and not be dispatched to a background thread!"; functionNode.parseFunctionBody(enclosingScope.getProblems()); generateMethodBodyForFunction(deferredVisitEnds, methodInfo, functionNode, enclosingScope, null); } public List<IVisitor> getDeferredVisitEndsList() { return deferredVisitEnds; } } /** * Get an ICodeGeneratorFactory that will always return the same ABCGenerator instance */ public static ICodeGeneratorFactory getABCGeneratorFactory() { return new ICodeGeneratorFactory() { public ICodeGenerator get () { return new ABCGenerator(); } }; } /** * Represents the result of {@link #generateConstantValue}(}. * <p> * In addition to producing the constant value itself, * the constant reduction process can also produce compiler problems. */ public static final class ConstantValue implements IConstantValue { public ConstantValue(Object value, Collection<ICompilerProblem> problems) { this.value = value; this.problems = problems; } private final Object value; private final Collection<ICompilerProblem> problems; @Override public Object getValue() { return value; } @Override public Collection<ICompilerProblem> getProblems() { return problems; } } }