/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.compiler.internal.as.codegen; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Vector; import java.util.Stack; import static org.apache.flex.abc.ABCConstants.*; import org.apache.flex.abc.ABCConstants; import org.apache.flex.abc.instructionlist.InstructionList; import org.apache.flex.abc.semantics.ECMASupport; import org.apache.flex.abc.semantics.Instruction; import org.apache.flex.abc.semantics.InstructionFactory; import org.apache.flex.abc.semantics.Label; import org.apache.flex.abc.semantics.MethodInfo; import org.apache.flex.abc.semantics.Name; import org.apache.flex.abc.semantics.Namespace; import org.apache.flex.abc.semantics.NoOperandsInstruction; import org.apache.flex.abc.semantics.Nsset; import org.apache.flex.abc.semantics.PooledValue; import org.apache.flex.compiler.constants.IASLanguageConstants; import org.apache.flex.compiler.constants.IASLanguageConstants.BuiltinType; import org.apache.flex.compiler.definitions.IAccessorDefinition; import org.apache.flex.compiler.definitions.IConstantDefinition; import org.apache.flex.compiler.definitions.IDefinition; import org.apache.flex.compiler.definitions.ITypeDefinition; import org.apache.flex.compiler.definitions.IVariableDefinition; import org.apache.flex.compiler.exceptions.CodegenInterruptedException; import org.apache.flex.compiler.exceptions.DuplicateLabelException; import org.apache.flex.compiler.exceptions.UnknownControlFlowTargetException; import org.apache.flex.compiler.internal.semantics.SemanticUtils; import org.apache.flex.compiler.internal.tree.as.BaseVariableNode; import org.apache.flex.compiler.internal.tree.as.VariableExpressionNode; import org.apache.flex.compiler.problems.AmbiguousGotoTargetProblem; import org.apache.flex.compiler.problems.AnyNamespaceCannotBeQualifierProblem; import org.apache.flex.compiler.problems.CodegenInternalProblem; import org.apache.flex.compiler.problems.DuplicateLabelProblem; import org.apache.flex.compiler.problems.DuplicateNamespaceDefinitionProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.InvalidLvalueProblem; import org.apache.flex.compiler.problems.InvalidNamespaceInitializerProblem; import org.apache.flex.compiler.problems.MultipleSwitchDefaultsProblem; import org.apache.flex.compiler.problems.NonConstantParamInitializerProblem; import org.apache.flex.compiler.problems.PackageCannotBeUsedAsValueProblem; import org.apache.flex.compiler.problems.UnexpectedReturnProblem; import org.apache.flex.compiler.problems.UnknownGotoTargetProblem; import org.apache.flex.compiler.problems.UnknownNamespaceProblem; import org.apache.flex.compiler.problems.UnknownBreakTargetProblem; import org.apache.flex.compiler.problems.UnknownContinueTargetProblem; import org.apache.flex.compiler.problems.VoidTypeProblem; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.tree.as.IASNode; import org.apache.flex.compiler.tree.as.IDynamicAccessNode; import org.apache.flex.compiler.tree.as.IBinaryOperatorNode; import org.apache.flex.compiler.tree.as.ICatchNode; import org.apache.flex.compiler.tree.as.IContainerNode; import org.apache.flex.compiler.tree.as.IExpressionNode; import org.apache.flex.compiler.tree.as.IForLoopNode; import org.apache.flex.compiler.tree.as.IFunctionCallNode; import org.apache.flex.compiler.tree.as.IFunctionNode; import org.apache.flex.compiler.tree.as.IIdentifierNode; import org.apache.flex.compiler.tree.as.IImportNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode; import org.apache.flex.compiler.tree.as.IParameterNode; import org.apache.flex.compiler.tree.as.IScopedNode; import org.apache.flex.compiler.tree.as.ITryNode; import org.apache.flex.compiler.tree.as.ITypedExpressionNode; import org.apache.flex.compiler.tree.as.IUnaryOperatorNode; import org.apache.flex.compiler.tree.as.IWhileLoopNode; import org.apache.flex.compiler.tree.as.IWithNode; import org.apache.flex.compiler.tree.as.ILanguageIdentifierNode.LanguageIdentifierKind; import org.apache.flex.compiler.tree.as.IOperatorNode.OperatorType; import org.apache.flex.compiler.tree.mxml.IMXMLEventSpecifierNode; import org.apache.flex.compiler.internal.as.codegen.LexicalScope.NestingState; import org.apache.flex.compiler.internal.definitions.AccessorDefinition; import org.apache.flex.compiler.internal.definitions.ClassDefinition; import org.apache.flex.compiler.internal.definitions.DefinitionBase; import org.apache.flex.compiler.internal.definitions.FunctionDefinition; import org.apache.flex.compiler.internal.definitions.GetterDefinition; import org.apache.flex.compiler.internal.definitions.NamespaceDefinition; import org.apache.flex.compiler.internal.definitions.SetterDefinition; import org.apache.flex.compiler.internal.definitions.TypeDefinitionBase; import org.apache.flex.compiler.internal.tree.as.BaseDefinitionNode; import org.apache.flex.compiler.internal.tree.as.BinaryOperatorInNode; import org.apache.flex.compiler.internal.tree.as.DynamicAccessNode; import org.apache.flex.compiler.internal.tree.as.EmbedNode; import org.apache.flex.compiler.internal.tree.as.ExpressionNodeBase; import org.apache.flex.compiler.internal.tree.as.ForLoopNode; import org.apache.flex.compiler.internal.tree.as.FunctionNode; import org.apache.flex.compiler.internal.tree.as.FunctionObjectNode; import org.apache.flex.compiler.internal.tree.as.IdentifierNode; import org.apache.flex.compiler.internal.tree.as.LabeledStatementNode; import org.apache.flex.compiler.internal.tree.as.MemberAccessExpressionNode; import org.apache.flex.compiler.internal.tree.as.NamespaceNode; import org.apache.flex.compiler.internal.tree.as.RegExpLiteralNode; import org.apache.flex.compiler.internal.tree.as.SwitchNode; import org.apache.flex.compiler.internal.tree.as.TernaryOperatorNode; import org.apache.flex.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 && 1/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 Falcon 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 = 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); 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); 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.flex.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 }; 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; } }