Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editorial: support built-in async functions #2942

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 65 additions & 5 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -13866,7 +13866,7 @@ <h1>Built-in Function Objects</h1>
<p>The behaviour specified for each built-in function via algorithm steps or other means is the specification of the function body behaviour for both [[Call]] and [[Construct]] invocations of the function. However, [[Construct]] invocation is not supported by all built-in functions. For each built-in function, when invoked with [[Call]], the [[Call]] _thisArgument_ provides the *this* value, the [[Call]] _argumentsList_ provides the named parameters, and the NewTarget value is *undefined*. When invoked with [[Construct]], the *this* value is uninitialized, the [[Construct]] _argumentsList_ provides the named parameters, and the [[Construct]] _newTarget_ parameter provides the NewTarget value. If the built-in function is implemented as an ECMAScript function object then this specified behaviour must be implemented by the ECMAScript code that is the body of the function. Built-in functions that are ECMAScript function objects must be strict functions. If a built-in constructor has any [[Call]] behaviour other than throwing a *TypeError* exception, an ECMAScript implementation of the function must be done in a manner that does not cause the function's [[IsClassConstructor]] internal slot to have the value *true*.</p>
<p>Built-in function objects that are not identified as constructors do not implement the [[Construct]] internal method unless otherwise specified in the description of a particular function. When a built-in constructor is called as part of a `new` expression the _argumentsList_ parameter of the invoked [[Construct]] internal method provides the values for the built-in constructor's named parameters.</p>
<p>Built-in functions that are not constructors do not have a *"prototype"* property unless otherwise specified in the description of a particular function.</p>
<p>If a built-in function object is not implemented as an ECMAScript function it must provide [[Call]] and [[Construct]] internal methods that conform to the following definitions:</p>
<p>Unless otherwise specified, a built-in function object must provide [[Call]] and [[Construct]] internal methods that conform to the following definitions:</p>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says that a built-in function is exotic (and synchronous) unless otherwise specified. So then a built-in would be ordinary (i.e., an ECMAScript function object) only if specified to be so. But, of course, none of them are, because the spec explicitly says that built-ins can be ordinary or exotic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmdyck I don't understand this comment. I think I'm missing some assumption. Can you rephrase?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 10.3 "Built-in Function Objects" says that built-in functions may be implemented as either ECMAScript function objects or as exotic function objects.

  • 10.2 "ECMAScript Function Objects" says that an ECMAScript function object has the [[Call]] internal method defined in 10.2.1.

  • 10.3 "Built-in Function Objects" says (in the status quo) that a built-in function that isn't implemented as an ECMAScript function object must have the [[Call]] internal method defined in 10.3.1.

So, in the status quo, a built-in's [[Call]] can be either 10.2.1 or 10.3.1, and the spec leaves the choice to the implementation.

  • This PR replaces the third point with a sentence that says a built-in function object must use 10.3.1 "unless otherwise specified".

  • But for all of the existing built-in functions, the spec never specifies whether to use 10.3.1 or 10.2.1 (because it's leaving that choice to the implementation), so it is never "otherwise specified", so the "unless otherwise specified" clause never holds.

So this new sentence implies that all existing built-in functions must use 10.3.1.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And I know that, at some point, the idea was floated to get rid of the 10.2.1 possibility for built-ins, but that's clearly not what this PR is trying to accomplish. That would presumably be a separate PR.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an observable difference. I am okay with it. Besides, we wanted to eliminate all the implications (and explicit statements) that "built-ins can be ECMAScript code, too" because it doesn't matter.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any example of built-in functions that are exotic?

In the spec? No, because the spec says that's up to the implementation.

If you're asking for an example of an implementation in which built-ins are exotic, there's v8. E.g. here is its implementation of String.p.endswith. It looks like most/all of its builtins are implemented in the Torque programming language. Not in ECMAScript, anyhow, so they're not 'ECMAScript function objects', i.e. not ordinary functions, so they're exotic functions.

In SpiderMonkey, it's implemented in C++, so also exotic.

My guess is that built-ins are exotic in most high-performance implementations, because they're easier to optimize that way.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolution from editor call is to change the definition of ordinary object to explicitly allow these [[Call]] and [[Construct]] methods so they're all considered ordinary for now. We'll do this in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in #2969.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#2969 expands the definition of "ordinary function" to include all built-in functions (however implemented). But I don't think that resolves this, because my concern doesn't hinge on the definition of "ordinary function". If you look at my response to bakkot above, it all still holds in a post-2969 spec (except that "exotic" in the first point now becomes "non-ECMAScript").

(Yes, it was during discussion of this concern that the editors call decided to expand the definition of "ordinary", but maybe that was just to deal with the discovery that built-ins could be exotic?)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think PR #3007 would be a basis for resolving my concern.


<emu-clause id="sec-built-in-function-objects-call-thisargument-argumentslist" type="internal method">
<h1>
Expand Down Expand Up @@ -13938,7 +13938,10 @@ <h1>
1. If _prototype_ is not present, set _prototype_ to _realm_.[[Intrinsics]].[[%Function.prototype%]].
1. Let _internalSlotsList_ be a List containing the names of all the internal slots that <emu-xref href="#sec-built-in-function-objects"></emu-xref> requires for the built-in function object that is about to be created.
1. Append to _internalSlotsList_ the elements of _additionalInternalSlotsList_.
1. Let _func_ be a new built-in function object that, when called, performs the action described by _behaviour_ using the provided arguments as the values of the corresponding parameters specified by _behaviour_. The new function object has internal slots whose names are the elements of _internalSlotsList_, and an [[InitialName]] internal slot.
1. If _behaviour_ is described as async, then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the verbiage for describing an async AC? "Let c be a new async Abstract Closure"? Or "Async Abstract Closure" with capital A?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say "async Abstract Closure", I think; the idea is what we will say "async function" or "async method" for the built-in function case (as in proposal-iterator-helpers).

We don't actually have any async ACs at the moment so it's not established yet.

1. Let _func_ be a new built-in async function object that, when called, performs the action described by _behaviour_ using the provided arguments as the values of the corresponding parameters specified by _behaviour_. The new function object has internal slots whose names are the elements of _internalSlotsList_, and an [[InitialName]] internal slot.
1. Else,
1. Let _func_ be a new built-in function object that, when called, performs the action described by _behaviour_ using the provided arguments as the values of the corresponding parameters specified by _behaviour_. The new function object has internal slots whose names are the elements of _internalSlotsList_, and an [[InitialName]] internal slot.
1. Set _func_.[[Prototype]] to _prototype_.
1. Set _func_.[[Extensible]] to *true*.
1. Set _func_.[[Realm]] to _realm_.
Expand All @@ -13954,6 +13957,57 @@ <h1>
</emu-clause>
</emu-clause>

<emu-clause id="sec-built-in-async-function-objects">
<h1>Built-in Async Function Objects</h1>
<p><dfn variants="built-in async function object,built-in async function objects">Built-in async function objects</dfn> are built-in function objects that provide [[Call]] and [[Construct]] internal methods that conform to the following definitions:</p>
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved

<emu-clause id="sec-built-in-async-function-objects-call" type="internal method">
<h1>
[[Call]] (
_thisArgument_: an ECMAScript language value,
_argumentsList_: a List of ECMAScript language values,
): a normal completion containing a Promise
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
</h1>
<dl class="header">
<dt>for</dt>
<dd>a built-in async function object _F_</dd>
</dl>
<emu-alg>
1. Let _callerContext_ be the running execution context.
1. If _callerContext_ is not already suspended, suspend _callerContext_.
1. Let _calleeContext_ be a new execution context.
1. Set the Function of _calleeContext_ to _F_.
1. Let _calleeRealm_ be _F_.[[Realm]].
1. Set the Realm of _calleeContext_ to _calleeRealm_.
1. Set the ScriptOrModule of _calleeContext_ to *null*.
1. Perform any necessary implementation-defined initialization of _calleeContext_.
1. Push _calleeContext_ onto the execution context stack; _calleeContext_ is now the running execution context.
1. Let _promiseCapability_ be ! NewPromiseCapability(%Promise%).
1. Let _resultsClosure_ be a new Abstract Closure with no parameters that captures _F_, _thisArgument_, and _argumentsList_ and performs the following steps when called:
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
1. Return the Completion Record that is the result of evaluating _F_ in a manner that conforms to the specification of _F_. _thisArgument_ is the *this* value, _argumentsList_ provides the named parameters, and the NewTarget value is *undefined*.
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
1. Perform AsyncFunctionStart(_promiseCapability_, _resultsClosure_).
1. Remove _calleeContext_ from the execution context stack and restore _callerContext_ as the running execution context.
1. Return _promiseCapability_.[[Promise]].
michaelficarra marked this conversation as resolved.
Show resolved Hide resolved
</emu-alg>
</emu-clause>

<emu-clause id="sec-built-in-async-function-objects-construct" type="internal method">
<h1>
[[Construct]] (
_argumentsList_: a List of ECMAScript language values,
_newTarget_: a constructor,
): either a normal completion containing an Object or a throw completion
</h1>
<dl class="header">
<dt>for</dt>
<dd>a built-in async function object _F_</dd>
</dl>
<emu-alg>
1. Throw a *TypeError* exception.
</emu-alg>
</emu-clause>
</emu-clause>

<emu-clause id="sec-built-in-exotic-object-internal-methods-and-slots">
<h1>Built-in Exotic Object Internal Methods and Slots</h1>
<p>This specification defines several kinds of built-in exotic objects. These objects generally behave similar to ordinary objects except for a few specific situations. The following exotic objects use the ordinary object internal methods except where it is explicitly specified otherwise below:</p>
Expand Down Expand Up @@ -46053,7 +46107,7 @@ <h1>Async Functions Abstract Operations</h1>
<h1>
AsyncFunctionStart (
_promiseCapability_: a PromiseCapability Record,
_asyncFunctionBody_: a |FunctionBody| Parse Node or an |ExpressionBody| Parse Node,
_asyncFunctionBody_: a |FunctionBody| Parse Node, an |ExpressionBody| Parse Node, or an Abstract Closure with no parameters,
): ~unused~
</h1>
<dl class="header">
Expand All @@ -46071,7 +46125,7 @@ <h1>
<h1>
AsyncBlockStart (
_promiseCapability_: a PromiseCapability Record,
_asyncBody_: a Parse Node,
_asyncBody_: a Parse Node or an Abstract Closure with no parameters,
_asyncContext_: an execution context,
): ~unused~
</h1>
Expand All @@ -46081,7 +46135,13 @@ <h1>
1. Assert: _promiseCapability_ is a PromiseCapability Record.
1. Let _runningContext_ be the running execution context.
1. [fence-effects="user-code"] Set the code evaluation state of _asyncContext_ such that when evaluation is resumed for that execution context the following steps will be performed:
1. Let _result_ be Completion(Evaluation of _asyncBody_).
1. If _asyncBody_ is a Parse Node, then
1. Let _result_ be Completion(Evaluation of _asyncBody_).
1. Else,
1. Assert: _asyncBody_ is an Abstract Closure with no parameters.
1. Let _result_ be Completion(_asyncBody_()).
1. If _result_ is a normal completion, then
1. Set _result_ to Completion Record { [[Type]]: ~return~, [[Value]]: _result_.[[Value]], [[Target]]: ~empty~ }.
1. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
1. Remove _asyncContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
1. If _result_.[[Type]] is ~normal~, then
Expand Down