package com.oracle.truffle.js.nodes.array;
import static com.oracle.truffle.js.runtime.builtins.JSAbstractArray.arraySetArrayType;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.array.ArrayLengthNodeFactory.ArrayLengthReadNodeGen;
import com.oracle.truffle.js.nodes.array.ArrayLengthNodeFactory.SetArrayLengthNodeGen;
import com.oracle.truffle.js.nodes.array.ArrayLengthNodeFactory.SetArrayLengthOrDeleteNodeGen;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.array.ScriptArray;
import com.oracle.truffle.js.runtime.array.SparseArray;
import com.oracle.truffle.js.runtime.builtins.JSAbstractArray;
import com.oracle.truffle.js.runtime.objects.JSObject;
@ImportStatic(ScriptArray.class)
public abstract class ArrayLengthNode extends JavaScriptBaseNode {
protected static final int MAX_TYPE_COUNT = 4;
protected ArrayLengthNode() {
}
protected static ScriptArray getArrayType(DynamicObject target) {
return JSObject.getArray(target);
}
public abstract static class ArrayLengthReadNode extends ArrayLengthNode {
public static ArrayLengthReadNode create() {
return ArrayLengthReadNodeGen.create();
}
public abstract int executeInt(DynamicObject target) throws UnexpectedResultException;
public abstract Object executeObject(DynamicObject target);
public final double executeDouble(DynamicObject target) {
Object result = executeObject(target);
if (result instanceof Integer) {
return (int) result;
}
return (double) result;
}
@Specialization(guards = {"arrayType.isInstance(getArrayType(target))", "arrayType.isStatelessType()", "isLengthAlwaysInt(arrayType)"}, limit = "MAX_TYPE_COUNT")
protected static int doIntLength(DynamicObject target,
@Cached("getArrayType(target)") ScriptArray arrayType) {
return arrayType.lengthInt(target);
}
@Specialization(guards = {"arrayType.isInstance(getArrayType(target))", "arrayType.isStatelessType()"}, replaces = "doIntLength", limit = "MAX_TYPE_COUNT")
protected static double doLongLength(DynamicObject target,
@Cached("getArrayType(target)") ScriptArray arrayType) {
return arrayType.length(target);
}
@Specialization(replaces = {"doIntLength", "doLongLength"}, rewriteOn = UnexpectedResultException.class)
protected static int doUncachedIntLength(DynamicObject target) throws UnexpectedResultException {
long uint32Len = JSAbstractArray.arrayGetLength(target);
assert uint32Len == getArrayType(target).length(target);
if (JSRuntime.longIsRepresentableAsInt(uint32Len)) {
return (int) uint32Len;
} else {
CompilerDirectives.transferToInterpreterAndInvalidate();
throw new UnexpectedResultException((double) uint32Len);
}
}
@Specialization(replaces = {"doUncachedIntLength"})
protected static double doUncachedLongLength(DynamicObject target) {
long uint32Len = JSAbstractArray.arrayGetLength(target);
assert uint32Len == getArrayType(target).length(target);
return uint32Len;
}
protected static boolean isLengthAlwaysInt(ScriptArray arrayType) {
return !(arrayType instanceof SparseArray);
}
}
public abstract static class ArrayLengthWriteNode extends ArrayLengthNode {
public static ArrayLengthWriteNode create(boolean strict) {
return SetArrayLengthNodeGen.create(strict);
}
public static ArrayLengthWriteNode createSetOrDelete(boolean strict) {
return SetArrayLengthOrDeleteNodeGen.create(strict);
}
public abstract void executeVoid(DynamicObject array, int length);
}
public abstract static class SetArrayLengthNode extends ArrayLengthWriteNode {
private final boolean strict;
protected SetArrayLengthNode(boolean strict) {
this.strict = strict;
}
@Specialization(guards = {"arrayType.isInstance(getArrayType(arrayObj))", "arrayType.isStatelessType()"}, limit = "MAX_TYPE_COUNT")
protected void doCached(DynamicObject arrayObj, int length,
@Cached("getArrayType(arrayObj)") ScriptArray arrayType,
@Cached("createSetLengthProfile()") ScriptArray.ProfileHolder setLengthProfile) {
assert length >= 0;
if (arrayType.isSealed()) {
setLengthSealed(arrayObj, length, arrayType, setLengthProfile);
return;
}
arraySetArrayType(arrayObj, arrayType.setLength(arrayObj, length, strict, setLengthProfile));
}
@Specialization(replaces = "doCached")
protected void doGeneric(DynamicObject arrayObj, int length,
@Cached("createBinaryProfile()") ConditionProfile sealedProfile,
@Cached("createSetLengthProfile()") ScriptArray.ProfileHolder setLengthProfile) {
assert length >= 0;
ScriptArray arrayType = getArrayType(arrayObj);
if (sealedProfile.profile(arrayType.isSealed())) {
setLengthSealed(arrayObj, length, arrayType, setLengthProfile);
return;
}
arraySetArrayType(arrayObj, arrayType.setLength(arrayObj, length, strict, setLengthProfile));
}
private void setLengthSealed(DynamicObject arrayObj, int length, ScriptArray arrayType, ScriptArray.ProfileHolder setLengthProfile) {
long minLength = arrayType.lastElementIndex(arrayObj) + 1;
if (length < minLength) {
ScriptArray array = arrayType.setLength(arrayObj, minLength, strict, setLengthProfile);
arraySetArrayType(arrayObj, array);
array.canDeleteElement(arrayObj, minLength - 1, strict);
return;
}
arraySetArrayType(arrayObj, arrayType.setLength(arrayObj, length, strict, setLengthProfile));
}
}
public abstract static class SetArrayLengthOrDeleteNode extends ArrayLengthWriteNode {
private final boolean strict;
protected SetArrayLengthOrDeleteNode(boolean strict) {
this.strict = strict;
}
@Specialization(guards = {"arrayType.isInstance(getArrayType(arrayObj))", "arrayType.isStatelessType()"}, limit = "MAX_TYPE_COUNT")
protected void doCached(DynamicObject arrayObj, int length,
@Cached("getArrayType(arrayObj)") ScriptArray arrayType,
@Cached("createSetLengthProfile()") ScriptArray.ProfileHolder setLengthProfile) {
assert length >= 0;
if (arrayType.isLengthNotWritable() || arrayType.isSealed()) {
deleteAndSetLength(arrayObj, length, arrayType, setLengthProfile);
return;
}
arraySetArrayType(arrayObj, arrayType.setLength(arrayObj, length, strict, setLengthProfile));
}
@Specialization(replaces = "doCached")
protected void doGeneric(DynamicObject arrayObj, int length,
@Cached("createBinaryProfile()") ConditionProfile mustDeleteProfile,
@Cached("createSetLengthProfile()") ScriptArray.ProfileHolder setLengthProfile) {
assert length >= 0;
ScriptArray arrayType = getArrayType(arrayObj);
if (mustDeleteProfile.profile(arrayType.isLengthNotWritable() || arrayType.isSealed())) {
deleteAndSetLength(arrayObj, length, arrayType, setLengthProfile);
return;
}
arraySetArrayType(arrayObj, arrayType.setLength(arrayObj, length, strict, setLengthProfile));
}
private void deleteAndSetLength(DynamicObject arrayObj, int length, ScriptArray arrayType, ScriptArray.ProfileHolder setLengthProfile) {
ScriptArray array = arrayType;
for (int i = array.lengthInt(arrayObj) - 1; i >= length; i--) {
if (array.canDeleteElement(arrayObj, i, strict)) {
array = array.deleteElement(arrayObj, i, strict);
arraySetArrayType(arrayObj, array);
}
}
arraySetArrayType(arrayObj, array.setLength(arrayObj, length, strict, setLengthProfile));
}
}
}