Commit 35b21148 authored by binji's avatar binji Committed by Commit bot

Atomics Futex API

BUG=chromium:497295
R=jarin@chromium.org
LOG=n

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

Cr-Commit-Position: refs/heads/master@{#29736}
parent 82339644
......@@ -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",
......
// 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
// 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_
......@@ -13,21 +13,34 @@
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,
]);
})
......@@ -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;
......
......@@ -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: %") \
......
// 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
......@@ -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) \
......
......@@ -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.
......
......@@ -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());
}
// 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();
}
})();
}
......@@ -669,6 +669,8 @@
'../../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',
......@@ -868,6 +870,7 @@
'../../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',
......
......@@ -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 = []
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment