package com.oracle.truffle.js.nodes.array;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.profiles.ValueProfile;
import com.oracle.truffle.js.nodes.access.JSHasPropertyNode;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.array.ScriptArray;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.Null;
public abstract class JSArrayNextElementIndexNode extends JSArrayElementIndexNode {
protected JSArrayNextElementIndexNode(JSContext context) {
super(context);
}
public static JSArrayNextElementIndexNode create(JSContext context) {
return JSArrayNextElementIndexNodeGen.create(context);
}
public final long executeLong(Object object, long currentIndex, long length) {
return executeLong(object, currentIndex, length, isArray(object));
}
public abstract long executeLong(Object object, long currentIndex, long length, boolean isArray);
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "getArrayType(object) == cachedArrayType",
"!cachedArrayType.hasHoles(object)"}, limit = "MAX_CACHED_ARRAY_TYPES")
public long doWithoutHolesCached(DynamicObject object, long currentIndex, @SuppressWarnings("unused") long length, @SuppressWarnings("unused") boolean isArray,
@Cached("getArrayTypeIfArray(object, isArray)") ScriptArray cachedArrayType) {
assert isSupportedArray(object) && cachedArrayType == getArrayType(object);
return cachedArrayType.nextElementIndex(object, currentIndex);
}
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "!hasHoles(object)"}, replaces = "doWithoutHolesCached")
public long doWithoutHolesUncached(DynamicObject object, long currentIndex, @SuppressWarnings("unused") long length, @SuppressWarnings("unused") boolean isArray) {
assert isSupportedArray(object);
return getArrayType(object).nextElementIndex(object, currentIndex);
}
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "getArrayType(object) == cachedArrayType",
"cachedArrayType.hasHoles(object)"}, limit = "MAX_CACHED_ARRAY_TYPES")
public long nextWithHolesCached(DynamicObject object, long currentIndex, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("getArrayTypeIfArray(object, isArray)") ScriptArray cachedArrayType,
@Cached("create(context)") JSArrayNextElementIndexNode nextElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isPlusOne) {
assert isSupportedArray(object) && cachedArrayType == getArrayType(object);
return holesArrayImpl(object, currentIndex, length, cachedArrayType, nextElementIndexNode, isPlusOne);
}
@Specialization(guards = {"isArray", "hasPrototypeElements(object) || hasHoles(object)"}, replaces = "nextWithHolesCached")
public long nextWithHolesUncached(DynamicObject object, long currentIndex, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create(context)") JSArrayNextElementIndexNode nextElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isPlusOne,
@Cached("createClassProfile()") ValueProfile arrayTypeProfile) {
assert isSupportedArray(object);
ScriptArray arrayType = arrayTypeProfile.profile(getArrayType(object));
return holesArrayImpl(object, currentIndex, length, arrayType, nextElementIndexNode, isPlusOne);
}
private long holesArrayImpl(DynamicObject object, long currentIndex, long length, ScriptArray array, JSArrayNextElementIndexNode nextElementIndexNode,
ConditionProfile isPlusOne) {
long nextIndex = array.nextElementIndex(object, currentIndex);
long plusOne = currentIndex + 1;
if (isPlusOne.profile(nextIndex == plusOne)) {
return nextIndex;
}
if (!context.getArrayPrototypeNoElementsAssumption().isValid()) {
DynamicObject prototype = JSObject.getPrototype(object);
while (prototype != Null.instance) {
long candidate = nextElementIndexNode.executeLong(prototype, currentIndex, length);
if (plusOne <= candidate && candidate < length) {
nextIndex = Math.min(nextIndex, candidate);
}
prototype = JSObject.getPrototype(prototype);
}
}
return nextIndex;
}
@Specialization(guards = {"!isArray", "isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)"})
public long nextObjectViaEnumeration(DynamicObject object, long currentIndex, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long currentIndexPlusOne = currentIndex + 1;
if (hasPropertyNode.executeBoolean(object, currentIndexPlusOne)) {
return currentIndexPlusOne;
}
return nextObjectViaEnumerationIntl(object, currentIndex, length);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)", "isSuitableForEnumBasedProcessing(object, length)"})
public long nextObjectViaFullEnumeration(DynamicObject object, long currentIndex, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long currentIndexPlusOne = currentIndex + 1;
if (hasPropertyNode.executeBoolean(object, currentIndexPlusOne)) {
return currentIndexPlusOne;
}
return nextObjectViaFullEnumerationIntl(object, currentIndex, length);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessing(object, length)"})
public long nextObjectViaPolling(Object object, long currentIndex, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long index = currentIndex + 1;
while (!hasPropertyNode.executeBoolean(object, index)) {
index++;
if (index >= length) {
return JSRuntime.MAX_SAFE_INTEGER_LONG;
}
}
return index;
}
@TruffleBoundary
private static long nextObjectViaEnumerationIntl(DynamicObject object, long currentIndex, long length) {
long result = length == 0 ? 1 : length;
for (Object key : JSObject.ownPropertyKeys(object)) {
if (key == null) {
continue;
}
if (key instanceof String) {
long candidate = JSRuntime.propertyNameToIntegerIndex((String) key);
if (candidate > currentIndex && candidate < result) {
result = candidate;
}
}
}
return result;
}
@TruffleBoundary
private static long nextObjectViaFullEnumerationIntl(DynamicObject object, long currentIndex, long length) {
long result = Long.MAX_VALUE;
DynamicObject chainObject = object;
do {
result = Math.min(result, nextObjectViaEnumerationIntl(chainObject, currentIndex, length));
chainObject = JSObject.getPrototype(chainObject);
} while (chainObject != Null.instance);
return result;
}
}