Return-Path: X-Original-To: apmail-flex-commits-archive@www.apache.org Delivered-To: apmail-flex-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id E3E4B113C2 for ; Tue, 22 Jul 2014 13:35:32 +0000 (UTC) Received: (qmail 46027 invoked by uid 500); 22 Jul 2014 13:35:28 -0000 Delivered-To: apmail-flex-commits-archive@flex.apache.org Received: (qmail 45980 invoked by uid 500); 22 Jul 2014 13:35:28 -0000 Mailing-List: contact commits-help@flex.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@flex.apache.org Delivered-To: mailing list commits@flex.apache.org Received: (qmail 45465 invoked by uid 99); 22 Jul 2014 13:35:27 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Jul 2014 13:35:27 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id B8FE49AF20B; Tue, 22 Jul 2014 13:35:27 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: cdutz@apache.org To: commits@flex.apache.org Date: Tue, 22 Jul 2014 13:35:43 -0000 Message-Id: <19595259615544058dc79de933d0cc0e@git.apache.org> In-Reply-To: <9b4488ac636245f08eb096463c80c652@git.apache.org> References: <9b4488ac636245f08eb096463c80c652@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [17/51] [partial] - Migrated the directory structure to a more Maven style structure. - Started migrating the Parser from Antlr2+3 and JFlex to Antlr4. http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/59f6373b/compiler/src/main/java/org/apache/flex/compiler/internal/as/codegen/ABCGeneratingReducer.java ---------------------------------------------------------------------- diff --git a/compiler/src/main/java/org/apache/flex/compiler/internal/as/codegen/ABCGeneratingReducer.java b/compiler/src/main/java/org/apache/flex/compiler/internal/as/codegen/ABCGeneratingReducer.java new file mode 100644 index 0000000..776993f --- /dev/null +++ b/compiler/src/main/java/org/apache/flex/compiler/internal/as/codegen/ABCGeneratingReducer.java @@ -0,0 +1,7137 @@ +/* + * + * 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 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 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 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 miniScopes = new Stack(); + + /** + * 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 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. = new Vector.(); + // } + // 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 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 : 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 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