[wasm] Add I32AtomicWait implementation

Bug=v8:8075
R=adamk@chromium.org,binji@chromium.org

Change-Id: I2367e24888a268ce93b1730195cb4767896861cc
Reviewed-on: https://chromium-review.googlesource.com/c/1341126
Reviewed-by: Ben Smith <binji@chromium.org>
Commit-Queue: Aseem Garg <aseemgarg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57624}
This commit is contained in:
Aseem Garg 2018-11-19 17:08:30 -08:00 committed by Commit Bot
parent de8609aaf5
commit 2b9bd491ce
15 changed files with 220 additions and 15 deletions

View File

@ -1232,6 +1232,7 @@ namespace internal {
ASM(WasmCompileLazy) \
TFC(WasmAllocateHeapNumber, AllocateHeapNumber, 1) \
TFC(WasmAtomicWake, WasmAtomicWake, 1) \
TFC(WasmI32AtomicWait, WasmI32AtomicWait, 1) \
TFC(WasmCallJavaScript, CallTrampoline, 1) \
TFC(WasmMemoryGrow, WasmMemoryGrow, 1) \
TFC(WasmRecordWrite, RecordWrite, 1) \
@ -1531,6 +1532,7 @@ namespace internal {
FOREACH_WASM_TRAPREASON(VTRAP) \
V(WasmAllocateHeapNumber) \
V(WasmAtomicWake) \
V(WasmI32AtomicWait) \
V(WasmCallJavaScript) \
V(WasmMemoryGrow) \
V(WasmRecordWrite) \

View File

@ -183,8 +183,8 @@ BUILTIN(AtomicsWait) {
Handle<JSArrayBuffer> array_buffer = sta->GetBuffer();
size_t addr = (i << 2) + sta->byte_offset();
return FutexEmulation::Wait(isolate, array_buffer, addr, value_int32,
timeout_number);
return FutexEmulation::WaitJs(isolate, array_buffer, addr, value_int32,
timeout_number);
}
} // namespace internal

View File

@ -123,6 +123,39 @@ TF_BUILTIN(WasmAtomicWake, WasmBuiltinsAssembler) {
ReturnRaw(SmiToInt32(result_smi));
}
TF_BUILTIN(WasmI32AtomicWait, WasmBuiltinsAssembler) {
TNode<Uint32T> address =
UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
TNode<Int32T> expected_value =
UncheckedCast<Int32T>(Parameter(Descriptor::kExpectedValue));
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_heap = UncheckedCast<HeapNumber>(
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
StoreHeapNumberValue(expected_value_heap,
ChangeInt32ToFloat64(expected_value));
TNode<HeapNumber> timeout_heap = UncheckedCast<HeapNumber>(
CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
StoreHeapNumberValue(timeout_heap, timeout);
TNode<Smi> result_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
Runtime::kWasmI32AtomicWait, centry, NoContextConstant(), instance,
address_heap, expected_value_heap, timeout_heap));
ReturnRaw(SmiToInt32(result_smi));
}
TF_BUILTIN(WasmMemoryGrow, WasmBuiltinsAssembler) {
TNode<Int32T> num_pages =
UncheckedCast<Int32T>(Parameter(Descriptor::kNumPages));

View File

@ -4086,6 +4086,34 @@ Node* WasmGraphBuilder::AtomicOp(wasm::WasmOpcode opcode, Node* const* inputs,
break;
}
case wasm::kExprI32AtomicWait: {
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]);
}
WasmI32AtomicWaitDescriptor 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::kWasmI32AtomicWait, RelocInfo::WASM_STUB_CALL);
node = graph()->NewNode(mcgraph()->common()->Call(call_descriptor),
call_target, address, inputs[1], timeout,
Effect(), Control());
break;
}
default:
FATAL_UNSUPPORTED_OPCODE(opcode);
}

View File

@ -86,6 +86,28 @@ void AtomicsWaitWakeHandle::Wake() {
isolate_->futex_wait_list_node()->NotifyWake();
}
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);
if (res->IsSmi()) {
int val = Smi::ToInt(res);
switch (val) {
case WaitReturnValue::kOk:
return ReadOnlyRoots(isolate).ok();
case WaitReturnValue::kNotEqual:
return ReadOnlyRoots(isolate).not_equal();
case WaitReturnValue::kTimedOut:
return ReadOnlyRoots(isolate).timed_out();
default:
UNREACHABLE();
}
}
return res;
}
Object* FutexEmulation::Wait(Isolate* isolate,
Handle<JSArrayBuffer> array_buffer, size_t addr,
int32_t value, double rel_timeout_ms) {
@ -139,7 +161,7 @@ Object* FutexEmulation::Wait(Isolate* isolate,
ResetWaitingOnScopeExit reset_waiting(node);
if (*p != value) {
result = ReadOnlyRoots(isolate).not_equal();
result = Smi::FromInt(WaitReturnValue::kNotEqual);
callback_result = AtomicsWaitEvent::kNotEqual;
break;
}
@ -197,7 +219,7 @@ Object* FutexEmulation::Wait(Isolate* isolate,
}
if (!node->waiting_) {
result = ReadOnlyRoots(isolate).ok();
result = Smi::FromInt(WaitReturnValue::kOk);
break;
}
@ -205,7 +227,7 @@ Object* FutexEmulation::Wait(Isolate* isolate,
if (use_timeout) {
current_time = base::TimeTicks::Now();
if (current_time >= timeout_time) {
result = ReadOnlyRoots(isolate).timed_out();
result = Smi::FromInt(WaitReturnValue::kTimedOut);
callback_result = AtomicsWaitEvent::kTimedOut;
break;
}

View File

@ -118,6 +118,11 @@ class FutexEmulation : public AllStatic {
// |rel_timeout_ms| can be Infinity.
// If woken, return "ok", otherwise return "timed-out". The initial check and
// the decision to wait happen atomically.
static Object* WaitJs(Isolate* isolate, Handle<JSArrayBuffer> array_buffer,
size_t addr, int32_t value, double rel_timeout_ms);
// 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);

View File

@ -361,6 +361,11 @@ void WasmAtomicWakeDescriptor::InitializePlatformSpecific(
DefaultInitializePlatformSpecific(data, kParameterCount);
}
void WasmI32AtomicWaitDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);
}
void CloneObjectWithVectorDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
DefaultInitializePlatformSpecific(data, kParameterCount);

View File

@ -77,6 +77,7 @@ namespace internal {
V(WasmMemoryGrow) \
V(WasmThrow) \
V(WasmAtomicWake) \
V(WasmI32AtomicWait) \
V(CloneObjectWithVector) \
BUILTIN_LIST_TFS(V)
@ -1114,6 +1115,16 @@ class WasmAtomicWakeDescriptor final : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(WasmAtomicWakeDescriptor, CallInterfaceDescriptor)
};
class WasmI32AtomicWaitDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS_NO_CONTEXT(kAddress, kExpectedValue, kTimeout)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::Uint32(), // result 1
MachineType::Uint32(), // kAddress
MachineType::Int32(), // kExpectedValue
MachineType::Float64()) // kTimeout
DECLARE_DESCRIPTOR(WasmI32AtomicWaitDescriptor, CallInterfaceDescriptor)
};
class CloneObjectWithVectorDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kSource, kFlags, kSlot, kVector)

View File

@ -257,13 +257,9 @@ RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
return reinterpret_cast<Object*>(entrypoint);
}
RUNTIME_FUNCTION(Runtime_WasmAtomicWake) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
CONVERT_NUMBER_CHECKED(uint32_t, address, Uint32, args[1]);
CONVERT_NUMBER_CHECKED(uint32_t, count, Uint32, args[2]);
// Should be called from within a handle scope
Handle<JSArrayBuffer> getSharedArrayBuffer(Handle<WasmInstanceObject> instance,
Isolate* isolate, uint32_t address) {
DCHECK(instance->has_memory_object());
Handle<JSArrayBuffer> array_buffer(instance->memory_object()->array_buffer(),
isolate);
@ -273,9 +269,35 @@ RUNTIME_FUNCTION(Runtime_WasmAtomicWake) {
// Should have trapped if address was OOB
DCHECK_LT(address, array_buffer->byte_length());
return array_buffer;
}
RUNTIME_FUNCTION(Runtime_WasmAtomicWake) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(WasmInstanceObject, instance, 0);
CONVERT_NUMBER_CHECKED(uint32_t, address, Uint32, args[1]);
CONVERT_NUMBER_CHECKED(uint32_t, count, Uint32, args[2]);
Handle<JSArrayBuffer> array_buffer =
getSharedArrayBuffer(instance, isolate, address);
return FutexEmulation::Wake(array_buffer, address, count);
}
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);
Handle<JSArrayBuffer> array_buffer =
getSharedArrayBuffer(instance, isolate, address);
return FutexEmulation::Wait(isolate, array_buffer, address, expected_value,
timeout);
}
} // namespace internal
} // namespace v8

View File

@ -531,6 +531,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_WASM(F, I) \
F(ThrowWasmError, 1, 1) \
F(ThrowWasmStackOverflow, 0, 1) \
F(WasmI32AtomicWait, 4, 1) \
F(WasmAtomicWake, 3, 1) \
F(WasmExceptionGetValues, 1, 1) \
F(WasmExceptionGetTag, 1, 1) \

View File

@ -62,6 +62,7 @@ struct WasmException;
#define ATOMIC_OP_LIST(V) \
V(AtomicWake, Uint32) \
V(I32AtomicWait, Uint32) \
V(I32AtomicLoad, Uint32) \
V(I64AtomicLoad, Uint64) \
V(I32AtomicLoad8U, Uint8) \

View File

@ -264,6 +264,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
// Atomic operations.
CASE_OP(AtomicWake, "atomic_wake")
CASE_I32_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")

View File

@ -419,6 +419,7 @@ bool IsJSCompatibleSignature(const FunctionSig* sig);
#define FOREACH_ATOMIC_OPCODE(V) \
V(AtomicWake, 0xfe00, i_ii) \
V(I32AtomicWait, 0xfe01, i_iil) \
V(I32AtomicLoad, 0xfe10, i_i) \
V(I64AtomicLoad, 0xfe11, l_i) \
V(I32AtomicLoad8U, 0xfe12, i_i) \
@ -538,6 +539,7 @@ bool IsJSCompatibleSignature(const FunctionSig* sig);
V(v_iii, kWasmStmt, kWasmI32, kWasmI32, kWasmI32) \
V(i_iii, kWasmI32, kWasmI32, kWasmI32, kWasmI32) \
V(l_ill, kWasmI64, kWasmI32, kWasmI64, kWasmI64) \
V(i_iil, kWasmI32, kWasmI32, kWasmI32, kWasmI64) \
V(i_r, kWasmI32, kWasmAnyRef)
#define FOREACH_SIMD_SIGNATURE(V) \

View File

@ -27,6 +27,26 @@ function WasmAtomicWakeFunction(memory, offset, index, num) {
return instance.exports.main(index, num);
}
function WasmI32AtomicWaitFunction(memory, offset, index, val, timeout) {
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "memory", 0, 20, "shared");
builder.addFunction("main",
makeSig([kWasmI32, kWasmI32, kWasmF64], [kWasmI32]))
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kExprI64SConvertF64,
kAtomicPrefix,
kExprI32AtomicWait, /* 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, timeout);
}
(function TestInvalidIndex() {
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
@ -35,15 +55,44 @@ function WasmAtomicWakeFunction(memory, offset, index, num) {
assertThrows(function() {
WasmAtomicWakeFunction(memory, 0, invalidIndex, -1);
}, Error);
assertThrows(function() {
WasmI32AtomicWaitFunction(memory, 0, invalidIndex, 0, -1);
}, Error);
assertThrows(function() {
WasmAtomicWakeFunction(memory, invalidIndex, 0, -1);
}, Error);
assertThrows(function() {
WasmI32AtomicWaitFunction(memory, invalidIndex, 0, 0, -1);
}, Error);
assertThrows(function() {
WasmAtomicWakeFunction(memory, invalidIndex/2, invalidIndex/2, -1);
}, Error);
assertThrows(function() {
WasmI32AtomicWaitFunction(memory, invalidIndex/2, invalidIndex/2, 0, -1);
}, Error);
});
})();
(function TestWaitTimeout() {
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));
var endTime = new Date();
assertTrue(endTime - startTime >= waitMs);
})();
(function TestWaitNotEqual() {
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
assertEquals(1, WasmI32AtomicWaitFunction(memory, 0, 0, 42, -1));
assertEquals(2, WasmI32AtomicWaitFunction(memory, 0, 0, 0, 0));
let i32a = new Int32Array(memory.buffer);
i32a[0] = 1;
assertEquals(1, WasmI32AtomicWaitFunction(memory, 0, 0, 0, -1));
})();
(function TestWakeCounts() {
let memory = new WebAssembly.Memory({initial: 1, maximum: 1, shared: true});
@ -59,16 +108,28 @@ if (this.Worker) {
return WasmAtomicWakeFunction(memory, offset, index, count);
};
// Wait adapter string that can be passed as a parameter to TestWaitWake to generate
let js_wake_adapter = (memory, offset, index, count) => {
let i32a = new Int32Array(memory.buffer, offset);
return Atomics.wake(i32a, index>>>2, count);
};
// 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/4, val);
let res = Atomics.wait(i32a, index>>>2, val);
if (res == "ok") return 0;
if (res == "not-equal") return 1;
return 2;
}`
let wasm_wait_adapter = `(memory, offset, index, val) => {
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});
let i32a = new Int32Array(memory.buffer);
@ -128,7 +189,7 @@ if (this.Worker) {
%AtomicsNumWaitersForTesting(i32a, num_workers));
// Finally wake and kill all workers.
wake_adapter(memory, 0, 4*num_workers, num_workers_wake)
wake_adapter(memory, 0, 4*num_workers, num_workers)
for (let id = 0; id < num_workers; id++) {
workers[id].terminate();
}
@ -138,4 +199,14 @@ if (this.Worker) {
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);
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);
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);
}

View File

@ -355,6 +355,7 @@ let kExprF64ReinterpretI64 = 0xbf;
let kAtomicPrefix = 0xfe;
let kExprAtomicWake = 0x00;
let kExprI32AtomicWait = 0x01;
let kExprI32AtomicLoad = 0x10;
let kExprI32AtomicLoad8U = 0x12;
let kExprI32AtomicLoad16U = 0x13;