All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.js.builtins.ArrayFunctionBuiltins Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.js.builtins;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Bind;
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.exception.AbstractTruffleException;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
import com.oracle.truffle.js.builtins.ArrayFunctionBuiltinsFactory.JSArrayFromAsyncNodeGen;
import com.oracle.truffle.js.builtins.ArrayFunctionBuiltinsFactory.JSArrayFromNodeGen;
import com.oracle.truffle.js.builtins.ArrayFunctionBuiltinsFactory.JSArrayOfNodeGen;
import com.oracle.truffle.js.builtins.ArrayFunctionBuiltinsFactory.JSIsArrayNodeGen;
import com.oracle.truffle.js.builtins.ArrayPrototypeBuiltins.JSArrayOperation;
import com.oracle.truffle.js.builtins.AsyncIteratorPrototypeBuiltins.AsyncIteratorAwaitNode;
import com.oracle.truffle.js.builtins.AsyncIteratorPrototypeBuiltins.AsyncIteratorAwaitNode.AsyncIteratorRootNode;
import com.oracle.truffle.js.nodes.access.AsyncIteratorCloseNode;
import com.oracle.truffle.js.nodes.access.CreateAsyncFromSyncIteratorNode;
import com.oracle.truffle.js.nodes.access.GetIteratorFromMethodNode;
import com.oracle.truffle.js.nodes.access.GetMethodNode;
import com.oracle.truffle.js.nodes.access.IsObjectNode;
import com.oracle.truffle.js.nodes.access.IteratorCloseNode;
import com.oracle.truffle.js.nodes.access.IteratorCompleteNode;
import com.oracle.truffle.js.nodes.access.IteratorStepNode;
import com.oracle.truffle.js.nodes.access.IteratorValueNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
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.array.JSSetLengthNode;
import com.oracle.truffle.js.nodes.control.TryCatchNode;
import com.oracle.truffle.js.nodes.control.YieldException;
import com.oracle.truffle.js.nodes.function.InternalCallNode;
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.promise.NewPromiseCapabilityNode;
import com.oracle.truffle.js.nodes.promise.PerformPromiseThenNode;
import com.oracle.truffle.js.nodes.promise.PromiseResolveNode;
import com.oracle.truffle.js.nodes.unary.IsConstructorNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSContext.BuiltinFunctionKey;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.builtins.BuiltinEnum;
import com.oracle.truffle.js.runtime.builtins.JSArray;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionObject;
import com.oracle.truffle.js.runtime.objects.IteratorRecord;
import com.oracle.truffle.js.runtime.objects.PromiseCapabilityRecord;
import com.oracle.truffle.js.runtime.objects.Undefined;

/**
 * Contains builtins for {@linkplain JSArray} function (constructor).
 */
public final class ArrayFunctionBuiltins extends JSBuiltinsContainer.SwitchEnum {

    public static final JSBuiltinsContainer BUILTINS = new ArrayFunctionBuiltins();

    protected ArrayFunctionBuiltins() {
        super(JSArray.CLASS_NAME, ArrayFunction.class);
    }

    public enum ArrayFunction implements BuiltinEnum {
        isArray(1),

        // ES6
        of(0),
        from(1),

        fromAsync(1);

        private final int length;

        ArrayFunction(int length) {
            this.length = length;
        }

        @Override
        public int getLength() {
            return length;
        }

        @Override
        public int getECMAScriptVersion() {
            return switch (this) {
                case of, from -> JSConfig.ECMAScript2015;
                case fromAsync -> JSConfig.StagingECMAScriptVersion;
                default -> BuiltinEnum.super.getECMAScriptVersion();
            };
        }
    }

    @Override
    protected Object createNode(JSContext context, JSBuiltin builtin, boolean construct, boolean newTarget, ArrayFunction builtinEnum) {
        switch (builtinEnum) {
            case isArray:
                return JSIsArrayNodeGen.create(context, builtin, args().fixedArgs(1).createArgumentNodes(context));
            case of:
                return JSArrayOfNodeGen.create(context, builtin, false, args().withThis().varArgs().createArgumentNodes(context));
            case from:
                return JSArrayFromNodeGen.create(context, builtin, false, args().withThis().fixedArgs(3).createArgumentNodes(context));
            case fromAsync:
                return JSArrayFromAsyncNodeGen.create(context, builtin, args().withThis().fixedArgs(3).createArgumentNodes(context));
        }
        return null;
    }

    public abstract static class JSIsArrayNode extends JSBuiltinNode {
        @Child private com.oracle.truffle.js.nodes.unary.JSIsArrayNode isArrayNode = com.oracle.truffle.js.nodes.unary.JSIsArrayNode.createIsArrayLike();

        public JSIsArrayNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin);
        }

        @Specialization
        protected boolean isArray(Object object) {
            return isArrayNode.execute(object);
        }
    }

    public abstract static class JSArrayFunctionOperation extends JSArrayOperation {
        @Child protected IsConstructorNode isConstructor = IsConstructorNode.create();

        public JSArrayFunctionOperation(JSContext context, JSBuiltin builtin, boolean isTypedArray) {
            super(context, builtin, isTypedArray);
        }

        protected Object constructOrArray(Object thisObj, long len, boolean provideLengthArg) {
            if (isTypedArrayImplementation) {
                return getArraySpeciesConstructorNode().typedArrayCreate(thisObj, JSRuntime.longToIntOrDouble(len));
            } else {
                if (isConstructor.executeBoolean(thisObj)) {
                    if (provideLengthArg) {
                        return getArraySpeciesConstructorNode().construct(thisObj, JSRuntime.longToIntOrDouble(len));
                    } else {
                        return getArraySpeciesConstructorNode().construct(thisObj);
                    }
                } else {
                    return arrayCreate(len);
                }
            }
        }
    }

    public abstract static class JSArrayOfNode extends JSArrayFunctionOperation {

        public JSArrayOfNode(JSContext context, JSBuiltin builtin, boolean isTypedArray) {
            super(context, builtin, isTypedArray);
        }

        @Specialization
        protected Object arrayOf(Object thisObj, Object[] args) {
            int len = args.length;
            Object obj = constructOrArray(thisObj, len, true);

            int pos = 0;
            for (Object arg : args) {
                Object value = JSRuntime.nullToUndefined(arg);
                writeOwn(obj, pos, value);
                pos++;
            }
            setLength(obj, len);
            return obj;
        }
    }

    public abstract static class JSArrayFromNode extends JSArrayFunctionOperation {
        @Child private JSFunctionCallNode callMapFnNode;
        @Child private IteratorCloseNode iteratorCloseNode;
        @Child private IteratorValueNode getIteratorValueNode;
        @Child private IteratorStepNode iteratorStepNode;
        @Child private GetMethodNode getIteratorMethodNode;
        @Child private JSGetLengthNode getSourceLengthNode;
        private final ConditionProfile isIterable = ConditionProfile.create();

        public JSArrayFromNode(JSContext context, JSBuiltin builtin, boolean isTypedArray) {
            super(context, builtin, isTypedArray);
            this.getIteratorMethodNode = GetMethodNode.create(context, Symbol.SYMBOL_ITERATOR);
        }

        protected void iteratorCloseAbrupt(Object iterator) {
            if (iteratorCloseNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                iteratorCloseNode = insert(IteratorCloseNode.create(getContext()));
            }
            iteratorCloseNode.executeAbrupt(iterator);
        }

        protected Object getIteratorValue(Object iteratorResult) {
            if (getIteratorValueNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                getIteratorValueNode = insert(IteratorValueNode.create());
            }
            return getIteratorValueNode.execute(iteratorResult);
        }

        protected Object iteratorStep(IteratorRecord iteratorRecord) {
            if (iteratorStepNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                iteratorStepNode = insert(IteratorStepNode.create());
            }
            return iteratorStepNode.execute(iteratorRecord);
        }

        protected final Object callMapFn(Object target, Object function, Object... userArguments) {
            if (callMapFnNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                callMapFnNode = insert(JSFunctionCallNode.createCall());
            }
            return callMapFnNode.executeCall(JSArguments.create(target, function, userArguments));
        }

        protected long getSourceLength(Object thisObject) {
            if (getSourceLengthNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                getSourceLengthNode = insert(JSGetLengthNode.create(getContext()));
            }
            return getSourceLengthNode.executeLong(thisObject);
        }

        @Specialization
        protected Object arrayFrom(Object thisObj, Object items, Object mapFn, Object thisArg,
                        @Cached GetIteratorFromMethodNode getIteratorFromMethod,
                        @Cached InlinedBranchProfile growProfile) {
            return arrayFromCommon(thisObj, items, mapFn, thisArg, true, getIteratorFromMethod, growProfile);
        }

        protected Object arrayFromCommon(Object thisObj, Object items, Object mapFn, Object thisArg, boolean setLength,
                        GetIteratorFromMethodNode getIteratorFromMethod, InlinedBranchProfile growProfile) {
            boolean mapping;
            if (mapFn == Undefined.instance) {
                mapping = false;
            } else {
                checkCallbackIsFunction(mapFn);
                mapping = true;
            }
            Object usingIterator = getIteratorMethodNode.executeWithTarget(items);
            if (isIterable.profile(usingIterator != Undefined.instance)) {
                return arrayFromIterable(thisObj, items, usingIterator, mapFn, thisArg, mapping, getIteratorFromMethod, growProfile);
            } else {
                // NOTE: source is not an Iterable so assume it is already an array-like object.
                Object itemsObject = toObject(items);
                return arrayFromArrayLike(thisObj, itemsObject, mapFn, thisArg, mapping, setLength);
            }
        }

        protected Object arrayFromIterable(Object thisObj, Object items, Object usingIterator, Object mapFn, Object thisArg, boolean mapping,
                        GetIteratorFromMethodNode getIteratorFromMethod, @SuppressWarnings("unused") InlinedBranchProfile growProfile) {
            Object obj = constructOrArray(thisObj, 0, false);

            IteratorRecord iteratorRecord = getIteratorFromMethod.execute(this, items, usingIterator);
            return arrayFromIteratorRecord(obj, iteratorRecord, mapFn, thisArg, mapping);
        }

        private Object arrayFromIteratorRecord(Object obj, IteratorRecord iteratorRecord, Object mapFn, Object thisArg, boolean mapping) {
            long k = 0;
            try {
                while (true) {
                    Object next = iteratorStep(iteratorRecord);
                    if (next == Boolean.FALSE) {
                        setLength(obj, k);
                        return obj;
                    }
                    Object mapped = getIteratorValue(next);
                    if (mapping) {
                        mapped = callMapFn(thisArg, mapFn, mapped, JSRuntime.positiveLongToIntOrDouble(k));
                    }
                    writeOwn(obj, k, mapped);
                    k++;
                }
            } catch (AbstractTruffleException ex) {
                iteratorCloseAbrupt(iteratorRecord.getIterator());
                throw ex; // should be executed by iteratorClose
            }
        }

        protected Object arrayFromArrayLike(Object thisObj, Object items, Object mapFn, Object thisArg, boolean mapping, boolean setLength) {
            long len = getSourceLength(items);

            Object obj = constructOrArray(thisObj, len, true);

            long k = 0;
            while (k < len) {
                Object value = read(items, k);
                Object mapped = value;
                if (mapping) {
                    mapped = callMapFn(thisArg, mapFn, mapped, JSRuntime.positiveLongToIntOrDouble(k));
                }
                writeOwn(obj, k, mapped);
                k++;
            }
            if (setLength) {
                setLength(obj, len);
            }
            return obj;
        }
    }

    @ImportStatic(Symbol.class)
    public abstract static class JSArrayFromAsyncNode extends JSArrayFunctionOperation {
        @Child private PropertySetNode setArgs;
        @Child private TryCatchNode.GetErrorObjectNode getErrorObjectNode;

        public JSArrayFromAsyncNode(JSContext context, JSBuiltin builtin) {
            super(context, builtin, false);
            this.setArgs = PropertySetNode.createSetHidden(AsyncIteratorAwaitNode.ARGS_ID, context);
        }

        private JSFunctionObject createFunctionWithArgs(ArrayFromAsyncArgs args, JSFunctionData functionData) {
            JSFunctionObject function = JSFunction.create(getRealm(), functionData);
            setArgs.setValue(function, args);
            return function;
        }

        @SuppressWarnings("truffle-static-method")
        @Specialization
        protected final Object arrayFromAsync(Object thisObj, Object asyncItems, Object mapFn, Object thisArg,
                        @Bind("this") Node node,
                        @Cached("create(getContext())") NewPromiseCapabilityNode newPromiseCapability,
                        @Cached("create(getContext(), SYMBOL_ASYNC_ITERATOR)") GetMethodNode getAsyncIteratorMethodNode,
                        @Cached("create(getContext(), SYMBOL_ITERATOR)") GetMethodNode getIteratorMethodNode,
                        @Cached("create(getContext())") JSGetLengthNode getLengthNode,
                        @Cached CreateAsyncFromSyncIteratorNode createAsyncFromSyncIterator,
                        @Cached GetIteratorFromMethodNode getIteratorFromMethodNode,
                        @Cached InternalCallNode internalCallNode,
                        @Cached("createCall()") JSFunctionCallNode callRejectNode,
                        @Cached InlinedConditionProfile isAsyncIterator) {
            var promiseCapability = newPromiseCapability.executeDefault();
            try {
                final boolean mapping;
                if (mapFn == Undefined.instance) {
                    mapping = false;
                } else {
                    checkCallbackIsFunction(mapFn);
                    mapping = true;
                }
                IteratorRecord asyncIteratorRecord;
                Object usingAsyncIterator = getAsyncIteratorMethodNode.executeWithTarget(asyncItems);
                if (isAsyncIterator.profile(node, usingAsyncIterator != Undefined.instance)) {
                    asyncIteratorRecord = getIteratorFromMethodNode.execute(node, asyncItems, usingAsyncIterator);
                } else {
                    Object usingSyncIterator = getIteratorMethodNode.executeWithTarget(asyncItems);
                    if (usingSyncIterator != Undefined.instance) {
                        IteratorRecord syncIteratorRecord = getIteratorFromMethodNode.execute(node, asyncItems, usingSyncIterator);
                        asyncIteratorRecord = createAsyncFromSyncIterator.execute(node, syncIteratorRecord);
                    } else {
                        asyncIteratorRecord = null;
                    }
                }

                Object result;
                JSFunctionObject closure;
                if (asyncIteratorRecord != null) {
                    result = constructOrArray(thisObj, 0, false);
                    var args = new ArrayFromAsyncIteratorArgs(promiseCapability, asyncIteratorRecord, result, mapping, mapFn, thisArg);
                    closure = createFunctionWithArgs(args, getContext().getOrCreateBuiltinFunctionData(
                                    BuiltinFunctionKey.ArrayFromAsyncIteratorResumption, ArrayFromAsyncIteratorResumptionRootNode::createFunctionImpl));
                } else {
                    /*
                     * Note: asyncItems is neither an AsyncIterable nor an Iterable so assume it is
                     * an array-like object.
                     */
                    Object arrayLike = toObject(asyncItems);
                    long len = getLengthNode.executeLong(arrayLike);
                    result = constructOrArray(thisObj, len, true);
                    var args = new ArrayFromAsyncArrayLikeArgs(promiseCapability, len, arrayLike, result, mapping, mapFn, thisArg);
                    closure = createFunctionWithArgs(args, getContext().getOrCreateBuiltinFunctionData(
                                    BuiltinFunctionKey.ArrayFromAsyncArrayLikeResumption, ArrayFromAsyncArrayLikeResumptionRootNode::createFunctionImpl));
                }

                internalCallNode.execute(JSFunction.getCallTarget(closure), JSArguments.createOneArg(Undefined.instance, closure, Undefined.instance));
            } catch (AbstractTruffleException ex) {
                Object error = getErrorObject(ex);
                callRejectNode.executeCall(JSArguments.createOneArg(Undefined.instance, promiseCapability.getReject(), error));
            }
            return promiseCapability.getPromise();
        }

        private Object getErrorObject(AbstractTruffleException ex) {
            if (getErrorObjectNode == null) {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                getErrorObjectNode = insert(TryCatchNode.GetErrorObjectNode.create(getContext()));
            }
            return getErrorObjectNode.execute(ex);
        }

        abstract static sealed class ArrayFromAsyncArgs {
            /** Array.fromAsync result promise. */
            final PromiseCapabilityRecord promiseCapability;
            /** Result array or array-like. */
            final Object result;

            final boolean mapping;
            final Object mapFn;
            final Object thisArg;

            /** Resumption point. */
            int state;
            /** Current result array index. */
            long resultIndex;

            ArrayFromAsyncArgs(PromiseCapabilityRecord promiseCapability, Object result, boolean mapping, Object mapFn, Object thisArg) {
                this.promiseCapability = promiseCapability;
                this.result = result;
                this.mapping = mapping;
                this.mapFn = mapFn;
                this.thisArg = thisArg;
            }
        }

        static final class ArrayFromAsyncIteratorArgs extends ArrayFromAsyncArgs {
            final IteratorRecord iterator;

            ArrayFromAsyncIteratorArgs(PromiseCapabilityRecord promiseCapability, IteratorRecord iterator, Object result, boolean mapping, Object mapFn, Object thisArg) {
                super(promiseCapability, result, mapping, mapFn, thisArg);
                this.iterator = iterator;
            }
        }

        static final class ArrayFromAsyncArrayLikeArgs extends ArrayFromAsyncArgs {
            final long len;
            final Object arrayLike;

            ArrayFromAsyncArrayLikeArgs(PromiseCapabilityRecord promiseCapability, long len, Object arrayLike, Object result, boolean mapping, Object mapFn, Object thisArg) {
                super(promiseCapability, result, mapping, mapFn, thisArg);
                this.len = len;
                this.arrayLike = arrayLike;
            }
        }

        protected abstract static class ArrayFromAsyncResumptionRootNode extends AsyncIteratorAwaitNode.AsyncIteratorRootNode {
            @Child private PromiseResolveNode promiseResolveNode;
            @Child private PerformPromiseThenNode performPromiseThenNode;
            @Child private PropertySetNode setArgs;
            @Child private JSSetLengthNode setLengthNode;
            @Child private WriteElementNode writeOwnElementNode;
            @Child private JSFunctionCallNode callMapFnNode;
            @Child private TryCatchNode.GetErrorObjectNode getErrorObjectNode;

            protected static final int STATE_START = 0;
            protected static final int STATE_AWAIT_NEXT_RESULT = 1;
            protected static final int STATE_AWAIT_MAPPED_VALUE = 2;

            public ArrayFromAsyncResumptionRootNode(JSContext context) {
                super(context);

                this.promiseResolveNode = PromiseResolveNode.create(context);
                this.performPromiseThenNode = PerformPromiseThenNode.create(context);
                this.setArgs = PropertySetNode.createSetHidden(AsyncIteratorAwaitNode.ARGS_ID, context);
                this.setLengthNode = JSSetLengthNode.create(context, THROW_ERROR);
                this.writeOwnElementNode = WriteElementNode.create(context, THROW_ERROR, true);
            }

            protected final JSFunctionObject createFunctionWithArgs(T args, JSFunctionData functionData) {
                JSFunctionObject function = JSFunction.create(getRealm(), functionData);
                setArgs.setValue(function, args);
                return function;
            }

            protected final Object suspendAwait(VirtualFrame frame, T args, Object promiseOrValue, int nextState, long k) {
                var promise = promiseResolveNode.executeDefault(promiseOrValue);

                // Save state and suspend built-in async function.
                assert getArgs(frame) == args;
                args.state = nextState;
                args.resultIndex = k;

                /*
                 * Once the awaited promise is fulfilled (f.) or rejected (r.), either (f.) resume
                 * at the suspended Await point, or (r.) run the abrupt completion handler closing
                 * the async iterator (if any) and rejecting the promise returned by Array.fromAsync
                 * with the Await error, respectively.
                 */
                var resumeAwait = JSFrameUtil.getFunctionObject(frame);
                var rejectAwait = createIfAbruptHandler(args);

                performPromiseThenNode.execute(promise, resumeAwait, rejectAwait);
                throw YieldException.AWAIT_NULL; // value is ignored
            }

            protected abstract JSFunctionObject createIfAbruptHandler(T args);

            protected final Object resumeAwait(VirtualFrame frame, T args, int expectedState) {
                // We have been restored at this point. Argument 0 is the awaited value.
                assert getArgs(frame) == args && args.state == expectedState;
                Object awaitedValue = JSArguments.getUserArgument(frame.getArguments(), 0);
                args.state = STATE_START;
                return awaitedValue;
            }

            protected final void setLength(Object thisObject, long length) {
                setLengthNode.execute(thisObject, indexToJS(length));
            }

            protected final void createDataPropertyOrThrow(Object result, long k, Object mappedValue) {
                writeOwnElementNode.executeWithTargetAndIndexAndValue(result, k, mappedValue);
            }

            protected final Object callMapFn(Object mapFn, Object thisArg, Object kValue, long k) {
                if (callMapFnNode == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    callMapFnNode = insert(JSFunctionCallNode.createCall());
                }
                return callMapFnNode.executeCall(JSArguments.create(thisArg, mapFn, kValue, indexToJS(k)));
            }

            protected final Object getErrorObject(AbstractTruffleException ex) {
                if (getErrorObjectNode == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    getErrorObjectNode = insert(TryCatchNode.GetErrorObjectNode.create(context));
                }
                return getErrorObjectNode.execute(ex);
            }
        }

        protected static final class ArrayFromAsyncIteratorResumptionRootNode extends ArrayFromAsyncResumptionRootNode {

            @Child private JSFunctionCallNode callNextMethodNode;
            @Child private IteratorValueNode iteratorValueNode;
            @Child private AsyncIteratorCloseNode asyncIteratorCloseNode;
            @Child private IsObjectNode isObjectNode;
            @Child private IteratorCompleteNode iteratorCompleteNode;

            ArrayFromAsyncIteratorResumptionRootNode(JSContext context) {
                super(context);
                this.callNextMethodNode = JSFunctionCallNode.createCall();
                this.iteratorValueNode = IteratorValueNode.create();
                this.asyncIteratorCloseNode = AsyncIteratorCloseNode.create(context);
                this.isObjectNode = IsObjectNode.create();
                this.iteratorCompleteNode = IteratorCompleteNode.create();
            }

            private Object checkIterResult(Object value) {
                if (!isObjectNode.executeBoolean(value)) {
                    throw Errors.createTypeErrorIterResultNotAnObject(value, this);
                }
                return value;
            }

            @Override
            public Object execute(VirtualFrame frame) {
                var args = getArgs(frame);

                var promiseCapability = args.promiseCapability;
                IteratorRecord iteratorRecord = args.iterator;
                Object result = args.result;
                boolean mapping = args.mapping;
                long k = args.resultIndex;
                returnNow: try {
                    for (int state = args.state; k < JSRuntime.MAX_SAFE_INTEGER_LONG; ++k, state = STATE_START) {
                        Object mappedValue;
                        if (state < STATE_AWAIT_MAPPED_VALUE) {
                            Object nextResult;
                            if (state == STATE_START) {
                                nextResult = callNextMethodNode.executeCall(JSArguments.createZeroArg(iteratorRecord.getIterator(), iteratorRecord.getNextMethod()));
                                nextResult = suspendAwait(frame, args, nextResult, STATE_AWAIT_NEXT_RESULT, k);
                            } else {
                                nextResult = resumeAwait(frame, args, STATE_AWAIT_NEXT_RESULT);
                                checkIterResult(nextResult);
                                if (iteratorCompleteNode.execute(nextResult)) {
                                    setLength(result, k);
                                    callResolve(promiseCapability, result);
                                    break returnNow;
                                }
                            }
                            Object nextValue = iteratorValueNode.execute(nextResult);
                            if (mapping) {
                                mappedValue = callMapFn(args.mapFn, args.thisArg, nextValue, k);
                                mappedValue = suspendAwait(frame, args, mappedValue, STATE_AWAIT_MAPPED_VALUE, k);
                            } else {
                                mappedValue = nextValue;
                            }
                        } else {
                            mappedValue = resumeAwait(frame, args, STATE_AWAIT_MAPPED_VALUE);
                        }
                        createDataPropertyOrThrow(result, k, mappedValue);
                    }
                    assert k >= JSRuntime.MAX_SAFE_INTEGER_LONG : k;
                    throw Errors.createTypeErrorIndexTooLarge();
                } catch (YieldException e) {
                    assert e.isAwait() && args.state != STATE_START;
                } catch (AbstractTruffleException e) {
                    Object error = getErrorObject(e);
                    asyncIteratorCloseNode.executeAbruptReject(iteratorRecord.getIterator(), error, promiseCapability);
                }
                return promiseCapability.getPromise();
            }

            @Override
            protected JSFunctionObject createIfAbruptHandler(ArrayFromAsyncIteratorArgs args) {
                return createFunctionWithArgs(args, context.getOrCreateBuiltinFunctionData(
                                BuiltinFunctionKey.ArrayFromAsyncAwaitIfAbruptClose, ArrayFromAsyncIteratorResumptionRootNode::createIfAbruptCloseImpl));
            }

            static JSFunctionData createFunctionImpl(JSContext context) {
                return JSFunctionData.createCallOnly(context, new ArrayFromAsyncIteratorResumptionRootNode(context).getCallTarget(), 1, Strings.EMPTY_STRING);
            }

            static JSFunctionData createIfAbruptCloseImpl(JSContext context) {
                return JSFunctionData.createCallOnly(context, new IfAbruptCloseNode(context).getCallTarget(), 1, Strings.EMPTY_STRING);
            }

            private static class IfAbruptCloseNode extends AsyncIteratorRootNode {
                @Child private AsyncIteratorCloseNode closeNode;

                IfAbruptCloseNode(JSContext context) {
                    super(context);
                    this.closeNode = AsyncIteratorCloseNode.create(context);
                }

                @Override
                public Object execute(VirtualFrame frame) {
                    var args = getArgs(frame);
                    var promiseCapability = args.promiseCapability;
                    Object error = valueNode.execute(frame);
                    closeNode.executeAbruptReject(args.iterator.getIterator(), error, promiseCapability);
                    return promiseCapability.getPromise();
                }
            }
        }

        protected static final class ArrayFromAsyncArrayLikeResumptionRootNode extends ArrayFromAsyncResumptionRootNode {

            @Child private ReadElementNode getNode;

            ArrayFromAsyncArrayLikeResumptionRootNode(JSContext context) {
                super(context);
                this.getNode = ReadElementNode.create(context);
            }

            @Override
            public Object execute(VirtualFrame frame) {
                var args = getArgs(frame);

                var promiseCapability = args.promiseCapability;
                Object result = args.result;
                boolean mapping = args.mapping;
                long len = args.len;
                long k = args.resultIndex;
                try {
                    for (int state = args.state; k < len; ++k, state = STATE_START) {
                        Object mappedValue;
                        if (state < STATE_AWAIT_MAPPED_VALUE) {
                            Object kValue;
                            if (state == STATE_START) {
                                kValue = getNode.executeWithTargetAndIndex(args.arrayLike, k);
                                kValue = suspendAwait(frame, args, kValue, STATE_AWAIT_NEXT_RESULT, k);
                            } else {
                                assert state == STATE_AWAIT_NEXT_RESULT;
                                kValue = resumeAwait(frame, args, STATE_AWAIT_NEXT_RESULT);
                            }
                            if (mapping) {
                                mappedValue = callMapFn(args.mapFn, args.thisArg, kValue, k);
                                mappedValue = suspendAwait(frame, args, mappedValue, STATE_AWAIT_MAPPED_VALUE, k);
                            } else {
                                mappedValue = kValue;
                            }
                        } else {
                            assert state == STATE_AWAIT_MAPPED_VALUE;
                            mappedValue = resumeAwait(frame, args, STATE_AWAIT_MAPPED_VALUE);
                        }
                        createDataPropertyOrThrow(result, k, mappedValue);
                    }
                    setLength(result, len);
                    callResolve(promiseCapability, result);
                } catch (YieldException e) {
                    assert e.isAwait() && args.state != STATE_START;
                } catch (AbstractTruffleException e) {
                    Object error = getErrorObject(e);
                    callReject(promiseCapability, error);
                }
                return promiseCapability.getPromise();
            }

            @Override
            protected JSFunctionObject createIfAbruptHandler(ArrayFromAsyncArrayLikeArgs args) {
                return createFunctionWithArgs(args, context.getOrCreateBuiltinFunctionData(
                                BuiltinFunctionKey.ArrayFromAsyncAwaitIfAbruptReturn, ArrayFromAsyncArrayLikeResumptionRootNode::createIfAbruptReturnImpl));
            }

            static JSFunctionData createFunctionImpl(JSContext context) {
                return JSFunctionData.createCallOnly(context, new ArrayFromAsyncArrayLikeResumptionRootNode(context).getCallTarget(), 1, Strings.EMPTY_STRING);
            }

            static JSFunctionData createIfAbruptReturnImpl(JSContext context) {
                return JSFunctionData.createCallOnly(context, new IfAbruptReturnNode(context).getCallTarget(), 1, Strings.EMPTY_STRING);
            }

            private static class IfAbruptReturnNode extends AsyncIteratorRootNode {

                IfAbruptReturnNode(JSContext context) {
                    super(context);
                }

                @Override
                public Object execute(VirtualFrame frame) {
                    var args = getArgs(frame);
                    var promiseCapability = args.promiseCapability;
                    Object error = valueNode.execute(frame);
                    callReject(promiseCapability, error);
                    return promiseCapability.getPromise();
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy