Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.oracle.truffle.js.nodes.function.JSFunctionCallNode Maven / Gradle / Ivy
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.js.nodes.function;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ValueProfile;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.js.nodes.JSGuards;
import com.oracle.truffle.js.nodes.JSNodeUtil;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode.JSConstantUndefinedNode;
import com.oracle.truffle.js.nodes.access.JSProxyCallNode;
import com.oracle.truffle.js.nodes.access.JSTargetableNode;
import com.oracle.truffle.js.nodes.access.OptionalChainNode.ShortCircuitException;
import com.oracle.truffle.js.nodes.access.OptionalChainNode.ShortCircuitTargetableNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertyNode;
import com.oracle.truffle.js.nodes.access.SuperPropertyReferenceNode;
import com.oracle.truffle.js.nodes.instrumentation.JSInputGeneratingNodeWrapper;
import com.oracle.truffle.js.nodes.instrumentation.JSMaterializedInvokeTargetableNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTags;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.FunctionCallTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadPropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.NodeObjectDescriptor;
import com.oracle.truffle.js.nodes.interop.ExportArgumentsNode;
import com.oracle.truffle.js.nodes.interop.ForeignObjectPrototypeNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSNoSuchMethodAdapter;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptFunctionCallNode;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionObject;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.DebugCounter;
import com.oracle.truffle.js.runtime.util.SimpleArrayList;
public abstract class JSFunctionCallNode extends JavaScriptNode implements JavaScriptFunctionCallNode {
private static final DebugCounter megamorphicCount = DebugCounter.create("Megamorphic call site count");
static final byte CALL = 0;
static final byte NEW = 1 << 0;
static final byte NEW_TARGET = 1 << 1;
protected final byte flags;
@Child protected AbstractCacheNode cacheNode;
protected JSFunctionCallNode(byte flags) {
this.flags = flags;
}
@NeverDefault
public static JSFunctionCallNode createCall() {
return create(false);
}
@NeverDefault
public static JSFunctionCallNode createNew() {
return create(true);
}
@NeverDefault
public static JSFunctionCallNode createNewTarget() {
return create(true, true);
}
public static JSFunctionCallNode create(boolean isNew) {
return create(isNew, false);
}
public static JSFunctionCallNode create(boolean isNew, boolean isNewTarget) {
return new ExecuteCallNode(createFlags(isNew, isNewTarget));
}
private static byte createFlags(boolean isNew, boolean isNewTarget) {
return (isNewTarget ? NEW_TARGET : (isNew ? NEW : CALL));
}
public static JSFunctionCallNode createCall(JavaScriptNode function, JavaScriptNode target, JavaScriptNode[] arguments, boolean isNew, boolean isNewTarget) {
byte flags = createFlags(isNew, isNewTarget);
boolean spread = hasSpreadArgument(arguments);
if (spread) {
return new CallSpreadNode(target, function, arguments, flags);
}
if (arguments.length == 0) {
return new Call0Node(target, function, flags);
} else if (arguments.length == 1) {
return new Call1Node(target, function, arguments[0], flags);
}
return new CallNNode(target, function, arguments, flags);
}
public static JSFunctionCallNode createInvoke(JSTargetableNode targetFunction, JavaScriptNode[] arguments, boolean isNew, boolean isNewTarget) {
byte flags = createFlags(isNew, isNewTarget);
boolean spread = hasSpreadArgument(arguments);
if (spread) {
return new InvokeSpreadNode(targetFunction, arguments, flags);
}
if (arguments.length == 0) {
return new Invoke0Node(targetFunction, flags);
} else if (arguments.length == 1) {
return new Invoke1Node(targetFunction, arguments[0], flags);
}
return new InvokeNNode(targetFunction, arguments, flags);
}
@NeverDefault
public static JSFunctionCallNode getUncachedCall() {
return Uncached.CALL;
}
@NeverDefault
public static JSFunctionCallNode getUncachedNew() {
return Uncached.NEW;
}
static boolean isNewTarget(byte flags) {
return (flags & NEW_TARGET) != 0;
}
static boolean isNew(byte flags) {
return (flags & NEW) != 0;
}
private static boolean hasSpreadArgument(JavaScriptNode[] arguments) {
for (JavaScriptNode arg : arguments) {
if (arg instanceof SpreadArgumentNode) {
return true;
}
}
return false;
}
public final boolean isNew() {
return isNew(flags);
}
public final boolean isInvoke() {
return this instanceof InvokeNode;
}
protected Object getPropertyKey() {
return null;
}
@Override
public boolean hasTag(Class extends Tag> tag) {
if (tag == FunctionCallTag.class) {
return true;
} else {
return super.hasTag(tag);
}
}
@Override
public Object getNodeObject() {
NodeObjectDescriptor descriptor = JSTags.createNodeObjectDescriptor();
descriptor.addProperty("isNew", isNew());
descriptor.addProperty("isInvoke", isInvoke());
return descriptor;
}
@ExplodeLoop
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
for (AbstractCacheNode c = cacheNode; c != null; c = c.nextNode) {
if (c.accept(function)) {
return c.executeCall(arguments);
}
}
CompilerDirectives.transferToInterpreterAndInvalidate();
return executeAndSpecialize(arguments);
}
private Object executeAndSpecialize(Object[] arguments) {
CompilerAsserts.neverPartOfCompilation();
Object function = JSArguments.getFunctionObject(arguments);
AbstractCacheNode c;
Lock lock = getLock();
lock.lock();
try {
AbstractCacheNode currentHead = cacheNode;
int cachedCount = 0;
boolean generic = false;
c = currentHead;
while (c != null) {
if (c.accept(function)) {
break;
}
if (isCached(c)) {
assert !generic;
cachedCount++;
} else {
generic = generic || isGeneric(c);
}
c = c.nextNode;
}
if (c == null) {
JSContext context = getLanguage().getJSContext();
if (cachedCount < context.getFunctionCacheLimit() && !generic) {
if (JSFunction.isJSFunction(function)) {
c = specializeDirectCall((JSFunctionObject) function, currentHead);
}
}
if (c == null) {
boolean hasCached = cachedCount > 0;
if (JSFunction.isJSFunction(function)) {
c = specializeGenericFunction(currentHead, hasCached);
} else if (JSProxy.isJSProxy(function)) {
c = insertAtFront(specializeProxyCall(function, context), currentHead);
} else if (JSGuards.isForeignObject(function)) {
c = specializeForeignCall(arguments, currentHead);
} else if (function instanceof JSNoSuchMethodAdapter) {
c = insertAtFront(new JSNoSuchMethodAdapterCacheNode(), currentHead);
} else {
c = insertAtFront(new GenericFallbackCacheNode(), dropCachedNodes(currentHead, hasCached));
}
}
assert c.getParent() != null;
}
} finally {
lock.unlock();
}
if (c.accept(function)) {
return c.executeCall(arguments);
} else {
throw CompilerDirectives.shouldNotReachHere("Inconsistent guard.");
}
}
private AbstractCacheNode specializeProxyCall(Object function, JSContext context) {
assert JSProxy.isJSProxy(function);
if (getParent() instanceof JSProxyCallNode) {
// Perform an actual call to protect against proxy cycles and deep nesting.
return new JSProxyCallCacheNode(isNew(flags), isNewTarget(flags), context);
} else {
return new JSProxyInlineCacheNode(isNew(flags), isNewTarget(flags), context);
}
}
private static boolean isCached(AbstractCacheNode c) {
return c instanceof JSFunctionCacheNode;
}
private static boolean isGeneric(AbstractCacheNode c) {
return c instanceof GenericJSFunctionCacheNode || c instanceof GenericFallbackCacheNode;
}
private static boolean isUncached(AbstractCacheNode c) {
return c instanceof JSProxyInlineCacheNode || c instanceof JSProxyCallCacheNode || c instanceof ForeignCallNode || c instanceof JSNoSuchMethodAdapterCacheNode;
}
private static int getCachedCount(AbstractCacheNode head) {
int count = 0;
for (AbstractCacheNode c = head; c != null; c = c.nextNode) {
if (isCached(c)) {
count++;
}
}
return count;
}
private AbstractCacheNode specializeDirectCall(JSFunctionObject functionObj, AbstractCacheNode head) {
final JSFunctionData functionData = JSFunction.getFunctionData(functionObj);
if (JSConfig.FunctionCacheOnInstance && !functionData.getContext().isMultiContext()) {
return specializeDirectCallInstance(functionObj, functionData, head);
} else {
return specializeDirectCallShared(functionObj, functionData, head);
}
}
private JSFunctionCacheNode specializeDirectCallInstance(JSFunctionObject functionObj, JSFunctionData functionData, AbstractCacheNode head) {
JSFunctionCacheNode obsoleteNode = null;
AbstractCacheNode previousNode = null;
for (AbstractCacheNode p = null, c = head; c != null; p = c, c = c.nextNode) {
if (c instanceof JSFunctionCacheNode) {
JSFunctionCacheNode current = (JSFunctionCacheNode) c;
if (current.isInstanceCache()) {
if (functionData == current.getFunctionData()) {
obsoleteNode = current;
previousNode = p;
break;
}
}
}
}
if (obsoleteNode == null) {
JSFunctionCacheNode directCall = createCallableNode(functionObj, functionData, isNew(flags), isNewTarget(flags), true);
return insertAtFront(directCall, head);
} else {
if (JSConfig.TraceFunctionCache) {
System.out.printf("FUNCTION CACHE changed function instance to function data cache %s (depth=%d)\n", getEncapsulatingSourceSection(), getCachedCount(head));
}
JSFunctionCacheNode newNode;
if (obsoleteNode instanceof FunctionInstanceCacheNode) {
DirectCallNode callNode = ((FunctionInstanceCacheNode) obsoleteNode).callNode;
if (functionData.isBound()) {
newNode = new BoundFunctionDataCacheNode(functionData, callNode);
} else if (functionObj instanceof JSFunctionObject.Unbound) {
newNode = new UnboundFunctionDataCacheNode(functionData, callNode);
} else {
newNode = new AnyFunctionDataCacheNode(functionData, callNode);
}
} else {
newNode = createCallableNode(functionObj, functionData, isNew(flags), isNewTarget(flags), false);
}
return replaceCached(newNode, head, obsoleteNode, previousNode);
}
}
private JSFunctionCacheNode specializeDirectCallShared(JSFunctionObject functionObj, JSFunctionData functionData, AbstractCacheNode head) {
final JSFunctionCacheNode directCall = createCallableNode(functionObj, functionData, isNew(flags), isNewTarget(flags), false);
return insertAtFront(directCall, head);
}
private AbstractCacheNode specializeGenericFunction(AbstractCacheNode head, boolean hasCached) {
AbstractCacheNode otherGeneric = dropCachedNodes(head, hasCached);
AbstractCacheNode newNode = new GenericJSFunctionCacheNode(flags, otherGeneric);
insert(newNode);
this.cacheNode = newNode;
reportPolymorphicSpecialize();
return newNode;
}
private static AbstractCacheNode dropCachedNodes(AbstractCacheNode head, boolean hasCached) {
if (!hasCached) {
assert getCachedCount(head) == 0;
return head;
}
AbstractCacheNode gen = null;
for (AbstractCacheNode c = head; c != null; c = c.nextNode) {
if (isCached(c)) {
continue;
}
assert isGeneric(c) || isUncached(c);
gen = c.withNext(gen);
}
return gen;
}
private AbstractCacheNode specializeForeignCall(Object[] arguments, AbstractCacheNode head) {
AbstractCacheNode newNode = null;
int userArgumentCount = JSArguments.getUserArgumentCount(arguments);
Object thisObject = JSArguments.getThisObject(arguments);
if (isNew(flags) || isNewTarget(flags)) {
int skippedArgs = isNewTarget(flags) ? 1 : 0;
newNode = new ForeignInstantiateNode(skippedArgs, userArgumentCount - skippedArgs);
} else if (JSGuards.isForeignObject(thisObject)) {
Object propertyKey = getPropertyKey();
if (propertyKey instanceof TruffleString propertyName) {
newNode = new ForeignInvokeNode(propertyName, userArgumentCount);
}
}
if (newNode == null) {
newNode = new ForeignExecuteNode(userArgumentCount);
}
return insertAtFront(newNode, head);
}
private T insertAtFront(T newNode, AbstractCacheNode head) {
insert(newNode);
newNode.nextNode = head;
this.cacheNode = newNode;
return newNode;
}
@SuppressWarnings("unused")
private T replaceCached(T newNode, AbstractCacheNode head, AbstractCacheNode obsoleteNode, AbstractCacheNode previousNode) {
assert previousNode == null || previousNode.nextNode == obsoleteNode;
insert(newNode);
newNode.nextNode = obsoleteNode.nextNode;
if (previousNode != null) {
previousNode.nextNode = newNode;
} else {
this.cacheNode = newNode;
}
return newNode;
}
@Override
public JavaScriptNode getTarget() {
return null;
}
protected final Object evaluateReceiver(VirtualFrame frame, Object target) {
JavaScriptNode targetNode = getTarget();
if (targetNode instanceof SuperPropertyReferenceNode) {
return ((SuperPropertyReferenceNode) targetNode).evaluateTarget(frame);
}
return target;
}
@ExplodeLoop
protected static Object[] executeFillObjectArraySpread(JavaScriptNode[] arguments, VirtualFrame frame, Object[] args, int fixedArgumentsLength) {
// assume size that avoids growing
SimpleArrayList argList = SimpleArrayList.create((long) fixedArgumentsLength + arguments.length + JSConfig.SpreadArgumentPlaceholderCount);
for (int i = 0; i < fixedArgumentsLength; i++) {
argList.addUnchecked(args[i]);
}
for (int i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof SpreadArgumentNode) {
((SpreadArgumentNode) arguments[i]).executeToList(frame, argList);
} else {
argList.addUncached(arguments[i].execute(frame));
}
}
return argList.toArray();
}
abstract static class CallNode extends JSFunctionCallNode {
/**
* May be {@code null}, the target value is {@code undefined}, then.
*/
@Child protected JavaScriptNode targetNode;
@Child protected JavaScriptNode functionNode;
protected CallNode(JavaScriptNode targetNode, JavaScriptNode functionNode, byte flags) {
super(flags);
this.targetNode = targetNode;
this.functionNode = functionNode;
}
@Override
public final JavaScriptNode getTarget() {
return targetNode;
}
public final JavaScriptNode getFunction() {
return functionNode;
}
protected abstract Object[] createArguments(VirtualFrame frame, Object target, Object function);
protected abstract JavaScriptNode[] getArgumentNodes();
protected abstract void materializeInstrumentableArguments();
@Override
public final Object execute(VirtualFrame frame) {
Object target = executeTarget(frame);
Object receiver = evaluateReceiver(frame, target);
Object function = functionNode.execute(frame);
// Note that the arguments must not be evaluated before the target.
return executeCall(createArguments(frame, receiver, function));
}
final Object executeTarget(VirtualFrame frame) {
if (targetNode != null) {
return targetNode.execute(frame);
} else {
return Undefined.instance;
}
}
@Override
public String expressionToString() {
return Objects.toString(functionNode.expressionToString(), INTERMEDIATE_VALUE) + "(...)";
}
@Override
protected Object getPropertyKey() {
return null;
}
@Override
public InstrumentableNode materializeInstrumentableNodes(Set> materializedTags) {
if (materializedTags.contains(FunctionCallTag.class)) {
materializeInstrumentableArguments();
if (this.hasSourceSection() && !functionNode.hasSourceSection()) {
transferSourceSectionAddExpressionTag(this, functionNode);
}
if (targetNode != null) {
// if we have a target, no de-sugaring needed
return this;
} else {
JavaScriptNode materializedTargetNode = JSInputGeneratingNodeWrapper.create(JSConstantUndefinedNode.createUndefined());
JavaScriptNode call = createCall(cloneUninitialized(functionNode, materializedTags), materializedTargetNode, cloneUninitialized(getArgumentNodes(), materializedTags), isNew(flags),
isNewTarget(flags));
transferSourceSectionAndTags(this, call);
return call;
}
} else {
return this;
}
}
}
static class Call0Node extends CallNode {
protected Call0Node(JavaScriptNode targetNode, JavaScriptNode functionNode, byte flags) {
super(targetNode, functionNode, flags);
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
return JSArguments.createZeroArg(target, function);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new Call0Node(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionNode, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return new JavaScriptNode[0];
}
@Override
protected void materializeInstrumentableArguments() {
}
}
static class Call1Node extends CallNode {
@Child protected JavaScriptNode argument0;
protected Call1Node(JavaScriptNode targetNode, JavaScriptNode functionNode, JavaScriptNode argument0, byte flags) {
super(targetNode, functionNode, flags);
this.argument0 = argument0;
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
Object arg0 = argument0.execute(frame);
return JSArguments.createOneArg(target, function, arg0);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new Call1Node(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionNode, materializedTags), cloneUninitialized(argument0, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return new JavaScriptNode[]{argument0};
}
@Override
protected void materializeInstrumentableArguments() {
if (!argument0.isInstrumentable()) {
argument0 = insert(JSInputGeneratingNodeWrapper.create(argument0));
}
}
}
static class CallNNode extends CallNode {
@Children protected final JavaScriptNode[] arguments;
protected CallNNode(JavaScriptNode targetNode, JavaScriptNode functionNode, JavaScriptNode[] arguments, byte flags) {
super(targetNode, functionNode, flags);
this.arguments = arguments;
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
Object[] args = JSArguments.createInitial(target, function, arguments.length);
return executeFillObjectArray(frame, args, JSArguments.RUNTIME_ARGUMENT_COUNT);
}
@ExplodeLoop
protected Object[] executeFillObjectArray(VirtualFrame frame, Object[] args, int delta) {
for (int i = 0; i < arguments.length; i++) {
assert !(arguments[i] instanceof SpreadArgumentNode);
args[i + delta] = arguments[i].execute(frame);
}
return args;
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new CallNNode(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionNode, materializedTags), cloneUninitialized(arguments, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return arguments;
}
@Override
protected void materializeInstrumentableArguments() {
for (int i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof SpreadArgumentNode) && !arguments[i].isInstrumentable()) {
arguments[i] = insert(JSInputGeneratingNodeWrapper.create(arguments[i]));
}
}
}
}
static class CallSpreadNode extends CallNNode {
protected CallSpreadNode(JavaScriptNode targetNode, JavaScriptNode functionNode, JavaScriptNode[] arguments, byte flags) {
super(targetNode, functionNode, arguments, flags);
}
@Override
protected Object[] executeFillObjectArray(VirtualFrame frame, Object[] args, int delta) {
return executeFillObjectArraySpread(arguments, frame, args, delta);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new CallSpreadNode(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionNode, materializedTags), cloneUninitialized(arguments, materializedTags), flags);
}
}
/**
* The target of {@link #functionTargetNode} also serves as the this argument of the call. If
* {@code true}, target not only serves as the this argument of the call, but also the target
* object for the member expression that retrieves the function.
*/
public abstract static class InvokeNode extends JSFunctionCallNode {
@Child protected JavaScriptNode targetNode;
@Child protected JSTargetableNode functionTargetNode;
protected InvokeNode(JavaScriptNode targetNode, JSTargetableNode functionTargetNode, byte flags) {
super(flags);
this.targetNode = targetNode;
this.functionTargetNode = functionTargetNode;
}
@Override
public final JavaScriptNode getTarget() {
if (targetNode != null) {
return targetNode;
}
return functionTargetNode.getTarget();
}
protected abstract Object[] createArguments(VirtualFrame frame, Object target, Object function);
protected abstract JavaScriptNode[] getArgumentNodes();
protected abstract void materializeInstrumentableArguments();
@Override
public final Object execute(VirtualFrame frame) {
Object target = executeTarget(frame);
Object receiver = evaluateReceiver(frame, target);
Object function = executeFunctionWithTarget(frame, target);
// Note that the arguments must not be evaluated before the target.
return executeCall(createArguments(frame, receiver, function));
}
protected final Object executeTarget(VirtualFrame frame) {
if (targetNode != null) {
return targetNode.execute(frame);
}
return functionTargetNode.evaluateTarget(frame);
}
final Object executeFunctionWithTarget(VirtualFrame frame, Object target) {
return functionTargetNode.executeWithTarget(frame, target);
}
@Override
public String expressionToString() {
return Objects.toString(functionTargetNode.expressionToString(), INTERMEDIATE_VALUE) + "(...)";
}
@Override
public InstrumentableNode materializeInstrumentableNodes(Set> materializedTags) {
if (targetNode != null) {
// if we have a target, no de-sugaring needed
return this;
}
if (materializedTags.contains(FunctionCallTag.class) || materializedTags.contains(ReadPropertyTag.class) ||
materializedTags.contains(ReadElementTag.class)) {
materializeInstrumentableArguments();
InvokeNode invoke = (InvokeNode) createInvoke(null, cloneUninitialized(getArgumentNodes(), materializedTags), isNew(flags), isNewTarget(flags));
JSTargetableNode functionTargetNodeDelegate = cloneUninitialized(getFunctionTargetDelegate(), materializedTags);
JavaScriptNode target = functionTargetNodeDelegate.getTarget();
invoke.targetNode = !target.isInstrumentable() ? JSInputGeneratingNodeWrapper.create(target) : target;
invoke.functionTargetNode = JSMaterializedInvokeTargetableNode.createFor(functionTargetNodeDelegate);
transferSourceSectionAndTags(functionTargetNodeDelegate, invoke.functionTargetNode);
transferSourceSectionAndTags(this, invoke);
return invoke;
} else {
return this;
}
}
private JSTargetableNode getFunctionTargetDelegate() {
if (functionTargetNode instanceof WrapperNode) {
return (JSTargetableNode) ((WrapperNode) functionTargetNode).getDelegateNode();
} else {
return functionTargetNode;
}
}
@Override
protected Object getPropertyKey() {
JavaScriptNode propertyNode = JSNodeUtil.getWrappedNode(functionTargetNode);
if (propertyNode instanceof ShortCircuitTargetableNode shortCircuitNode) {
propertyNode = JSNodeUtil.getWrappedNode(shortCircuitNode.getExpressionNode());
}
if (propertyNode instanceof PropertyNode) {
return ((PropertyNode) propertyNode).getPropertyKey();
}
return null;
}
public JSTargetableNode getFunctionTargetNode() {
return functionTargetNode;
}
}
static class Invoke0Node extends InvokeNode {
protected Invoke0Node(JSTargetableNode functionNode, byte flags) {
this(null, functionNode, flags);
}
protected Invoke0Node(JavaScriptNode targetNode, JSTargetableNode functionNode, byte flags) {
super(targetNode, functionNode, flags);
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
return JSArguments.createZeroArg(target, function);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new Invoke0Node(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionTargetNode, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return new JavaScriptNode[0];
}
@Override
protected void materializeInstrumentableArguments() {
}
}
static class Invoke1Node extends InvokeNode {
@Child protected JavaScriptNode argument0;
protected Invoke1Node(JSTargetableNode functionNode, JavaScriptNode argument0, byte flags) {
this(null, functionNode, argument0, flags);
}
protected Invoke1Node(JavaScriptNode targetNode, JSTargetableNode functionNode, JavaScriptNode argument0, byte flags) {
super(targetNode, functionNode, flags);
this.argument0 = argument0;
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
Object arg0 = argument0.execute(frame);
return JSArguments.createOneArg(target, function, arg0);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new Invoke1Node(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionTargetNode, materializedTags), cloneUninitialized(argument0, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return new JavaScriptNode[]{argument0};
}
@Override
protected void materializeInstrumentableArguments() {
if (!argument0.isInstrumentable()) {
argument0 = insert(JSInputGeneratingNodeWrapper.create(argument0));
}
}
}
static class InvokeNNode extends InvokeNode {
@Children protected final JavaScriptNode[] arguments;
protected InvokeNNode(JSTargetableNode functionNode, JavaScriptNode[] arguments, byte flags) {
this(null, functionNode, arguments, flags);
}
protected InvokeNNode(JavaScriptNode targetNode, JSTargetableNode functionNode, JavaScriptNode[] arguments, byte flags) {
super(targetNode, functionNode, flags);
this.arguments = arguments;
}
@Override
protected final Object[] createArguments(VirtualFrame frame, Object target, Object function) {
Object[] args = JSArguments.createInitial(target, function, arguments.length);
return executeFillObjectArray(frame, args, JSArguments.RUNTIME_ARGUMENT_COUNT);
}
@ExplodeLoop
protected Object[] executeFillObjectArray(VirtualFrame frame, Object[] args, int delta) {
for (int i = 0; i < arguments.length; i++) {
assert !(arguments[i] instanceof SpreadArgumentNode);
args[i + delta] = arguments[i].execute(frame);
}
return args;
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new InvokeNNode(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionTargetNode, materializedTags), cloneUninitialized(arguments, materializedTags), flags);
}
@Override
protected JavaScriptNode[] getArgumentNodes() {
return arguments;
}
@Override
protected void materializeInstrumentableArguments() {
for (int i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof SpreadArgumentNode) && !arguments[i].isInstrumentable()) {
arguments[i] = insert(JSInputGeneratingNodeWrapper.create(arguments[i]));
}
}
}
}
static class InvokeSpreadNode extends InvokeNNode {
protected InvokeSpreadNode(JSTargetableNode functionNode, JavaScriptNode[] arguments, byte flags) {
this(null, functionNode, arguments, flags);
}
protected InvokeSpreadNode(JavaScriptNode targetNode, JSTargetableNode functionNode, JavaScriptNode[] arguments, byte flags) {
super(targetNode, functionNode, arguments, flags);
}
@Override
protected Object[] executeFillObjectArray(VirtualFrame frame, Object[] args, int delta) {
return executeFillObjectArraySpread(arguments, frame, args, delta);
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new InvokeSpreadNode(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionTargetNode, materializedTags), cloneUninitialized(arguments, materializedTags),
flags);
}
}
static class ExecuteCallNode extends JSFunctionCallNode {
protected ExecuteCallNode(byte flags) {
super(flags);
}
@Override
public Object execute(VirtualFrame frame) {
throw Errors.shouldNotReachHere();
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return new ExecuteCallNode(flags);
}
}
protected static JSFunctionCacheNode createCallableNode(JSFunctionObject function, JSFunctionData functionData, boolean isNew, boolean isNewTarget, boolean cacheOnInstance) {
CallTarget callTarget = getCallTarget(functionData, isNew, isNewTarget);
assert callTarget != null;
if (function instanceof JSFunctionObject.Bound boundFunction && isBoundFunctionNestingDepthWithinLimits(boundFunction)) {
if (cacheOnInstance) {
return new BoundFunctionInstanceCallNode(boundFunction, isNew, isNewTarget);
} else {
return new DynamicBoundFunctionCallNode(isNew, isNewTarget, functionData);
}
} else {
JSFunctionCacheNode node = tryInlineBuiltinFunctionCall(function, functionData, callTarget, cacheOnInstance);
if (node != null) {
return node;
}
if (cacheOnInstance) {
return new FunctionInstanceCacheNode(function, callTarget);
} else if (function instanceof JSFunctionObject.Bound) {
// used in the case of a deeply nested bound function
return new BoundFunctionDataCacheNode(functionData, callTarget);
} else if (function instanceof JSFunctionObject.Unbound) {
return new UnboundFunctionDataCacheNode(functionData, callTarget);
} else {
return new AnyFunctionDataCacheNode(functionData, callTarget);
}
}
}
private static boolean isBoundFunctionNestingDepthWithinLimits(JSFunctionObject.Bound function) {
JSFunctionObject.Bound boundFunction = function;
for (int i = 0; i < JSConfig.BoundFunctionUnpackLimit; i++) {
Object targetFunction = boundFunction.getBoundTargetFunction();
if (targetFunction instanceof JSFunctionObject.Bound nestedBoundFunction) {
boundFunction = nestedBoundFunction;
} else {
return true;
}
}
return false;
}
protected static CallTarget getCallTarget(JSFunctionData functionData, boolean isNew, boolean isNewTarget) {
if (isNewTarget) {
return functionData.getConstructNewTarget();
} else if (isNew) {
return functionData.getConstructTarget();
} else {
return functionData.getCallTarget();
}
}
private static JSFunctionCacheNode tryInlineBuiltinFunctionCall(JSFunctionObject function, JSFunctionData functionData, CallTarget callTarget, boolean cacheOnInstance) {
if (!JSConfig.InlineTrivialBuiltins) {
return null;
}
if (callTarget instanceof RootCallTarget) {
RootNode rootNode = ((RootCallTarget) callTarget).getRootNode();
if (rootNode instanceof FunctionRootNode) {
JavaScriptNode body = ((FunctionRootNode) rootNode).getBody();
if (body instanceof JSBuiltinNode) {
JSBuiltinNode builtinNode = (JSBuiltinNode) body;
JSBuiltinNode.Inlined inlined = builtinNode.tryCreateInlined();
if (inlined != null) {
if (cacheOnInstance) {
return new InlinedBuiltinFunctionInstanceCacheNode(function, callTarget, inlined);
} else {
return new InlinedBuiltinFunctionDataCacheNode(functionData, callTarget, inlined);
}
} else if (builtinNode.isCallerSensitive()) {
if (cacheOnInstance) {
return new CallerSensitiveBuiltinFunctionInstanceCacheNode(function, functionData, callTarget);
} else {
return new CallerSensitiveBuiltinFunctionDataCacheNode(functionData, callTarget);
}
}
}
}
}
return null;
}
private static final class FunctionInstanceCacheNode extends DirectJSFunctionCacheNode {
private final JSFunctionObject functionObj;
FunctionInstanceCacheNode(JSFunctionObject functionObj, CallTarget callTarget) {
super(callTarget);
this.functionObj = functionObj;
}
@Override
protected boolean accept(Object function) {
return functionObj == function;
}
@Override
protected JSFunctionData getFunctionData() {
return JSFunction.getFunctionData(functionObj);
}
@Override
protected boolean isInstanceCache() {
return true;
}
}
private static final class UnboundFunctionDataCacheNode extends DirectJSFunctionCacheNode {
private final JSFunctionData functionData;
UnboundFunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget) {
super(callTarget);
this.functionData = functionData;
assert !functionData.isBound();
}
UnboundFunctionDataCacheNode(JSFunctionData functionData, DirectCallNode directCallNode) {
super(directCallNode);
this.functionData = functionData;
}
@Override
protected boolean accept(Object function) {
return function instanceof JSFunctionObject.Unbound && JSFunction.getFunctionData((JSFunctionObject.Unbound) function) == functionData;
}
@Override
protected JSFunctionData getFunctionData() {
return functionData;
}
}
private static final class BoundFunctionDataCacheNode extends DirectJSFunctionCacheNode {
private final JSFunctionData functionData;
BoundFunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget) {
super(callTarget);
this.functionData = functionData;
assert functionData.isBound();
}
BoundFunctionDataCacheNode(JSFunctionData functionData, DirectCallNode directCallNode) {
super(directCallNode);
this.functionData = functionData;
}
@Override
protected boolean accept(Object function) {
return function instanceof JSFunctionObject.Bound && JSFunction.getFunctionData((JSFunctionObject.Bound) function) == functionData;
}
@Override
protected JSFunctionData getFunctionData() {
return functionData;
}
}
private static final class AnyFunctionDataCacheNode extends DirectJSFunctionCacheNode {
private final JSFunctionData functionData;
AnyFunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget) {
super(callTarget);
this.functionData = functionData;
}
AnyFunctionDataCacheNode(JSFunctionData functionData, DirectCallNode directCallNode) {
super(directCallNode);
this.functionData = functionData;
}
@Override
protected boolean accept(Object function) {
return function instanceof JSFunctionObject && JSFunction.getFunctionData((JSFunctionObject) function) == functionData;
}
@Override
protected JSFunctionData getFunctionData() {
return functionData;
}
}
private abstract static class AbstractCacheNode extends JavaScriptBaseNode {
@Child protected AbstractCacheNode nextNode;
protected abstract boolean accept(Object function);
public abstract Object executeCall(Object[] arguments);
protected AbstractCacheNode withNext(AbstractCacheNode newNext) {
AbstractCacheNode copy = (AbstractCacheNode) copy();
copy.nextNode = newNext;
return copy;
}
}
private abstract static class JSFunctionCacheNode extends AbstractCacheNode {
JSFunctionCacheNode() {
}
protected boolean isInstanceCache() {
return false;
}
protected abstract JSFunctionData getFunctionData();
}
private static final class BoundFunctionInstanceCallNode extends JSFunctionCacheNode {
@Child private AbstractCacheNode boundNode;
private final JSFunctionObject boundFunctionObj;
private final Object boundThis;
private final Object targetFunctionObj;
private final Object[] addArguments;
private final boolean useDynamicThis;
private final boolean isNewTarget;
BoundFunctionInstanceCallNode(JSFunctionObject.Bound function, boolean isNew, boolean isNewTarget) {
this.boundFunctionObj = function;
Object lastReceiver;
Object lastFunction = function;
List prefixArguments = new ArrayList<>();
do {
JSFunctionObject.Bound boundFunction = (JSFunctionObject.Bound) lastFunction;
Object[] extraArguments = boundFunction.getBoundArguments();
prefixArguments.addAll(0, Arrays.asList(extraArguments));
lastReceiver = boundFunction.getBoundThis();
lastFunction = boundFunction.getBoundTargetFunction();
} while (lastFunction instanceof JSFunctionObject.Bound && !isNewTarget);
// Note: We cannot unpack nested bound functions if this is a construct-with-newTarget.
// This is because we need to apply the SameValue(F, newTarget) check below recursively
// for all bound functions F until we reach the unbound target function.
// As a result, we nest a BoundCallNode for every bound function layer to be unpacked.
this.addArguments = prefixArguments.toArray(JSArguments.EMPTY_ARGUMENTS_ARRAY);
this.targetFunctionObj = lastFunction;
if (isNew || isNewTarget) {
this.useDynamicThis = true;
this.boundThis = null;
} else {
this.useDynamicThis = false;
this.boundThis = lastReceiver;
}
this.isNewTarget = isNewTarget;
if (JSFunction.isJSFunction(lastFunction)) {
JSFunctionObject lastJSFunction = (JSFunctionObject) lastFunction;
this.boundNode = createCallableNode(lastJSFunction, JSFunction.getFunctionData(lastJSFunction), isNew, isNewTarget, true);
} else if (JSProxy.isJSProxy(lastFunction)) {
assert JSRuntime.isCallableProxy((JSDynamicObject) lastFunction);
this.boundNode = new JSProxyCallCacheNode(isNew, isNewTarget, function.getJSContext());
} else {
assert JSRuntime.isCallableForeign(lastFunction);
int expectedArgumentCount = -1; // unknown
if (isNew || isNewTarget) {
int skippedArgs = isNewTarget ? 1 : 0;
this.boundNode = new ForeignInstantiateNode(skippedArgs, expectedArgumentCount);
} else {
this.boundNode = new ForeignExecuteNode(expectedArgumentCount);
}
}
}
@Override
public Object executeCall(Object[] arguments) {
assert checkTargetFunction(arguments);
return boundNode.executeCall(bindExtraArguments(arguments));
}
private Object[] bindExtraArguments(Object[] origArgs) {
Object target = useDynamicThis ? JSArguments.getThisObject(origArgs) : boundThis;
int skip = isNewTarget ? 1 : 0;
Object[] origUserArgs = JSArguments.extractUserArguments(origArgs, skip);
int newUserArgCount = addArguments.length + origUserArgs.length;
Object[] arguments = JSArguments.createInitial(target, targetFunctionObj, skip + newUserArgCount);
JSArguments.setUserArguments(arguments, skip, addArguments);
JSArguments.setUserArguments(arguments, skip + addArguments.length, origUserArgs);
if (isNewTarget) {
Object newTarget = JSArguments.getNewTarget(origArgs);
if (newTarget == JSArguments.getFunctionObject(origArgs)) {
newTarget = targetFunctionObj;
}
arguments[JSArguments.RUNTIME_ARGUMENT_COUNT] = newTarget;
}
return arguments;
}
private boolean checkTargetFunction(Object[] arguments) {
Object targetFunction = JSArguments.getFunctionObject(arguments);
while (targetFunction instanceof JSFunctionObject.Bound boundFunction) {
targetFunction = boundFunction.getBoundTargetFunction();
if (isNewTarget) {
// see note above
return targetFunctionObj == targetFunction;
}
}
return targetFunctionObj == targetFunction;
}
@Override
protected boolean accept(Object function) {
return function == boundFunctionObj;
}
@Override
protected JSFunctionData getFunctionData() {
return JSFunction.getFunctionData(boundFunctionObj);
}
@Override
protected boolean isInstanceCache() {
return true;
}
}
private static final class DynamicBoundFunctionCallNode extends JSFunctionCacheNode {
@Child private JSFunctionCallNode boundTargetCallNode;
private final boolean useDynamicThis;
private final boolean isNewTarget;
private final JSFunctionData boundFunctionData;
DynamicBoundFunctionCallNode(boolean isNew, boolean isNewTarget, JSFunctionData boundFunctionData) {
super();
this.useDynamicThis = (isNew || isNewTarget);
this.isNewTarget = isNewTarget;
this.boundFunctionData = boundFunctionData;
this.boundTargetCallNode = JSFunctionCallNode.create(isNew, isNewTarget);
}
@Override
public Object executeCall(Object[] arguments) {
return boundTargetCallNode.executeCall(bindExtraArguments(arguments));
}
private Object[] bindExtraArguments(Object[] origArgs) {
JSFunctionObject.Bound function = (JSFunctionObject.Bound) JSArguments.getFunctionObject(origArgs);
Object boundTargetFunction = function.getBoundTargetFunction();
Object boundThis = useDynamicThis ? JSArguments.getThisObject(origArgs) : function.getBoundThis();
Object[] boundArguments = function.getBoundArguments();
int skip = isNewTarget ? 1 : 0;
Object[] origUserArgs = JSArguments.extractUserArguments(origArgs, skip);
int newUserArgCount = boundArguments.length + origUserArgs.length;
Object[] arguments = JSArguments.createInitial(boundThis, boundTargetFunction, skip + newUserArgCount);
JSArguments.setUserArguments(arguments, skip, boundArguments);
JSArguments.setUserArguments(arguments, skip + boundArguments.length, origUserArgs);
if (isNewTarget) {
Object newTarget = JSArguments.getNewTarget(origArgs);
if (newTarget == function) {
newTarget = boundTargetFunction;
}
arguments[JSArguments.RUNTIME_ARGUMENT_COUNT] = newTarget;
}
return arguments;
}
@Override
protected boolean accept(Object function) {
return JSFunction.isJSFunction(function) && boundFunctionData == JSFunction.getFunctionData((JSFunctionObject) function);
}
@Override
protected JSFunctionData getFunctionData() {
return boundFunctionData;
}
}
private abstract static class DirectJSFunctionCacheNode extends JSFunctionCacheNode {
@Child DirectCallNode callNode;
DirectJSFunctionCacheNode(CallTarget callTarget) {
this.callNode = Truffle.getRuntime().createDirectCallNode(callTarget);
if (callTarget instanceof RootCallTarget) {
RootNode root = ((RootCallTarget) callTarget).getRootNode();
if (root instanceof FunctionRootNode && ((FunctionRootNode) root).isInlineImmediately()) {
insert(callNode);
if (((FunctionRootNode) root).isSplitImmediately()) {
callNode.cloneCallTarget();
}
callNode.forceInlining();
}
}
}
DirectJSFunctionCacheNode(DirectCallNode callNode) {
this.callNode = callNode;
}
@Override
public final Object executeCall(Object[] arguments) {
return callNode.call(arguments);
}
}
private abstract static class InlinedBuiltinCallNode extends JSFunctionCacheNode {
private final CallTarget callTarget;
@Child private JSBuiltinNode.Inlined builtinNode;
@Child private DirectCallNode callNode;
InlinedBuiltinCallNode(CallTarget callTarget, JSBuiltinNode.Inlined builtinNode) {
this.callTarget = callTarget;
this.builtinNode = builtinNode;
}
@Override
public Object executeCall(Object[] arguments) {
if (callNode != null) {
return callNode.call(arguments);
}
try {
return builtinNode.callInlined(arguments);
} catch (JSBuiltinNode.RewriteToCallException e) {
// rewrite inlined builtin to call
CompilerDirectives.transferToInterpreterAndInvalidate();
callNode = insert(Truffle.getRuntime().createDirectCallNode(callTarget));
callNode.cloneCallTarget();
callNode.forceInlining();
return callNode.call(arguments);
}
}
}
private static final class InlinedBuiltinFunctionInstanceCacheNode extends InlinedBuiltinCallNode {
private final JSFunctionObject functionObj;
InlinedBuiltinFunctionInstanceCacheNode(JSFunctionObject functionObj, CallTarget callTarget, JSBuiltinNode.Inlined builtinNode) {
super(callTarget, builtinNode);
this.functionObj = functionObj;
}
@Override
protected boolean accept(Object function) {
return functionObj == function;
}
@Override
protected JSFunctionData getFunctionData() {
return JSFunction.getFunctionData(functionObj);
}
@Override
protected boolean isInstanceCache() {
return true;
}
}
private static final class InlinedBuiltinFunctionDataCacheNode extends InlinedBuiltinCallNode {
private final JSFunctionData functionData;
InlinedBuiltinFunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget, JSBuiltinNode.Inlined builtinNode) {
super(callTarget, builtinNode);
this.functionData = functionData;
}
@Override
protected boolean accept(Object function) {
return JSFunction.isJSFunction(function) && functionData == JSFunction.getFunctionData((JSFunctionObject) function);
}
@Override
protected JSFunctionData getFunctionData() {
return functionData;
}
}
private abstract static class CallerSensitiveBuiltinCallNode extends JSFunctionCacheNode {
@Child private DirectCallNode callNode;
protected final JSFunctionData functionData;
CallerSensitiveBuiltinCallNode(JSFunctionData functionData, CallTarget callTarget) {
this.functionData = functionData;
this.callNode = Truffle.getRuntime().createDirectCallNode(callTarget);
}
@Override
public Object executeCall(Object[] arguments) {
JSRealm realm = getRealm();
JavaScriptBaseNode prev = realm.getCallNode();
try {
realm.setCallNode(this);
return callNode.call(arguments);
} finally {
realm.setCallNode(prev);
}
}
@Override
protected final JSFunctionData getFunctionData() {
return functionData;
}
}
private static final class CallerSensitiveBuiltinFunctionInstanceCacheNode extends CallerSensitiveBuiltinCallNode {
private final JSFunctionObject functionObj;
CallerSensitiveBuiltinFunctionInstanceCacheNode(JSFunctionObject functionObj, JSFunctionData functionData, CallTarget callTarget) {
super(functionData, callTarget);
this.functionObj = functionObj;
}
@Override
protected boolean accept(Object function) {
return functionObj == function;
}
@Override
protected boolean isInstanceCache() {
return true;
}
}
private static final class CallerSensitiveBuiltinFunctionDataCacheNode extends CallerSensitiveBuiltinCallNode {
CallerSensitiveBuiltinFunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget) {
super(functionData, callTarget);
}
@Override
protected boolean accept(Object function) {
return JSFunction.isJSFunction(function) && functionData == JSFunction.getFunctionData((JSFunctionObject) function);
}
}
private abstract static class ForeignCallNode extends AbstractCacheNode {
@Child private ExportArgumentsNode exportArgumentsNode;
@Child private ImportValueNode typeConvertNode;
private final ValueProfile functionClassProfile = ValueProfile.createClassProfile();
ForeignCallNode(int expectedArgumentCount) {
this.exportArgumentsNode = ExportArgumentsNode.create(expectedArgumentCount);
this.typeConvertNode = ImportValueNode.create();
}
@Override
protected boolean accept(Object function) {
return JSGuards.isForeignObject(functionClassProfile.profile(function));
}
protected final Object getForeignFunction(Object[] arguments) {
return functionClassProfile.profile(JSArguments.getFunctionObject(arguments));
}
protected final Object[] exportArguments(Object[] arguments) {
return exportArgumentsNode.export(JSArguments.extractUserArguments(arguments));
}
protected final Object[] exportArguments(Object[] arguments, int skip) {
return exportArgumentsNode.export(JSArguments.extractUserArguments(arguments, skip));
}
protected final Object convertForeignReturn(Object returnValue) {
return typeConvertNode.executeWithTarget(returnValue);
}
}
private static class ForeignExecuteNode extends ForeignCallNode {
@Child protected InteropLibrary interop;
ForeignExecuteNode(int expectedArgumentCount) {
super(expectedArgumentCount);
this.interop = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
}
@Override
public Object executeCall(Object[] arguments) {
Object function = getForeignFunction(arguments);
Object[] callArguments = exportArguments(arguments);
try {
return convertForeignReturn(interop.execute(function, callArguments));
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
throw Errors.createTypeErrorInteropException(function, e, "execute", this);
}
}
}
private static final class ForeignInvokeNode extends ForeignExecuteNode {
private final TruffleString functionName;
private final String functionNameJavaString;
private final ValueProfile thisClassProfile = ValueProfile.createClassProfile();
@Child private ForeignObjectPrototypeNode foreignObjectPrototypeNode;
@Child protected JSFunctionCallNode callJSFunctionNode;
@Child protected PropertyGetNode getFunctionNode;
private final BranchProfile errorBranch = BranchProfile.create();
@CompilationFinal private boolean optimistic = true;
ForeignInvokeNode(TruffleString functionName, int expectedArgumentCount) {
super(expectedArgumentCount);
this.functionName = functionName;
this.functionNameJavaString = Strings.toJavaString(functionName);
}
@Override
public Object executeCall(Object[] arguments) {
Object receiver = thisClassProfile.profile(JSArguments.getThisObject(arguments));
Object[] callArguments = exportArguments(arguments);
Object callReturn;
/*
* If the receiver is a foreign object, the property node does not send the READ message
* but returns the receiver, in which case we send an INVOKE message here instead.
*/
if (JSGuards.isForeignObject(receiver)) {
assert JSArguments.getFunctionObject(arguments) == receiver;
if (interop.isNull(receiver)) {
errorBranch.enter();
throw Errors.createTypeErrorCannotGetProperty(functionName, receiver, false, this);
}
if (optimistic) {
try {
callReturn = interop.invokeMember(receiver, functionNameJavaString, callArguments);
} catch (UnknownIdentifierException | UnsupportedMessageException e) {
CompilerDirectives.transferToInterpreterAndInvalidate();
optimistic = false;
callReturn = fallback(receiver, arguments, callArguments, e);
} catch (UnsupportedTypeException | ArityException e) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, e, "invokeMember", functionName, this);
}
} else {
if (interop.isMemberInvocable(receiver, functionNameJavaString)) {
try {
callReturn = interop.invokeMember(receiver, functionNameJavaString, callArguments);
} catch (UnknownIdentifierException | UnsupportedMessageException | UnsupportedTypeException | ArityException e) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, e, "invokeMember", functionName, this);
}
} else {
callReturn = fallback(receiver, arguments, callArguments, null);
}
}
} else {
Object function = getForeignFunction(arguments);
try {
callReturn = interop.execute(function, callArguments);
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(function, e, "execute", this);
}
}
return convertForeignReturn(callReturn);
}
@InliningCutoff
private Object fallback(Object receiver, Object[] arguments, Object[] callArguments, InteropException caughtException) {
InteropException ex = caughtException;
if (getContext().getLanguageOptions().hasForeignObjectPrototype() || JSInteropUtil.isBoxedPrimitive(receiver, interop)) {
Object function = maybeGetFromPrototype(receiver);
if (function != Undefined.instance) {
return callJSFunction(receiver, function, arguments);
}
}
if (getContext().getLanguageOptions().hasForeignHashProperties() && interop.hasHashEntries(receiver) && interop.isHashEntryReadable(receiver, functionName)) {
try {
Object function = interop.readHashValue(receiver, functionName);
return InteropLibrary.getUncached().execute(function, callArguments);
} catch (UnsupportedMessageException | UnknownKeyException | UnsupportedTypeException | ArityException e) {
ex = e;
// fall through
}
}
errorBranch.enter();
// There is no valid function to invoke. Do not throw if the invocation
// is optional, short-circuit instead.
if (getParent() instanceof InvokeNode invokeNode && invokeNode.getFunctionTargetDelegate() instanceof ShortCircuitTargetableNode) {
throw ShortCircuitException.instance();
}
throw Errors.createTypeErrorInteropException(receiver, ex != null ? ex : UnknownIdentifierException.create(Strings.toJavaString(functionName)), "invokeMember", functionName, this);
}
private Object maybeGetFromPrototype(Object receiver) {
if (foreignObjectPrototypeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
foreignObjectPrototypeNode = insert(ForeignObjectPrototypeNode.create());
}
JSDynamicObject prototype = foreignObjectPrototypeNode.execute(receiver);
return getFunction(prototype, receiver);
}
private Object getFunction(Object object, Object receiver) {
if (getFunctionNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getFunctionNode = insert(PropertyGetNode.create(functionName, getContext()));
}
return getFunctionNode.getValueOrUndefined(object, receiver);
}
private Object callJSFunction(Object receiver, Object function, Object[] arguments) {
if (callJSFunctionNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callJSFunctionNode = insert(JSFunctionCallNode.createCall());
}
return callJSFunctionNode.executeCall(JSArguments.create(receiver, function, JSArguments.extractUserArguments(arguments)));
}
private JSContext getContext() {
return getLanguage().getJSContext();
}
}
private static class ForeignInstantiateNode extends ForeignCallNode {
@Child protected InteropLibrary interop;
private final int skip;
ForeignInstantiateNode(int skip, int expectedArgumentCount) {
super(expectedArgumentCount);
this.skip = skip;
this.interop = InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit);
}
@Override
public Object executeCall(Object[] arguments) {
Object function = getForeignFunction(arguments);
Object[] callArguments = exportArguments(arguments, skip);
try {
return convertForeignReturn(interop.instantiate(function, callArguments));
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
throw Errors.createTypeErrorInteropException(function, e, "instantiate", this);
}
}
}
/**
* Generic case for {@link JSFunction}s.
*/
private static class GenericJSFunctionCacheNode extends AbstractCacheNode {
private final byte flags;
@Child private IndirectCallNode indirectCallNode;
@Child private AbstractCacheNode next;
private final BranchProfile initBranch;
GenericJSFunctionCacheNode(byte flags, AbstractCacheNode next) {
this.flags = flags;
this.indirectCallNode = Truffle.getRuntime().createIndirectCallNode();
this.next = next;
this.initBranch = BranchProfile.create();
megamorphicCount.inc();
}
@Override
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
JSFunctionObject functionObject = (JSFunctionObject) function;
JSFunctionData functionData = JSFunction.getFunctionData(functionObject);
if (isNewTarget(flags)) {
return indirectCallNode.call(functionData.getConstructNewTarget(initBranch), arguments);
} else if (isNew(flags)) {
return indirectCallNode.call(functionData.getConstructTarget(initBranch), arguments);
} else {
return indirectCallNode.call(functionData.getCallTarget(initBranch), arguments);
}
}
@Override
protected boolean accept(Object function) {
return JSFunction.isJSFunction(function);
}
}
private static class JSProxyInlineCacheNode extends AbstractCacheNode {
@Child private JSProxyCallNode proxyCall;
JSProxyInlineCacheNode(boolean isNew, boolean isNewTarget, JSContext context) {
this.proxyCall = JSProxyCallNode.create(context, isNew, isNewTarget);
}
@Override
public Object executeCall(Object[] arguments) {
assert accept(JSArguments.getFunctionObject(arguments));
return proxyCall.execute(arguments);
}
@Override
protected boolean accept(Object function) {
return JSProxy.isJSProxy(function);
}
}
private static class JSProxyCallCacheNode extends AbstractCacheNode {
@Child private DirectCallNode proxyCallNode;
JSProxyCallCacheNode(boolean isNew, boolean isNewTarget, JSContext context) {
JSFunctionData functionData = JSProxy.createProxyCallFunctionData(context);
CallTarget target;
if (isNewTarget) {
target = functionData.getConstructNewTarget();
} else if (isNew) {
target = functionData.getConstructTarget();
} else {
assert !isNew && !isNewTarget;
target = functionData.getCallTarget();
}
this.proxyCallNode = DirectCallNode.create(target);
}
@Override
public Object executeCall(Object[] arguments) {
assert accept(JSArguments.getFunctionObject(arguments));
return proxyCallNode.call(arguments);
}
@Override
protected boolean accept(Object function) {
return JSProxy.isJSProxy(function);
}
}
private static class JSNoSuchMethodAdapterCacheNode extends AbstractCacheNode {
@Child private JSFunctionCallNode noSuchMethodCallNode;
JSNoSuchMethodAdapterCacheNode() {
this.noSuchMethodCallNode = JSFunctionCallNode.createCall();
}
@Override
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
assert accept(function);
JSNoSuchMethodAdapter noSuchMethod = (JSNoSuchMethodAdapter) function;
Object[] handlerArguments = JSArguments.createInitial(noSuchMethod.getThisObject(), noSuchMethod.getFunction(), JSArguments.getUserArgumentCount(arguments) + 1);
JSArguments.setUserArgument(handlerArguments, 0, noSuchMethod.getKey());
JSArguments.setUserArguments(handlerArguments, 1, JSArguments.extractUserArguments(arguments));
return noSuchMethodCallNode.executeCall(handlerArguments);
}
@Override
protected boolean accept(Object function) {
return function instanceof JSNoSuchMethodAdapter;
}
}
/**
* Fallback (TypeError) and Java method/class/package.
*/
private static class GenericFallbackCacheNode extends AbstractCacheNode {
GenericFallbackCacheNode() {
megamorphicCount.inc();
}
@Override
protected boolean accept(Object function) {
return !JSFunction.isJSFunction(function) && !JSProxy.isJSProxy(function) &&
!(JSGuards.isForeignObject(function)) &&
!(function instanceof JSNoSuchMethodAdapter);
}
@Override
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
throw typeError(function);
}
@TruffleBoundary
private JSException typeError(Object function) {
Object expressionStr = null;
JSFunctionCallNode callNode = null;
for (Node current = this; current != null; current = current.getParent()) {
if (current instanceof JSFunctionCallNode) {
callNode = (JSFunctionCallNode) current;
break;
}
}
if (callNode != null) {
if (callNode instanceof InvokeNode) {
expressionStr = ((InvokeNode) callNode).functionTargetNode.expressionToString();
} else if (callNode instanceof CallNode) {
expressionStr = ((CallNode) callNode).functionNode.expressionToString();
}
}
return Errors.createTypeErrorNotAFunction(expressionStr != null ? expressionStr : function, this);
}
}
private static class Uncached extends JSFunctionCallNode {
static final Uncached CALL = new Uncached(createFlags(false, false));
static final Uncached NEW = new Uncached(createFlags(true, false));
protected Uncached(byte flags) {
super(flags);
}
@Override
public Object execute(VirtualFrame frame) {
throw Errors.shouldNotReachHere();
}
@Override
public Object executeCall(Object[] arguments) {
Object functionObject = JSArguments.getFunctionObject(arguments);
Object[] functionArgs = JSArguments.extractUserArguments(arguments);
if (isNew()) {
return JSRuntime.construct(functionObject, functionArgs);
} else {
return JSRuntime.call(functionObject, JSArguments.getThisObject(arguments), functionArgs);
}
}
@Override
protected JavaScriptNode copyUninitialized(Set> materializedTags) {
return this;
}
}
}