futex-emulation.cc 10 KB
Newer Older
binji's avatar
binji committed
1 2 3 4 5 6 7 8 9 10 11 12
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/futex-emulation.h"

#include <limits>

#include "src/base/macros.h"
#include "src/base/platform/time.h"
#include "src/handles-inl.h"
#include "src/isolate.h"
13
#include "src/numbers/conversions.h"
14
#include "src/objects-inl.h"
15
#include "src/objects/js-array-buffer-inl.h"
binji's avatar
binji committed
16 17 18 19

namespace v8 {
namespace internal {

20 21
using AtomicsWaitEvent = v8::Isolate::AtomicsWaitEvent;

binji's avatar
binji committed
22 23 24 25 26
base::LazyMutex FutexEmulation::mutex_ = LAZY_MUTEX_INITIALIZER;
base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ =
    LAZY_INSTANCE_INITIALIZER;


27 28 29
void FutexWaitListNode::NotifyWake() {
  // Lock the FutexEmulation mutex before notifying. We know that the mutex
  // will have been unlocked if we are currently waiting on the condition
30 31 32
  // variable. The mutex will not be locked if FutexEmulation::Wait hasn't
  // locked it yet. In that case, we set the interrupted_
  // flag to true, which will be tested after the mutex locked by a future wait.
33
  base::MutexGuard lock_guard(FutexEmulation::mutex_.Pointer());
34 35 36
  // if not waiting, this will not have any effect.
  cond_.NotifyOne();
  interrupted_ = true;
37 38 39
}


binji's avatar
binji committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
FutexWaitList::FutexWaitList() : head_(nullptr), tail_(nullptr) {}


void FutexWaitList::AddNode(FutexWaitListNode* node) {
  DCHECK(node->prev_ == nullptr && node->next_ == nullptr);
  if (tail_) {
    tail_->next_ = node;
  } else {
    head_ = node;
  }

  node->prev_ = tail_;
  node->next_ = nullptr;
  tail_ = node;
}


void FutexWaitList::RemoveNode(FutexWaitListNode* node) {
  if (node->prev_) {
    node->prev_->next_ = node->next_;
  } else {
    head_ = node->next_;
  }

  if (node->next_) {
    node->next_->prev_ = node->prev_;
  } else {
    tail_ = node->prev_;
  }

  node->prev_ = node->next_ = nullptr;
}

73
void AtomicsWaitWakeHandle::Wake() {
74 75 76 77 78
  // Adding a separate `NotifyWake()` variant that doesn't acquire the lock
  // itself would likely just add unnecessary complexity..
  // The split lock by itself isn’t an issue, as long as the caller properly
  // synchronizes this with the closing `AtomicsWaitCallback`.
  {
79
    base::MutexGuard lock_guard(FutexEmulation::mutex_.Pointer());
80 81
    stopped_ = true;
  }
82 83
  isolate_->futex_wait_list_node()->NotifyWake();
}
binji's avatar
binji committed
84

85 86
enum WaitReturnValue : int { kOk = 0, kNotEqual = 1, kTimedOut = 2 };

87 88 89 90
Object FutexEmulation::WaitJs(Isolate* isolate,
                              Handle<JSArrayBuffer> array_buffer, size_t addr,
                              int32_t value, double rel_timeout_ms) {
  Object res = Wait32(isolate, array_buffer, addr, value, rel_timeout_ms);
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  if (res->IsSmi()) {
    int val = Smi::ToInt(res);
    switch (val) {
      case WaitReturnValue::kOk:
        return ReadOnlyRoots(isolate).ok();
      case WaitReturnValue::kNotEqual:
        return ReadOnlyRoots(isolate).not_equal();
      case WaitReturnValue::kTimedOut:
        return ReadOnlyRoots(isolate).timed_out();
      default:
        UNREACHABLE();
    }
  }
  return res;
}

107 108 109
Object FutexEmulation::Wait32(Isolate* isolate,
                              Handle<JSArrayBuffer> array_buffer, size_t addr,
                              int32_t value, double rel_timeout_ms) {
110 111 112
  return Wait<int32_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
}

113 114 115
Object FutexEmulation::Wait64(Isolate* isolate,
                              Handle<JSArrayBuffer> array_buffer, size_t addr,
                              int64_t value, double rel_timeout_ms) {
116 117 118 119
  return Wait<int64_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
}

template <typename T>
120 121 122
Object FutexEmulation::Wait(Isolate* isolate,
                            Handle<JSArrayBuffer> array_buffer, size_t addr,
                            T value, double rel_timeout_ms) {
123
  DCHECK_LT(addr, array_buffer->byte_length());
binji's avatar
binji committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143

  bool use_timeout = rel_timeout_ms != V8_INFINITY;

  base::TimeDelta rel_timeout;
  if (use_timeout) {
    // Convert to nanoseconds.
    double rel_timeout_ns = rel_timeout_ms *
                            base::Time::kNanosecondsPerMicrosecond *
                            base::Time::kMicrosecondsPerMillisecond;
    if (rel_timeout_ns >
        static_cast<double>(std::numeric_limits<int64_t>::max())) {
      // 2**63 nanoseconds is 292 years. Let's just treat anything greater as
      // infinite.
      use_timeout = false;
    } else {
      rel_timeout = base::TimeDelta::FromNanoseconds(
          static_cast<int64_t>(rel_timeout_ns));
    }
  }

144 145 146 147 148 149 150 151 152
  AtomicsWaitWakeHandle stop_handle(isolate);

  isolate->RunAtomicsWaitCallback(AtomicsWaitEvent::kStartWait, array_buffer,
                                  addr, value, rel_timeout_ms, &stop_handle);

  if (isolate->has_scheduled_exception()) {
    return isolate->PromoteScheduledException();
  }

153
  Object result;
154
  AtomicsWaitEvent callback_result = AtomicsWaitEvent::kWokenUp;
binji's avatar
binji committed
155

156
  do {  // Not really a loop, just makes it easier to break out early.
157
    base::MutexGuard lock_guard(mutex_.Pointer());
158 159 160 161 162 163 164
    void* backing_store = array_buffer->backing_store();

    FutexWaitListNode* node = isolate->futex_wait_list_node();
    node->backing_store_ = backing_store;
    node->wait_addr_ = addr;
    node->waiting_ = true;

165 166 167
    // Reset node->waiting_ = false when leaving this scope (but while
    // still holding the lock).
    ResetWaitingOnScopeExit reset_waiting(node);
168

169
    T* p = reinterpret_cast<T*>(static_cast<int8_t*>(backing_store) + addr);
170
    if (*p != value) {
171
      result = Smi::FromInt(WaitReturnValue::kNotEqual);
172 173 174 175 176 177 178 179 180 181 182 183
      callback_result = AtomicsWaitEvent::kNotEqual;
      break;
    }

    base::TimeTicks timeout_time;
    base::TimeTicks current_time;

    if (use_timeout) {
      current_time = base::TimeTicks::Now();
      timeout_time = current_time + rel_timeout;
    }

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    wait_list_.Pointer()->AddNode(node);

    while (true) {
      bool interrupted = node->interrupted_;
      node->interrupted_ = false;

      // Unlock the mutex here to prevent deadlock from lock ordering between
      // mutex_ and mutexes locked by HandleInterrupts.
      mutex_.Pointer()->Unlock();

      // Because the mutex is unlocked, we have to be careful about not dropping
      // an interrupt. The notification can happen in three different places:
      // 1) Before Wait is called: the notification will be dropped, but
      //    interrupted_ will be set to 1. This will be checked below.
      // 2) After interrupted has been checked here, but before mutex_ is
      //    acquired: interrupted is checked again below, with mutex_ locked.
      //    Because the wakeup signal also acquires mutex_, we know it will not
      //    be able to notify until mutex_ is released below, when waiting on
      //    the condition variable.
      // 3) After the mutex is released in the call to WaitFor(): this
      // notification will wake up the condition variable. node->waiting() will
      // be false, so we'll loop and then check interrupts.
      if (interrupted) {
207
        Object interrupt_object = isolate->stack_guard()->HandleInterrupts();
208 209
        if (interrupt_object->IsException(isolate)) {
          result = interrupt_object;
210
          callback_result = AtomicsWaitEvent::kTerminatedExecution;
211 212 213
          mutex_.Pointer()->Lock();
          break;
        }
214
      }
binji's avatar
binji committed
215

216
      mutex_.Pointer()->Lock();
binji's avatar
binji committed
217

218 219 220 221
      if (node->interrupted_) {
        // An interrupt occurred while the mutex_ was unlocked. Don't wait yet.
        continue;
      }
binji's avatar
binji committed
222

223 224 225 226 227
      if (stop_handle.has_stopped()) {
        node->waiting_ = false;
        callback_result = AtomicsWaitEvent::kAPIStopped;
      }

228
      if (!node->waiting_) {
229
        result = Smi::FromInt(WaitReturnValue::kOk);
230 231 232
        break;
      }

233 234 235 236
      // No interrupts, now wait.
      if (use_timeout) {
        current_time = base::TimeTicks::Now();
        if (current_time >= timeout_time) {
237
          result = Smi::FromInt(WaitReturnValue::kTimedOut);
238
          callback_result = AtomicsWaitEvent::kTimedOut;
239 240 241 242 243 244 245 246 247 248 249 250 251
          break;
        }

        base::TimeDelta time_until_timeout = timeout_time - current_time;
        DCHECK_GE(time_until_timeout.InMicroseconds(), 0);
        bool wait_for_result =
            node->cond_.WaitFor(mutex_.Pointer(), time_until_timeout);
        USE(wait_for_result);
      } else {
        node->cond_.Wait(mutex_.Pointer());
      }

      // Spurious wakeup, interrupt or timeout.
binji's avatar
binji committed
252
    }
253

254
    wait_list_.Pointer()->RemoveNode(node);
255
  } while (false);
256

257 258 259 260 261 262 263 264
  isolate->RunAtomicsWaitCallback(callback_result, array_buffer, addr, value,
                                  rel_timeout_ms, nullptr);

  if (isolate->has_scheduled_exception()) {
    CHECK_NE(callback_result, AtomicsWaitEvent::kTerminatedExecution);
    result = isolate->PromoteScheduledException();
  }

binji's avatar
binji committed
265 266 267
  return result;
}

268 269
Object FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
                            uint32_t num_waiters_to_wake) {
270
  DCHECK_LT(addr, array_buffer->byte_length());
binji's avatar
binji committed
271 272 273 274

  int waiters_woken = 0;
  void* backing_store = array_buffer->backing_store();

275
  base::MutexGuard lock_guard(mutex_.Pointer());
binji's avatar
binji committed
276 277
  FutexWaitListNode* node = wait_list_.Pointer()->head_;
  while (node && num_waiters_to_wake > 0) {
278 279
    if (backing_store == node->backing_store_ && addr == node->wait_addr_ &&
        node->waiting_) {
binji's avatar
binji committed
280 281
      node->waiting_ = false;
      node->cond_.NotifyOne();
282 283 284
      if (num_waiters_to_wake != kWakeAll) {
        --num_waiters_to_wake;
      }
binji's avatar
binji committed
285 286 287 288 289 290 291 292 293
      waiters_woken++;
    }

    node = node->next_;
  }

  return Smi::FromInt(waiters_woken);
}

294 295
Object FutexEmulation::NumWaitersForTesting(Handle<JSArrayBuffer> array_buffer,
                                            size_t addr) {
296
  DCHECK_LT(addr, array_buffer->byte_length());
binji's avatar
binji committed
297 298
  void* backing_store = array_buffer->backing_store();

299
  base::MutexGuard lock_guard(mutex_.Pointer());
binji's avatar
binji committed
300 301 302 303

  int waiters = 0;
  FutexWaitListNode* node = wait_list_.Pointer()->head_;
  while (node) {
304 305
    if (backing_store == node->backing_store_ && addr == node->wait_addr_ &&
        node->waiting_) {
binji's avatar
binji committed
306 307 308 309 310 311 312 313 314 315 316
      waiters++;
    }

    node = node->next_;
  }

  return Smi::FromInt(waiters);
}

}  // namespace internal
}  // namespace v8