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

Float timeout for wait #36

Closed
rossberg opened this issue Jun 26, 2017 · 22 comments
Closed

Float timeout for wait #36

rossberg opened this issue Jun 26, 2017 · 22 comments

Comments

@rossberg
Copy link
Member

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?

@binji
Copy link
Member

binji commented Jun 26, 2017

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.

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.

Btw, if a float is used anyway, why not stick to the metric system properly and use seconds as the base unit?

I chose milliseconds to match ES Atomics.wait. If we use seconds, then the above calculation is reduced to ~50 days at nanosecond granularity. Still plenty, I think.

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 wait so perhaps it's more consistent that way.

@lars-t-hansen
Copy link

And to round that out, Atomics.wait uses a millisecond base units to match existing APIs that use that, notably performance.now() but there are probably others; I had the impression this representation is pretty standard.

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 undefined or other OOB value, even if it's clunky.

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.

@domenic
Copy link
Member

domenic commented Jun 27, 2017

@rossberg
Copy link
Member Author

rossberg commented Jun 29, 2017 via email

@lars-t-hansen
Copy link

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).

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.

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.

Again, the JS API is what it is simply to be conventional. I agree that specifying something different for wasm is fine.

@rossberg
Copy link
Member Author

rossberg commented Jun 29, 2017 via email

@binji
Copy link
Member

binji commented Jun 29, 2017

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.

@lars-t-hansen
Copy link

I got 292 years for signed 64-bit at nanosecond precision, did I miscalculate?

More likely I did, I did this in my head and tried to be conservative :)

binji added a commit that referenced this issue Jun 30, 2017
@jfbastien
Copy link
Member

(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.

@RyanLamansky
Copy link

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.

@jfbastien
Copy link
Member

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.

@rossberg
Copy link
Member Author

rossberg commented Jul 3, 2017 via email

@titzer
Copy link

titzer commented Jul 3, 2017 via email

@jfbastien
Copy link
Member

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!

@rossberg
Copy link
Member Author

rossberg commented Jul 5, 2017 via email

@titzer
Copy link

titzer commented Jul 5, 2017

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.

@jfbastien
Copy link
Member

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.

@rossberg
Copy link
Member Author

rossberg commented Jul 6, 2017 via email

@jfbastien
Copy link
Member

Right, topping out at 68 years seems acceptable to me, timespec with sizeof(time_t) == 4 does the same and we're just being lazy. Overall I don't really care, but the arguments I've heard so far don't seem correct to me (except the "elegance" argument, I buy that). I think to diverge on something so simple we should get compelling and accurate reasons why we want to change.

@titzer
Copy link

titzer commented Jul 7, 2017

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.

@jfbastien
Copy link
Member

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 😄

@binji
Copy link
Member

binji commented Jul 25, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants