| /* |
| * |
| * 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.royale.compiler.internal.as.codegen; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Vector; |
| import java.util.Stack; |
| |
| import static org.apache.royale.abc.ABCConstants.*; |
| |
| import org.apache.royale.abc.ABCConstants; |
| import org.apache.royale.abc.instructionlist.InstructionList; |
| import org.apache.royale.abc.semantics.ECMASupport; |
| import org.apache.royale.abc.semantics.Instruction; |
| import org.apache.royale.abc.semantics.InstructionFactory; |
| import org.apache.royale.abc.semantics.Label; |
| import org.apache.royale.abc.semantics.MethodInfo; |
| import org.apache.royale.abc.semantics.Name; |
| import org.apache.royale.abc.semantics.Namespace; |
| import org.apache.royale.abc.semantics.NoOperandsInstruction; |
| import org.apache.royale.abc.semantics.Nsset; |
| import org.apache.royale.abc.semantics.PooledValue; |
| import org.apache.royale.compiler.constants.IASLanguageConstants; |
| import org.apache.royale.compiler.constants.IASLanguageConstants.BuiltinType; |
| import org.apache.royale.compiler.definitions.IAccessorDefinition; |
| import org.apache.royale.compiler.definitions.IConstantDefinition; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.ITypeDefinition; |
| import org.apache.royale.compiler.definitions.IVariableDefinition; |
| import org.apache.royale.compiler.definitions.references.INamespaceResolvedReference; |
| import org.apache.royale.compiler.exceptions.CodegenInterruptedException; |
| import org.apache.royale.compiler.exceptions.DuplicateLabelException; |
| import org.apache.royale.compiler.exceptions.UnknownControlFlowTargetException; |
| import org.apache.royale.compiler.internal.semantics.SemanticUtils; |
| import org.apache.royale.compiler.internal.tree.as.BaseVariableNode; |
| import org.apache.royale.compiler.internal.tree.as.VariableExpressionNode; |
| import org.apache.royale.compiler.problems.AmbiguousGotoTargetProblem; |
| import org.apache.royale.compiler.problems.AnyNamespaceCannotBeQualifierProblem; |
| import org.apache.royale.compiler.problems.CallNonFunctionProblem; |
| import org.apache.royale.compiler.problems.CodegenInternalProblem; |
| import org.apache.royale.compiler.problems.DuplicateLabelProblem; |
| import org.apache.royale.compiler.problems.DuplicateNamespaceDefinitionProblem; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.problems.InvalidLvalueProblem; |
| import org.apache.royale.compiler.problems.InvalidNamespaceInitializerProblem; |
| import org.apache.royale.compiler.problems.MultipleSwitchDefaultsProblem; |
| import org.apache.royale.compiler.problems.NonConstantParamInitializerProblem; |
| import org.apache.royale.compiler.problems.PackageCannotBeUsedAsValueProblem; |
| import org.apache.royale.compiler.problems.UnexpectedReturnProblem; |
| import org.apache.royale.compiler.problems.UnknownGotoTargetProblem; |
| import org.apache.royale.compiler.problems.UnknownNamespaceProblem; |
| import org.apache.royale.compiler.problems.UnknownBreakTargetProblem; |
| import org.apache.royale.compiler.problems.UnknownContinueTargetProblem; |
| import org.apache.royale.compiler.problems.VoidTypeProblem; |
| import org.apache.royale.compiler.projects.ICompilerProject; |
| import org.apache.royale.compiler.tree.as.IASNode; |
| import org.apache.royale.compiler.tree.as.IDynamicAccessNode; |
| import org.apache.royale.compiler.tree.as.IBinaryOperatorNode; |
| import org.apache.royale.compiler.tree.as.ICatchNode; |
| import org.apache.royale.compiler.tree.as.IContainerNode; |
| import org.apache.royale.compiler.tree.as.IExpressionNode; |
| import org.apache.royale.compiler.tree.as.IForLoopNode; |
| import org.apache.royale.compiler.tree.as.IFunctionCallNode; |
| import org.apache.royale.compiler.tree.as.IFunctionNode; |
| import org.apache.royale.compiler.tree.as.IIdentifierNode; |
| import org.apache.royale.compiler.tree.as.IImportNode; |
| import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode; |
| import org.apache.royale.compiler.tree.as.IParameterNode; |
| import org.apache.royale.compiler.tree.as.IScopedNode; |
| import org.apache.royale.compiler.tree.as.ITryNode; |
| import org.apache.royale.compiler.tree.as.ITypedExpressionNode; |
| import org.apache.royale.compiler.tree.as.IUnaryOperatorNode; |
| import org.apache.royale.compiler.tree.as.IWhileLoopNode; |
| import org.apache.royale.compiler.tree.as.IWithNode; |
| import org.apache.royale.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind; |
| import org.apache.royale.compiler.tree.as.IOperatorNode.OperatorType; |
| import org.apache.royale.compiler.tree.mxml.IMXMLEventSpecifierNode; |
| import org.apache.royale.compiler.internal.as.codegen.LexicalScope.NestingState; |
| import org.apache.royale.compiler.internal.definitions.AccessorDefinition; |
| import org.apache.royale.compiler.internal.definitions.ClassDefinition; |
| import org.apache.royale.compiler.internal.definitions.DefinitionBase; |
| import org.apache.royale.compiler.internal.definitions.FunctionDefinition; |
| import org.apache.royale.compiler.internal.definitions.GetterDefinition; |
| import org.apache.royale.compiler.internal.definitions.NamespaceDefinition; |
| import org.apache.royale.compiler.internal.definitions.SetterDefinition; |
| import org.apache.royale.compiler.internal.definitions.TypeDefinitionBase; |
| import org.apache.royale.compiler.internal.tree.as.BaseDefinitionNode; |
| import org.apache.royale.compiler.internal.tree.as.BinaryOperatorInNode; |
| import org.apache.royale.compiler.internal.tree.as.DynamicAccessNode; |
| import org.apache.royale.compiler.internal.tree.as.EmbedNode; |
| import org.apache.royale.compiler.internal.tree.as.ExpressionNodeBase; |
| import org.apache.royale.compiler.internal.tree.as.ForLoopNode; |
| import org.apache.royale.compiler.internal.tree.as.FunctionNode; |
| import org.apache.royale.compiler.internal.tree.as.FunctionObjectNode; |
| import org.apache.royale.compiler.internal.tree.as.IdentifierNode; |
| import org.apache.royale.compiler.internal.tree.as.LabeledStatementNode; |
| import org.apache.royale.compiler.internal.tree.as.MemberAccessExpressionNode; |
| import org.apache.royale.compiler.internal.tree.as.NamespaceNode; |
| import org.apache.royale.compiler.internal.tree.as.RegExpLiteralNode; |
| import org.apache.royale.compiler.internal.tree.as.SwitchNode; |
| import org.apache.royale.compiler.internal.tree.as.TernaryOperatorNode; |
| import org.apache.royale.compiler.internal.tree.as.VariableNode; |
| |
| /* |
| * Notes on changing the reducer: |
| * |
| * - Do not add OP_finddefinition, OP_finddefstrict or OP_getlex instructions directly. Instead use |
| * LexicalScope.findProperty() and LexicalScope.getPropertyValue(), as these methods will |
| * ensure the correct instructions are added when inlining a function body where the scope |
| * chain can not be depended on. |
| */ |
| public class ABCGeneratingReducer |
| { |
| /** |
| * A struct for the decoded pieces of a catch block. |
| */ |
| public static class CatchPrototype |
| { |
| Name catchType; |
| Name catchVarName; |
| InstructionList catchBody; |
| } |
| |
| /** |
| * ConditionalFragment holds the disparate elements of a conditional statement fragment. |
| */ |
| public class ConditionalFragment |
| { |
| IASNode site; |
| /** |
| * condition is non-null when the conditional expression is not a constant. |
| * condition is null if an unconditional fragement, or if the fragement is |
| * a constant, in which case constantCondition is non-null. |
| */ |
| InstructionList condition; |
| /** |
| * constantCondition is non-null when the conditional expression is a constant. |
| * constantCondition is null if an unconditional fragement, or if the fragement is |
| * a non-constant, in which case condition is non-null. |
| */ |
| Object constantCondition; |
| InstructionList statement; |
| |
| private Label statementLabel = null; |
| |
| /** |
| * Construct a ConditionalFragment with a single statement. |
| */ |
| ConditionalFragment( IASNode site, InstructionList condition, InstructionList statement) |
| { |
| this.site = site; |
| this.condition = condition; |
| this.constantCondition = null; |
| this.statement = statement; |
| } |
| |
| /** |
| * Construct a ConditionalFragment with a list of statements, |
| * coalescing them into a composite statement. |
| */ |
| ConditionalFragment( IASNode site, InstructionList condition, Vector<InstructionList> statements) |
| { |
| this(site, condition, null, statements); |
| } |
| |
| /** |
| * Construct a constant conditional ConditionalFragment with a list of |
| * statements, coalescing them into a composite statement. |
| */ |
| ConditionalFragment(IASNode site, Object constantConditional, Vector<InstructionList> statements) |
| { |
| this(site, null, constantConditional, statements); |
| } |
| |
| /** |
| * Private constructor called by public constructors |
| * @param site |
| * @param condition |
| * @param constantCondition |
| * @param statements |
| */ |
| private ConditionalFragment(IASNode site, InstructionList condition, Object constantCondition, Vector<InstructionList> statements) |
| { |
| assert (condition == null || constantCondition == null) : "condition and constantCondition can't both be non-null"; |
| this.site = site; |
| this.condition = condition; |
| this.constantCondition = constantCondition; |
| this.statement = createInstructionList(site); |
| |
| for (InstructionList stmt : statements) |
| { |
| this.statement.addAll(stmt); |
| } |
| } |
| |
| /** |
| * @return the label of the fragment's condition, or |
| * its statement label if it's the default alternative. |
| */ |
| Label getLabel() |
| { |
| if ( !this.isUnconditionalAlternative() ) |
| { |
| if ( this.condition != null ) |
| return this.condition.getLabel(); |
| else |
| return getStatementLabel(); |
| } |
| else |
| { |
| return getStatementLabel(); |
| } |
| } |
| |
| /** |
| * Cache and return the label of this conditional fragment's |
| * statement so it can be retrieved after the statement's been |
| * added to the overall conditional statement and this.statement |
| * is invalidated. |
| */ |
| Label getStatementLabel() |
| { |
| if ( null == this.statementLabel ) |
| this.statementLabel = this.statement.getLabel(); |
| |
| return this.statementLabel; |
| } |
| |
| /** |
| * @return true if this is an unconditional alternative, |
| * i.e. a default case or the last else in an if/else if/else. |
| */ |
| boolean isUnconditionalAlternative() |
| { |
| return (null == this.condition) && (null == this.constantCondition); |
| } |
| |
| void convertConstantConditionToInstruction() |
| { |
| if ((constantCondition == null && condition != null) || condition == null) |
| return; |
| |
| condition = transform_constant_value(site, constantCondition); |
| constantCondition = null; |
| } |
| |
| /** |
| * @return true if this is a constant conditonal fragement i.e. case 7: |
| */ |
| boolean isConstantConditional() |
| { |
| return this.constantCondition != null; |
| } |
| } |
| |
| /** |
| * The RuntimeMultiname class holds the components of |
| * the various types of runtime multiname, and puts them |
| * together in the right order to generate the names and |
| * instruction sequences to jamble up the AVM. |
| * |
| * Only some permutations of the compile-time/runtime |
| * qualifier::name possibilities are valid: |
| * Name::expression generates MultinameL |
| * expression::expression generates RTQnameL |
| * expression::name generates RTQname |
| * (name::name is a plain Multiname) |
| */ |
| class RuntimeMultiname |
| { |
| /** |
| * The compile-time qualifier of a MultinameL. |
| * compileTimeName isn't valid (this would |
| * be a compile-time constant Multiname). |
| */ |
| Name compileTimeQualifier; |
| |
| /** |
| * The runtime qualifier of a RTQname or RTQNameL. |
| * Compile-time and runtime names are valid. |
| */ |
| InstructionList runtimeQualifier; |
| |
| /** |
| * The compile-time constant name of a RTQName. |
| * compileTimeQualifier isn't valid (this would |
| * be a compile-time constant Multiname). |
| */ |
| Name compileTimeName; |
| |
| /** |
| * The runtime name of a MultinameL or RTQnameL. |
| */ |
| InstructionList runtimeName; |
| |
| /** |
| * Construct a RTQnameL-type runtime name. |
| * @param qualifier - the runtime qualifier. |
| * @param name - the runtime name. |
| */ |
| RuntimeMultiname(InstructionList qualifier, InstructionList runtime_name) |
| { |
| this.runtimeQualifier = qualifier; |
| this.runtimeName = runtime_name; |
| } |
| |
| /** |
| * Construct a RTQname-type runtime name. |
| * @param qualifier - the runtime qualifier. |
| * @param name - the compile-time name. |
| */ |
| RuntimeMultiname(InstructionList qualifier, Name name) |
| { |
| this.runtimeQualifier = qualifier; |
| this.compileTimeName = name; |
| } |
| |
| /** |
| * Construct a MultinameL-type runtime name. |
| * @param qualifier - the Multiname. Note this |
| * is the only kind of runtime name that receives |
| * a qualifying name. |
| * @param nameL - the runtime expression that |
| * yields the base name. |
| */ |
| RuntimeMultiname(Name qualifier, InstructionList nameL) |
| { |
| assert(qualifier.getKind() == CONSTANT_MultinameL || qualifier.getKind() == CONSTANT_MultinameLA): "Bad qualifier kind " + qualifier.getKind() + " on name " + qualifier.toString(); |
| this.compileTimeQualifier = qualifier; |
| this.runtimeName = nameL; |
| } |
| |
| /** |
| * Assemble the ingredients into an expression. |
| * @param opcode - one of OP_getproperty or OP_setproperty. |
| * @param rhs - the value of a setproperty call. Passed to |
| * this routine because there may be name-specific |
| * instructions before and after the value. |
| */ |
| InstructionList generateGetOrSet(IASNode iNode, int opcode, InstructionList rhs) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| // Note: numerous microoptimization opportunities here |
| // to avoid storing values in temps by creative use of |
| // OP_dup and OP_swap, especially in getproperty gen. |
| |
| if ( this.compileTimeQualifier != null && this.runtimeName != null ) |
| { |
| // Generate MultinameL type code. |
| Binding name_temp = currentScope.allocateTemp(); |
| |
| result.addAll(this.runtimeName); |
| result.addInstruction(OP_dup); |
| result.addInstruction(name_temp.setlocal()); |
| // findprop(MultinameL) consumes a name from the value stack. |
| result.addInstruction(OP_findpropstrict, this.compileTimeQualifier); |
| result.addInstruction(name_temp.getlocal()); |
| if ( rhs != null ) |
| result.addAll(rhs); |
| |
| // get/setprop(MultinameL) consumes a name from the value stack. |
| result.addInstruction(opcode, this.compileTimeQualifier); |
| } |
| else if ( this.runtimeQualifier != null && this.runtimeName != null ) |
| { |
| // Generate RTQnameL type code. |
| Binding name_temp = currentScope.allocateTemp(); |
| Binding qual_temp = currentScope.allocateTemp(); |
| |
| Name rtqnl = new Name(CONSTANT_RTQnameL, null, null); |
| |
| result.addAll(getRuntimeName(iNode)); |
| result.addInstruction(name_temp.setlocal()); |
| |
| result.addAll(getRuntimeQualifier(iNode)); |
| result.addInstruction(OP_dup); |
| result.addInstruction(qual_temp.setlocal()); |
| |
| result.addInstruction(name_temp.getlocal()); |
| // findprop(RTQNameL) consumes namespace and name from the value stack. |
| result.addInstruction(OP_findpropstrict, rtqnl); |
| result.addInstruction(qual_temp.getlocal()); |
| result.addInstruction(name_temp.getlocal()); |
| if ( rhs != null ) |
| result.addAll(rhs); |
| |
| // get/setprop(RTQNameL) consumes namespace and name from the value stack. |
| result.addInstruction(opcode, rtqnl); |
| |
| currentScope.releaseTemp(name_temp); |
| currentScope.releaseTemp(qual_temp); |
| } |
| else |
| { |
| // Last valid combination generates a RTQname. |
| assert(this.runtimeQualifier != null && this.compileTimeName != null) : "Unknown runtime name configuration: " + this.toString(); |
| |
| Name rtqn = new Name(CONSTANT_RTQname, null, this.compileTimeName.getBaseName()); |
| |
| result.addAll(getRuntimeQualifier(iNode)); |
| result.addInstruction(OP_dup); |
| // findprop(RTQName) consumes a namespace from the value stack. |
| result.addInstruction(OP_findpropstrict, rtqn); |
| result.addInstruction(OP_swap); |
| if ( rhs != null ) |
| result.addAll(rhs); |
| |
| // get/setprop(RTQName) consumes a namespace from the value stack. |
| result.addInstruction(opcode, rtqn); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Generate the InstructionList that expresses this RuntimeName's qualifier. |
| * @param iNode - an IASNode to contribute debug info to the result InstructionList. |
| * @return the runtime qualifier setup instructions. |
| */ |
| public InstructionList getRuntimeQualifier(IASNode iNode) |
| { |
| assert(hasRuntimeQualifier()); |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(replicate(this.runtimeQualifier)); |
| |
| // Ensure the last instruction is an OP_coerce to Namespace. |
| Instruction last = result.lastElement(); |
| |
| if ( |
| last.getOpcode() != OP_coerce || |
| last.getOperandCount() == 0 || |
| ! namespaceType.equals(last.getOperand(0)) |
| ) |
| { |
| result.addInstruction(OP_coerce, namespaceType); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @return true if this RuntimeName has a runtime qualifier. |
| */ |
| public boolean hasRuntimeQualifier() |
| { |
| return this.runtimeQualifier != null; |
| } |
| |
| /** |
| * @return true if this RuntimeName has a runtime name. |
| */ |
| public boolean hasRuntimeName() |
| { |
| return this.runtimeName != null; |
| } |
| |
| /** |
| * Generate the InstructionList that expresses this RuntimeName's name. |
| * @param iNode - an IASNode to contribute debug info to the result InstructionList. |
| * @return the runtime name setup instructions. |
| */ |
| public InstructionList getRuntimeName(IASNode iNode) |
| { |
| assert(hasRuntimeName()); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(replicate(this.runtimeName)); |
| if ( result.lastElement().getOpcode() != OP_coerce_s) |
| result.addInstruction(OP_coerce_s); |
| return result; |
| } |
| |
| /** |
| * Generate the runtime name appropriate to this qualifier/name combination. |
| * @return the runtime name appropriate to this qualifier/name combination. |
| */ |
| public Name generateName() |
| { |
| Name result; |
| |
| if ( this.compileTimeQualifier != null ) |
| { |
| result = this.compileTimeQualifier; |
| } |
| else if ( this.runtimeQualifier != null && this.runtimeName != null ) |
| { |
| result = new Name(CONSTANT_RTQnameL, null, null); |
| } |
| else |
| { |
| // Last valid combination generates a RTQname. |
| assert(this.runtimeQualifier != null && this.compileTimeName != null) : "Unknown runtime name configuration: " + this.toString(); |
| |
| result = new Name(CONSTANT_RTQname, null, this.compileTimeName.getBaseName()); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Convenience method generates an assignment |
| * @param value - the value to set. |
| * @return the instruction sequence to set a |
| * the given multiname's value. |
| */ |
| InstructionList generateAssignment(IASNode iNode, InstructionList value) |
| { |
| return generateGetOrSet(iNode, getAssignmentOpcode(iNode), value); |
| } |
| |
| /** |
| * Deduce the correct assignment opcode for a property. |
| * @param iNode - the root of the reference to the property. |
| * @return OP_initproperty if the property is const, OP_setproperty if not. |
| */ |
| int getAssignmentOpcode(IASNode iNode) |
| { |
| if ( SemanticUtils.isConst(iNode, currentScope.getProject()) && ABCGeneratingReducer.this.inInitializingContext(iNode) ) |
| return OP_initproperty; |
| else |
| return OP_setproperty; |
| } |
| |
| /** |
| * Convenience method generates an r-value. |
| * @return the instruction sequence to look up |
| * the given multiname. |
| */ |
| InstructionList generateRvalue(IASNode iNode) |
| { |
| return generateGetOrSet(iNode, OP_getproperty, null); |
| } |
| |
| /** |
| * Diagnostic toString() method, |
| * used primarily to debug the assertion in generate(). |
| */ |
| @Override |
| public String toString() |
| { |
| return "\n\t" + compileTimeQualifier + ",\n\t" + runtimeQualifier + ",\n\t" + compileTimeName + ",\n\t" + runtimeName; |
| } |
| } |
| |
| /** |
| * ForKeyValueLoopState holds the state of a for/in or for each/in loop: |
| * the Hasnext2Wrapper that tracks the state of the locals required to |
| * support the hasnext2 instruction, and the Labels that mark the top |
| * of the loop and the test. |
| */ |
| private class ForKeyValueLoopState |
| { |
| /** |
| * Label for the loop-test logic. |
| */ |
| Label test = new Label(); |
| |
| /** |
| * Label for the top of the loop body. |
| */ |
| Label loop = new Label(); |
| |
| /** |
| * Hasnext2 management structure. |
| */ |
| LexicalScope.Hasnext2Wrapper hasnext = currentScope.hasnext2(); |
| |
| /** |
| * Generate the loop prologue code. |
| * @return loop prologue code. |
| */ |
| InstructionList generatePrologue(IASNode iNode, InstructionList base) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| // Set up the object and index registers. |
| result.addInstruction(OP_pushbyte, 0); |
| result.addInstruction(hasnext.index_temp.setlocal()); |
| |
| result.addAll(base); |
| result.addInstruction(hasnext.stem_temp.setlocal()); |
| |
| // Go to the loop test. |
| result.addInstruction(OP_jump, test); |
| |
| // Top of loop processing. |
| result.addInstruction(OP_label); |
| result.labelCurrent(loop); |
| return result; |
| } |
| |
| /** |
| * Generate the instruction sequence that |
| * fetches the next key or value. |
| * @return the instruction sequence that |
| * fetches the next key or value. |
| */ |
| InstructionList generateKeyOrValue(int opcode) |
| { |
| InstructionList result = new InstructionList(); |
| result.addInstruction(hasnext.stem_temp.getlocal()); |
| result.addInstruction(hasnext.index_temp.getlocal()); |
| result.addInstruction(opcode); |
| return result; |
| } |
| |
| /** |
| * Generate the loop epilogue code. |
| * @return loop epilogue code. |
| */ |
| InstructionList generateEpilogue() |
| { |
| InstructionList result = new InstructionList(); |
| result.addInstruction(hasnext.instruction); |
| result.labelCurrent(test); |
| currentScope.getFlowManager().resolveContinueLabel(result); |
| result.addInstruction(OP_iftrue, loop); |
| hasnext.release(); |
| return result; |
| } |
| } |
| |
| /** |
| * The current LexicalScope. Set by the caller, |
| * changes during reduction when a scoped construct |
| * such as an embedded function or a with statement |
| * is encountered. |
| */ |
| private LexicalScope currentScope; |
| |
| /** |
| * Instructions sent by the caller to be added to |
| * a function definition. Usually these are field |
| * initialization expressions in a constructor. |
| */ |
| private InstructionList instanceInitializers; |
| |
| /** |
| * "Mini scope" regions, i.e., blocks of the method body. |
| */ |
| Stack<InstructionList> miniScopes = new Stack<InstructionList>(); |
| |
| /** |
| * A shared name for the Namespace type. |
| */ |
| public static final Name namespaceType = new Name("Namespace"); |
| |
| /** |
| * A shared name for the XML type. |
| */ |
| public static final Name xmlType = new Name("XML"); |
| |
| /** |
| * A shared name for the XMLList type. |
| */ |
| public static final Name xmlListType = new Name("XMLList"); |
| |
| /** |
| * A shared name for the RegExp type. |
| */ |
| public static final Name regexType = new Name("RegExp"); |
| |
| /** |
| * A shared name for the void type. |
| */ |
| public static final Name voidType = new Name("void"); |
| |
| /** |
| * Generate code to push a numeric constant. |
| */ |
| public static void pushNumericConstant(long value, InstructionList result_list) |
| { |
| result_list.pushNumericConstant(value); |
| } |
| |
| /** |
| * Generate code to push a numeric value onto the stack. This will take into account the type |
| * of the expression that generated the number - e.g. a numeric value produced from a const of type 'int' |
| * will produce at worst a pushint instruction |
| * |
| * Used by the constant folding routines so we don't lose type info when all we have is a Number. |
| * |
| * @param result_list IL to add the instruction. |
| * @param value the numeric value to push |
| * @param type the type of the expression that produced the value. |
| */ |
| public void pushNumericConstant(InstructionList result_list, Number value, IDefinition type) |
| { |
| ICompilerProject project = currentScope.getProject(); |
| if( type == project.getBuiltinType(BuiltinType.INT) ) |
| { |
| int val = ECMASupport.toInt32(value); |
| if( (byte)val == val ) |
| { |
| result_list.addInstruction(OP_pushbyte, val); |
| } |
| else if( (short)val == val ) |
| { |
| result_list.addInstruction(OP_pushshort, val); |
| } |
| else |
| { |
| result_list.addInstruction(OP_pushint, Integer.valueOf(val)); |
| } |
| } |
| else if( type == project.getBuiltinType(BuiltinType.UINT) ) |
| { |
| long uval = ECMASupport.toUInt32(value.doubleValue()); |
| if ((uval & 0x7F) == uval) { // Pushbyte sign extends |
| result_list.addInstruction(OP_pushbyte, (int)uval); |
| } |
| else if ((uval & 0x7FFF) == uval) { // Pushshort sign extends |
| result_list.addInstruction(OP_pushshort, (int)uval); |
| } |
| else { |
| result_list.addInstruction(OP_pushuint, Long.valueOf(uval)); |
| } |
| } |
| else |
| { |
| double dval = value.doubleValue(); |
| if( ECMASupport.isNan(dval) ) |
| { |
| result_list.addInstruction(OP_pushnan); |
| } |
| else if (!Double.isInfinite(dval) && (dval == ECMASupport.toInt32(value)) ) |
| { |
| // distinguish pos/neg 0 |
| // java treats -0 and 0 as equal, but divide by -0 results in NEG INFINITY, whereas pos |
| // 0 results in POS INFINITY |
| // positive 0 can be encoded with a pushbyte, but neg zero requires a pushdouble |
| if( dval == 0.0 && 1.0/dval == Double.NEGATIVE_INFINITY ) |
| result_list.addInstruction(OP_pushdouble, dval); |
| else |
| // Integer |
| pushNumericConstant(result_list, ECMASupport.toInt32(value), project.getBuiltinType(BuiltinType.INT)); |
| } |
| else if( Double.isNaN(dval) ) |
| { |
| result_list.addInstruction(OP_pushnan); |
| } |
| else |
| { |
| result_list.addInstruction(OP_pushdouble, value.doubleValue()); |
| } |
| } |
| } |
| |
| /** |
| * @return the currently active collection of problems. |
| */ |
| public Collection<ICompilerProblem> getProblems() |
| { |
| return currentScope.getProblems(); |
| } |
| |
| /** |
| * Generate a binary operator. |
| * @param l - the left-hand operand. |
| * @param r - the right-hand operand. |
| * @param opcode - the operator's opcode. |
| * @return the combined instruction sequence with the operator appended. |
| */ |
| InstructionList binaryOp(IASNode iNode, InstructionList l, InstructionList r, int opcode) |
| { |
| checkBinaryOp(iNode, opcode); |
| return binaryOperatorBody(iNode, l, r, InstructionFactory.getInstruction(opcode)); |
| } |
| |
| /** |
| * Generate a conditional jump operator. |
| * @param l - the left-hand operand. |
| * @param r - the right-hand operand. |
| * @param opcode - the operator's opcode. |
| * @return the combined instruction sequence with the operator appended. |
| * The target is filled in by a downstream reduction. |
| */ |
| InstructionList conditionalJump(IASNode iNode, InstructionList l, InstructionList r, int opcode) |
| { |
| checkBinaryOp(iNode, opcode); |
| return binaryOperatorBody(iNode, l, r, InstructionFactory.getTargetableInstruction(opcode)); |
| } |
| |
| /** |
| * Generate a binary operator. |
| * @param l - the left-hand operand. |
| * @param r - the right-hand operand. |
| * @param operator - the operator, as an Instruction. |
| * @return the combined instruction sequence with the operator appended. |
| */ |
| InstructionList binaryOperatorBody(IASNode iNode, InstructionList l, InstructionList r, Instruction operator) |
| { |
| InstructionList result = createInstructionList(iNode, l.size() + r.size() + 1); |
| result.addAll(l); |
| result.addAll(r); |
| result.addInstruction(operator); |
| return result; |
| } |
| |
| /** |
| * Perform semantic checks on a binary operator. |
| */ |
| public void checkBinaryOp (IASNode iNode, int opcode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkBinaryOperator(iNode, opcode); |
| } |
| |
| /** |
| * Resolve a dotted name, e.g., foo.bar.baz |
| */ |
| Binding dottedName(IASNode iNode, String qualifiers, String base_name) |
| { |
| if ( iNode instanceof IdentifierNode ) |
| { |
| return currentScope.resolveName((IdentifierNode)iNode); |
| } |
| else if ( iNode instanceof ExpressionNodeBase ) |
| { |
| ExpressionNodeBase expr = (ExpressionNodeBase) iNode; |
| Name n = expr.getMName(currentScope.getProject()); |
| |
| if ( n == null ) |
| { |
| currentScope.addProblem(new CodegenInternalProblem(iNode, "Unable to resove member name: " + iNode.toString())); |
| n = new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, qualifiers)), base_name); |
| } |
| |
| return currentScope.getBinding(iNode, n, expr.resolve(currentScope.getProject())); |
| } |
| |
| //else |
| currentScope.addProblem(new CodegenInternalProblem(iNode, "Unable to resove to a dotted name: " + iNode.toString())); |
| return new Binding(iNode, new Name(CONSTANT_Qname, new Nsset(new Namespace(CONSTANT_PackageNs, qualifiers)), base_name), null); |
| } |
| |
| /** |
| * Resolve a dotted name, e.g., foo.bar.baz, where the whole dotted name is a package |
| * this is an error, and a diagnostic will be emitted |
| */ |
| Binding errorPackageName(IASNode iNode, String qualifiers, String base_name) |
| { |
| currentScope.addProblem(new PackageCannotBeUsedAsValueProblem(iNode, "'" + qualifiers + "." + base_name + "'")); |
| return new Binding(iNode, new Name(qualifiers + "." + base_name), null); |
| } |
| |
| private void generateAssignmentOp(IASNode site, Binding target, InstructionList result) |
| { |
| boolean inlined = generateInlineSetterAccess(target, result, true); |
| |
| if (!inlined) |
| result.addInstruction(getAssignmentOpcode(site, target), target.getName()); |
| } |
| |
| /** |
| * Deduce the correct assignment opcode for a Binding. |
| * |
| * @param b - the Binding of interest. |
| * @return the appropriate opcode to assign to the entity represented by the |
| * Binding. |
| */ |
| int getAssignmentOpcode(IASNode site, Binding b) |
| { |
| if (b.getDefinition() instanceof IConstantDefinition && this.inInitializingContext(site)) |
| return OP_initproperty; |
| else if (b.isSuperQualified()) |
| return OP_setsuper; |
| else |
| return OP_setproperty; |
| } |
| |
| /** |
| * Is the generator in an initializing context? |
| * @param iNode - the i-node of interest. |
| * @return true if the generator is in a context where it should |
| * generate initializer calls, i.e., OP_initproperty to set properties. |
| */ |
| boolean inInitializingContext(IASNode iNode) |
| { |
| return SemanticUtils.isInVariableDeclaration(iNode) || SemanticUtils.isInConstructor(iNode); |
| } |
| |
| |
| /** |
| * Common routine used by reductions that |
| * need an empty list. |
| * @param iNode - the current AST node. |
| */ |
| public InstructionList createInstructionList(IASNode iNode) |
| { |
| return createInstructionList(iNode, 0); |
| } |
| |
| /** |
| * Common routine used by reductions that |
| * need an empty list. |
| * @param iNode - the current AST node. |
| * @param capacity - requested capacity of the new list. |
| * May be adjusted to accomodate debug instructions. |
| */ |
| public InstructionList createInstructionList(IASNode iNode, int capacity) |
| { |
| DebugInfoInstructionList result; |
| |
| String file_name = SemanticUtils.getFileName(iNode); |
| |
| // Note: getLine() uses zero-based counting. |
| int line_num = iNode.getLine() + 1; |
| |
| // Adjust the capacity requirement if debug |
| // instructions are to be emitted. |
| if ( currentScope.emitFile(file_name) ) |
| capacity++; |
| if ( currentScope.emitLine(line_num) ) |
| capacity++; |
| |
| // If the required capacity is less than three |
| // instructions, the InstructionList can hold |
| // them organically. Specifying a capacity to |
| // the InstructionList ctor causes it to allocate |
| // a separate ArrayList. |
| if ( capacity > 3 ) |
| result = new DebugInfoInstructionList(capacity); |
| else |
| result = new DebugInfoInstructionList(); |
| |
| if ( currentScope.emitFile(file_name) ) |
| { |
| currentScope.setDebugFile(file_name); |
| result.addInstruction(OP_debugfile, currentScope.getDebugFile()); |
| } |
| |
| if ( currentScope.emitLine(line_num) ) |
| { |
| // Set the line number in the instruction list - it will |
| // emit or not emit the debugline depending on how it's used |
| result.setDebugLine(line_num); |
| currentScope.setDebugLine(line_num); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Instruction list to help with emitting OP_debuglines. |
| * |
| * When given a line number, it will emit an OP_debugline when addInstruction is called with an |
| * executable instruction. |
| * |
| * If addAll is called, then the debugline will not be emitted as the IL passed in should already have |
| * any necessary debuglines if it contains executable instrucitons. |
| * |
| */ |
| private static class DebugInfoInstructionList extends InstructionList |
| { |
| /** |
| * Constant for we don't have a line number |
| */ |
| public static final int NO_LINE = -1; |
| /** |
| * The line number to use for an op_debugline when an executable instruction is added |
| */ |
| private int line_num = NO_LINE; |
| |
| DebugInfoInstructionList (int capacity) |
| { |
| super(capacity); |
| } |
| DebugInfoInstructionList () |
| { |
| super(); |
| } |
| |
| /** |
| * Set the line number for this IL |
| * @param line_num the line number |
| */ |
| void setDebugLine(int line_num) |
| { |
| this.line_num = line_num; |
| } |
| |
| /** |
| * Add the instruction to the IL. If the instruction is |
| * an exectuable instruction, and we have not emitted the line number yet, |
| * then this will insert an OP_debugline with the line number before the instruction is |
| * added. |
| * @param insn the instruction to be added. |
| * @return the added instruction |
| */ |
| public Instruction addInstruction(Instruction insn) |
| { |
| |
| if( line_num != NO_LINE && insn.isExecutable() ) |
| { |
| // add the debugline instruction first |
| |
| // reset the line number |
| // do this before adding the instruction so |
| // we don't infinitely recurse |
| int line = line_num; |
| line_num = NO_LINE; |
| |
| addInstruction(OP_debugline, line); |
| } |
| return super.addInstruction(insn); |
| } |
| } |
| |
| /** |
| * Error trap. |
| */ |
| public Binding error_namespaceAccess(IASNode iNode, IASNode raw_qualifier, Binding qualified_name) |
| { |
| String qualifier = ((IdentifierNode)raw_qualifier).getName(); |
| |
| // TODO: In some circumstances, the namespace qualifier |
| // may be an invalid attribute on a declaration. |
| currentScope.addProblem(new UnknownNamespaceProblem(raw_qualifier, qualifier)); |
| |
| return qualified_name; |
| } |
| |
| /** |
| * Error trap. |
| */ |
| public InstructionList error_reduce_Op_AssignId(IASNode iNode, InstructionList non_lvalue, InstructionList rvalue) |
| { |
| currentScope.addProblem(new InvalidLvalueProblem(iNode)); |
| InstructionList result = createInstructionList(iNode, 1); |
| // Since we are reducing to an expression, make sure the instruction |
| // list we return, produces a value on the operand stack. |
| result.addInstruction(ABCConstants.OP_pushundefined); |
| return result; |
| } |
| |
| /** |
| * Generate access to a named entity. |
| * @param name - the entity's name. |
| * @return an instruction sequence to access the entity. |
| */ |
| InstructionList generateAccess(Binding name) |
| { |
| return generateAccess(name, AccessType.Strict); |
| } |
| |
| /** |
| * Enumerate possible access types to a named entity; |
| * Lenient is used in typeof expressions to access |
| * variables referred to by simple name and for the |
| * member references of member access expressions. |
| * All other expressions use strict access. |
| */ |
| private enum AccessType { Strict, Lenient } |
| |
| /** |
| * Generate access to a named entity. |
| * @param name - the entity's name. |
| * @param accessType - one of Lenient or Strict. |
| * @return an instruction sequence to access the entity. |
| */ |
| InstructionList generateAccess(Binding name, AccessType accessType) |
| { |
| InstructionList result = new InstructionList(2); |
| generateAccess(name, accessType, result); |
| return result; |
| } |
| |
| /** |
| * Generate access to a named entity. |
| * @param name - the entity's name. |
| * @param result - the instruction sequence to generate into. |
| */ |
| void generateAccess(Binding binding, InstructionList result) |
| { |
| generateAccess(binding, AccessType.Strict, result); |
| } |
| |
| /** |
| * Generate access to a named entity. |
| * @param name - the entity's name. |
| * @param accessType - one of Lenient or Strict. |
| * @param result - the instruction sequence to generate into. |
| */ |
| void generateAccess(Binding binding, AccessType accessType, InstructionList result) |
| { |
| if (binding.isLocal()) |
| { |
| result.addInstruction(binding.getlocal()); |
| } |
| else |
| { |
| assert (binding.getName() != null) : "non-local Binding " + binding + " must have a name"; |
| currentScope.getMethodBodySemanticChecker().checkGetProperty(binding); |
| |
| boolean inlined = generateInlineGetterAccess(binding, result, false); |
| |
| if (!inlined) |
| generateAccess(binding, binding.isSuperQualified(), accessType, result); |
| } |
| } |
| |
| /** |
| * Generate access to a named entity. |
| * @param binding - the entity's binding. |
| * @param is_super - set if the name was explicitly qualified with "super." |
| * @param accessType - one of Lenient or Strict. |
| * @param result - the instruction sequence to generate into. |
| */ |
| void generateAccess(Binding binding, final boolean is_super, AccessType accessType, InstructionList result) |
| { |
| final Name name = binding.getName(); |
| assert (name != null) : "binding must have a name when calling generateAccess()"; |
| |
| final IASNode node = binding.getNode(); |
| if ( name.isTypeName() ) |
| { |
| // a type names node should always be a ITypedExpressionNode |
| ITypedExpressionNode typeNode = (ITypedExpressionNode)node; |
| IExpressionNode collectionNode = typeNode.getCollectionNode(); |
| IDefinition collectionDefinition = SemanticUtils.getDefinition(collectionNode, currentScope.getProject()); |
| Binding collectionBinding = currentScope.getBinding(collectionNode, name.getTypeNameBase(), collectionDefinition); |
| generateAccess(collectionBinding, is_super, AccessType.Strict, result); |
| |
| IExpressionNode typeTypeNode = typeNode.getTypeNode(); |
| IDefinition typeDefinition = SemanticUtils.getDefinition(typeTypeNode, currentScope.getProject()); |
| Binding typeBinding = currentScope.getBinding(typeTypeNode, name.getTypeNameParameter(), typeDefinition); |
| generateTypeNameParameter(typeBinding, result); |
| |
| result.addInstruction(OP_applytype, 1); |
| } |
| else |
| { |
| // Test whether we're refering to the class being initialized, for example: |
| // public class C { |
| // private static var v:Vector.<C> = new Vector.<C>(); |
| // } |
| // as in this case we need to do a getlocal0 as the class |
| // hasn't been initialized yet |
| boolean useLocal0 = false; |
| if (node instanceof IdentifierNode) |
| { |
| IdentifierNode id = (IdentifierNode)node; |
| IDefinition def = id.resolve(currentScope.getProject()); |
| if (SemanticUtils.isRefToClassBeingInited(id, def) && !currentScope.insideInlineFunction()) |
| useLocal0 = true; |
| } |
| |
| // TODO: use getslot when we can. |
| // TODO: we can at least do this when we're accessing something in an activation object |
| if (useLocal0) |
| { |
| result.addInstruction(OP_getlocal0); |
| } |
| else if (is_super) |
| { |
| result.addAll(currentScope.findProperty(binding, true)); |
| result.addInstruction(OP_getsuper, name); |
| } |
| else if (accessType == AccessType.Lenient) |
| { |
| result.addAll(currentScope.findProperty(binding, false)); |
| result.addInstruction(OP_getproperty, name); |
| } |
| else |
| { |
| result.addAll(currentScope.getPropertyValue(binding)); |
| } |
| } |
| } |
| |
| private boolean generateInlineGetterAccess(Binding binding, InstructionList result, boolean isQualified) |
| { |
| IDefinition def = binding.getDefinition(); |
| if (!(def instanceof AccessorDefinition && currentScope.getMethodBodySemanticChecker().canGetterBeInlined((AccessorDefinition)def))) |
| return false; |
| |
| AccessorDefinition accessorDefinition = (AccessorDefinition)def; |
| if (accessorDefinition instanceof SetterDefinition) |
| accessorDefinition = accessorDefinition.resolveCorrespondingAccessor(currentScope.getProject()); |
| |
| assert (accessorDefinition != null) : "generateInlineGetterAccess() called with no getter definition"; |
| |
| FunctionNode functionNode = (FunctionNode)accessorDefinition.getFunctionNode(); |
| return inlineFunction(accessorDefinition, functionNode, result, isQualified); |
| } |
| |
| private boolean generateInlineSetterAccess(Binding binding, InstructionList result, boolean isQualified) |
| { |
| IDefinition def = binding.getDefinition(); |
| if (!(def instanceof AccessorDefinition && currentScope.getMethodBodySemanticChecker().canSetterBeInlined((AccessorDefinition)def))) |
| return false; |
| |
| AccessorDefinition accessorDefinition = (AccessorDefinition)def; |
| if (accessorDefinition instanceof GetterDefinition) |
| accessorDefinition = accessorDefinition.resolveCorrespondingAccessor(currentScope.getProject()); |
| |
| assert (accessorDefinition != null) : "generateInlineSetterAccess() called with no setter definition"; |
| |
| FunctionNode functionNode = (FunctionNode)accessorDefinition.getFunctionNode(); |
| return inlineFunction(accessorDefinition, functionNode, result, isQualified); |
| } |
| |
| private boolean generateInlineFunctionCall(Binding binding, InstructionList result, boolean isQualified, Vector<InstructionList> args) |
| { |
| IDefinition def = binding.getDefinition(); |
| if (!(def instanceof FunctionDefinition && (!(def instanceof IAccessorDefinition)) && currentScope.getMethodBodySemanticChecker().canFunctionBeInlined((FunctionDefinition)def))) |
| return false; |
| |
| FunctionDefinition functionDef = (FunctionDefinition)binding.getDefinition(); |
| FunctionNode functionNode = (FunctionNode)functionDef.getFunctionNode(); |
| |
| InstructionList insn = createInstructionList(functionNode); |
| for (InstructionList arg: args) |
| insn.addAll(arg); |
| |
| if (inlineFunction(functionDef, functionNode, insn, isQualified)) |
| { |
| result.addAll(insn); |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| private boolean inlineFunction(FunctionDefinition functionDef, FunctionNode functionNode, InstructionList result, boolean isQualified) |
| { |
| try |
| { |
| InlineFunctionLexicalScope inlineFunctionScope = currentScope.pushInlineFunctionFrame(functionDef.getContainingScope(), isQualified, functionNode); |
| currentScope = inlineFunctionScope; |
| |
| // generate the instructions for the body of the function |
| IScopedNode body = functionNode.getScopedNode(); |
| InstructionList insns = inlineFunctionScope.getGenerator().generateInstructions(body, CmcEmitter.__statement_NT, inlineFunctionScope); |
| |
| currentScope = currentScope.popFrame(); |
| |
| final ICompilerProject project = currentScope.getProject(); |
| if (currentScope.getMethodBodySemanticChecker().functionBodyHasNonInlineableInstructions(insns, functionDef.isInline(), functionDef.getBaseName())) |
| { |
| return false; |
| } |
| else |
| { |
| inlineFunctionScope.assignActualsToFormals(functionDef, result); |
| |
| if (isQualified) |
| result.addInstruction(inlineFunctionScope.setContainingClass()); |
| |
| result.addAll(insns); |
| |
| // test for a fallthrough when the return type is non-void, as we |
| // need coerce undefined to the return type to mimic returnvoid behavior |
| if (!(functionDef instanceof SetterDefinition) && result.canFallThrough() || result.hasPendingLabels()) |
| { |
| TypeDefinitionBase returnType = (TypeDefinitionBase)functionDef.resolveReturnType(project); |
| if (returnType != currentScope.getProject().getBuiltinType(BuiltinType.VOID) && |
| returnType != currentScope.getProject().getBuiltinType(BuiltinType.ANY_TYPE)) |
| { |
| result.addInstruction(OP_pushundefined); |
| result.addInstruction(OP_coerce, returnType.getMName(project)); |
| } |
| } |
| |
| result.labelNext(inlineFunctionScope.getInlinedFunctionCallSiteLabel()); |
| inlineFunctionScope.mergeIntoContainingScope(result); |
| } |
| } |
| finally |
| { |
| functionNode.discardFunctionBody(); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Generate code to assign to a named entity. |
| * @param target - the entity to be assigned to. |
| * @param rvalue - the value to assign. |
| * @return an instruction sequence that stores |
| * the given rvalue in the target. |
| * |
| */ |
| InstructionList generateAssignment(IASNode iNode, Binding target, InstructionList rvalue) |
| { |
| return generateAssignment(iNode, target, rvalue, false); |
| } |
| |
| /** |
| * Generate code to assign to a named entity. |
| * @param target - the entity to be assigned to. |
| * @param rvalue - the value to assign. |
| * @param need_value - when true, |
| * leave a DUP of the rvalue on the stack. |
| * @return an instruction sequence that stores |
| * the given rvalue in the target, and leaves |
| * a DUP of the rvalue on the stack if need_value is set. |
| * |
| */ |
| InstructionList generateAssignment(IASNode iNode, Binding target, InstructionList rvalue, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode, rvalue.size() + 4); |
| |
| // We may need to know if the RHS is in a local |
| // for some micro-optimizations; the rvalue instruction |
| // list is about to be invalidated, so speculatively |
| // determine that information here. |
| int rhsOpcode = rvalue.lastElement().getOpcode(); |
| final boolean rhsIsLocal = |
| rhsOpcode == OP_getlocal || |
| rhsOpcode == OP_getlocal0 || |
| rhsOpcode == OP_getlocal1 || |
| rhsOpcode == OP_getlocal2 || |
| rhsOpcode == OP_getlocal3; |
| |
| result.addAll(rvalue); |
| if ( need_value ) |
| result.addInstruction(OP_dup); |
| |
| // when assigning to a local or slot, we need to generate the type coerce and |
| // runtime illegal write to const checks, so combine in this if block. |
| if ( target.isLocal() || target.slotIdIsSet() ) |
| { |
| if ( target.getDefinition() != null ) |
| { |
| ITypeDefinition tgtType = target.getDefinition().resolveType(currentScope.getProject()); |
| ITypeDefinition srcType = null; |
| |
| srcType = SemanticUtils.resolveRHSTypeOfAssignment(currentScope.getProject(), iNode); |
| |
| if ( |
| tgtType != null && |
| tgtType != ClassDefinition.getAnyTypeClassDefinition() && |
| !(srcType == tgtType && rhsIsLocal) |
| ) |
| { |
| coerce(result, tgtType); |
| } |
| } |
| |
| // If the target is a const and this assignment occurs outside the const's declaration, |
| // emulate the AVM's behavior by compiling in a throw statement. |
| // Note that this behavior differs from ASC's until ASC-4376 is fixed. |
| // We need to keep this runtime check, as in strict mode this is a semantic error, but |
| // in non-strict mode it needs to be caught at run time. |
| if ( |
| SemanticUtils.isConstDefinition(target.getDefinition()) && |
| !SemanticUtils.isInVariableDeclaration(iNode) && |
| target.getName() != null |
| ) |
| { |
| result.addInstruction(OP_pushstring, "Illegal write to local const " + target.getName().getBaseName()); |
| result.addInstruction(OP_throw); |
| } |
| else if (target.slotIdIsSet()) |
| { |
| if (currentScope.needsActivation()) |
| { |
| // Restore the activation object. |
| result.addInstruction(currentScope.getActivationStorage().getlocal()); |
| result.addInstruction(OP_swap); |
| } |
| |
| result.addInstruction(OP_setslot, target.getSlotId()); |
| } |
| else |
| { |
| result.addInstruction(target.setlocal()); |
| } |
| } |
| else |
| { |
| boolean inlined = generateInlineSetterAccess(target, result, false); |
| |
| if (!inlined) |
| { |
| IDefinition def = target.getDefinition(); |
| if (def instanceof GetterDefinition) |
| { |
| boolean isSuper = target.isSuperQualified(); |
| SetterDefinition setter = (SetterDefinition)((GetterDefinition)def). |
| resolveCorrespondingAccessor(currentScope.getProject()); |
| target = currentScope.getBinding(setter); |
| if (isSuper) |
| target.setSuperQualified(true); |
| } |
| result.addAll(currentScope.findProperty(target, false)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(getAssignmentOpcode(iNode, target), target.getName()); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Generate a catch block. |
| * @param catch_proto - the catch block's prototype. |
| */ |
| InstructionList generateCatchBlock( Label try_start, Label try_end, CatchPrototype catch_proto) |
| { |
| InstructionList scope_reinit = currentScope.getFlowManager().getScopeStackReinit(); |
| InstructionList current_catch = new InstructionList(catch_proto.catchBody.size() + scope_reinit.size() + 15); |
| |
| // Common prologue code. |
| if ( currentScope.needsThis() ) |
| { |
| current_catch.addInstruction(OP_getlocal0); |
| current_catch.addInstruction(OP_pushscope); |
| } |
| |
| if( currentScope.needsActivation() ) |
| { |
| // Restore the activation object. |
| current_catch.addInstruction(currentScope.getActivationStorage().getlocal()); |
| current_catch.addInstruction(OP_pushscope); |
| } |
| |
| // Re-establish enclosing exception scopes. |
| current_catch.addAll(scope_reinit); |
| |
| int handler_number = currentScope.getMethodBodyVisitor().visitException(try_start, try_end, current_catch.getLabel(), catch_proto.catchType, catch_proto.catchVarName); |
| Binding exception_storage = currentScope.getFlowManager().getFinallyContext().getExceptionStorage(); |
| |
| current_catch.addInstruction(OP_newcatch, handler_number); |
| current_catch.addInstruction(OP_dup); |
| if ( exception_storage != null ) |
| { |
| current_catch.addInstruction(exception_storage.setlocal()); |
| current_catch.addInstruction(OP_dup); |
| } |
| current_catch.addInstruction(OP_pushscope); |
| current_catch.addInstruction(OP_swap); |
| current_catch.addInstruction(OP_setslot, 1); |
| |
| current_catch.addAll(catch_proto.catchBody); |
| |
| if ( current_catch.canFallThrough() || current_catch.hasPendingLabels() ) |
| { |
| current_catch.addInstruction(OP_popscope); |
| } |
| |
| return current_catch; |
| } |
| |
| /** |
| * Generate a compound assignment statement. |
| */ |
| InstructionList generateCompoundAssignment(IASNode iNode, Binding lvalue, InstructionList expr, int opcode, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode, expr.size() + (lvalue.isLocal()? 4 : 8)); |
| |
| currentScope.getMethodBodySemanticChecker().checkCompoundAssignment(iNode, lvalue, opcode); |
| |
| if ( lvalue.isLocal() ) |
| { |
| result.addInstruction(lvalue.getlocal()); |
| result.addAll(expr); |
| result.addInstruction(opcode); |
| if ( need_value ) |
| result.addInstruction(OP_dup); |
| |
| // Coerce to the right type if the local has a type anno |
| if ( lvalue.getDefinition() != null ) |
| { |
| ITypeDefinition type = lvalue.getDefinition().resolveType(currentScope.getProject()); |
| |
| if ( type != ClassDefinition.getAnyTypeClassDefinition() ) |
| { |
| coerce(result, type); |
| } |
| } |
| |
| result.addInstruction(lvalue.setlocal()); |
| } |
| else |
| { |
| result.addAll(currentScope.findProperty(lvalue, true)); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_getproperty, lvalue.getName()); |
| result.addAll(expr); |
| result.addInstruction(opcode); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| result.addInstruction(getAssignmentOpcode(iNode, lvalue), lvalue.getName()); |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| } |
| |
| return result; |
| } |
| |
| InstructionList generateCompoundBracketAssignment(IASNode iNode, InstructionList stem, InstructionList index, InstructionList expr, int opcode, boolean need_value) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IBinaryOperatorNode)iNode).getLeftOperandNode(); |
| InstructionList result = createInstructionList(iNode, stem.size() * 2 + index.size() + expr.size() + 11 ); |
| |
| // Although ASC evaluates the stem twice, it only evaluates the index once. |
| // TODO: Inspect the index expression for side effects so the temp can be |
| // elided in most cases. |
| Binding index_temp = currentScope.allocateTemp(); |
| result.addAll(index); |
| result.addInstruction(index_temp.setlocal()); |
| |
| result.addAll(replicate(stem)); |
| result.addInstruction(index_temp.getlocal()); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| result.addAll(expr); |
| result.addInstruction(opcode); |
| |
| Binding value_temp = currentScope.allocateTemp(); |
| result.addInstruction(value_temp.setlocal()); |
| |
| result.addAll(stem); |
| result.addInstruction(index_temp.getlocal()); |
| result.addInstruction(value_temp.getlocal()); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| |
| if ( need_value ) |
| result.addInstruction(value_temp.getlocal()); |
| |
| currentScope.releaseTemp(value_temp); |
| currentScope.releaseTemp(index_temp); |
| |
| return result; |
| } |
| |
| InstructionList generateCompoundAssignmentToRuntimeName(IASNode iNode, RuntimeMultiname name, InstructionList expr, int opcode, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(name.generateRvalue(iNode)); |
| result.addAll(expr); |
| result.addInstruction(opcode); |
| |
| if ( need_value ) |
| { |
| // TODO: In many cases this temp can be avoided by more sophisticated analysis. |
| // TODO: See also comments in RuntimeMultiname.generateGetOrSet(). |
| Binding temp = currentScope.allocateTemp(); |
| result.addInstruction(temp.setlocal()); |
| InstructionList temp_rhs = new InstructionList(); |
| temp_rhs.addInstruction(temp.getlocal()); |
| result.addAll(name.generateAssignment(iNode, temp_rhs)); |
| result.addInstruction(temp.getlocal()); |
| currentScope.releaseTemp(temp); |
| } |
| else |
| { |
| result = name.generateAssignment(iNode, result); |
| } |
| |
| return result; |
| } |
| |
| InstructionList generateCompoundMemberAssignment(IASNode iNode, InstructionList stem, Binding member, InstructionList expr, int fetch_opcode, int assign_opcode, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode, stem.size() * 2 + expr.size() + 5 ); |
| currentScope.getMethodBodySemanticChecker().checkCompoundAssignment(iNode, member, assign_opcode); |
| |
| // TODO: Depending on the resolution of ASC-4159 and |
| // the corresponding Royale backwards compatibility |
| // issue, cache the stem expression in a local to avoid |
| // multiple evaluations. |
| result.addAll(replicate(stem)); |
| result.addInstruction(fetch_opcode, member.getName()); |
| |
| result.addAll(expr); |
| result.addInstruction(assign_opcode); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(OP_dup); |
| } |
| |
| result.addAll(stem); |
| result.addInstruction(OP_swap); |
| result.addInstruction(getAssignmentOpcode(iNode, member), member.getName()); |
| |
| return result; |
| } |
| |
| /** |
| * Generate a compound logical assignment expression to a named lvalue. |
| * @param iNode - the assignment operator (root of the subtree). |
| * @param lvalue - the lvalue's name. |
| * @param expr - the expression to assign. |
| * @param is_and - true if the expression is &&=, false if it's ||=. |
| * @param need_value - true if the expression's not used in a void context. |
| */ |
| InstructionList generateCompoundLogicalAssignment(IASNode iNode, Binding lvalue, InstructionList expr, boolean is_and, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| int failure_test = is_and? OP_iffalse : OP_iftrue; |
| |
| currentScope.getMethodBodySemanticChecker().checkCompoundAssignment(iNode, lvalue, failure_test); |
| |
| Label tail = new Label(); |
| |
| if ( lvalue.isLocal() ) |
| { |
| // Fetch and test the current value. |
| result.addInstruction(lvalue.getlocal()); |
| // The current value may not be the result value, |
| // but for now assume it is. |
| if ( need_value ) |
| result.addInstruction( OP_dup); |
| |
| result.addInstruction( failure_test, tail ); |
| |
| // Test succeeded: reset the value, but first |
| // pop the value speculatively dup'd above. |
| if ( need_value ) |
| result.addInstruction(OP_pop); |
| |
| result.addAll(expr); |
| |
| if ( need_value ) |
| result.addInstruction(OP_dup); |
| |
| result.addInstruction(lvalue.setlocal()); |
| } |
| else |
| { |
| // Fetch, speculatively dup, and test the current value. |
| result.addAll(currentScope.getPropertyValue(lvalue)); |
| |
| if ( need_value ) |
| result.addInstruction(OP_dup); |
| |
| result.addInstruction(failure_test, tail); |
| |
| if ( need_value ) |
| result.addInstruction(OP_pop); |
| |
| result.addAll(expr); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(OP_dup); |
| } |
| |
| result.addAll(currentScope.findProperty(lvalue, true)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(getAssignmentOpcode(iNode, lvalue), lvalue.getName()); |
| |
| } |
| |
| result.labelNext(tail); |
| return result; |
| } |
| |
| /** |
| * Generate compound logical assignment to a runtime name, e.g., n::x ||= foo; |
| * @param iNode - the root of the assignment subtree. |
| * @param name - the runtime name. |
| * @param expr - the second operand of the implied binary expression. |
| * @param is_and - true if the result is set to the second operand iff the first operand is true. |
| * @param need_value - true if the value of the assignment is required. |
| */ |
| InstructionList generateCompoundLogicalRuntimeNameAssignment(IASNode iNode, RuntimeMultiname name, InstructionList expr, boolean is_and, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| Label tail = new Label(); |
| int failure_test = is_and? OP_iffalse : OP_iftrue; |
| |
| Binding rhs_temp = null; |
| InstructionList rhs_fetch = null; |
| |
| // Fetch, speculatively dup, and test the current value. |
| result.addAll(name.generateRvalue(iNode)); |
| |
| if ( need_value ) |
| result.addInstruction(OP_dup); |
| |
| result.addInstruction(failure_test, tail); |
| |
| if ( need_value ) |
| { |
| // Clear the speculative dup. |
| result.addInstruction(OP_pop); |
| } |
| |
| result.addAll(expr); |
| |
| if ( need_value ) |
| { |
| // The runtime multiname instruction sequence |
| // doesn't allow the rhs expression to travel |
| // on the stack. |
| rhs_temp = currentScope.allocateTemp(); |
| rhs_fetch = createInstructionList(iNode); |
| |
| result.addInstruction(OP_dup); |
| result.addInstruction(rhs_temp.setlocal()); |
| rhs_fetch.addInstruction(rhs_temp.getlocal()); |
| |
| result.addAll(name.generateAssignment(iNode, rhs_fetch)); |
| currentScope.releaseTemp(rhs_temp); |
| } |
| else |
| { |
| result = (name.generateAssignment(iNode, result)); |
| } |
| |
| result.labelNext(tail); |
| return result; |
| } |
| |
| /** |
| * Generate a compound logical assignment expression to a a[i] type lvalue. |
| * @param iNode - the assignment operator (root of the subtree). |
| * @param stem - the expression that generates the lvalue's stem, e.g., a in a[i] |
| * @param index - the index expression. |
| * @param expr - the expression to assign. |
| * @param is_and - true if the expression is &&=, false if it's ||=. |
| * @param need_value - true if the expression's not used in a void context. |
| */ |
| InstructionList generateCompoundLogicalBracketAssignment(IASNode iNode, InstructionList stem, InstructionList index, InstructionList expr, boolean is_and, boolean need_value) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IBinaryOperatorNode)iNode).getLeftOperandNode(); |
| |
| InstructionList result = createInstructionList(iNode, stem.size() * 2 + index.size() + expr.size() + 11 ); |
| |
| Label tail = new Label(); |
| int failure_test = is_and? OP_iffalse : OP_iftrue; |
| |
| // Although ASC evaluates the stem twice, it only evaluates the index once. |
| // TODO: Inspect the index expression for side effects so the temp can be |
| // elided in most cases. |
| Binding index_temp = currentScope.allocateTemp(); |
| result.addAll(index); |
| result.addInstruction(index_temp.setlocal()); |
| |
| result.addAll(replicate(stem)); |
| result.addInstruction(index_temp.getlocal()); |
| result.addAll(stem); |
| result.addInstruction(index_temp.getlocal()); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| // Assume this is the result. |
| result.addInstruction(OP_dup); |
| result.addInstruction(failure_test, tail); |
| |
| // Pop the speculative result and assign the correct one. |
| result.addInstruction(OP_pop); |
| result.addAll(expr); |
| |
| result.labelNext(tail); |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| currentScope.releaseTemp(index_temp); |
| |
| return result; |
| } |
| |
| /** |
| * Generate a compound logical assignment expression to a foo.bar type lvalue |
| * @param iNode - the assignment operator (root of the subtree). |
| * @param stem - the expression that generates the lvalue's stem, e.g., a in a[i] |
| * @param index - the index expression. |
| * @param expr - the expression to assign. |
| * @param is_and - true if the expression is &&=, false if it's ||=. |
| * @param need_value - true if the expression's not used in a void context. |
| */ |
| InstructionList generateCompoundLogicalMemberAssignment(IASNode iNode, InstructionList stem, Binding member, InstructionList expr, int fetch_opcode, boolean is_and, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| int failure_test = is_and? OP_iffalse : OP_iftrue; |
| currentScope.getMethodBodySemanticChecker().checkCompoundAssignment(iNode, member, failure_test); |
| |
| Label tail = new Label(); |
| |
| result.addAll(replicate(stem)); |
| result.addAll(stem); |
| result.addInstruction(OP_getproperty, member.getName()); |
| // Assume this is the result. |
| result.addInstruction(OP_dup); |
| result.addInstruction(failure_test, tail); |
| |
| result.addInstruction(OP_pop); |
| result.addAll(expr); |
| |
| result.labelNext(tail); |
| Binding value_temp = null; |
| |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| result.addInstruction(getAssignmentOpcode(iNode, member), member.getName()); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| return result; |
| |
| } |
| |
| /** |
| * generateFunctionBody() wrapper suitable for calling from the BURM. |
| * See JBurg ENHRQ <N> : the grammar that accepts the BURM's parameters |
| * to a JBurg.Reduction routine doesn't grok function calls, so the BURM |
| * cannot call generateFunctionBody(body, name.getName()); |
| */ |
| InstructionList generateFunctionBody(IASNode iNode, InstructionList function_body, Binding return_type) |
| { |
| return generateFunctionBody(iNode, function_body, return_type != null? return_type.getName(): null); |
| } |
| |
| /** |
| * Generate boilerplate function prolog/epilog code. |
| * @param block - the actual CFG. |
| * @param return_type - the function's return type. |
| * @return the function body. |
| */ |
| InstructionList generateFunctionBody(IASNode iNode, InstructionList function_body, Name return_type) |
| { |
| currentScope.getMethodBodySemanticChecker().checkFunctionBody(iNode); |
| |
| currentScope.getMethodInfo().setReturnType(return_type); |
| InstructionList result = currentScope.finishMethodDeclaration( !function_body.isEmpty() || haveInstanceInitializers(), SemanticUtils.getFileName(iNode)); |
| |
| // Constructor-specific processing: add the instance initializers, |
| // add a constructsuper call if none exists. |
| if ( haveInstanceInitializers() && currentScope.getNestingState() == NestingState.NotNested) |
| { |
| result.addAll(this.instanceInitializers); |
| |
| // If this is a constructor and there's no explicit |
| // super() call, synthesize one. |
| // Note that this may be a semantic error if the |
| // superclass' constructor needs arguments. |
| if ( !function_body.hasSuchInstruction(OP_constructsuper) ) |
| { |
| currentScope.getMethodBodySemanticChecker().checkDefaultSuperCall(iNode); |
| // Call the superclass' constructor after the instance |
| // init instructions; this doesn't seem like an abstractly |
| // correct sequence, but it's what ASC does. |
| result.addInstruction(OP_getlocal0); |
| result.addInstruction(OP_constructsuper, 0); |
| } |
| } |
| |
| result.addAll(function_body); |
| |
| // Epilog code. |
| if ( result.canFallThrough() || result.hasPendingLabels() ) |
| { |
| // Synthesize a returnvoid instruction, using the |
| // single-purpose returnvoid so the caller can |
| // search for it. |
| |
| // If, at some point, the MBSC walks all functions' CFGs, |
| // then all returnvoid processing can be centralized and |
| // this specialized Instruction will be unnecessary. |
| result.addInstruction(synthesizedReturnVoid); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * This returnvoid instruction is used when the reducer injects a returnvoid at |
| * the end of a method body; the caller (with access to the MethodBodyInfo) can |
| * create a control flow graph and search it to emit a diagnostic as appropriate. |
| */ |
| public static final Instruction synthesizedReturnVoid = new NoOperandsInstruction(OP_returnvoid); |
| |
| /** |
| * Generate a named nested function. |
| * |
| * This will generate init instructions either in controlFlow?Sensitive?Destination, or in the current scopes hoisted init instructions. |
| * Most of the time the instuctions will go in the hoisted init instructions, but if the nested function is declared |
| * in a 'with' or 'catch' block then the init instructions need to go into the normal controlFlow?Sensitive?Destination. |
| * |
| * @param iNode - the function node for the nested function. |
| * @param controlFlowSensitiveDestination - the instruction list to add instructions to |
| * @param func_name - the function's name. |
| * @param return_type - the function's return type. |
| * @param function_body - the body of the function. |
| * @pre the function's lexical scope must be at the |
| * top of the lexical scope stack, with the declaring |
| * function's lexical scope under it. |
| * @post the nested function's MethodInfo is filled in, |
| * the function body is attached to its MethodBodyInfo, |
| * and the declaring function's initialization sequence |
| * gets code to declare the function. |
| */ |
| private void generateNestedFunction(IASNode iNode, InstructionList controlFlowSensitiveDestination, Binding func_name, Name return_type, InstructionList function_body) |
| { |
| currentScope.setFunctionName(func_name.getName().getBaseName()); |
| currentScope.generateNestedFunction(generateFunctionBody(iNode, function_body, return_type)); |
| // Pull the nested function's MethodInfo out of its scope before we pop it. |
| MethodInfo nested_method_info = currentScope.getMethodInfo(); |
| |
| currentScope = currentScope.popFrame(); |
| |
| // Initialize the nested function; add a variable |
| // to the containing function scope and add |
| // newfunction/setproperty logic to the containing |
| // function's hoisted initialization instructions. |
| currentScope.makeVariable(func_name); |
| |
| InstructionList init_insns = SemanticUtils.canNestedFunctionBeHoisted(iNode) ? currentScope.getHoistedInitInstructions() : controlFlowSensitiveDestination; |
| |
| // The containing function must be marked needsActivation() so the |
| // binding cannot be local. |
| assert(!func_name.isLocal()); |
| init_insns.addInstruction(OP_findproperty, func_name.getName()); |
| init_insns.addInstruction(OP_newfunction, nested_method_info); |
| init_insns.addInstruction(OP_setproperty, func_name.getName()); |
| } |
| |
| /** |
| * Generate a try/catch/finally (or try/finally) compound statement. |
| * @param try_stmt - the body of the try block. |
| * @param catch_blocks - associated catch blocks. |
| * May be null if no catch blocks are present. |
| * @param finally_stmt - the body of the finally block. |
| */ |
| InstructionList generateTryCatchFinally ( InstructionList try_stmt, Vector<CatchPrototype> catch_blocks, InstructionList finally_stmt) |
| { |
| InstructionList normal_flow_fixup = new InstructionList(); |
| InstructionList catch_insns = new InstructionList(); |
| InstructionList final_catch = new InstructionList(); |
| InstructionList finally_insns = new InstructionList(); |
| InstructionList final_throw = new InstructionList(); |
| |
| ExceptionHandlingContext finally_context = currentScope.getFlowManager().getFinallyContext(); |
| |
| Label final_catch_target = final_catch.getLabel(); |
| |
| // We need a local to store the caught exception. |
| Binding exception_storage = finally_context.getExceptionStorage(); |
| |
| Collection<Label> pending_normal_control_flow = try_stmt.stripPendingLabels(); |
| |
| if ( try_stmt.canFallThrough() || pending_normal_control_flow != null ) |
| { |
| normal_flow_fixup.addInstruction(OP_jump, finally_context.finallyBlock); |
| } |
| else |
| { |
| // Extend the region past a terminating |
| // throw statement to give the AVM a |
| // little buffer to figure out its |
| // exception-handling regions. |
| normal_flow_fixup.addInstruction(OP_nop); |
| } |
| |
| Label try_start = try_stmt.getLabel(); |
| Label try_end = normal_flow_fixup.getLastLabel(); |
| |
| Label finally_region_end = null; |
| |
| if ( null == catch_blocks ) |
| { |
| finally_region_end = try_end; |
| } |
| else |
| { |
| for ( CatchPrototype catch_proto: catch_blocks ) |
| { |
| |
| InstructionList catch_body = generateCatchBlock(try_start, try_end, catch_proto); |
| |
| boolean is_last_catch = catch_proto.equals(catch_blocks.lastElement()); |
| |
| if ( catch_body.canFallThrough() ) |
| { |
| // Signal the finally block that this execution succeeded. |
| catch_body.addInstruction(OP_pushbyte, 0); |
| catch_body.addInstruction(finally_context.finallyReturnStorage.setlocal()); |
| catch_body.addInstruction(OP_jump, finally_context.finallyBlock); |
| } |
| else if ( is_last_catch ) |
| { |
| // Extend the region past a terminating throw |
| // insn to give the AVM a little buffer. |
| catch_body.addInstruction(OP_nop); |
| } |
| |
| if ( is_last_catch ) |
| finally_region_end = catch_body.getLastLabel(); |
| |
| catch_insns.addAll(catch_body); |
| } |
| } |
| |
| // Set up the exception handler for the finally block. |
| currentScope.getMethodBodyVisitor().visitException(try_start, finally_region_end, final_catch_target, null, null); |
| |
| // The final catch block only needs to save the |
| // caught exception for a rethrow. |
| if ( currentScope.needsThis() ) |
| { |
| final_catch.addInstruction(OP_getlocal0); |
| final_catch.addInstruction(OP_pushscope); |
| } |
| |
| if( currentScope.needsActivation() ) |
| { |
| // Restore the activation object |
| final_catch.addInstruction(currentScope.getActivationStorage().getlocal()); |
| final_catch.addInstruction(OP_pushscope); |
| } |
| |
| final_catch.addAll(currentScope.getFlowManager().getScopeStackReinit()); |
| final_catch.addInstruction(exception_storage.setlocal()); |
| // Signal the finally epilog that this execution failed |
| // and should rethrow. |
| final_catch.addInstruction(OP_pushbyte, currentScope.getFlowManager().getFinallyAlternativesSize() + 1); |
| final_catch.addInstruction(finally_context.finallyReturnStorage.setlocal()); |
| |
| // falls through |
| // final_catch.addInstruction(OP_jump, finally_head); |
| |
| finally_insns.addInstruction(finally_context.finallyReturnStorage.getlocal()); |
| finally_insns.addInstruction(OP_convert_i); |
| finally_insns.addAll(currentScope.getFlowManager().getFinallySwitch()); |
| |
| // Label the start of the final set of instructions. |
| if (!finally_stmt.isEmpty()) |
| { |
| finally_stmt.labelFirst(finally_context.finallyBlock); |
| } |
| else |
| // This is just an expedient for this degenerate finally. |
| finally_insns.labelFirst(finally_context.finallyBlock); |
| |
| final_throw.addInstruction(exception_storage.getlocal()); |
| final_throw.labelCurrent(finally_context.finallyDoRethrow); |
| final_throw.addInstruction(OP_throw); |
| |
| // Assemble the statement. |
| InstructionList result = new InstructionList(); |
| |
| result.addInstruction(OP_pushbyte, 0); |
| result.addInstruction(finally_context.finallyReturnStorage.setlocal()); |
| |
| result.addAll(try_stmt); |
| |
| // Send all "next statement" type control flow into the finally block. |
| result.addAllPendingLabels(pending_normal_control_flow); |
| |
| result.addAll(normal_flow_fixup); |
| |
| result.addAll(catch_insns); |
| result.addAll(final_catch); |
| |
| result.addAll(finally_stmt); |
| result.addAll(finally_insns); |
| result.addAll(final_throw); |
| |
| for ( ExceptionHandlingContext.FinallyReturn retblock: finally_context.finallyReturns ) |
| result.addAll(retblock.getInstructions()); |
| |
| // TODO: Need more analysis of how this temp travels through |
| // the system before it can be safely released. For now |
| // just leak it. |
| // currentScope.releaseTemp(exception_storage); |
| |
| // TODO: Removing a hanging kill exposed a latent bug |
| // in hasPendingLabels() and end-of-routine processing. |
| // Give the CG a harmless instruction that will generate |
| // a returnvoid if this is the last statement in the routine. |
| result.addInstruction(OP_nop); |
| |
| // Fallthrough out of the finally block. |
| result.labelNext(finally_context.finallyDoFallthrough); |
| |
| return result; |
| } |
| |
| /** |
| * Reduce a name to an instruction list that can be used in a parameterized type expression. |
| * This is different from the normal name -> expression because '*' maps to pushnull. |
| * @param iNode the node |
| * @param param_name the name to reduce |
| * @return IL with instructions to push the appropriate value on the stack |
| */ |
| public InstructionList reduce_typeNameParameter(IASNode iNode, Binding param_name) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| generateTypeNameParameter(param_name, result); |
| |
| return result; |
| } |
| |
| /** |
| * Generate the instruction sequence that designates |
| * the parameter of a parameterized type, e.g., |
| * String in Vector.<String> or * in Vector.<*>. |
| * @param param_node - the type parameter's node. |
| * @param param_name - the type parameter's name. |
| * May be null in the * case. |
| * @param result - the instruction sequence to generate into. |
| */ |
| private void generateTypeNameParameter(Binding param, InstructionList result) |
| { |
| if ( param == null || param.getName() == null || param.getName().couldBeAnyType() ) |
| result.addInstruction(OP_pushnull); |
| else |
| generateAccess(param, false, AccessType.Strict, result); |
| } |
| |
| /** |
| * @return A |
| * {@link ControlFlowContextManager.ControlFlowContextSearchCriteria} that |
| * will find the control context that a break statement without a label in |
| * the current control flow context refers to. This should always be a |
| * control flow context for any of: |
| * <ul> |
| * <li>a labeled statement</li> |
| * <li>a loop</li> |
| * <li>a switch statement</li> |
| * </ul> |
| */ |
| private ControlFlowContextManager.ControlFlowContextSearchCriteria getBreakCriteria() |
| { |
| return ControlFlowContextManager.breakWithOutLabelCriteria; |
| } |
| |
| /** |
| * @return A |
| * {@link ControlFlowContextManager.ControlFlowContextSearchCriteria} that |
| * will find the control context that a continue statement without a label |
| * in the current control flow context refers to. This should always be a |
| * control flow context for a loop. |
| */ |
| private ControlFlowContextManager.ControlFlowContextSearchCriteria getContinueCriteria() |
| { |
| return ControlFlowContextManager.continueWithOutLabelCriteria; |
| } |
| |
| /** |
| * @param Label name in a break statement with a label. |
| * @return A |
| * {@link ControlFlowContextManager.ControlFlowContextSearchCriteria} that |
| * will find the control context that a break statement with a label in the |
| * current control flow context refers to. This should always be a control |
| * flow context for labeled statement. |
| */ |
| private ControlFlowContextManager.ControlFlowContextSearchCriteria getBreakCriteria(String label) |
| { |
| return currentScope.getFlowManager().breakWithLabelCriteria(label); |
| } |
| |
| /** |
| * @param Label name in a continue statement with a label. |
| * @return A |
| * {@link ControlFlowContextManager.ControlFlowContextSearchCriteria} that |
| * will find the control context that a continue statement with a label |
| * in the current control flow context refers to. This should always be a |
| * control flow context for a loop. |
| */ |
| private ControlFlowContextManager.ControlFlowContextSearchCriteria getContinueCriteria(String label) |
| { |
| return currentScope.getFlowManager().continueWithLabelCriteria(label); |
| } |
| |
| /** |
| * @param Label name in a goto statement. |
| * @return A |
| * {@link ControlFlowContextManager.ControlFlowContextSearchCriteria} that |
| * will find the control context that a goto statement in the |
| * current control flow context refers to. This should always be a control |
| * flow context for labeled statement. |
| */ |
| private ControlFlowContextManager.ControlFlowContextSearchCriteria getGotoCriteria(String label) |
| { |
| return currentScope.getFlowManager().gotoLabelCriteria(label, false); |
| } |
| |
| /** |
| * Constructs a sequence of code that will jump to the proper location in the |
| * control flow context matched by the criteria object. The returned code will |
| * run finally blocks and adjust the scope stack if needed. |
| * @param criteria Criteria object that matches the control flow context and specifies the location |
| * in that context to which the generated code will jump to. |
| * @return An {@link InstructionList} that will jump to the proper location in the |
| * control flow context matched by the criteria object |
| * @throws UnknownControlFlowTargetException |
| */ |
| private InstructionList getBranchTarget(ControlFlowContextManager.ControlFlowContextSearchCriteria criteria) |
| throws UnknownControlFlowTargetException |
| { |
| return currentScope.getFlowManager().getBranchTarget(criteria); |
| } |
| |
| /** |
| * @return the double content of a numeric literal. |
| * @param iNode - the literal node. |
| */ |
| Double getDoubleContent(IASNode iNode) |
| { |
| return SemanticUtils.getDoubleContent(iNode); |
| } |
| |
| /** |
| * @return the name of an identifier. |
| * @param iNode - the IIdentifier node. |
| */ |
| String getIdentifierContent(IASNode iNode) |
| { |
| return SemanticUtils.getIdentifierContent(iNode); |
| } |
| |
| /** |
| * @return the int content of a numeric literal. |
| * @param iNode - the literal node. |
| */ |
| Integer getIntegerContent(IASNode iNode) |
| { |
| return SemanticUtils.getIntegerContent(iNode); |
| } |
| |
| /** |
| * @return always zero. |
| * @param iNode - the literal node. |
| */ |
| Integer getIntegerZeroContent(IASNode iNode) |
| { |
| return 0; |
| } |
| |
| /** |
| * @return always zero. |
| * @param iNode - the literal node. |
| */ |
| Long getIntegerZeroContentAsLong(IASNode iNode) |
| { |
| return 0L; |
| } |
| |
| /** |
| * @return a node cast to MXMLEventSpecifierNode type. |
| * @param iNode - the MXMLEventSpecifierNode node. |
| */ |
| IMXMLEventSpecifierNode getMXMLEventSpecifierContent(IASNode iNode) |
| { |
| return SemanticUtils.getMXMLEventSpecifierContent(iNode); |
| } |
| |
| /** |
| * Find all active exception handling blocks or scopes, |
| * and set up finally return sequences and/or popscopes. |
| * @param original - the original control-flow sequence. |
| * @param criterion - the search criterion that identifies |
| * the boundary of the region to be exited. |
| * @return a control-flow sequence that is either the |
| * original sequence or the start of a trampoline of |
| * finally blocks, popscopes, and whatever else is |
| * necessary to exit the control-flow region. |
| * @throws UnknownControlFlowTargetException if the criterion is not on the control-flow stack. |
| */ |
| private InstructionList getNonLocalControlFlow(InstructionList original, ControlFlowContextManager.ControlFlowContextSearchCriteria criterion) |
| throws UnknownControlFlowTargetException |
| { |
| return currentScope.getFlowManager().getNonLocalControlFlow(original, criterion); |
| } |
| |
| /** |
| * Find all active exception handling blocks or scopes, |
| * and set up finally return sequences and/or popscopes. |
| * @param criterion - the search criterion that identifies |
| * the boundary of the region to be exited and the target |
| * in the found region to jump to. |
| * @return a control-flow sequence that is either the |
| * original sequence or the start of a trampoline of |
| * finally blocks, popscopes, and whatever else is |
| * necessary to exit the control-flow region. |
| * @throws UnknownControlFlowTargetException if the criterion is not on the control-flow stack. |
| */ |
| private InstructionList getNonLocalControlFlow(ControlFlowContextManager.ControlFlowContextSearchCriteria criterion) |
| throws UnknownControlFlowTargetException |
| { |
| return currentScope.getFlowManager().getNonLocalControlFlow(getBranchTarget(criterion), criterion); |
| } |
| |
| /** |
| * @return a parameter's definition. |
| * @param iNode - the parameter node. |
| */ |
| IDefinition getParameterContent(IASNode iNode) |
| { |
| return SemanticUtils.getParameterContent(iNode); |
| } |
| |
| /** |
| * @return the string content of a literal. |
| * @param iNode - the literal node. |
| */ |
| String getStringLiteralContent(IASNode iNode) |
| { |
| return SemanticUtils.getStringLiteralContent(iNode); |
| } |
| |
| /** |
| * @return the uint content of a numeric literal. |
| * @param iNode - the literal node. |
| */ |
| Long getUintContent(IASNode iNode) |
| { |
| return SemanticUtils.getUintContent(iNode); |
| } |
| |
| /** |
| * @return the Boolean content of a BOOLEAN literal. |
| * @param iNode - the literal node. |
| */ |
| boolean getBooleanContent(IASNode iNode) |
| { |
| return SemanticUtils.getBooleanContent(iNode); |
| } |
| |
| /** |
| * @return true if instance initialization instructions are present. |
| */ |
| private boolean haveInstanceInitializers() |
| { |
| return instanceInitializers != null; |
| } |
| |
| /* |
| * |
| * ******************************* |
| * ** Cost/Decision Functions ** |
| * ******************************* |
| * |
| */ |
| |
| /** |
| * Explore a MemberAccessNode and |
| * decide if its stem is a reference |
| * to a package. |
| * |
| * This method will always return a result greater than what isPackageName will return, |
| * as package name must "win" over dotted name. |
| */ |
| int isDottedName(IASNode n) |
| { |
| int result = Integer.MAX_VALUE; |
| |
| if ( n instanceof MemberAccessExpressionNode ) |
| { |
| MemberAccessExpressionNode ma = (MemberAccessExpressionNode)n; |
| |
| if(ma.stemIsPackage()) |
| // This needs to be greater than the value returned from isPackageName, |
| // so that isPackageName wins |
| result = 2; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Explore a MemberAccessNode and |
| * decide if it is a reference |
| * to a package. |
| * |
| * This method will always return a result less than what isDottedName will return, |
| * as package name must "win" over dotted name. |
| */ |
| int isPackageName(IASNode n) |
| { |
| int result = Integer.MAX_VALUE; |
| |
| if ( n instanceof MemberAccessExpressionNode ) |
| { |
| MemberAccessExpressionNode ma = (MemberAccessExpressionNode)n; |
| |
| if(ma.isPackageReference()) |
| // This needs to be less than the value returned from isDottedName, |
| // so that isPackageName wins |
| result = 1; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Get the definition associated with a node's qualifier |
| * and decide if the qualifier is a compile-time constant. |
| * @param iNode - the node to check. |
| * @pre - the node has an IdentifierNode 0th child. |
| * @return an attractive cost if the child has a known namespace, i.e., |
| * it's a compile-time constant qualifier. |
| */ |
| int qualifierIsCompileTimeConstant(IASNode iNode) |
| { |
| IdentifierNode qualifier = (IdentifierNode)SemanticUtils.getNthChild(iNode, 0); |
| IDefinition def = qualifier.resolve(currentScope.getProject()); |
| |
| int result = def instanceof NamespaceDefinition ? 1 : Integer.MAX_VALUE; |
| return result; |
| } |
| |
| /** |
| * @return a feasible cost if a node has a compile-time constant defintion. |
| */ |
| int isCompileTimeConstant(IASNode iNode) |
| { |
| if ( SemanticUtils.transformNameToConstantValue(iNode, currentScope.getProject()) != null ) |
| return 1; |
| else |
| return Integer.MAX_VALUE; |
| } |
| |
| /** |
| * Check a Binding to decide if its referent |
| * is statically known to be a Namespace. |
| * @param b - the Binding to check. |
| * @return true if the given Binding refers to a namespace. |
| */ |
| private boolean isNamespace(Binding b) |
| { |
| return b.getDefinition() instanceof NamespaceDefinition; |
| } |
| |
| /** |
| * Check a Binding to see if it's defined as the ANY namespace. |
| * @param b - the Binding to check. |
| * @return true if the Binding's definition is the ANY namespace definition. |
| */ |
| private boolean isAnyNamespace(Binding b) |
| { |
| return b.getDefinition().equals(NamespaceDefinition.getAnyNamespaceReference()); |
| } |
| |
| /** |
| * @return a feasible cost (1) if the node is for 'new Array()' |
| */ |
| int isEmptyArrayConstructor(IASNode iNode) |
| { |
| ICompilerProject project = currentScope.getProject(); |
| IIdentifierNode identifierNode = (IIdentifierNode)SemanticUtils.getNthChild(iNode, 1); |
| if (identifierNode.resolve(project) == project.getBuiltinType(BuiltinType.ARRAY)) |
| return 1; |
| |
| return Integer.MAX_VALUE; |
| } |
| |
| /** |
| * @return a feasible cost (1) if the node is for 'new Object()' |
| */ |
| int isEmptyObjectConstructor(IASNode iNode) |
| { |
| ICompilerProject project = currentScope.getProject(); |
| IIdentifierNode identifierNode = (IIdentifierNode)SemanticUtils.getNthChild(iNode, 1); |
| if (identifierNode.resolve(project) == project.getBuiltinType(BuiltinType.OBJECT)) |
| return 1; |
| |
| return Integer.MAX_VALUE; |
| } |
| |
| /* |
| * ************************ |
| * ** Reduction prologues |
| * ************************ |
| */ |
| |
| public void prologue_anonymousFunction(IASNode iNode) |
| { |
| // TODO: The current scope may not |
| // need an activation if the anonymous |
| // function doesn't access any of the |
| // current scope's locals. |
| currentScope = currentScope.pushFrame(); |
| currentScope.declareAnonymousFunction(); |
| currentScope.setInitialControlFlowRegionNode(((FunctionObjectNode)iNode).getFunctionNode().getScopedNode()); |
| } |
| |
| public void prologue_functionObject(IASNode iNode) |
| { |
| final FunctionNode innerFunctionNode = ((FunctionObjectNode)iNode).getFunctionNode(); |
| currentScope = currentScope.pushFrame(); |
| currentScope.declareFunctionObject(innerFunctionNode.getName()); |
| currentScope.setInitialControlFlowRegionNode(innerFunctionNode.getScopedNode()); |
| prologue_function(iNode); |
| } |
| |
| public void prologue_blockStmt(IASNode iNode) |
| { |
| // Note: don't create an InstructionList with |
| // line number information here; this IL is |
| // a pure placeholder. |
| this.miniScopes.push(new InstructionList()); |
| } |
| |
| public void prologue_blockStmt_to_finally_clause(IASNode iNode) |
| { |
| currentScope.getFlowManager().startFinallyContext(iNode); |
| } |
| |
| public void prologue_catchBlock(IASNode iNode) |
| { |
| currentScope.getFlowManager().startCatchContext((ICatchNode)iNode); |
| } |
| |
| private void loop_prologue_common(IASNode loopNode, IASNode loopContents) |
| { |
| currentScope.getFlowManager().startLoopControlFlowContext(loopContents); |
| } |
| |
| public void prologue_countedForStmt(IASNode iNode) |
| { |
| loop_prologue_common(iNode, ((IForLoopNode)iNode).getStatementContentsNode()); |
| } |
| |
| public void prologue_doStmt(IASNode iNode) |
| { |
| loop_prologue_common(iNode, ((IWhileLoopNode)iNode).getStatementContentsNode()); |
| } |
| |
| public void prologue_forEachStmt(IASNode iNode) |
| { |
| loop_prologue_common(iNode, ((IForLoopNode)iNode).getStatementContentsNode()); |
| } |
| |
| public void prologue_forInStmt(IASNode iNode) |
| { |
| loop_prologue_common(iNode, ((IForLoopNode)iNode).getStatementContentsNode()); |
| } |
| |
| void prologue_function(IASNode n) |
| { |
| FunctionDefinition func = null; |
| |
| IFunctionNode funcNode = null; |
| |
| if ( n instanceof IFunctionNode ) |
| funcNode = (IFunctionNode)n; |
| else if( n instanceof FunctionObjectNode ) |
| funcNode = ((FunctionObjectNode)n).getFunctionNode(); |
| |
| // Add any parse problems which might have been encountered while lazily parsing the function |
| if( funcNode instanceof FunctionNode ) |
| { |
| Collection<ICompilerProblem> parseProblems = ((FunctionNode) funcNode).getParsingProblems(); |
| for( ICompilerProblem problem : parseProblems ) |
| currentScope.addProblem(problem); |
| } |
| |
| assert funcNode != null : n + " has no FunctionNode child"; |
| func = (FunctionDefinition)funcNode.getDefinition(); |
| assert(func != null): n + " has no definition."; |
| |
| currentScope.setLocalASScope(func.getContainedScope()); |
| currentScope.resetDebugInfo(); |
| |
| // Set the current file name - the function body will always start with an OP_debugfile |
| // so we never need emit another one unless the method body spans multiple files |
| currentScope.setDebugFile(SemanticUtils.getFileName(n)); |
| |
| currentScope.getMethodBodySemanticChecker().checkFunctionDefinition(funcNode, func); |
| for ( int i = 0; i < funcNode.getChildCount(); i++ ) |
| scanFunctionBodyForActivations(funcNode.getChild(i)); |
| } |
| |
| public void prologue_labeledStmt(IASNode iNode) |
| { |
| // Since the Prologue code runs before the subtrees have been reduced, |
| // we can't get the label string via the non_resolving_identifier reduction. |
| try |
| { |
| currentScope.getFlowManager().startLabeledStatementControlFlowContext((LabeledStatementNode) iNode); |
| } |
| catch ( DuplicateLabelException dup_label ) |
| { |
| currentScope.addProblem(new DuplicateLabelProblem(iNode)); |
| } |
| } |
| |
| public void prologue_mxmlEventSpecifier(IASNode iNode) |
| { |
| // The method that implements an event specifier has a single parameter. |
| // Its name is "event" (which is discovered from the ParameterDefinition |
| // that the event node creates). |
| // Its type, such as flash.events.MouseEvent, has already been determined |
| // when we created the MethodInfo, so we access it there |
| // rather than creatinga another equivalent Name. |
| currentScope.makeParameter( |
| getMXMLEventSpecifierContent(iNode).getEventParameterDefinition(), |
| currentScope.getMethodInfo().getParamTypes().get(0)); |
| |
| } |
| |
| public void prologue_switchStmt(IASNode iNode) |
| { |
| // Establish a switch context so that breaks inside the switch statement will work. |
| currentScope.getFlowManager().startSwitchContext((SwitchNode)iNode); |
| } |
| |
| public void prologue_tryCatchFinallyStmt(IASNode iNode) |
| { |
| currentScope.getFlowManager().startExceptionContext((ITryNode) iNode); |
| currentScope.getFlowManager().getFinallyContext().setHasFinallyBlock(true); |
| } |
| |
| public void prologue_tryCatchStmt(IASNode iNode) |
| { |
| currentScope.getFlowManager().startExceptionContext((ITryNode) iNode); |
| } |
| |
| public void prologue_tryFinallyStmt(IASNode iNode) |
| { |
| currentScope.getFlowManager().startExceptionContext((ITryNode) iNode); |
| currentScope.getFlowManager().getFinallyContext().setHasFinallyBlock(true); |
| } |
| |
| public void prologue_typedFunction_to_statement(IASNode iNode) |
| { |
| // Note: A named nested function does |
| // always need an activation record. |
| currentScope = currentScope.pushFrame(); |
| currentScope.declareNestedFunction(); |
| currentScope.setInitialControlFlowRegionNode(((IFunctionNode)iNode).getScopedNode()); |
| prologue_function(iNode); |
| } |
| |
| public void prologue_typelessFunction_to_statement(IASNode iNode) |
| { |
| // Note: A named nested function does |
| // always need an activation record. |
| currentScope = currentScope.pushFrame(); |
| currentScope.declareNestedFunction(); |
| currentScope.setInitialControlFlowRegionNode(((IFunctionNode)iNode).getScopedNode()); |
| prologue_function(iNode); |
| } |
| |
| /** |
| * Count the number of typeof levels active. |
| */ |
| private int typeofCount = 0; |
| |
| /** |
| * Before reducing the operands of a typeof, |
| * we need to increment the typeof counter. |
| * It will be decremented by the reduction's |
| * epilogue. |
| */ |
| public void prologue_typeof(IASNode iNode) |
| { |
| this.typeofCount++; |
| } |
| |
| public void prologue_whileStmt(IASNode iNode) |
| { |
| currentScope.getFlowManager().startLoopControlFlowContext(((IWhileLoopNode)iNode).getStatementContentsNode()); |
| } |
| |
| public void prologue_withStmt(IASNode iNode) |
| { |
| currentScope.getFlowManager().startWithContext(((IWithNode)iNode).getStatementContentsNode()); |
| } |
| |
| /* |
| * ************************* |
| * ** Reduction actions ** |
| * ************************* |
| */ |
| |
| |
| public InstructionList reduce_anonymousFunction(IASNode iNode, InstructionList function_body) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| |
| currentScope.generateNestedFunction(function_body); |
| result.addInstruction(OP_newfunction, currentScope.getMethodInfo()); |
| |
| currentScope = currentScope.popFrame(); |
| return result; |
| } |
| |
| public InstructionList reduce_functionObject(IASNode iNode, IASNode function_name, InstructionList plist, Binding return_binding, InstructionList function_body) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| Binding nestedFunctionName = currentScope.resolveName((IdentifierNode)function_name); |
| Name return_type; |
| |
| if ( return_binding != null ) |
| return_type = return_binding.getName(); |
| else |
| return_type = null; |
| |
| currentScope.generateNestedFunction(generateFunctionBody(iNode, function_body, return_type)); |
| // Pull the nested function's MethodInfo out of its scope before we pop it. |
| MethodInfo nested_method_info = currentScope.getMethodInfo(); |
| currentScope = currentScope.popFrame(); |
| |
| // Create a wrapper routine that returns the named |
| // routine wrapped in a closure; it's an anonymous |
| // function with an activation record. |
| currentScope = currentScope.pushFrame(); |
| currentScope.declareAnonymousFunction(); |
| currentScope.setInitialControlFlowRegionNode(((FunctionObjectNode)iNode).getFunctionNode().getScopedNode()); |
| currentScope.setNeedsActivation(); |
| |
| currentScope.makeVariable( |
| nestedFunctionName, |
| SemanticUtils.getAETName(currentScope.getProject().getBuiltinType(BuiltinType.FUNCTION), currentScope.getProject()), |
| null, // no meta tags |
| null, // no initializer |
| LexicalScope.VariableMutability.Constant |
| ); |
| |
| InstructionList wrapper_insns = createInstructionList(iNode); |
| |
| wrapper_insns.addInstruction(OP_newfunction, nested_method_info); |
| wrapper_insns.addInstruction(OP_dup); |
| wrapper_insns.addInstruction(OP_findproperty, nestedFunctionName.getName()); |
| wrapper_insns.addInstruction(OP_swap); |
| wrapper_insns.addInstruction(OP_setslot, 1); |
| wrapper_insns.addInstruction(OP_returnvalue); |
| currentScope.generateNestedFunction(generateFunctionBody(iNode, wrapper_insns, LexicalScope.anyType)); |
| |
| MethodInfo wrapper_method_info = currentScope.getMethodInfo(); |
| currentScope = currentScope.popFrame(); |
| |
| result.addInstruction(OP_newfunction, wrapper_method_info); |
| // Doesn't matter much what we use as "this" -- |
| // the store acts on the activation. |
| result.addInstruction(OP_getlocal0); |
| result.addInstruction(OP_call, 0); |
| |
| return result; |
| |
| } |
| |
| public InstructionList reduce_arrayIndexExpr(IASNode iNode, InstructionList stem, boolean is_super, InstructionList index) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)iNode; |
| |
| if ( is_super ) |
| currentScope.getMethodBodySemanticChecker().checkSuperAccess(arrayIndexNode); |
| |
| InstructionList result; |
| |
| if ( !is_super ) |
| { |
| result = createInstructionList(arrayIndexNode, stem.size() + index.size() + 1); |
| result.addAll(stem); |
| } |
| else |
| { |
| result = createInstructionList(arrayIndexNode, index.size() + 2); |
| // Use "this" as the stem. |
| result.addInstruction(OP_getlocal0); |
| } |
| result.addAll(index); |
| |
| result.addInstruction(arrayAccess(arrayIndexNode, is_super? OP_getsuper: OP_getproperty)); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_arrayLiteral(IASNode iNode, Vector<InstructionList> elements) |
| { |
| // No semantic restrictions on array literals. |
| |
| // TODO: Investigate optimizing. |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList element: elements ) |
| result.addAll(element); |
| |
| result.addInstruction(OP_newarray, elements.size()); |
| return result; |
| } |
| |
| public InstructionList reduce_assignToBracketExpr_to_expression(IASNode iNode, InstructionList stem, InstructionList index, InstructionList r, boolean is_super) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IBinaryOperatorNode)iNode).getLeftOperandNode(); |
| |
| currentScope.getMethodBodySemanticChecker().checkAssignToBracketExpr(iNode); |
| |
| Binding local = currentScope.allocateTemp(); |
| |
| InstructionList result = createInstructionList(iNode, (is_super ? 1:stem.size()) + index.size() + r.size() + 5); |
| if( is_super ) |
| result.addInstruction(OP_getlocal0); |
| else |
| result.addAll(stem); |
| |
| result.addAll(index); |
| result.addAll(r); |
| result.addInstruction(OP_dup); |
| result.addInstruction(local.setlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, is_super ? OP_setsuper : OP_setproperty)); |
| result.addInstruction(local.getlocal() ); |
| |
| currentScope.releaseTemp(local); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToBracketExpr_to_void_expression(IASNode iNode, InstructionList stem, InstructionList index, InstructionList r, boolean is_super) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IBinaryOperatorNode)iNode).getLeftOperandNode(); |
| |
| /* |
| ITypeDefinition destinationType = arrayIndexNode.resolveType(currentScope.getProject()); |
| IExpressionNode valueNode = ((IBinaryOperatorNode)iNode).getRightOperandNode(); |
| |
| IDefinition type_def = valueNode.resolveType(currentScope.getProject()); //SemanticUtils.getDefinitionOfUnderlyingType(, |
| // true, currentScope.getProject()); |
| boolean need_coerce = !type_def.equals(destinationType) || (valueNode instanceof TernaryOperatorNode); |
| */ |
| |
| currentScope.getMethodBodySemanticChecker().checkAssignToBracketExpr(iNode); |
| |
| int num_ins = /*need_coerce ? 2 :*/ 1; |
| |
| InstructionList result = createInstructionList(iNode, (is_super ? num_ins : stem.size()) + index.size() + r.size() + num_ins); |
| if( is_super ) |
| result.addInstruction(OP_getlocal0); |
| else |
| result.addAll(stem); |
| result.addAll(index); |
| result.addAll(r); |
| /* |
| if (need_coerce) |
| coerce(result, destinationType); |
| */ |
| result.addInstruction(arrayAccess(arrayIndexNode, is_super ? OP_setsuper : OP_setproperty)); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToMemberExpr_to_expression(IASNode iNode, InstructionList stem, Binding member, InstructionList r) |
| { |
| currentScope.getMethodBodySemanticChecker().checkAssignment(iNode, member); |
| |
| Binding local = currentScope.allocateTemp(); |
| |
| InstructionList result = createInstructionList(iNode, stem.size() + r.size() + 5); |
| result.addAll(stem); |
| result.addAll(r); |
| result.addInstruction(OP_dup); |
| result.addInstruction(local.setlocal() ); |
| generateAssignmentOp(iNode, member, result); |
| result.addInstruction(local.getlocal() ); |
| |
| currentScope.releaseTemp(local); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToMemberExpr_to_void_expression(IASNode iNode, InstructionList stem, Binding member, InstructionList r) |
| { |
| currentScope.getMethodBodySemanticChecker().checkAssignment(iNode, member); |
| |
| InstructionList result = createInstructionList(iNode, stem.size() + r.size() + 1); |
| result.addAll(stem); |
| result.addAll(r); |
| |
| generateAssignmentOp(iNode, member, result); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToDescendantsExpr(IASNode iNode, InstructionList stem, Binding member, InstructionList r, boolean need_value) |
| { |
| // Note: This code doesn't assign to descendants! |
| // While one might think it should, it does in fact |
| // mirror the bytecode ASC emitted. |
| currentScope.getMethodBodySemanticChecker().checkAssignment(iNode, member); |
| |
| Binding local = null; |
| |
| InstructionList result = createInstructionList(iNode, stem.size() + r.size() + 5); |
| result.addAll(stem); |
| result.addAll(r); |
| if ( need_value ) |
| { |
| local = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(local.setlocal() ); |
| } |
| |
| generateAssignmentOp(iNode, member, result); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(local.getlocal()); |
| currentScope.releaseTemp(local); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToNameExpr_to_void_expression(IASNode iNode, Binding lvalue, InstructionList r) |
| { |
| currentScope.getMethodBodySemanticChecker().checkAssignment(iNode, lvalue); |
| return generateAssignment(iNode, lvalue, r); |
| } |
| |
| public InstructionList reduce_assignToNameExpr_to_expression(IASNode iNode, Binding lvalue, InstructionList r) |
| { |
| currentScope.getMethodBodySemanticChecker().checkAssignment(iNode, lvalue); |
| return generateAssignment(iNode, lvalue, r, true); |
| } |
| |
| public InstructionList reduce_assignToQualifiedMemberExpr(IASNode iNode, InstructionList stem, Binding qualifier, Binding member, InstructionList rhs, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| |
| if ( !isNamespace(qualifier) ) |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| } |
| // else the qualifier's namespace is already present in the name. |
| result.addAll(rhs); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| generateAssignmentOp(iNode, member, result); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToQualifiedRuntimeMemberExpr(IASNode iNode, InstructionList stem, Binding qualifier, InstructionList runtime_member_selector, InstructionList rhs, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| if ( isNamespace(qualifier) ) |
| { |
| result.addAll(runtime_member_selector); |
| // Extract the URI from the namespace and use it to construct a qualified name. |
| NamespaceDefinition ns_def = (NamespaceDefinition)qualifier.getDefinition(); |
| Name qualified_name = new Name(CONSTANT_MultinameL, new Nsset(ns_def.resolveAETNamespace(currentScope.getProject())), null); |
| result.addAll(rhs); |
| result.addInstruction(OP_setproperty, qualified_name); |
| } |
| else |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addAll(runtime_member_selector); |
| result.addAll(rhs); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| result.addInstruction(OP_setproperty, new Name(CONSTANT_RTQnameL, null, null)); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| } |
| return result; |
| } |
| |
| public InstructionList reduce_assignToQualifiedAttributeExpr(IASNode iNode, InstructionList stem, Binding qualifier, Binding member, InstructionList rhs, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| |
| if ( !isNamespace(qualifier) ) |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| } |
| // else the qualifier's namespace is already present in the name. |
| |
| result.addAll(rhs); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| generateAssignmentOp(iNode, member, result); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToQualifiedRuntimeAttributeExpr(IASNode iNode, InstructionList stem, Binding qualifier, InstructionList runtime_member_selector, InstructionList rhs, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| |
| if ( isAnyNamespace(qualifier) ) |
| { |
| IASNode site = qualifier.getNode() != null? qualifier.getNode(): iNode; |
| |
| currentScope.addProblem(new AnyNamespaceCannotBeQualifierProblem(site)); |
| } |
| else if ( ! isNamespace(qualifier) ) |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addAll(runtime_member_selector); |
| result.addInstruction(OP_coerce_s); |
| result.addAll(rhs); |
| Binding value_temp = null; |
| |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| result.addInstruction(OP_setproperty, new Name(CONSTANT_RTQnameLA, null, null)); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| } |
| else |
| { |
| result.addAll(runtime_member_selector); |
| // Extract the URI from the namespace and use it to construct a qualified name. |
| NamespaceDefinition ns_def = (NamespaceDefinition)qualifier.getDefinition(); |
| Name qualified_name = new Name(CONSTANT_MultinameLA, new Nsset(ns_def.resolveAETNamespace(currentScope.getProject())), null); |
| result.addAll(rhs); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| result.addInstruction(OP_setproperty, qualified_name); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| } |
| return result; |
| } |
| |
| |
| public InstructionList reduce_assignToRuntimeNameExpr(IASNode iNode, RuntimeMultiname lval, InstructionList r, final boolean need_value) |
| { |
| InstructionList result; |
| |
| if ( need_value ) |
| { |
| // TODO: In many cases this temp can be avoided by more sophisticated analysis. |
| // TODO: See also comments in RuntimeMultiname.generateGetOrSet(). |
| Binding temp = currentScope.allocateTemp(); |
| result = createInstructionList(iNode); |
| result.addAll(r); |
| result.addInstruction(temp.setlocal()); |
| InstructionList temp_rhs = new InstructionList(); |
| temp_rhs.addInstruction(temp.getlocal()); |
| result.addAll(lval.generateAssignment(iNode, temp_rhs)); |
| result.addInstruction(temp.getlocal()); |
| currentScope.releaseTemp(temp); |
| } |
| else |
| { |
| result = lval.generateAssignment(iNode, r); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_assignToUnqualifiedRuntimeAttributeExpr(IASNode iNode, InstructionList stem, InstructionList rt_attr, InstructionList rhs, boolean need_value) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addAll(rt_attr); |
| result.addAll(rhs); |
| |
| Binding value_temp = null; |
| if ( need_value ) |
| { |
| value_temp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal()); |
| } |
| |
| result.addInstruction(OP_setproperty, new Name(CONSTANT_MultinameLA, new Nsset(new Namespace(CONSTANT_PackageNs)), null)); |
| |
| if ( need_value ) |
| { |
| result.addInstruction(value_temp.getlocal()); |
| currentScope.releaseTemp(value_temp); |
| } |
| |
| return result; |
| } |
| |
| public Binding reduce_attributeName(IASNode iNode, Binding attr_name) |
| { |
| // do nothing - just return the contained name |
| // which will already be set up correctly for an attribute name |
| return attr_name; |
| } |
| |
| public InstructionList reduce_blockStmt_to_finally_clause(IASNode iNode, Vector<InstructionList> stmts) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList stmt: stmts ) |
| result.addAll(stmt); |
| |
| currentScope.getFlowManager().endFinallyContext(); |
| return result; |
| |
| } |
| |
| public InstructionList reduce_blockStmt_to_statement(IASNode iNode, Vector<InstructionList> stmts) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(this.miniScopes.pop()); |
| |
| for ( InstructionList stmt: stmts ) |
| { |
| // Some statement-level reductions return null. |
| if ( stmt != null ) |
| { |
| result.addAll(stmt); |
| } |
| } |
| |
| return result; |
| } |
| |
| public Boolean reduce_booleanLiteral(IASNode iNode) |
| { |
| return SemanticUtils.getBooleanContent(iNode); |
| } |
| |
| public InstructionList reduce_breakStmt(IASNode iNode) |
| { |
| try |
| { |
| ControlFlowContextManager.ControlFlowContextSearchCriteria criterion = getBreakCriteria(); |
| return getNonLocalControlFlow(criterion); |
| } |
| catch ( UnknownControlFlowTargetException no_target ) |
| { |
| currentScope.addProblem(new UnknownBreakTargetProblem(iNode)); |
| return createInstructionList(iNode); |
| } |
| |
| } |
| |
| public String reduce_by_concatenation(IASNode iNode, String first, String second) |
| { |
| return first + "." + second; |
| } |
| |
| public CatchPrototype reduce_catchBlockTyped(IASNode iNode, Binding var_name, Binding exception, InstructionList action) |
| { |
| CatchPrototype result = new CatchPrototype(); |
| result.catchVarName = var_name.getName(); |
| result.catchType = exception.getName(); |
| result.catchBody = action; |
| |
| currentScope.getFlowManager().endCatchContext(); |
| return result; |
| } |
| |
| public CatchPrototype reduce_catchBlockUntyped(IASNode iNode, Binding var_name, InstructionList action) |
| { |
| CatchPrototype result = new CatchPrototype(); |
| result.catchVarName = var_name.getName(); |
| result.catchType = null; |
| result.catchBody = action; |
| |
| currentScope.getFlowManager().endCatchContext(); |
| return result; |
| } |
| |
| public InstructionList reduce_commaExpr(IASNode iNode, InstructionList payload_expr, Vector<InstructionList> exprs) |
| { |
| // TODO: Investigate optimizing this. |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList other_expr: exprs ) |
| result.addAll(other_expr); |
| |
| // The payload expression is the last expression in the |
| // comma list. It was inverted by IASNodeAdapter into |
| // the 0th position to work around limitations of the |
| // BURG's n-ary operand processor. |
| result.addAll(payload_expr); |
| |
| return result; |
| } |
| |
| public ConditionalFragment reduce_conditionalFragment(IASNode iNode, InstructionList condition, Vector<InstructionList> consequents) |
| { |
| return new ConditionalFragment(iNode, condition, consequents); |
| } |
| |
| public ConditionalFragment reduce_constantConditionalFragment(IASNode iNode, Object constantCondition, Vector<InstructionList> consequents) |
| { |
| return new ConditionalFragment(iNode, constantCondition, consequents); |
| } |
| |
| /** |
| * reduce a concatenation of two strings to a string constant |
| * @param iNode the node |
| * @param l the left string |
| * @param r the right string |
| * @return the concatenated string |
| */ |
| public String reduce_constantStringConcatenation(IASNode iNode, String l, String r) |
| { |
| return l + r; |
| } |
| |
| /** |
| * reduce a concatenation of a string and a constant value to a string constant. The constant |
| * value will be converted to a string according to the ECMA spec, and then will be concatenated with the |
| * string. |
| * @param iNode the node |
| * @param l the left string |
| * @param r the right value |
| * @return the concatenated string |
| */ |
| public String reduce_constantStringConcatenation(IASNode iNode, String l, Object r) |
| { |
| return reduce_constantStringConcatenation(iNode, l, ECMASupport.toString(r)); |
| } |
| |
| /** |
| * reduce a concatenation of a constant value and a string to a string constant. The constant |
| * value will be converted to a string according to the ECMA spec, and then will be concatenated with the |
| * string. |
| * @param iNode the node |
| * @param l the left value |
| * @param r the right string |
| * @return the concatenated string |
| */ |
| public String reduce_constantStringConcatenation(IASNode iNode, Object l, String r) |
| { |
| return reduce_constantStringConcatenation(iNode, ECMASupport.toString(l), r); |
| } |
| |
| /** |
| * reduce addition of two constant values. If either of the constants is a String, then string |
| * concatenation will be performed. OTherwise the constants will be converted to Numbers, according to the |
| * ECMA spec, and added |
| * @param iNode the node |
| * @param l the left value |
| * @param r the right value |
| * @return the result of adding the two values |
| */ |
| public Object reduce_constantAddition(IASNode iNode, Object l, Object r) |
| { |
| checkBinaryOp(iNode, OP_add); |
| if( l instanceof String ) |
| return reduce_constantStringConcatenation(iNode, (String)l, r); |
| else if( r instanceof String ) |
| return reduce_constantStringConcatenation(iNode, l, (String)r); |
| else |
| return ECMASupport.toNumeric(l).doubleValue() + ECMASupport.toNumeric(r).doubleValue(); |
| } |
| |
| public InstructionList reduce_continueStmt(IASNode iNode) |
| { |
| try |
| { |
| ControlFlowContextManager.ControlFlowContextSearchCriteria criterion = getContinueCriteria(); |
| return getNonLocalControlFlow(criterion); |
| } |
| catch ( UnknownControlFlowTargetException no_target ) |
| { |
| currentScope.addProblem(new UnknownContinueTargetProblem(iNode)); |
| return createInstructionList(iNode); |
| } |
| } |
| |
| public InstructionList reduce_countedForStmt(IASNode iNode, InstructionList init, InstructionList test_insns, InstructionList incr, InstructionList body) |
| { |
| InstructionList test_insns_with_debug_ops = ensureInstructionListHasDebugInfo(test_insns, init); |
| InstructionList result = createInstructionList(iNode, init.size() + test_insns_with_debug_ops.size() + incr.size() + body.size() + 5); |
| |
| // Initialize counters, jump to test |
| result.addAll(init); |
| |
| result.addInstruction(OP_jump, test_insns_with_debug_ops.getLabel()); |
| |
| // Loop body |
| InstructionList loop_body = new InstructionList(); |
| Label loop_head = new Label(); |
| loop_body.addInstruction(OP_label); |
| loop_body.labelCurrent(loop_head); |
| loop_body.addAll(body); |
| |
| // Set the continue target on the correct instruction. |
| if ( !incr.isEmpty() ) |
| currentScope.getFlowManager().resolveContinueLabel(incr); |
| else |
| currentScope.getFlowManager().resolveContinueLabel(loop_body); |
| |
| result.addAll(loop_body); |
| |
| // Now add the loop counter increments. |
| // The continue context needs to be set |
| // first for InstructionList hygiene. |
| result.addAll(incr); |
| |
| // Add the test; a successful test branches |
| // back to the loop head. The test generation |
| // rules guarantee this last instruction is a |
| // branch instruction that needs a target. |
| test_insns_with_debug_ops.lastElement().setTarget(loop_head); |
| result.addAll(test_insns_with_debug_ops); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| |
| return result; |
| } |
| |
| /** |
| * Check that the dest InstructionList contains debug information. If not, a new InstructionList |
| * will be returned with the debug information extracted from the src InstructionLevel. |
| * |
| * @param dest the InstructionList to ensure has debug information |
| * @param src the InstructionList from which to obtain debug information if needed |
| * @return the resulting InstructionList with debug information |
| */ |
| private static InstructionList ensureInstructionListHasDebugInfo(final InstructionList dest, final InstructionList src) |
| { |
| // the condition has some debug info, so nothing to do here. |
| if (dest.hasSuchInstruction(OP_debugline)) |
| return dest; |
| |
| // If we don't have any debug info on the condition IL, get it from the init IL. |
| String debugFile = null; |
| int debugLine = LexicalScope.DEBUG_LINE_UNKNOWN; |
| final ArrayList<Instruction> srcInstructions = src.getInstructions(); |
| for (int i = 0; i < srcInstructions.size(); i++) |
| { |
| final Instruction insn = srcInstructions.get(i); |
| if (insn.getOpcode() == OP_debugfile) |
| { |
| debugFile = (String)insn.getOperand(0); |
| } |
| else if (insn.getOpcode() == OP_debugline) |
| { |
| debugLine = insn.getImmediate(); |
| break; |
| } |
| } |
| |
| InstructionList result = new DebugInfoInstructionList(dest.size() + 2); |
| if (debugFile != null) |
| result.addInstruction(OP_debugfile, debugFile); |
| |
| if (debugLine != LexicalScope.DEBUG_LINE_UNKNOWN) |
| result.addInstruction(OP_debugline, debugLine); |
| |
| result.addAll(dest); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_defaultXMLNamespace(IASNode iNode, InstructionList ns_expr) |
| { |
| InstructionList result = createInstructionList(iNode); |
| // TODO: This could be optimized for the string constant case, |
| // but ASC doesn't do so, which means it's future and probably |
| // also not well tested in the AVM. |
| result.addAll(ns_expr); |
| result.addInstruction(OP_dxnslate); |
| currentScope.setSetsDxns(); |
| return result; |
| } |
| |
| /** |
| * Reduce expression like:<br> |
| * {@code delete o[p]} |
| * |
| * @param iNode Tree node for the {@code delete} statement. |
| * @param stem Instructions for creating a {@code DynamicAccessNode}. |
| * @param index Instructions for initializing the index expression. |
| * @return Instructions for executing a {@code delete} statement. |
| */ |
| public InstructionList reduce_deleteBracketExpr(IASNode iNode, InstructionList stem, InstructionList index) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IUnaryOperatorNode)iNode).getOperandNode(); |
| |
| // ASC doesn't check bracket expressions, and it's difficult |
| // to see what could usefully be done in any case. |
| // currentScope.getMethodBodySemanticChecker().checkDeleteExpr(iNode); |
| InstructionList result = createInstructionList(iNode, stem.size() + index.size() + 1); |
| result.addAll(stem); |
| result.addAll(index); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_deleteproperty)); |
| return result; |
| } |
| |
| /** |
| * Reduce E4X expression like:<br> |
| * {@code delete x.@[foo]} |
| * |
| * @param iNode Tree node for the {@code delete} statement. |
| * @param stem Instructions for creating a |
| * {@code MemberAccessExpressionNode}. |
| * @param index Instructions for initializing the array index expression. |
| * @return Instructions for executing a {@code delete} statement. |
| */ |
| public InstructionList reduce_deleteAtBracketExpr(IASNode iNode, InstructionList stem, InstructionList index) |
| { |
| final InstructionList result = createInstructionList(iNode, stem.size() + index.size() + 1); |
| result.addAll(stem); |
| result.addAll(index); |
| // The namespace is ignored by AVM. We choose to use the default |
| // namespace at the current scope. |
| final Namespace ns = ((INamespaceResolvedReference)NamespaceDefinition |
| .getDefaultNamespaceDefinition(currentScope.getLocalASScope())) |
| .resolveAETNamespace(currentScope.getProject()); |
| final Name multinameLA = new Name( |
| CONSTANT_MultinameLA, |
| new Nsset(ns), |
| null); |
| result.addInstruction(OP_deleteproperty, multinameLA); |
| return result; |
| } |
| |
| public InstructionList reduce_deleteDescendantsExpr(IASNode iNode, InstructionList stem, Binding field) |
| { |
| // TODO: Investigate semantics of this. |
| // This special case should get a warning or some indication it's a tautology. |
| |
| InstructionList result = createInstructionList(iNode, stem.size() + 1); |
| result.addAll(stem); |
| result.addInstruction(OP_pop); |
| result.addInstruction(OP_pushtrue); |
| return result; |
| } |
| |
| public InstructionList reduce_deleteExprExprExpr(IASNode iNode, InstructionList expr) |
| { |
| // The expression must be run to allow for side effects. |
| InstructionList result = createInstructionList(iNode, expr.size() + 2); |
| result.addInstruction(OP_getlocal0); |
| result.addAll(expr); |
| result.addInstruction(arrayAccess(iNode, OP_deleteproperty)); |
| return result; |
| } |
| |
| public InstructionList reduce_deleteMemberExpr(IASNode iNode, InstructionList stem, Binding field) |
| { |
| currentScope.getMethodBodySemanticChecker().checkDeleteExpr(iNode, field); |
| |
| InstructionList result = createInstructionList(iNode, stem.size() + 1); |
| result.addAll(stem); |
| result.addInstruction(OP_deleteproperty, field.getName()); |
| return result; |
| } |
| |
| public InstructionList reduce_deleteRuntimeNameExpr(IASNode iNode, InstructionList stem, RuntimeMultiname rt_name) |
| { |
| // TODO: Any relevant semantic checks? |
| |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| if ( rt_name.hasRuntimeQualifier() ) |
| result.addAll(rt_name.getRuntimeQualifier(iNode)); |
| if ( rt_name.hasRuntimeName() ) |
| result.addAll(rt_name.getRuntimeName(iNode)); |
| result.addInstruction(OP_deleteproperty, rt_name.generateName()); |
| return result; |
| } |
| |
| public InstructionList reduce_deleteNameExpr(IASNode iNode, Binding n) |
| { |
| currentScope.getMethodBodySemanticChecker().checkDeleteExpr(iNode, n); |
| |
| InstructionList result = createInstructionList(iNode, 2); |
| result.addAll(currentScope.findProperty(n, false)); |
| result.addInstruction(OP_deleteproperty, n.getName()); |
| return result; |
| } |
| |
| public InstructionList reduce_doStmt(IASNode iNode, InstructionList body, InstructionList cond) |
| { |
| InstructionList result = createInstructionList(iNode, cond.size() + body.size() + 1); |
| currentScope.getFlowManager().resolveContinueLabel(cond); |
| |
| if ( body.isEmpty() || body.firstElement().getOpcode() != OP_label ) |
| result.addInstruction(OP_label); |
| |
| result.addAll(body); |
| result.addAll(cond); |
| result.addInstruction(OP_iftrue, result.getLabel()); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_e4xFilter(IASNode iNode, InstructionList stem, InstructionList filter) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| LexicalScope.Hasnext2Wrapper hasnext = currentScope.hasnext2(); |
| |
| // Cache each value of e.employee. |
| Binding value_temp = currentScope.allocateTemp(); |
| // Cache the result XMLList. |
| Binding result_temp = currentScope.allocateTemp(); |
| |
| // Emit the stem expression and cache |
| // the stem object in a local register. |
| result.addAll(stem); |
| result.addInstruction(OP_checkfilter); |
| result.addInstruction(hasnext.stem_temp.setlocal() ); |
| |
| // Initialize the index register. |
| result.addInstruction(OP_pushbyte, 0); |
| result.addInstruction(hasnext.index_temp.setlocal()); |
| |
| // Create the result XMLList. |
| result.addAll(currentScope.getPropertyValue(xmlListType, currentScope.getProject().getBuiltinType(BuiltinType.XMLLIST))); |
| result.addInstruction(OP_pushstring, ""); |
| result.addInstruction(OP_construct, 1); |
| result.addInstruction(result_temp.setlocal()); |
| |
| // Jump to the loop test. |
| Label test = new Label(); |
| result.addInstruction(OP_jump, test); |
| |
| // Top of the loop: put an AET Label on |
| // the ABC OP_Label. |
| Label loop = new Label(); |
| result.labelNext(loop); |
| result.addInstruction(OP_label); |
| // Get the next value and cache it. |
| result.addInstruction(hasnext.stem_temp.getlocal()); |
| result.addInstruction(hasnext.index_temp.getlocal()); |
| result.addInstruction(OP_nextvalue); |
| result.addInstruction(OP_dup); |
| result.addInstruction(value_temp.setlocal() ); |
| // See ECMA-357 11.2.4 |
| result.addInstruction(OP_pushwith); |
| |
| result.addAll(filter); |
| |
| Label no_match = new Label(); |
| result.addInstruction(OP_iffalse, no_match); |
| |
| result.addInstruction(result_temp.getlocal() ); |
| result.addInstruction(hasnext.index_temp.getlocal() ); |
| result.addInstruction(value_temp.getlocal() ); |
| |
| result.addInstruction(arrayAccess(iNode, OP_setproperty)); |
| |
| result.labelNext(no_match); |
| // See ECMA-357 11.2.4 |
| result.addInstruction(OP_popscope); |
| result.labelNext(test); |
| result.addInstruction(hasnext.instruction); |
| result.addInstruction(OP_iftrue, loop); |
| |
| result.addInstruction(result_temp.getlocal()); |
| |
| hasnext.release(); |
| currentScope.releaseTemp(value_temp); |
| currentScope.releaseTemp(result_temp); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_embed(IASNode iNode) |
| { |
| String name = ""; |
| try |
| { |
| name = ((EmbedNode)iNode).getName(currentScope.getProject(), getProblems()); |
| } |
| catch (InterruptedException e) |
| { |
| throw new CodegenInterruptedException(e); |
| } |
| |
| // the parent of an embed node is always a variable |
| final VariableNode variableNode = (VariableNode)iNode.getParent(); |
| final IVariableDefinition variableDef = (IVariableDefinition)variableNode.getDefinition(); |
| final ITypeDefinition variableType = variableDef.resolveType(currentScope.getProject()); |
| final String typeName = variableType.getQualifiedName(); |
| |
| InstructionList result; |
| if ("Class".equals(typeName)) |
| { |
| result = createInstructionList(iNode); |
| Name embedName = new Name(name); |
| result.addInstruction(OP_getlex, embedName); |
| } |
| else if ("String".equals(typeName)) |
| { |
| result = createInstructionList(iNode); |
| result.addInstruction(OP_pushstring, name); |
| } |
| else |
| { |
| assert false : "this problem should have been caught already at EmbedNode construction time"; |
| result = null; |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_forKeyValueStmt(IASNode iNode, Binding it, InstructionList base, InstructionList body, int opcode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkLValue(iNode, it); |
| |
| InstructionList result = createInstructionList(iNode, body.size() + base.size() + 15); |
| |
| ForKeyValueLoopState fkv = new ForKeyValueLoopState(); |
| |
| result.addAll(fkv.generatePrologue(iNode, base)); |
| |
| result.addAll( |
| generateAssignment( |
| iNode, |
| it, |
| fkv.generateKeyOrValue(opcode) |
| ) |
| ); |
| |
| result.addAll(body); |
| |
| result.addAll(fkv.generateEpilogue()); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| |
| return result; |
| } |
| |
| /** |
| * Reduce {@code for(it in base)} or {@code for each(it in base)} loop with |
| * variable initializer. |
| * |
| * @param iNode {@code ForLoopNode} |
| * @param single_decl Instructions to initialize a loop variable. |
| * @param base Instructions to create a loop base. |
| * @param body For loop body instructions. |
| * @param opcode Use {@code OP_nextname} for "for" loops; use |
| * {@code OP_nextvalue} for "for each" loops. |
| * @return Instructions generated for the "for" loop node. |
| */ |
| public InstructionList reduce_forVarDeclInStmt(IASNode iNode, InstructionList single_decl, InstructionList base, InstructionList body, int opcode) |
| { |
| final InstructionList result = createInstructionList(iNode); |
| result.addAll(single_decl); |
| |
| final VariableNode var_node = findIteratorVariable((ForLoopNode)iNode); |
| final Binding it = currentScope.resolveName((IdentifierNode)var_node.getNameExpressionNode()); |
| |
| currentScope.getMethodBodySemanticChecker().checkLValue(iNode, it); |
| |
| final ForKeyValueLoopState fkv = new ForKeyValueLoopState(); |
| result.addAll(fkv.generatePrologue(iNode, base)); |
| result.addAll(generateAssignment(iNode, it, fkv.generateKeyOrValue(opcode))); |
| result.addAll(body); |
| result.addAll(fkv.generateEpilogue()); |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| return result; |
| } |
| |
| /** |
| * Find the iterator {@code VariableNode} in a {@code ForLoopNode}. |
| * |
| * <pre> |
| * for (var it : String = "hello" in array) {...} |
| * for each (var it : String = "hello" in array) {...} |
| * </pre> |
| * |
| * @param for_loop_node "for" loop node. |
| * @return {@code VariableNode} of the iterator variable. |
| */ |
| private static VariableNode findIteratorVariable(final ForLoopNode for_loop_node) |
| { |
| assert for_loop_node != null : "'for' loop node can't be null"; |
| |
| final IASNode conditionalStatement = for_loop_node.getConditionalsContainerNode().getChild(0); |
| final BinaryOperatorInNode in_node = (BinaryOperatorInNode)conditionalStatement; |
| final VariableExpressionNode var_expr_node = (VariableExpressionNode)in_node.getLeftOperandNode(); |
| final VariableNode var_node = (VariableNode)var_expr_node.getChild(0); |
| return var_node; |
| } |
| |
| /** |
| * Reduce statement like:<br> |
| * {@code for (a[it] in base) body;} |
| * |
| * @param iNode Tree node for the for-in loop. |
| * @param stem Instructions for creating a {@code DynamicAccessNode}. |
| * @param index Instructions for initializing the index expression. |
| * @param base Instructions for initializing the base object of the <code>in</code> expression. |
| * @param body Instructions for initializing the body of the for-loop. |
| * @param opcode Opcode for <code>nextname</code> or ,code>nextvalue</code> instruction. |
| * @param is_super Flag indicating whether the stem of the array index expression is <code>super</code>. |
| * @return Instructions for executing the {@code for-in} statement. |
| */ |
| public InstructionList reduce_forKeyValueArrayStmt(IASNode iNode, InstructionList stem, InstructionList index, InstructionList base, InstructionList body, int opcode, boolean is_super) |
| { |
| IContainerNode conditionalStatementsNode = ((IForLoopNode)iNode).getConditionalsContainerNode(); |
| IBinaryOperatorNode inNode = (IBinaryOperatorNode)SemanticUtils.getNthChild(conditionalStatementsNode, 0); |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)inNode.getLeftOperandNode(); |
| |
| ForKeyValueLoopState fkv = new ForKeyValueLoopState(); |
| |
| InstructionList result = createInstructionList(iNode, body.size() + base.size() + 15); |
| |
| result.addAll(fkv.generatePrologue(iNode, base)); |
| |
| if( is_super ) |
| result.addInstruction(OP_getlocal0); |
| else |
| result.addAll(stem); |
| |
| result.addAll(index); |
| result.addAll(fkv.generateKeyOrValue(opcode)); |
| |
| result.addInstruction(arrayAccess(arrayIndexNode, is_super ? OP_setsuper : OP_setproperty)); |
| |
| result.addAll(body); |
| |
| result.addAll(fkv.generateEpilogue()); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| return result; |
| } |
| |
| public InstructionList reduce_forKeyValueMemberStmt(IASNode iNode, InstructionList stem, Binding member, InstructionList base, InstructionList body, int opcode, boolean is_super) |
| { |
| currentScope.getMethodBodySemanticChecker().checkLValue(iNode, member); |
| |
| ForKeyValueLoopState fkv = new ForKeyValueLoopState(); |
| |
| InstructionList result = createInstructionList(iNode, body.size() + base.size() + 15); |
| |
| result.addAll(fkv.generatePrologue(iNode, base)); |
| |
| if( is_super ) |
| result.addInstruction(OP_getlocal0); |
| else |
| result.addAll(stem); |
| |
| result.addAll(fkv.generateKeyOrValue(opcode)); |
| |
| generateAssignmentOp(iNode, member, result); |
| |
| result.addAll(body); |
| |
| result.addAll(fkv.generateEpilogue()); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| return result; |
| } |
| |
| /** |
| * Reduce expression like:<br> |
| * {@code a[i]()} |
| * |
| * @param iNode Tree node for the function call. |
| * @param stem Instructions for creating a {@code DynamicAccessNode}. |
| * @param index Instructions for initializing the index expression. |
| * @return Instructions for executing a {@code function call} statement. |
| */ |
| public InstructionList reduce_functionAsBracketExpr(IASNode iNode, InstructionList stem, InstructionList index, Vector<InstructionList> args) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IFunctionCallNode)iNode).getNameNode(); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| result.addAll(index); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| result.addInstruction(OP_swap); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| result.addInstruction(OP_call, args.size() ); // note: args.size() is an immediate operand to OP_call |
| return result; |
| } |
| |
| public InstructionList reduce_functionAsMemberExpr(IASNode iNode, InstructionList stem, Binding method_name, Vector<InstructionList> args) |
| { |
| // Perform general function call checks and specific member checks |
| currentScope.getMethodBodySemanticChecker().checkFunctionCall(iNode, method_name, args); |
| |
| // TODO: Investigate optimizing this. |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| |
| boolean inlined = generateInlineFunctionCall(method_name, result, true, args); |
| |
| if (!inlined) |
| { |
| for (InstructionList arg: args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_callproperty, new Object[] {method_name.getName(), args.size() } ); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_functionAsRandomExpr(IASNode iNode, InstructionList random_expr, Vector<InstructionList> args) |
| { |
| // TODO: Investigate optimizing this. |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(random_expr); |
| |
| // TODO: currentScope.getThis() or sth similar |
| result.addInstruction(OP_getlocal0); |
| |
| for( InstructionList arg: args ) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_call, args.size()); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_functionCallExpr_to_expression(IASNode iNode, Binding method_name, Vector<InstructionList> args) |
| { |
| return reduce_functionCall_common(iNode, method_name, args, true); |
| } |
| |
| public InstructionList reduce_functionCallExpr_to_void_expression(IASNode iNode, Binding method_name, Vector<InstructionList> args) |
| { |
| return reduce_functionCall_common(iNode, method_name, args, false); |
| } |
| |
| private InstructionList reduce_functionCall_common(IASNode iNode, Binding method_name, Vector<InstructionList> args, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkFunctionCall(iNode, method_name, args); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if (method_name.isLocal()) |
| { |
| result.addInstruction(method_name.getlocal()); |
| result.addInstruction(OP_getlocal0); |
| for (InstructionList arg : args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_call, args.size()); |
| if (!need_result) |
| { |
| result.addInstruction(OP_pop); |
| } |
| } |
| else if ( SemanticUtils.isMethodBinding(method_name.getDefinition()) ) |
| { |
| // A call to a class' method. |
| boolean inlined = generateInlineFunctionCall(method_name, result, false, args); |
| |
| if (!inlined) |
| { |
| result.addAll(currentScope.findProperty(method_name, true)); |
| for (InstructionList arg : args) |
| result.addAll(arg); |
| |
| int opcode = need_result ? OP_callproperty : OP_callpropvoid; |
| result.addInstruction(opcode, new Object[] {method_name.getName(), args.size()}); |
| } |
| } |
| else if ( method_name.getDefinition() != null ) |
| { |
| // We're in some context where the method can be found lexically. |
| assert ( ! method_name.getName().isRuntimeName() ); |
| |
| boolean inlined = generateInlineGetterAccess(method_name, result, false); |
| |
| if (!inlined) |
| result.addAll(currentScope.getPropertyValue(method_name)); |
| |
| result.addInstruction(OP_getglobalscope); |
| for (InstructionList arg : args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_call, args.size()); |
| |
| |
| if ( ! need_result ) |
| result.addInstruction(OP_pop); |
| } |
| else |
| { |
| // Probably in a with or filter block. |
| result.addInstruction(OP_findpropstrict, method_name.getName()); |
| for (InstructionList arg : args) |
| result.addAll(arg); |
| |
| int opcode = need_result ? OP_callproperty : OP_callpropvoid; |
| result.addInstruction(opcode, new Object[] {method_name.getName(), args.size()}); |
| |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_functionCallOfSuperclassMethod_to_expression(IASNode iNode, InstructionList stem, Binding method_name, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkSuperAccess(iNode); |
| if (method_name.getDefinition() instanceof AccessorDefinition) |
| getProblems().add(new CallNonFunctionProblem(iNode, method_name.getName().getBaseName())); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( stem != null ) |
| result.addAll(stem); |
| else |
| // Implicit stem is "this". |
| result.addInstruction(OP_getlocal0); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| result.addInstruction(OP_callsuper, new Object[] {method_name.getName(), args.size() } ); |
| return result; |
| } |
| |
| public InstructionList reduce_functionCallOfSuperclassMethod_to_void_expression(IASNode iNode, InstructionList stem, Binding method_name, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkSuperAccess(iNode); |
| if (method_name.getDefinition() instanceof AccessorDefinition) |
| getProblems().add(new CallNonFunctionProblem(iNode, method_name.getName().getBaseName())); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( stem != null ) |
| result.addAll(stem); |
| else |
| // Implicit stem is "this" |
| result.addInstruction(OP_getlocal0); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| result.addInstruction(OP_callsupervoid, new Object[] {method_name.getName(), args.size() } ); |
| return result; |
| } |
| |
| public InstructionList reduce_groupedVoidExpression(IASNode iNode, Vector<InstructionList> contents) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList expr: contents ) |
| result.addAll(expr); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_ifElseIf(IASNode iNode, InstructionList test, InstructionList then, Vector<ConditionalFragment> if_elseif) |
| { |
| // need to convert any constant values back |
| // to a conditional expression when not generating |
| // a lookup switch. |
| // TODO: CMP-1762 make use of any constant values to optimize away |
| // constant if/else ifs. |
| convertConstantValueConditionsToInstructions(if_elseif); |
| |
| // TODO: Experiment with optimizing this. |
| // Iterating twice over the arms of the if |
| // might cost more than it helps. |
| InstructionList result = createInstructionList(iNode); |
| |
| boolean has_else = !if_elseif.isEmpty(); |
| |
| // Label to the "next statement." |
| Label tail = null; |
| |
| // TODO: Use conditionalJump style tests. |
| result.addAll(test); |
| |
| if ( has_else ) |
| result.addInstruction(OP_iffalse, if_elseif.firstElement().getLabel()); |
| else |
| tail = addBranch(result, tail, OP_iffalse); |
| |
| result.addAll(then); |
| |
| if ( has_else ) |
| { |
| tail = addInterstitialControlFlow(result, tail); |
| |
| for ( int i = 0; i < if_elseif.size() - 1; i++ ) |
| { |
| ConditionalFragment alternative = if_elseif.elementAt(i); |
| |
| if ( !alternative.isUnconditionalAlternative() ) |
| { |
| // Add the test and conditional jump to the next alternative. |
| result.addAll(alternative.condition); |
| ConditionalFragment next_alternative = if_elseif.elementAt(i+1); |
| result.addInstruction(OP_iffalse, next_alternative.getLabel()); |
| } |
| |
| result.addAll(alternative.statement); |
| tail = addInterstitialControlFlow(result, tail); |
| } |
| |
| ConditionalFragment last_clause = if_elseif.lastElement(); |
| |
| if ( !last_clause.isUnconditionalAlternative() ) |
| { |
| result.addAll(last_clause.condition); |
| tail = addBranch(result, tail, OP_iffalse); |
| result.addAll(last_clause.statement); |
| } |
| else |
| { |
| result.addAll(last_clause.statement); |
| } |
| } |
| |
| if ( tail != null ) |
| result.labelNext(tail); |
| return result; |
| } |
| |
| /** |
| * Add a branch instruction to an InstructionList. |
| * @param insns - the InstructionList. |
| * @param previousLabel - any existing label that targets the jump destination. |
| * @param opcode - the opcode of the branch (e.g., OP_jump, OP_iffalse, etc.) |
| * @return a label that targets the jump destination; if previousLabel was |
| * not null, then previousLabel, otherwise a new Label. The caller is |
| * responsible for saving this label and applying it appropriately at |
| * the destination point. |
| */ |
| private Label addBranch(InstructionList insns, Label previousLabel, int opcode) |
| { |
| Label result = previousLabel != null? previousLabel: new Label(); |
| insns.addInstruction(opcode, result); |
| return result; |
| } |
| |
| /** |
| * Add jumps from one bit of a control-flow statement to the next, |
| * if they're necessary. |
| * @param insns - the InstructionList that's building up the statement. |
| * @param previousLabel - any existing Label that targets the next part of |
| * the statement. May be null if this is the first interstitial jump. |
| * @return previousLabel if previousLabel was not null; |
| * null if previousLabel was null and no jump is necessary; |
| * otherwise a new Label that the caller must use to label the next |
| * part of the statement. |
| */ |
| private Label addInterstitialControlFlow(InstructionList insns, Label previousLabel) |
| { |
| Label result; |
| |
| if ( insns.canFallThrough() ) |
| { |
| result = addBranch(insns, previousLabel, OP_jump); |
| } |
| else if ( insns.hasPendingLabels() ) |
| { |
| // TODO: InstructionList needs a way to "alias" |
| // labels; with such a facility, this jump could be elided. |
| result = addBranch(insns, previousLabel, OP_jump); |
| } |
| else |
| { |
| result = previousLabel; |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_importDirective(IASNode iNode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkImportDirective(((IImportNode)iNode)); |
| return createInstructionList(iNode); |
| } |
| |
| public InstructionList reduce_labeledBreakStmt(IASNode iNode) |
| { |
| String target = ((IdentifierNode)SemanticUtils.getNthChild(iNode, 0)).getName(); |
| try |
| { |
| ControlFlowContextManager.ControlFlowContextSearchCriteria criterion = getBreakCriteria(target); |
| return getNonLocalControlFlow(criterion); |
| } |
| catch ( UnknownControlFlowTargetException no_target ) |
| { |
| currentScope.addProblem(new UnknownBreakTargetProblem(SemanticUtils.getNthChild(iNode, 0))); |
| return createInstructionList(iNode); |
| } |
| } |
| |
| public InstructionList reduce_labeledContinueStmt(IASNode iNode) |
| { |
| String target = ((IdentifierNode)SemanticUtils.getNthChild(iNode, 0)).getName(); |
| try |
| { |
| ControlFlowContextManager.ControlFlowContextSearchCriteria criterion = getContinueCriteria(target); |
| return getNonLocalControlFlow(criterion); |
| } |
| catch ( UnknownControlFlowTargetException no_target ) |
| { |
| currentScope.addProblem(new UnknownContinueTargetProblem(SemanticUtils.getNthChild(iNode, 0))); |
| return createInstructionList(iNode); |
| } |
| } |
| |
| public InstructionList reduce_gotoStmt(IASNode iNode) |
| { |
| IdentifierNode labelIdentifierNode = (IdentifierNode)SemanticUtils.getNthChild(iNode, 0); |
| String target = labelIdentifierNode.getName(); |
| try |
| { |
| ControlFlowContextManager.ControlFlowContextSearchCriteria criterion = getGotoCriteria(target); |
| return getNonLocalControlFlow(criterion); |
| } |
| catch ( UnknownControlFlowTargetException no_target ) |
| { |
| Collection<LabeledStatementNode> gotoLabels = currentScope.getFlowManager().getGotoLabels(target); |
| if (gotoLabels.isEmpty()) |
| currentScope.addProblem(new UnknownGotoTargetProblem(labelIdentifierNode)); |
| else |
| currentScope.addProblem(new AmbiguousGotoTargetProblem(labelIdentifierNode, gotoLabels)); |
| return createInstructionList(iNode); |
| } |
| } |
| |
| public InstructionList reduce_labeledStmt(IASNode iNode, String label, InstructionList substatement) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| Label gotoLabel = currentScope.getFlowManager().getGotoLabel(label); |
| // Since duplicate labels are invisible to goto statements, |
| // we can fail to find a goto context for a label. |
| if (gotoLabel != null) |
| { |
| result.labelNext(gotoLabel); |
| result.addInstruction(OP_label); |
| } |
| |
| result.addAll(substatement); |
| |
| currentScope.getFlowManager().finishLabeledStatementControlFlowContext(result); |
| return result; |
| } |
| |
| public ConditionalFragment reduce_lastElse(IASNode iNode, InstructionList else_clause) |
| { |
| return new ConditionalFragment(iNode, null, else_clause); |
| } |
| |
| public InstructionList reduce_logicalAndExpr(IASNode iNode, InstructionList l, InstructionList r) |
| { |
| InstructionList result = createInstructionList(iNode, l.size() + r.size() + 3); |
| Label tail = new Label(); |
| |
| result.addAll(l); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_iffalse, tail); |
| result.addInstruction(OP_pop); |
| result.addAll(r); |
| result.labelNext(tail); |
| return result; |
| } |
| |
| public InstructionList reduce_logicalNotExpr(IASNode iNode, InstructionList expr) |
| { |
| InstructionList result = createInstructionList(iNode, expr.size() + 1); |
| result.addAll(expr); |
| result.addInstruction(OP_not); |
| return result; |
| } |
| |
| public InstructionList reduce_logicalOrExpr(IASNode iNode, InstructionList l, InstructionList r) |
| { |
| InstructionList result = createInstructionList(iNode, l.size() + r.size() + 3); |
| Label tail = new Label(); |
| |
| result.addAll(l); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_iftrue, tail); |
| result.addInstruction(OP_pop); |
| result.addAll(r); |
| result.labelNext(tail); |
| return result; |
| } |
| |
| public InstructionList reduce_memberAccessExpr(IASNode iNode, InstructionList stem, Binding member, int opcode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkMemberAccess(iNode, member, opcode); |
| InstructionList result = createInstructionList(iNode, stem.size() + 1); |
| |
| result.addAll(stem); |
| |
| boolean inlined = generateInlineGetterAccess(member, result, true); |
| |
| if (!inlined) |
| result.addInstruction(opcode, member.getName()); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_qualifiedMemberAccessExpr(IASNode iNode, InstructionList stem, Binding qualifier, Binding member, int opcode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkMemberAccess(iNode, member, opcode); |
| InstructionList result = createInstructionList(iNode); |
| Name member_name = member.getName(); |
| |
| result.addAll(stem); |
| if ( isNamespace(qualifier) ) |
| { |
| result.addInstruction(opcode, member_name); |
| } |
| else |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addInstruction(opcode, member_name); |
| } |
| return result; |
| } |
| |
| public InstructionList reduce_qualifiedAttributeRuntimeMemberExpr(IASNode iNode, InstructionList stem, Binding qualifier, InstructionList runtime_member_selector, int opcode) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| if ( isNamespace(qualifier) ) |
| { |
| result.addAll(runtime_member_selector); |
| // Extract the URI from the namespace and use it to construct a qualified name. |
| NamespaceDefinition ns_def = (NamespaceDefinition)qualifier.getDefinition(); |
| Name qualified_name = new Name(CONSTANT_MultinameLA, new Nsset(ns_def.resolveAETNamespace(currentScope.getProject())), null); |
| result.addInstruction(opcode, qualified_name); |
| } |
| else |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addAll(runtime_member_selector); |
| result.addInstruction(OP_coerce_s); |
| result.addInstruction(opcode, new Name(CONSTANT_RTQnameLA, null, null)); |
| } |
| return result; |
| } |
| |
| public InstructionList reduce_qualifiedMemberRuntimeNameExpr(IASNode iNode, InstructionList stem, Binding qualifier, InstructionList runtime_member_selector) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| if ( isNamespace(qualifier) ) |
| { |
| result.addAll(runtime_member_selector); |
| // Extract the URI from the namespace and use it to construct a qualified name. |
| NamespaceDefinition ns_def = (NamespaceDefinition)qualifier.getDefinition(); |
| Name qualified_name = new Name(CONSTANT_MultinameL, new Nsset(ns_def.resolveAETNamespace(currentScope.getProject())), null); |
| result.addInstruction(OP_getproperty, qualified_name); |
| } |
| else |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addAll(runtime_member_selector); |
| result.addInstruction(OP_coerce_s); |
| result.addInstruction(OP_getproperty, new Name(CONSTANT_RTQnameL, null, null)); |
| } |
| return result; |
| } |
| |
| public InstructionList reduce_qualifiedAttributeExpr(IASNode iNode, InstructionList stem, Binding qualifier, Binding member, int opcode) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| if ( !isNamespace(qualifier) ) |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addInstruction(opcode, member.getName()); |
| } |
| else |
| { |
| // the qualifier's namespace is already present in the name. |
| result.addInstruction(opcode, member.getName()); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_unqualifiedAttributeExpr(IASNode iNode, InstructionList stem, InstructionList rt_attr, int opcode) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addAll(rt_attr); |
| result.addInstruction(opcode, new Name(CONSTANT_MultinameLA, new Nsset(new Namespace(CONSTANT_PackageNs)), null)); |
| |
| return result; |
| } |
| |
| /** |
| * Reduce runtime attribute expression, such as {@code @[exp]}. |
| * |
| * @param iNode Node for {@code @[...]}. It is a |
| * {@code ArrayIndexExpressionID(Op_AtID, ...)}. |
| * @param rt_attr Instructions generated for the runtime name expression. |
| * @return Instructions to get the value of an attribute described with a |
| * runtime name. |
| */ |
| public InstructionList reduce_runtimeAttributeExp(IASNode iNode, InstructionList rt_attr) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(rt_attr); |
| final Nsset qualifiers = SemanticUtils.getOpenNamespaces(iNode, currentScope.getProject()); |
| result.addInstruction(OP_getproperty, new Name(CONSTANT_MultinameLA, qualifiers, null)); |
| return result; |
| } |
| |
| public InstructionList reduce_mxmlEventSpecifier(IASNode iNode, Vector<InstructionList> stmts) |
| { |
| // TODO: Investigate optimizing. |
| InstructionList body = createInstructionList(iNode); |
| |
| // An MXMLEventSpecifierNode can have N child nodes which are statements. |
| // We need to codegen all of them as an effective method body. |
| for ( InstructionList stmt: stmts ) |
| body.addAll(stmt); |
| |
| return generateFunctionBody(iNode, body, LexicalScope.anyType); |
| } |
| |
| public Binding reduce_namespaceAccess(IASNode iNode, IASNode qualifier, Binding qualified_name) |
| { |
| currentScope.getMethodBodySemanticChecker().checkQualifier(qualifier); |
| // All the qualifying syntax nodes are just concrete. |
| return qualified_name; |
| } |
| |
| public RuntimeMultiname reduce_namespaceAccess_to_runtime_name(IASNode iNode, Binding qualified_name) |
| { |
| InstructionList qualifier = createInstructionList(iNode); |
| generateAccess(reduce_simpleName(iNode.getChild(0)), qualifier); |
| qualifier.addInstruction(OP_coerce, namespaceType); |
| return new RuntimeMultiname(qualifier, qualified_name.getName()); |
| } |
| |
| public InstructionList reduce_namespaceAsName_to_expression(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode); |
| Binding b = reduce_simpleName(iNode); |
| generateAccess(b, result); |
| result.addInstruction(OP_coerce, namespaceType); |
| return result; |
| } |
| |
| public Name reduce_namespaceAsName_to_multinameL(IASNode iNode) |
| { |
| return reduce_namespaceAsName_to_multinameL(iNode, false); |
| } |
| |
| public Name reduce_namespaceAsName_to_multinameL(IASNode iNode, final boolean is_attribute) |
| { |
| IdentifierNode qualifier = (IdentifierNode)iNode; |
| NamespaceDefinition ns = (NamespaceDefinition) qualifier.resolve(currentScope.getProject()); |
| |
| int name_kind = is_attribute? CONSTANT_MultinameLA: CONSTANT_MultinameL; |
| return new Name(name_kind, new Nsset( ns.resolveAETNamespace(currentScope.getProject())), null); |
| } |
| |
| |
| public Binding reduce_namespaceAsName_to_name(IASNode iNode) |
| { |
| // Note: unresolved namespaces are trapped by a bespoke rule. |
| return currentScope.resolveName((org.apache.royale.compiler.internal.tree.as.IdentifierNode)iNode); |
| } |
| |
| public InstructionList reduce_namespaceDeclaration(IASNode iNode, Binding ns_name) |
| { |
| // Get the AET Namespace from the namespace's definition. |
| NamespaceDefinition def = ((NamespaceNode)iNode).getDefinition(); |
| return reduce_namespaceDeclarationConstantInitializer(iNode, ns_name, def.resolveAETNamespace(currentScope.getProject())); |
| } |
| |
| public InstructionList reduce_namespaceDeclarationConstantInitializer(IASNode iNode, Binding ns_name, String uri) |
| { |
| // Make a new CONSTANT_Namespace with the given URI - all user defined namespaces initialized with a |
| // String literal become CONSTANT_Namespaces |
| return reduce_namespaceDeclarationConstantInitializer(iNode, ns_name, new Namespace(CONSTANT_Namespace, uri)); |
| } |
| |
| /** |
| * Does the work for the various reduce_namespaceDecl methods. Creates a new property, with an initial value |
| * of the Namespace passed in. |
| */ |
| public InstructionList reduce_namespaceDeclarationConstantInitializer(IASNode iNode, Binding ns_name, Namespace initializer) |
| { |
| currentScope.getMethodBodySemanticChecker().checkNamespaceDeclaration(iNode, ns_name); |
| |
| NamespaceDefinition def = ((NamespaceNode)iNode).getDefinition(); |
| |
| switch (SemanticUtils.getMultiDefinitionType(def, currentScope.getProject())) |
| { |
| case AMBIGUOUS: |
| currentScope.addProblem(new DuplicateNamespaceDefinitionProblem(iNode)); |
| break; |
| case NONE: |
| break; |
| default: |
| assert false; // I don't think namespaces can have other type of multiple definitions |
| } |
| |
| if( initializer == null ) |
| { |
| // Can't declare a namespace if we don't know what its initial value is |
| getProblems().add( new InvalidNamespaceInitializerProblem(iNode) ); |
| return null; |
| } |
| |
| currentScope.makeNamespace(ns_name, initializer); |
| if ( ns_name.isLocal() ) |
| { |
| // Locals have no initializer, so the variable |
| // needs explicit initialization code. |
| currentScope.getHoistedInitInstructions().addInstruction(OP_pushnamespace, initializer); |
| currentScope.getHoistedInitInstructions().addInstruction(ns_name.setlocal()); |
| |
| } |
| return null; |
| } |
| |
| public InstructionList reduce_namespaceDeclarationInitializer(IASNode iNode, Binding ns_name, Binding second_ns) |
| { |
| Namespace ns_init = null; |
| |
| if ( second_ns.getDefinition() instanceof NamespaceDefinition ) |
| { |
| NamespaceDefinition ns_def = (NamespaceDefinition)second_ns.getDefinition(); |
| // Grab the AET namespace from the initializer |
| ns_init = ns_def.resolveAETNamespace(currentScope.getProject()); |
| } |
| |
| return reduce_namespaceDeclarationConstantInitializer(iNode, ns_name, ns_init); |
| } |
| |
| public RuntimeMultiname reduce_namespaceMultinameL(IASNode iNode, IASNode qualifier_node, InstructionList expr) |
| { |
| currentScope.getMethodBodySemanticChecker().checkQualifier(qualifier_node); |
| Name qualifier = reduce_namespaceAsName_to_multinameL(qualifier_node); |
| return new RuntimeMultiname(qualifier, expr); |
| } |
| |
| public RuntimeMultiname reduce_namespaceRTQName(IASNode iNode, InstructionList qualifier, Binding qualified_name) |
| { |
| return new RuntimeMultiname(qualifier, qualified_name.getName()); |
| } |
| |
| public RuntimeMultiname reduce_namespaceRTQNameL(IASNode iNode, InstructionList qualifier, InstructionList expr) |
| { |
| return new RuntimeMultiname(qualifier, expr); |
| } |
| |
| public InstructionList reduce_neqExpr(IASNode iNode, InstructionList l, InstructionList r) |
| { |
| InstructionList result = binaryOp(iNode, l, r, OP_equals); |
| result.addInstruction(OP_not); |
| return result; |
| } |
| |
| public Binding reduce_nameToTypeName(Binding name, boolean check_name) |
| { |
| if ( check_name ) |
| currentScope.getMethodBodySemanticChecker().checkTypeName(name); |
| return name; |
| } |
| |
| public InstructionList reduce_newMemberProperty(IASNode iNode, InstructionList stem, Binding member, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkNewExpr(iNode); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_constructprop, new Object[] { member.getName(), args.size() } ); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_newAsRandomExpr(IASNode iNode, InstructionList random_expr, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkNewExpr(iNode); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(random_expr); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_construct, args.size() ); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_newEmptyArray(IASNode iNode) |
| { |
| // Codegen 'new Array()' efficiently like an array literal |
| // rather than like a constructor call. That is, |
| // newarray [0] |
| // rather than |
| // findpropstrict :Array |
| // constructprop :Array, (0) |
| InstructionList result = createInstructionList(iNode); |
| result.addInstruction(OP_newarray, 0); |
| return result; |
| } |
| |
| public InstructionList reduce_newEmptyObject(IASNode iNode) |
| { |
| // Codegen 'new Object()' efficiently like an object literal |
| // rather than like a constructor call. That is, |
| // newobject {0} |
| // rather than |
| // findpropstrict :Object |
| // constructprop :Object, (0) |
| InstructionList result = createInstructionList(iNode); |
| result.addInstruction(OP_newobject, 0); |
| return result; |
| } |
| |
| public InstructionList reduce_newExpr(IASNode iNode, Binding class_binding, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkNewExpr(iNode, class_binding, args); |
| |
| // TODO: Investigate optimizing this. |
| InstructionList result = createInstructionList(iNode); |
| |
| Name class_name = class_binding.getName(); |
| |
| if ( class_binding.isLocal() || class_name.isTypeName() ) |
| { |
| generateAccess(class_binding, result); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_construct, args.size()); |
| } |
| else |
| { |
| result.addAll(currentScope.findProperty(class_binding, true)); |
| |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| |
| result.addInstruction(OP_constructprop, new Object[] { class_name, args.size() } ); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_newVectorLiteral(IASNode iNode, InstructionList literal) |
| { |
| return literal; |
| } |
| |
| public InstructionList reduce_nilExpr_to_conditionalJump(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(InstructionFactory.getTargetableInstruction(OP_jump)); |
| return result; |
| } |
| |
| public InstructionList reduce_nilExpr_to_expression(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushundefined); |
| return result; |
| } |
| |
| public Object reduce_nullLiteral_to_constant_value(IASNode iNode) |
| { |
| return ABCConstants.NULL_VALUE; |
| } |
| |
| public InstructionList reduce_nullLiteral_to_object_literal(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushnull); |
| return result; |
| } |
| |
| public InstructionList reduce_objectLiteral(IASNode iNode, Vector<InstructionList> elements) |
| { |
| // TODO: Investigate optimizing. |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList element: elements ) |
| { |
| result.addAll(element); |
| } |
| |
| result.addInstruction(OP_newobject, elements.size()); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_objectLiteralElement(IASNode iNode, InstructionList id, InstructionList value) |
| { |
| InstructionList result = createInstructionList(iNode, value.size() + 1); |
| result.addAll(id); |
| // TODO: Push type analysis up through the CG, |
| // so that string constants don't go through |
| // convert_s. See http://bugs.adobe.com/jira/browse/CMP-118 |
| result.addInstruction(OP_convert_s); |
| result.addAll(value); |
| return result; |
| } |
| |
| public InstructionList reduce_optionalParameter(IASNode iNode, Name param_name, Binding param_type, Object raw_default_value) |
| { |
| IParameterNode parameter_node = (IParameterNode)iNode; |
| PooledValue transformed_default_value; |
| // raw_default_value will be null if the parameter's default value was not a constant expression. |
| if( raw_default_value == null ) |
| { |
| currentScope.addProblem(new NonConstantParamInitializerProblem(parameter_node.getAssignedValueNode())); |
| // re-write non-constant expression to undefined, so resulting ABC will pass the verifier. |
| transformed_default_value = new PooledValue(ABCConstants.UNDEFINED_VALUE); |
| } |
| else |
| { |
| PooledValue default_value = new PooledValue(raw_default_value); |
| transformed_default_value = |
| currentScope.getMethodBodySemanticChecker().checkInitialValue(parameter_node, param_type, default_value); |
| } |
| currentScope.makeParameter(getParameterContent(iNode), param_type.getName()); |
| currentScope.addDefaultValue(transformed_default_value); |
| return null; |
| } |
| |
| public Binding reduce_parameterizedName(IASNode iNode, Binding base, Binding param) |
| { |
| Name param_name = param.getName(); |
| |
| return new Binding(iNode, new Name(base.getName(), param_name), null); |
| } |
| |
| public InstructionList reduce_parameterizedTypeExpression(IASNode iNode, InstructionList base, InstructionList param) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(base); |
| result.addAll(param); |
| result.addInstruction(OP_applytype, 1); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_plist(IASNode iNode, Vector<InstructionList> pdecl) |
| { |
| // Parameter processing is done by side effect |
| // in the parameter reduction. |
| return null; |
| } |
| |
| public InstructionList reduce_postDecBracketExpr(IASNode iNode, InstructionList stem, InstructionList index, boolean need_result) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IUnaryOperatorNode)iNode).getOperandNode(); |
| |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false); |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| Binding stem_tmp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(stem_tmp.setlocal() ); |
| |
| result.addAll(index); |
| result.addInstruction(OP_dup); |
| |
| // Get the initial value, and dup a copy |
| // onto the stack as the value of this expression. |
| Binding index_tmp = currentScope.allocateTemp(); |
| result.addInstruction(index_tmp.setlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| result.addInstruction(op_unplus()); |
| |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| |
| result.addInstruction(OP_decrement); |
| Binding result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal() ); |
| |
| result.addInstruction(stem_tmp.getlocal() ); |
| result.addInstruction(index_tmp.getlocal() ); |
| result.addInstruction(result_tmp.getlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| |
| currentScope.releaseTemp(stem_tmp); |
| currentScope.releaseTemp(index_tmp); |
| currentScope.releaseTemp(result_tmp); |
| |
| return result; |
| } |
| |
| |
| public InstructionList reduce_postDecMemberExpr(IASNode iNode, InstructionList stem, Binding field, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false, field); |
| |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_getproperty, field.getName()); |
| result.addInstruction(op_unplus()); |
| |
| // Save a copy of the original value to use as the value of this expression. |
| Binding orig_tmp = null; |
| if( need_result ) |
| { |
| result.addInstruction(OP_dup); |
| orig_tmp = currentScope.allocateTemp(); |
| result.addInstruction(orig_tmp.setlocal() ); |
| } |
| |
| result.addInstruction(OP_decrement); |
| |
| generateAssignmentOp(iNode, field, result); |
| |
| if( need_result ) |
| { |
| result.addInstruction(orig_tmp.getlocal() ); |
| currentScope.releaseTemp(orig_tmp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_postDecNameExpr(IASNode iNode, Binding unary, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false, unary); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( unary.isLocal() ) { |
| ICompilerProject project = currentScope.getProject(); |
| |
| IDefinition inType = SemanticUtils.resolveUnaryExprType(iNode, project); |
| |
| IDefinition intType = project.getBuiltinType(BuiltinType.INT); |
| IDefinition numberType = project.getBuiltinType(BuiltinType.NUMBER); |
| if( inType == intType) |
| { |
| // Decrementing a local, typed as int |
| // we can use declocal_i since we know the result will be stored in an int |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(unary.declocal_i()); |
| } |
| else if( inType == numberType) |
| { |
| // Decrementing a local, typed as Number |
| // we can use declocal since we know the result will be stored in a Number |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(unary.declocal()); |
| } |
| else |
| { |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(op_unplus()); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_decrement); |
| coerce(result, inType); |
| result.addInstruction(unary.setlocal()); |
| } |
| } |
| else |
| { |
| Name n = unary.getName(); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_getproperty,n); |
| result.addInstruction(op_unplus()); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_decrement); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_setproperty,n); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_postIncBracketExpr(IASNode iNode, InstructionList stem, InstructionList index, boolean need_result) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IUnaryOperatorNode)iNode).getOperandNode(); |
| |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true); |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| Binding stem_tmp = currentScope.allocateTemp(); |
| result.addInstruction(stem_tmp.setlocal() ); |
| |
| result.addAll(index); |
| result.addInstruction(OP_dup); |
| |
| // Get the initial value, and dup a copy |
| // onto the stack as the value of this expression. |
| Binding index_tmp = currentScope.allocateTemp(); |
| result.addInstruction(index_tmp.setlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| result.addInstruction(op_unplus()); |
| |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| |
| result.addInstruction(OP_increment); |
| Binding result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal()); |
| |
| result.addInstruction(stem_tmp.getlocal() ); |
| result.addInstruction(index_tmp.getlocal() ); |
| result.addInstruction(result_tmp.getlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| |
| currentScope.releaseTemp(stem_tmp); |
| currentScope.releaseTemp(index_tmp); |
| currentScope.releaseTemp(result_tmp); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_postIncMemberExpr(IASNode iNode, InstructionList stem, Binding field, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true, field); |
| |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_getproperty, field.getName()); |
| result.addInstruction(op_unplus()); |
| |
| // Save a copy of the original value to use as the value of this expression. |
| Binding orig_tmp = null; |
| if( need_result ) |
| { |
| result.addInstruction(OP_dup); |
| orig_tmp = currentScope.allocateTemp(); |
| result.addInstruction(orig_tmp.setlocal() ); |
| } |
| |
| result.addInstruction(OP_increment); |
| result.addInstruction(OP_setproperty, field.getName()); |
| |
| if( need_result ) |
| { |
| result.addInstruction(orig_tmp.getlocal() ); |
| currentScope.releaseTemp(orig_tmp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_postIncNameExpr(IASNode iNode, Binding unary, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true, unary); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( unary.isLocal() ) { |
| ICompilerProject project = currentScope.getProject(); |
| |
| IDefinition inType = SemanticUtils.resolveUnaryExprType(iNode, project); |
| |
| IDefinition intType = project.getBuiltinType(BuiltinType.INT); |
| IDefinition numberType = project.getBuiltinType(BuiltinType.NUMBER); |
| if( inType == intType) |
| { |
| // Incrementing a local, typed as int |
| // we can use inclocal_i since we know the result will be stored in an int |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(unary.inclocal_i()); |
| } |
| else if( inType == numberType) |
| { |
| // Incrementing a local, typed as Number |
| // we can use inclocal since we know the result will be stored in a Number |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(unary.inclocal()); |
| } |
| else |
| { |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(op_unplus()); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_increment); |
| coerce(result, inType); |
| result.addInstruction(unary.setlocal()); |
| } |
| } |
| else |
| { |
| Name n = unary.getName(); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_getproperty,n); |
| result.addInstruction(op_unplus()); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_increment); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_setproperty,n); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preDecBracketExpr(IASNode iNode, InstructionList stem, InstructionList index, boolean need_result) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IUnaryOperatorNode)iNode).getOperandNode(); |
| |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| Binding stem_tmp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(stem_tmp.setlocal() ); |
| |
| result.addAll(index); |
| result.addInstruction(OP_dup); |
| |
| // Get the initial value. |
| Binding index_tmp = currentScope.allocateTemp(); |
| result.addInstruction(index_tmp.setlocal()); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| |
| // Increment, and dup a copy onto |
| // the stack as the value of this expression. |
| result.addInstruction(OP_decrement); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| Binding result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal()); |
| |
| result.addInstruction(stem_tmp.getlocal() ); |
| result.addInstruction(index_tmp.getlocal() ); |
| result.addInstruction(result_tmp.getlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| |
| currentScope.releaseTemp(stem_tmp); |
| currentScope.releaseTemp(index_tmp); |
| currentScope.releaseTemp(result_tmp); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preDecMemberExpr(IASNode iNode, InstructionList stem, Binding field, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false, field); |
| |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_getproperty, field.getName()); |
| result.addInstruction(OP_decrement); |
| |
| Binding result_tmp = null; |
| // Save a copy of the result to use as the value of this expression. |
| if( need_result ) |
| { |
| result.addInstruction(OP_dup); |
| result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal() ); |
| } |
| |
| result.addInstruction(OP_setproperty, field.getName()); |
| |
| if( need_result ) |
| { |
| result.addInstruction(result_tmp.getlocal() ); |
| currentScope.releaseTemp(result_tmp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preDecNameExpr(IASNode iNode, Binding unary, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, false, unary); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( unary.isLocal() ) { |
| ICompilerProject project = currentScope.getProject(); |
| |
| IDefinition inType = SemanticUtils.resolveUnaryExprType(iNode, project); |
| |
| IDefinition intType = project.getBuiltinType(BuiltinType.INT); |
| IDefinition numberType = project.getBuiltinType(BuiltinType.NUMBER); |
| if( inType == intType) |
| { |
| // Decrementing a local, typed as int |
| // we can use declocal_i since we know the result will be stored in an int |
| result.addInstruction(unary.declocal_i()); |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| } |
| else if( inType == numberType) |
| { |
| // Decrementing a local, typed as Number |
| // we can use declocal since we know the result will be stored in a Number |
| result.addInstruction(unary.declocal()); |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| } |
| else |
| { |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(OP_decrement); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| coerce(result, inType); |
| result.addInstruction(unary.setlocal()); |
| } |
| } |
| else |
| { |
| Name n = unary.getName(); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_getproperty,n); |
| result.addInstruction(OP_decrement); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_setproperty,n); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preIncBracketExpr(IASNode iNode, InstructionList stem, InstructionList index, boolean need_result) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)((IUnaryOperatorNode)iNode).getOperandNode(); |
| |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true); |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(stem); |
| Binding stem_tmp = currentScope.allocateTemp(); |
| result.addInstruction(OP_dup); |
| result.addInstruction(stem_tmp.setlocal() ); |
| |
| result.addAll(index); |
| result.addInstruction(OP_dup); |
| |
| // Get the initial value. |
| Binding index_tmp = currentScope.allocateTemp(); |
| result.addInstruction(index_tmp.setlocal() ); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_getproperty)); |
| |
| // Increment, and dup a copy onto |
| // the stack as the value of this expression. |
| result.addInstruction(OP_increment); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| Binding result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal() ); |
| |
| result.addInstruction(stem_tmp.getlocal() ); |
| result.addInstruction(index_tmp.getlocal()); |
| result.addInstruction(result_tmp.getlocal()); |
| result.addInstruction(arrayAccess(arrayIndexNode, OP_setproperty)); |
| |
| currentScope.releaseTemp(stem_tmp); |
| currentScope.releaseTemp(index_tmp); |
| currentScope.releaseTemp(result_tmp); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preIncMemberExpr(IASNode iNode, InstructionList stem, Binding field, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true, field); |
| |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(stem); |
| result.addInstruction(OP_dup); |
| result.addInstruction(OP_getproperty, field.getName()); |
| result.addInstruction(OP_increment); |
| |
| Binding result_tmp = null; |
| // Save a copy of the result to use as the value of this expression. |
| if( need_result ) |
| { |
| result.addInstruction(OP_dup); |
| result_tmp = currentScope.allocateTemp(); |
| result.addInstruction(result_tmp.setlocal()); |
| } |
| |
| result.addInstruction(OP_setproperty, field.getName()); |
| |
| if( need_result ) |
| { |
| result.addInstruction(result_tmp.getlocal()); |
| currentScope.releaseTemp(result_tmp); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_preIncNameExpr(IASNode iNode, Binding unary, boolean need_result) |
| { |
| currentScope.getMethodBodySemanticChecker().checkIncDec(iNode, true, unary); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( unary.isLocal() ) { |
| ICompilerProject project = currentScope.getProject(); |
| |
| IDefinition inType = SemanticUtils.resolveUnaryExprType(iNode, project); |
| |
| IDefinition intType = project.getBuiltinType(BuiltinType.INT); |
| IDefinition numberType = project.getBuiltinType(BuiltinType.NUMBER); |
| if( inType == intType) |
| { |
| // Incrementing a local, typed as int |
| // we can use inclocal_i since we know the result will be stored in an int |
| result.addInstruction(unary.inclocal_i()); |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| } |
| else if( inType == numberType) |
| { |
| // Incrementing a local, typed as Number |
| // we can use inclocal since we know the result will be stored in a Number |
| result.addInstruction(unary.inclocal()); |
| if( need_result ) |
| result.addInstruction(unary.getlocal()); |
| } |
| else |
| { |
| result.addInstruction(unary.getlocal()); |
| result.addInstruction(OP_increment); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| coerce(result, inType); |
| result.addInstruction(unary.setlocal()); |
| } |
| } |
| else |
| { |
| Name n = unary.getName(); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_getproperty,n); |
| result.addInstruction(OP_increment); |
| if( need_result ) |
| result.addInstruction(OP_dup); |
| result.addAll(currentScope.findProperty(unary, true)); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_setproperty,n); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Add instructions to the instructionlist passed in to coerce the top value of the stack |
| * to the given type. |
| * |
| * This will generate a generic OP_coerce, or one of the more specific coercion/conversion opcodes |
| * such as convert_d for Number, coerce_s for String, etc. |
| * |
| * Matches ASCs behavior. |
| * |
| * @param il the InstructionList to add the instruction to |
| * @param toType the type you want to coerce the top stack value to |
| */ |
| public void coerce(InstructionList il, IDefinition toType ) |
| { |
| if ( toType == null ) |
| return; |
| |
| assert toType instanceof ITypeDefinition; |
| |
| ICompilerProject project = currentScope.getProject(); |
| |
| if( toType == project.getBuiltinType(BuiltinType.ANY_TYPE) ) |
| { |
| il.addInstruction(OP_coerce_a); |
| } |
| else if ( toType == project.getBuiltinType(BuiltinType.STRING) ) |
| { |
| il.addInstruction(OP_coerce_s); |
| } |
| else if ( toType == project.getBuiltinType(BuiltinType.BOOLEAN) ) |
| { |
| il.addInstruction(OP_convert_b); |
| } |
| else if( toType == project.getBuiltinType(BuiltinType.NUMBER) ) |
| { |
| il.addInstruction(OP_convert_d); |
| } |
| else if( toType == project.getBuiltinType(BuiltinType.INT) ) |
| { |
| il.addInstruction(OP_convert_i); |
| } |
| else if( toType == project.getBuiltinType(BuiltinType.UINT) ) |
| { |
| il.addInstruction(OP_convert_u); |
| } |
| else |
| { |
| il.addInstruction(OP_coerce, ((DefinitionBase)toType).getMName(project)); |
| } |
| } |
| public InstructionList reduce_regexLiteral(IASNode iNode) |
| { |
| String flags = ((RegExpLiteralNode) iNode).getFlagString(); |
| |
| // If flags are defined, additional pushstring instruction is needed |
| InstructionList result = createInstructionList(iNode, (flags.isEmpty()) ? 4 : 5); |
| |
| result.addAll(currentScope.getPropertyValue(regexType, currentScope.getProject().getBuiltinType(BuiltinType.REGEXP))); |
| result.addInstruction(OP_pushstring, getStringLiteralContent(iNode)); |
| |
| if (!flags.isEmpty()) |
| { |
| result.addInstruction(OP_pushstring, flags); |
| result.addInstruction(OP_construct, 2); |
| } |
| else |
| { |
| result.addInstruction(OP_construct, 1); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_requiredParameter(IASNode iNode, Name param_name, Binding param_type) |
| { |
| currentScope.makeParameter(getParameterContent(iNode), param_type.getName()); |
| return null; |
| } |
| |
| public InstructionList reduce_restParameter(IASNode iNode, Name param_name, Binding param_type) |
| { |
| currentScope.getMethodBodySemanticChecker().checkRestParameter(iNode, param_type); |
| currentScope.makeParameter(getParameterContent(iNode), param_type.getName()); |
| return null; |
| } |
| |
| public InstructionList reduce_returnVoidSideEffect(IASNode iNode, InstructionList value) |
| { |
| currentScope.getMethodBodySemanticChecker().checkReturnValue(iNode); |
| |
| // Evaluate the expression for possible side effects. |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(value); |
| result.addInstruction(OP_returnvoid); |
| return result; |
| } |
| |
| public InstructionList reduce_returnValue(IASNode iNode, InstructionList value) |
| { |
| currentScope.getMethodBodySemanticChecker().checkReturnValue(iNode); |
| return reduce_returnWithSideEffects(iNode, value, OP_returnvalue); |
| } |
| |
| public InstructionList reduce_returnVoidValue(IASNode iNode, InstructionList no_value) |
| { |
| currentScope.getMethodBodySemanticChecker().checkReturnVoid(iNode); |
| return reduce_returnWithSideEffects(iNode, no_value, OP_returnvoid); |
| } |
| |
| |
| /** |
| * Reduce a return statement that may have side effects |
| * (either mutation or reading volatile state). |
| * @param iNode - the root IASNode of the return statement. |
| * @param value - the value to be returned, or just computed. |
| * @param opcode - OP_returnvalue or OP_returnvoid. |
| */ |
| private InstructionList reduce_returnWithSideEffects(IASNode iNode, InstructionList value, int opcode) |
| { |
| // Create two InstructionLists; the first, result list |
| // goes in the body of the try block. It computes the |
| // result value and stores it in a temp -- this allows |
| // the control-flow unwinding code to pop scopes, etc., |
| // with no effect on the AS3 program's observable computation. |
| // The second IL is a control-flow stub that fetches the |
| // return value from the temp and returns it; this stub |
| // goes to the control-flow manager to be massaged into |
| // the control-flow unwinding code required by the current |
| // control flow contexts, if any. |
| // HOWEVER: as an optimization, the temp and the stub IL |
| // are only used when the flow manager says they're needed. |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(value); |
| |
| Binding result_temp = null; |
| boolean need_temp = currentScope.getFlowManager().hasNontrivialFlowCharacteristics(); |
| |
| if ( need_temp ) |
| { |
| result_temp = currentScope.allocateTemp(); |
| result.addInstruction(result_temp.setlocal()); |
| } |
| |
| InstructionList controlFlowStub = null; |
| FunctionDefinition function_def = SemanticUtils.getFunctionDefinition(iNode); |
| if ( need_temp ) |
| { |
| controlFlowStub = createInstructionList(iNode); |
| controlFlowStub.addInstruction(result_temp.getlocal()); |
| addRelevantReturnOpcode(function_def, opcode, controlFlowStub); |
| } |
| else |
| { |
| addRelevantReturnOpcode(function_def, opcode, result); |
| } |
| |
| try |
| { |
| if ( need_temp ) |
| { |
| // The controlFlowStub may be replaced by code that |
| // branches to some context unwinding. |
| result.addAll(getNonLocalControlFlow(controlFlowStub, ControlFlowContextManager.FIND_ALL_CONTEXTS)); |
| } |
| else |
| { |
| // Ensure we get back the same IL as sent in, |
| // or the ABC will be incorrect. |
| InstructionList prev_result = result; |
| result = getNonLocalControlFlow(result, ControlFlowContextManager.FIND_ALL_CONTEXTS); |
| assert result == prev_result; |
| } |
| } |
| catch ( UnknownControlFlowTargetException cant_return ) |
| { |
| currentScope.addProblem(new UnexpectedReturnProblem(iNode)); |
| return createInstructionList(iNode); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_returnVoid(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| currentScope.getMethodBodySemanticChecker().checkReturnVoid(iNode); |
| FunctionDefinition functionDef = SemanticUtils.getFunctionDefinition(iNode); |
| addRelevantReturnOpcode(functionDef, OP_returnvoid, result); |
| |
| try |
| { |
| return getNonLocalControlFlow(result, ControlFlowContextManager.FIND_ALL_CONTEXTS); |
| } |
| catch ( UnknownControlFlowTargetException cant_return ) |
| { |
| currentScope.addProblem(new UnexpectedReturnProblem(iNode)); |
| return createInstructionList(iNode); |
| } |
| |
| } |
| |
| /** |
| * When inlining a method, a jump opcode must be emitted rather than a return |
| * |
| * @param functionDef the function definition from which we're returning |
| * @param opcode the original opcode which may be translated when inlining a function |
| * @param result the InstructionList onto which the instruction will be added |
| */ |
| private void addRelevantReturnOpcode(FunctionDefinition functionDef, int opcode, InstructionList result) |
| { |
| if (currentScope.insideInlineFunction()) |
| { |
| Label inlinedFunctionCallSiteLabel = ((InlineFunctionLexicalScope)currentScope).getInlinedFunctionCallSiteLabel(); |
| switch (opcode) |
| { |
| case OP_returnvoid: |
| { |
| // returnvoid pushes undefined onto the stack, so mimic that behavior |
| result.addInstruction(OP_pushundefined); |
| |
| // if the function returns a type, undefined is coerced to that type |
| if (functionDef != null) |
| { |
| TypeDefinitionBase returnType = (TypeDefinitionBase)functionDef.resolveReturnType(currentScope.getProject()); |
| if (returnType != null && |
| !returnType.equals(ClassDefinition.getVoidClassDefinition()) && |
| !returnType.equals(ClassDefinition.getAnyTypeClassDefinition())) |
| { |
| result.addInstruction(OP_coerce, returnType.getMName(currentScope.getProject())); |
| } |
| } |
| |
| result.addInstruction(OP_jump, inlinedFunctionCallSiteLabel); |
| break; |
| } |
| case OP_returnvalue: |
| result.addInstruction(OP_jump, inlinedFunctionCallSiteLabel); |
| break; |
| default: |
| result.addInstruction(opcode); |
| } |
| } |
| else |
| { |
| result.addInstruction(opcode); |
| } |
| } |
| |
| public InstructionList reduce_runtimeNameExpression(IASNode iNode, InstructionList expr) |
| { |
| return expr; |
| } |
| |
| public Binding reduce_simpleName(IASNode iNode) |
| { |
| final Binding result; |
| final IdentifierNode identifier = (IdentifierNode)iNode; |
| if (identifier.getName().equals(IASLanguageConstants.ANY_TYPE) && SemanticUtils.isE4XWildcardProperty(identifier)) |
| { |
| // TODO: This is a specific fix for CMP-1731. CMP-696 should be able |
| // to cover this use case in a more generic and robust way. Revisit |
| // this implementation when CMP-696 is fixed. |
| final ICompilerProject project = currentScope.getProject(); |
| final Nsset qualifiers = SemanticUtils.getOpenNamespaces(iNode, project); |
| final Name name = new Name(CONSTANT_Multiname, qualifiers, null); |
| result = new Binding(iNode, name, identifier.resolve(project)); |
| } |
| else |
| { |
| result = currentScope.resolveName(identifier); |
| currentScope.getMethodBodySemanticChecker().checkSimpleName(iNode, result); |
| } |
| return result; |
| } |
| |
| public Name reduce_declName(IASNode iNode ) |
| { |
| // We are the name of a declaration, get the containing DefinitionNode and grab the |
| // name from there |
| BaseDefinitionNode bdn = (BaseDefinitionNode)iNode.getAncestorOfType(BaseDefinitionNode.class); |
| DefinitionBase db = bdn.getDefinition(); |
| Name n = db.getMName(currentScope.getProject()); |
| return n; |
| } |
| |
| public InstructionList reduce_strictneqExpr(IASNode iNode, InstructionList l, InstructionList r) |
| { |
| InstructionList result = binaryOp(iNode, l, r, OP_strictequals); |
| result.addInstruction(OP_not); |
| return result; |
| } |
| |
| public Binding reduce_superAccess(IASNode iNode, Binding qualified_name) |
| { |
| currentScope.getMethodBodySemanticChecker().checkSuperAccess(iNode); |
| qualified_name.setSuperQualified(true); |
| return qualified_name; |
| } |
| |
| public InstructionList reduce_superCallExpr(IASNode iNode, Vector<InstructionList> args) |
| { |
| currentScope.getMethodBodySemanticChecker().checkExplicitSuperCall(iNode, args); |
| // TODO: Investigate optimizing this. |
| InstructionList result = createInstructionList(iNode); |
| // Special case first part: don't look for the "super" name, |
| // push "this" on the stack. |
| result.addInstruction(OP_getlocal0); |
| for ( InstructionList arg: args) |
| result.addAll(arg); |
| // Special case second part: call constructsuper. |
| result.addInstruction(OP_constructsuper, args.size() ); |
| return result; |
| } |
| |
| private static class LookupSwitchInfo |
| { |
| public int minCase = Integer.MAX_VALUE; |
| public int maxCase = Integer.MIN_VALUE; |
| public ConditionalFragment defaultCase; |
| } |
| |
| public InstructionList reduce_switchStmt(IASNode iNode, InstructionList switch_expr, Vector<ConditionalFragment> cases) |
| { |
| if (cases.size() == 0) |
| { |
| return reduce_trivial_switchStmt(iNode, switch_expr); |
| } |
| else |
| { |
| IExpressionNode conditional = ((SwitchNode)iNode).getConditionalNode(); |
| LookupSwitchInfo lookupSwitchInfo = getLookupSwitchInfo(conditional, cases); |
| |
| if (lookupSwitchInfo != null) |
| return reduce_lookup_switchStmt(iNode, switch_expr, cases, lookupSwitchInfo); |
| else |
| return reduce_ifelse_switchStmt(iNode, switch_expr, cases); |
| } |
| } |
| |
| /** |
| * Inspect the switch cases to determine if a lookup switch can be used. Any criteria |
| * for using a lookup switch or if/else chain should be adjusted here. |
| * |
| * @param conditional |
| * @param cases |
| * @return LookupSwitchInfo if a lookup switch can be used, otherwise null |
| */ |
| private LookupSwitchInfo getLookupSwitchInfo(IExpressionNode conditional, Vector<ConditionalFragment> cases) |
| { |
| // only generate a lookup switch if the conditional type is of type int or uint |
| IDefinition conditionalType = conditional.resolveType(currentScope.getProject()); |
| if (!SemanticUtils.isBuiltin(conditionalType, BuiltinType.INT, currentScope.getProject()) && |
| !SemanticUtils.isBuiltin(conditionalType, BuiltinType.UINT, currentScope.getProject())) |
| return null; |
| |
| LookupSwitchInfo lookupSwitchInfo = new LookupSwitchInfo(); |
| |
| boolean hasConditionalCases = false; |
| for (ConditionalFragment currentCase : cases) |
| { |
| if (currentCase.isUnconditionalAlternative()) |
| { |
| lookupSwitchInfo.defaultCase = currentCase; |
| continue; |
| } |
| |
| if (currentCase.constantCondition == null) |
| return null; |
| |
| assert (SemanticUtils.isBuiltin(conditionalType, BuiltinType.INT, currentScope.getProject()) || |
| SemanticUtils.isBuiltin(conditionalType, BuiltinType.UINT, currentScope.getProject())) : "only switching on type int or uint supported"; |
| |
| long value; |
| if (SemanticUtils.isBuiltin(conditionalType, BuiltinType.UINT, currentScope.getProject())) |
| value = ECMASupport.toUInt32(currentCase.constantCondition); |
| else |
| value = ECMASupport.toInt32(currentCase.constantCondition); |
| |
| // We could potentially use longs here, but for now just fallback to an if/else |
| // jump in the case where a value is outside the integeger range unless we find |
| // real world code where it makes sense to handle longs too. |
| if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) |
| return null; |
| |
| lookupSwitchInfo.minCase = Math.min((int)value, lookupSwitchInfo.minCase); |
| lookupSwitchInfo.maxCase = Math.max((int)value, lookupSwitchInfo.maxCase); |
| hasConditionalCases = true; |
| } |
| |
| // if there are no conditional cases, don't generate lookup switch |
| if (!hasConditionalCases) |
| return null; |
| |
| int range = lookupSwitchInfo.maxCase - lookupSwitchInfo.minCase; |
| |
| // if the range is greater than 20 and if less than 50% ranges dense, then use if/else |
| if ((range > 20) && (range < cases.size() * 2)) |
| return null; |
| |
| // limit the size of the jump table to cut down on compile time |
| // memory building up the table, and avoid any potential player |
| // bugs with massive lookup switches |
| if (range > (2 ^ 16)) |
| return null; |
| |
| return lookupSwitchInfo; |
| } |
| |
| private InstructionList reduce_lookup_switchStmt(IASNode iNode, InstructionList switch_expr, Vector<ConditionalFragment> cases, LookupSwitchInfo lookupSwitchInfo) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(switch_expr); |
| result.addInstruction(OP_convert_i); |
| |
| // save some space in the case table, by not generating entries |
| // for any values between 0 and the min case value |
| int caseOffset = 0; |
| if (lookupSwitchInfo.minCase > 0) |
| { |
| caseOffset = lookupSwitchInfo.minCase; |
| lookupSwitchInfo.minCase = 0; |
| lookupSwitchInfo.maxCase = lookupSwitchInfo.maxCase - caseOffset; |
| result.addInstruction(OP_pushint, Integer.valueOf(caseOffset)); |
| result.addInstruction(OP_subtract_i); |
| } |
| // if we have negative values in the case expression, need |
| // to offset from zero |
| else if (lookupSwitchInfo.minCase < 0) |
| { |
| caseOffset = lookupSwitchInfo.minCase; |
| lookupSwitchInfo.minCase = 0; |
| lookupSwitchInfo.maxCase = lookupSwitchInfo.maxCase - caseOffset; |
| result.addInstruction(OP_pushint, new Integer(lookupSwitchInfo.minCase + 1)); |
| result.addInstruction(OP_add_i); |
| } |
| |
| Label switchTail = null; |
| Label defaultLabel; |
| |
| if (lookupSwitchInfo.defaultCase != null) |
| defaultLabel = lookupSwitchInfo.defaultCase.getLabel(); |
| else |
| defaultLabel = switchTail = new Label(); |
| |
| // generate the case table, maxCase+2, as count from zero |
| // and add one for the default label at the end |
| Label[] caseLabels = new Label[lookupSwitchInfo.maxCase + 2]; |
| |
| // pre-fill the case label table with the default label. |
| Arrays.fill(caseLabels, defaultLabel); |
| |
| // fill in specific labels for any specified values |
| for (ConditionalFragment current_case : cases) |
| { |
| if (current_case.isUnconditionalAlternative()) |
| continue; |
| |
| Object caseValue = current_case.constantCondition; |
| // a constant of value 90000 was in a SWC as a double even though |
| // the type of the constant was int |
| if (caseValue instanceof Double) |
| caseValue = new Integer(((Double)caseValue).intValue()); |
| assert (caseValue instanceof Integer) : "reduce_lookup_switchStmt called on non integer case value"; |
| final int index = (Integer)caseValue - caseOffset; |
| // if there is already a non-default value for this |
| // index, ignore it, as only the first case counts |
| if (caseLabels[index] != defaultLabel) |
| continue; |
| |
| caseLabels[index] = current_case.getLabel(); |
| } |
| |
| result.addInstruction(OP_lookupswitch, caseLabels); |
| |
| Label default_case_label = null; |
| for (ConditionalFragment current_case : cases) |
| { |
| if (current_case.isUnconditionalAlternative()) |
| { |
| // The parser rejects duplicate default alternatives |
| // in some situations, but not in others. |
| if (default_case_label == null) |
| default_case_label = current_case.statement.getLabel(); |
| else |
| currentScope.addProblem(new MultipleSwitchDefaultsProblem(current_case.site)); |
| } |
| |
| result.addAll(current_case.statement); |
| } |
| |
| switchTail = addInterstitialControlFlow(result, switchTail); |
| |
| // No continue here, and thus there is no need |
| // to check for previous labeled flow contexts. |
| currentScope.getFlowManager().finishSwitchControlFlowContext(result); |
| |
| if ( switchTail != null ) |
| result.labelNext(switchTail); |
| |
| return result; |
| } |
| |
| private InstructionList reduce_ifelse_switchStmt(IASNode iNode, InstructionList switch_expr, Vector<ConditionalFragment> cases) |
| { |
| assert (cases.size() > 0) : "reduce_ifelse_switchStmt called on switch stmt with no cases"; |
| |
| // need to convert any constant values back |
| // to a conditional expression when not generating |
| // a lookup switch |
| convertConstantValueConditionsToInstructions(cases); |
| |
| // TODO: Optimize InstructionList size. |
| InstructionList result = createInstructionList(iNode); |
| Label default_case_label = null; |
| Label switch_tail = null; |
| |
| // Get the switch value and save it in a temp. |
| Binding switch_temp = currentScope.allocateTemp(); |
| result.addAll(switch_expr); |
| result.addInstruction(switch_temp.setlocal()); |
| |
| // First, jump to the switch table. |
| if ( ! cases.elementAt(0).isUnconditionalAlternative() || cases.size() == 1 ) |
| result.addInstruction(OP_jump, cases.elementAt(0).getLabel()); |
| else |
| result.addInstruction(OP_jump, cases.elementAt(1).getLabel()); |
| |
| |
| for ( ConditionalFragment current_case: cases ) |
| { |
| // Ensure the case begins with a OP_label instruction |
| // so we can branch back to it. |
| if ( current_case.statement.isEmpty() || current_case.statement.firstElement().getOpcode() != OP_label ) |
| { |
| InstructionList labeled_list = createInstructionList(iNode); |
| labeled_list.addInstruction(OP_label); |
| labeled_list.addAll(current_case.statement); |
| current_case.statement = labeled_list; |
| } |
| |
| if ( current_case.isUnconditionalAlternative() ) |
| { |
| // The parser rejects duplicate default alternatives |
| // in some situations, but not in others. |
| if ( default_case_label == null ) |
| default_case_label = current_case.statement.getLabel(); |
| else |
| currentScope.addProblem(new MultipleSwitchDefaultsProblem(current_case.site)); |
| } |
| // no need to check for duplicate cases, as the first case is always chosen in this situation |
| |
| current_case.getStatementLabel(); // Touch the label so it will be properly recorded. |
| result.addAll(current_case.statement); |
| } |
| |
| // Jump to the tail. |
| switch_tail = addInterstitialControlFlow(result, null); |
| |
| // Emit the "switch table" as if/else statements. |
| for ( ConditionalFragment current_case: cases ) |
| { |
| if ( !current_case.isUnconditionalAlternative() ) |
| { |
| result.addAll(current_case.condition); |
| result.addInstruction(switch_temp.getlocal()); |
| result.addInstruction(OP_ifstricteq, current_case.getStatementLabel() ); |
| } |
| } |
| |
| // Emit the jump to the default case. |
| if ( default_case_label != null ) |
| { |
| result.addInstruction(OP_jump, default_case_label); |
| } |
| |
| currentScope.releaseTemp(switch_temp); |
| |
| // No continue here, and thus there is no need |
| // to check for previous labeled flow contexts. |
| currentScope.getFlowManager().finishSwitchControlFlowContext(result); |
| |
| if ( switch_tail != null ) |
| result.labelNext(switch_tail); |
| |
| return result; |
| } |
| |
| private InstructionList reduce_trivial_switchStmt(IASNode iNode, InstructionList switch_expr) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(switch_expr); |
| // Balance the stack! |
| result.addInstruction(OP_pop); |
| currentScope.getFlowManager().finishSwitchControlFlowContext(result); |
| return result; |
| } |
| |
| /** |
| * If the optimized constant case of conditional fragements are not being used, need to go through |
| * all cases and convert any constant conditionals back to a conditional instruction list for codegen. |
| * |
| * @param cases |
| */ |
| private void convertConstantValueConditionsToInstructions(Vector<ConditionalFragment> cases) |
| { |
| for (ConditionalFragment current_case : cases) |
| { |
| current_case.convertConstantConditionToInstruction(); |
| |
| if (current_case.isConstantConditional()) |
| { |
| current_case.condition = transform_constant_value(current_case.site, current_case.constantCondition); |
| } |
| } |
| } |
| |
| public InstructionList reduce_ternaryExpr(IASNode iNode, InstructionList test, InstructionList when_true, InstructionList when_false) |
| { |
| // AIR (and not FlashPlayer) requires a coerce_a at the end of each clause if the results will be |
| // assigned to a Dictionary. |
| IBinaryOperatorNode binaryNode = (IBinaryOperatorNode)iNode.getAncestorOfType(IBinaryOperatorNode.class); |
| boolean isBracketAssign = binaryNode != null && binaryNode.getOperator() == OperatorType.ASSIGNMENT |
| && binaryNode.getLeftOperandNode() instanceof DynamicAccessNode; |
| ITypeDefinition destinationType = null; |
| if (isBracketAssign) |
| destinationType = binaryNode.getLeftOperandNode().resolveType(currentScope.getProject()); |
| TernaryOperatorNode ternaryNode = (TernaryOperatorNode)iNode; |
| |
| IExpressionNode valueNode; |
| IDefinition type_def; //= valueNode.resolveType(currentScope.getProject()); //SemanticUtils.getDefinitionOfUnderlyingType(, |
| // true, currentScope.getProject()); |
| boolean need_coerce = false; |
| |
| InstructionList result = createInstructionList(iNode, test.size() + when_true.size() + when_false.size() + 2); |
| Label tail = new Label(); |
| |
| result.addAll(test); |
| result.addInstruction(OP_iffalse, when_false.getLabel()); |
| result.addAll(when_true); |
| valueNode = ternaryNode.getLeftOperandNode(); |
| type_def = valueNode.resolveType(currentScope.getProject()); |
| need_coerce = type_def != null && !type_def.equals(destinationType); |
| if (need_coerce && isBracketAssign) |
| coerce(result, destinationType); |
| result.addInstruction(OP_jump, tail); |
| result.addAll(when_false); |
| valueNode = ternaryNode.getRightOperandNode(); |
| type_def = valueNode.resolveType(currentScope.getProject()); |
| need_coerce = type_def != null && !type_def.equals(destinationType); |
| if (need_coerce && isBracketAssign) |
| coerce(result, destinationType); |
| result.labelNext(tail); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_throwStmt(IASNode iNode, InstructionList tossable) |
| { |
| currentScope.getMethodBodySemanticChecker().checkThrow(iNode); |
| |
| InstructionList result = createInstructionList(iNode, tossable.size() + 1); |
| result.addAll(tossable); |
| result.addInstruction(OP_throw); |
| return result; |
| } |
| |
| public InstructionList reduce_tryCatchFinallyStmt(IASNode iNode, InstructionList try_stmt, InstructionList finally_stmt, Vector<CatchPrototype> catch_blocks) |
| { |
| InstructionList result = generateTryCatchFinally(try_stmt, catch_blocks, finally_stmt); |
| currentScope.getFlowManager().finishExceptionContext(); |
| return result; |
| } |
| |
| public InstructionList reduce_tryCatchStmt(IASNode iNode, InstructionList try_stmt, Vector<CatchPrototype> catch_blocks) |
| { |
| // TODO: Optimize. |
| InstructionList result = createInstructionList(iNode); |
| |
| if ( try_stmt.isEmpty() ) |
| { |
| // TODO: The catch clause gen may have |
| // side effects, so we can't skip this. |
| try_stmt = createInstructionList(iNode); |
| try_stmt.addInstruction(OP_nop); |
| } |
| |
| Label catch_tail = new Label(); |
| |
| result.addAll(try_stmt); |
| result.addInstruction(OP_jump, catch_tail); |
| |
| // Get labels for the start and end of the try block. |
| Label try_start = result.getLabel(); |
| Label try_end = result.getLastLabel(); |
| |
| for ( CatchPrototype catch_proto: catch_blocks ) |
| { |
| boolean is_last_catch = catch_proto.equals(catch_blocks.lastElement()); |
| |
| InstructionList catch_body = generateCatchBlock(try_start, try_end, catch_proto); |
| |
| if( !is_last_catch && catch_body.canFallThrough() ) |
| catch_body.addInstruction(OP_jump, catch_tail); |
| |
| result.addAll(catch_body); |
| } |
| |
| currentScope.getFlowManager().finishExceptionContext(); |
| |
| result.labelNext(catch_tail); |
| return result; |
| } |
| |
| public InstructionList reduce_tryFinallyStmt(IASNode iNode, InstructionList try_stmt, InstructionList finally_stmt) |
| { |
| InstructionList result = generateTryCatchFinally(try_stmt, null, finally_stmt); |
| currentScope.getFlowManager().finishExceptionContext(); |
| return result; |
| } |
| |
| public InstructionList reduce_typedFunction_to_statement(IASNode iNode, InstructionList plist, Binding return_type, InstructionList block) |
| { |
| Binding nestedFunctionName = currentScope.resolveName((IdentifierNode)SemanticUtils.getNthChild(iNode, 0)); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| this.generateNestedFunction( |
| iNode, |
| this.miniScopes.empty()? result: this.miniScopes.peek(), |
| nestedFunctionName, |
| return_type.getName(), |
| block |
| ); |
| |
| currentScope.getMethodBodySemanticChecker().checkNestedFunctionDecl((IFunctionNode)iNode); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_typedVariableDecl(IASNode iNode, Name var_name, Binding var_type, Vector<InstructionList> chained_decls) |
| { |
| BaseVariableNode var_node = (BaseVariableNode)iNode; |
| currentScope.getMethodBodySemanticChecker().checkVariableDeclaration(iNode); |
| |
| // Must check for this before we call makeVariable, but need to emit the instructions after |
| // makeVariable so that Bindings are set up correctly with the right local register indexes. |
| boolean needsHoistedInitInsns = currentScope.needsHoistedInitInsns(var_name, false); |
| |
| Binding var = currentScope.resolveName((IdentifierNode)var_node.getNameExpressionNode()); |
| currentScope.makeVariable(var, var_type.getName(), ((BaseDefinitionNode)iNode).getMetaInfos()); |
| |
| // Now that makeVariable is done, emit the hoisted instructions if we needed them. |
| if(needsHoistedInitInsns) |
| addHoistedInstructionsForVarDecl(var_name, var_type); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| |
| for ( InstructionList decl: chained_decls ) |
| result.addAll(decl); |
| |
| return result; |
| } |
| |
| /** |
| * Helper method to add hoisted init instructions for a given variable. |
| * |
| * This is neccessary when local vars live in registers, and they are referenced before they are inited. |
| * If we don't emit the init insns at the top of the method, then the locals could have the wrong values/types |
| * when they're referenced resulting in incorrect behavior at runtime |
| * @param var_name the name of the local var |
| * @param var_type the type of the local var |
| */ |
| private void addHoistedInstructionsForVarDecl (Name var_name, Binding var_type) |
| { |
| Binding var_binding = currentScope.getLocalBinding(var_name); |
| if ( var_binding != null && var_binding.isLocal() ) |
| { |
| // Add initializer code to the init instructions. |
| InstructionList init_insns = currentScope.getHoistedInitInstructions(); |
| |
| ICompilerProject project = currentScope.getProject(); |
| |
| IDefinition type_def = var_type.getDefinition(); |
| |
| if ( |
| type_def == project.getBuiltinType(BuiltinType.INT) || |
| type_def == project.getBuiltinType(BuiltinType.UINT) |
| ) |
| { |
| init_insns.addInstruction(OP_pushbyte, 0); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| else if ( type_def == project.getBuiltinType(BuiltinType.BOOLEAN) ) |
| { |
| init_insns.addInstruction(OP_pushfalse); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| else if ( type_def == project.getBuiltinType(BuiltinType.NUMBER) ) |
| { |
| init_insns.addInstruction(OP_pushnan); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| else if ( type_def == project.getBuiltinType(BuiltinType.ANY_TYPE) ) |
| { |
| init_insns.addInstruction(OP_pushundefined); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| else if ( |
| type_def instanceof ITypeDefinition && |
| ((ITypeDefinition)type_def).isInstanceOf((ITypeDefinition)project.getBuiltinType(BuiltinType.OBJECT), project) |
| ) |
| { |
| init_insns.addInstruction(OP_pushnull); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| else |
| { |
| init_insns.addInstruction(OP_pushundefined); |
| init_insns.addInstruction(var_binding.setlocal()); |
| } |
| } |
| } |
| |
| public InstructionList reduce_typedVariableDeclWithInitializer(IASNode iNode, Name var_name, Binding var_type, InstructionList var_initializer, Vector<InstructionList> chained_decls) |
| { |
| BaseVariableNode var_node = (BaseVariableNode)iNode; |
| |
| currentScope.getMethodBodySemanticChecker().checkVariableDeclaration(iNode); |
| |
| // Must check for this before we call makeVariable, but need to emit the instructions after |
| // makeVariable so that Bindings are set up correctly with the right local register indexes. |
| boolean refedBeforeInited = currentScope.needsHoistedInitInsns(var_name, true); |
| |
| Binding var = currentScope.resolveName((IdentifierNode)var_node.getNameExpressionNode()); |
| currentScope.makeVariable(var, var_type.getName(), ((BaseDefinitionNode)iNode).getMetaInfos()); |
| |
| // If the binding has already been referenced, then we need to emit some hoisted init instructions |
| // so that the initial value will be correct. |
| if( refedBeforeInited ) |
| addHoistedInstructionsForVarDecl(var_name, var_type); |
| |
| currentScope.getMethodBodySemanticChecker().checkInitialization(iNode, var); |
| InstructionList result = generateAssignment(iNode, var, var_initializer); |
| for ( InstructionList decl: chained_decls ) |
| result.addAll(decl); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_typedVariableDeclWithConstantInitializer(IASNode iNode, Name var_name, Binding var_type, Object constant_var_initializer, Vector<InstructionList> chained_decls) |
| { |
| BaseVariableNode var_node = (BaseVariableNode)iNode; |
| PooledValue transformed_constant_initializer = |
| currentScope.getMethodBodySemanticChecker().checkInitialValue(var_node, var_type, new PooledValue(constant_var_initializer)); |
| |
| // if this definition isn't a const, then just use the normal variable initialization, as |
| // we only initialize the const slots and not var slots |
| if (!SemanticUtils.isConst(iNode, currentScope.getProject())) |
| { |
| InstructionList var_initializer = transform_pooled_value(iNode, transformed_constant_initializer); |
| return reduce_typedVariableDeclWithInitializer(iNode, var_name, var_type, var_initializer, chained_decls); |
| } |
| |
| currentScope.getMethodBodySemanticChecker().checkVariableDeclaration(iNode); |
| InstructionList result = createInstructionList(iNode); |
| Binding var = currentScope.resolveName((IdentifierNode)var_node.getNameExpressionNode()); |
| currentScope.makeVariable(var, var_type.getName(), ((BaseDefinitionNode)iNode).getMetaInfos(), transformed_constant_initializer.getValue()); |
| |
| // If the variable is in a local, then it needs to be initialized at the start of the method. |
| if ( var.isLocal() ) |
| { |
| InstructionList var_initializer = transform_pooled_value(iNode, transformed_constant_initializer); |
| currentScope.getHoistedInitInstructions().addAll(reduce_typedVariableDeclWithInitializer(iNode, var_name, var_type, var_initializer, new Vector<InstructionList>())); |
| } |
| |
| for ( InstructionList decl: chained_decls ) |
| result.addAll(decl); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_typedBindableVariableDecl(IASNode iNode, Name name, Binding var_type, Vector<InstructionList> chained_decls) |
| { |
| BaseVariableNode vn = (BaseVariableNode)iNode; |
| currentScope.getMethodBodySemanticChecker().checkBindableVariableDeclaration(iNode, vn.getDefinition()); |
| InstructionList result = createInstructionList(iNode); |
| Binding var = currentScope.resolveName((IdentifierNode)vn.getNameExpressionNode()); |
| currentScope.makeBindableVariable(var, var_type.getName(), vn.getMetaInfos()); |
| for ( InstructionList decl: chained_decls ) |
| result.addAll(decl); |
| |
| return result; |
| } |
| public InstructionList reduce_typedBindableVariableDeclWithInitializer(IASNode iNode, Name var_name, Binding var_type, InstructionList var_initializer, Vector<InstructionList> chained_decls) |
| { |
| BaseVariableNode vn = (BaseVariableNode)iNode; |
| currentScope.getMethodBodySemanticChecker().checkBindableVariableDeclaration(iNode, vn.getDefinition()); |
| Binding var = currentScope.resolveName((IdentifierNode)vn.getNameExpressionNode()); |
| currentScope.makeBindableVariable(var, var_type.getName(), vn.getMetaInfos()); |
| // pass in null as definition so the assignment goes to the backing var and not the setter |
| // maybe we'll have to get the actual var def someday. |
| InstructionList result = generateAssignment(iNode, new Binding(iNode, BindableHelper.getBackingPropertyName(var_name), null), var_initializer); |
| for ( InstructionList decl: chained_decls ) |
| result.addAll(decl); |
| |
| return result; |
| } |
| public Binding reduce_typedVariableExpression(IASNode iNode, Name var_name, Binding var_type) |
| { |
| VariableExpressionNode var_expr_node = (VariableExpressionNode)iNode; |
| BaseVariableNode var_node = (BaseVariableNode) var_expr_node.getTargetVariable(); |
| currentScope.getMethodBodySemanticChecker().checkVariableDeclaration(SemanticUtils.getNthChild(iNode, 0)); |
| Binding var = currentScope.resolveName((IdentifierNode)var_node.getNameExpressionNode()); |
| currentScope.makeVariable(var, var_type.getName(), var_node.getMetaInfos()); |
| |
| return var; |
| } |
| |
| public InstructionList reduce_typelessFunction(IASNode iNode, InstructionList plist, InstructionList block) |
| { |
| Binding nestedFunctionName = currentScope.resolveName((IdentifierNode)SemanticUtils.getNthChild(iNode, 0)); |
| |
| InstructionList result = createInstructionList(iNode); |
| |
| this.generateNestedFunction( |
| iNode, |
| this.miniScopes.empty()? result: this.miniScopes.peek(), |
| nestedFunctionName, |
| null, |
| block |
| ); |
| |
| currentScope.getMethodBodySemanticChecker().checkNestedFunctionDecl((IFunctionNode)iNode); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_typeof_expr(IASNode iNode, InstructionList operand) |
| { |
| InstructionList result = unaryOp(iNode, operand, OP_typeof); |
| this.typeofCount--; |
| return result; |
| } |
| |
| public InstructionList reduce_typeof_name(IASNode iNode, Binding binding) |
| { |
| InstructionList result = null; |
| |
| if ( binding.getDefinition() != null ) |
| { |
| ITypeDefinition typezo = binding.getDefinition().resolveType(currentScope.getProject()); |
| |
| ICompilerProject project = currentScope.getProject(); |
| |
| if ( typezo == project.getBuiltinType(BuiltinType.STRING) ) |
| { |
| result = createInstructionList(iNode); |
| result.addInstruction(OP_pushstring, "string"); |
| } |
| else if ( |
| typezo == project.getBuiltinType(BuiltinType.NUMBER) || |
| typezo == project.getBuiltinType(BuiltinType.INT) || |
| typezo == project.getBuiltinType(BuiltinType.UINT) |
| ) |
| { |
| result = createInstructionList(iNode); |
| result.addInstruction(OP_pushstring, "number"); |
| } |
| else if ( typezo == project.getBuiltinType(BuiltinType.BOOLEAN) ) |
| { |
| result = createInstructionList(iNode); |
| result.addInstruction(OP_pushstring, "boolean"); |
| } |
| } |
| |
| |
| if ( result == null ) |
| { |
| result = createInstructionList(iNode); |
| generateAccess(binding, AccessType.Lenient, result); |
| result.addInstruction(OP_typeof); |
| } |
| |
| this.typeofCount--; |
| return result; |
| } |
| |
| public InstructionList reduce_useNamespaceDirective(IASNode iNode, Binding ns_name) |
| { |
| currentScope.getMethodBodySemanticChecker().checkUseNamespaceDirective(iNode, ns_name); |
| return createInstructionList(iNode); |
| } |
| |
| public InstructionList reduce_variableExpression(IASNode iNode, Vector<InstructionList> decls) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| for ( InstructionList var_decl: decls ) |
| result.addAll(var_decl); |
| return result; |
| } |
| |
| public InstructionList reduce_vectorLiteral(IASNode iNode, Binding type_param, Vector<InstructionList> elements) |
| { |
| currentScope.getMethodBodySemanticChecker().checkVectorLiteral(iNode, type_param); |
| InstructionList result = createInstructionList(iNode); |
| |
| Nsset ns_set = new Nsset(new Namespace(CONSTANT_PackageNs, IASLanguageConstants.Vector_impl_package)); |
| Name vector_name = new Name(CONSTANT_Qname, ns_set, IASLanguageConstants.Vector); |
| |
| result.addAll(currentScope.getPropertyValue(vector_name, currentScope.getProject().getBuiltinType(BuiltinType.VECTOR))); |
| generateTypeNameParameter(type_param, result); |
| result.addInstruction(OP_applytype, 1); |
| result.pushNumericConstant(elements.size()); |
| result.addInstruction(OP_construct, 1); |
| |
| // Share this instruction; there may be many elements. |
| Instruction setter = arrayAccess(iNode, OP_setproperty); |
| |
| for ( int i = 0; i < elements.size(); i++ ) |
| { |
| result.addInstruction(OP_dup); |
| result.pushNumericConstant(i); |
| result.addAll(elements.elementAt(i)); |
| result.addInstruction(setter); |
| } |
| |
| return result; |
| } |
| |
| public InstructionList reduce_voidExpr_to_expression(IASNode iNode) |
| { |
| InstructionList il = createInstructionList(iNode); |
| il.addInstruction(OP_pushundefined); |
| return il; |
| } |
| |
| public Binding reduce_voidExpr_to_return_type_name(IASNode node) |
| { |
| return new Binding(node, voidType, null); |
| } |
| |
| public Binding reduce_voidExpr_to_type_name(IASNode node) |
| { |
| getProblems().add(new VoidTypeProblem(node)); |
| return new Binding(node, voidType, null); |
| } |
| |
| public Object reduce_void0Literal_to_constant_value(IASNode iNode) |
| { |
| return UNDEFINED_VALUE; |
| } |
| |
| public InstructionList reduce_void0Literal_to_object_literal(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushundefined); |
| return result; |
| } |
| |
| public InstructionList reduce_instructionListExpression(IASNode iNode) |
| { |
| return ((InstructionListNode)iNode).getInstructions(); |
| } |
| |
| public InstructionList reduce_void0Operator(IASNode iNode) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushundefined); |
| return result; |
| } |
| |
| public Object reduce_voidOperator_to_constant_value(IASNode iNode, Object constant_value) |
| { |
| return UNDEFINED_VALUE; |
| } |
| |
| public InstructionList reduce_voidOperator_to_expression(IASNode iNode, InstructionList expr) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addAll(expr); |
| result.addInstruction(OP_pop); |
| result.addInstruction(OP_pushundefined); |
| return result; |
| } |
| |
| public InstructionList reduce_whileStmt(IASNode iNode, InstructionList cond, InstructionList body) |
| { |
| InstructionList result = createInstructionList(iNode, cond.size() + body.size() + 5); |
| currentScope.getFlowManager().resolveContinueLabel(cond); |
| |
| // Jump to the test. |
| result.addInstruction(OP_jump, cond.getLabel()); |
| |
| // Create an emitter-time label, and attach |
| // it to an OP_label instruction for the |
| // backwards branch. |
| result.addInstruction(OP_label); |
| Label loop = result.getLastLabel(); |
| result.addAll(body); |
| |
| result.addAll(cond); |
| result.addInstruction(OP_iftrue, loop); |
| |
| currentScope.getFlowManager().finishLoopControlFlowContext(result); |
| |
| return result; |
| } |
| |
| public InstructionList reduce_withStmt(IASNode iNode, InstructionList new_scope, InstructionList body) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(new_scope); |
| |
| if ( currentScope.getFlowManager().hasWithStorage()) |
| { |
| result.addInstruction(OP_dup); |
| result.addInstruction(currentScope.getFlowManager().getWithStorage().setlocal()); |
| } |
| |
| result.addInstruction(OP_pushwith); |
| result.addAll(body); |
| result.addInstruction(OP_popscope); |
| |
| currentScope.getFlowManager().finishWithContext(result); |
| return result; |
| } |
| |
| /** |
| * The enumeration of states that the code generator finds interesting in an XML literal. |
| * There are states here than the reducer, below, doesn't consider especially interesting; |
| * see computeXMLContentStateMatrix() for their usage. |
| */ |
| private enum XMLContentState { TagStart, TagLiteral, TagName, Attr, ValueNeedsEquals, Value, TagEnd, ContentLiteral, ContentExpression }; |
| |
| @SuppressWarnings("incomplete-switch") |
| public InstructionList reduce_XMLContent(IASNode iNode, Vector<InstructionList> exprs) |
| { |
| InstructionList result = createInstructionList(iNode); |
| |
| result.addAll(currentScope.getPropertyValue(xmlType, currentScope.getProject().getBuiltinType(BuiltinType.XML))); |
| |
| // Add all the arguments up into a single String. |
| |
| XMLContentState[] content_state = computeXMLContentStateMatrix(exprs); |
| |
| result.addAll(exprs.elementAt(0)); |
| |
| for ( int i = 1; i < exprs.size(); i++ ) |
| { |
| result.addAll(exprs.elementAt(i)); |
| |
| switch ( content_state[i] ) |
| { |
| case TagName: |
| { |
| // Parser elides whitespace after tag name expressions. |
| result.addInstruction(OP_pushstring, " "); |
| result.addInstruction(OP_add); |
| } |
| break; |
| case Attr: |
| { |
| // Parser elides whitespace before attribute name expressions. |
| if ( content_state[i-1] != XMLContentState.TagName ) |
| { |
| result.addInstruction(OP_pushstring, " "); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_add); |
| } |
| } |
| break; |
| case ValueNeedsEquals: |
| { |
| if ( content_state[i-1] == XMLContentState.Attr ) |
| { |
| // Parser elided this token. |
| result.addInstruction(OP_pushstring, "="); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_add); |
| } |
| } |
| // falls through |
| case Value: |
| { |
| result.addInstruction(OP_esc_xattr); |
| |
| // The attribute will need quote marks for the AVM's XML parser. |
| result.addInstruction(OP_pushstring, "\""); |
| result.addInstruction(OP_swap); |
| result.addInstruction(OP_add); |
| result.addInstruction(OP_pushstring, "\""); |
| result.addInstruction(OP_add); |
| } |
| break; |
| case ContentExpression: |
| result.addInstruction(OP_esc_xelem); |
| } |
| |
| result.addInstruction(OP_add); |
| } |
| |
| result.addInstruction(OP_construct, 1); |
| return result; |
| } |
| |
| public InstructionList reduce_XMLList(IASNode iNode, Vector<InstructionList> exprs) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(currentScope.getPropertyValue(xmlListType, currentScope.getProject().getBuiltinType(BuiltinType.XMLLIST))); |
| // The first and last elements are the <> and </> brackets. |
| // An empty XMLList <></> should be reduced by the constant |
| // case, below, so we know we have at least one element. |
| result.addAll(exprs.get(1)); |
| for ( int i = 2; i < exprs.size() - 1; i++ ) |
| { |
| result.addAll(exprs.get(i)); |
| result.addInstruction(OP_add); |
| } |
| result.addInstruction(OP_construct, 1); |
| return result; |
| } |
| |
| public InstructionList reduce_XMLListConst(IASNode iNode, Vector<String> elements) |
| { |
| InstructionList result = createInstructionList(iNode); |
| result.addAll(currentScope.getPropertyValue(xmlListType, currentScope.getProject().getBuiltinType(BuiltinType.XMLLIST))); |
| StringBuilder buffer = new StringBuilder(); |
| // The first and last elements are the <> and </> brackets. |
| for ( int i = 1; i < elements.size() -1; i++ ) |
| buffer.append(elements.elementAt(i)); |
| result.addInstruction(OP_pushstring, buffer.toString()); |
| result.addInstruction(OP_construct, 1); |
| return result; |
| } |
| |
| /** |
| * Clone an InstructionList. |
| * @return the clone, appropriately cast. |
| * @note clone() always returns Object. |
| */ |
| InstructionList replicate(InstructionList prototype) |
| { |
| return (InstructionList)prototype.clone(); |
| } |
| |
| /** |
| * Set this reducer's list of a priori instructions; |
| * typically these are field initializers passed in to |
| * a constructor routine. |
| */ |
| public void setInstanceInitializers(InstructionList insns) |
| { |
| this.instanceInitializers = insns; |
| } |
| |
| /** |
| * Set this reducer's initial LexicalScope. |
| */ |
| public void setCurrentscope(LexicalScope scope) |
| { |
| this.currentScope = scope; |
| } |
| |
| /* |
| * ****************************** |
| * ** Transformation routines ** |
| * ****************************** |
| */ |
| |
| public InstructionList transform_boolean_constant(IASNode iNode, Boolean boolean_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| |
| if ( Boolean.TRUE.equals(boolean_constant) ) |
| result.addInstruction(OP_pushtrue); |
| else |
| result.addInstruction(OP_pushfalse); |
| |
| return result; |
| } |
| |
| public InstructionList transform_double_constant(IASNode iNode, Double double_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| pushNumericConstant(result, double_constant, SemanticUtils.resolveType(iNode, currentScope.getProject())); |
| return result; |
| } |
| |
| public InstructionList transform_numeric_constant(IASNode iNode, Number numeric_constant) |
| { |
| if( numeric_constant instanceof Integer) |
| return transform_integer_constant(iNode, (Integer)numeric_constant); |
| if( numeric_constant instanceof Long ) |
| return transform_long_constant(iNode, (Long)numeric_constant); |
| if( numeric_constant instanceof Double) |
| return transform_double_constant(iNode, (Double)numeric_constant); |
| return createInstructionList(iNode, 1); |
| } |
| |
| /** |
| * Transform any PooledValue into an expression, so we can constant fold all sorts of expressions. |
| * @param iNode the node that generated the constant_value |
| * @param pooled_value the pooled value |
| * @return an InstructionList that contains instructions to push the pooled_value onto the stack. |
| */ |
| public InstructionList transform_pooled_value(IASNode iNode, PooledValue pooled_value ) |
| { |
| return transform_constant_value(iNode, pooled_value.getValue()); |
| } |
| |
| /** |
| * Transform any constant_value into an expression, so we can constant fold all sorts of expressions. |
| * @param iNode the node that generated the constant_value |
| * @param constant_value the constant value |
| * @return an InstructionList that contains instructions to push the constant_value onto the stack. |
| */ |
| public InstructionList transform_constant_value(IASNode iNode, Object constant_value ) |
| { |
| assert (!(constant_value instanceof PooledValue)) : "transform_constant_value should not be called with a PooledValue"; |
| |
| if( constant_value instanceof Boolean ) |
| { |
| return transform_boolean_constant(iNode, (Boolean)constant_value); |
| } |
| if( constant_value instanceof Number ) |
| { |
| return transform_numeric_constant(iNode, (Number)constant_value); |
| } |
| if( constant_value instanceof String ) |
| { |
| return transform_string_constant(iNode, (String)constant_value); |
| } |
| |
| InstructionList result = createInstructionList(iNode, 1); |
| if( constant_value instanceof Namespace) |
| { |
| result.addInstruction(OP_pushnamespace, constant_value); |
| } |
| else if( constant_value == ABCConstants.NULL_VALUE ) |
| { |
| result.addInstruction(OP_pushnull); |
| } |
| else if( constant_value == ABCConstants.UNDEFINED_VALUE ) |
| { |
| result.addInstruction(OP_pushundefined); |
| } |
| else |
| { |
| assert false : "unknown constant type"; |
| } |
| |
| return result; |
| } |
| /** |
| * transform a string_constant to a constant_value - essentially a no-op, but need a reduction |
| * so we can assign it a cost |
| */ |
| public Object transform_string_constant_to_constant(IASNode iNode, String string_constant) |
| { |
| return string_constant; |
| } |
| |
| /** |
| * transform a boolean_constant to a constant_value - essentially a no-op, but need a reduction |
| * so we can assign it a cost |
| */ |
| public Object transform_boolean_constant_to_constant(IASNode iNode, Boolean boolean_constant) |
| { |
| return boolean_constant; |
| } |
| |
| /** |
| * transform a numeric_constant to a constant_value - essentially a no-op, but need a reduction |
| * so we can assign it a cost |
| */ |
| public Object transform_numeric_constant_to_constant(IASNode iNode, Number numeric_constant) |
| { |
| return numeric_constant; |
| } |
| |
| public InstructionList transform_expression_to_conditionalJump(IASNode iNode, InstructionList expression) |
| { |
| InstructionList result = createInstructionList(iNode, expression.size() + 1); |
| result.addAll(expression); |
| result.addInstruction(InstructionFactory.getTargetableInstruction(OP_iftrue)); |
| return result; |
| } |
| |
| public Object transform_expression_to_constant_value(IASNode iNode, InstructionList expression) |
| { |
| // return null - something higher up will report any appropriate diagnostics. |
| return null; |
| } |
| |
| public InstructionList transform_expression_to_void_expression(IASNode iNode, InstructionList expression) |
| { |
| InstructionList result = createInstructionList(iNode, expression.size() + 1); |
| result.addAll(expression); |
| result.addInstruction(OP_pop); |
| return result; |
| } |
| |
| public InstructionList transform_integer_constant(IASNode iNode, Integer integer_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| |
| pushNumericConstant(result, integer_constant, SemanticUtils.resolveType(iNode, currentScope.getProject())); |
| return result; |
| } |
| |
| public InstructionList transform_long_constant(IASNode iNode, Long long_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| |
| pushNumericConstant(result, long_constant, SemanticUtils.resolveType(iNode, currentScope.getProject())); |
| return result; |
| } |
| |
| public Object transform_name_to_constant_value(IASNode iNode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkConstantValue(iNode); |
| |
| return SemanticUtils.transformNameToConstantValue(iNode, currentScope.getProject()); |
| } |
| |
| public InstructionList transform_name_to_expression(IASNode iNode, Binding name) |
| { |
| |
| return generateAccess(name, determineAccessType(iNode)); |
| } |
| |
| public InstructionList transform_non_resolving_identifier(IASNode iNode, String non_resolving_identifier) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushstring, non_resolving_identifier); |
| return result; |
| } |
| |
| public InstructionList transform_runtime_name_expression(IASNode iNode, RuntimeMultiname runtime_name_expression) |
| { |
| return runtime_name_expression.generateRvalue(iNode); |
| } |
| |
| public InstructionList transform_string_constant(IASNode iNode, String string_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| result.addInstruction(OP_pushstring, string_constant); |
| return result; |
| } |
| |
| public InstructionList transform_uint_constant(IASNode iNode, Long uint_constant) |
| { |
| InstructionList result = createInstructionList(iNode, 1); |
| pushNumericConstant(result, uint_constant, SemanticUtils.resolveType(iNode, currentScope.getProject())); |
| return result; |
| } |
| |
| /** |
| * Generate a unary operator. |
| * @param operand - the operand expression. |
| * @param opcode - the operator's opcode. |
| * @return an InstructionList that applies the operator to the operand. |
| */ |
| InstructionList unaryOp(IASNode iNode, InstructionList operand, int opcode) |
| { |
| checkUnaryOp(iNode, opcode); |
| InstructionList result = createInstructionList(iNode, operand.size() + 1); |
| result.addAll(operand); |
| result.addInstruction(opcode); |
| return result; |
| } |
| |
| public void checkUnaryOp (IASNode iNode, int opcode) |
| { |
| currentScope.getMethodBodySemanticChecker().checkUnaryOperator(iNode, opcode); |
| } |
| |
| /** |
| * Walk a syntax tree and note any construct that |
| * will cause the ABC function to need an activation record. |
| */ |
| void scanFunctionBodyForActivations(IASNode n) |
| { |
| if ( n != null ) |
| { |
| switch ( n.getNodeID() ) |
| { |
| case WithID: |
| case AnonymousFunctionID: |
| case FunctionID: |
| { |
| currentScope.setNeedsActivation(); |
| break; |
| } |
| default: |
| { |
| // Note: There is no need to create an activation |
| // for catch or finally. See ActivationSanityTest |
| // for examples which prove this. |
| |
| for ( int i = 0; i < n.getChildCount(); i++ ) |
| scanFunctionBodyForActivations(n.getChild(i)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Is this expression a string literal? |
| * @return true if the expression is a string literal, i.e., |
| * it has one pushstring instruction. |
| */ |
| public static boolean isStringLiteral(InstructionList insns) |
| { |
| return insns.lastElement().getOpcode() == OP_pushstring; |
| } |
| |
| /** |
| * Extract the string literal from an InstructionList. |
| * @param insns - the InstructionList of interest. |
| * @return the string literal from insns' final |
| * (and presumably only executable) instruction. |
| */ |
| public static String getStringLiteral(InstructionList insns) |
| { |
| assert isStringLiteral(insns); |
| return insns.lastElement().getOperand(0).toString(); |
| } |
| |
| /** |
| * Work around the linear presentation of XML initializer elements from the parser |
| * by doing ad-hoc pattern matching. |
| * @param exprs - a Vector of expressions. |
| * @return A state transition matrix to generate code for the elements. |
| * @see "ECMA-357 11.1.4 : XML Initialiser" |
| * Expressions may be used to compute parts of an XML initialiser. |
| * Expressions are delimited by curly braces and may appear inside tags or element content. |
| * Inside a tag, expressions may be used to compute a tag name, attribute name, or attribute value. |
| * Inside an element, expressions may be used to compute element content. |
| */ |
| private XMLContentState[] computeXMLContentStateMatrix( Vector<InstructionList> exprs) |
| { |
| XMLContentState[] result = new XMLContentState[exprs.size()]; |
| |
| // First element must be a string or the parser wouldn't have put us here. |
| // But it may be a simple < or </ literal or a <foo literal; the former |
| // imply that the next expression is going to be a TagName, the latter just |
| // is a TagLiteral that puts us into the middle of things. |
| |
| assert isStringLiteral(exprs.elementAt(0)) : exprs.elementAt(0); |
| String first_element = getStringLiteral(exprs.elementAt(0)); |
| |
| if ( first_element.equals("<") || first_element.equals("</") ) |
| result[0] = XMLContentState.TagStart; |
| else |
| result[0] = XMLContentState.TagLiteral; |
| |
| for ( int i = 1; i < exprs.size(); i++ ) |
| { |
| InstructionList insns = exprs.elementAt(i); |
| if ( isStringLiteral(insns) ) |
| { |
| switch ( result[i-1] ) |
| { |
| case TagStart: |
| case TagLiteral: |
| case TagName: |
| case Attr: |
| case Value: |
| case ValueNeedsEquals: |
| { |
| if ( getStringLiteral(insns).matches(".*>") ) |
| result[i] = XMLContentState.TagEnd; |
| else |
| result[i] = XMLContentState.TagLiteral; |
| break; |
| } |
| case TagEnd: |
| case ContentLiteral: |
| case ContentExpression: |
| if ( getStringLiteral(insns).matches("<.*") ) |
| result[i] = XMLContentState.TagStart; |
| else |
| result[i] = XMLContentState.ContentLiteral; |
| } |
| } |
| else |
| { |
| // Now the fun starts. |
| // The most important thing to do here is to get the |
| // Value, and ContentExpression states right; |
| // these are the entities that require special instructions. |
| switch ( result[i-1] ) |
| { |
| case TagStart: |
| result[i] = XMLContentState.TagName; |
| break; |
| // An expression following a tag name or |
| // an attribute value should be an attribute name. |
| case TagName: |
| case Value: |
| result[i] = XMLContentState.Attr; |
| break; |
| // An expression following an attribute name |
| // should be an attribute value. |
| case Attr: |
| result[i] = XMLContentState.ValueNeedsEquals; |
| break; |
| case TagEnd: |
| case ContentLiteral: |
| case ContentExpression: |
| result[i] = XMLContentState.ContentExpression; |
| break; |
| case TagLiteral: |
| { |
| // The literal is free-form input and needs |
| // to be analyzed. |
| switch ( getXMLLiteralContentState(getStringLiteral(exprs.elementAt(i-1)) ) ) |
| { |
| case TagStart: |
| result[i] = XMLContentState.TagName; |
| break; |
| case Attr: |
| // Since Attr here implies we saw |
| // a '=' character, the XML string |
| // won't need one appended. |
| result[i] = XMLContentState.Value; |
| break; |
| case Value: |
| result[i] = XMLContentState.Attr; |
| break; |
| default: |
| result[i] = XMLContentState.ContentExpression; |
| } |
| break; |
| } |
| default: |
| assert false: "Unhandled case " + result[i-1]; |
| } |
| |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Analyze an XML literal and see what state its last character implies. |
| * @return the XMLContentState corresponding to the significance |
| * of the last literal; e.g., "foo=" returns Attr, "foo" returns Value. |
| */ |
| private XMLContentState getXMLLiteralContentState(String literal) |
| { |
| char lastChar = literal.charAt(literal.length()-1); |
| |
| if ( lastChar == '<' ) |
| { |
| return XMLContentState.TagStart; |
| } |
| else if ( lastChar == '=' ) |
| { |
| return XMLContentState.Attr; |
| } |
| |
| return XMLContentState.ContentExpression; |
| } |
| |
| /** |
| * @return return the opcode to use to convert the top stack value to a numeric |
| * or not |
| */ |
| int op_unplus () |
| { |
| return OP_convert_d; |
| } |
| |
| /** |
| * Reduce a array index expression ( a[x] ) in an MXML data binding |
| * destination to: a[x] = firstParam |
| */ |
| public InstructionList reduce_arrayIndexExpr_to_mxmlDataBindingSetter(IASNode iNode, InstructionList stem, InstructionList index, boolean isSuper) |
| { |
| IDynamicAccessNode arrayIndexNode = (IDynamicAccessNode)iNode; |
| |
| int stemInstructionCount = isSuper ? 1 : stem.size(); |
| InstructionList result = |
| createInstructionList(arrayIndexNode, stemInstructionCount + index.size() + 3 + 1); // +1 for the returnvoid |
| |
| if (isSuper) |
| result.addInstruction(OP_getlocal0); |
| else |
| result.addAll(stem); |
| result.addAll(index); |
| result.addInstruction(OP_getlocal1); |
| int opCode = isSuper ? OP_setsuper : OP_setproperty; |
| result.addInstruction(arrayAccess(arrayIndexNode, opCode)); |
| return result; |
| } |
| |
| /** |
| * Reduce a member access expression ( a.b ) in an MXML data binding |
| * destination to: a.b = firstParam |
| */ |
| public InstructionList reduce_memberAccessExpr_to_mxmlDataBindingSetter(IASNode memberAccessExpr, InstructionList stem, Binding member) |
| { |
| currentScope.getMethodBodySemanticChecker().checkLValue(memberAccessExpr, member); |
| InstructionList result = |
| createInstructionList(memberAccessExpr, stem.size() + 2 + 1); // +1 for the returnvoid |
| result.addAll(stem); |
| result.addInstruction(OP_getlocal1); |
| result.addInstruction(OP_setproperty, member.getName()); |
| return result; |
| } |
| |
| /** |
| * Reduce a qualified member access expression ( a.ns::b ) in an MXML data binding |
| * destination to: a.ns::b = firstParam |
| */ |
| public InstructionList reduce_qualifiedMemberAccessExpr_to_mxmlDataBindingSetter(IASNode qualifiedMemberAccessExpr, InstructionList stem, Binding qualifier, Binding member) |
| { |
| InstructionList result = |
| createInstructionList(qualifiedMemberAccessExpr); |
| |
| // We may need to work around CMP-751 by recreating |
| // the correct (trivial) qualifier for base.*::foo. |
| Name member_name = member.getName(); |
| |
| result.addAll(stem); |
| |
| if ( !isNamespace(qualifier) ) |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| } |
| // else the qualifier's namespace is already present in the name. |
| result.addInstruction(OP_getlocal1); |
| result.addInstruction(OP_setproperty, member_name); |
| |
| return result; |
| } |
| |
| /** |
| * Reduce a qualified member access expression ( a.ns::["b"] ) in an MXML data binding |
| * destination to: a.ns::["b"] = firstParam |
| */ |
| public InstructionList reduce_qualifiedMemberRuntimeNameExpr_to_mxmlDataBindingSetter(IASNode qualifiedMemberRuntimeExpr, InstructionList stem, Binding qualifier, InstructionList runtime_member) |
| { |
| InstructionList result = createInstructionList(qualifiedMemberRuntimeExpr); |
| |
| result.addAll(stem); |
| if ( isNamespace(qualifier) ) |
| { |
| result.addAll(runtime_member); |
| // Extract the URI from the namespace and use it to construct a qualified name. |
| NamespaceDefinition ns_def = (NamespaceDefinition)qualifier.getDefinition(); |
| Name qualified_name = new Name(CONSTANT_MultinameL, new Nsset(ns_def.resolveAETNamespace(currentScope.getProject())), null); |
| result.addInstruction(OP_getlocal1); |
| result.addInstruction(OP_setproperty, qualified_name); |
| } |
| else |
| { |
| generateAccess(qualifier, result); |
| // Verifier insists on this. |
| result.addInstruction(OP_coerce, namespaceType); |
| result.addAll(runtime_member); |
| result.addInstruction(OP_getlocal1); |
| result.addInstruction(OP_setproperty, new Name(CONSTANT_RTQnameL, null, null)); |
| } |
| return result; |
| } |
| |
| /** |
| * Reduce a runtime name ( nsVar::x ) in an MXML data binding |
| * destination to: nsVar::x = firstParam |
| * <p> |
| * This reduction is used when the qualifer is not a compiler time constant namespace. |
| */ |
| public InstructionList reduceRuntimeName_to_mxmlDataBindingSetter(IASNode runtimeNameNode, RuntimeMultiname runtime_name_expression) |
| { |
| InstructionList rhs = new InstructionList(1); |
| rhs.addInstruction(OP_getlocal1); |
| return runtime_name_expression.generateGetOrSet(runtimeNameNode, OP_setproperty, rhs); |
| } |
| |
| /** |
| * Reduce a compile time constant name ( x ) in an MXML data binding |
| * destination to: x = firstParam. |
| */ |
| public InstructionList reduceName_to_mxmlDataBindingSetter(IASNode nameNode, Binding name) |
| { |
| currentScope.getMethodBodySemanticChecker().checkLValue(nameNode, name); |
| InstructionList result = new InstructionList(3 + 1); // +1 because of the returnvoid |
| result.addAll(currentScope.findProperty(name, true)); |
| result.addInstruction(ABCConstants.OP_getlocal1); |
| result.addInstruction(OP_setproperty, name.getName()); |
| return result; |
| } |
| |
| /** |
| * Synthesize an array access instruction |
| * with the correct runtime multiname. |
| * <p> |
| * This handles <code>whatever[...]</code>, |
| * including <code>this[...]</code> and <code>super[...]</code>. |
| * |
| * @param iNode - an IDynamicAccessNode from the construct |
| * that needs an array access instruction. |
| * @param opcode - the opcode of the instruction desired. |
| * @return an instruction with the specified opcode |
| * and the set of open namespaces at the given node. |
| */ |
| private Instruction arrayAccess(IASNode iNode, int opcode) |
| { |
| ICompilerProject project = currentScope.getProject(); |
| |
| // Determine if iNode represents super[...] (either as an l-value or an r-value). |
| // If so, the namespace set will be slightly different; it will have |
| // ProtectedNs(thisClass) instead of ProtectedNs(superClass>. |
| IDefinition superDef = null; |
| if (iNode instanceof IDynamicAccessNode) |
| { |
| IExpressionNode arrayNode = ((IDynamicAccessNode)iNode).getLeftOperandNode(); |
| if (arrayNode instanceof ILanguageIdentifierNode && |
| ((ILanguageIdentifierNode)arrayNode).getKind() == LanguageIdentifierKind.SUPER) |
| { |
| IIdentifierNode superNode = (IIdentifierNode)arrayNode; |
| superDef = superNode.resolveType(project); |
| } |
| } |
| |
| Nsset nsSet = superDef != null ? |
| SemanticUtils.getOpenNamespacesForSuper(iNode, project, superDef) : |
| SemanticUtils.getOpenNamespaces(iNode, project); |
| |
| Name name = new Name(ABCConstants.CONSTANT_MultinameL, nsSet, null); |
| |
| return InstructionFactory.getInstruction(opcode, name); |
| } |
| |
| /** |
| * Determine the proper access type for a name. |
| * @param iNode - the name's AST. |
| * @return AccessType.Lenient if the node is a unqualified |
| * name or the member reference leaf of a member access |
| * node, AccessType.Strict in all other cases. |
| */ |
| private AccessType determineAccessType(IASNode iNode) |
| { |
| if ( iNode.getParent() instanceof MemberAccessExpressionNode) |
| { |
| MemberAccessExpressionNode maen = (MemberAccessExpressionNode)iNode.getParent(); |
| if ( !maen.isMemberReference(iNode) ) |
| { |
| return AccessType.Strict; |
| } |
| } |
| |
| return (this.typeofCount > 0)? |
| AccessType.Lenient: |
| AccessType.Strict; |
| } |
| } |