package com.oracle.truffle.js.builtins;
import java.nio.ByteBuffer;
import java.util.NoSuchElementException;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
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.interop.InteropLibrary;
import com.oracle.truffle.api.library.CachedLibrary;
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.js.builtins.ArrayPrototypeBuiltins.ArraySpeciesConstructorNode;
import com.oracle.truffle.js.builtins.JSConstructTypedArrayNodeGen.IntegerIndexedObjectCreateNodeGen;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.access.GetIteratorNode;
import com.oracle.truffle.js.nodes.access.GetMethodNode;
import com.oracle.truffle.js.nodes.access.GetPrototypeFromConstructorNode;
import com.oracle.truffle.js.nodes.access.IsJSObjectNode;
import com.oracle.truffle.js.nodes.access.IteratorStepNode;
import com.oracle.truffle.js.nodes.access.IteratorValueNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.ReadElementNode;
import com.oracle.truffle.js.nodes.access.WriteElementNode;
import com.oracle.truffle.js.nodes.array.JSGetLengthNode;
import com.oracle.truffle.js.nodes.cast.JSToIndexNode;
import com.oracle.truffle.js.nodes.function.JSBuiltin;
import com.oracle.truffle.js.nodes.function.JSBuiltinNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.array.TypedArray;
import com.oracle.truffle.js.runtime.array.TypedArrayFactory;
import com.oracle.truffle.js.runtime.builtins.JSAbstractBuffer;
import com.oracle.truffle.js.runtime.builtins.JSArrayBuffer;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferView;
import com.oracle.truffle.js.runtime.builtins.JSObjectFactory;
import com.oracle.truffle.js.runtime.builtins.JSSharedArrayBuffer;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.SimpleArrayList;
@ImportStatic({JSArrayBuffer.class, JSRuntime.class, JSConfig.class})
public abstract class JSConstructTypedArrayNode extends JSBuiltinNode {
@Child private JSToIndexNode toIndexNode;
@Child private GetPrototypeFromConstructorNode getPrototypeFromConstructorViewNode;
@Child private GetPrototypeFromConstructorNode getPrototypeFromConstructorBufferNode;
@Child private IntegerIndexedObjectCreateNode integerIndexObjectCreateNode;
@Child private ArraySpeciesConstructorNode arraySpeciesConstructorNode;
private final BranchProfile errorBranch = BranchProfile.create();
private final TypedArrayFactory factory;
public JSConstructTypedArrayNode(JSContext context, JSBuiltin builtin) {
super(context, builtin);
this.factory = findTypedArrayFactory(builtin.getName(), context);
}
private static TypedArrayFactory findTypedArrayFactory(String name, JSContext context) {
for (TypedArrayFactory typedArrayFactory : TypedArray.factories(context)) {
if (typedArrayFactory.getName().equals(name)) {
return typedArrayFactory;
}
}
throw new NoSuchElementException(name);
}
private long toIndex(Object target) {
if (toIndexNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toIndexNode = insert(JSToIndexNode.create());
}
return toIndexNode.executeLong(target);
}
private DynamicObject getPrototypeFromConstructorView(DynamicObject newTarget) {
if (getPrototypeFromConstructorViewNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getPrototypeFromConstructorViewNode = insert(GetPrototypeFromConstructorNode.create(getContext(), null, realm -> realm.getArrayBufferViewPrototype(factory)));
}
return getPrototypeFromConstructorViewNode.executeWithConstructor(newTarget);
}
private DynamicObject getPrototypeFromConstructorBuffer(DynamicObject newTarget) {
if (getPrototypeFromConstructorBufferNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getPrototypeFromConstructorBufferNode = insert(GetPrototypeFromConstructorNode.create(getContext(), null, JSRealm::getArrayBufferPrototype));
}
return getPrototypeFromConstructorBufferNode.executeWithConstructor(newTarget);
}
private DynamicObject integerIndexedObjectCreate(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject proto) {
if (integerIndexObjectCreateNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
integerIndexObjectCreateNode = insert(IntegerIndexedObjectCreateNodeGen.create(getContext(), factory));
}
return integerIndexObjectCreateNode.execute(arrayBuffer, typedArray, offset, length, proto);
}
protected final ReadElementNode createReadNode() {
return ReadElementNode.create(getContext());
}
private void checkDetachedBuffer(DynamicObject buffer) {
if (!getContext().getTypedArrayNotDetachedAssumption().isValid() && JSArrayBuffer.isDetachedBuffer(buffer)) {
throw Errors.createTypeErrorDetachedBuffer();
}
}
@Specialization(guards = {"!isUndefined(newTarget)", "isJSHeapArrayBuffer(arrayBuffer)"})
protected DynamicObject doArrayBuffer(DynamicObject newTarget, DynamicObject arrayBuffer, Object byteOffset0, Object length0,
@Cached("createBinaryProfile()") ConditionProfile lengthIsUndefined) {
checkDetachedBuffer(arrayBuffer);
byte[] byteArray = JSArrayBuffer.getByteArray(arrayBuffer);
int arrayBufferLength = byteArray.length;
return doArrayBufferImpl(arrayBuffer, byteOffset0, length0, newTarget, arrayBufferLength, false, lengthIsUndefined);
}
@Specialization(guards = {"!isUndefined(newTarget)", "isJSDirectArrayBuffer(arrayBuffer)"})
protected DynamicObject doDirectArrayBuffer(DynamicObject newTarget, DynamicObject arrayBuffer, Object byteOffset0, Object length0,
@Cached("createBinaryProfile()") ConditionProfile lengthIsUndefined) {
checkDetachedBuffer(arrayBuffer);
ByteBuffer byteBuffer = JSArrayBuffer.getDirectByteBuffer(arrayBuffer);
int arrayBufferLength = byteBuffer.limit();
return doArrayBufferImpl(arrayBuffer, byteOffset0, length0, newTarget, arrayBufferLength, true, lengthIsUndefined);
}
private DynamicObject doArrayBufferImpl(DynamicObject arrayBuffer, Object byteOffset0, Object length0, DynamicObject newTarget, int bufferByteLength, boolean direct,
ConditionProfile lengthIsUndefinedProfile) {
final int elementSize = factory.getBytesPerElement();
final long byteOffset = toIndex(byteOffset0);
rangeCheckIsMultipleOfElementSize(byteOffset % elementSize == 0, "start offset", factory.getName(), elementSize);
long length = 0;
if (!lengthIsUndefinedProfile.profile(length0 == Undefined.instance)) {
length = toIndex(length0);
assert length >= 0;
}
checkDetachedBuffer(arrayBuffer);
if (lengthIsUndefinedProfile.profile(length0 == Undefined.instance)) {
rangeCheckIsMultipleOfElementSize(bufferByteLength % elementSize == 0, "buffer.byteLength", factory.getName(), elementSize);
length = ((bufferByteLength - byteOffset) / elementSize);
rangeCheck(length >= 0, "length < 0");
}
checkLengthLimit(length, elementSize);
final int byteLength = toByteLength((int) length, elementSize);
rangeCheck(byteOffset + byteLength <= bufferByteLength, "length exceeds buffer bounds");
assert byteOffset <= Integer.MAX_VALUE && length <= Integer.MAX_VALUE;
TypedArray typedArray = factory.createArrayType(direct, byteOffset != 0);
return createTypedArray(arrayBuffer, typedArray, (int) byteOffset, (int) length, newTarget);
}
@Specialization(guards = {"!isUndefined(newTarget)", "isJSSharedArrayBuffer(arrayBuffer)"})
protected DynamicObject doSharedArrayBuffer(DynamicObject newTarget, DynamicObject arrayBuffer, Object byteOffset0, Object length0,
@Cached("createBinaryProfile()") ConditionProfile lengthCondition) {
return doDirectArrayBuffer(newTarget, arrayBuffer, byteOffset0, length0, lengthCondition);
}
@SuppressWarnings("unused")
@Specialization(guards = {"!isUndefined(newTarget)", "isJSArrayBufferView(arrayBufferView)"})
protected DynamicObject doArrayBufferView(DynamicObject newTarget, DynamicObject arrayBufferView, Object byteOffset0, Object length0) {
TypedArray sourceType = JSArrayBufferView.typedArrayGetArrayType(arrayBufferView);
long length = sourceType.length(arrayBufferView);
DynamicObject srcData = JSArrayBufferView.getArrayBuffer(arrayBufferView);
DynamicObject defaultBufferConstructor = getContext().getRealm().getArrayBufferConstructor();
DynamicObject bufferConstructor;
if (JSSharedArrayBuffer.isJSSharedArrayBuffer(srcData)) {
bufferConstructor = defaultBufferConstructor;
} else {
bufferConstructor = getArraySpeciesConstructorNode().speciesConstructor(srcData, defaultBufferConstructor);
}
DynamicObject arrayBuffer = createTypedArrayBuffer(length);
JSObject.setPrototype(arrayBuffer, getPrototypeFromConstructorBuffer(bufferConstructor));
checkDetachedBuffer(srcData);
boolean elementTypeIsBig = JSRuntime.isTypedArrayBigIntFactory(factory);
boolean sourceTypeIsBig = sourceType instanceof TypedArray.TypedBigIntArray;
if (elementTypeIsBig != sourceTypeIsBig) {
throw Errors.createTypeErrorCannotMixBigIntWithOtherTypes(this);
}
TypedArray typedArray = factory.createArrayType(getContext().isOptionDirectByteBuffer(), false);
DynamicObject result = createTypedArray(arrayBuffer, typedArray, 0, (int) length, newTarget);
assert typedArray == JSArrayBufferView.typedArrayGetArrayType(result);
for (long i = 0; i < length; i++) {
Object element = sourceType.getElement(arrayBufferView, i);
typedArray.setElement(result, i, element, false);
}
return result;
}
protected ArraySpeciesConstructorNode getArraySpeciesConstructorNode() {
if (arraySpeciesConstructorNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
arraySpeciesConstructorNode = insert(ArraySpeciesConstructorNode.create(getContext(), true));
}
return arraySpeciesConstructorNode;
}
@SuppressWarnings("unused")
@Specialization(guards = {"!isUndefined(newTarget)", "isUndefined(arg0)"})
protected DynamicObject doEmpty(DynamicObject newTarget, DynamicObject arg0, Object byteOffset0, Object length0) {
return createTypedArrayWithLength(0, newTarget);
}
@Specialization(guards = {"!isUndefined(newTarget)", "length >= 0"})
protected DynamicObject doIntLength(DynamicObject newTarget, int length, @SuppressWarnings("unused") Object byteOffset0, @SuppressWarnings("unused") Object length0) {
return createTypedArrayWithLength(length, newTarget);
}
@Specialization(guards = {"!isUndefined(newTarget)", "!isJSObject(arg0)", "!isForeignObject(arg0)"}, replaces = "doIntLength")
protected DynamicObject doLength(DynamicObject newTarget, Object arg0, @SuppressWarnings("unused") Object byteOffset0, @SuppressWarnings("unused") Object length0) {
return createTypedArrayWithLength(toIndex(arg0), newTarget);
}
@SuppressWarnings("unused")
@Specialization(guards = {"!isUndefined(newTarget)", "isJSObject(object)", "!isJSAbstractBuffer(object)", "!isJSArrayBufferView(object)"})
protected DynamicObject doObject(DynamicObject newTarget, DynamicObject object, Object byteOffset0, Object length0,
@Cached("createGetIteratorMethod()") GetMethodNode getIteratorMethodNode,
@Cached("createBinaryProfile()") ConditionProfile isIterableProfile,
@Cached("createWriteOwn()") WriteElementNode writeOwnNode,
@Cached("createCall()") JSFunctionCallNode iteratorCallNode,
@Cached("create()") IsJSObjectNode isObjectNode,
@Cached("create(getContext())") IteratorStepNode iteratorStepNode,
@Cached("create(getContext())") IteratorValueNode getIteratorValueNode,
@Cached("createGetLength()") JSGetLengthNode getLengthNode,
@Cached("create(getContext())") ReadElementNode readNode,
@Cached("create(NEXT, getContext())") PropertyGetNode getNextMethodNode,
@Cached("create()") BranchProfile growProfile) {
assert JSRuntime.isObject(object) && !JSArrayBufferView.isJSArrayBufferView(object) && !JSAbstractBuffer.isJSAbstractBuffer(object);
DynamicObject proto = getPrototypeFromConstructorView(newTarget);
assert JSRuntime.isObject(proto);
Object usingIterator = getIteratorMethodNode.executeWithTarget(object);
if (isIterableProfile.profile(usingIterator != Undefined.instance)) {
SimpleArrayList<Object> values = GetIteratorNode.iterableToList(object, usingIterator, iteratorCallNode, isObjectNode, iteratorStepNode, getIteratorValueNode, getNextMethodNode, this,
growProfile);
int len = values.size();
DynamicObject arrayBuffer = createTypedArrayBuffer(len);
TypedArray typedArray = factory.createArrayType(getContext().isOptionDirectByteBuffer(), false);
DynamicObject obj = integerIndexedObjectCreate(arrayBuffer, typedArray, 0, len, proto);
for (int k = 0; k < len; k++) {
Object kValue = values.get(k);
writeOwnNode.executeWithTargetAndIndexAndValue(obj, k, kValue);
}
return obj;
}
long len = getLengthNode.executeLong(object);
DynamicObject arrayBuffer = createTypedArrayBuffer(len);
assert len <= Integer.MAX_VALUE;
TypedArray typedArray = factory.createArrayType(getContext().isOptionDirectByteBuffer(), false);
DynamicObject obj = integerIndexedObjectCreate(arrayBuffer, typedArray, 0, (int) len, proto);
for (int k = 0; k < len; k++) {
Object kValue = readNode.executeWithTargetAndIndex(object, k);
writeOwnNode.executeWithTargetAndIndexAndValue(obj, k, kValue);
}
return obj;
}
@Specialization(guards = {"!isUndefined(newTarget)", "isForeignObject(object)"}, limit = "InteropLibraryLimit")
protected DynamicObject doForeignObject(DynamicObject newTarget, Object object, @SuppressWarnings("unused") Object byteOffset0, @SuppressWarnings("unused") Object length0,
@CachedLibrary("object") InteropLibrary interop,
@Cached("createWriteOwn()") WriteElementNode writeOwnNode,
@Cached ImportValueNode importValue) {
long length;
if (interop.hasArrayElements(object)) {
length = toIndex(JSInteropUtil.getArraySize(object, interop, this));
} else {
length = 0;
}
DynamicObject obj = createTypedArrayWithLength(length, newTarget);
assert length <= Integer.MAX_VALUE;
for (int k = 0; k < length; k++) {
Object kValue = JSInteropUtil.readArrayElementOrDefault(object, k, 0, interop, importValue, this);
writeOwnNode.executeWithTargetAndIndexAndValue(obj, k, kValue);
}
return obj;
}
GetMethodNode createGetIteratorMethod() {
return GetMethodNode.create(getContext(), null, Symbol.SYMBOL_ITERATOR);
}
WriteElementNode createWriteOwn() {
return WriteElementNode.create(getContext(), true, true);
}
JSGetLengthNode createGetLength() {
return JSGetLengthNode.create(getContext());
}
@SuppressWarnings("unused")
@Specialization(guards = {"isUndefined(newTarget)"})
protected DynamicObject doUndefinedNewTarget(Object newTarget, Object arg0, Object byteOffset0, Object length0) {
throw Errors.createTypeError("newTarget is not a function");
}
private DynamicObject createTypedArrayBuffer(long length) {
assert length >= 0;
int elementSize = factory.getBytesPerElement();
checkLengthLimit(length, elementSize);
int byteLength = toByteLength((int) length, elementSize);
assert length <= Integer.MAX_VALUE && byteLength >= 0 && byteLength <= Integer.MAX_VALUE;
if (getContext().isOptionDirectByteBuffer()) {
return JSArrayBuffer.createDirectArrayBuffer(getContext(), byteLength);
} else {
return JSArrayBuffer.createArrayBuffer(getContext(), byteLength);
}
}
private DynamicObject createTypedArrayWithLength(long length, DynamicObject newTarget) {
DynamicObject arrayBuffer = createTypedArrayBuffer(length);
TypedArray typedArray = factory.createArrayType(getContext().isOptionDirectByteBuffer(), false);
return createTypedArray(arrayBuffer, typedArray, 0, (int) length, newTarget);
}
private DynamicObject createTypedArray(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject newTarget) {
DynamicObject proto = getPrototypeFromConstructorView(newTarget);
assert JSRuntime.isObject(proto);
return integerIndexedObjectCreate(arrayBuffer, typedArray, offset, length, proto);
}
private int checkLengthLimit(long length, int elementSize) {
if (length > getContext().getContextOptions().getMaxTypedArrayLength() / elementSize) {
errorBranch.enter();
throw throwInappropriateLengthError(length);
}
return (int) length;
}
private static int toByteLength(int length, int elementSize) {
int byteLength = length * elementSize;
assert byteLength >= 0 && byteLength / elementSize == length;
return byteLength;
}
@TruffleBoundary
private RuntimeException throwInappropriateLengthError(long length) {
if (getContext().isOptionNashornCompatibilityMode()) {
throw Errors.createRangeError("inappropriate array buffer length: " + length);
} else if (getContext().isOptionV8CompatibilityMode()) {
throw Errors.createRangeError("Invalid array buffer length");
} else {
throw Errors.createRangeError("Invalid typed array length: " + length);
}
}
private boolean rangeCheck(boolean condition, String message) {
if (!condition) {
errorBranch.enter();
throw Errors.createRangeError(message);
}
return condition;
}
private boolean rangeCheckIsMultipleOfElementSize(boolean condition, String what, String name, int bytesPerElement) {
if (!condition) {
errorBranch.enter();
throw createRangeErrorNotMultipleOfElementSize(what, name, bytesPerElement);
}
return condition;
}
@TruffleBoundary
private static RuntimeException createRangeErrorNotMultipleOfElementSize(String what, String name, int bytesPerElement) {
return Errors.createRangeError(String.format("%s of %s should be a multiple of %d", what, name, bytesPerElement));
}
abstract static class IntegerIndexedObjectCreateNode extends JavaScriptBaseNode {
final JSContext context;
final TypedArrayFactory factory;
IntegerIndexedObjectCreateNode(JSContext context, TypedArrayFactory factory) {
this.context = context;
this.factory = factory;
}
abstract DynamicObject execute(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject proto);
@Specialization(guards = "isDefaultPrototype(proto)")
DynamicObject doDefaultProto(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, @SuppressWarnings("unused") DynamicObject proto) {
JSObjectFactory objectFactory = context.getArrayBufferViewFactory(factory);
return JSArrayBufferView.createArrayBufferView(context, objectFactory, arrayBuffer, typedArray, offset, length);
}
@Specialization(guards = {"!isDefaultPrototype(proto)", "context.isMultiContext()"})
DynamicObject doMultiContext(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject proto) {
JSObjectFactory objectFactory = context.getArrayBufferViewFactory(factory);
return JSArrayBufferView.createArrayBufferViewWithProto(context, objectFactory, arrayBuffer, typedArray, offset, length, proto);
}
@SuppressWarnings("unused")
@Specialization(guards = {"!isDefaultPrototype(proto)", "!context.isMultiContext()", "proto == cachedProto"}, limit = "1")
DynamicObject doCachedProto(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject proto,
@Cached("proto") DynamicObject cachedProto,
@Cached("makeObjectFactory(cachedProto)") JSObjectFactory objectFactory) {
return JSArrayBufferView.createArrayBufferView(context, objectFactory, arrayBuffer, typedArray, offset, length);
}
@Specialization(guards = {"!isDefaultPrototype(proto)", "!context.isMultiContext()"}, replaces = "doCachedProto")
DynamicObject doUncachedProto(DynamicObject arrayBuffer, TypedArray typedArray, int offset, int length, DynamicObject proto) {
return JSArrayBufferView.createArrayBufferView(context, makeObjectFactory(proto), arrayBuffer, typedArray, offset, length);
}
boolean isDefaultPrototype(DynamicObject proto) {
return proto == context.getRealm().getArrayBufferViewPrototype(factory);
}
@TruffleBoundary
JSObjectFactory makeObjectFactory(DynamicObject prototype) {
return JSObjectFactory.createBound(context, prototype, JSObjectUtil.getProtoChildShape(prototype, JSArrayBufferView.INSTANCE, context));
}
}
}