Reland "[wasm] Add I64AtomicWait implementation"
This is a reland of 9436e8a817
This CL simplifies the wasm/futex.js test so that it doesn't push the
limits of d8.
Original change's description:
> [wasm] Add I64AtomicWait implementation
>
> Bug=v8:8075
> R=adamk@chromium.org,binji@chromium.org
>
> Change-Id: I11ef5daccd043123b23e60c93ee0df79cabe9ccd
> Reviewed-on: https://chromium-review.googlesource.com/c/1342948
> Reviewed-by: Adam Klein <adamk@chromium.org>
> Reviewed-by: Ben Smith <binji@chromium.org>
> Commit-Queue: Aseem Garg <aseemgarg@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#57917}
Change-Id: Ifd26f1ecdb9fe24a1896162bb4d4285f9188a9ba
Reviewed-on: https://chromium-review.googlesource.com/c/1351304
Commit-Queue: Aseem Garg <aseemgarg@chromium.org>
Reviewed-by: Ben Smith <binji@chromium.org>
Reviewed-by: Adam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57953}
This commit is contained in:
parent
172ab5dcc9
commit
5b55ec2ac0
@ -7791,7 +7791,7 @@ class V8_EXPORT Isolate {
|
||||
*/
|
||||
typedef void (*AtomicsWaitCallback)(AtomicsWaitEvent event,
|
||||
Local<SharedArrayBuffer> array_buffer,
|
||||
size_t offset_in_bytes, int32_t value,
|
||||
size_t offset_in_bytes, int64_t value,
|
||||
double timeout_in_ms,
|
||||
AtomicsWaitWakeHandle* stop_handle,
|
||||
void* data);
|
||||
|
@ -1239,6 +1239,7 @@ namespace internal {
|
||||
TFC(WasmAllocateHeapNumber, AllocateHeapNumber, 1) \
|
||||
TFC(WasmAtomicWake, WasmAtomicWake, 1) \
|
||||
TFC(WasmI32AtomicWait, WasmI32AtomicWait, 1) \
|
||||
TFC(WasmI64AtomicWait, WasmI64AtomicWait, 1) \
|
||||
TFC(WasmCallJavaScript, CallTrampoline, 1) \
|
||||
TFC(WasmMemoryGrow, WasmMemoryGrow, 1) \
|
||||
TFC(WasmRecordWrite, RecordWrite, 1) \
|
||||
@ -1539,6 +1540,7 @@ namespace internal {
|
||||
V(WasmAllocateHeapNumber) \
|
||||
V(WasmAtomicWake) \
|
||||
V(WasmI32AtomicWait) \
|
||||
V(WasmI64AtomicWait) \
|
||||
V(WasmCallJavaScript) \
|
||||
V(WasmMemoryGrow) \
|
||||
V(WasmRecordWrite) \
|
||||
|
@ -156,6 +156,47 @@ TF_BUILTIN(WasmI32AtomicWait, WasmBuiltinsAssembler) {
|
||||
ReturnRaw(SmiToInt32(result_smi));
|
||||
}
|
||||
|
||||
TF_BUILTIN(WasmI64AtomicWait, WasmBuiltinsAssembler) {
|
||||
TNode<Uint32T> address =
|
||||
UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
|
||||
TNode<Uint32T> expected_value_high =
|
||||
UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueHigh));
|
||||
TNode<Uint32T> expected_value_low =
|
||||
UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueLow));
|
||||
TNode<Float64T> timeout =
|
||||
UncheckedCast<Float64T>(Parameter(Descriptor::kTimeout));
|
||||
|
||||
TNode<Object> instance = LoadInstanceFromFrame();
|
||||
TNode<Code> centry = LoadCEntryFromInstance(instance);
|
||||
|
||||
TNode<Code> target = LoadBuiltinFromFrame(Builtins::kAllocateHeapNumber);
|
||||
|
||||
// TODO(aseemgarg): Use SMIs if possible for address and expected_value
|
||||
TNode<HeapNumber> address_heap = UncheckedCast<HeapNumber>(
|
||||
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
|
||||
StoreHeapNumberValue(address_heap, ChangeUint32ToFloat64(address));
|
||||
|
||||
TNode<HeapNumber> expected_value_high_heap = UncheckedCast<HeapNumber>(
|
||||
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
|
||||
StoreHeapNumberValue(expected_value_high_heap,
|
||||
ChangeUint32ToFloat64(expected_value_high));
|
||||
|
||||
TNode<HeapNumber> expected_value_low_heap = UncheckedCast<HeapNumber>(
|
||||
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
|
||||
StoreHeapNumberValue(expected_value_low_heap,
|
||||
ChangeUint32ToFloat64(expected_value_low));
|
||||
|
||||
TNode<HeapNumber> timeout_heap = UncheckedCast<HeapNumber>(
|
||||
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
|
||||
StoreHeapNumberValue(timeout_heap, timeout);
|
||||
|
||||
TNode<Smi> result_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
|
||||
Runtime::kWasmI64AtomicWait, centry, NoContextConstant(), instance,
|
||||
address_heap, expected_value_high_heap, expected_value_low_heap,
|
||||
timeout_heap));
|
||||
ReturnRaw(SmiToInt32(result_smi));
|
||||
}
|
||||
|
||||
TF_BUILTIN(WasmMemoryGrow, WasmBuiltinsAssembler) {
|
||||
TNode<Int32T> num_pages =
|
||||
UncheckedCast<Int32T>(Parameter(Descriptor::kNumPages));
|
||||
|
@ -4114,6 +4114,40 @@ Node* WasmGraphBuilder::AtomicOp(wasm::WasmOpcode opcode, Node* const* inputs,
|
||||
break;
|
||||
}
|
||||
|
||||
case wasm::kExprI64AtomicWait: {
|
||||
Node* index = CheckBoundsAndAlignment(
|
||||
wasm::ValueTypes::MemSize(MachineType::Uint32()), inputs[0], offset,
|
||||
position);
|
||||
// Now that we've bounds-checked, compute the effective address.
|
||||
Node* address = graph()->NewNode(mcgraph()->machine()->Int32Add(),
|
||||
Uint32Constant(offset), index);
|
||||
Node* timeout;
|
||||
if (mcgraph()->machine()->Is32()) {
|
||||
timeout = BuildF64SConvertI64(inputs[2]);
|
||||
} else {
|
||||
timeout = graph()->NewNode(mcgraph()->machine()->RoundInt64ToFloat64(),
|
||||
inputs[2]);
|
||||
}
|
||||
Node* expected_value_low = graph()->NewNode(
|
||||
mcgraph()->machine()->TruncateInt64ToInt32(), inputs[1]);
|
||||
Node* tmp = graph()->NewNode(mcgraph()->machine()->Word64Shr(), inputs[1],
|
||||
Int64Constant(32));
|
||||
Node* expected_value_high =
|
||||
graph()->NewNode(mcgraph()->machine()->TruncateInt64ToInt32(), tmp);
|
||||
WasmI64AtomicWaitDescriptor interface_descriptor;
|
||||
auto call_descriptor = Linkage::GetStubCallDescriptor(
|
||||
mcgraph()->zone(), interface_descriptor,
|
||||
interface_descriptor.GetStackParameterCount(),
|
||||
CallDescriptor::kNoFlags, Operator::kNoProperties,
|
||||
StubCallMode::kCallWasmRuntimeStub);
|
||||
Node* call_target = mcgraph()->RelocatableIntPtrConstant(
|
||||
wasm::WasmCode::kWasmI64AtomicWait, RelocInfo::WASM_STUB_CALL);
|
||||
node = graph()->NewNode(mcgraph()->common()->Call(call_descriptor),
|
||||
call_target, address, expected_value_high,
|
||||
expected_value_low, timeout, Effect(), Control());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
FATAL_UNSUPPORTED_OPCODE(opcode);
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ enum WaitReturnValue : int { kOk = 0, kNotEqual = 1, kTimedOut = 2 };
|
||||
Object* FutexEmulation::WaitJs(Isolate* isolate,
|
||||
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
||||
int32_t value, double rel_timeout_ms) {
|
||||
Object* res = Wait(isolate, array_buffer, addr, value, rel_timeout_ms);
|
||||
Object* res = Wait32(isolate, array_buffer, addr, value, rel_timeout_ms);
|
||||
if (res->IsSmi()) {
|
||||
int val = Smi::ToInt(res);
|
||||
switch (val) {
|
||||
@ -104,9 +104,22 @@ Object* FutexEmulation::WaitJs(Isolate* isolate,
|
||||
return res;
|
||||
}
|
||||
|
||||
Object* FutexEmulation::Wait32(Isolate* isolate,
|
||||
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
||||
int32_t value, double rel_timeout_ms) {
|
||||
return Wait<int32_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
|
||||
}
|
||||
|
||||
Object* FutexEmulation::Wait64(Isolate* isolate,
|
||||
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
||||
int64_t value, double rel_timeout_ms) {
|
||||
return Wait<int64_t>(isolate, array_buffer, addr, value, rel_timeout_ms);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Object* FutexEmulation::Wait(Isolate* isolate,
|
||||
Handle<JSArrayBuffer> array_buffer, size_t addr,
|
||||
int32_t value, double rel_timeout_ms) {
|
||||
T value, double rel_timeout_ms) {
|
||||
DCHECK_LT(addr, array_buffer->byte_length());
|
||||
|
||||
bool use_timeout = rel_timeout_ms != V8_INFINITY;
|
||||
@ -153,8 +166,7 @@ Object* FutexEmulation::Wait(Isolate* isolate,
|
||||
// still holding the lock).
|
||||
ResetWaitingOnScopeExit reset_waiting(node);
|
||||
|
||||
int32_t* p =
|
||||
reinterpret_cast<int32_t*>(static_cast<int8_t*>(backing_store) + addr);
|
||||
T* p = reinterpret_cast<T*>(static_cast<int8_t*>(backing_store) + addr);
|
||||
if (*p != value) {
|
||||
result = Smi::FromInt(WaitReturnValue::kNotEqual);
|
||||
callback_result = AtomicsWaitEvent::kNotEqual;
|
||||
@ -263,7 +275,8 @@ Object* FutexEmulation::Wake(Handle<JSArrayBuffer> array_buffer, size_t addr,
|
||||
base::MutexGuard 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_) {
|
||||
if (backing_store == node->backing_store_ && addr == node->wait_addr_ &&
|
||||
node->waiting_) {
|
||||
node->waiting_ = false;
|
||||
node->cond_.NotifyOne();
|
||||
if (num_waiters_to_wake != kWakeAll) {
|
||||
|
@ -123,8 +123,13 @@ class FutexEmulation : public AllStatic {
|
||||
|
||||
// Same as WaitJs above except it returns 0 (ok), 1 (not equal) and 2 (timed
|
||||
// out) as expected by Wasm.
|
||||
static Object* Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
|
||||
size_t addr, int32_t value, double rel_timeout_ms);
|
||||
static Object* Wait32(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
|
||||
size_t addr, int32_t value, double rel_timeout_ms);
|
||||
|
||||
// Same as Wait32 above except it checks for an int64_t value in the
|
||||
// array_buffer.
|
||||
static Object* Wait64(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
|
||||
size_t addr, int64_t value, double rel_timeout_ms);
|
||||
|
||||
// Wake |num_waiters_to_wake| threads that are waiting on the given |addr|.
|
||||
// |num_waiters_to_wake| can be kWakeAll, in which case all waiters are
|
||||
@ -142,6 +147,10 @@ class FutexEmulation : public AllStatic {
|
||||
friend class FutexWaitListNode;
|
||||
friend class AtomicsWaitWakeHandle;
|
||||
|
||||
template <typename T>
|
||||
static Object* Wait(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
|
||||
size_t addr, T value, double rel_timeout_ms);
|
||||
|
||||
// `mutex_` protects the composition of `wait_list_` (i.e. no elements may be
|
||||
// added or removed without holding this mutex), as well as the `waiting_`
|
||||
// and `interrupted_` fields for each individual list node that is currently
|
||||
|
@ -366,6 +366,11 @@ void WasmI32AtomicWaitDescriptor::InitializePlatformSpecific(
|
||||
DefaultInitializePlatformSpecific(data, kParameterCount);
|
||||
}
|
||||
|
||||
void WasmI64AtomicWaitDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
DefaultInitializePlatformSpecific(data, kParameterCount);
|
||||
}
|
||||
|
||||
void CloneObjectWithVectorDescriptor::InitializePlatformSpecific(
|
||||
CallInterfaceDescriptorData* data) {
|
||||
DefaultInitializePlatformSpecific(data, kParameterCount);
|
||||
|
@ -78,6 +78,7 @@ namespace internal {
|
||||
V(WasmThrow) \
|
||||
V(WasmAtomicWake) \
|
||||
V(WasmI32AtomicWait) \
|
||||
V(WasmI64AtomicWait) \
|
||||
V(CloneObjectWithVector) \
|
||||
BUILTIN_LIST_TFS(V)
|
||||
|
||||
@ -1125,6 +1126,19 @@ class WasmI32AtomicWaitDescriptor final : public CallInterfaceDescriptor {
|
||||
DECLARE_DESCRIPTOR(WasmI32AtomicWaitDescriptor, CallInterfaceDescriptor)
|
||||
};
|
||||
|
||||
class WasmI64AtomicWaitDescriptor final : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DEFINE_PARAMETERS_NO_CONTEXT(kAddress, kExpectedValueHigh, kExpectedValueLow,
|
||||
kTimeout)
|
||||
DEFINE_RESULT_AND_PARAMETER_TYPES(
|
||||
MachineType::Uint32(), // result 1
|
||||
MachineType::Uint32(), // kAddress
|
||||
MachineType::Uint32(), // kExpectedValueHigh
|
||||
MachineType::Uint32(), // kExpectedValueLow
|
||||
MachineType::Float64()) // kTimeout
|
||||
DECLARE_DESCRIPTOR(WasmI64AtomicWaitDescriptor, CallInterfaceDescriptor)
|
||||
};
|
||||
|
||||
class CloneObjectWithVectorDescriptor final : public CallInterfaceDescriptor {
|
||||
public:
|
||||
DEFINE_PARAMETERS(kSource, kFlags, kSlot, kVector)
|
||||
|
@ -4193,7 +4193,7 @@ void Isolate::SetAtomicsWaitCallback(v8::Isolate::AtomicsWaitCallback callback,
|
||||
|
||||
void Isolate::RunAtomicsWaitCallback(v8::Isolate::AtomicsWaitEvent event,
|
||||
Handle<JSArrayBuffer> array_buffer,
|
||||
size_t offset_in_bytes, int32_t value,
|
||||
size_t offset_in_bytes, int64_t value,
|
||||
double timeout_in_ms,
|
||||
AtomicsWaitWakeHandle* stop_handle) {
|
||||
DCHECK(array_buffer->is_shared());
|
||||
|
@ -1443,7 +1443,7 @@ class Isolate final : private HiddenFactory {
|
||||
void* data);
|
||||
void RunAtomicsWaitCallback(v8::Isolate::AtomicsWaitEvent event,
|
||||
Handle<JSArrayBuffer> array_buffer,
|
||||
size_t offset_in_bytes, int32_t value,
|
||||
size_t offset_in_bytes, int64_t value,
|
||||
double timeout_in_ms,
|
||||
AtomicsWaitWakeHandle* stop_handle);
|
||||
|
||||
|
@ -283,20 +283,42 @@ RUNTIME_FUNCTION(Runtime_WasmAtomicWake) {
|
||||
return FutexEmulation::Wake(array_buffer, address, count);
|
||||
}
|
||||
|
||||
double WaitTimeoutInMs(double timeout_ns) {
|
||||
return timeout_ns < 0
|
||||
? V8_INFINITY
|
||||
: timeout_ns / (base::Time::kNanosecondsPerMicrosecond *
|
||||
base::Time::kMicrosecondsPerMillisecond);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmI32AtomicWait) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(4, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
|
||||
CONVERT_NUMBER_CHECKED(uint32_t, address, Uint32, args[1]);
|
||||
CONVERT_NUMBER_CHECKED(int32_t, expected_value, Int32, args[2]);
|
||||
CONVERT_DOUBLE_ARG_CHECKED(timeout, 3);
|
||||
timeout = timeout < 0 ? V8_INFINITY
|
||||
: timeout / (base::Time::kNanosecondsPerMicrosecond *
|
||||
base::Time::kMicrosecondsPerMillisecond);
|
||||
CONVERT_DOUBLE_ARG_CHECKED(timeout_ns, 3);
|
||||
double timeout_ms = WaitTimeoutInMs(timeout_ns);
|
||||
Handle<JSArrayBuffer> array_buffer =
|
||||
getSharedArrayBuffer(instance, isolate, address);
|
||||
return FutexEmulation::Wait(isolate, array_buffer, address, expected_value,
|
||||
timeout);
|
||||
return FutexEmulation::Wait32(isolate, array_buffer, address, expected_value,
|
||||
timeout_ms);
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(5, args.length());
|
||||
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
|
||||
CONVERT_NUMBER_CHECKED(uint32_t, address, Uint32, args[1]);
|
||||
CONVERT_NUMBER_CHECKED(uint32_t, expected_value_high, Uint32, args[2]);
|
||||
CONVERT_NUMBER_CHECKED(uint32_t, expected_value_low, Uint32, args[3]);
|
||||
CONVERT_DOUBLE_ARG_CHECKED(timeout_ns, 4);
|
||||
int64_t expected_value = (static_cast<uint64_t>(expected_value_high) << 32) |
|
||||
static_cast<uint64_t>(expected_value_low);
|
||||
double timeout_ms = WaitTimeoutInMs(timeout_ns);
|
||||
Handle<JSArrayBuffer> array_buffer =
|
||||
getSharedArrayBuffer(instance, isolate, address);
|
||||
return FutexEmulation::Wait64(isolate, array_buffer, address, expected_value,
|
||||
timeout_ms);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
@ -532,6 +532,7 @@ namespace internal {
|
||||
F(ThrowWasmError, 1, 1) \
|
||||
F(ThrowWasmStackOverflow, 0, 1) \
|
||||
F(WasmI32AtomicWait, 4, 1) \
|
||||
F(WasmI64AtomicWait, 5, 1) \
|
||||
F(WasmAtomicWake, 3, 1) \
|
||||
F(WasmExceptionGetValues, 1, 1) \
|
||||
F(WasmExceptionGetTag, 1, 1) \
|
||||
|
@ -63,6 +63,7 @@ struct WasmException;
|
||||
#define ATOMIC_OP_LIST(V) \
|
||||
V(AtomicWake, Uint32) \
|
||||
V(I32AtomicWait, Uint32) \
|
||||
V(I64AtomicWait, Uint32) \
|
||||
V(I32AtomicLoad, Uint32) \
|
||||
V(I64AtomicLoad, Uint64) \
|
||||
V(I32AtomicLoad8U, Uint8) \
|
||||
|
@ -264,7 +264,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
|
||||
|
||||
// Atomic operations.
|
||||
CASE_OP(AtomicWake, "atomic_wake")
|
||||
CASE_I32_OP(AtomicWait, "atomic_wait")
|
||||
CASE_INT_OP(AtomicWait, "atomic_wait")
|
||||
CASE_UNSIGNED_ALL_OP(AtomicLoad, "atomic_load")
|
||||
CASE_UNSIGNED_ALL_OP(AtomicStore, "atomic_store")
|
||||
CASE_UNSIGNED_ALL_OP(AtomicAdd, "atomic_add")
|
||||
|
@ -420,6 +420,7 @@ bool IsJSCompatibleSignature(const FunctionSig* sig);
|
||||
#define FOREACH_ATOMIC_OPCODE(V) \
|
||||
V(AtomicWake, 0xfe00, i_ii) \
|
||||
V(I32AtomicWait, 0xfe01, i_iil) \
|
||||
V(I64AtomicWait, 0xfe02, i_ill) \
|
||||
V(I32AtomicLoad, 0xfe10, i_i) \
|
||||
V(I64AtomicLoad, 0xfe11, l_i) \
|
||||
V(I32AtomicLoad8U, 0xfe12, i_i) \
|
||||
@ -540,6 +541,7 @@ bool IsJSCompatibleSignature(const FunctionSig* sig);
|
||||
V(i_iii, kWasmI32, kWasmI32, kWasmI32, kWasmI32) \
|
||||
V(l_ill, kWasmI64, kWasmI32, kWasmI64, kWasmI64) \
|
||||
V(i_iil, kWasmI32, kWasmI32, kWasmI32, kWasmI64) \
|
||||
V(i_ill, kWasmI32, kWasmI32, kWasmI64, kWasmI64) \
|
||||
V(i_r, kWasmI32, kWasmAnyRef)
|
||||
|
||||
#define FOREACH_SIMD_SIGNATURE(V) \
|
||||
|
@ -28742,7 +28742,7 @@ struct AtomicsWaitCallbackInfo {
|
||||
Local<v8::SharedArrayBuffer> expected_sab;
|
||||
v8::Isolate::AtomicsWaitEvent expected_event;
|
||||
double expected_timeout;
|
||||
int32_t expected_value;
|
||||
int64_t expected_value;
|
||||
size_t expected_offset;
|
||||
|
||||
size_t ncalls = 0;
|
||||
@ -28764,7 +28764,7 @@ class StopAtomicsWaitThread : public v8::base::Thread {
|
||||
|
||||
void AtomicsWaitCallbackForTesting(
|
||||
v8::Isolate::AtomicsWaitEvent event, Local<v8::SharedArrayBuffer> sab,
|
||||
size_t offset_in_bytes, int32_t value, double timeout_in_ms,
|
||||
size_t offset_in_bytes, int64_t value, double timeout_in_ms,
|
||||
v8::Isolate::AtomicsWaitWakeHandle* wake_handle, void* data) {
|
||||
AtomicsWaitCallbackInfo* info = static_cast<AtomicsWaitCallbackInfo*>(data);
|
||||
info->ncalls++;
|
||||
|
@ -10,7 +10,7 @@
|
||||
load("test/mjsunit/wasm/wasm-constants.js");
|
||||
load("test/mjsunit/wasm/wasm-module-builder.js");
|
||||
|
||||
function WasmAtomicWakeFunction(memory, offset, index, num) {
|
||||
function WasmAtomicWake(memory, offset, index, num) {
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addImportedMemory("m", "memory", 0, 20, "shared");
|
||||
builder.addFunction("main", kSig_i_ii)
|
||||
@ -27,7 +27,7 @@ function WasmAtomicWakeFunction(memory, offset, index, num) {
|
||||
return instance.exports.main(index, num);
|
||||
}
|
||||
|
||||
function WasmI32AtomicWaitFunction(memory, offset, index, val, timeout) {
|
||||
function WasmI32AtomicWait(memory, offset, index, val, timeout) {
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addImportedMemory("m", "memory", 0, 20, "shared");
|
||||
builder.addFunction("main",
|
||||
@ -47,166 +47,264 @@ function WasmI32AtomicWaitFunction(memory, offset, index, val, timeout) {
|
||||
return instance.exports.main(index, val, timeout);
|
||||
}
|
||||
|
||||
function WasmI64AtomicWait(memory, offset, index, val_low,
|
||||
val_high, timeout) {
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addImportedMemory("m", "memory", 0, 20, "shared");
|
||||
// Wrapper for I64AtomicWait that takes two I32 values and combines to into
|
||||
// I64 for the instruction parameter.
|
||||
builder.addFunction("main",
|
||||
makeSig([kWasmI32, kWasmI32, kWasmI32, kWasmF64], [kWasmI32]))
|
||||
.addLocals({i64_count: 1}) // local that is passed as value param to wait
|
||||
.addBody([
|
||||
kExprGetLocal, 1,
|
||||
kExprI64UConvertI32,
|
||||
kExprI64Const, 32,
|
||||
kExprI64Shl,
|
||||
kExprGetLocal, 2,
|
||||
kExprI64UConvertI32,
|
||||
kExprI64Ior,
|
||||
kExprSetLocal, 4, // Store the created I64 value in local
|
||||
kExprGetLocal, 0,
|
||||
kExprGetLocal, 4,
|
||||
kExprGetLocal, 3,
|
||||
kExprI64SConvertF64,
|
||||
kAtomicPrefix,
|
||||
kExprI64AtomicWait, /* alignment */ 0, offset])
|
||||
.exportAs("main");
|
||||
|
||||
// Instantiate module, get function exports
|
||||
let module = new WebAssembly.Module(builder.toBuffer());
|
||||
let instance = new WebAssembly.Instance(module, {m: {memory}});
|
||||
return instance.exports.main(index, val_high, val_low, timeout);
|
||||
}
|
||||
|
||||
(function TestInvalidIndex() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
|
||||
// Valid indexes are 0-65535 (1 page).
|
||||
[-2, 65536, 0xffffffff].forEach(function(invalidIndex) {
|
||||
assertThrows(function() {
|
||||
WasmAtomicWakeFunction(memory, 0, invalidIndex, -1);
|
||||
WasmAtomicWake(memory, 0, invalidIndex, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmI32AtomicWaitFunction(memory, 0, invalidIndex, 0, -1);
|
||||
WasmI32AtomicWait(memory, 0, invalidIndex, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmAtomicWakeFunction(memory, invalidIndex, 0, -1);
|
||||
WasmI64AtomicWait(memory, 0, invalidIndex, 0, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmI32AtomicWaitFunction(memory, invalidIndex, 0, 0, -1);
|
||||
WasmAtomicWake(memory, invalidIndex, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmAtomicWakeFunction(memory, invalidIndex/2, invalidIndex/2, -1);
|
||||
WasmI32AtomicWait(memory, invalidIndex, 0, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmI32AtomicWaitFunction(memory, invalidIndex/2, invalidIndex/2, 0, -1);
|
||||
WasmI64AtomicWait(memory, invalidIndex, 0, 0, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmAtomicWake(memory, invalidIndex/2, invalidIndex/2, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmI32AtomicWait(memory, invalidIndex/2, invalidIndex/2, 0, -1);
|
||||
}, Error);
|
||||
assertThrows(function() {
|
||||
WasmI64AtomicWait(memory, invalidIndex/2, invalidIndex/2, 0, 0, -1);
|
||||
}, Error);
|
||||
});
|
||||
})();
|
||||
|
||||
(function TestWaitTimeout() {
|
||||
(function TestI32WaitTimeout() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
var waitMs = 100;
|
||||
var startTime = new Date();
|
||||
assertEquals(2, WasmI32AtomicWaitFunction(memory, 0, 0, 0, waitMs*1000000));
|
||||
assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 0, waitMs*1000000));
|
||||
var endTime = new Date();
|
||||
assertTrue(endTime - startTime >= waitMs);
|
||||
})();
|
||||
|
||||
(function TestWaitNotEqual() {
|
||||
(function TestI64WaitTimeout() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
assertEquals(1, WasmI32AtomicWaitFunction(memory, 0, 0, 42, -1));
|
||||
var waitMs = 100;
|
||||
var startTime = new Date();
|
||||
assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 0, 0, waitMs*1000000));
|
||||
var endTime = new Date();
|
||||
assertTrue(endTime - startTime >= waitMs);
|
||||
})();
|
||||
|
||||
assertEquals(2, WasmI32AtomicWaitFunction(memory, 0, 0, 0, 0));
|
||||
(function TestI32WaitNotEqual() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
assertEquals(1, WasmI32AtomicWait(memory, 0, 0, 42, -1));
|
||||
|
||||
assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 0, 0));
|
||||
|
||||
let i32a = new Int32Array(memory.buffer);
|
||||
i32a[0] = 1;
|
||||
assertEquals(1, WasmI32AtomicWaitFunction(memory, 0, 0, 0, -1));
|
||||
assertEquals(1, WasmI32AtomicWait(memory, 0, 0, 0, -1));
|
||||
assertEquals(2, WasmI32AtomicWait(memory, 0, 0, 1, 0));
|
||||
})();
|
||||
|
||||
(function TestI64WaitNotEqual() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
assertEquals(1, WasmI64AtomicWait(memory, 0, 0, 42, 0, -1));
|
||||
|
||||
assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 0, 0, 0));
|
||||
|
||||
let i32a = new Int32Array(memory.buffer);
|
||||
i32a[0] = 1;
|
||||
i32a[1] = 2;
|
||||
assertEquals(1, WasmI64AtomicWait(memory, 0, 0, 0, 0, -1));
|
||||
assertEquals(2, WasmI64AtomicWait(memory, 0, 0, 1, 2, 0));
|
||||
})();
|
||||
|
||||
(function TestWakeCounts() {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
|
||||
[-1, 0, 4, 100, 0xffffffff].forEach(function(count) {
|
||||
WasmAtomicWakeFunction(memory, 0, 0, count);
|
||||
WasmAtomicWake(memory, 0, 0, count);
|
||||
});
|
||||
})();
|
||||
|
||||
//// WORKER ONLY TESTS
|
||||
|
||||
if (this.Worker) {
|
||||
let wasm_wake_adapter = (memory, offset, index, count) => {
|
||||
return WasmAtomicWakeFunction(memory, offset, index, count);
|
||||
};
|
||||
|
||||
let js_wake_adapter = (memory, offset, index, count) => {
|
||||
let i32a = new Int32Array(memory.buffer, offset);
|
||||
return Atomics.wake(i32a, index>>>2, count);
|
||||
};
|
||||
// This test creates 4 workers that wait on consecutive (8 byte separated to
|
||||
// satisfy alignments for all kinds of wait) memory locations to test various
|
||||
// wait/wake combinations. For each combination, each thread waits 3 times
|
||||
// expecting all 4 threads to be woken with wake(4) in first iteration, all 4
|
||||
// to be woken with wake(5) in second iteration and, 3 and 1 to be woken in
|
||||
// third iteration.
|
||||
|
||||
// Wait adapter strings that can be passed as a parameter to TestWaitWake to generate
|
||||
// custom worker script
|
||||
let js_wait_adapter = `(memory, offset, index, val) => {
|
||||
let i32a = new Int32Array(memory.buffer, offset);
|
||||
let res = Atomics.wait(i32a, index>>>2, val);
|
||||
if (res == "ok") return 0;
|
||||
if (res == "not-equal") return 1;
|
||||
return 2;
|
||||
}`
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
let i32a = new Int32Array(memory.buffer);
|
||||
const numWorkers = 4;
|
||||
|
||||
let wasm_wait_adapter = `(memory, offset, index, val) => {
|
||||
let workerScript = `onmessage = function(msg) {
|
||||
load("test/mjsunit/wasm/wasm-constants.js");
|
||||
load("test/mjsunit/wasm/wasm-module-builder.js");
|
||||
${WasmI32AtomicWaitFunction.toString()}
|
||||
return WasmI32AtomicWaitFunction(memory, offset, index, val, -1);
|
||||
}`
|
||||
|
||||
let TestWaitWake = function(wait_adapter, wake_adapter, num_workers, num_workers_wake) {
|
||||
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
|
||||
${WasmI32AtomicWait.toString()}
|
||||
${WasmI64AtomicWait.toString()}
|
||||
let id = msg.id;
|
||||
let memory = msg.memory;
|
||||
let i32a = new Int32Array(memory.buffer);
|
||||
// SAB values:
|
||||
// memory[id*4], where id in range [0, num_workers]:
|
||||
// 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.
|
||||
//
|
||||
// memory[num_workers*4]:
|
||||
// always 0. Each worker is waiting on this index.
|
||||
// indices are right shifted by 2 for Atomics.wait to convert them to index
|
||||
// for Int32Array
|
||||
// for wasm-wake numWorkers threads
|
||||
let result = Atomics.wait(i32a, 0>>>2, 0);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers + 1 threads
|
||||
result = Atomics.wait(i32a, 8>>>2, 0);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers - 1 threads
|
||||
result = Atomics.wait(i32a, 16>>>2, 0);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers threads
|
||||
result = WasmI32AtomicWait(memory, 0, 24, 0, -1);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers + 1 threads
|
||||
result = WasmI32AtomicWait(memory, 0, 32, 0, -1);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers - 1 threads
|
||||
result = WasmI32AtomicWait(memory, 0, 40, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers threads
|
||||
result = WasmI32AtomicWait(memory, 0, 48, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers + 1 threads
|
||||
result = WasmI32AtomicWait(memory, 0, 56, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers - 1 threads
|
||||
result = WasmI32AtomicWait(memory, 0, 64, 0, -1);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers threads
|
||||
result = WasmI64AtomicWait(memory, 0, 72, 0, 0, -1);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers + 1 threads
|
||||
result = WasmI64AtomicWait(memory, 0, 80, 0, 0, -1);
|
||||
postMessage(result);
|
||||
// for js-wake numWorkers - 1 threads
|
||||
result = WasmI64AtomicWait(memory, 0, 88, 0, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers threads
|
||||
result = WasmI64AtomicWait(memory, 0, 96, 0, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers + 1 threads
|
||||
result = WasmI64AtomicWait(memory, 0, 104, 0, 0, -1);
|
||||
postMessage(result);
|
||||
// for wasm-wake numWorkers - 1 threads
|
||||
result = WasmI64AtomicWait(memory, 0, 112, 0, 0, -1);
|
||||
postMessage(result);
|
||||
};`;
|
||||
|
||||
let workerScript =
|
||||
`onmessage = function(msg) {
|
||||
let id = msg.id;
|
||||
let memory = msg.memory;
|
||||
let i32a = new Int32Array(memory.buffer);
|
||||
let wait_adapter = eval(msg.wait_adapter);
|
||||
let result = wait_adapter(memory, 0, 4*${num_workers}, 0);
|
||||
// Set i32a[id] to 1 to notify the main thread which workers were
|
||||
// woken up.
|
||||
Atomics.store(i32a, id, 1);
|
||||
postMessage(result);
|
||||
};`;
|
||||
let waitForAllWorkers = function(index) {
|
||||
// index is right shifted by 2 to convert to index in Int32Array
|
||||
while (%AtomicsNumWaitersForTesting(i32a, index>>>2) != numWorkers) {}
|
||||
}
|
||||
|
||||
let workers = [];
|
||||
for (let id = 0; id < num_workers; id++) {
|
||||
workers[id] = new Worker(workerScript, {type: 'string'});
|
||||
workers[id].postMessage({id, memory, wait_adapter});
|
||||
}
|
||||
|
||||
// Spin until all workers are waiting on the futex.
|
||||
while (%AtomicsNumWaitersForTesting(i32a, num_workers) != num_workers) {}
|
||||
|
||||
if (num_workers_wake < num_workers) {
|
||||
assertEquals(num_workers_wake, wake_adapter(memory, 0, 4*num_workers, num_workers_wake));
|
||||
let jsWakeCheck = function(index, num, workers, msg) {
|
||||
waitForAllWorkers(index);
|
||||
let indexJs = index>>>2; // convert to index in Int32Array
|
||||
if (num >= numWorkers) {
|
||||
// if numWorkers or more is passed to wake, numWorkers workers should be
|
||||
// woken.
|
||||
assertEquals(numWorkers, Atomics.wake(i32a, indexJs, num));
|
||||
} else {
|
||||
assertEquals(num_workers, wake_adapter(memory, 0, 4*num_workers, num_workers_wake));
|
||||
num_workers_wake = num_workers;
|
||||
// if num < numWorkers is passed to wake, num workers should be woken.
|
||||
// Then the remaining workers are woken for the next part
|
||||
assertEquals(num, Atomics.wake(i32a, indexJs, num));
|
||||
assertEquals(numWorkers-num, Atomics.wake(i32a, indexJs, numWorkers));
|
||||
}
|
||||
|
||||
let wokenCount = 0;
|
||||
while (wokenCount < num_workers_wake) {
|
||||
for (let id = 0; id < num_workers; 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(0, workers[id].getMessage());
|
||||
workers[id].terminate();
|
||||
wokenCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(num_workers - num_workers_wake,
|
||||
%AtomicsNumWaitersForTesting(i32a, num_workers));
|
||||
|
||||
// Finally wake and kill all workers.
|
||||
wake_adapter(memory, 0, 4*num_workers, num_workers)
|
||||
for (let id = 0; id < num_workers; id++) {
|
||||
workers[id].terminate();
|
||||
for (let id = 0; id < numWorkers; id++) {
|
||||
assertEquals(msg, workers[id].getMessage());
|
||||
}
|
||||
};
|
||||
|
||||
TestWaitWake(js_wait_adapter, wasm_wake_adapter, 1, 1);
|
||||
TestWaitWake(js_wait_adapter, wasm_wake_adapter, 4, 4);
|
||||
TestWaitWake(js_wait_adapter, wasm_wake_adapter, 4, 3);
|
||||
TestWaitWake(js_wait_adapter, wasm_wake_adapter, 3, 4);
|
||||
let wasmWakeCheck = function(index, num, workers, msg) {
|
||||
waitForAllWorkers(index);
|
||||
if (num >= numWorkers) {
|
||||
// if numWorkers or more is passed to wake, numWorkers workers should be
|
||||
// woken.
|
||||
assertEquals(numWorkers, WasmAtomicWake(memory, 0, index, num));
|
||||
} else {
|
||||
// if num < numWorkers is passed to wake, num workers should be woken.
|
||||
// Then the remaining workers are woken for the next part
|
||||
assertEquals(num, WasmAtomicWake(memory, 0, index, num));
|
||||
assertEquals(numWorkers-num,
|
||||
WasmAtomicWake(memory, 0, index, numWorkers));
|
||||
}
|
||||
for (let id = 0; id < numWorkers; id++) {
|
||||
assertEquals(msg, workers[id].getMessage());
|
||||
}
|
||||
};
|
||||
|
||||
TestWaitWake(wasm_wait_adapter, wasm_wake_adapter, 1, 1);
|
||||
TestWaitWake(wasm_wait_adapter, wasm_wake_adapter, 4, 4);
|
||||
TestWaitWake(wasm_wait_adapter, wasm_wake_adapter, 4, 3);
|
||||
TestWaitWake(wasm_wait_adapter, wasm_wake_adapter, 3, 4);
|
||||
let workers = [];
|
||||
for (let id = 0; id < numWorkers; id++) {
|
||||
workers[id] = new Worker(workerScript, {type: 'string'});
|
||||
workers[id].postMessage({id, memory});
|
||||
}
|
||||
|
||||
TestWaitWake(wasm_wait_adapter, js_wake_adapter, 1, 1);
|
||||
TestWaitWake(wasm_wait_adapter, js_wake_adapter, 4, 4);
|
||||
TestWaitWake(wasm_wait_adapter, js_wake_adapter, 4, 3);
|
||||
TestWaitWake(wasm_wait_adapter, js_wake_adapter, 3, 4);
|
||||
wasmWakeCheck(0, numWorkers, workers, "ok");
|
||||
wasmWakeCheck(8, numWorkers + 1, workers, "ok");
|
||||
wasmWakeCheck(16, numWorkers - 1, workers, "ok");
|
||||
|
||||
jsWakeCheck(24, numWorkers, workers, 0);
|
||||
jsWakeCheck(32, numWorkers + 1, workers, 0);
|
||||
jsWakeCheck(40, numWorkers - 1, workers, 0);
|
||||
|
||||
wasmWakeCheck(48, numWorkers, workers, 0);
|
||||
wasmWakeCheck(56, numWorkers + 1, workers, 0);
|
||||
wasmWakeCheck(64, numWorkers - 1, workers, 0);
|
||||
|
||||
jsWakeCheck(72, numWorkers, workers, 0);
|
||||
jsWakeCheck(80, numWorkers + 1, workers, 0);
|
||||
jsWakeCheck(88, numWorkers - 1, workers, 0);
|
||||
|
||||
wasmWakeCheck(96, numWorkers, workers, 0);
|
||||
wasmWakeCheck(104, numWorkers + 1, workers, 0);
|
||||
wasmWakeCheck(112, numWorkers - 1, workers, 0);
|
||||
|
||||
for (let id = 0; id < numWorkers; id++) {
|
||||
workers[id].terminate();
|
||||
}
|
||||
}
|
||||
|
@ -357,6 +357,7 @@ let kAtomicPrefix = 0xfe;
|
||||
|
||||
let kExprAtomicWake = 0x00;
|
||||
let kExprI32AtomicWait = 0x01;
|
||||
let kExprI64AtomicWait = 0x02;
|
||||
let kExprI32AtomicLoad = 0x10;
|
||||
let kExprI32AtomicLoad8U = 0x12;
|
||||
let kExprI32AtomicLoad16U = 0x13;
|
||||
|
Loading…
Reference in New Issue
Block a user