package com.oracle.truffle.js.nodes.promise;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
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.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.nodes.unary.IsCallableNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.builtins.JSPromise;
import com.oracle.truffle.js.runtime.objects.PromiseCapabilityRecord;
import com.oracle.truffle.js.runtime.objects.PromiseReactionRecord;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.SimpleArrayList;
public class PerformPromiseThenNode extends JavaScriptBaseNode {
private final JSContext context;
@Child private IsCallableNode isCallableFulfillNode = IsCallableNode.create();
@Child private IsCallableNode isCallableRejectNode = IsCallableNode.create();
@Child private PropertyGetNode getPromiseFulfillReactionsNode;
@Child private PropertyGetNode getPromiseRejectReactionsNode;
@Child private PropertyGetNode getPromiseResultNode;
@Child private PropertyGetNode getPromiseIsHandledNode;
@Child private PropertySetNode setPromiseIsHandledNode;
@Child private PromiseReactionJobNode promiseReactionJobNode;
private final ConditionProfile pendingProf = ConditionProfile.createBinaryProfile();
private final ConditionProfile fulfilledProf = ConditionProfile.createBinaryProfile();
private final ConditionProfile unhandledProf = ConditionProfile.createBinaryProfile();
private final BranchProfile growProfile = BranchProfile.create();
protected PerformPromiseThenNode(JSContext context) {
this.context = context;
this.getPromiseFulfillReactionsNode = PropertyGetNode.createGetHidden(JSPromise.PROMISE_FULFILL_REACTIONS, context);
this.getPromiseRejectReactionsNode = PropertyGetNode.createGetHidden(JSPromise.PROMISE_REJECT_REACTIONS, context);
this.setPromiseIsHandledNode = PropertySetNode.createSetHidden(JSPromise.PROMISE_IS_HANDLED, context);
}
public static PerformPromiseThenNode create(JSContext context) {
return new PerformPromiseThenNode(context);
}
@SuppressWarnings("unchecked")
public DynamicObject execute(DynamicObject promise, Object onFulfilled, Object onRejected, PromiseCapabilityRecord resultCapability) {
assert JSPromise.isJSPromise(promise);
Object onFulfilledHandler = isCallableFulfillNode.executeBoolean(onFulfilled) ? onFulfilled : Undefined.instance;
Object onRejectedHandler = isCallableRejectNode.executeBoolean(onRejected) ? onRejected : Undefined.instance;
assert resultCapability != null || (onFulfilledHandler != Undefined.instance && onRejectedHandler != Undefined.instance);
PromiseReactionRecord fulfillReaction = PromiseReactionRecord.create(resultCapability, onFulfilledHandler, true);
PromiseReactionRecord rejectReaction = PromiseReactionRecord.create(resultCapability, onRejectedHandler, false);
int promiseState = JSPromise.getPromiseState(promise);
if (pendingProf.profile(promiseState == JSPromise.PENDING)) {
((SimpleArrayList<? super PromiseReactionRecord>) getPromiseFulfillReactionsNode.getValue(promise)).add(fulfillReaction, growProfile);
((SimpleArrayList<? super PromiseReactionRecord>) getPromiseRejectReactionsNode.getValue(promise)).add(rejectReaction, growProfile);
} else if (fulfilledProf.profile(promiseState == JSPromise.FULFILLED)) {
Object value = getPromiseResult(promise);
DynamicObject job = getPromiseReactionJob(fulfillReaction, value);
context.promiseEnqueueJob(context.getRealm(), job);
} else {
assert promiseState == JSPromise.REJECTED;
Object reason = getPromiseResult(promise);
if (unhandledProf.profile(!getPromiseIsHandled(promise))) {
context.notifyPromiseRejectionTracker(promise, JSPromise.REJECTION_TRACKER_OPERATION_HANDLE, Undefined.instance);
}
DynamicObject job = getPromiseReactionJob(rejectReaction, reason);
context.promiseEnqueueJob(context.getRealm(), job);
}
setPromiseIsHandledNode.setValueBoolean(promise, true);
if (resultCapability == null) {
return Undefined.instance;
}
return resultCapability.getPromise();
}
private DynamicObject getPromiseReactionJob(PromiseReactionRecord reaction, Object value) {
if (promiseReactionJobNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
promiseReactionJobNode = insert(PromiseReactionJobNode.create(context));
}
return promiseReactionJobNode.execute(reaction, value);
}
private Object getPromiseResult(DynamicObject promise) {
if (getPromiseResultNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getPromiseResultNode = insert(PropertyGetNode.createGetHidden(JSPromise.PROMISE_RESULT, context));
}
return getPromiseResultNode.getValue(promise);
}
private boolean getPromiseIsHandled(DynamicObject promise) {
try {
if (getPromiseIsHandledNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getPromiseIsHandledNode = insert(PropertyGetNode.createGetHidden(JSPromise.PROMISE_IS_HANDLED, context));
}
return getPromiseIsHandledNode.getValueBoolean(promise);
} catch (UnexpectedResultException e) {
throw Errors.shouldNotReachHere();
}
}
}