Commit fd1b8bbf authored by Ben Smith's avatar Ben Smith Committed by Commit Bot

[wasm] Add bulk memory flag; parse passive segments

See the WebAssembly bulk memory proposal here:
https://github.com/WebAssembly/bulk-memory-operations

This initial CL adds a wasm experimental flag:
`--experimental-wasm-bulk-memory`, and also parsing of passive segments.

A passive segment is one that is not copied into the table/memory on
instantiation, but instead later via the `{table,memory}.init`
instructions.

The binary format of passive data segments is unlikely to change, but
the format for passive element segments may change (see
https://github.com/WebAssembly/bulk-memory-operations/pull/39).

Bug: v8:7747
Change-Id: I2a7fb9bc7648a722a8c4aab4185c68d3d0843858
Reviewed-on: https://chromium-review.googlesource.com/c/1330015
Commit-Queue: Ben Smith <binji@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57451}
parent 8974fa04
......@@ -1257,6 +1257,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Check that indirect function table segments are within bounds.
//--------------------------------------------------------------------------
for (const WasmTableInit& table_init : module_->table_inits) {
if (!table_init.active) continue;
DCHECK(table_init.table_index < table_instances_.size());
uint32_t base = EvalUint32InitExpr(table_init.offset);
size_t table_size = table_instances_[table_init.table_index].table_size;
......@@ -1270,6 +1271,7 @@ MaybeHandle<WasmInstanceObject> InstanceBuilder::Build() {
// Check that memory segments are within bounds.
//--------------------------------------------------------------------------
for (const WasmDataSegment& seg : module_->data_segments) {
if (!seg.active) continue;
uint32_t base = EvalUint32InitExpr(seg.dest_addr);
if (!in_bounds(base, seg.source.length(), instance->memory_size())) {
thrower_->LinkError("data segment is out of bounds");
......@@ -1449,6 +1451,8 @@ void InstanceBuilder::LoadDataSegments(Handle<WasmInstanceObject> instance) {
uint32_t source_size = segment.source.length();
// Segments of size == 0 are just nops.
if (source_size == 0) continue;
// Passive segments are not copied during instantiation.
if (!segment.active) continue;
uint32_t dest_offset = EvalUint32InitExpr(segment.dest_addr);
DCHECK(in_bounds(dest_offset, source_size, instance->memory_size()));
byte* dest = instance->memory_start() + dest_offset;
......@@ -2192,6 +2196,9 @@ void InstanceBuilder::InitializeTables(Handle<WasmInstanceObject> instance) {
void InstanceBuilder::LoadTableSegments(Handle<WasmInstanceObject> instance) {
NativeModule* native_module = module_object_->native_module();
for (auto& table_init : module_->table_inits) {
// Passive segments are not copied during instantiation.
if (!table_init.active) continue;
uint32_t base = EvalUint32InitExpr(table_init.offset);
uint32_t num_entries = static_cast<uint32_t>(table_init.entries.size());
uint32_t index = table_init.table_index;
......
......@@ -753,23 +753,34 @@ class ModuleDecoderImpl : public Decoder {
}
for (uint32_t i = 0; ok() && i < element_count; ++i) {
const byte* pos = pc();
uint32_t table_index = consume_u32v("table index");
if (!enabled_features_.anyref && table_index != 0) {
errorf(pos, "illegal table index %u != 0", table_index);
}
if (table_index >= module_->tables.size()) {
errorf(pos, "out of bounds table index %u", table_index);
break;
}
if (module_->tables[table_index].type != kWasmAnyFunc) {
errorf(pos, "Invalid element segment. Table %u is not of type AnyFunc",
table_index);
break;
bool is_active;
uint32_t table_index;
WasmInitExpr offset;
consume_segment_header("table index", &is_active, &table_index, &offset);
if (failed()) return;
if (is_active) {
if (table_index >= module_->tables.size()) {
errorf(pos, "out of bounds table index %u", table_index);
break;
}
if (module_->tables[table_index].type != kWasmAnyFunc) {
errorf(pos,
"Invalid element segment. Table %u is not of type AnyFunc",
table_index);
break;
}
}
WasmInitExpr offset = consume_init_expr(module_.get(), kWasmI32);
uint32_t num_elem =
consume_count("number of elements", kV8MaxWasmTableEntries);
module_->table_inits.emplace_back(table_index, offset);
if (is_active) {
module_->table_inits.emplace_back(table_index, offset);
} else {
module_->table_inits.emplace_back();
}
WasmTableInit* init = &module_->table_inits.back();
for (uint32_t j = 0; j < num_elem; j++) {
WasmFunction* func = nullptr;
......@@ -829,18 +840,41 @@ class ModuleDecoderImpl : public Decoder {
consume_count("data segments count", kV8MaxWasmDataSegments);
module_->data_segments.reserve(data_segments_count);
for (uint32_t i = 0; ok() && i < data_segments_count; ++i) {
const byte* pos = pc();
if (!module_->has_memory) {
error("cannot load data without memory");
break;
}
TRACE("DecodeDataSegment[%d] module+%d\n", i,
static_cast<int>(pc_ - start_));
module_->data_segments.push_back({
WasmInitExpr(), // dest_addr
{0, 0} // source
});
bool is_active;
uint32_t memory_index;
WasmInitExpr dest_addr;
consume_segment_header("memory index", &is_active, &memory_index,
&dest_addr);
if (failed()) break;
if (is_active && memory_index != 0) {
errorf(pos, "illegal memory index %u != 0", memory_index);
break;
}
uint32_t source_length = consume_u32v("source size");
uint32_t source_offset = pc_offset();
if (is_active) {
module_->data_segments.emplace_back(dest_addr);
} else {
module_->data_segments.emplace_back();
}
WasmDataSegment* segment = &module_->data_segments.back();
DecodeDataSegmentInModule(module_.get(), segment);
consume_bytes(source_length, "segment data");
if (failed()) break;
segment->source = {source_offset, source_length};
}
}
......@@ -1069,17 +1103,6 @@ class ModuleDecoderImpl : public Decoder {
}
// Decodes a single data segment entry inside a module starting at {pc_}.
void DecodeDataSegmentInModule(WasmModule* module, WasmDataSegment* segment) {
expect_u8("linear memory index", 0);
segment->dest_addr = consume_init_expr(module, kWasmI32);
uint32_t source_length = consume_u32v("source size");
uint32_t source_offset = pc_offset();
consume_bytes(source_length, "segment data");
if (failed()) return;
segment->source = {source_offset, source_length};
}
// Calculate individual global offsets and total size of globals table.
void CalculateGlobalOffsets(WasmModule* module) {
......@@ -1465,6 +1488,55 @@ class ModuleDecoderImpl : public Decoder {
}
return attribute;
}
void consume_segment_header(const char* name, bool* is_active,
uint32_t* index, WasmInitExpr* offset) {
const byte* pos = pc();
// In the MVP, this is a table or memory index field that must be 0, but
// we've repurposed it as a flags field in the bulk memory proposal.
uint32_t flags;
if (enabled_features_.bulk_memory) {
flags = consume_u32v("flags");
if (failed()) return;
} else {
flags = consume_u32v(name);
if (failed()) return;
if (flags != 0) {
errorf(pos, "illegal %s %u != 0", name, flags);
return;
}
}
bool read_index;
bool read_offset;
if (flags == SegmentFlags::kActiveNoIndex) {
*is_active = true;
read_index = false;
read_offset = true;
} else if (flags == SegmentFlags::kPassive) {
*is_active = false;
read_index = false;
read_offset = false;
} else if (flags == SegmentFlags::kActiveWithIndex) {
*is_active = true;
read_index = true;
read_offset = true;
} else {
errorf(pos, "illegal flag value %u. Must be 0, 1, or 2", flags);
return;
}
if (read_index) {
*index = consume_u32v(name);
} else {
*index = 0;
}
if (read_offset) {
*offset = consume_init_expr(module_.get(), kWasmI32);
}
}
};
ModuleResult DecodeWasmModule(const WasmFeatures& enabled,
......
......@@ -50,6 +50,13 @@ enum MemoryFlags : uint8_t {
kSharedAndMaximum = 3
};
// Flags for data and element segments.
enum SegmentFlags : uint8_t {
kActiveNoIndex = 0, // Active segment with a memory/table index of zero.
kPassive = 1, // Passive segment.
kActiveWithIndex = 2, // Active segment with a given memory/table index.
};
// Binary encoding of sections identifiers.
enum SectionCode : int8_t {
kUnknownSectionCode = 0, // code for unknown sections
......
......@@ -21,6 +21,8 @@
SEPARATOR \
V(anyref, "anyref opcodes", false) \
SEPARATOR \
V(mut_global, "import/export mutable global support", true)
V(mut_global, "import/export mutable global support", true) \
SEPARATOR \
V(bulk_memory, "bulk memory opcodes", false)
#endif // V8_WASM_WASM_FEATURE_FLAGS_H_
......@@ -84,8 +84,16 @@ struct WasmException {
// Static representation of a wasm data segment.
struct WasmDataSegment {
// Construct an active segment.
explicit WasmDataSegment(WasmInitExpr dest_addr)
: dest_addr(dest_addr), active(true) {}
// Construct a passive segment, which has no dest_addr.
WasmDataSegment() : active(false) {}
WasmInitExpr dest_addr; // destination memory address of the data.
WireBytesRef source; // start offset in the module bytes.
bool active = true; // true if copied automatically during instantiation.
};
// Static representation of a wasm indirect call table.
......@@ -105,12 +113,17 @@ struct WasmTable {
struct WasmTableInit {
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(WasmTableInit);
// Construct an active segment.
WasmTableInit(uint32_t table_index, WasmInitExpr offset)
: table_index(table_index), offset(offset) {}
: table_index(table_index), offset(offset), active(true) {}
// Construct a passive segment, which has no table index or offset.
WasmTableInit() : table_index(0), active(false) {}
uint32_t table_index;
WasmInitExpr offset;
std::vector<uint32_t> entries;
bool active; // true if copied automatically during instantiation.
};
// Static representation of a wasm import.
......
......@@ -19,11 +19,21 @@
#define SIG_INDEX(v) U32V_1(v)
#define FUNC_INDEX(v) U32V_1(v)
#define TABLE_INDEX(v) U32V_1(v)
#define EXCEPTION_INDEX(v) U32V_1(v)
#define NO_NAME U32V_1(0)
#define ENTRY_COUNT(v) U32V_1(v)
// Segment flags
#define ACTIVE_NO_INDEX 0
#define PASSIVE 1
#define ACTIVE_WITH_INDEX 2
// The table index field in an element segment was repurposed as a flags field.
// To specify a table index, we have to set the flag value to 2, followed by
// the table index.
#define TABLE_INDEX0 U32V_1(ACTIVE_NO_INDEX)
#define TABLE_INDEX(v) U32V_1(ACTIVE_WITH_INDEX), U32V_1(v)
#define ZERO_ALIGNMENT 0
#define ZERO_OFFSET 0
......
// Copyright 2018 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-bulk-memory
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
(function TestPassiveDataSegment() {
const builder = new WasmModuleBuilder();
builder.addMemory(1, 1, false);
builder.addPassiveDataSegment([0, 1, 2]);
builder.addPassiveDataSegment([3, 4]);
// Should not throw.
const module = builder.instantiate();
})();
(function TestPassiveElementSegment() {
const builder = new WasmModuleBuilder();
builder.addFunction('f', kSig_v_v).addBody([]);
builder.setTableLength(1);
builder.addPassiveElementSegment([0, 0, 0]);
builder.addPassiveElementSegment([0, 0]);
// Should not throw.
const module = builder.instantiate();
})();
......@@ -77,6 +77,11 @@ let kWasmAnyFunctionTypeForm = 0x70;
let kHasMaximumFlag = 1;
// Segment flags
let kActiveNoIndex = 0;
let kPassive = 1;
let kActiveWithIndex = 2;
// Function declaration flags
let kDeclFunctionName = 0x01;
let kDeclFunctionImport = 0x02;
......
......@@ -299,7 +299,13 @@ class WasmModuleBuilder {
}
addDataSegment(addr, data, is_global = false) {
this.data_segments.push({addr: addr, data: data, is_global: is_global});
this.data_segments.push(
{addr: addr, data: data, is_global: is_global, is_active: true});
return this.data_segments.length - 1;
}
addPassiveDataSegment(data) {
this.data_segments.push({data: data, is_active: false});
return this.data_segments.length - 1;
}
......@@ -309,7 +315,7 @@ class WasmModuleBuilder {
addElementSegment(base, is_global, array, is_import = false) {
this.element_segments.push({base: base, is_global: is_global,
array: array});
array: array, is_active: true});
if (!is_global) {
var length = base + array.length;
if (length > this.table_length_min && !is_import) {
......@@ -322,6 +328,11 @@ class WasmModuleBuilder {
return this;
}
addPassiveElementSegment(array, is_import = false) {
this.element_segments.push({array: array, is_active: false});
return this;
}
appendToTable(array) {
for (let n of array) {
if (typeof n != 'number')
......@@ -554,14 +565,18 @@ class WasmModuleBuilder {
section.emit_u32v(inits.length);
for (let init of inits) {
section.emit_u8(0); // table index
if (init.is_global) {
section.emit_u8(kExprGetGlobal);
if (init.is_active) {
section.emit_u8(0); // table index / flags
if (init.is_global) {
section.emit_u8(kExprGetGlobal);
} else {
section.emit_u8(kExprI32Const);
}
section.emit_u32v(init.base);
section.emit_u8(kExprEnd);
} else {
section.emit_u8(kExprI32Const);
section.emit_u8(kPassive); // flags
}
section.emit_u32v(init.base);
section.emit_u8(kExprEnd);
section.emit_u32v(init.array.length);
for (let index of init.array) {
section.emit_u32v(index);
......@@ -623,17 +638,21 @@ class WasmModuleBuilder {
binary.emit_section(kDataSectionCode, section => {
section.emit_u32v(wasm.data_segments.length);
for (let seg of wasm.data_segments) {
section.emit_u8(0); // linear memory index 0
if (seg.is_global) {
// initializer is a global variable
section.emit_u8(kExprGetGlobal);
section.emit_u32v(seg.addr);
if (seg.is_active) {
section.emit_u8(0); // linear memory index 0 / flags
if (seg.is_global) {
// initializer is a global variable
section.emit_u8(kExprGetGlobal);
section.emit_u32v(seg.addr);
} else {
// initializer is a constant
section.emit_u8(kExprI32Const);
section.emit_u32v(seg.addr);
}
section.emit_u8(kExprEnd);
} else {
// initializer is a constant
section.emit_u8(kExprI32Const);
section.emit_u32v(seg.addr);
section.emit_u8(kPassive); // flags
}
section.emit_u8(kExprEnd);
section.emit_u32v(seg.data.length);
section.emit_bytes(seg.data);
}
......
......@@ -912,7 +912,7 @@ TEST_F(WasmModuleVerifyTest, Regression_735887) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(1), // entry count
TABLE_INDEX(0), WASM_INIT_EXPR_I32V_1(0),
TABLE_INDEX0, WASM_INIT_EXPR_I32V_1(0),
1, // elements count
0x9A) // invalid I32V as function index
};
......@@ -931,7 +931,7 @@ TEST_F(WasmModuleVerifyTest, OneIndirectFunction_one_entry) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(1), // entry count
TABLE_INDEX(0), WASM_INIT_EXPR_I32V_1(0),
TABLE_INDEX0, WASM_INIT_EXPR_I32V_1(0),
1, // elements count
FUNC_INDEX(0))};
......@@ -957,7 +957,7 @@ TEST_F(WasmModuleVerifyTest, MultipleIndirectFunctions) {
// table elements ----------------------------------------------
SECTION(Element,
ENTRY_COUNT(1), // entry count
TABLE_INDEX(0), WASM_INIT_EXPR_I32V_1(0),
TABLE_INDEX0, WASM_INIT_EXPR_I32V_1(0),
ADD_COUNT(FUNC_INDEX(0), FUNC_INDEX(1), FUNC_INDEX(2),
FUNC_INDEX(3), FUNC_INDEX(0), FUNC_INDEX(1),
FUNC_INDEX(2), FUNC_INDEX(3))),
......@@ -975,6 +975,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMultipleTables) {
// Test that if we have multiple tables, in the element section we can target
// and initialize all tables.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -987,7 +988,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMultipleTables) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(2), // entry count
TABLE_INDEX(0), // element for table 0
TABLE_INDEX0, // element for table 0
WASM_INIT_EXPR_I32V_1(0), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -1005,6 +1006,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMixedTables) {
// Test that if we have multiple tables, both imported and module-defined, in
// the element section we can target and initialize all tables.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -1031,7 +1033,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMixedTables) {
// elements ------------------------------------------------------------
SECTION(Element,
4, // entry count
TABLE_INDEX(0), // element for table 0
TABLE_INDEX0, // element for table 0
WASM_INIT_EXPR_I32V_1(0), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -1058,6 +1060,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMultipleTablesArbitraryOrder) {
// Test that the order in which tables are targeted in the element secion
// can be arbitrary.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -1070,7 +1073,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMultipleTablesArbitraryOrder) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(3), // entry count
TABLE_INDEX(0), // element for table 1
TABLE_INDEX0, // element for table 1
WASM_INIT_EXPR_I32V_1(0), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -1079,7 +1082,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMultipleTablesArbitraryOrder) {
2, // elements count
FUNC_INDEX(0), // entry 0
FUNC_INDEX(0), // entry 1
TABLE_INDEX(0), // element for table 1
TABLE_INDEX0, // element for table 1
WASM_INIT_EXPR_I32V_1(3), // index
1, // elements count
FUNC_INDEX(0)), // function
......@@ -1092,6 +1095,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMixedTablesArbitraryOrder) {
// Test that the order in which tables are targeted in the element secion can
// be arbitrary. In this test, tables can be both imported and module-defined.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -1127,7 +1131,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionMixedTablesArbitraryOrder) {
2, // elements count
FUNC_INDEX(0), // entry 0
FUNC_INDEX(0), // entry 1
TABLE_INDEX(0), // element for table 2
TABLE_INDEX0, // element for table 2
WASM_INIT_EXPR_I32V_1(2), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -1145,6 +1149,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionDontInitAnyRefTable) {
// Test that tables of type 'AnyRef' cannot be initialized by the element
// section.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -1157,7 +1162,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionDontInitAnyRefTable) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(2), // entry count
TABLE_INDEX(0), // element for table 0
TABLE_INDEX0, // element for table 0
WASM_INIT_EXPR_I32V_1(0), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -1175,6 +1180,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionDontInitAnyRefImportedTable) {
// Test that imported tables of type AnyRef cannot be initialized in the
// elements section.
WASM_FEATURE_SCOPE(anyref);
WASM_FEATURE_SCOPE(bulk_memory);
static const byte data[] = {
// sig#0 ---------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
......@@ -1201,7 +1207,7 @@ TEST_F(WasmModuleVerifyTest, ElementSectionDontInitAnyRefImportedTable) {
// elements ------------------------------------------------------------
SECTION(Element,
ENTRY_COUNT(4), // entry count
TABLE_INDEX(0), // element for table 0
TABLE_INDEX0, // element for table 0
WASM_INIT_EXPR_I32V_1(10), // index
1, // elements count
FUNC_INDEX(0), // function
......@@ -2201,6 +2207,37 @@ TEST_F(WasmModuleVerifyTest, MultipleNameSections) {
EXPECT_EQ(3u, result.value()->name.length());
}
TEST_F(WasmModuleVerifyTest, PassiveDataSegment) {
static const byte data[] = {
// memory declaration ----------------------------------------------------
SECTION(Memory, ENTRY_COUNT(1), 0, 1),
// data segments --------------------------------------------------------
SECTION(Data, ENTRY_COUNT(1), PASSIVE, ADD_COUNT('h', 'i')),
};
EXPECT_FAILURE(data);
WASM_FEATURE_SCOPE(bulk_memory);
EXPECT_VERIFIES(data);
EXPECT_OFF_END_FAILURE(data, arraysize(data) - 5);
}
TEST_F(WasmModuleVerifyTest, PassiveElementSegment) {
static const byte data[] = {
// sig#0 -----------------------------------------------------------------
SIGNATURES_SECTION_VOID_VOID,
// funcs -----------------------------------------------------------------
ONE_EMPTY_FUNCTION(SIG_INDEX(0)),
// table declaration -----------------------------------------------------
SECTION(Table, ENTRY_COUNT(1), kLocalAnyFunc, 0, 1),
// element segments -----------------------------------------------------
SECTION(Element, ENTRY_COUNT(1), PASSIVE,
ADD_COUNT(FUNC_INDEX(0), FUNC_INDEX(0))),
};
EXPECT_FAILURE(data);
WASM_FEATURE_SCOPE(bulk_memory);
EXPECT_VERIFIES(data);
EXPECT_OFF_END_FAILURE(data, arraysize(data) - 5);
}
#undef WASM_FEATURE_SCOPE
#undef WASM_FEATURE_SCOPE_VAL
#undef EXPECT_INIT_EXPR
......
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