package com.oracle.truffle.js.runtime.array;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
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.Truffle;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeCloneable;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.runtime.Boundaries;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.array.dyn.AbstractConstantArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantEmptyArray;
import com.oracle.truffle.js.runtime.array.dyn.ConstantObjectArray;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.Undefined;
public abstract class ScriptArray {
public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
public abstract Object getElement(DynamicObject object, long index);
public abstract Object getElementInBounds(DynamicObject object, long index);
public abstract ScriptArray setElementImpl(DynamicObject object, long index, Object value, boolean strict);
public final ScriptArray setElement(DynamicObject object, long index, Object value, boolean strict) {
if (isFrozen()) {
if (strict) {
setElementFrozenStrict(index);
}
return this;
} else if (isLengthNotWritable()) {
if (index >= length(object)) {
if (strict) {
throw Errors.createTypeErrorLengthNotWritable();
}
return this;
}
}
return setElementImpl(object, index, value, strict);
}
@TruffleBoundary
private static void setElementFrozenStrict(long index) {
JSContext context = JavaScriptLanguage.getCurrentJSRealm().getContext();
if (context.isOptionNashornCompatibilityMode()) {
throw Errors.createTypeErrorFormat("Cannot set property \"%d\" of frozen array", index);
} else {
throw Errors.createTypeErrorCannotRedefineProperty(String.valueOf(index));
}
}
public abstract ScriptArray deleteElementImpl(DynamicObject object, long index, boolean strict);
public final ScriptArray deleteElement(DynamicObject object, long index, boolean strict) {
assert canDeleteElement(object, index, strict);
return deleteElementImpl(object, index, strict);
}
public final boolean canDeleteElement(DynamicObject object, long index, boolean strict) {
if (isSealed()) {
if (hasElement(object, index)) {
if (strict) {
throw Errors.createTypeErrorCannotDeletePropertyOfSealedArray(index);
}
return false;
}
}
return true;
}
public abstract boolean hasElement(DynamicObject object, long index);
public abstract long length(DynamicObject object);
public abstract int lengthInt(DynamicObject object);
protected interface ProfileAccess {
}
protected interface SetLengthProfileAccess extends ProfileAccess {
default boolean lengthZero(ProfileHolder profile, boolean condition) {
return profile.profile(this, 0, condition);
}
default boolean lengthLess(ProfileHolder profile, boolean condition) {
return profile.profile(this, 1, condition);
}
default boolean zeroBasedSetUsedLength(ProfileHolder profile, boolean condition) {
return profile.profile(this, 2, condition);
}
default boolean zeroBasedClearUnusedArea(ProfileHolder profile, boolean condition) {
return profile.profile(this, 3, condition);
}
default boolean contiguousZeroUsed(ProfileHolder profile, boolean condition) {
return profile.profile(this, 4, condition);
}
default boolean contiguousNegativeUsed(ProfileHolder profile, boolean condition) {
return profile.profile(this, 5, condition);
}
default boolean contiguousShrinkUsed(ProfileHolder profile, boolean condition) {
return profile.profile(this, 6, condition);
}
default boolean clearUnusedArea(ProfileHolder profile, boolean condition) {
return profile.profile(this, 7, condition);
}
}
protected static final SetLengthProfileAccess SET_LENGTH_PROFILE = new SetLengthProfileAccess() {
};
public static ProfileHolder createSetLengthProfile() {
return ProfileHolder.create(8, SetLengthProfileAccess.class);
}
public abstract ScriptArray setLengthImpl(DynamicObject object, long len, ProfileHolder profile);
public final ScriptArray setLength(DynamicObject object, long len, boolean strict, ProfileHolder profile) {
if (isLengthNotWritable()) {
if (strict) {
throw Errors.createTypeErrorLengthNotWritable();
}
return this;
} else if (isSealed()) {
assert len >= lastElementIndex(object) + 1;
}
return setLengthImpl(object, len, profile);
}
public final ScriptArray setLength(DynamicObject object, long len, boolean strict) {
return setLength(object, len, strict, ProfileHolder.empty());
}
public abstract long firstElementIndex(DynamicObject object);
public abstract long lastElementIndex(DynamicObject object);
public abstract long nextElementIndex(DynamicObject object, long index);
public abstract long previousElementIndex(DynamicObject object, long index);
public boolean isInBoundsFast(DynamicObject object, long index) {
return firstElementIndex(object) <= index && index <= lastElementIndex(object);
}
public Iterable<Object> asIterable(DynamicObject object) {
return new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new DefaultIterator(object);
}
};
}
protected final class DefaultIterator implements Iterator<Object> {
private long currentIndex;
private final DynamicObject arrayObject;
public DefaultIterator(DynamicObject arrayObject) {
this.arrayObject = arrayObject;
this.currentIndex = firstElementIndex(arrayObject);
}
@Override
public void remove() {
currentIndex--;
}
@Override
public Object next() {
assert currentIndex >= firstElementIndex(arrayObject);
Object element = getElement(arrayObject, currentIndex);
currentIndex = nextElementIndex(arrayObject, currentIndex);
return element;
}
@Override
public boolean hasNext() {
assert currentIndex >= firstElementIndex(arrayObject);
return currentIndex <= lastElementIndex(arrayObject);
}
}
public abstract Object[] toArray(DynamicObject object);
public static AbstractConstantArray createConstantEmptyArray() {
return ConstantEmptyArray.createConstantEmptyArray();
}
public static AbstractConstantArray createConstantArray(Object[] elements) {
if (elements == null || elements.length == 0) {
return createConstantEmptyArray();
} else {
return ConstantObjectArray.createConstantObjectArray();
}
}
public static boolean valueIsByte(int value) {
return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE;
}
@TruffleBoundary
public String toString(DynamicObject object) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (; i < length(object); i++) {
if (i != 0) {
sb.append(",");
}
Object element = getElement(object, i);
if (element != Null.instance && element != Undefined.instance) {
sb.append(element);
}
}
if (i < length(object)) {
sb.append(",... [" + (length(object) - i + 1) + " more]");
}
return sb.toString();
}
@TruffleBoundary
protected static final void traceArrayTransition(ScriptArray oldArray, ScriptArray newArray, long index, Object value) {
String access = oldArray.getClass().getSimpleName() + " -> " + newArray.getClass().getSimpleName();
Stream<Node> nodeStream = null;
List<FrameInstance> stackTrace = new ArrayList<>();
Truffle.getRuntime().iterateFrames(frameInstance -> {
stackTrace.add(frameInstance);
return null;
});
nodeStream = StreamSupport.stream(stackTrace.spliterator(), false).filter(fi -> fi.getCallNode() != null).map(fi -> fi.getCallNode());
int stackTraceLimit = JavaScriptLanguage.getCurrentJSRealm().getContext().getContextOptions().getStackTraceLimit();
StackTraceElement[] array = nodeStream.filter(n -> n.getEncapsulatingSourceSection() != null).map(node -> {
SourceSection callNodeSourceSection = node.getEncapsulatingSourceSection();
String declaringClass = "js";
String methodName = node.getRootNode().getName();
String fileName = callNodeSourceSection.isAvailable() ? callNodeSourceSection.getSource().getName() : "<unknown>";
int startLine = callNodeSourceSection.getStartLine();
return new StackTraceElement(declaringClass, methodName != null ? methodName : "<unknown>", fileName, startLine);
}).limit(stackTraceLimit).toArray(StackTraceElement[]::new);
System.out.printf("[js] array transition %-48s |index %5s |value %-20s |caller %5s\n", access, index, value, array[0]);
}
@TruffleBoundary
protected static final void traceWrite(String access, long index, Object value) {
System.out.printf("[js] array set %-48s |index %5s |value %-20s\n", access, index, value);
}
public boolean isHolesType() {
return false;
}
public abstract boolean hasHoles(DynamicObject object);
public abstract ScriptArray removeRangeImpl(DynamicObject object, long start, long end);
public final ScriptArray removeRange(DynamicObject object, long start, long end) {
assert start >= 0 && start <= end;
if (isSealed()) {
throw Errors.createTypeErrorCannotDeletePropertyOfSealedArray(start);
}
return removeRangeImpl(object, start, end);
}
public final ScriptArray removeRange(DynamicObject object, long start, long end, BranchProfile errorBranch) {
assert start >= 0 && start <= end;
if (isSealed()) {
errorBranch.enter();
throw Errors.createTypeErrorCannotDeletePropertyOfSealedArray(start);
}
return removeRangeImpl(object, start, end);
}
public abstract ScriptArray addRangeImpl(DynamicObject object, long offset, int size);
public final ScriptArray addRange(DynamicObject object, long offset, int size) {
if (!isExtensible()) {
throw addRangeNotExtensible();
}
return addRangeImpl(object, offset, size);
}
@TruffleBoundary
private JSException addRangeNotExtensible() {
if (isFrozen()) {
throw Errors.createTypeError("Cannot add property of frozen array");
} else if (isSealed()) {
throw Errors.createTypeError("Cannot add property to sealed array");
} else {
throw Errors.createTypeError("Cannot add property to non-extensible array");
}
}
public List<Object> ownPropertyKeys(DynamicObject object) {
assert !isHolesType() || !hasHoles(object);
return ownPropertyKeysContiguous(object);
}
protected final List<Object> ownPropertyKeysContiguous(DynamicObject object) {
return makeRangeList(firstElementIndex(object), lastElementIndex(object) + 1);
}
@TruffleBoundary
protected final List<Object> ownPropertyKeysHoles(DynamicObject object) {
long currentIndex = firstElementIndex(object);
long start = currentIndex;
long end = currentIndex;
int total = 0;
List<Long> rangeList = new ArrayList<>();
while (currentIndex <= lastElementIndex(object)) {
if (currentIndex == end) {
end = currentIndex + 1;
} else {
assert end < currentIndex;
assert start < end;
total += end - start;
rangeList.add(start);
rangeList.add(end);
start = currentIndex;
end = currentIndex + 1;
}
currentIndex = nextElementIndex(object, currentIndex);
}
if (start < end) {
total += end - start;
if (rangeList.isEmpty()) {
return makeRangeList(start, end);
}
rangeList.add(start);
rangeList.add(end);
}
return makeMultiRangeList(total, toLongArray(rangeList));
}
private static long[] toLongArray(List<Long> longList) {
long[] longArray = new long[longList.size()];
for (int i = 0; i < longArray.length; i++) {
longArray[i] = longList.get(i);
}
return longArray;
}
public static List<Object> makeRangeList(final long rangeStart, final long rangeEnd) {
assert rangeEnd - rangeStart >= 0 && rangeEnd - rangeStart <= Integer.MAX_VALUE;
return new AbstractList<Object>() {
@Override
public Object get(int index) {
if (index >= 0 && rangeStart + index < rangeEnd) {
return Boundaries.stringValueOf(rangeStart + index);
} else {
throw new IndexOutOfBoundsException();
}
}
@Override
public int size() {
return (int) (rangeEnd - rangeStart);
}
};
}
protected static List<Object> makeMultiRangeList(final int total, final long[] ranges) {
return new AbstractList<Object>() {
@Override
public Object get(int index) {
if (index >= 0) {
long relativeIndex = index;
for (int rangeIndex = 0; rangeIndex < ranges.length; rangeIndex += 2) {
long rangeStart = ranges[rangeIndex];
long rangeEnd = ranges[rangeIndex + 1];
long rangeLen = rangeEnd - rangeStart;
if (relativeIndex < rangeLen) {
return Boundaries.stringValueOf(rangeStart + relativeIndex);
} else {
relativeIndex -= rangeLen;
}
}
}
throw new IndexOutOfBoundsException();
}
@Override
public int size() {
return total;
}
};
}
protected static int nextPower(int length) {
if (length < 8) {
return 8;
} else {
return nextPow2(length);
}
}
private static int nextPow2(int val) {
int x = val - 1;
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return x + 1;
}
public boolean isSealed() {
return false;
}
public boolean isFrozen() {
return false;
}
public boolean isLengthNotWritable() {
return false;
}
public boolean isExtensible() {
return true;
}
public abstract ScriptArray seal();
public abstract ScriptArray freeze();
public abstract ScriptArray setLengthNotWritable();
public abstract ScriptArray preventExtensions();
public abstract boolean isStatelessType();
public final boolean isInstance(ScriptArray other) {
CompilerAsserts.partialEvaluationConstant(this);
if (isStatelessType()) {
return this == other;
} else {
return this.getClass().isInstance(other);
}
}
public final ScriptArray cast(ScriptArray other) {
CompilerAsserts.partialEvaluationConstant(this);
if (isStatelessType()) {
assert this == other;
return this;
} else {
return this.getClass().cast(other);
}
}
public interface ProfileHolder {
boolean profile(ProfileAccess profileAccess, int index, boolean condition);
static ProfileHolder create(int profileCount, Class<?> profileAccessClass) {
return new ProfileHolderImpl(profileCount, profileAccessClass);
}
static ProfileHolder empty() {
return ProfileHolderImpl.EMPTY;
}
}
private static final class ProfileHolderImpl extends NodeCloneable implements ProfileHolder {
@CompilationFinal(dimensions = 1) private ConditionProfile[] conditionProfiles;
private Class<?> profileAccessClass;
private static final ProfileHolderImpl EMPTY = new ProfileHolderImpl();
private ProfileHolderImpl(int profileCount, Class<?> profileAccessClass) {
this.conditionProfiles = new ConditionProfile[profileCount];
this.profileAccessClass = profileAccessClass;
}
private ProfileHolderImpl() {
}
@Override
public boolean profile(ProfileAccess profileAccess, int index, boolean condition) {
assert profileAccessClass == null || profileAccessClass.isInstance(profileAccess);
ConditionProfile[] profiles = this.conditionProfiles;
if (profiles == null) {
return condition;
}
ConditionProfile profile = profiles[index];
if (profile == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
profile = profiles[index] = ConditionProfile.createBinaryProfile();
}
return profile.profile(condition);
}
}
}