Commit 0202a040 authored by Deepti Gandluri's avatar Deepti Gandluri Committed by Commit Bot

[wasm] Module bytes can set shared attribute on memory

- Validate that atomic ops can only be called when shared memory is declared
- Throw Compile/Link erros on mismatch between declared, imported memory
- Test harness helpers for setting shared memory, tests

BUG=v8:6532

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

Change-Id: I43fe3d04bb7e3e0a2cecca0528578f98844d2608
Reviewed-on: https://chromium-review.googlesource.com/665379
Commit-Queue: Brad Nelson <bradnelson@chromium.org>
Reviewed-by: 's avatarBrad Nelson <bradnelson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48019}
parent 70372dfc
......@@ -1155,6 +1155,14 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return true;
}
bool CheckHasSharedMemory() {
if (!VALIDATE(this->module_->has_shared_memory)) {
this->error(this->pc_ - 1, "Atomic opcodes used without shared memory");
return false;
}
return true;
}
// Decodes the body of a function.
void DecodeFunctionBody() {
TRACE("wasm-decode %p...%p (module+%u, %d bytes)\n",
......@@ -1655,6 +1663,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
}
case kAtomicPrefix: {
CHECK_PROTOTYPE_OPCODE(threads);
if (!CheckHasSharedMemory()) break;
len++;
byte atomic_index =
this->template read_u8<validate>(this->pc_ + 1, "atomic index");
......
......@@ -1467,6 +1467,14 @@ int InstanceBuilder::ProcessImports(Handle<FixedArray> code_table,
return -1;
}
}
if (module_->has_shared_memory != buffer->is_shared()) {
thrower_->LinkError(
"mismatch in shared state of memory, declared = %d, imported = "
"%d",
module_->has_shared_memory, buffer->is_shared());
return -1;
}
break;
}
case kExternalGlobal: {
......
......@@ -474,7 +474,8 @@ class ModuleDecoder : public Decoder {
consume_resizable_limits(
"memory", "pages", FLAG_wasm_max_mem_pages,
&module_->initial_pages, &module_->has_maximum_pages,
kSpecMaxWasmMemoryPages, &module_->maximum_pages);
kSpecMaxWasmMemoryPages, &module_->maximum_pages,
&module_->has_shared_memory);
break;
}
case kExternalGlobal: {
......@@ -542,7 +543,7 @@ class ModuleDecoder : public Decoder {
consume_resizable_limits(
"memory", "pages", FLAG_wasm_max_mem_pages, &module_->initial_pages,
&module_->has_maximum_pages, kSpecMaxWasmMemoryPages,
&module_->maximum_pages);
&module_->maximum_pages, &module_->has_shared_memory);
}
}
......@@ -1082,12 +1083,30 @@ class ModuleDecoder : public Decoder {
void consume_resizable_limits(const char* name, const char* units,
uint32_t max_initial, uint32_t* initial,
bool* has_max, uint32_t max_maximum,
uint32_t* maximum) {
uint32_t* maximum,
bool* has_shared_memory = nullptr) {
uint8_t flags = consume_u8("resizable limits flags");
const byte* pos = pc();
if (flags & 0xfe) {
errorf(pos - 1, "invalid %s limits flags", name);
if (FLAG_experimental_wasm_threads) {
bool is_memory = (strcmp(name, "memory") == 0);
if (flags & 0xfc || (!is_memory && (flags & 0xfe))) {
errorf(pos - 1, "invalid %s limits flags", name);
}
if (flags == 3) {
DCHECK(has_shared_memory != nullptr);
*has_shared_memory = true;
} else if (flags == 2) {
errorf(pos - 1,
"%s limits flags should have maximum defined if shared is true",
name);
}
} else {
if (flags & 0xfe) {
errorf(pos - 1, "invalid %s limits flags", name);
}
}
*initial = consume_u32v("initial size");
*has_max = false;
if (*initial > max_initial) {
......
......@@ -26,6 +26,13 @@ const uint8_t kWasmAnyFunctionTypeForm = 0x70;
const uint8_t kResizableMaximumFlag = 1;
const uint8_t kNoMaximumFlag = 0;
enum MemoryFlags : uint8_t {
kNoMaximum = 0,
kMaximum = 1,
kSharedNoMaximum = 2,
kSharedAndMaximum = 3
};
enum SectionCode : int8_t {
kUnknownSectionCode = 0, // code for unknown sections
kTypeSectionCode = 1, // Function signature declarations
......
......@@ -226,7 +226,8 @@ WasmModuleBuilder::WasmModuleBuilder(Zone* zone)
start_function_index_(-1),
min_memory_size_(16),
max_memory_size_(0),
has_max_memory_size_(false) {}
has_max_memory_size_(false),
has_shared_memory_(false) {}
WasmFunctionBuilder* WasmModuleBuilder::AddFunction(FunctionSig* sig) {
functions_.push_back(new (zone_) WasmFunctionBuilder(this));
......@@ -325,6 +326,8 @@ void WasmModuleBuilder::SetMaxMemorySize(uint32_t value) {
max_memory_size_ = value;
}
void WasmModuleBuilder::SetHasSharedMemory() { has_shared_memory_ = true; }
void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const {
// == Emit magic =============================================================
buffer.write_u32(kWasmMagic);
......@@ -396,8 +399,13 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer& buffer) const {
{
size_t start = EmitSection(kMemorySectionCode, buffer);
buffer.write_u8(1); // memory count
buffer.write_u8(has_max_memory_size_ ? kResizableMaximumFlag
: kNoMaximumFlag);
if (has_shared_memory_) {
buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kSharedAndMaximum
: MemoryFlags::kSharedNoMaximum);
} else {
buffer.write_u8(has_max_memory_size_ ? MemoryFlags::kMaximum
: MemoryFlags::kNoMaximum);
}
buffer.write_u32v(min_memory_size_);
if (has_max_memory_size_) {
buffer.write_u32v(max_memory_size_);
......
......@@ -236,6 +236,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
void AddExport(Vector<const char> name, WasmFunctionBuilder* builder);
void SetMinMemorySize(uint32_t value);
void SetMaxMemorySize(uint32_t value);
void SetHasSharedMemory();
// Writing methods.
void WriteTo(ZoneBuffer& buffer) const;
......@@ -295,6 +296,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
uint32_t min_memory_size_;
uint32_t max_memory_size_;
bool has_max_memory_size_;
bool has_shared_memory_;
};
inline FunctionSig* WasmFunctionBuilder::signature() {
......
......@@ -187,10 +187,11 @@ struct V8_EXPORT_PRIVATE WasmModule {
std::unique_ptr<Zone> signature_zone;
uint32_t initial_pages = 0; // initial size of the memory in 64k pages
uint32_t maximum_pages = 0; // maximum size of the memory in 64k pages
bool has_shared_memory = false; // true if memory is a SharedArrayBuffer
bool has_maximum_pages = false; // true if there is a maximum memory size
bool has_memory = false; // true if the memory was defined or imported
bool mem_export = false; // true if the memory is exported
int start_function_index = -1; // start function, >= 0 if any
bool has_memory = false; // true if the memory was defined or imported
bool mem_export = false; // true if the memory is exported
int start_function_index = -1; // start function, >= 0 if any
std::vector<WasmGlobal> globals;
uint32_t globals_size = 0;
......
......@@ -60,6 +60,7 @@ void RunU32BinOp(WasmOpcode wasm_op, Uint32BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
uint32_t* memory = r.builder().AddMemoryElems<uint32_t>(8);
r.builder().SetHasSharedMemory();
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
......@@ -84,6 +85,7 @@ WASM_EXEC_TEST(I32Exchange) { RunU32BinOp(kExprI32AtomicExchange, Exchange); }
void RunU16BinOp(WasmOpcode wasm_op, Uint16BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
r.builder().SetHasSharedMemory();
uint16_t* memory = r.builder().AddMemoryElems<uint16_t>(8);
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
......@@ -111,6 +113,7 @@ WASM_EXEC_TEST(I32Exchange16U) {
void RunU8BinOp(WasmOpcode wasm_op, Uint8BinOp expected_op) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t> r(kExecuteCompiled);
r.builder().SetHasSharedMemory();
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(8);
BUILD(r, WASM_ATOMICS_BINOP(wasm_op, WASM_I32V_1(0), WASM_GET_LOCAL(0)));
......@@ -138,6 +141,7 @@ WASM_EXEC_TEST(I32Exchange8U) {
WASM_EXEC_TEST(I32CompareExchange) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t, uint32_t> r(kExecuteCompiled);
r.builder().SetHasSharedMemory();
uint32_t* memory = r.builder().AddMemoryElems<uint32_t>(8);
BUILD(r,
WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange, WASM_I32V_1(0),
......@@ -157,6 +161,7 @@ WASM_EXEC_TEST(I32CompareExchange) {
WASM_EXEC_TEST(I32CompareExchange16U) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t, uint32_t> r(kExecuteCompiled);
r.builder().SetHasSharedMemory();
uint16_t* memory = r.builder().AddMemoryElems<uint16_t>(8);
BUILD(r, WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange16U,
WASM_I32V_1(0), WASM_GET_LOCAL(0),
......@@ -176,6 +181,7 @@ WASM_EXEC_TEST(I32CompareExchange16U) {
WASM_EXEC_TEST(I32CompareExchange8U) {
EXPERIMENTAL_FLAG_SCOPE(threads);
WasmRunner<uint32_t, uint32_t, uint32_t> r(kExecuteCompiled);
r.builder().SetHasSharedMemory();
uint8_t* memory = r.builder().AddMemoryElems<uint8_t>(8);
BUILD(r,
WASM_ATOMICS_TERNARY_OP(kExprI32AtomicCompareExchange8U, WASM_I32V_1(0),
......
......@@ -157,6 +157,8 @@ class TestingModuleBuilder {
test_module_.maximum_pages = maximum_pages;
}
void SetHasSharedMemory() { test_module_.has_shared_memory = true; }
uint32_t AddFunction(FunctionSig* sig, Handle<Code> code, const char* name);
uint32_t AddJsFunction(FunctionSig* sig, const char* source,
......
......@@ -23,7 +23,7 @@ let memory = new WebAssembly.Memory({initial: 1, maximum: maxSize, shared: true}
function GetAtomicBinOpFunction(wasmExpression) {
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
......@@ -41,7 +41,7 @@ function GetAtomicBinOpFunction(wasmExpression) {
function GetAtomicCmpExchangeFunction(wasmExpression) {
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
builder.addImportedMemory("m", "imported_mem", 0, maxSize, "shared");
builder.addFunction("main", kSig_i_iii)
.addBody([
kExprGetLocal, 0,
......
......@@ -4,6 +4,9 @@
// Flags: --experimental-wasm-threads
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
function assertMemoryIsValid(memory) {
assertSame(WebAssembly.Memory.prototype, memory.__proto__);
assertSame(WebAssembly.Memory, memory.constructor);
......@@ -50,3 +53,51 @@ function assertMemoryIsValid(memory) {
assertThrows(() => new WebAssembly.Memory({initial: 0, shared: true}),
TypeError);
})();
(function TestCompileWithUndefinedShared() {
print("TestCompileWithUndefinedShared");
let memory = new WebAssembly.Memory({
initial: 0, maximum: 10, shared: true});
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem", 0, undefined, "shared");
assertThrows(() => new WebAssembly.Module(builder.toBuffer()),
WebAssembly.CompileError);
})();
(function TestCompileAtomicOpUndefinedShared() {
print("TestCompileAtomicOpUndefinedShared");
let memory = new WebAssembly.Memory({
initial: 0, maximum: 10, shared: true});
let builder = new WasmModuleBuilder();
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kAtomicPrefix,
kExprI32AtomicAdd]);
builder.addImportedMemory("m", "imported_mem");
assertThrows(() => new WebAssembly.Module(builder.toBuffer()),
WebAssembly.CompileError);
})();
(function TestInstantiateWithUndefinedShared() {
print("TestInstantiateWithUndefinedShared");
let memory = new WebAssembly.Memory({
initial: 0, maximum: 10, shared: true});
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem");
let module = new WebAssembly.Module(builder.toBuffer());
assertThrows(() => new WebAssembly.Instance(module,
{m: {imported_mem: memory}}), WebAssembly.LinkError);
})();
(function TestInstantiateWithImportNotSharedDefined() {
print("TestInstantiateWithImportNotSharedDefined");
let memory = new WebAssembly.Memory({
initial: 0, maximum: 10, shared: false});
let builder = new WasmModuleBuilder();
builder.addImportedMemory("m", "imported_mem", 0, 10, "shared");
let module = new WebAssembly.Module(builder.toBuffer());
assertThrows(() => new WebAssembly.Instance(module,
{m: {imported_mem: memory}}), WebAssembly.LinkError);
})();
......@@ -171,8 +171,8 @@ class WasmModuleBuilder {
return this;
}
addMemory(min, max, exp) {
this.memory = {min: min, max: max, exp: exp};
addMemory(min, max, exp, shared) {
this.memory = {min: min, max: max, exp: exp, shared: shared};
return this;
}
......@@ -240,9 +240,9 @@ class WasmModuleBuilder {
return this.num_imported_globals++;
}
addImportedMemory(module = "", name, initial = 0, maximum) {
addImportedMemory(module = "", name, initial = 0, maximum, shared) {
let o = {module: module, name: name, kind: kExternalMemory,
initial: initial, maximum: maximum};
initial: initial, maximum: maximum, shared: shared};
this.imports.push(o);
return this;
}
......@@ -348,7 +348,12 @@ class WasmModuleBuilder {
section.emit_u8(imp.mutable);
} else if (imp.kind == kExternalMemory) {
var has_max = (typeof imp.maximum) != "undefined";
section.emit_u8(has_max ? 1 : 0); // flags
var is_shared = (typeof imp.shared) != "undefined";
if (is_shared) {
section.emit_u8(has_max ? 3 : 2); // flags
} else {
section.emit_u8(has_max ? 1 : 0); // flags
}
section.emit_u32v(imp.initial); // initial
if (has_max) section.emit_u32v(imp.maximum); // maximum
} else if (imp.kind == kExternalTable) {
......@@ -395,9 +400,16 @@ class WasmModuleBuilder {
binary.emit_section(kMemorySectionCode, section => {
section.emit_u8(1); // one memory entry
const has_max = wasm.memory.max !== undefined;
section.emit_u32v(has_max ? kResizableMaximumFlag : 0);
const is_shared = wasm.memory.shared !== undefined;
// Emit flags (bit 0: reszeable max, bit 1: shared memory)
if (is_shared) {
section.emit_u8(has_max ? 3 : 2);
} else {
section.emit_u8(has_max ? 1 : 0);
}
section.emit_u32v(wasm.memory.min);
if (has_max) section.emit_u32v(wasm.memory.max);
if (wasm.memory.shared) section.emit_u8(1);
});
}
......
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