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 JSArrayFirstElementIndexNode extends JSArrayElementIndexNode {
protected JSArrayFirstElementIndexNode(JSContext context) {
super(context);
}
public static JSArrayFirstElementIndexNode create(JSContext context) {
return JSArrayFirstElementIndexNodeGen.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.firstElementIndex(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).firstElementIndex(object);
}
@Specialization(guards = {"isArray", "!hasPrototypeElements(object)", "getArrayType(object) == cachedArrayType",
"cachedArrayType.hasHoles(object)"}, limit = "MAX_CACHED_ARRAY_TYPES")
public long doWithHolesCached(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("getArrayTypeIfArray(object, isArray)") ScriptArray cachedArrayType,
@Cached("create(context)") JSArrayNextElementIndexNode nextElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isZero) {
assert isSupportedArray(object) && cachedArrayType == getArrayType(object);
return holesArrayImpl(object, length, cachedArrayType, nextElementIndexNode, isZero);
}
@Specialization(guards = {"isArray", "hasPrototypeElements(object) || hasHoles(object)"}, replaces = "doWithHolesCached")
public long doWithHolesUncached(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create(context)") JSArrayNextElementIndexNode nextElementIndexNode,
@Cached("createBinaryProfile()") ConditionProfile isZero,
@Cached("createClassProfile()") ValueProfile arrayTypeProfile) {
assert isSupportedArray(object);
ScriptArray array = arrayTypeProfile.profile(getArrayType(object));
return holesArrayImpl(object, length, array, nextElementIndexNode, isZero);
}
private long holesArrayImpl(DynamicObject object, long length, ScriptArray array,
JSArrayNextElementIndexNode nextElementIndexNode, ConditionProfile isZero) {
long firstIndex = array.firstElementIndex(object);
if (isZero.profile(firstIndex == 0)) {
return firstIndex;
}
DynamicObject prototype = object;
while (prototype != Null.instance) {
long firstProtoIndex = nextElementIndexNode.executeLong(prototype, -1, length);
if (firstProtoIndex == 0) {
return 0;
}
if (firstIndex > 0) {
firstIndex = Math.min(firstIndex, firstProtoIndex);
}
if (!context.getArrayPrototypeNoElementsAssumption().isValid()) {
prototype = JSObject.getPrototype(prototype);
} else {
break;
}
}
return firstIndex;
}
@Specialization(guards = {"!isArray", "isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)"})
public long firstObjectViaEnumeration(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
if (hasPropertyNode.executeBoolean(object, 0)) {
return 0;
}
return firstObjectViaEnumerationIntl(object, length);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessingUsingOwnKeys(object, length)", "isSuitableForEnumBasedProcessing(object, length)"})
public long firstObjectViaFullEnumeration(DynamicObject object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
if (hasPropertyNode.executeBoolean(object, 0)) {
return 0;
}
return firstObjectViaFullEnumerationIntl(object, length);
}
@Specialization(guards = {"!isArray", "!isSuitableForEnumBasedProcessing(object, length)"})
public long doObject(Object object, long length, @SuppressWarnings("unused") boolean isArray,
@Cached("create()") JSHasPropertyNode hasPropertyNode) {
long index = 0;
while (!hasPropertyNode.executeBoolean(object, index) && index <= (length - 1)) {
index++;
}
return index;
}
@TruffleBoundary
private static long firstObjectViaEnumerationIntl(DynamicObject object, 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 >= 0 && candidate < result) {
result = candidate;
}
}
}
return result;
}
@TruffleBoundary
private static long firstObjectViaFullEnumerationIntl(DynamicObject object, long length) {
long result = Long.MAX_VALUE;
DynamicObject chainObject = object;
do {
result = Math.min(result, firstObjectViaEnumerationIntl(chainObject, length));
chainObject = JSObject.getPrototype(chainObject);
} while (chainObject != Null.instance);
return result;
}
}