package com.oracle.truffle.js.nodes.control;
import java.util.ArrayDeque;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.api.profiles.ConditionProfile;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.nodes.arguments.AccessIndexedArgumentNode;
import com.oracle.truffle.js.nodes.function.InternalCallNode;
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.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSFrameUtil;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunction.AsyncGeneratorState;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSPromise;
import com.oracle.truffle.js.runtime.objects.AsyncGeneratorRequest;
import com.oracle.truffle.js.runtime.objects.Completion;
import com.oracle.truffle.js.runtime.objects.PromiseCapabilityRecord;
import com.oracle.truffle.js.runtime.objects.Undefined;
public class AsyncGeneratorResumeNextNode extends JavaScriptBaseNode {
@Child private PropertyGetNode getGeneratorStateNode;
@Child private PropertySetNode setGeneratorStateNode;
@Child private PropertyGetNode getAsyncGeneratorQueueNode;
@Child private JSFunctionCallNode callPromiseResolveNode;
@Child private PerformPromiseThenNode performPromiseThenNode;
@Child private NewPromiseCapabilityNode newPromiseCapabilityNode;
@Child private AsyncGeneratorResolveNode asyncGeneratorResolveNode;
@Child private AsyncGeneratorRejectNode asyncGeneratorRejectNode;
@Child private PropertySetNode setGeneratorNode;
@Child private PropertySetNode setPromiseIsHandledNode;
@Child private PromiseResolveNode promiseResolveNode;
private final ConditionProfile abruptProf = ConditionProfile.createBinaryProfile();
protected final JSContext context;
static final HiddenKey RETURN_PROCESSOR_GENERATOR = new HiddenKey("Generator");
protected AsyncGeneratorResumeNextNode(JSContext context) {
this.context = context;
this.getGeneratorStateNode = PropertyGetNode.createGetHidden(JSFunction.ASYNC_GENERATOR_STATE_ID, context);
this.setGeneratorStateNode = PropertySetNode.createSetHidden(JSFunction.ASYNC_GENERATOR_STATE_ID, context);
this.getAsyncGeneratorQueueNode = PropertyGetNode.createGetHidden(JSFunction.ASYNC_GENERATOR_QUEUE_ID, context);
this.asyncGeneratorResolveNode = AsyncGeneratorResolveNode.create(context);
}
public static AsyncGeneratorResumeNextNode create(JSContext context) {
return new AsyncGeneratorResumeNextNode.WithCall(context);
}
public static AsyncGeneratorResumeNextNode createTailCall(JSContext context) {
return new AsyncGeneratorResumeNextNode(context);
}
@SuppressWarnings("unchecked")
public final Object execute(VirtualFrame frame, DynamicObject generator) {
for (;;) {
AsyncGeneratorState state = (AsyncGeneratorState) getGeneratorStateNode.getValue(generator);
assert state != AsyncGeneratorState.Executing;
if (state == AsyncGeneratorState.AwaitingReturn) {
return Undefined.instance;
}
ArrayDeque<AsyncGeneratorRequest> queue = (ArrayDeque<AsyncGeneratorRequest>) getAsyncGeneratorQueueNode.getValue(generator);
if (queue.isEmpty()) {
return Undefined.instance;
}
AsyncGeneratorRequest next = queue.peekFirst();
if (abruptProf.profile(next.isAbruptCompletion())) {
if (state == AsyncGeneratorState.SuspendedStart) {
state = AsyncGeneratorState.Completed;
setGeneratorStateNode.setValue(generator, state);
}
if (state == AsyncGeneratorState.Completed) {
if (next.isReturn()) {
enterReturnBranch();
setGeneratorStateNode.setValue(generator, AsyncGeneratorState.AwaitingReturn);
DynamicObject promise = promiseResolve(next.getCompletionValue());
DynamicObject onFulfilled = createAsyncGeneratorReturnProcessorFulfilledFunction(generator);
DynamicObject onRejected = createAsyncGeneratorReturnProcessorRejectedFunction(generator);
PromiseCapabilityRecord throwawayCapability = newThrowawayCapability();
performPromiseThenNode.execute(promise, onFulfilled, onRejected, throwawayCapability);
return Undefined.instance;
} else {
assert next.isThrow();
enterThrowBranch();
asyncGeneratorRejectNode.performReject(frame, generator, next.getCompletionValue());
continue;
}
}
} else if (state == AsyncGeneratorState.Completed) {
asyncGeneratorResolveNode.performResolve(frame, generator, Undefined.instance, true);
continue;
}
assert state == AsyncGeneratorState.SuspendedStart || state == AsyncGeneratorState.SuspendedYield;
setGeneratorStateNode.setValue(generator, AsyncGeneratorState.Executing);
return performResumeNext(generator, next.getCompletion());
}
}
private DynamicObject promiseResolve(Object value) {
if (context.usePromiseResolve()) {
if (promiseResolveNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
promiseResolveNode = insert(PromiseResolveNode.create(context));
}
return promiseResolveNode.execute(context.getRealm().getPromiseConstructor(), value);
} else {
if (callPromiseResolveNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callPromiseResolveNode = insert(JSFunctionCallNode.createCall());
}
PromiseCapabilityRecord promiseCapability = newPromiseCapability();
callPromiseResolveNode.executeCall(JSArguments.createOneArg(Undefined.instance, promiseCapability.getResolve(), value));
return promiseCapability.getPromise();
}
}
protected Object performResumeNext(@SuppressWarnings("unused") DynamicObject generator, Completion completion) {
return completion;
}
private static class WithCall extends AsyncGeneratorResumeNextNode {
@Child private PropertyGetNode getGeneratorTarget;
@Child private PropertyGetNode getGeneratorContext;
@Child private InternalCallNode callNode;
protected WithCall(JSContext context) {
super(context);
this.getGeneratorTarget = PropertyGetNode.createGetHidden(JSFunction.ASYNC_GENERATOR_TARGET_ID, context);
this.getGeneratorContext = PropertyGetNode.createGetHidden(JSFunction.ASYNC_GENERATOR_CONTEXT_ID, context);
this.callNode = InternalCallNode.create();
}
@Override
protected Object performResumeNext(DynamicObject generator, Completion completion) {
CallTarget generatorTarget = (CallTarget) getGeneratorTarget.getValue(generator);
Object generatorContext = getGeneratorContext.getValue(generator);
callNode.execute(generatorTarget, new Object[]{generatorContext, generator, completion});
return Undefined.instance;
}
}
private void enterReturnBranch() {
if (performPromiseThenNode == null || setGeneratorNode == null || setPromiseIsHandledNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.performPromiseThenNode = insert(PerformPromiseThenNode.create(context));
this.setGeneratorNode = insert(PropertySetNode.createSetHidden(RETURN_PROCESSOR_GENERATOR, context));
this.setPromiseIsHandledNode = insert(PropertySetNode.createSetHidden(JSPromise.PROMISE_IS_HANDLED, context));
}
}
private PromiseCapabilityRecord newPromiseCapability() {
if (newPromiseCapabilityNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
newPromiseCapabilityNode = insert(NewPromiseCapabilityNode.create(context));
}
return newPromiseCapabilityNode.executeDefault();
}
private PromiseCapabilityRecord newThrowawayCapability() {
if (context.getEcmaScriptVersion() >= JSConfig.ECMAScript2019) {
return null;
}
if (setPromiseIsHandledNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
setPromiseIsHandledNode = insert(PropertySetNode.createSetHidden(JSPromise.PROMISE_IS_HANDLED, context));
}
PromiseCapabilityRecord throwawayCapability = newPromiseCapability();
setPromiseIsHandledNode.setValueBoolean(throwawayCapability.getPromise(), true);
return throwawayCapability;
}
private void enterThrowBranch() {
if (asyncGeneratorRejectNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.asyncGeneratorRejectNode = insert(AsyncGeneratorRejectNode.create(context));
}
}
private DynamicObject createAsyncGeneratorReturnProcessorFulfilledFunction(DynamicObject generator) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncGeneratorReturnFulfilled, (c) -> createAsyncGeneratorReturnProcessorFulfilledImpl(c));
DynamicObject function = JSFunction.create(context.getRealm(), functionData);
setGeneratorNode.setValue(function, generator);
return function;
}
private static JSFunctionData createAsyncGeneratorReturnProcessorFulfilledImpl(JSContext context) {
class AsyncGeneratorReturnFulfilledRootNode extends JavaScriptRootNode {
@Child private JavaScriptNode valueNode = AccessIndexedArgumentNode.create(0);
@Child private AsyncGeneratorResolveNode asyncGeneratorResolveNode = AsyncGeneratorResolveNode.create(context);
@Child private PropertyGetNode getGenerator = PropertyGetNode.createGetHidden(RETURN_PROCESSOR_GENERATOR, context);
@Child private PropertySetNode setGeneratorState = PropertySetNode.createSetHidden(JSFunction.ASYNC_GENERATOR_STATE_ID, context);
@Override
public Object execute(VirtualFrame frame) {
DynamicObject functionObject = JSFrameUtil.getFunctionObject(frame);
DynamicObject generatorObject = (DynamicObject) getGenerator.getValue(functionObject);
setGeneratorState.setValue(generatorObject, AsyncGeneratorState.Completed);
Object value = valueNode.execute(frame);
return asyncGeneratorResolveNode.execute(frame, generatorObject, value, true);
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new AsyncGeneratorReturnFulfilledRootNode());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
private DynamicObject createAsyncGeneratorReturnProcessorRejectedFunction(DynamicObject generator) {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.AsyncGeneratorReturnRejected, (c) -> createAsyncGeneratorReturnProcessorRejectedImpl(c));
DynamicObject function = JSFunction.create(context.getRealm(), functionData);
setGeneratorNode.setValue(function, generator);
return function;
}
private static JSFunctionData createAsyncGeneratorReturnProcessorRejectedImpl(JSContext context) {
class AsyncGeneratorReturnRejectedRootNode extends JavaScriptRootNode {
@Child private JavaScriptNode reasonNode = AccessIndexedArgumentNode.create(0);
@Child private AsyncGeneratorRejectNode asyncGeneratorRejectNode = AsyncGeneratorRejectNode.create(context);
@Child private PropertyGetNode getGenerator = PropertyGetNode.createGetHidden(RETURN_PROCESSOR_GENERATOR, context);
@Child private PropertySetNode setGeneratorState = PropertySetNode.createSetHidden(JSFunction.ASYNC_GENERATOR_STATE_ID, context);
@Override
public Object execute(VirtualFrame frame) {
DynamicObject functionObject = JSFrameUtil.getFunctionObject(frame);
DynamicObject generatorObject = (DynamicObject) getGenerator.getValue(functionObject);
setGeneratorState.setValue(generatorObject, AsyncGeneratorState.Completed);
Object reason = reasonNode.execute(frame);
return asyncGeneratorRejectNode.execute(frame, generatorObject, reason);
}
}
CallTarget callTarget = Truffle.getRuntime().createCallTarget(new AsyncGeneratorReturnRejectedRootNode());
return JSFunctionData.createCallOnly(context, callTarget, 1, "");
}
}