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

Normative: Allow Atomics methods on ArrayBuffers #1908

Merged
merged 1 commit into from
Aug 11, 2020

Conversation

syg
Copy link
Contributor

@syg syg commented Mar 20, 2020

This is to align with wasm allowing atomics to work on non-shared memory.

Most methods work in the obvious deterministic way on ArrayBuffers, with the exceptions of:

  • Atomics.wait still throws on ArrayBuffers.
  • Atomics.notify always returns 0 on ArrayBuffers.

@syg syg added normative change Affects behavior required to correctly evaluate some ECMAScript source text needs consensus This needs committee consensus before it can be eligible to be merged. labels Mar 20, 2020
@syg syg requested a review from a team March 20, 2020 22:16
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
spec.html Outdated
@@ -37657,7 +37657,6 @@ <h1>ValidateSharedIntegerTypedArray ( _typedArray_ [ , _waitable_ ] )</h1>
1. If ! IsUnclampedIntegerElementType(_type_) is *false* and ! IsBigIntElementType(_type_) is *false*, throw a *TypeError* exception.
1. Assert: _typedArray_ has a [[ViewedArrayBuffer]] internal slot.
1. Let _buffer_ be _typedArray_.[[ViewedArrayBuffer]].
1. If IsSharedArrayBuffer(_buffer_) is *false*, throw a *TypeError* exception.
Copy link
Member

Choose a reason for hiding this comment

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

this part is normative, but not part of the rename; can it be separated into its own commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Preferably not because the rename would be misleading without changing this line. The only reason it's renamed is because the AO now applies to all TypedArrays, not just SAB-backed ones.

Copy link
Member

Choose a reason for hiding this comment

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

Sure, but the new name isn't wrong even if it only applies to shared array buffers.

Copy link
Contributor Author

@syg syg Mar 24, 2020

Choose a reason for hiding this comment

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

I don't understand why I would land a renaming PR that is less clear, separately.

Like I said, I wouldn't rename this at all without the normative change. And since there's no mechanical advantage to having a separate commit here I would not split it.

Copy link
Member

@ljharb ljharb Mar 24, 2020

Choose a reason for hiding this comment

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

right, but it makes the commit separable, even if it's in the same PR. iow, this commit is normative, not editorial, as it's currently labelled.

(iow, you do have a separate commit here, labelled editorial, so if there's no mechanical advantage, why would you have the commit be separate?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you saying you'd prefer that if this were a separate commit at all, if that separate commit must be normative, it should just be folded into the other one? That's fine with me. It ended up being a separate commit because it was a logical chunk of work.

Copy link
Member

Choose a reason for hiding this comment

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

mainly just that i'd prefer individual commits be atomic and labelled properly; if the intention is to fold them all up, then it doesn't matter.

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

syg commented Mar 23, 2020

Addressed review and rebased.

@ljharb ljharb added has consensus This has committee consensus. needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 and removed needs consensus This needs committee consensus before it can be eligible to be merged. labels Mar 31, 2020
@rwaldron
Copy link
Contributor

@syg I'm going to add this to the end of the priority testing queue

@syg
Copy link
Contributor Author

syg commented Mar 31, 2020

@erights raised the following good question: "Are different optimization allowed between a single-agent program that uses Atomics on a non-shared SAB and a single-agent program that uses Atomics on an AB?"

If one takes different optimizations to mean different observable behavior, then the answer is no. On paper, since Atomics on ABs do not introduce SeqCst events (or, indeed, any events), an implementation is allowed to do things to Atomics on ABs that it's not allowed to do to Atomics on SABs, like coalesce writes, reorder them, etc. But it's only allowed to do that if it can prove that such changes are non-observable inside that single agent. SABs need extra guidance because observability is extended to the agent cluster -- proving single agent (non-)observability is insufficient.

However, if one takes different optimizations to mean can different codegen be observed by something outside of the spec's normative control like devtools, then the answer is yes. This is something the spec does not legislate anyhow.

In practice, I reckon the answer to both interpretations above is no. I see no reason why compiler authors would go the length of writing optimizations for Atomics used on ABs instead of reusing the same optimizations as Atomics used on SABs.

Cc @tlively and @lars-t-hansen for thoughts from the wasm side.

@tlively
Copy link

tlively commented Mar 31, 2020

I would expect different (semantics-preserving) optimizations to be allowed for the reasons you mention. I assume doing going out of the way to do extra optimizations of unshared atomic operations would be low priority for implementors since that is a rather niche situation. On the other hand, it might not be difficult to arrange for a compiler to notice that the atomics are not shared early in the pipeline and lower them to nonatomic operations that can subsequently be optimized using the same code paths as normal reads and writes.

@erights
Copy link

erights commented Apr 1, 2020

To be clear, I was asking, not advocating. If the observable behavior is already equivalent, then we're done.

I do not think that the spec for AB should be loosened in any way, to enable optimizations that we're not currently doing for AB. Such equivalence of allowed behavior is not attractive to me, even if everyone else were happy with it.

Let me ask a different question: If the allowed behavior is not equivalent: Is the allowed observable behavior on AB a subset of the allowed observable behavior on SAB? If not, I'd like to understand the difference. Until I do understand the difference, I'm still not yet advocating that anything be changed.

@syg
Copy link
Contributor Author

syg commented Apr 1, 2020

Let me ask a different question: If the allowed behavior is not equivalent: Is the allowed observable behavior on AB a subset of the allowed observable behavior on SAB? If not, I'd like to understand the difference.

The allowed observable single-thread behavior of AB and the allowed observable behavior of non-shared SAB are the same. What differs is what codegen is allowed, but since there are no concurrent ways to observe the SAB since it's non-shared, the differently generated code have the same observable behavior.

The only way you might observe a difference is a non-normative host-implemented observation utility like a debugger.

tl;dr The observable behavior is already equivalent given how people currently understand how to optimize single-threaded JS, so I think we're done.

@lars-t-hansen
Copy link
Contributor

I would expect different (semantics-preserving) optimizations to be allowed for the reasons you mention. I assume doing going out of the way to do extra optimizations of unshared atomic operations would be low priority for implementors since that is a rather niche situation. On the other hand, it might not be difficult to arrange for a compiler to notice that the atomics are not shared early in the pipeline and lower them to nonatomic operations that can subsequently be optimized using the same code paths as normal reads and writes.

I agree.

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

anba commented May 14, 2020

This seems to be missing detached array buffer checks.

@anba
Copy link
Contributor

anba commented Jun 22, 2020

The Set part of GetModifySetValueInBuffer for non-shared buffers seems to be missing, too. And as currently written, it returns the result after applying op for non-shared buffers, whereas for shared buffers, the original read value is returned.

@syg
Copy link
Contributor Author

syg commented Jun 22, 2020

Yeah this PR is very wrong. Thanks @anba, will fix and rebase.

@syg
Copy link
Contributor Author

syg commented Jun 22, 2020

Rebased and fixed the issues @anba pointed out.

spec.html Outdated Show resolved Hide resolved
spec.html Outdated
<p>The abstract operation ValidateSharedIntegerTypedArray takes argument _typedArray_ and optional argument _waitable_ (a Boolean). It performs the following steps when called:</p>
<emu-clause id="sec-validateintegertypedarray" aoid="ValidateIntegerTypedArray" oldid="sec-validatesharedintegertypedarray">
<h1>ValidateIntegerTypedArray ( _typedArray_ [ , _waitable_ ] )</h1>
<p>The abstract operation ValidateIntegerTypedArray takes argument _typedArray_ and optional argument _waitable_ (a Boolean). It performs the following steps when called:</p>
Copy link
Member

Choose a reason for hiding this comment

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

it seems weird for me (both before and after this change) that an abstract operation called "validate" returns something. is there a better name for this abstract op that conveys that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd consider that outside the scope of this PR.

One option is to tack on another verb, like ValidateIntegerTypedArrayAndGetBuffer and ValidateAtomicAccessAndGetIndex, that's pretty verbose.

Another option is to ToValidIntegerTypedArrayBuffer, but it might be hard to understand the intent of the AO without reading what it does.

In any case, we should do those as a follow-on PR.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe GetSharedIntegerTypedArrayBuffer?

I'm not sure it's outside the scope; you're renaming the op in this PR, so any concerns about the name seem thus in scope of the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It no longer applies to shared buffers only, which is why this renames that op to delete the word "shared". It doesn't follow to me that a complete rename is also in scope.

Also GetIntegerTypedArrayBuffer doesn't capture most of what the AO does: validating the kind of TypedArray the argument is depending on the parameters.

@anba
Copy link
Contributor

anba commented Jun 23, 2020

We need to have additional detached buffer checks:

  • AtomicReadModifyWrite, after ToBigInt/ToInteger.
  • Atomics.compareExchange, after ToBigInt/ToInteger.
  • Atomics.store, after ToBigInt/ToInteger.
  • AtomicLoad, after ValidateAtomicAccess.
  • (I'm not sure if we also want to check for detached buffers in Atomics.notify, because we're returning 0 anyway.)

ValidateAtomicAccess also needs to a slight modification: Step 3 ("Let length be typedArray.[[ArrayLength]]") should be moved before step 2 ("Let accessIndex be ? ToIndex(requestIndex)."), because we don't want to read [[ArrayLength]] on a typed array with a detached buffer. I'm not sure that rule is documented anywhere, but IIRC Allen made sure this never happens when he wrote the ES6 spec. And it also makes things a bit easier for implementors, because some implementations return 0 for [[ArrayLength]] with a detached buffer, but per spec the original length should be returned.

@syg
Copy link
Contributor Author

syg commented Jun 23, 2020

  • AtomicReadModifyWrite, after ToBigInt/ToInteger.
  • Atomics.compareExchange, after ToBigInt/ToInteger.
  • Atomics.store, after ToBigInt/ToInteger.
  • AtomicLoad, after ValidateAtomicAccess.

For these, I'll delay the detached check to be as late as possible. I don't see a good reason to check detachment early, do you, @anba?

  • (I'm not sure if we also want to check for detached buffers in Atomics.notify, because we're returning 0 anyway.)

I think, out of consistency of errors, yes. Trying to access a detached buffer is an egregious enough behavior that we shouldn't hide it even if we could return an answer for Atomics.notify.

ValidateAtomicAccess also needs to a slight modification: Step 3 ("Let length be typedArray.[[ArrayLength]]") should be moved before step 2 ("Let accessIndex be ? ToIndex(requestIndex)."), because we don't want to read [[ArrayLength]] on a typed array with a detached buffer. I'm not sure that rule is documented anywhere, but IIRC Allen made sure this never happens when he wrote the ES6 spec. And it also makes things a bit easier for implementors, because some implementations return 0 for [[ArrayLength]] with a detached buffer, but per spec the original length should be returned.

It looks like this invariant is already broken in the following place. IIEO [[DefineProperty]] calls IsValidIntegerIndex, which gets [[ArrayLength]] without checking if the underlying buffer is detached. It's not until step 3.b.vi.2, when IntegerIndexedElementSet is called, that the detachment check is done. I think the property you said holds everywhere else.

Noted about some implementations returning 0 for [[ArrayLength]] for views with detached buffers. (V8 is incidentally buggy for [[DefineProperty]]: we throw a RangeError instead of a TypeError.)

@syg
Copy link
Contributor Author

syg commented Jun 24, 2020

I'm convinced by the consistency argument that most AB methods already do multiple detach checks. I've added a second detach check before [[ArrayLength]] where appropriate. Notably, AtomicLoad, Atomics.notify, and Atomics.wait only have a single check.

@anba
Copy link
Contributor

anba commented Jun 24, 2020

I'd prefer to have the first detached array buffer check in ValidateIntegerTypedArray instead of ValidateAtomicAccess. Similar to how ValidateTypedArray works.


We could even call ValidateTypeArray here, if we want to reduce some duplicate steps:

  1. If waitable is not present, set waitable to false.
  2. Let buffer be ? ValidateTypedArray(typedArray).
  3. Let typeName be typedArray.[[TypedArrayName]].
  4. Let type be the Element Type value in Table 61 for typeName.
  5. If waitable is true, then
    1. If typeName is not "Int32Array" or "BigInt64Array", throw a TypeError exception.
  6. Else,
    1. If ! IsUnclampedIntegerElementType(type) is false and ! IsBigIntElementType(type) is false, throw a TypeError exception.
  7. Return buffer.

@syg
Copy link
Contributor Author

syg commented Jun 24, 2020

I thought about that, but then we would have 2-3 detach checks for some Atomics on ABs:

  1. In ValidateIntegerTypedArray
  2. In ValidateAtomicAccess, after ? ToIndex, to preserve the property you want of not getting [[ArrayLength]] on detached TAs.
  3. Before doing the actual operation, depending on the op.

Edit: In #1908 (comment), my being convinced of having multiple detach checks doesn't take away from minimizing the number of checks.

@anba
Copy link
Contributor

anba commented Jun 24, 2020

2\. In ValidateAtomicAccess, after ? ToIndex, to preserve the property you want of not getting [[ArrayLength]] on detached TAs.

That one can be changed without a detached check by simply moving the access to [[ArrayLength]] before calling ToIndex. Basically what implementations like SpiderMonkey or V8 already have to do.

@syg
Copy link
Contributor Author

syg commented Jun 24, 2020

That one can be changed without a detached check by simply moving the access to [[ArrayLength]] before calling ToIndex. Basically what implementations like SpiderMonkey or V8 already have to do.

That seems fine, I'll do that.

I think misunderstood your original implementation-freedom point: you were saying even though [[ArrayLength]] isn't zeroed on buffer detaching, implementations might want to do that. I interpreted that to mean you wanted ValidateAtomicAccess to throw a TypeError if the buffer were detached. While what you were saying was it's fine to throw a RangeError on detached buffer, as long as it's explicit in the spec algorithm that the value of [[ArrayLength]] is manually cached in a local before possible detachment.

@anba
Copy link
Contributor

anba commented Jun 24, 2020

Yes, exactly that. Sorry if I couldn't make it clear enough!

spec.html Outdated Show resolved Hide resolved
@rwaldron rwaldron added has test262 tests and removed needs test262 tests The proposal should specify how to test an implementation. Ideally via github.com/tc39/test262 labels Jun 30, 2020
@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 Jul 29, 2020
@ljharb ljharb requested review from michaelficarra, bakkot and a team July 31, 2020 05:14
@ljharb ljharb self-assigned this Jul 31, 2020
spec.html Outdated Show resolved Hide resolved
spec.html Outdated Show resolved Hide resolved
Allow Atomics methods to work on ArrayBuffers in a fully deterministic
fashion. Atomics.wait still throws when used on ArrayBuffers, while
Atomics.notify always returns 0.
@ljharb ljharb merged commit ac0740b into tc39:master Aug 11, 2020
pull bot pushed a commit to Alan-love/v8 that referenced this pull request Aug 13, 2020
This reached consensus in the March 2020 TC39.
tc39/ecma262#1908

This aligns JS with wasm, which allows atomics operations on non-shared
linear memory.

Bug: v8:10687, v8:9921
Change-Id: I7b60473b271cee6bccb342e97a4fd3781aedddb4
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2330802
Commit-Queue: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69392}
h2oche pushed a commit to h2oche/ecma262 that referenced this pull request Aug 16, 2020
Allow Atomics methods to work on ArrayBuffers in a fully deterministic
fashion. Atomics.wait still throws when used on ArrayBuffers, while
Atomics.notify always returns 0.
h2oche pushed a commit to h2oche/ecma262 that referenced this pull request Aug 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has consensus This has committee consensus. has test262 tests normative change Affects behavior required to correctly evaluate some ECMAScript source text
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants