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.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage.LanguageReference;
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.InteropLibrary;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
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.NodeCost;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ValueProfile;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
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.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertyNode;
import com.oracle.truffle.js.nodes.access.SuperPropertyReferenceNode;
import com.oracle.truffle.js.nodes.cast.JSToObjectNode;
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.JavaScriptFunctionCallNode;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSShape;
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;
}
public static JSFunctionCallNode createCall() {
return create(false);
}
public static JSFunctionCallNode createNew() {
return create(true);
}
public static JSFunctionCallNode createNewTarget() {
return create(false, 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);
}
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 abstract Object getPropertyKey();
@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;
}
public static JSFunctionCallNode createInternalCall(JavaScriptNode[] arguments) {
return createCall(arguments[0], arguments[1], Arrays.copyOfRange(arguments, 2, arguments.length), false, false);
}
@ExplodeLoop
public final 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) {
if (cachedCount < JavaScriptLanguage.getCurrentJSRealm().getContext().getFunctionCacheLimit() && !generic) {
if (JSFunction.isJSFunction(function)) {
c = specializeDirectCall((DynamicObject) function, currentHead);
}
}
if (c == null) {
boolean hasCached = cachedCount > 0;
if (JSFunction.isJSFunction(function)) {
c = specializeGenericFunction(currentHead, hasCached);
} else if (JSProxy.isJSProxy(function)) {
c = insertAtFront(new JSProxyCacheNode(null, JSFunctionCallNode.isNew(flags), JSFunctionCallNode.isNewTarget(flags)), currentHead);
} else if (JSGuards.isForeignObject(function)) {
c = specializeForeignCall(arguments, currentHead);
} else if (function instanceof JSNoSuchMethodAdapter) {
c = insertAtFront(new JSNoSuchMethodAdapterCacheNode(null), currentHead);
} else {
c = insertAtFront(new GenericFallbackCacheNode(null), 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 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 JSProxyCacheNode || 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(DynamicObject functionObj, AbstractCacheNode head) {
assert JSFunction.isJSFunction(functionObj);
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(DynamicObject 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) {
newNode = new FunctionDataCacheNode(functionData, ((FunctionInstanceCacheNode) obsoleteNode).callNode);
} else {
newNode = createCallableNode(functionObj, functionData, isNew(flags), isNewTarget(flags), false);
}
return replaceCached(newNode, head, obsoleteNode, previousNode);
}
}
private JSFunctionCacheNode specializeDirectCallShared(DynamicObject 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 != null && propertyKey instanceof String) {
newNode = new ForeignInvokeNode((String) propertyKey, userArgumentCount);
}
}
if (newNode == null) {
newNode = new ForeignExecuteNode(userArgumentCount);
}
return insertAtFront(newNode, head);
}
private <T extends AbstractCacheNode> T insertAtFront(T newNode, AbstractCacheNode head) {
insert(newNode);
newNode.nextNode = head;
this.cacheNode = newNode;
return newNode;
}
@SuppressWarnings("unused")
private <T extends AbstractCacheNode> 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 NodeCost getCost() {
if (cacheNode == null) {
return NodeCost.UNINITIALIZED;
} else if (isGeneric(cacheNode)) {
return NodeCost.MEGAMORPHIC;
} else if (cacheNode.nextNode != null) {
return NodeCost.POLYMORPHIC;
} else {
return NodeCost.MONOMORPHIC;
}
}
@Override
public abstract JavaScriptNode getTarget();
protected final Object evaluateReceiver(VirtualFrame frame, Object target) {
JavaScriptNode targetNode = JSNodeUtil.getWrappedNode(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, BranchProfile growProfile) {
SimpleArrayList<Object> 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, growProfile);
} else {
argList.add(arguments[i].execute(frame), growProfile);
}
}
return argList.toArray();
}
abstract static class CallNode extends JSFunctionCallNode {
@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 Object execute(VirtualFrame frame) {
Object target = executeTarget(frame);
Object receiver = evaluateReceiver(frame, target);
Object function = functionNode.execute(frame);
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<Class<? extends Tag>> materializedTags) {
if (materializedTags.contains(FunctionCallTag.class)) {
materializeInstrumentableArguments();
if (this.hasSourceSection() && !functionNode.hasSourceSection()) {
transferSourceSectionAddExpressionTag(this, functionNode);
}
if (targetNode != null) {
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<Class<? extends Tag>> 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<Class<? extends Tag>> 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<Class<? extends Tag>> 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 {
private final BranchProfile growProfile = BranchProfile.create();
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, growProfile);
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return new CallSpreadNode(cloneUninitialized(targetNode, materializedTags), cloneUninitialized(functionNode, materializedTags), cloneUninitialized(arguments, materializedTags), flags);
}
}
public abstract static class InvokeNode extends JSFunctionCallNode {
@Child protected JavaScriptNode targetNode;
@Child protected JSTargetableNode functionTargetNode;
protected InvokeNode(JSTargetableNode functionTargetNode, byte flags) {
super(flags);
this.functionTargetNode = functionTargetNode;
}
protected InvokeNode(JavaScriptNode targetNode, JSTargetableNode functionTargetNode, byte flags) {
super(flags);
this.targetNode = targetNode;
this.functionTargetNode = functionTargetNode;
}
@Override
public 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 Object execute(VirtualFrame frame) {
Object target = executeTarget(frame);
Object receiver = evaluateReceiver(frame, target);
Object function = executeFunctionWithTarget(frame, target);
return executeCall(createArguments(frame, receiver, function));
}
protected 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<Class<? extends Tag>> materializedTags) {
if (targetNode != null) {
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 = functionTargetNode;
if (propertyNode instanceof WrapperNode) {
propertyNode = (JavaScriptNode) ((WrapperNode) propertyNode).getDelegateNode();
}
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<Class<? extends Tag>> 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<Class<? extends Tag>> 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<Class<? extends Tag>> 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 {
private final BranchProfile growProfile = BranchProfile.create();
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, growProfile);
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> 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 final JavaScriptNode getTarget() {
return null;
}
@Override
public Object execute(VirtualFrame frame) {
throw Errors.shouldNotReachHere();
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return new ExecuteCallNode(flags);
}
@Override
protected Object getPropertyKey() {
return null;
}
}
protected static JSFunctionCacheNode createCallableNode(DynamicObject function, JSFunctionData functionData, boolean isNew, boolean isNewTarget, boolean cacheOnInstance) {
CallTarget callTarget = getCallTarget(functionData, isNew, isNewTarget);
assert callTarget != null;
if (JSFunction.isBoundFunction(function)) {
if (cacheOnInstance) {
return new BoundFunctionInstanceCallNode(function, 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 {
return new FunctionDataCacheNode(functionData, callTarget);
}
}
}
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(DynamicObject 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 UnboundJSFunctionCacheNode {
private final DynamicObject functionObj;
FunctionInstanceCacheNode(DynamicObject functionObj, CallTarget callTarget) {
super(callTarget);
assert JSFunction.isJSFunction(functionObj);
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 FunctionDataCacheNode extends UnboundJSFunctionCacheNode {
private final JSFunctionData functionData;
FunctionDataCacheNode(JSFunctionData functionData, CallTarget callTarget) {
super(callTarget);
this.functionData = functionData;
}
FunctionDataCacheNode(JSFunctionData functionData, DirectCallNode directCallNode) {
super(directCallNode);
this.functionData = functionData;
}
@Override
protected boolean accept(Object function) {
return JSFunction.isJSFunction(function) && functionData == JSFunction.getFunctionData((DynamicObject) function);
}
@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;
}
@Override
public final NodeCost getCost() {
return NodeCost.NONE;
}
}
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 DynamicObject boundFunctionObj;
private final Object boundThis;
private final DynamicObject targetFunctionObj;
private final Object[] addArguments;
private final boolean useDynamicThis;
private final boolean isNewTarget;
BoundFunctionInstanceCallNode(DynamicObject function, boolean isNew, boolean isNewTarget) {
super();
assert JSFunction.isBoundFunction(function);
this.boundFunctionObj = function;
Object lastReceiver;
DynamicObject lastFunction = function;
List<Object> prefixArguments = new ArrayList<>();
do {
Object[] extraArguments = JSFunction.getBoundArguments(lastFunction);
prefixArguments.addAll(0, Arrays.asList(extraArguments));
lastReceiver = JSFunction.getBoundThis(lastFunction);
lastFunction = JSFunction.getBoundTargetFunction(lastFunction);
} while (JSFunction.isBoundFunction(lastFunction) && !isNewTarget);
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;
this.boundNode = createCallableNode(lastFunction, JSFunction.getFunctionData(lastFunction), isNew, isNewTarget, true);
}
@Override
public Object executeCall(Object[] arguments) {
assert checkTargetFunction(arguments);
return boundNode.executeCall(bindExtraArguments(arguments));
}
private Object[] (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) {
DynamicObject targetFunction = (DynamicObject) JSArguments.getFunctionObject(arguments);
while (JSFunction.isBoundFunction(targetFunction)) {
targetFunction = JSFunction.getBoundTargetFunction(targetFunction);
if (isNewTarget) {
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[] (Object[] origArgs) {
DynamicObject function = (DynamicObject) JSArguments.getFunctionObject(origArgs);
if (!JSFunction.isBoundFunction(function)) {
throw Errors.shouldNotReachHere();
}
DynamicObject boundTargetFunction = JSFunction.getBoundTargetFunction(function);
Object boundThis = useDynamicThis ? JSArguments.getThisObject(origArgs) : JSFunction.getBoundThis(function);
Object[] boundArguments = JSFunction.getBoundArguments(function);
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((DynamicObject) function);
}
@Override
protected JSFunctionData getFunctionData() {
return boundFunctionData;
}
}
private abstract static class UnboundJSFunctionCacheNode extends JSFunctionCacheNode {
@Child DirectCallNode callNode;
UnboundJSFunctionCacheNode(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();
}
}
}
UnboundJSFunctionCacheNode(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) {
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 DynamicObject functionObj;
InlinedBuiltinFunctionInstanceCacheNode(DynamicObject functionObj, CallTarget callTarget, JSBuiltinNode.Inlined builtinNode) {
super(callTarget, builtinNode);
assert JSFunction.isJSFunction(functionObj);
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((DynamicObject) 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 = functionData.getContext().getRealm();
JavaScriptNode prev = realm.getCallNode();
try {
realm.setCallNode((JavaScriptNode) getParent());
return callNode.call(arguments);
} finally {
realm.setCallNode(prev);
}
}
@Override
protected final JSFunctionData getFunctionData() {
return functionData;
}
}
private static final class CallerSensitiveBuiltinFunctionInstanceCacheNode extends CallerSensitiveBuiltinCallNode {
private final DynamicObject functionObj;
CallerSensitiveBuiltinFunctionInstanceCacheNode(DynamicObject functionObj, JSFunctionData functionData, CallTarget callTarget) {
super(functionData, callTarget);
assert JSFunction.isJSFunction(functionObj);
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((DynamicObject) 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 String functionName;
private final ValueProfile thisClassProfile = ValueProfile.createClassProfile();
@Child protected Node invokeNode;
@Child private JSToObjectNode toObjectNode;
@Child private ForeignObjectPrototypeNode foreignObjectPrototypeNode;
@Child protected JSFunctionCallNode callJSFunctionNode;
@Child protected PropertyGetNode getFunctionNode;
@CompilationFinal private LanguageReference<JavaScriptLanguage> languageRef;
private final BranchProfile errorBranch = BranchProfile.create();
@CompilationFinal private boolean optimistic = true;
ForeignInvokeNode(String functionName, int expectedArgumentCount) {
super(expectedArgumentCount);
this.functionName = functionName;
}
@Override
public Object executeCall(Object[] arguments) {
Object receiver = thisClassProfile.profile(JSArguments.getThisObject(arguments));
Object[] callArguments = exportArguments(arguments);
Object callReturn;
if (JSGuards.isForeignObject(receiver)) {
assert JSArguments.getFunctionObject(arguments) == receiver;
if (interop.isNull(receiver)) {
errorBranch.enter();
throw Errors.createTypeErrorCannotGetProperty(getContext(), functionName, receiver, false, this);
}
Object object = toObject(receiver);
if (object != receiver) {
assert JSDynamicObject.isJSDynamicObject(object);
return callJSFunction(receiver, getFunction(object), arguments);
}
if (optimistic) {
try {
callReturn = interop.invokeMember(receiver, functionName, callArguments);
} catch (UnknownIdentifierException | UnsupportedMessageException e) {
if (getContext().getContextOptions().hasForeignObjectPrototype()) {
CompilerDirectives.transferToInterpreterAndInvalidate();
optimistic = false;
callReturn = maybeInvokeFromPrototype(arguments, receiver);
} else {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, e, "invokeMember", functionName, this);
}
} catch (UnsupportedTypeException | ArityException e) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, e, "invokeMember", functionName, this);
}
} else {
if (interop.isMemberInvocable(receiver, functionName)) {
try {
callReturn = interop.invokeMember(receiver, functionName, callArguments);
} catch (UnknownIdentifierException | UnsupportedMessageException | UnsupportedTypeException | ArityException e) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, e, "invokeMember", functionName, this);
}
} else {
callReturn = maybeInvokeFromPrototype(arguments, receiver);
}
}
} 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);
}
private Object maybeInvokeFromPrototype(Object[] arguments, Object receiver) {
if (foreignObjectPrototypeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
foreignObjectPrototypeNode = insert(ForeignObjectPrototypeNode.create());
}
DynamicObject prototype = foreignObjectPrototypeNode.executeDynamicObject(receiver);
Object function = getFunction(prototype);
if (function == Undefined.instance) {
errorBranch.enter();
throw Errors.createTypeErrorInteropException(receiver, UnknownIdentifierException.create(functionName), "invokeMember", functionName, this);
}
return callJSFunction(receiver, function, arguments);
}
private Object getFunction(Object object) {
if (getFunctionNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getFunctionNode = insert(PropertyGetNode.create(functionName, getContext()));
}
return getFunctionNode.getValue(object);
}
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 Object toObject(Object object) {
if (toObjectNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toObjectNode = insert(JSToObjectNode.createToObject(getContext()));
}
return toObjectNode.execute(object);
}
private JSContext getContext() {
if (languageRef == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
languageRef = lookupLanguageReference(JavaScriptLanguage.class);
}
return languageRef.get().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);
}
}
}
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);
DynamicObject functionObject = (DynamicObject) 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 JSProxyCacheNode extends AbstractCacheNode {
@Child private JSProxyCallNode proxyCall;
@Child private AbstractCacheNode next;
private final boolean isNew;
private final boolean isNewTarget;
JSProxyCacheNode(AbstractCacheNode next, boolean isNew, boolean isNewTarget) {
this.next = next;
this.isNew = isNew;
this.isNewTarget = isNewTarget;
}
@Override
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
if (JSProxy.isJSProxy(function)) {
if (proxyCall == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
JSContext context = JSShape.getJSContext(((DynamicObject) function).getShape());
proxyCall = insert(JSProxyCallNode.create(context, isNew, isNewTarget));
}
return proxyCall.execute(arguments);
} else {
return next.executeCall(arguments);
}
}
@Override
protected boolean accept(Object function) {
return JSProxy.isJSProxy(function);
}
}
private static class JSNoSuchMethodAdapterCacheNode extends AbstractCacheNode {
@Child private JSFunctionCallNode noSuchMethodCallNode;
@Child private AbstractCacheNode next;
JSNoSuchMethodAdapterCacheNode(AbstractCacheNode next) {
this.noSuchMethodCallNode = JSFunctionCallNode.createCall();
this.next = next;
}
@Override
public Object executeCall(Object[] arguments) {
Object function = JSArguments.getFunctionObject(arguments);
if (function instanceof JSNoSuchMethodAdapter) {
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);
} else {
return next.executeCall(arguments);
}
}
@Override
protected boolean accept(Object function) {
return function instanceof JSNoSuchMethodAdapter;
}
}
private static class GenericFallbackCacheNode extends AbstractCacheNode {
@Child private AbstractCacheNode next;
GenericFallbackCacheNode(AbstractCacheNode next) {
this.next = next;
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);
}
}
}