Atomics Futex API
authorbinji <binji@chromium.org>
Fri, 17 Jul 2015 17:11:32 +0000 (10:11 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 17 Jul 2015 17:11:47 +0000 (17:11 +0000)
BUG=chromium:497295
R=jarin@chromium.org
LOG=n

Review URL: https://codereview.chromium.org/1208933006

Cr-Commit-Position: refs/heads/master@{#29736}

13 files changed:
BUILD.gn
src/futex-emulation.cc [new file with mode: 0644]
src/futex-emulation.h [new file with mode: 0644]
src/harmony-atomics.js
src/isolate.h
src/messages.h
src/runtime/runtime-futex.cc [new file with mode: 0644]
src/runtime/runtime.h
test/cctest/cctest.cc
test/cctest/test-api.cc
test/mjsunit/harmony/futex.js [new file with mode: 0644]
tools/gyp/v8.gyp
tools/js2c.py

index 65e5101..2f19808 100644 (file)
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -876,6 +876,8 @@ source_set("v8_base") {
     "src/full-codegen.h",
     "src/func-name-inferrer.cc",
     "src/func-name-inferrer.h",
+    "src/futex-emulation.cc",
+    "src/futex-emulation.h",
     "src/gdb-jit.cc",
     "src/gdb-jit.h",
     "src/global-handles.cc",
@@ -1075,6 +1077,7 @@ source_set("v8_base") {
     "src/runtime/runtime-debug.cc",
     "src/runtime/runtime-forin.cc",
     "src/runtime/runtime-function.cc",
+    "src/runtime/runtime-futex.cc",
     "src/runtime/runtime-generator.cc",
     "src/runtime/runtime-i18n.cc",
     "src/runtime/runtime-internal.cc",
diff --git a/src/futex-emulation.cc b/src/futex-emulation.cc
new file mode 100644 (file)
index 0000000..b8168a8
--- /dev/null
@@ -0,0 +1,233 @@
+// 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/conversions.h"
+#include "src/handles-inl.h"
+#include "src/isolate.h"
+#include "src/list-inl.h"
+
+namespace v8 {
+namespace internal {
+
+base::LazyMutex FutexEmulation::mutex_ = LAZY_MUTEX_INITIALIZER;
+base::LazyInstance<FutexWaitList>::type FutexEmulation::wait_list_ =
+    LAZY_INSTANCE_INITIALIZER;
+
+
+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;
+}
+
+
+Object* FutexEmulation::Wait(Isolate* isolate,
+                             Handle<JSArrayBuffer> array_buffer, size_t addr,
+                             int32_t value, double rel_timeout_ms) {
+  // We never want to wait longer than this amount of time; this way we can
+  // interrupt this thread even if this is an "infinitely blocking" wait.
+  // TODO(binji): come up with a better way of interrupting only when
+  // necessary, rather than busy-waiting.
+  const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromMilliseconds(50);
+
+  DCHECK(addr < NumberToSize(isolate, array_buffer->byte_length()));
+
+  void* backing_store = array_buffer->backing_store();
+  int32_t* p =
+      reinterpret_cast<int32_t*>(static_cast<int8_t*>(backing_store) + addr);
+
+  base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
+
+  if (*p != value) {
+    return Smi::FromInt(Result::kNotEqual);
+  }
+
+  FutexWaitListNode* node = isolate->futex_wait_list_node();
+
+  node->backing_store_ = backing_store;
+  node->wait_addr_ = addr;
+  node->waiting_ = true;
+
+  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));
+    }
+  }
+
+  base::TimeDelta rel_time_left = rel_timeout;
+
+  wait_list_.Pointer()->AddNode(node);
+
+  Object* result;
+
+  while (true) {
+    base::TimeDelta time_to_wait = (use_timeout && rel_time_left < kMaxWaitTime)
+                                       ? rel_time_left
+                                       : kMaxWaitTime;
+
+    base::Time start_time = base::Time::NowFromSystemTime();
+    bool wait_for_result = node->cond_.WaitFor(mutex_.Pointer(), time_to_wait);
+    USE(wait_for_result);
+
+    if (!node->waiting_) {
+      result = Smi::FromInt(Result::kOk);
+      break;
+    }
+
+    // Spurious wakeup or timeout.
+    base::Time end_time = base::Time::NowFromSystemTime();
+    base::TimeDelta waited_for = end_time - start_time;
+    rel_time_left -= waited_for;
+
+    if (use_timeout && rel_time_left < base::TimeDelta::FromMicroseconds(0)) {
+      result = Smi::FromInt(Result::kTimedOut);
+      break;
+    }
+
+    // Potentially handle interrupts before continuing to wait.
+    Object* interrupt_object = isolate->stack_guard()->HandleInterrupts();
+    if (interrupt_object->IsException()) {
+      result = interrupt_object;
+      break;
+    }
+  }
+
+  wait_list_.Pointer()->RemoveNode(node);
+
+  return result;
+}
+
+
+Object* FutexEmulation::Wake(Isolate* isolate,
+                             Handle<JSArrayBuffer> array_buffer, size_t addr,
+                             int num_waiters_to_wake) {
+  DCHECK(addr < NumberToSize(isolate, array_buffer->byte_length()));
+
+  int waiters_woken = 0;
+  void* backing_store = array_buffer->backing_store();
+
+  base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
+  FutexWaitListNode* node = wait_list_.Pointer()->head_;
+  while (node && num_waiters_to_wake > 0) {
+    if (backing_store == node->backing_store_ && addr == node->wait_addr_) {
+      node->waiting_ = false;
+      node->cond_.NotifyOne();
+      --num_waiters_to_wake;
+      waiters_woken++;
+    }
+
+    node = node->next_;
+  }
+
+  return Smi::FromInt(waiters_woken);
+}
+
+
+Object* FutexEmulation::WakeOrRequeue(Isolate* isolate,
+                                      Handle<JSArrayBuffer> array_buffer,
+                                      size_t addr, int num_waiters_to_wake,
+                                      int32_t value, size_t addr2) {
+  DCHECK(addr < NumberToSize(isolate, array_buffer->byte_length()));
+  DCHECK(addr2 < NumberToSize(isolate, array_buffer->byte_length()));
+
+  void* backing_store = array_buffer->backing_store();
+  int32_t* p =
+      reinterpret_cast<int32_t*>(static_cast<int8_t*>(backing_store) + addr);
+
+  base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
+  if (*p != value) {
+    return Smi::FromInt(Result::kNotEqual);
+  }
+
+  // Wake |num_waiters_to_wake|
+  int waiters_woken = 0;
+  FutexWaitListNode* node = wait_list_.Pointer()->head_;
+  while (node) {
+    if (backing_store == node->backing_store_ && addr == node->wait_addr_) {
+      if (num_waiters_to_wake > 0) {
+        node->waiting_ = false;
+        node->cond_.NotifyOne();
+        --num_waiters_to_wake;
+        waiters_woken++;
+      } else {
+        node->wait_addr_ = addr2;
+      }
+    }
+
+    node = node->next_;
+  }
+
+  return Smi::FromInt(waiters_woken);
+}
+
+
+Object* FutexEmulation::NumWaitersForTesting(Isolate* isolate,
+                                             Handle<JSArrayBuffer> array_buffer,
+                                             size_t addr) {
+  DCHECK(addr < NumberToSize(isolate, array_buffer->byte_length()));
+  void* backing_store = array_buffer->backing_store();
+
+  base::LockGuard<base::Mutex> lock_guard(mutex_.Pointer());
+
+  int waiters = 0;
+  FutexWaitListNode* node = wait_list_.Pointer()->head_;
+  while (node) {
+    if (backing_store == node->backing_store_ && addr == node->wait_addr_) {
+      waiters++;
+    }
+
+    node = node->next_;
+  }
+
+  return Smi::FromInt(waiters);
+}
+
+}  // namespace internal
+}  // namespace v8
diff --git a/src/futex-emulation.h b/src/futex-emulation.h
new file mode 100644 (file)
index 0000000..2a07f33
--- /dev/null
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef V8_FUTEX_EMULATION_H_
+#define V8_FUTEX_EMULATION_H_
+
+#include <stdint.h>
+
+#include "src/allocation.h"
+#include "src/base/lazy-instance.h"
+#include "src/base/macros.h"
+#include "src/base/platform/condition-variable.h"
+#include "src/base/platform/mutex.h"
+#include "src/handles.h"
+
+// Support for emulating futexes, a low-level synchronization primitive. They
+// are natively supported by Linux, but must be emulated for other platforms.
+// This library emulates them on all platforms using mutexes and condition
+// variables for consistency.
+//
+// This is used by the Futex API defined in the SharedArrayBuffer draft spec,
+// found here: https://github.com/lars-t-hansen/ecmascript_sharedmem
+
+namespace v8 {
+
+namespace base {
+class TimeDelta;
+}  // base
+
+namespace internal {
+
+class Isolate;
+
+class FutexWaitListNode {
+ public:
+  FutexWaitListNode()
+      : prev_(nullptr),
+        next_(nullptr),
+        backing_store_(nullptr),
+        wait_addr_(0),
+        waiting_(false) {}
+
+ private:
+  friend class FutexEmulation;
+  friend class FutexWaitList;
+
+  base::ConditionVariable cond_;
+  FutexWaitListNode* prev_;
+  FutexWaitListNode* next_;
+  void* backing_store_;
+  size_t wait_addr_;
+  bool waiting_;
+
+  DISALLOW_COPY_AND_ASSIGN(FutexWaitListNode);
+};
+
+
+class FutexWaitList {
+ public:
+  FutexWaitList();
+
+  void AddNode(FutexWaitListNode* node);
+  void RemoveNode(FutexWaitListNode* node);
+
+ private:
+  friend class FutexEmulation;
+
+  FutexWaitListNode* head_;
+  FutexWaitListNode* tail_;
+
+  DISALLOW_COPY_AND_ASSIGN(FutexWaitList);
+};
+
+
+class FutexEmulation : public AllStatic {
+ public:
+  // These must match the values in src/harmony-atomics.js
+  enum Result {
+    kOk = 0,
+    kNotEqual = -1,
+    kTimedOut = -2,
+  };
+
+  // Check that array_buffer[addr] == value, and return kNotEqual if not. If
+  // they are equal, block execution on |isolate|'s thread until woken via
+  // |Wake|, or when the time given in |rel_timeout_ms| elapses. Note that
+  // |rel_timeout_ms| can be Infinity.
+  // If woken, return kOk, otherwise return kTimedOut. The initial check and
+  // the decision to wait happen atomically.
+  static Object* Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
+                      size_t addr, int32_t value, double rel_timeout_ms);
+
+  // Wake |num_waiters_to_wake| threads that are waiting on the given |addr|.
+  // The rest of the waiters will continue to wait. The return value is the
+  // number of woken waiters.
+  static Object* Wake(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
+                      size_t addr, int num_waiters_to_wake);
+
+  // Check that array_buffer[addr] == value, and return kNotEqual if not. If
+  // they are equal, wake |num_waiters_to_wake| threads that are waiting on the
+  // given |addr|. The rest of the waiters will continue to wait, but will now
+  // be waiting on |addr2| instead of |addr|. The return value is the number of
+  // woken waiters or kNotEqual as described above.
+  static Object* WakeOrRequeue(Isolate* isolate,
+                               Handle<JSArrayBuffer> array_buffer, size_t addr,
+                               int num_waiters_to_wake, int32_t value,
+                               size_t addr2);
+
+  // Return the number of threads waiting on |addr|. Should only be used for
+  // testing.
+  static Object* NumWaitersForTesting(Isolate* isolate,
+                                      Handle<JSArrayBuffer> array_buffer,
+                                      size_t addr);
+
+ private:
+  static base::LazyMutex mutex_;
+  static base::LazyInstance<FutexWaitList>::type wait_list_;
+};
+}
+}  // namespace v8::internal
+
+#endif  // V8_FUTEX_EMULATION_H_
index b137d43..3c7968b 100644 (file)
 
 var GlobalObject = global.Object;
 
+var MathMax;
+
+utils.Import(function(from) {
+  MathMax = from.MathMax;
+});
+
 // -------------------------------------------------------------------
 
 
 function CheckSharedTypedArray(sta) {
-  if (!%_IsSharedTypedArray(sta)) {
+  if (!%IsSharedTypedArray(sta)) {
     throw MakeTypeError(kNotSharedTypedArray, sta);
   }
 }
 
 function CheckSharedIntegerTypedArray(ia) {
-  if (!%_IsSharedIntegerTypedArray(ia)) {
+  if (!%IsSharedIntegerTypedArray(ia)) {
     throw MakeTypeError(kNotIntegerSharedTypedArray, ia);
   }
 }
 
+function CheckSharedInteger32TypedArray(ia) {
+  CheckSharedIntegerTypedArray(ia);
+  if (%_ClassOf(ia) !== 'Int32Array') {
+    throw MakeTypeError(kNotInt32SharedTypedArray, ia);
+  }
+}
+
 //-------------------------------------------------------------------
 
 function AtomicsCompareExchangeJS(sta, index, oldValue, newValue) {
@@ -124,6 +137,50 @@ function AtomicsIsLockFreeJS(size) {
   return %_AtomicsIsLockFree(size);
 }
 
+// Futexes
+
+function AtomicsFutexWaitJS(ia, index, value, timeout) {
+  CheckSharedInteger32TypedArray(ia);
+  index = $toInteger(index);
+  if (index < 0 || index >= %_TypedArrayGetLength(ia)) {
+    return UNDEFINED;
+  }
+  if (IS_UNDEFINED(timeout)) {
+    timeout = INFINITY;
+  } else {
+    timeout = $toNumber(timeout);
+    if (NUMBER_IS_NAN(timeout)) {
+      timeout = INFINITY;
+    } else {
+      timeout = MathMax(0, timeout);
+    }
+  }
+  return %AtomicsFutexWait(ia, index, value, timeout);
+}
+
+function AtomicsFutexWakeJS(ia, index, count) {
+  CheckSharedInteger32TypedArray(ia);
+  index = $toInteger(index);
+  if (index < 0 || index >= %_TypedArrayGetLength(ia)) {
+    return UNDEFINED;
+  }
+  count = MathMax(0, $toInteger(count));
+  return %AtomicsFutexWake(ia, index, count);
+}
+
+function AtomicsFutexWakeOrRequeueJS(ia, index1, count, value, index2) {
+  CheckSharedInteger32TypedArray(ia);
+  index1 = $toInteger(index1);
+  count = MathMax(0, $toInteger(count));
+  value = $toInt32(value);
+  index2 = $toInteger(index2);
+  if (index1 < 0 || index1 >= %_TypedArrayGetLength(ia) ||
+      index2 < 0 || index2 >= %_TypedArrayGetLength(ia)) {
+    return UNDEFINED;
+  }
+  return %AtomicsFutexWakeOrRequeue(ia, index1, count, value, index2);
+}
+
 // -------------------------------------------------------------------
 
 function AtomicsConstructor() {}
@@ -136,6 +193,13 @@ var Atomics = new AtomicsConstructor();
 
 %AddNamedProperty(Atomics, symbolToStringTag, "Atomics", READ_ONLY | DONT_ENUM);
 
+// These must match the values in src/futex-emulation.h
+utils.InstallConstants(Atomics, [
+  "OK", 0,
+  "NOTEQUAL", -1,
+  "TIMEDOUT", -2,
+]);
+
 utils.InstallFunctions(Atomics, DONT_ENUM, [
   "compareExchange", AtomicsCompareExchangeJS,
   "load", AtomicsLoadJS,
@@ -147,6 +211,9 @@ utils.InstallFunctions(Atomics, DONT_ENUM, [
   "xor", AtomicsXorJS,
   "exchange", AtomicsExchangeJS,
   "isLockFree", AtomicsIsLockFreeJS,
+  "futexWait", AtomicsFutexWaitJS,
+  "futexWake", AtomicsFutexWakeJS,
+  "futexWakeOrRequeue", AtomicsFutexWakeOrRequeueJS,
 ]);
 
 })
index a67f0c7..42e22b4 100644 (file)
@@ -15,6 +15,7 @@
 #include "src/date.h"
 #include "src/execution.h"
 #include "src/frames.h"
+#include "src/futex-emulation.h"
 #include "src/global-handles.h"
 #include "src/handles.h"
 #include "src/hashmap.h"
@@ -1130,6 +1131,8 @@ class Isolate {
     return array_buffer_allocator_;
   }
 
+  FutexWaitListNode* futex_wait_list_node() { return &futex_wait_list_node_; }
+
  protected:
   explicit Isolate(bool enable_serializer);
 
@@ -1363,6 +1366,8 @@ class Isolate {
 
   v8::ArrayBuffer::Allocator* array_buffer_allocator_;
 
+  FutexWaitListNode futex_wait_list_node_;
+
   friend class ExecutionAccess;
   friend class HandleScopeImplementer;
   friend class OptimizingCompileDispatcher;
index f83023b..fc98118 100644 (file)
@@ -163,6 +163,7 @@ class CallSite {
   T(NotTypedArray, "this is not a typed array.")                               \
   T(NotSharedTypedArray, "% is not a shared typed array.")                     \
   T(NotIntegerSharedTypedArray, "% is not an integer shared typed array.")     \
+  T(NotInt32SharedTypedArray, "% is not an int32 shared typed array.")         \
   T(ObjectGetterExpectingFunction,                                             \
     "Object.prototype.__defineGetter__: Expecting function")                   \
   T(ObjectGetterCallable, "Getter must be a function: %")                      \
diff --git a/src/runtime/runtime-futex.cc b/src/runtime/runtime-futex.cc
new file mode 100644 (file)
index 0000000..590c66b
--- /dev/null
@@ -0,0 +1,94 @@
+// 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 "src/v8.h"
+
+#include "src/arguments.h"
+#include "src/base/platform/time.h"
+#include "src/globals.h"
+#include "src/runtime/runtime-utils.h"
+
+// Implement Futex API for SharedArrayBuffers as defined in the
+// SharedArrayBuffer draft spec, found here:
+// https://github.com/lars-t-hansen/ecmascript_sharedmem
+
+namespace v8 {
+namespace internal {
+
+RUNTIME_FUNCTION(Runtime_AtomicsFutexWait) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 4);
+  CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sta, 0);
+  CONVERT_SIZE_ARG_CHECKED(index, 1);
+  CONVERT_INT32_ARG_CHECKED(value, 2);
+  CONVERT_DOUBLE_ARG_CHECKED(timeout, 3);
+  RUNTIME_ASSERT(sta->GetBuffer()->is_shared());
+  RUNTIME_ASSERT(index < NumberToSize(isolate, sta->length()));
+  RUNTIME_ASSERT(sta->type() == kExternalInt32Array);
+  RUNTIME_ASSERT(timeout == V8_INFINITY || !std::isnan(timeout));
+
+  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
+  size_t addr = index << 2;
+
+  return FutexEmulation::Wait(isolate, array_buffer, addr, value, timeout);
+}
+
+
+RUNTIME_FUNCTION(Runtime_AtomicsFutexWake) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 3);
+  CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sta, 0);
+  CONVERT_SIZE_ARG_CHECKED(index, 1);
+  CONVERT_INT32_ARG_CHECKED(count, 2);
+  RUNTIME_ASSERT(sta->GetBuffer()->is_shared());
+  RUNTIME_ASSERT(index < NumberToSize(isolate, sta->length()));
+  RUNTIME_ASSERT(sta->type() == kExternalInt32Array);
+
+  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
+  size_t addr = index << 2;
+
+  return FutexEmulation::Wake(isolate, array_buffer, addr, count);
+}
+
+
+RUNTIME_FUNCTION(Runtime_AtomicsFutexWakeOrRequeue) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 5);
+  CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sta, 0);
+  CONVERT_SIZE_ARG_CHECKED(index1, 1);
+  CONVERT_INT32_ARG_CHECKED(count, 2);
+  CONVERT_INT32_ARG_CHECKED(value, 3);
+  CONVERT_SIZE_ARG_CHECKED(index2, 4);
+  RUNTIME_ASSERT(sta->GetBuffer()->is_shared());
+  RUNTIME_ASSERT(index1 < NumberToSize(isolate, sta->length()));
+  RUNTIME_ASSERT(index2 < NumberToSize(isolate, sta->length()));
+  RUNTIME_ASSERT(sta->type() == kExternalInt32Array);
+
+  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
+  size_t addr1 = index1 << 2;
+  size_t addr2 = index2 << 2;
+
+  return FutexEmulation::WakeOrRequeue(isolate, array_buffer, addr1, count,
+                                       value, addr2);
+}
+
+
+RUNTIME_FUNCTION(Runtime_AtomicsFutexNumWaitersForTesting) {
+  HandleScope scope(isolate);
+  DCHECK(args.length() == 2);
+  CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, sta, 0);
+  CONVERT_SIZE_ARG_CHECKED(index, 1);
+  RUNTIME_ASSERT(sta->GetBuffer()->is_shared());
+  RUNTIME_ASSERT(index < NumberToSize(isolate, sta->length()));
+  RUNTIME_ASSERT(sta->type() == kExternalInt32Array);
+
+  Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
+  size_t addr = index << 2;
+
+  return FutexEmulation::NumWaitersForTesting(isolate, array_buffer, addr);
+}
+}
+}  // namespace v8::internal
index 0ab0777..9157aef 100644 (file)
@@ -66,6 +66,13 @@ namespace internal {
   F(AtomicsIsLockFree, 1, 1)
 
 
+#define FOR_EACH_INTRINSIC_FUTEX(F)  \
+  F(AtomicsFutexWait, 4, 1)          \
+  F(AtomicsFutexWake, 3, 1)          \
+  F(AtomicsFutexWakeOrRequeue, 5, 1) \
+  F(AtomicsFutexNumWaitersForTesting, 2, 1)
+
+
 #define FOR_EACH_INTRINSIC_CLASSES(F)         \
   F(ThrowNonMethodError, 0, 1)                \
   F(ThrowUnsupportedSuperError, 0, 1)         \
@@ -728,6 +735,7 @@ namespace internal {
   FOR_EACH_INTRINSIC_DEBUG(F)               \
   FOR_EACH_INTRINSIC_FORIN(F)               \
   FOR_EACH_INTRINSIC_FUNCTION(F)            \
+  FOR_EACH_INTRINSIC_FUTEX(F)               \
   FOR_EACH_INTRINSIC_GENERATOR(F)           \
   FOR_EACH_INTRINSIC_I18N(F)                \
   FOR_EACH_INTRINSIC_INTERNAL(F)            \
index 35e7665..f18ed55 100644 (file)
@@ -137,7 +137,10 @@ static void PrintTestList(CcTest* current) {
 
 
 class CcTestArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
-  virtual void* Allocate(size_t length) { return malloc(length); }
+  virtual void* Allocate(size_t length) {
+    void* data = AllocateUninitialized(length);
+    return data == NULL ? data : memset(data, 0, length);
+  }
   virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
   virtual void Free(void* data, size_t length) { free(data); }
   // TODO(dslomov): Remove when v8:2823 is fixed.
index 24573a4..5de0490 100644 (file)
@@ -44,6 +44,7 @@
 #include "src/compilation-cache.h"
 #include "src/debug.h"
 #include "src/execution.h"
+#include "src/futex-emulation.h"
 #include "src/objects.h"
 #include "src/parser.h"
 #include "src/unicode-inl.h"
@@ -21889,3 +21890,38 @@ TEST(CompatibleReceiverCheckOnCachedICHandler) {
       "result;\n",
       0);
 }
+
+class FutexInterruptionThread : public v8::base::Thread {
+ public:
+  explicit FutexInterruptionThread(v8::Isolate* isolate)
+      : Thread(Options("FutexInterruptionThread")), isolate_(isolate) {}
+
+  virtual void Run() {
+    // Wait a bit before terminating.
+    v8::base::OS::Sleep(v8::base::TimeDelta::FromMilliseconds(100));
+    v8::V8::TerminateExecution(isolate_);
+  }
+
+ private:
+  v8::Isolate* isolate_;
+};
+
+
+TEST(FutexInterruption) {
+  i::FLAG_harmony_sharedarraybuffer = true;
+  i::FLAG_harmony_atomics = true;
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  LocalContext env;
+
+  FutexInterruptionThread timeout_thread(isolate);
+
+  v8::TryCatch try_catch(CcTest::isolate());
+  timeout_thread.Start();
+
+  CompileRun(
+      "var ab = new SharedArrayBuffer(4);"
+      "var i32a = new Int32Array(ab);"
+      "Atomics.futexWait(i32a, 0, 0);");
+  CHECK(try_catch.HasTerminated());
+}
diff --git a/test/mjsunit/harmony/futex.js b/test/mjsunit/harmony/futex.js
new file mode 100644 (file)
index 0000000..c7e1f5c
--- /dev/null
@@ -0,0 +1,274 @@
+// 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.
+
+// Flags: --allow-natives-syntax --harmony-atomics --harmony-sharedarraybuffer
+
+(function TestFailsWithNonSharedArray() {
+  var ab = new ArrayBuffer(16);
+
+  var i8a = new Int8Array(ab);
+  var i16a = new Int16Array(ab);
+  var i32a = new Int32Array(ab);
+  var ui8a = new Uint8Array(ab);
+  var ui8ca = new Uint8ClampedArray(ab);
+  var ui16a = new Uint16Array(ab);
+  var ui32a = new Uint32Array(ab);
+  var f32a = new Float32Array(ab);
+  var f64a = new Float64Array(ab);
+
+  [i8a, i16a, i32a, ui8a, ui8ca, ui16a, ui32a, f32a, f64a].forEach(function(
+      ta) {
+    assertThrows(function() { Atomics.futexWait(ta, 0, 0); });
+    assertThrows(function() { Atomics.futexWake(ta, 0, 1); });
+    assertThrows(function() { Atomics.futexWakeOrRequeue(ta, 0, 1, 0, 0); });
+  });
+})();
+
+(function TestFailsWithNonSharedInt32Array() {
+  var sab = new SharedArrayBuffer(16);
+
+  var i8a = new Int8Array(sab);
+  var i16a = new Int16Array(sab);
+  var ui8a = new Uint8Array(sab);
+  var ui8ca = new Uint8ClampedArray(sab);
+  var ui16a = new Uint16Array(sab);
+  var ui32a = new Uint32Array(sab);
+  var f32a = new Float32Array(sab);
+  var f64a = new Float64Array(sab);
+
+  [i8a, i16a, ui8a, ui8ca, ui16a, ui32a, f32a, f64a].forEach(function(
+      ta) {
+    assertThrows(function() { Atomics.futexWait(ta, 0, 0); });
+    assertThrows(function() { Atomics.futexWake(ta, 0, 1); });
+    assertThrows(function() { Atomics.futexWakeOrRequeue(ta, 0, 1, 0, 0); });
+  });
+})();
+
+(function TestInvalidIndex() {
+  var i32a = new Int32Array(new SharedArrayBuffer(16));
+
+  // Valid indexes are 0-3.
+  [-1, 4, 100].forEach(function(invalidIndex) {
+    assertEquals(undefined, Atomics.futexWait(i32a, invalidIndex, 0));
+    assertEquals(undefined, Atomics.futexWake(i32a, invalidIndex, 0));
+    var validIndex = 0;
+    assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, invalidIndex, 0, 0,
+                                                       validIndex));
+    assertEquals(undefined, Atomics.futexWakeOrRequeue(i32a, validIndex, 0, 0,
+                                                       invalidIndex));
+  });
+
+})();
+
+(function TestWaitTimeout() {
+  var i32a = new Int32Array(new SharedArrayBuffer(16));
+  var waitMs = 100;
+  var startTime = new Date();
+  assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, waitMs));
+  var endTime = new Date();
+  assertTrue(endTime - startTime >= waitMs);
+})();
+
+(function TestWaitNotEqual() {
+  var i32a = new Int32Array(new SharedArrayBuffer(16));
+  assertEquals(Atomics.NOTEQUAL, Atomics.futexWait(i32a, 0, 42));
+})();
+
+(function TestWaitNegativeTimeout() {
+  var i32a = new Int32Array(new SharedArrayBuffer(16));
+  assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, -1));
+  assertEquals(Atomics.TIMEDOUT, Atomics.futexWait(i32a, 0, 0, -Infinity));
+})();
+
+//// WORKER ONLY TESTS
+
+if (this.Worker) {
+
+  var TestWaitWithTimeout = function(timeout) {
+    var sab = new SharedArrayBuffer(16);
+    var i32a = new Int32Array(sab);
+
+    var workerScript =
+      `onmessage = function(sab) {
+         var i32a = new Int32Array(sab);
+         var result = Atomics.futexWait(i32a, 0, 0, ${timeout});
+         postMessage(result);
+       };`;
+
+    var worker = new Worker(workerScript);
+    worker.postMessage(sab, [sab]);
+
+    // Spin until the worker is waiting on the futex.
+    while (%AtomicsFutexNumWaitersForTesting(i32a, 0) != 1) {}
+
+    Atomics.futexWake(i32a, 0, 1);
+    assertEquals(Atomics.OK, worker.getMessage());
+    worker.terminate();
+  };
+
+  // Test various infinite timeouts
+  TestWaitWithTimeout(undefined);
+  TestWaitWithTimeout(NaN);
+  TestWaitWithTimeout(Infinity);
+
+
+  (function TestWakeMulti() {
+    var sab = new SharedArrayBuffer(20);
+    var i32a = new Int32Array(sab);
+
+    // SAB values:
+    // i32a[id], where id in range [0, 3]:
+    //   0 => Worker |id| is still waiting on the futex
+    //   1 => Worker |id| is not waiting on futex, but has not be reaped by the
+    //        main thread.
+    //   2 => Worker |id| has been reaped.
+    //
+    // i32a[4]:
+    //   always 0. Each worker is waiting on this index.
+
+    var workerScript =
+      `onmessage = function(msg) {
+         var id = msg.id;
+         var i32a = new Int32Array(msg.sab);
+
+         // Wait on i32a[4] (should be zero).
+         var result = Atomics.futexWait(i32a, 4, 0);
+         // Set i32a[id] to 1 to notify the main thread which workers were
+         // woken up.
+         Atomics.store(i32a, id, 1);
+         postMessage(result);
+       };`;
+
+    var id;
+    var workers = [];
+    for (id = 0; id < 4; id++) {
+      workers[id] = new Worker(workerScript);
+      workers[id].postMessage({sab: sab, id: id}, [sab]);
+    }
+
+    // Spin until all workers are waiting on the futex.
+    while (%AtomicsFutexNumWaitersForTesting(i32a, 4) != 4) {}
+
+    // Wake up three waiters.
+    assertEquals(3, Atomics.futexWake(i32a, 4, 3));
+
+    var wokenCount = 0;
+    var waitingId = 0 + 1 + 2 + 3;
+    while (wokenCount < 3) {
+      for (id = 0; id < 4; id++) {
+        // Look for workers that have not yet been reaped. Set i32a[id] to 2
+        // when they've been processed so we don't look at them again.
+        if (Atomics.compareExchange(i32a, id, 1, 2) == 1) {
+          assertEquals(Atomics.OK, workers[id].getMessage());
+          workers[id].terminate();
+          waitingId -= id;
+          wokenCount++;
+        }
+      }
+    }
+
+    assertEquals(3, wokenCount);
+    assertEquals(0, Atomics.load(i32a, waitingId));
+    assertEquals(1, %AtomicsFutexNumWaitersForTesting(i32a, 4));
+
+    // Finally wake the last waiter.
+    assertEquals(1, Atomics.futexWake(i32a, 4, 1));
+    assertEquals(Atomics.OK, workers[waitingId].getMessage());
+    workers[waitingId].terminate();
+
+    assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, 4));
+
+  })();
+
+  (function TestWakeOrRequeue() {
+    var sab = new SharedArrayBuffer(24);
+    var i32a = new Int32Array(sab);
+
+    // SAB values:
+    // i32a[id], where id in range [0, 3]:
+    //   0 => Worker |id| is still waiting on the futex
+    //   1 => Worker |id| is not waiting on futex, but has not be reaped by the
+    //        main thread.
+    //   2 => Worker |id| has been reaped.
+    //
+    // i32a[4]:
+    //   always 0. Each worker will initially wait on this index.
+    //
+    // i32a[5]:
+    //   always 0. Requeued workers will wait on this index.
+
+    var workerScript =
+      `onmessage = function(msg) {
+         var id = msg.id;
+         var i32a = new Int32Array(msg.sab);
+
+         var result = Atomics.futexWait(i32a, 4, 0, Infinity);
+         Atomics.store(i32a, id, 1);
+         postMessage(result);
+       };`;
+
+    var workers = [];
+    for (id = 0; id < 4; id++) {
+      workers[id] = new Worker(workerScript);
+      workers[id].postMessage({sab: sab, id: id}, [sab]);
+    }
+
+    // Spin until all workers are waiting on the futex.
+    while (%AtomicsFutexNumWaitersForTesting(i32a, 4) != 4) {}
+
+    var index1 = 4;
+    var index2 = 5;
+
+    // If futexWakeOrRequeue is called with the incorrect value, it shouldn't
+    // wake any waiters.
+    assertEquals(Atomics.NOTEQUAL,
+                 Atomics.futexWakeOrRequeue(i32a, index1, 1, 42, index2));
+
+    assertEquals(4, %AtomicsFutexNumWaitersForTesting(i32a, index1));
+    assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index2));
+
+    // Now wake with the correct value.
+    assertEquals(1, Atomics.futexWakeOrRequeue(i32a, index1, 1, 0, index2));
+
+    // The workers that are still waiting should atomically be transferred to
+    // the new index.
+    assertEquals(3, %AtomicsFutexNumWaitersForTesting(i32a, index2));
+
+    // The woken worker may not have been scheduled yet. Look for which thread
+    // has set its i32a value to 1.
+    var wokenCount = 0;
+    while (wokenCount < 1) {
+      for (id = 0; id < 4; id++) {
+        if (Atomics.compareExchange(i32a, id, 1, 2) == 1) {
+          wokenCount++;
+        }
+      }
+    }
+
+    assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index1));
+
+    // Wake the remaining waiters.
+    assertEquals(3, Atomics.futexWake(i32a, index2, 3));
+
+    // As above, wait until the workers have been scheduled.
+    wokenCount = 0;
+    while (wokenCount < 3) {
+      for (id = 0; id < 4; id++) {
+        if (Atomics.compareExchange(i32a, id, 1, 2) == 1) {
+          wokenCount++;
+        }
+      }
+    }
+
+    assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index1));
+    assertEquals(0, %AtomicsFutexNumWaitersForTesting(i32a, index2));
+
+    for (id = 0; id < 4; ++id) {
+      assertEquals(Atomics.OK, workers[id].getMessage());
+      workers[id].terminate();
+    }
+
+  })();
+
+}
index 04855e5..ccc6e21 100644 (file)
         '../../src/full-codegen.h',
         '../../src/func-name-inferrer.cc',
         '../../src/func-name-inferrer.h',
+        '../../src/futex-emulation.cc',
+        '../../src/futex-emulation.h',
         '../../src/gdb-jit.cc',
         '../../src/gdb-jit.h',
         '../../src/global-handles.cc',
         '../../src/runtime/runtime-debug.cc',
         '../../src/runtime/runtime-forin.cc',
         '../../src/runtime/runtime-function.cc',
+        '../../src/runtime/runtime-futex.cc',
         '../../src/runtime/runtime-generator.cc',
         '../../src/runtime/runtime-i18n.cc',
         '../../src/runtime/runtime-internal.cc',
index b5436f9..ff8e2dc 100755 (executable)
@@ -196,7 +196,7 @@ def ReadMacros(lines):
   return (constants, macros)
 
 
-TEMPLATE_PATTERN = re.compile(r'^\s+T\(([A-Z][a-zA-Z]*),')
+TEMPLATE_PATTERN = re.compile(r'^\s+T\(([A-Z][a-zA-Z0-9]*),')
 
 def ReadMessageTemplates(lines):
   templates = []