-
Notifications
You must be signed in to change notification settings - Fork 50
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
Float timeout for wait #36
Comments
AIUI, the typical assumption of timeouts are that they provide a guarantee that it won't be less than the specified value, so it is OK for the implementation to round up to the granularity they provide. My quick calculations show that we have nanosecond precision for timeouts of up to 142 years with a 64-bit float given we use milliseconds as the base unit. Not sure any OS can actually provide timeouts at that granularity anyway.
I chose milliseconds to match ES Also: the primary reason to use float was to provide a way to specify an infinite timeout without having a special value. That said, we already treat negative values as special for |
And to round that out, ISTR very early versions of the SAB proposal used an integer number of nanoseconds or microseconds, but with only 32-bit integers in ES that's not very appealing. In ES, "infinite" timeout could be handled by passing IMO nothing stops us from having a different API for Wasm than for ES, and an int64 number of nanoseconds might be just fine, but I think changing the base unit from milliseconds to seconds while still keeping the argument a floating point number is just inviting trouble. |
I'm still a bit confused how that is supposed to be handled. AFAICT, even
OS-level time(r)s are integers everywhere, e.g. C and POSIX have `time_t`,
which is in seconds. POSIX timers use `timespec`, which is a pair of a
`time_t` and a long nanoseconds value. If I were to map a float timeout to
this faithfully, I would presumably need values out of range of `time_t` to
be implemented via looping over multiple OS timer events? And in fact, the
number of iterations this loop may require is itself far out of range for a
single 64 bit integer value, so you'd even need to implement nested loops
(up to 1024/64 = 16 levels of nesting, if I'm not mistaken).
Now, you may argue that this isn't practically relevant, but it would be
rather fishy to prescribe something that nobody is going to implement
faithfully. Given the hardware/OS-oriented nature of the WebAssembly
instruction set in particular, I would feel much more comfortable with
something akin to what hardware/OSes do.
https://w3ctag.github.io/design-principles/#milliseconds
Well, this is an assembly-level instruction, not a web API.
|
In principle, yes. Firefox had fairly elaborate code here, once, to handle that. (Not quite that elaborate though, just a single loop.) A revamp of threading code changed it and now it looks like we just clamp it to the number of nanoseconds representable in a signed 64-bit int, which really should be plenty - that's roughly a 1000-day wait, if I'm not mistaken.
Again, the JS API is what it is simply to be conventional. I agree that specifying something different for wasm is fine. |
If it's gonna get clamped to an integer anyway, isn't it rather pointless
to have a float in the first place? Neither the precision nor the value
range it provides is actually usable.
Leaving the JS API alone is perfectly okay, I mainly care about the
instruction.
|
OK, I'm fine with switching this to a signed 64-bit int. Seems like we will want at least microsecond granularity, but given that timespec is nanosecond granularity, we should probably just use that. I got 292 years for signed 64-bit at nanosecond precision, did I miscalculate? 2**63 / (1e9 * 60 * 60 * 24 * 365) Then we can say that value < 0 represent an infinite wait. |
More likely I did, I did this in my head and tried to be conservative :) |
(from #43) I think we need to justify why we'd diverge from https://w3ctag.github.io/design-principles/#milliseconds that @domenic points out. This is weirdly inconsistent with the JS side (both ns and using integer instead of float). I don't really understand the argument for this change: perceived implementation complexity is already there for JS, so the change saves engines zero. The discrepancy does, however, add complexity to users because JS and wasm would now be different. |
WebAssembly is not JS. Or is it? Its most popular implementations are built around JS engines today, but that isn't the universal case. In my opinion, WebAssembly should bias toward hardware and OS trends rather than JS whenever there's controversy like this. |
WebAssembly isn't JS, it isn't web-exclusive, and it certainly isn't assembly. I agree we should bias towards whatever low-level things when we can, but here I simply don't see integers as being better than floating-point. I actually like that floating-point has a concept for infinity which allows us to naturally express "forever" instead of (ab)using negative values. Not that floating-point is hugely better here, it's merely slightly elegant. When faced with two semi-equal choices, I'd bias towards consistency with the web. In this case that argues for milliseconds, and matching what SAB does. I'm happy to change my point of view if someone has a compelling argument against floating-point and for integers, but I haven't heard it yet. |
Well, this bit of elegance is paid for by the inelegance of having (1)
NaNs, signed zeroes and the like, (2) rounding errors and brittle equality,
and (3) an absurd value range that I'm sure nobody is going to implement
correctly.
Most importantly, it's a serious impedance mismatch with the primitive
mechanism it is meant to give access to.
Floats introduce all sorts of corner cases and surprises, and the usual
advice is to avoid them where possible (*). JavaScript uses floats in this
case because that's all it has -- and we all know how many problems that
creates in all corners of the language. Why have this deficiency leak into
unrelated parts of WebAssembly?
(*) You'll find plenty of sources on the web that recommend against using
floats for counters, timers, currency, embedded systems, etc. Somewhat
related, the infamous patriot missile bug was due to loss of accuracy in a
(single-precision) floating point timer.
|
On Fri, Jun 30, 2017 at 10:36 PM, JF Bastien ***@***.***> wrote:
(from #43 <#43>) I think we
need to justify why we'd diverge from https://w3ctag.github.io/
design-principles/#milliseconds that @domenic <https://github.com/domenic>
points out. This is weirdly inconsistent with the JS side (both ns and
using integer instead of float). I don't really understand the argument for
this change: perceived implementation complexity is already there for JS,
so the change saves engines zero.
Well, to be fair, it only saves engines that already have that
implementation. It would force a new, e.g. non-web implementation, to adopt
the same complexity. This is something we've shied away from on other
fronts; e.g. float->int conversions do not work the way they do in JS, even
though the functionality is already in engines. That's both because it'd
actually be a lot of work for a new engine and the results from JS are not
at all intuitive. I'd put this in the same category. All hardware clocks
that I am aware of reason in terms of integers, so to impose floating point
on this feature requires engine complexity to translate floating point to
integers and back.
The discrepancy does, however, add complexity to users because JS and wasm
would now be different.
If users want floating point wait times, it's no problem to implement it as
a pure wasm library routine. I'd argue that that is cleaner, gives portable
results, and presents no real obstacle to web compatibility.
… —
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#36 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ALnq1Jj4BdMs_ybYyVXYj8JtZcTC_oGuks5sJVxGgaJpZM4OFb-t>
.
|
You never wait for exactly the amount specified: the system simply won't honor that. Any imprecision is therefore totally fine. You wait for at least that amount. Zero, minus zero, subnormals, NaN, infinity are all red herrings here. Waiting for zero or minus zero or negative or subnormal is waiting for no time, you'll be woken up next convenient time. Waiting for NaN is silly but has a defined meaning in SAB. Waiting for infinity is really desirable! |
On 5 July 2017 at 05:57, JF Bastien ***@***.***> wrote:
You never wait for exactly the amount specified: the system simply won't
honor that. Any imprecision is therefore totally fine. You wait for at
least that amount.
According to @lars-t-hansen that is not the case for Firefox. And I doubt
it is the case for any other browser either, because a correct
implementation of this semantics would be rather involved, as I pointed out
above. I'd bet that they all clamp the range somewhere smallish.
For JavaScript that is probably fine -- it essentially treats the value as
an integer, because JS has no proper integers. Wasm OTOH has proper
integers, and such sloppiness and false pretence is undesirable.
|
The pernicious corner cases @rossberg-chromium mentions is another reason why implementing the "float wait" in user-land would be desirable. The application or library can choose to implement input corner cases how it pleases, and changing that doesn't require changing the engine spec. The larger issue here is that float wait functionality isn't provided by any layer under the engine--neither hardware nor operating system. It might be in web browsers by accident already, but we aren't even sure if it's well spec'd. It is therefore additional complexity that isn't needed by most programs. That's an even stronger argument for it to be a library in user-land if possible. Where we have put functionality into engines before, it's either because it is provided by the layer below or it would be too slow or insecure to implement in user-land. I see no inherent performance cost here, as the logic is just float math in a loop around the underlying "integer wait", which WASM engines will compile to fast machine code anyway. It is in better keeping with WebAssembly's minimalist approach to leave float wait times up to the layer above the engine. |
Here's how WebKit implements this: bool ThreadCondition::timedWait(Mutex& mutex, double absoluteTime)
{
if (absoluteTime < currentTime())
return false;
if (absoluteTime > INT_MAX) {
wait(mutex);
return true;
}
int timeSeconds = static_cast<int>(absoluteTime);
int timeNanoseconds = static_cast<int>((absoluteTime - timeSeconds) * 1E9);
timespec targetTime;
targetTime.tv_sec = timeSeconds;
targetTime.tv_nsec = timeNanoseconds;
return pthread_cond_timedwait(&m_condition, &mutex.impl(), &targetTime) == 0;
} At this point we're not making progress online. Happy to discuss at the next face-to-face meeting. |
In other words, any timeout that is not within integer range is simply
ignored completely.
FWIW, V8 does something similar.
So we have three implementations for `wait` in JS, all of which essentially
treat the argument value as an integer, and substitute different defaults
when they are outside the integer range.
|
Right, topping out at 68 years seems acceptable to me, |
This question might just be a matter of perspective. From a whats-in-an-engine point of view, our design should bias toward exposing what's in layers below the engine in a portable, efficient way. As for the web platform, the APIs take floats because that's all JavaScript offers. As @rossberg-chromium found, the Web API has essentially defined the API in terms of integers already. IMO we should make that explicit in WASM and avoid all the unwanted corner cases that arise from floating point. |
I agree with the validity of your argument, except for "avoid all the unwanted corner cases that arise from floating point". I don't think there are any unwanted corner cases here, things seem to just fall out nicely in this case, which never happens with floating-point. So if you drop that part, then yeah it's a matter of taste: expose int because low-level does that, or be consistent because we already expose double. That's easy to put in front of the CG, discuss for 5 minutes, and come to an agreement. Phrased that way I absolutely don't care if my taste loses out, it's super minor and doesn't matter in the grand scheme of things. I just don't want to change something based on what I perceive to be a flawed argument about floating-point. Maybe I'm wrong and there is a corner case I don't understand. If so I'd like to know 😄 |
We discussed this in the July CG meeting and decided to use int64 nanoseconds instead of f64 milliseconds. See https://github.com/WebAssembly/meetings/blob/master/2017/CG-07.md#threads |
The
wait
operator takes its timeout operand as a float. It is not clear to me how this could be implemented correctly for large values. Unless I'm missing something, the implementation would need a timer that has the value range of a large float combined with the resolution of a small float over that entire value range. How would this be handled?Btw, if a float is used anyway, why not stick to the metric system properly and use seconds as the base unit?
The text was updated successfully, but these errors were encountered: