| /* |
| * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.ir; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.ir.annotations.Immutable; |
| import jdk.nashorn.internal.ir.visitor.NodeVisitor; |
| import jdk.nashorn.internal.parser.TokenType; |
| |
| /** |
| * IR representation for a runtime call. |
| */ |
| @Immutable |
| public class RuntimeNode extends Expression { |
| private static final long serialVersionUID = 1L; |
| |
| /** |
| * Request enum used for meta-information about the runtime request |
| */ |
| public enum Request { |
| /** An addition with at least one object */ |
| ADD(TokenType.ADD, Type.OBJECT, 2, true), |
| /** Request to enter debugger */ |
| DEBUGGER, |
| /** New operator */ |
| NEW, |
| /** Typeof operator */ |
| TYPEOF, |
| /** Reference error type */ |
| REFERENCE_ERROR, |
| /** Delete operator */ |
| DELETE(TokenType.DELETE, Type.BOOLEAN, 1), |
| /** Delete operator for slow scopes */ |
| SLOW_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false), |
| /** Delete operator that always fails -- see Lower */ |
| FAIL_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false), |
| /** === operator with at least one object */ |
| EQ_STRICT(TokenType.EQ_STRICT, Type.BOOLEAN, 2, true), |
| /** == operator with at least one object */ |
| EQ(TokenType.EQ, Type.BOOLEAN, 2, true), |
| /** {@literal >=} operator with at least one object */ |
| GE(TokenType.GE, Type.BOOLEAN, 2, true), |
| /** {@literal >} operator with at least one object */ |
| GT(TokenType.GT, Type.BOOLEAN, 2, true), |
| /** in operator */ |
| IN(TokenType.IN, Type.BOOLEAN, 2), |
| /** instanceof operator */ |
| INSTANCEOF(TokenType.INSTANCEOF, Type.BOOLEAN, 2), |
| /** {@literal <=} operator with at least one object */ |
| LE(TokenType.LE, Type.BOOLEAN, 2, true), |
| /** {@literal <} operator with at least one object */ |
| LT(TokenType.LT, Type.BOOLEAN, 2, true), |
| /** !== operator with at least one object */ |
| NE_STRICT(TokenType.NE_STRICT, Type.BOOLEAN, 2, true), |
| /** != operator with at least one object */ |
| NE(TokenType.NE, Type.BOOLEAN, 2, true), |
| /** is undefined */ |
| IS_UNDEFINED(TokenType.EQ_STRICT, Type.BOOLEAN, 2), |
| /** is not undefined */ |
| IS_NOT_UNDEFINED(TokenType.NE_STRICT, Type.BOOLEAN, 2), |
| /** Get template object from raw and cooked string arrays. */ |
| GET_TEMPLATE_OBJECT(TokenType.TEMPLATE, Type.SCRIPT_OBJECT, 2); |
| |
| /** token type */ |
| private final TokenType tokenType; |
| |
| /** return type for request */ |
| private final Type returnType; |
| |
| /** arity of request */ |
| private final int arity; |
| |
| /** Can the specializer turn this into something that works with 1 or more primitives? */ |
| private final boolean canSpecialize; |
| |
| private Request() { |
| this(TokenType.VOID, Type.OBJECT, 0); |
| } |
| |
| private Request(final TokenType tokenType, final Type returnType, final int arity) { |
| this(tokenType, returnType, arity, false); |
| } |
| |
| private Request(final TokenType tokenType, final Type returnType, final int arity, final boolean canSpecialize) { |
| this.tokenType = tokenType; |
| this.returnType = returnType; |
| this.arity = arity; |
| this.canSpecialize = canSpecialize; |
| } |
| |
| /** |
| * Can this request type be specialized? |
| * |
| * @return true if request can be specialized |
| */ |
| public boolean canSpecialize() { |
| return canSpecialize; |
| } |
| |
| /** |
| * Get arity |
| * |
| * @return the arity of the request |
| */ |
| public int getArity() { |
| return arity; |
| } |
| |
| /** |
| * Get the return type |
| * |
| * @return return type for request |
| */ |
| public Type getReturnType() { |
| return returnType; |
| } |
| |
| /** |
| * Get token type |
| * |
| * @return token type for request |
| */ |
| public TokenType getTokenType() { |
| return tokenType; |
| } |
| |
| /** |
| * Get the non-strict name for this request. |
| * |
| * @return the name without _STRICT suffix |
| */ |
| public String nonStrictName() { |
| switch(this) { |
| case NE_STRICT: |
| return NE.name(); |
| case EQ_STRICT: |
| return EQ.name(); |
| default: |
| return name(); |
| } |
| } |
| |
| /** |
| * Derive a runtime node request type for a node |
| * @param node the node |
| * @return request type |
| */ |
| public static Request requestFor(final Expression node) { |
| switch (node.tokenType()) { |
| case TYPEOF: |
| return Request.TYPEOF; |
| case IN: |
| return Request.IN; |
| case INSTANCEOF: |
| return Request.INSTANCEOF; |
| case EQ_STRICT: |
| return Request.EQ_STRICT; |
| case NE_STRICT: |
| return Request.NE_STRICT; |
| case EQ: |
| return Request.EQ; |
| case NE: |
| return Request.NE; |
| case LT: |
| return Request.LT; |
| case LE: |
| return Request.LE; |
| case GT: |
| return Request.GT; |
| case GE: |
| return Request.GE; |
| default: |
| assert false; |
| return null; |
| } |
| } |
| |
| /** |
| * Is this an undefined check? |
| * |
| * @param request request |
| * |
| * @return true if undefined check |
| */ |
| public static boolean isUndefinedCheck(final Request request) { |
| return request == IS_UNDEFINED || request == IS_NOT_UNDEFINED; |
| } |
| |
| /** |
| * Is this an EQ or EQ_STRICT? |
| * |
| * @param request a request |
| * |
| * @return true if EQ or EQ_STRICT |
| */ |
| public static boolean isEQ(final Request request) { |
| return request == EQ || request == EQ_STRICT; |
| } |
| |
| /** |
| * Is this an NE or NE_STRICT? |
| * |
| * @param request a request |
| * |
| * @return true if NE or NE_STRICT |
| */ |
| public static boolean isNE(final Request request) { |
| return request == NE || request == NE_STRICT; |
| } |
| |
| /** |
| * Is this strict? |
| * |
| * @param request a request |
| * |
| * @return true if script |
| */ |
| public static boolean isStrict(final Request request) { |
| return request == EQ_STRICT || request == NE_STRICT; |
| } |
| |
| /** |
| * If this request can be reversed, return the reverse request |
| * Eq EQ {@literal ->} NE. |
| * |
| * @param request request to reverse |
| * |
| * @return reversed request or null if not applicable |
| */ |
| public static Request reverse(final Request request) { |
| switch (request) { |
| case EQ: |
| case EQ_STRICT: |
| case NE: |
| case NE_STRICT: |
| return request; |
| case LE: |
| return GE; |
| case LT: |
| return GT; |
| case GE: |
| return LE; |
| case GT: |
| return LT; |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Invert the request, only for non equals comparisons. |
| * |
| * @param request a request |
| * |
| * @return the inverted request, or null if not applicable |
| */ |
| public static Request invert(final Request request) { |
| switch (request) { |
| case EQ: |
| return NE; |
| case EQ_STRICT: |
| return NE_STRICT; |
| case NE: |
| return EQ; |
| case NE_STRICT: |
| return EQ_STRICT; |
| case LE: |
| return GT; |
| case LT: |
| return GE; |
| case GE: |
| return LT; |
| case GT: |
| return LE; |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Check if this is a comparison |
| * |
| * @param request a request |
| * |
| * @return true if this is a comparison, null otherwise |
| */ |
| public static boolean isComparison(final Request request) { |
| switch (request) { |
| case EQ: |
| case EQ_STRICT: |
| case NE: |
| case NE_STRICT: |
| case LE: |
| case LT: |
| case GE: |
| case GT: |
| case IS_UNDEFINED: |
| case IS_NOT_UNDEFINED: |
| return true; |
| default: |
| return false; |
| } |
| } |
| } |
| |
| /** Runtime request. */ |
| private final Request request; |
| |
| /** Call arguments. */ |
| private final List<Expression> args; |
| |
| /** |
| * Constructor |
| * |
| * @param token token |
| * @param finish finish |
| * @param request the request |
| * @param args arguments to request |
| */ |
| public RuntimeNode(final long token, final int finish, final Request request, final List<Expression> args) { |
| super(token, finish); |
| |
| this.request = request; |
| this.args = args; |
| } |
| |
| private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final List<Expression> args) { |
| super(runtimeNode); |
| |
| this.request = request; |
| this.args = args; |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param token token |
| * @param finish finish |
| * @param request the request |
| * @param args arguments to request |
| */ |
| public RuntimeNode(final long token, final int finish, final Request request, final Expression... args) { |
| this(token, finish, request, Arrays.asList(args)); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param parent parent node from which to inherit source, token, finish |
| * @param request the request |
| * @param args arguments to request |
| */ |
| public RuntimeNode(final Expression parent, final Request request, final Expression... args) { |
| this(parent, request, Arrays.asList(args)); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param parent parent node from which to inherit source, token, finish |
| * @param request the request |
| * @param args arguments to request |
| */ |
| public RuntimeNode(final Expression parent, final Request request, final List<Expression> args) { |
| super(parent); |
| |
| this.request = request; |
| this.args = args; |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param parent parent node from which to inherit source, token, finish and arguments |
| * @param request the request |
| */ |
| public RuntimeNode(final UnaryNode parent, final Request request) { |
| this(parent, request, parent.getExpression()); |
| } |
| |
| /** |
| * Constructor used to replace a binary node with a runtime request. |
| * |
| * @param parent parent node from which to inherit source, token, finish and arguments |
| */ |
| public RuntimeNode(final BinaryNode parent) { |
| this(parent, Request.requestFor(parent), parent.lhs(), parent.rhs()); |
| } |
| |
| /** |
| * Reset the request for this runtime node |
| * @param request request |
| * @return new runtime node or same if same request |
| */ |
| public RuntimeNode setRequest(final Request request) { |
| if (this.request == request) { |
| return this; |
| } |
| return new RuntimeNode(this, request, args); |
| } |
| |
| /** |
| * Return type for the ReferenceNode |
| */ |
| @Override |
| public Type getType() { |
| return request.getReturnType(); |
| } |
| |
| @Override |
| public Node accept(final NodeVisitor<? extends LexicalContext> visitor) { |
| if (visitor.enterRuntimeNode(this)) { |
| return visitor.leaveRuntimeNode(setArgs(Node.accept(visitor, args))); |
| } |
| |
| return this; |
| } |
| |
| @Override |
| public void toString(final StringBuilder sb, final boolean printType) { |
| sb.append("ScriptRuntime."); |
| sb.append(request); |
| sb.append('('); |
| |
| boolean first = true; |
| |
| for (final Node arg : args) { |
| if (!first) { |
| sb.append(", "); |
| } else { |
| first = false; |
| } |
| |
| arg.toString(sb, printType); |
| } |
| |
| sb.append(')'); |
| } |
| |
| /** |
| * Get the arguments for this runtime node |
| * @return argument list |
| */ |
| public List<Expression> getArgs() { |
| return Collections.unmodifiableList(args); |
| } |
| |
| /** |
| * Set the arguments of this runtime node |
| * @param args new arguments |
| * @return new runtime node, or identical if no change |
| */ |
| public RuntimeNode setArgs(final List<Expression> args) { |
| if (this.args == args) { |
| return this; |
| } |
| return new RuntimeNode(this, request, args); |
| } |
| |
| /** |
| * Get the request that this runtime node implements |
| * @return the request |
| */ |
| public Request getRequest() { |
| return request; |
| } |
| |
| /** |
| * Is this runtime node, engineered to handle the "at least one object" case of the defined |
| * requests and specialize on demand, really primitive. This can happen e.g. after AccessSpecializer |
| * In that case it can be turned into a simpler primitive form in CodeGenerator |
| * |
| * @return true if all arguments now are primitive |
| */ |
| public boolean isPrimitive() { |
| for (final Expression arg : args) { |
| if (arg.getType().isObject()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |