blob: f4e17456651516dfb3e81880da82ab228646ccfd [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include 'src/builtins/builtins-promise.h'
#include 'src/builtins/builtins-promise-gen.h'
namespace runtime {
extern transitioning runtime RejectPromise(
implicit context: Context)(JSPromise, JSAny, Boolean): JSAny;
extern transitioning runtime PromiseRevokeReject(
implicit context: Context)(JSPromise): JSAny;
extern transitioning runtime PromiseRejectAfterResolved(
implicit context: Context)(JSPromise, JSAny): JSAny;
extern transitioning runtime PromiseResolveAfterResolved(
implicit context: Context)(JSPromise, JSAny): JSAny;
extern transitioning runtime PromiseRejectEventFromStack(
implicit context: Context)(JSPromise, JSAny): JSAny;
}
// https://tc39.es/ecma262/#sec-promise-abstract-operations
namespace promise {
extern macro PromiseForwardingHandlerSymbolConstant(): Symbol;
const kPromiseForwardingHandlerSymbol: Symbol =
PromiseForwardingHandlerSymbolConstant();
extern macro PromiseHandledBySymbolConstant(): Symbol;
const kPromiseHandledBySymbol: Symbol = PromiseHandledBySymbolConstant();
extern macro ResolveStringConstant(): String;
const kResolveString: String = ResolveStringConstant();
extern macro IsPromiseResolveProtectorCellInvalid(): bool;
extern macro AllocateRootFunctionWithContext(
constexpr intptr, FunctionContext, NativeContext): JSFunction;
extern macro PromiseReactionMapConstant(): Map;
extern macro PromiseFulfillReactionJobTaskMapConstant(): Map;
extern macro PromiseRejectReactionJobTaskMapConstant(): Map;
extern transitioning builtin ResolvePromise(Context, JSPromise, JSAny): JSAny;
extern transitioning builtin EnqueueMicrotask(Context, Microtask): Undefined;
macro ExtractHandlerContextInternal(
implicit context: Context)(
handler: Callable|Undefined): Context labels NotFound {
let iter: JSAny = handler;
while (true) {
typeswitch (iter) {
case (b: JSBoundFunction): {
iter = b.bound_target_function;
}
case (p: JSProxy): {
iter = p.target;
}
case (f: JSFunction): {
return f.context;
}
case (JSAny): {
break;
}
}
}
goto NotFound;
}
macro ExtractHandlerContext(
implicit context: Context)(handler: Callable|Undefined): Context {
try {
return ExtractHandlerContextInternal(handler) otherwise NotFound;
} label NotFound deferred {
return context;
}
}
macro ExtractHandlerContext(
implicit context: Context)(primary: Callable|Undefined,
secondary: Callable|Undefined): Context {
try {
return ExtractHandlerContextInternal(primary) otherwise NotFound;
} label NotFound deferred {
return ExtractHandlerContextInternal(secondary) otherwise Default;
} label Default deferred {
return context;
}
}
transitioning macro MorphAndEnqueuePromiseReaction(
implicit context: Context)(promiseReaction: PromiseReaction,
argument: JSAny, reactionType: constexpr PromiseReactionType): void {
let primaryHandler: Callable|Undefined;
let secondaryHandler: Callable|Undefined;
if constexpr (reactionType == kPromiseReactionFulfill) {
primaryHandler = promiseReaction.fulfill_handler;
secondaryHandler = promiseReaction.reject_handler;
} else {
static_assert(reactionType == kPromiseReactionReject);
primaryHandler = promiseReaction.reject_handler;
secondaryHandler = promiseReaction.fulfill_handler;
}
// According to HTML, we use the context of the appropriate handler as the
// context of the microtask. See step 3 of HTML's EnqueueJob:
// https://html.spec.whatwg.org/C/#enqueuejob(queuename,-job,-arguments)
const handlerContext: Context =
ExtractHandlerContext(primaryHandler, secondaryHandler);
// Morph {current} from a PromiseReaction into a PromiseReactionJobTask
// and schedule that on the microtask queue. We try to minimize the number
// of stores here to avoid write barrier overhead.
static_assert(
kPromiseReactionSize ==
kPromiseReactionJobTaskSizeOfAllPromiseReactionJobTasks);
if constexpr (reactionType == kPromiseReactionFulfill) {
*UnsafeConstCast(&promiseReaction.map) =
PromiseFulfillReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseFulfillReactionJobTask>(promiseReaction);
promiseReactionJobTask.argument = argument;
promiseReactionJobTask.context = handlerContext;
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
static_assert(
kPromiseReactionFulfillHandlerOffset ==
kPromiseReactionJobTaskHandlerOffset);
static_assert(
kPromiseReactionPromiseOrCapabilityOffset ==
kPromiseReactionJobTaskPromiseOrCapabilityOffset);
@if(V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA)
static_assert(
kPromiseReactionContinuationPreservedEmbedderDataOffset ==
kPromiseReactionJobTaskContinuationPreservedEmbedderDataOffset);
} else {
static_assert(reactionType == kPromiseReactionReject);
*UnsafeConstCast(&promiseReaction.map) =
PromiseRejectReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseRejectReactionJobTask>(promiseReaction);
promiseReactionJobTask.argument = argument;
promiseReactionJobTask.context = handlerContext;
promiseReactionJobTask.handler = primaryHandler;
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
static_assert(
kPromiseReactionPromiseOrCapabilityOffset ==
kPromiseReactionJobTaskPromiseOrCapabilityOffset);
@if(V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA)
static_assert(
kPromiseReactionContinuationPreservedEmbedderDataOffset ==
kPromiseReactionJobTaskContinuationPreservedEmbedderDataOffset);
}
}
// https://tc39.es/ecma262/#sec-triggerpromisereactions
transitioning macro TriggerPromiseReactions(
implicit context: Context)(reactions: Zero|PromiseReaction,
argument: JSAny, reactionType: constexpr PromiseReactionType): void {
// We need to reverse the {reactions} here, since we record them on the
// JSPromise in the reverse order.
let current = reactions;
let reversed: Zero|PromiseReaction = kZero;
// As an additional safety net against misuse of the V8 Extras API, we
// sanity check the {reactions} to make sure that they are actually
// PromiseReaction instances and not actual JavaScript values (which
// would indicate that we're rejecting or resolving an already settled
// promise), see https://crbug.com/931640 for details on this.
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
currentReaction.next = reversed;
reversed = currentReaction;
}
}
}
// Morph the {reactions} into PromiseReactionJobTasks and push them
// onto the microtask queue.
current = reversed;
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
MorphAndEnqueuePromiseReaction(currentReaction, argument, reactionType);
}
}
}
}
// https://tc39.es/ecma262/#sec-fulfillpromise
transitioning builtin FulfillPromise(
implicit context: Context)(promise: JSPromise, value: JSAny): Undefined {
// Assert: The value of promise.[[PromiseState]] is "pending".
dcheck(promise.Status() == PromiseState::kPending);
RunContextPromiseHookResolve(promise);
// 2. Let reactions be promise.[[PromiseFulfillReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 3. Set promise.[[PromiseResult]] to value.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = value;
// 6. Set promise.[[PromiseState]] to "fulfilled".
promise.SetStatus(PromiseState::kFulfilled);
// 7. Return TriggerPromiseReactions(reactions, value).
TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);
return Undefined;
}
extern macro PromiseBuiltinsAssembler::
IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(): bool;
extern macro PromiseBuiltinsAssembler::
IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(uint32):
bool;
// https://tc39.es/ecma262/#sec-rejectpromise
transitioning builtin RejectPromise(
implicit context: Context)(promise: JSPromise, reason: JSAny,
debugEvent: Boolean): JSAny {
const promiseHookFlags = PromiseHookFlags();
// If promise hook is enabled or the debugger is active, let
// the runtime handle this operation, which greatly reduces
// the complexity here and also avoids a couple of back and
// forth between JavaScript and C++ land.
if (IsIsolatePromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(
promiseHookFlags) ||
!promise.HasHandler()) {
// 7. If promise.[[PromiseIsHandled]] is false, perform
// HostPromiseRejectionTracker(promise, "reject").
// We don't try to handle rejecting {promise} without handler
// here, but we let the C++ code take care of this completely.
return runtime::RejectPromise(promise, reason, debugEvent);
}
RunContextPromiseHookResolve(promise, promiseHookFlags);
// 2. Let reactions be promise.[[PromiseRejectReactions]].
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 3. Set promise.[[PromiseResult]] to reason.
// 4. Set promise.[[PromiseFulfillReactions]] to undefined.
// 5. Set promise.[[PromiseRejectReactions]] to undefined.
promise.reactions_or_result = reason;
// 6. Set promise.[[PromiseState]] to "rejected".
promise.SetStatus(PromiseState::kRejected);
// 8. Return TriggerPromiseReactions(reactions, reason).
TriggerPromiseReactions(reactions, reason, kPromiseReactionReject);
return Undefined;
}
const kPromiseCapabilitySize:
constexpr int31 generates 'PromiseCapability::kSize';
type PromiseResolvingFunctionContext extends FunctionContext;
extern enum PromiseResolvingFunctionContextSlot extends intptr
constexpr 'PromiseBuiltins::PromiseResolvingFunctionContextSlot' {
kPromiseSlot: Slot<PromiseResolvingFunctionContext, JSPromise>,
kAlreadyResolvedSlot: Slot<PromiseResolvingFunctionContext, Boolean>,
kDebugEventSlot: Slot<PromiseResolvingFunctionContext, Boolean>,
kPromiseContextLength
}
type PromiseCapabilitiesExecutorContext extends FunctionContext;
extern enum FunctionContextSlot extends intptr
constexpr 'PromiseBuiltins::FunctionContextSlot' {
kCapabilitySlot: Slot<PromiseCapabilitiesExecutorContext, PromiseCapability>,
kCapabilitiesContextLength
}
@export
macro CreatePromiseCapabilitiesExecutorContext(
nativeContext: NativeContext,
capability: PromiseCapability): PromiseCapabilitiesExecutorContext {
const executorContext = %RawDownCast<PromiseCapabilitiesExecutorContext>(
AllocateSyntheticFunctionContext(
nativeContext, FunctionContextSlot::kCapabilitiesContextLength));
InitContextSlot(
executorContext, FunctionContextSlot::kCapabilitySlot, capability);
return executorContext;
}
@export
macro CreatePromiseCapability(
promise: JSReceiver|Undefined, resolve: JSFunction|Undefined,
reject: JSFunction|Undefined): PromiseCapability {
return new PromiseCapability{
map: kPromiseCapabilityMap,
promise: promise,
resolve: resolve,
reject: reject
};
}
@export
struct PromiseResolvingFunctions {
resolve: JSFunction;
reject: JSFunction;
context: Context;
}
const kPromiseCapabilityDefaultResolveSharedFun: constexpr intptr
generates 'RootIndex::kPromiseCapabilityDefaultResolveSharedFun';
const kPromiseCapabilityDefaultRejectSharedFun: constexpr intptr
generates 'RootIndex::kPromiseCapabilityDefaultRejectSharedFun';
@export
macro CreatePromiseResolvingFunctions(
implicit context: Context)(promise: JSPromise, debugEvent: Boolean,
nativeContext: NativeContext): PromiseResolvingFunctions {
const promiseContext = CreatePromiseResolvingFunctionsContext(
promise, debugEvent, nativeContext);
const resolve: JSFunction = AllocateRootFunctionWithContext(
kPromiseCapabilityDefaultResolveSharedFun, promiseContext, nativeContext);
const reject: JSFunction = AllocateRootFunctionWithContext(
kPromiseCapabilityDefaultRejectSharedFun, promiseContext, nativeContext);
return PromiseResolvingFunctions{
resolve: resolve,
reject: reject,
context: promiseContext
};
}
const kPromiseGetCapabilitiesExecutorSharedFun: constexpr intptr
generates 'RootIndex::kPromiseGetCapabilitiesExecutorSharedFun';
transitioning macro InnerNewPromiseCapability(
implicit context: Context)(constructor: HeapObject,
debugEvent: Boolean): PromiseCapability {
const nativeContext = LoadNativeContext(context);
if (constructor ==
*NativeContextSlot(nativeContext, ContextSlot::PROMISE_FUNCTION_INDEX)) {
const promise = NewJSPromise();
const pair =
CreatePromiseResolvingFunctions(promise, debugEvent, nativeContext);
return CreatePromiseCapability(promise, pair.resolve, pair.reject);
} else {
// We have to create the capability before the associated promise
// because the builtin PromiseConstructor uses the executor.
const capability = CreatePromiseCapability(Undefined, Undefined, Undefined);
const executorContext =
CreatePromiseCapabilitiesExecutorContext(nativeContext, capability);
const executor = AllocateRootFunctionWithContext(
kPromiseGetCapabilitiesExecutorSharedFun, executorContext,
nativeContext);
const promiseConstructor = UnsafeCast<Constructor>(constructor);
const promise = Construct(promiseConstructor, executor);
capability.promise = promise;
if (!Is<Callable>(capability.resolve) || !Is<Callable>(capability.reject)) {
ThrowTypeError(MessageTemplate::kPromiseNonCallable);
}
return capability;
}
}
// https://tc39.es/ecma262/#sec-newpromisecapability
transitioning builtin NewPromiseCapability(
implicit context: Context)(maybeConstructor: Object,
debugEvent: Boolean): PromiseCapability {
typeswitch (maybeConstructor) {
case (Smi): {
ThrowTypeError(MessageTemplate::kNotConstructor, maybeConstructor);
}
case (constructor: HeapObject): {
if (!IsConstructor(constructor)) {
ThrowTypeError(MessageTemplate::kNotConstructor, maybeConstructor);
}
return InnerNewPromiseCapability(constructor, debugEvent);
}
}
}
// https://tc39.es/ecma262/#sec-promise-reject-functions
transitioning javascript builtin PromiseCapabilityDefaultReject(
js-implicit context: Context, receiver: JSAny)(reason: JSAny): JSAny {
const context = %RawDownCast<PromiseResolvingFunctionContext>(context);
// 2. Let promise be F.[[Promise]].
const promise =
*ContextSlot(context, PromiseResolvingFunctionContextSlot::kPromiseSlot);
// 3. Let alreadyResolved be F.[[AlreadyResolved]].
const alreadyResolved = *ContextSlot(
context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot);
// 4. If alreadyResolved.[[Value]] is true, return undefined.
if (alreadyResolved == True) {
return runtime::PromiseRejectAfterResolved(promise, reason);
}
// 5. Set alreadyResolved.[[Value]] to true.
*ContextSlot(
context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot) =
True;
// 6. Return RejectPromise(promise, reason).
const debugEvent = *ContextSlot(
context, PromiseResolvingFunctionContextSlot::kDebugEventSlot);
return RejectPromise(promise, reason, debugEvent);
}
// https://tc39.es/ecma262/#sec-promise-resolve-functions
transitioning javascript builtin PromiseCapabilityDefaultResolve(
js-implicit context: Context, receiver: JSAny)(resolution: JSAny): JSAny {
const context = %RawDownCast<PromiseResolvingFunctionContext>(context);
// 2. Let promise be F.[[Promise]].
const promise: JSPromise =
*ContextSlot(context, PromiseResolvingFunctionContextSlot::kPromiseSlot);
// 3. Let alreadyResolved be F.[[AlreadyResolved]].
const alreadyResolved: Boolean = *ContextSlot(
context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot);
// 4. If alreadyResolved.[[Value]] is true, return undefined.
if (alreadyResolved == True) {
return runtime::PromiseResolveAfterResolved(promise, resolution);
}
// 5. Set alreadyResolved.[[Value]] to true.
*ContextSlot(
context, PromiseResolvingFunctionContextSlot::kAlreadyResolvedSlot) =
True;
// The rest of the logic (and the catch prediction) is
// encapsulated in the dedicated ResolvePromise builtin.
return ResolvePromise(context, promise, resolution);
}
@export
transitioning macro PerformPromiseThenImpl(
implicit context: Context)(promise: JSPromise,
onFulfilled: Callable|Undefined, onRejected: Callable|Undefined,
resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
if (promise.Status() == PromiseState::kPending) {
// The {promise} is still in "Pending" state, so we just record a new
// PromiseReaction holding both the onFulfilled and onRejected callbacks.
// Once the {promise} is resolved we decide on the concrete handler to
// push onto the microtask queue.
const promiseReactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
const reaction = NewPromiseReaction(
promiseReactions, resultPromiseOrCapability, onFulfilled, onRejected);
promise.reactions_or_result = reaction;
} else {
const reactionsOrResult = promise.reactions_or_result;
let microtask: PromiseReactionJobTask;
let handlerContext: Context;
if (promise.Status() == PromiseState::kFulfilled) {
handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
microtask = NewPromiseFulfillReactionJobTask(
handlerContext, reactionsOrResult, onFulfilled,
resultPromiseOrCapability);
} else
deferred {
dcheck(promise.Status() == PromiseState::kRejected);
handlerContext = ExtractHandlerContext(onRejected, onFulfilled);
microtask = NewPromiseRejectReactionJobTask(
handlerContext, reactionsOrResult, onRejected,
resultPromiseOrCapability);
if (!promise.HasHandler()) {
runtime::PromiseRevokeReject(promise);
}
}
EnqueueMicrotask(handlerContext, microtask);
}
promise.SetHasHandler();
}
transitioning javascript builtin PerformPromiseThenFunction(
js-implicit context: NativeContext, receiver: JSAny)(onFulfilled: JSAny,
onRejected: JSAny): JSAny {
const jsPromise = Cast<JSPromise>(receiver) otherwise unreachable;
const callableOnFulfilled = Cast<Callable>(onFulfilled) otherwise unreachable;
const callableOnRejected = Cast<Callable>(onRejected) otherwise unreachable;
PerformPromiseThenImpl(
jsPromise, callableOnFulfilled, callableOnRejected, Undefined);
return Undefined;
}
// https://tc39.es/ecma262/#sec-performpromisethen
transitioning builtin PerformPromiseThen(
implicit context: Context)(promise: JSPromise,
onFulfilled: Callable|Undefined, onRejected: Callable|Undefined,
resultPromise: JSPromise|Undefined): JSAny {
PerformPromiseThenImpl(promise, onFulfilled, onRejected, resultPromise);
return resultPromise;
}
// https://tc39.es/ecma262/#sec-promise-reject-functions
transitioning javascript builtin PromiseReject(
js-implicit context: NativeContext, receiver: JSAny)(
reason: JSAny): JSAny {
// 1. Let C be the this value.
// 2. If Type(C) is not Object, throw a TypeError exception.
const receiver = Cast<JSReceiver>(receiver) otherwise
ThrowTypeError(MessageTemplate::kCalledOnNonObject, 'PromiseReject');
const promiseFun = *NativeContextSlot(ContextSlot::PROMISE_FUNCTION_INDEX);
if (promiseFun == receiver) {
const promise = NewJSPromise(PromiseState::kRejected, reason);
runtime::PromiseRejectEventFromStack(promise, reason);
return promise;
} else {
// 3. Let promiseCapability be ? NewPromiseCapability(C).
const capability = NewPromiseCapability(receiver, True);
// 4. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
const reject = UnsafeCast<Callable>(capability.reject);
Call(context, reject, Undefined, reason);
// 5. Return promiseCapability.[[Promise]].
return capability.promise;
}
}
const kPromiseExecutorAlreadyInvoked: constexpr MessageTemplate
generates 'MessageTemplate::kPromiseExecutorAlreadyInvoked';
// https://tc39.es/ecma262/#sec-getcapabilitiesexecutor-functions
transitioning javascript builtin PromiseGetCapabilitiesExecutor(
js-implicit context: Context, receiver: JSAny)(resolve: JSAny,
reject: JSAny): JSAny {
const context = %RawDownCast<PromiseCapabilitiesExecutorContext>(context);
const capability: PromiseCapability =
*ContextSlot(context, FunctionContextSlot::kCapabilitySlot);
if (capability.resolve != Undefined || capability.reject != Undefined)
deferred {
ThrowTypeError(kPromiseExecutorAlreadyInvoked);
}
capability.resolve = resolve;
capability.reject = reject;
return Undefined;
}
macro IsPromiseResolveLookupChainIntact(
implicit context: Context)(nativeContext: NativeContext,
constructor: JSReceiver): bool {
if (IsForceSlowPath()) return false;
const promiseFun =
*NativeContextSlot(nativeContext, ContextSlot::PROMISE_FUNCTION_INDEX);
return promiseFun == constructor && !IsPromiseResolveProtectorCellInvalid();
}
// https://tc39.es/ecma262/#sec-getpromiseresolve
transitioning macro GetPromiseResolve(
implicit context: Context)(nativeContext: NativeContext,
constructor: Constructor): JSAny {
// 1. Assert: IsConstructor(constructor) is true.
// We can skip the "resolve" lookup on {constructor} if it's the
// Promise constructor and the Promise.resolve protector is intact,
// as that guards the lookup path for the "resolve" property on the
// Promise constructor. In this case, promiseResolveFunction is undefined,
// and when CallResolve is called with it later, it will call Promise.resolve.
let promiseResolveFunction: JSAny = Undefined;
if (!IsPromiseResolveLookupChainIntact(nativeContext, constructor)) {
let promiseResolve: JSAny;
// 2. Let promiseResolve be ? Get(constructor, "resolve").
promiseResolve = GetProperty(constructor, kResolveString);
// 3. If IsCallable(promiseResolve) is false, throw a TypeError exception.
promiseResolveFunction =
Cast<Callable>(promiseResolve) otherwise ThrowTypeError(
MessageTemplate::kCalledNonCallable, 'resolve');
}
// 4. return promiseResolve.
return promiseResolveFunction;
}
transitioning macro CallResolve(
implicit context: Context)(constructor: Constructor, resolve: JSAny,
value: JSAny): JSAny {
// Undefined can never be a valid value for the resolve function,
// instead it is used as a special marker for the fast path.
if (resolve == Undefined) {
return PromiseResolve(constructor, value);
} else
deferred {
return Call(context, UnsafeCast<Callable>(resolve), constructor, value);
}
}
transitioning javascript builtin PromiseConstructorLazyDeoptContinuation(
js-implicit context: NativeContext, receiver: JSAny)(promise: JSAny,
reject: JSAny, exception: JSAny|TheHole, _result: JSAny): JSAny {
// Clear pending message since the exception is not going to be rethrown.
torque_internal::SetPendingMessage(TheHole);
typeswitch (exception) {
case (TheHole): {
}
case (e: JSAny): {
Call(context, reject, Undefined, e);
}
}
return promise;
}
extern macro PromiseCapabilityDefaultRejectSharedFunConstant():
SharedFunctionInfo;
extern macro PromiseCapabilityDefaultResolveSharedFunConstant():
SharedFunctionInfo;
extern macro PromiseGetCapabilitiesExecutorSharedFunConstant():
SharedFunctionInfo;
}