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

Layering: Refine execution context requirements for Jobs #1984

Merged
merged 1 commit into from Aug 8, 2020

Conversation

syg
Copy link
Contributor

@syg syg commented May 7, 2020

Closes #1930

This PR intentionally leaves the actual steps to ensure there's a usable execution context implementation/host-defined.

My philosophy here is to be as loose as we can be without tripping assertions.

Some nuances:

  • The choice of Realm is loosely constrained, just that it's non-null. The particular Realm only matters when a Job schedules a non-user function to run. If the Job runs a user function, calling that function will push a new execution context with that function's Realm.
  • The active script or module could be null. The requirement is that Jobs that evaluate user code should propagate the active script at the queueing time to the time when the Job is run. I don't know any real world examples where we must propagate forward a null active script, but I don't see any reasons in allowing that possibility to be conformant.

@syg syg added editorial change layering Related to interactions with other specs labels May 7, 2020
@syg syg requested a review from a team May 7, 2020 22:56
spec.html Outdated
@@ -7636,11 +7645,15 @@ <h1>HostEnqueuePromiseJob ( _job_, _realm_ )</h1>
<p>HostEnqueuePromiseJob is a host-defined abstract operation that schedules the Job Abstract Closure _job_ to be performed, at some future time. The Abstract Closures used with this algorithm are intended to be related to the handling of Promises, or otherwise, to be scheduled with equal priority to Promise handling operations.</p>
<p>The _realm_ parameter is passed through to hosts with no normative requirements; it is either *null* or a Realm.</p>

<p>The implementation of HostEnqueuePromiseJob must conform to the requirements in <emu-xref href="#sec-jobs"></emu-xref>. Additionally, Jobs enqueued by this hook must conform to all of the following requirements:</p>
<ul>
<li>Let _scriptOrModule_ be GetActiveScriptOrModule(). If _realm_ is not *null*, when the Job runs, the implementation must perform implementation-defined steps such that execution is prepared to evaluate code in _scriptOrModule_ before the _job_ Abstract Closure is called.</li>
Copy link
Member

Choose a reason for hiding this comment

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

I believe _realm_ is separate from the realm we need to worry about here. The current realm record when HostEnqueuePromiseJob is called needs to be the current realm record when the job is executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current realm record when HostEnqueuePromiseJob is called needs to be the current realm record when the job is executed.

There is no normative requirement for this.

Copy link
Member

Choose a reason for hiding this comment

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

The job uses the current realm to create objects that are exposed to user code. How is that not a normative requirement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're referring specifically to PromiseResolveThenableJob's use of CreateResolvingFunctions, yes?

There is a normative requirement that the resolving functions are created in some Realm, but not in the same Realm. I'd want the requirements on hosts to be as lax as we can make it without breaking things. Does creating the resolving functions in a different realm break anything?

(I do see that there is an ambiguity in the current PR that won't prepare to evaluate code if the thenable is a revoked proxy where realm is null. That case still needs to create the resolving functions, even though they become unobservable in that case. If you're operating on the assumption that all steps are observable all the time, the [[Realm]] assert in CreateBuiltinFunction will fail.)

Copy link
Member

Choose a reason for hiding this comment

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

The specification, before the change that underspecified this, was very clear about using the current realm. We shouldn't change that, and I see zero benefit to saying "hosts can create objects in whatever random realm they want"

Copy link
Member

@devsnek devsnek May 8, 2020

Choose a reason for hiding this comment

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

Actually with a revoked proxy, the exception would be passed as a rejection to rejection handlers or rejection tracking, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point on the TypeError that gets created in the PromiseThenableJob case; this PR needs to say PromiseThenableJobs need a valid Realm at all times.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this PR is a strict improvement on the current spec as-is.

It is not clear to me if it makes sense to normatively require that the Realm of the top of the execution context stack when the job executes is the same as the Realm of the top of the execution context stack when the Host is invoked. This part of the interaction between specs/hosts/implementations is complicated enough that it is hard for me to be sure whether or not anyone actually wants to violate that potential normative requirement. It may have been required prior to the layering PR, but the point of the layering PR was that that code was not usable for most hosts, so I don't really care what it said.

So, for now, I would propose that we add a NOTE to the line this conversation is attached to which reads

Note: typically this results in the execution context stack having as its topmost item an execution context whose Realm component was the Realm component of the topmost item on the execution stack when HostEnqueuePromiseJob was invoked, although this is not a normative requirement

and amend that later if we become confident it should be a normative requirement.

Copy link
Member

Choose a reason for hiding this comment

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

In the case of that TypeError, it would at least matter for V8's Error.prepareStackTrace API, since it is scoped by realm.

Copy link
Contributor Author

@syg syg May 8, 2020

Choose a reason for hiding this comment

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

Whatever V8 does is definitely not normative. We do need to say that a Realm must be present for the error, but that it must be the Realm of the active script at the time of queuing is undesirable for me, see #1984 (comment).

spec.html Outdated
</ol>
</li>
<li>Only one Job may be actively undergoing evaluation at any point in time.</li>
<li>Once evaluation of a Job starts, it must run to completion before evaluation of any other Job starts.</li>
<li>The Abstract Closure must return a normal completion, implementing its own handling of errors.</li>
</ul>

<p>At any point in time, an execution is <dfn id="job-preparedtoevaluatecode">prepared to evaluate code</dfn> in _scriptOrModule_ (a Module Record, a Script Record, or *null*) when all of the following conditions are true:</p>
Copy link
Member

Choose a reason for hiding this comment

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

what is "an execution"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A particular evaluation of ECMAScript code: what evaluation steps were taken, and if SABs were used, stuff related to the memory model. I'm open to other things that "prepared to execute code" can be applied to, executions (more precisely, execution prefixes since we're talking about a moment in time) made the most sense to me.

spec.html Outdated Show resolved Hide resolved
@syg syg removed the request for review from a team May 8, 2020 02:38
@syg
Copy link
Contributor Author

syg commented May 8, 2020

Unrequesting review for now. Layering here is pretty messy, and this PR is currently incorrect wrt to devsnek's exception point, but also needs to think through more carefully where the lax Realm requirements is needed to see if there are other bugs.

Part of my motivation for pushing for lax Realm requirements is because of the incumbent settings object stack mess in HTML. Incumbents, most of the time, but not always, matches up with the Realm of active user code. But because it doesn't always, it seemed less headache all around to make the requirements on the ecma262 lax: if HTML needs to use the incumbent Realm as the executing context's Realm for some jobs some time instead of the active script's Realm at the time of queuing a Job, I don't see why ecma262 needs to care.

As someone who will most likely be doing the bulk of layering editorial reviews and some authoring for future APIs, extra constraints here are onerous in having to re-page in incumbent machinery.

Copy link
Member

@littledan littledan left a comment

Choose a reason for hiding this comment

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

This seems like a really good improvement.

I wonder if we could build on this concept of "prepared to evaluate code" to specify, once and for all, that hosts/implementations have to preserve the single-threaded nature of JS and not call in "re-entrantly". But I'm not sure how exactly to differentiate that from a fair-game synchronous callback; I don't have any particular wording in mind.

@syg
Copy link
Contributor Author

syg commented May 8, 2020

I wonder if we could build on this concept of "prepared to evaluate code" to specify, once and for all, that hosts/implementations have to preserve the single-threaded nature of JS and not call in "re-entrantly". But I'm not sure how exactly to differentiate that from a fair-game synchronous callback; I don't have any particular wording in mind.

That's an interesting idea I haven't given much thought to yet. Specifying reentrancy in a more robust manner would be good all around. What's an example of a fair-game synchronous callback? You mean ones that don't "run script", whatever that might mean more formally?

@syg
Copy link
Contributor Author

syg commented May 8, 2020

Motivations and explanations for the changes in ef0495a:

  1. When the handler is not undefined in a PromiseReactionJob, or when the then function is not undefined in PromiseResolveThenableJob, the realm passed to HostEnqueuePromiseJob is never null. This used to be null for revoked Proxies, but it was pointed out that error creation for throwing the TypeError on revoked Proxies still needs to happen in some Realm. In that corner case, this PR uses the current Realm. Unfortunately implementations disagree here:
    1. Using the current Realm for revoked proxies matches what V8 does.
    2. SpiderMonkey uses the Realm of the then callable regardless whether it is a revoked Proxy. SpiderMonkey's behavior isn't speccable currently since the only objects the spec track creation Realms on are functions. cc @codehag for visibility.
    3. I don't know what Safari does.
  2. The prepared to evaluate code property is now specified on Job Abstract Closures to be a precondition that must hold on every invocation.
  3. The Realm requirement for the prepared to evaluate code property remains lax: it doesn't call for a specific Realm to be the current Realm at the time some Job Abstract Closure runs. This is web reality: HTML uses different Realms depending on if there's an active script to propagate, for instance.
  4. Propagating the active script or module is split out to its own property on Job Abstract Closures. This is to leave the freedom for hosts to null out the active script on a job-by-job basis. No Jobs do this now but e.g. HTML nulls it out for some of its own callbacks, so this is a bit of futureproofing.

cc @domenic for visibility.

@syg syg added the spec bug label May 8, 2020
@syg
Copy link
Contributor Author

syg commented May 8, 2020

Point 1 above is a spec bug in that it's been underspecified AFAICT both in 262 and in HTML and the current state is incoherent.

However, though we don't have interop, this PR isn't a normative change, since what Realm the job initially runs in has ultimately been at host discretion. So the V8, SM, and possibly Safari disagreement above is a web thing imo. The fact that SpiderMonkey uses some Realm that's unutterable in ecma262-ese doesn't really matter, since we don't have any normative requirement on the entry Realm for Promise Jobs anyway.

@syg
Copy link
Contributor Author

syg commented May 8, 2020

@bakkot That note about the Realm being the same would've been good, but after poring over the settings object stacks in HTML again today, it's not even typically true. :(

To repeat some of what I commented above, the realm in the HostEnqueuePromiseJob is used by HTML for its entry settings object concept. By the time the abstract closure gets called, the entry settings object's Realm is the topmost on the stack iff there isn't an active script. If there's an active script to propagate for import.meta, then the active script's Realm is the topmost. More typically, this realm is never used since the handler function's [[Call]] will push a new execution context with the function's own Realm.

@syg syg requested a review from a team May 8, 2020 22:46
@syg syg added editor call to be discussed in the next editor call and removed editor call to be discussed in the next editor call labels May 12, 2020
spec.html Outdated Show resolved Hide resolved
spec.html Outdated
</emu-note>

<p>A Job _job_ <dfn id="job-propagatesactivescriptormodule">propagates the script or module</dfn> _scriptOrModule_ (a Script Record, a Module Record, or *null*) if all of the following conditions are true whenever _job_ is called:</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not clear to me whether "whenever job is called" means here. A Job is just an abstract closure. It looks like this is trying to make an assertion about the ways in which the Job is invoked, but that's not really a property of the Job itself (as currently described).

Perhaps it would be better to speak of "an invocation of a Job" here? (If necessary, we could define that term above, by appending to "Such operations accept a Job Abstract Closure as the parameter and schedule it to be performed at some future time" the phrase "; this is called an invocation of the job".)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Invocation sgtm.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ended up rephrasing step 2 as "Invoke the Job Abstract Closure" from "Call the Abstract Closure". The definition of Abstract Closure already talks about invocations, so I don't think we need to further define what "invocation of a job" means.

spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
@syg
Copy link
Contributor Author

syg commented Jun 29, 2020

Note that this PR is not designed to be gated on #1951 and uses "implementation-defined" to be more consistent with the current spec language. #1951 or this will be revised, whichever lands first.

spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
Copy link
Contributor

@bakkot bakkot left a comment

Choose a reason for hiding this comment

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

:shipit:

@syg syg added the editor call to be discussed in the next editor call label Jul 1, 2020
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
@syg syg removed the editor call to be discussed in the next editor call label Jul 1, 2020
Copy link
Member

@devsnek devsnek left a comment

Choose a reason for hiding this comment

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

This seems to work, though its a bit prose-y. I guess there isn't a clean way to specify it with more concrete algorithm steps?

@syg
Copy link
Contributor Author

syg commented Jul 9, 2020

I guess there isn't a clean way to specify it with more concrete algorithm steps?

I don't think so, since we're trying to describe constraints instead of prescribing exact steps. Even if it were formatted as concrete steps, it'd still look like "Perform steps such that <constraint> holds", which seems only more verbose to me.

spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
@syg
Copy link
Contributor Author

syg commented Aug 6, 2020

Rebased

@ljharb ljharb self-assigned this Aug 7, 2020
@ljharb
Copy link
Member

ljharb commented Aug 7, 2020

This has the requisite consensus and reviews; I'll plan to land it later tonight or tomorrow unless there's any remaining feedback (cc @tc39/ecma262-editors)

@ljharb ljharb merged commit a59a820 into tc39:master Aug 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
editorial change layering Related to interactions with other specs spec bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Jobs underspecified
5 participants