package com.oracle.truffle.js.nodes.access;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.ValueType;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.profiles.LoopConditionProfile;
import com.oracle.truffle.js.builtins.ArrayPrototypeBuiltins.BasicArrayOperation;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.array.JSArrayFirstElementIndexNode;
import com.oracle.truffle.js.nodes.array.JSArrayLastElementIndexNode;
import com.oracle.truffle.js.nodes.array.JSArrayNextElementIndexNode;
import com.oracle.truffle.js.nodes.array.JSArrayPreviousElementIndexNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferView;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.JSClassProfile;
public abstract class ForEachIndexCallNode extends JavaScriptBaseNode {
public abstract static class CallbackNode extends JavaScriptBaseNode {
public abstract Object apply(long index, Object value, Object target, Object callback, Object callbackThisArg, Object currentResult);
}
@ValueType
public static final class MaybeResult<T> {
private final T result;
private final boolean resultPresent;
public MaybeResult(T result, boolean resultPresent) {
this.result = result;
this.resultPresent = resultPresent;
}
public static <T> MaybeResult<T> returnResult(T result) {
return new MaybeResult<>(result, true);
}
public static <T> MaybeResult<T> continueResult(T result) {
return new MaybeResult<>(result, false);
}
public boolean isPresent() {
return resultPresent;
}
public T get() {
return result;
}
}
public abstract static class MaybeResultNode extends JavaScriptBaseNode {
public abstract MaybeResult<Object> apply(long index, Object value, Object callbackResult, Object currentResult);
}
@Child private IsArrayNode isArrayNode = IsArrayNode.createIsAnyArray();
protected final JSClassProfile targetClassProfile = JSClassProfile.create();
protected final LoopConditionProfile loopCond = LoopConditionProfile.createCountingProfile();
@Child private CallbackNode callbackNode;
@Child protected MaybeResultNode maybeResultNode;
@Child private ReadElementNode.ReadElementArrayDispatchNode readElementNode;
@Child private JSArrayFirstElementIndexNode firstElementIndexNode;
@Child private JSArrayLastElementIndexNode lastElementIndexNode;
@Child private JSHasPropertyNode hasPropertyNode;
@Child private ImportValueNode toJSTypeNode;
@Child private InteropLibrary interop;
protected final JSContext context;
protected ForEachIndexCallNode(JSContext context, CallbackNode callbackArgumentsNode, MaybeResultNode maybeResultNode) {
this.callbackNode = callbackArgumentsNode;
this.maybeResultNode = maybeResultNode;
this.context = context;
this.readElementNode = ReadElementNode.ReadElementArrayDispatchNode.create();
}
public static ForEachIndexCallNode create(JSContext context, CallbackNode callbackArgumentsNode, MaybeResultNode maybeResultNode, boolean forward) {
if (forward) {
return new ForwardForEachIndexCallNode(context, callbackArgumentsNode, maybeResultNode);
} else {
return new BackwardForEachIndexCallNode(context, callbackArgumentsNode, maybeResultNode);
}
}
public final Object executeForEachIndex(Object target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult) {
boolean isArray = isArrayNode.execute(target);
if (isArray && context.getArrayPrototypeNoElementsAssumption().isValid()) {
return executeForEachIndexFast((DynamicObject) target, callback, callbackThisArg, fromIndex, length, initialResult);
} else {
return executeForEachIndexSlow(target, callback, callbackThisArg, fromIndex, length, initialResult);
}
}
protected abstract Object executeForEachIndexFast(DynamicObject target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult);
protected abstract Object executeForEachIndexSlow(Object target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult);
protected final long firstElementIndex(DynamicObject target, long length) {
if (firstElementIndexNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
firstElementIndexNode = insert(JSArrayFirstElementIndexNode.create(context));
}
return firstElementIndexNode.executeLong(target, length);
}
protected final long lastElementIndex(DynamicObject target, long length) {
if (lastElementIndexNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
lastElementIndexNode = insert(JSArrayLastElementIndexNode.create(context));
}
return lastElementIndexNode.executeLong(target, length);
}
protected final InteropLibrary getInterop() {
if (interop == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
interop = insert(InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit));
}
return interop;
}
protected Object foreignRead(Object target, long index) {
if (toJSTypeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toJSTypeNode = insert(ImportValueNode.create());
}
return JSInteropUtil.readArrayElementOrDefault(target, index, Undefined.instance, getInterop(), toJSTypeNode, this);
}
protected Object getElement(Object target, long index, boolean isForeign) {
if (!isForeign) {
assert JSDynamicObject.isJSDynamicObject(target);
return JSObject.get((DynamicObject) target, index, targetClassProfile);
} else {
return foreignRead(target, index);
}
}
protected final void checkHasDetachedBuffer(Object view) {
if (!context.getTypedArrayNotDetachedAssumption().isValid() && JSArrayBufferView.isJSArrayBufferView(view) && JSArrayBufferView.hasDetachedBuffer((DynamicObject) view)) {
throw Errors.createTypeErrorDetachedBuffer();
}
}
protected final Object callback(long index, Object value, Object target, Object callback, Object callbackThisArg, Object currentResult) {
if (callbackNode == null) {
return callbackThisArg;
} else {
return callbackNode.apply(index, value, target, callback, callbackThisArg, currentResult);
}
}
protected final Object readElementInBounds(DynamicObject target, long index) {
return readElementNode.executeArrayGet(target, JSObject.getArray(target), index, target, Undefined.instance, context);
}
protected final boolean hasProperty(Object target, long index) {
if (hasPropertyNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
hasPropertyNode = insert(JSHasPropertyNode.create());
}
return hasPropertyNode.executeBoolean(target, index);
}
protected static final class ForwardForEachIndexCallNode extends ForEachIndexCallNode {
private final ConditionProfile fromIndexZero = ConditionProfile.createBinaryProfile();
@Child private JSArrayNextElementIndexNode nextElementIndexNode;
public ForwardForEachIndexCallNode(JSContext context, CallbackNode callbackArgumentsNode, MaybeResultNode maybeResultNode) {
super(context, callbackArgumentsNode, maybeResultNode);
}
@Override
protected Object executeForEachIndexFast(DynamicObject target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult) {
long index = fromIndexZero.profile(fromIndex == 0) ? firstElementIndex(target, length) : nextElementIndex(target, fromIndex - 1, length);
Object currentResult = initialResult;
long count = 0;
while (loopCond.profile(index < length && index <= lastElementIndex(target, length))) {
Object value = readElementInBounds(target, index);
Object callbackResult = callback(index, value, target, callback, callbackThisArg, currentResult);
MaybeResult<Object> maybeResult = maybeResultNode.apply(index, value, callbackResult, currentResult);
checkHasDetachedBuffer(target);
currentResult = maybeResult.get();
if (maybeResult.isPresent()) {
break;
}
count++;
index = nextElementIndex(target, index, length);
}
BasicArrayOperation.reportLoopCount(this, count);
return currentResult;
}
@Override
protected Object executeForEachIndexSlow(Object target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult) {
Object currentResult = initialResult;
boolean isForeign = JSRuntime.isForeignObject(target);
if (isForeign) {
if (!getInterop().hasArrayElements(target)) {
return currentResult;
}
}
for (long index = fromIndex; index < length; index++) {
if (hasProperty(target, index)) {
Object value = getElement(target, index, isForeign);
Object callbackResult = callback(index, value, target, callback, callbackThisArg, currentResult);
MaybeResult<Object> maybeResult = maybeResultNode.apply(index, value, callbackResult, currentResult);
checkHasDetachedBuffer(target);
currentResult = maybeResult.get();
if (maybeResult.isPresent()) {
break;
}
}
}
BasicArrayOperation.reportLoopCount(this, length - fromIndex);
return currentResult;
}
private long nextElementIndex(DynamicObject target, long currentIndex, long length) {
if (nextElementIndexNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
nextElementIndexNode = insert(JSArrayNextElementIndexNode.create(context));
}
return nextElementIndexNode.executeLong(target, currentIndex, length);
}
}
protected static final class BackwardForEachIndexCallNode extends ForEachIndexCallNode {
@Child protected JSArrayPreviousElementIndexNode previousElementIndexNode;
public BackwardForEachIndexCallNode(JSContext context, CallbackNode callbackArgumentsNode, MaybeResultNode maybeResultNode) {
super(context, callbackArgumentsNode, maybeResultNode);
}
@Override
protected Object executeForEachIndexFast(DynamicObject target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult) {
assert fromIndex < length;
long index = previousElementIndex(target, fromIndex + 1);
Object currentResult = initialResult;
long count = 0;
while (loopCond.profile(index >= 0 && index >= firstElementIndex(target, length))) {
Object value = readElementInBounds(target, index);
Object callbackResult = callback(index, value, target, callback, callbackThisArg, currentResult);
MaybeResult<Object> maybeResult = maybeResultNode.apply(index, value, callbackResult, currentResult);
checkHasDetachedBuffer(target);
currentResult = maybeResult.get();
if (maybeResult.isPresent()) {
break;
}
count++;
index = previousElementIndex(target, index);
}
BasicArrayOperation.reportLoopCount(this, count);
return currentResult;
}
@Override
protected Object executeForEachIndexSlow(Object target, Object callback, Object callbackThisArg, long fromIndex, long length, Object initialResult) {
Object currentResult = initialResult;
boolean isForeign = JSRuntime.isForeignObject(target);
if (isForeign) {
if (!getInterop().hasArrayElements(target)) {
return currentResult;
}
}
for (long index = fromIndex; index >= 0; index--) {
if (hasProperty(target, index)) {
Object value = getElement(target, index, isForeign);
Object callbackResult = callback(index, value, target, callback, callbackThisArg, currentResult);
MaybeResult<Object> maybeResult = maybeResultNode.apply(index, value, callbackResult, currentResult);
checkHasDetachedBuffer(target);
currentResult = maybeResult.get();
if (maybeResult.isPresent()) {
break;
}
}
}
BasicArrayOperation.reportLoopCount(this, fromIndex);
return currentResult;
}
private long previousElementIndex(DynamicObject target, long currentIndex) {
if (previousElementIndexNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
previousElementIndexNode = insert(JSArrayPreviousElementIndexNode.create(context));
}
return previousElementIndexNode.executeLong(target, currentIndex);
}
}
}