Commit ac774a36 authored by Deepti Gandluri's avatar Deepti Gandluri Committed by Commit Bot

[wasm] Implement atomic add/sub operations.

 - Decode logic for atomic operations
 - Implementations for I32AtomicAdd, I32AtomicAdd8U, I32AtomicAdd16U,
I32AtomicSub, I32AtomicSub8U, I32AtomicSub16U
 - cctest value helpers for Uint16/Uint8 types

R=binji@chromium.org, bbudge@chromium.org, bradnelson@chromium.org

BUG=v8:6532

Change-Id: I710ee8ef566c5e33866afdf5b47375c2ea6fdbe6
Reviewed-on: https://chromium-review.googlesource.com/595241Reviewed-by: 's avatarBill Budge <bbudge@chromium.org>
Reviewed-by: 's avatarBrad Nelson <bradnelson@chromium.org>
Reviewed-by: 's avatarBen Smith <binji@chromium.org>
Commit-Queue: Deepti Gandluri <gdeepti@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47102}
parent 5a506a9a
...@@ -3748,6 +3748,60 @@ Node* WasmGraphBuilder::Simd8x16ShuffleOp(const uint8_t shuffle[16], ...@@ -3748,6 +3748,60 @@ Node* WasmGraphBuilder::Simd8x16ShuffleOp(const uint8_t shuffle[16],
inputs[0], inputs[1]); inputs[0], inputs[1]);
} }
Node* WasmGraphBuilder::AtomicOp(wasm::WasmOpcode opcode,
const NodeVector& inputs,
wasm::WasmCodePosition position) {
Node* node;
switch (opcode) {
case wasm::kExprI32AtomicAdd: {
BoundsCheckMem(MachineType::Uint32(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicAdd(MachineType::Uint32()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
case wasm::kExprI32AtomicSub: {
BoundsCheckMem(MachineType::Uint32(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicSub(MachineType::Uint32()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
case wasm::kExprI32AtomicAdd16U: {
BoundsCheckMem(MachineType::Uint16(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicAdd(MachineType::Uint16()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
case wasm::kExprI32AtomicSub16U: {
BoundsCheckMem(MachineType::Uint16(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicSub(MachineType::Uint16()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
case wasm::kExprI32AtomicAdd8U: {
BoundsCheckMem(MachineType::Uint8(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicAdd(MachineType::Uint8()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
case wasm::kExprI32AtomicSub8U: {
BoundsCheckMem(MachineType::Uint8(), inputs[0], 0, position);
node = graph()->NewNode(
jsgraph()->machine()->AtomicSub(MachineType::Uint8()), MemBuffer(0),
inputs[0], inputs[1], *effect_, *control_);
break;
}
default:
FATAL_UNSUPPORTED_OPCODE(opcode);
}
*effect_ = node;
return node;
}
static void RecordFunctionCompilation(CodeEventListener::LogEventsAndTags tag, static void RecordFunctionCompilation(CodeEventListener::LogEventsAndTags tag,
Isolate* isolate, Handle<Code> code, Isolate* isolate, Handle<Code> code,
const char* message, uint32_t index, const char* message, uint32_t index,
......
...@@ -271,6 +271,9 @@ class WasmGraphBuilder { ...@@ -271,6 +271,9 @@ class WasmGraphBuilder {
Node* Simd8x16ShuffleOp(const uint8_t shuffle[16], Node* const* inputs); Node* Simd8x16ShuffleOp(const uint8_t shuffle[16], Node* const* inputs);
Node* AtomicOp(wasm::WasmOpcode opcode, const NodeVector& inputs,
wasm::WasmCodePosition position);
bool has_simd() const { return has_simd_; } bool has_simd() const { return has_simd_; }
wasm::ModuleEnv* module_env() const { return module_; } wasm::ModuleEnv* module_env() const { return module_; }
......
...@@ -1369,18 +1369,13 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -1369,18 +1369,13 @@ class WasmFullDecoder : public WasmDecoder {
break; break;
} }
case kAtomicPrefix: { case kAtomicPrefix: {
if (module_ == nullptr || !module_->is_asm_js()) {
error("Atomics are allowed only in AsmJs modules");
break;
}
CHECK_PROTOTYPE_OPCODE(threads); CHECK_PROTOTYPE_OPCODE(threads);
len = 2; len++;
byte atomic_opcode = read_u8<true>(pc_ + 1, "atomic index"); byte atomic_index = read_u8<true>(pc_ + 1, "atomic index");
opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_opcode); opcode = static_cast<WasmOpcode>(opcode << 8 | atomic_index);
sig = WasmOpcodes::AtomicSignature(opcode); TRACE(" @%-4d #%-20s|", startrel(pc_),
if (sig) { WasmOpcodes::OpcodeName(opcode));
BuildAtomicOperator(opcode); len += DecodeAtomicOpcode(opcode);
}
break; break;
} }
default: { default: {
...@@ -1692,7 +1687,22 @@ class WasmFullDecoder : public WasmDecoder { ...@@ -1692,7 +1687,22 @@ class WasmFullDecoder : public WasmDecoder {
return len; return len;
} }
void BuildAtomicOperator(WasmOpcode opcode) { UNIMPLEMENTED(); } unsigned DecodeAtomicOpcode(WasmOpcode opcode) {
unsigned len = 0;
FunctionSig* sig = WasmOpcodes::AtomicSignature(opcode);
if (sig != nullptr) {
compiler::NodeVector inputs(sig->parameter_count(), zone_);
for (int i = static_cast<int>(sig->parameter_count() - 1); i >= 0; --i) {
Value val = Pop(i, sig->GetParam(i));
inputs[i] = val.node;
}
TFNode* node = BUILD(AtomicOp, opcode, inputs, position());
Push(GetReturnType(sig), node);
} else {
error("invalid atomic opcode");
}
return len;
}
void DoReturn() { void DoReturn() {
int count = static_cast<int>(sig_->return_count()); int count = static_cast<int>(sig_->return_count());
......
...@@ -192,6 +192,7 @@ v8_executable("cctest") { ...@@ -192,6 +192,7 @@ v8_executable("cctest") {
"unicode-helpers.h", "unicode-helpers.h",
"wasm/test-run-wasm-64.cc", "wasm/test-run-wasm-64.cc",
"wasm/test-run-wasm-asmjs.cc", "wasm/test-run-wasm-asmjs.cc",
"wasm/test-run-wasm-atomics.cc",
"wasm/test-run-wasm-interpreter.cc", "wasm/test-run-wasm-interpreter.cc",
"wasm/test-run-wasm-js.cc", "wasm/test-run-wasm-js.cc",
"wasm/test-run-wasm-module.cc", "wasm/test-run-wasm-module.cc",
......
...@@ -211,6 +211,7 @@ ...@@ -211,6 +211,7 @@
'wasm/test-run-wasm.cc', 'wasm/test-run-wasm.cc',
'wasm/test-run-wasm-64.cc', 'wasm/test-run-wasm-64.cc',
'wasm/test-run-wasm-asmjs.cc', 'wasm/test-run-wasm-asmjs.cc',
'wasm/test-run-wasm-atomics.cc',
'wasm/test-run-wasm-interpreter.cc', 'wasm/test-run-wasm-interpreter.cc',
'wasm/test-run-wasm-js.cc', 'wasm/test-run-wasm-js.cc',
'wasm/test-run-wasm-module.cc', 'wasm/test-run-wasm-module.cc',
......
...@@ -306,12 +306,22 @@ class ValueHelper { ...@@ -306,12 +306,22 @@ class ValueHelper {
return std::vector<int16_t>(&kValues[0], &kValues[arraysize(kValues)]); return std::vector<int16_t>(&kValues[0], &kValues[arraysize(kValues)]);
} }
static const std::vector<uint16_t> uint16_vector() {
std::vector<int16_t> values = int16_vector();
return std::vector<uint16_t>(values.begin(), values.end());
}
static const std::vector<int8_t> int8_vector() { static const std::vector<int8_t> int8_vector() {
static const int8_t kValues[] = { static const int8_t kValues[] = {
0, 1, 2, INT8_MAX - 1, INT8_MAX, INT8_MIN, INT8_MIN + 1, -2, -1}; 0, 1, 2, INT8_MAX - 1, INT8_MAX, INT8_MIN, INT8_MIN + 1, -2, -1};
return std::vector<int8_t>(&kValues[0], &kValues[arraysize(kValues)]); return std::vector<int8_t>(&kValues[0], &kValues[arraysize(kValues)]);
} }
static const std::vector<uint8_t> uint8_vector() {
std::vector<int8_t> values = int8_vector();
return std::vector<uint8_t>(values.begin(), values.end());
}
static const std::vector<uint32_t> ror_vector() { static const std::vector<uint32_t> ror_vector() {
static const uint32_t kValues[31] = { static const uint32_t kValues[31] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
...@@ -330,7 +340,9 @@ class ValueHelper { ...@@ -330,7 +340,9 @@ class ValueHelper {
#define FOR_INT32_INPUTS(var) FOR_INPUTS(int32_t, int32, var) #define FOR_INT32_INPUTS(var) FOR_INPUTS(int32_t, int32, var)
#define FOR_UINT32_INPUTS(var) FOR_INPUTS(uint32_t, uint32, var) #define FOR_UINT32_INPUTS(var) FOR_INPUTS(uint32_t, uint32, var)
#define FOR_INT16_INPUTS(var) FOR_INPUTS(int16_t, int16, var) #define FOR_INT16_INPUTS(var) FOR_INPUTS(int16_t, int16, var)
#define FOR_UINT16_INPUTS(var) FOR_INPUTS(uint16_t, uint16, var)
#define FOR_INT8_INPUTS(var) FOR_INPUTS(int8_t, int8, var) #define FOR_INT8_INPUTS(var) FOR_INPUTS(int8_t, int8, var)
#define FOR_UINT8_INPUTS(var) FOR_INPUTS(uint8_t, uint8, var)
#define FOR_INT64_INPUTS(var) FOR_INPUTS(int64_t, int64, var) #define FOR_INT64_INPUTS(var) FOR_INPUTS(int64_t, int64, var)
#define FOR_UINT64_INPUTS(var) FOR_INPUTS(uint64_t, uint64, var) #define FOR_UINT64_INPUTS(var) FOR_INPUTS(uint64_t, uint64, var)
#define FOR_FLOAT32_INPUTS(var) FOR_INPUTS(float, float32, var) #define FOR_FLOAT32_INPUTS(var) FOR_INPUTS(float, float32, var)
......
// Copyright 2017 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 "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/wasm-macro-gen.h"
#define WASM_ATOMICS_OP(op) kAtomicPrefix, static_cast<byte>(op)
#define WASM_ATOMICS_BINOP(op, x, y) x, y, WASM_ATOMICS_OP(op)
typedef uint32_t (*Uint32BinOp)(uint32_t, uint32_t);
typedef uint16_t (*Uint16BinOp)(uint16_t, uint16_t);
typedef uint8_t (*Uint8BinOp)(uint8_t, uint8_t);
template <typename T>
T Add(T a, T b) {
return a + b;
}
template <typename T>
T Sub(T a, T b) {
return a - b;
}
void RunU32BinOp(WasmOpcode wasm_op, Uint32BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
uint32_t* memory = r.module().AddMemoryElems<uint32_t>(8);
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
FOR_UINT32_INPUTS(i) {
uint32_t initial = *i;
FOR_UINT32_INPUTS(j) {
r.module().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(*j));
uint32_t expected = expected_op(*i, *j);
CHECK_EQ(expected, r.module().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32Add) { RunU32BinOp(kExprI32AtomicAdd, Add); }
WASM_EXEC_TEST(I32Sub) { RunU32BinOp(kExprI32AtomicSub, Sub); }
void RunU16BinOp(WasmOpcode wasm_op, Uint16BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
uint16_t* memory = r.module().AddMemoryElems<uint16_t>(8);
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
FOR_UINT16_INPUTS(i) {
uint16_t initial = *i;
FOR_UINT16_INPUTS(j) {
r.module().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(*j));
uint16_t expected = expected_op(*i, *j);
CHECK_EQ(expected, r.module().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32Add16U) { RunU16BinOp(kExprI32AtomicAdd16U, Add); }
WASM_EXEC_TEST(I32Sub16U) { RunU16BinOp(kExprI32AtomicSub16U, Sub); }
void RunU8BinOp(WasmOpcode wasm_op, Uint8BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
uint8_t* memory = r.module().AddMemoryElems<uint8_t>(8);
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
FOR_UINT8_INPUTS(i) {
uint8_t initial = *i;
FOR_UINT8_INPUTS(j) {
r.module().WriteMemory(&memory[0], initial);
CHECK_EQ(initial, r.Call(*j));
uint8_t expected = expected_op(*i, *j);
CHECK_EQ(expected, r.module().ReadMemory(&memory[0]));
}
}
}
WASM_EXEC_TEST(I32Add8U) { RunU8BinOp(kExprI32AtomicAdd8U, Add); }
WASM_EXEC_TEST(I32Sub8U) { RunU8BinOp(kExprI32AtomicSub8U, Sub); }
// Copyright 2017 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: --experimental-wasm-threads
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
function VerifyBoundsCheck(buffer, funcs, max) {
kPageSize = 65536;
// Test out of bounds at boundary
for (j = 0; j < funcs.length; j++) {
for (i = buffer.byteLength - funcs[j].memtype_size + 1;
i < buffer.byteLength + funcs[j].memtype_size + 4; i++) {
assertTraps(kTrapMemOutOfBounds, () => funcs[j].func(i, 5));
}
// Test out of bounds at maximum + 1
assertTraps(kTrapMemOutOfBounds, () =>
funcs[j].func((max + 1) * kPageSize, 5));
}
}
(function TestAtomicAdd() {
print("TestAtomicAdd");
let memory = new WebAssembly.Memory({initial: 1, maximum: 10, shared: true});
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
builder.addFunction("atomic_add", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicAdd])
.exportAs("atomic_add");
builder.addFunction("atomic_add16", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicAdd16U])
.exportAs("atomic_add16");
builder.addFunction("atomic_add8", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicAdd8U])
.exportAs("atomic_add8");
// Instantiate module, get function exports
let module = new WebAssembly.Module(builder.toBuffer());
let instance = (new WebAssembly.Instance(module,
{m: {imported_mem: memory}}));
// 32-bit Add
let i32 = new Uint32Array(memory.buffer);
for (i = 0; i < i32.length; i++) {
i32[i] = i;
assertEquals(i, instance.exports.atomic_add(i * 4, 0xACEDACED));
assertEquals(i + 0xACEDACED, i32[i]);
}
// 16-bit Add
let i16 = new Uint16Array(memory.buffer);
for (i = 0; i < i16.length; i++) {
i16[i] = i;
assertEquals(i, instance.exports.atomic_add16(i * 2, 0x1234));
assertEquals(i + 0x1234, i16[i]);
}
// 8-bit Add
let i8 = new Uint8Array(memory.buffer);
for (i = 0; i < i8.length; i++) {
i8[i] = 0x11;
assertEquals(0x11, instance.exports.atomic_add8(i, 0xee));
assertEquals(0x11 + 0xee, i8[i]);
}
var adds = [{func: instance.exports.atomic_add, memtype_size: 4},
{func: instance.exports.atomic_add16, memtype_size: 2},
{func: instance.exports.atomic_add8, memtype_size: 1}];
VerifyBoundsCheck(memory.buffer, adds, 10);
})();
(function TestAtomicSub() {
print("TestAtomicSub");
let memory = new WebAssembly.Memory({initial: 5, maximum: 20, shared: true});
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
builder.addFunction("atomic_sub", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicSub])
.exportAs("atomic_sub");
builder.addFunction("atomic_sub16", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicSub16U])
.exportAs("atomic_sub16");
builder.addFunction("atomic_sub8", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicSub8U])
.exportAs("atomic_sub8");
let module = new WebAssembly.Module(builder.toBuffer());
let instance = (new WebAssembly.Instance(module,
{m: {imported_mem: memory}}));
// 32-bit Sub
let i32 = new Uint32Array(memory.buffer);
for (i = 0; i < i32.length; i++) {
i32[i] = i32.length;
assertEquals(i32.length, instance.exports.atomic_sub(i * 4, i));
assertEquals(i32.length - i, i32[i]);
}
// 16-bit Sub
let i16 = new Uint16Array(memory.buffer);
for (i = 0; i < i16.length; i++) {
i16[i] = 0xffff;
assertEquals(0xffff, instance.exports.atomic_sub16(i * 2, 0x1234));
assertEquals(0xffff - 0x1234, i16[i]);
}
// 8-bit Sub
let i8 = new Uint8Array(memory.buffer);
for (i = 0; i < i8.length; i++) {
i8[i] = 0xff;
assertEquals(0xff, instance.exports.atomic_sub8(i, i % 0xff));
assertEquals(0xff - i % 0xff, i8[i]);
}
var subs = [{func: instance.exports.atomic_sub, memtype_size: 4},
{func: instance.exports.atomic_sub16, memtype_size: 2},
{func: instance.exports.atomic_sub8, memtype_size: 1}];
VerifyBoundsCheck(memory.buffer, subs, 20);
})();
...@@ -320,6 +320,16 @@ let kExprI64ReinterpretF64 = 0xbd; ...@@ -320,6 +320,16 @@ let kExprI64ReinterpretF64 = 0xbd;
let kExprF32ReinterpretI32 = 0xbe; let kExprF32ReinterpretI32 = 0xbe;
let kExprF64ReinterpretI64 = 0xbf; let kExprF64ReinterpretI64 = 0xbf;
// Prefix opcodes
let kAtomicPrefix = 0xfe;
let kExprI32AtomicAdd = 0x1e;
let kExprI32AtomicAdd8U = 0x20;
let kExprI32AtomicAdd16U = 0x21;
let kExprI32AtomicSub = 0x25;
let kExprI32AtomicSub8U = 0x27;
let kExprI32AtomicSub16U = 0x28;
let kTrapUnreachable = 0; let kTrapUnreachable = 0;
let kTrapMemOutOfBounds = 1; let kTrapMemOutOfBounds = 1;
let kTrapDivByZero = 2; let kTrapDivByZero = 2;
......
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