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 JSArrayLastElementIndexNode extends JSArrayElementIndexNode {
protected JSArrayLastElementIndexNode(JSContext context) {
super(context);
}
public static JSArrayLastElementIndexNode create(JSContext context) {
return JSArrayLastElementIndexNodeGen.create(context);
}
public final long executeLong(Object object, long length) {
return executeLong(object, length, isArray(object));
}
public abstract long executeLong(Object object, 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, @SuppressWarnings("unused") long length, @SuppressWarnings("unused") boolean isArray,
@Cached("getArrayTypeIfArray(object, isArray)") ScriptArray cachedArrayType) {
assert isSupportedArray(object) && cachedArrayType == getArrayType(object);
return cachedArrayType.lastElementIndex(object);
}
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "!hasHoles(object)"}, replaces = "doWithoutHolesCached")
public long doWithoutHolesUncached(DynamicObject object, @SuppressWarnings("unused") long length, @SuppressWarnings("unused") boolean isArray) {
assert isSupportedArray(object);
return getArrayType(object).lastElementIndex(object);
}
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "getArrayType(object) == cachedArrayType",
"cachedArrayType.hasHoles(object)"}, limit = "MAX_CACHED_ARRAY_TYPES")
public long doWithHolesCached(DynamicObject object, long length, boolean isArray,
@Cached("getArrayTypeIfArray(object, isArray)") ScriptArray cachedArrayType,
@Cached("create(context)") JSArrayPreviousElementIndexNode previousElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isLengthMinusOne) {
assert isSupportedArray(object) && cachedArrayType == getArrayType(object);
return holesArrayImpl(object, length, cachedArrayType, previousElementIndexNode, isLengthMinusOne, isArray);
}
@Specialization(guards = {"isArray", "hasPrototypeElements(object) || hasHoles(object)"}, replaces = "doWithHolesCached")
public long doWithHolesUncached(DynamicObject object, long length, boolean isArray,
@Cached("create(context)") JSArrayPreviousElementIndexNode previousElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isLengthMinusOne,
@Cached("createClassProfile()") ValueProfile arrayTypeProfile) {
assert isSupportedArray(object);
ScriptArray arrayType = arrayTypeProfile.profile(getArrayType(object));
return holesArrayImpl(object, length, arrayType, previousElementIndexNode, isLengthMinusOne, isArray);
}
private long holesArrayImpl(DynamicObject object, long length, ScriptArray array,
JSArrayPreviousElementIndexNode previousElementIndexNode, ConditionProfile isLengthMinusOne, @SuppressWarnings("unused") boolean isArray) {
long lastIndex = array.lastElementIndex(object);
if (isLengthMinusOne.profile(lastIndex == length - 1)) {
return lastIndex;
}
DynamicObject prototype = object;
while (prototype != Null.instance) {
long candidate = previousElementIndexNode.executeLong(prototype, length);
lastIndex = Math.max(lastIndex, candidate);
if (lastIndex >= (length - 1)) {
return length - 1;
}
if (!context.getArrayPrototypeNoElementsAssumption().isValid()) {
prototype = JSObject.getPrototype(prototype);
} else {
break;
}
}
return lastIndex;
}
@Specialization(guards = {"!isArray", "isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)"})
public long doObjectViaEnumeration(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long lengthMinusOne = length - 1;
if (hasPropertyNode.executeBoolean(object, lengthMinusOne)) {
return lengthMinusOne;
}
return doObjectViaEnumerationIntl(object, lengthMinusOne);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)", "isSuitableForEnumBasedProcessing(object, length)"})
public long doObjectViaFullEnumeration(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long lengthMinusOne = length - 1;
if (hasPropertyNode.executeBoolean(object, lengthMinusOne)) {
return lengthMinusOne;
}
return doObjectViaFullEnumerationIntl(object, lengthMinusOne);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessing(object, length)"})
public long doObject(Object object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long index = length - 1;
while (!hasPropertyNode.executeBoolean(object, index) && index > 0) {
index--;
}
return index;
}
@TruffleBoundary
private static long doObjectViaEnumerationIntl(DynamicObject object, long lengthMinusOne) {
long result = -1;
for (Object key : JSObject.ownPropertyKeys(object)) {
if (key == null) {
continue;
}
if (key instanceof String) {
long candidate = JSRuntime.propertyNameToIntegerIndex((String) key);
if (candidate < lengthMinusOne && candidate > result) {
result = candidate;
}
}
}
return result;
}
@TruffleBoundary
private static long doObjectViaFullEnumerationIntl(DynamicObject object, long length) {
long result = -1;
DynamicObject chainObject = object;
do {
result = Math.max(result, doObjectViaEnumerationIntl(chainObject, length));
chainObject = JSObject.getPrototype(chainObject);
} while (chainObject != Null.instance);
return result;
}
}