[wasm] Implement the table.grow instruction

This CL add decoding and code generation for the table.grow
instruction. For code generation we just generate a runtime
call. The implementation is quite straight-forward. However,
I did several small cleanups along the way. I hope it's still
acceptable. I could also split out some cleanups into separate
CLs.

R=mstarzinger@chromium.org

Bug: v8:7581
Change-Id: Id885b7e70eb4f5bccfe779eb216f7cc9302ea3a5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1593078
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61192}
This commit is contained in:
Andreas Haas 2019-05-03 10:06:10 +02:00 committed by Commit Bot
parent 9ebd7612da
commit ef3c733810
15 changed files with 351 additions and 22 deletions

View File

@ -4733,6 +4733,16 @@ Node* WasmGraphBuilder::TableCopy(uint32_t table_src_index,
return result;
}
Node* WasmGraphBuilder::TableGrow(uint32_t table_index, Node* value,
Node* delta) {
Node* args[] = {
graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)), value,
BuildConvertUint32ToSmiWithSaturation(delta, FLAG_wasm_max_table_size)};
Node* result =
BuildCallToRuntime(Runtime::kWasmTableGrow, args, arraysize(args));
return BuildChangeSmiToInt32(result);
}
class WasmDecorator final : public GraphDecorator {
public:
explicit WasmDecorator(NodeOriginTable* origins, wasm::Decoder* decoder)

View File

@ -385,6 +385,7 @@ class WasmGraphBuilder {
Node* ElemDrop(uint32_t elem_segment_index, wasm::WasmCodePosition position);
Node* TableCopy(uint32_t table_src_index, uint32_t table_dst_index, Node* dst,
Node* src, Node* size, wasm::WasmCodePosition position);
Node* TableGrow(uint32_t table_index, Node* value, Node* delta);
bool has_simd() const { return has_simd_; }

View File

@ -604,5 +604,23 @@ RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
if (oob) return ThrowTableOutOfBounds(isolate, instance);
return ReadOnlyRoots(isolate).undefined_value();
}
RUNTIME_FUNCTION(Runtime_WasmTableGrow) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
auto instance =
Handle<WasmInstanceObject>(GetWasmInstanceOnStackTop(isolate), isolate);
CONVERT_UINT32_ARG_CHECKED(table_index, 0);
CONVERT_ARG_CHECKED(Object, value_raw, 1);
// TODO(mstarzinger): Manually box because parameters are not visited yet.
Handle<Object> value(value_raw, isolate);
CONVERT_UINT32_ARG_CHECKED(delta, 2);
Handle<WasmTableObject> table(
WasmTableObject::cast(instance->tables()->get(table_index)), isolate);
int result = WasmTableObject::Grow(isolate, table, delta, value);
return Smi::FromInt(result);
}
} // namespace internal
} // namespace v8

View File

@ -545,6 +545,7 @@ namespace internal {
F(WasmFunctionTableSet, 4, 1) \
F(WasmTableInit, 5, 1) \
F(WasmTableCopy, 5, 1) \
F(WasmTableGrow, 3, 1) \
F(WasmIndirectCallCheckSignatureAndGetTargetInstance, 3, 1) \
F(WasmIndirectCallGetTargetAddress, 2, 1) \
F(WasmIsValidAnyFuncValue, 1, 1) \

View File

@ -1992,6 +1992,10 @@ class LiftoffCompiler {
Vector<Value> args) {
unsupported(decoder, "table.copy");
}
void TableGrow(FullDecoder* decoder, const TableIndexImmediate<validate>& imm,
Value& value, Value& delta, Value* result) {
unsupported(decoder, "table.grow");
}
private:
LiftoffAssembler asm_;

View File

@ -732,7 +732,9 @@ struct ControlBase {
const Value& value, const Value& size) \
F(TableInit, const TableInitImmediate<validate>& imm, Vector<Value> args) \
F(ElemDrop, const ElemDropImmediate<validate>& imm) \
F(TableCopy, const TableCopyImmediate<validate>& imm, Vector<Value> args)
F(TableCopy, const TableCopyImmediate<validate>& imm, Vector<Value> args) \
F(TableGrow, const TableIndexImmediate<validate>& imm, const Value& value, \
const Value& delta, Value* result)
// Generic Wasm bytecode decoder with utilities for decoding immediates,
// lengths, etc.
@ -1296,6 +1298,10 @@ class WasmDecoder : public Decoder {
TableCopyImmediate<validate> imm(decoder, pc);
return 2 + imm.length;
}
case kExprTableGrow: {
TableIndexImmediate<validate> imm(decoder, pc);
return 2 + imm.length;
}
default:
decoder->error(pc, "invalid numeric opcode");
return 2;
@ -1545,7 +1551,16 @@ class WasmFullDecoder : public WasmDecoder<validate> {
const char* SafeOpcodeNameAt(const byte* pc) {
if (pc >= this->end_) return "<end>";
return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(*pc));
WasmOpcode opcode = static_cast<WasmOpcode>(*pc);
if (!WasmOpcodes::IsPrefixOpcode(opcode)) {
return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(opcode));
}
// We need one more byte.
++pc;
if (pc >= this->end_) return "<end>";
byte sub_opcode = *pc;
opcode = static_cast<WasmOpcode>(opcode << 8 | sub_opcode);
return WasmOpcodes::OpcodeName(static_cast<WasmOpcode>(opcode));
}
inline Zone* zone() const { return zone_; }
@ -2058,7 +2073,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
TableIndexImmediate<validate> imm(this, this->pc_);
len = 1 + imm.length;
if (!this->Validate(this->pc_, imm)) break;
auto value = Pop(0, this->module_->tables[imm.index].type);
auto value = Pop(1, this->module_->tables[imm.index].type);
auto index = Pop(0, kWasmI32);
CALL_INTERFACE_IF_REACHABLE(SetTable, index, value, imm);
break;
@ -2216,6 +2231,8 @@ class WasmFullDecoder : public WasmDecoder<validate> {
opcode = static_cast<WasmOpcode>(opcode << 8 | numeric_index);
if (opcode < kExprMemoryInit) {
CHECK_PROTOTYPE_OPCODE(sat_f2i_conversions);
} else if (opcode == kExprTableGrow) {
CHECK_PROTOTYPE_OPCODE(anyref);
} else {
CHECK_PROTOTYPE_OPCODE(bulk_memory);
}
@ -2651,6 +2668,16 @@ class WasmFullDecoder : public WasmDecoder<validate> {
CALL_INTERFACE_IF_REACHABLE(TableCopy, imm, VectorOf(args_));
break;
}
case kExprTableGrow: {
TableIndexImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_, imm)) break;
len += imm.length;
auto delta = Pop(1, sig->GetParam(1));
auto value = Pop(0, this->module_->tables[imm.index].type);
auto* result = Push(kWasmI32);
CALL_INTERFACE_IF_REACHABLE(TableGrow, imm, value, delta, result);
break;
}
default:
this->error("invalid numeric opcode");
break;

View File

@ -534,33 +534,44 @@ class WasmGraphBuildingInterface {
BUILD(MemoryInit, imm.data_segment_index, dst.node, src.node, size.node,
decoder->position());
}
void DataDrop(FullDecoder* decoder, const DataDropImmediate<validate>& imm) {
BUILD(DataDrop, imm.index, decoder->position());
}
void MemoryCopy(FullDecoder* decoder,
const MemoryCopyImmediate<validate>& imm, const Value& dst,
const Value& src, const Value& size) {
BUILD(MemoryCopy, dst.node, src.node, size.node, decoder->position());
}
void MemoryFill(FullDecoder* decoder,
const MemoryIndexImmediate<validate>& imm, const Value& dst,
const Value& value, const Value& size) {
BUILD(MemoryFill, dst.node, value.node, size.node, decoder->position());
}
void TableInit(FullDecoder* decoder, const TableInitImmediate<validate>& imm,
Vector<Value> args) {
BUILD(TableInit, imm.table.index, imm.elem_segment_index, args[0].node,
args[1].node, args[2].node, decoder->position());
}
void ElemDrop(FullDecoder* decoder, const ElemDropImmediate<validate>& imm) {
BUILD(ElemDrop, imm.index, decoder->position());
}
void TableCopy(FullDecoder* decoder, const TableCopyImmediate<validate>& imm,
Vector<Value> args) {
BUILD(TableCopy, imm.table_src.index, imm.table_dst.index, args[0].node,
args[1].node, args[2].node, decoder->position());
}
void TableGrow(FullDecoder* decoder, const TableIndexImmediate<validate>& imm,
Value& value, Value& delta, Value* result) {
result->node = BUILD(TableGrow, imm.index, value.node, delta.node);
}
private:
SsaEnv* ssa_env_;
compiler::WasmGraphBuilder* builder_;

View File

@ -851,7 +851,6 @@ int WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t new_size = old_size + count;
auto new_store = isolate->factory()->CopyFixedArrayAndGrow(
handle(table->entries(), isolate), count);
table->set_entries(*new_store, WriteBarrierMode::UPDATE_WRITE_BARRIER);
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
@ -864,6 +863,12 @@ int WasmTableObject::Grow(Isolate* isolate, Handle<WasmTableObject> table,
// the instances that import a given table.
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset))->value();
if (table_index > 0) {
continue;
}
// For Table 0 we have to update the indirect function table.
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(dispatch_tables->get(i)), isolate);
DCHECK_EQ(old_size, instance->indirect_function_table_size());
@ -1026,6 +1031,12 @@ void WasmTableObject::ClearDispatchTables(Isolate* isolate,
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset))->value();
if (table_index > 0) {
// Only table 0 has a dispatch table in the instance at the moment.
continue;
}
Handle<WasmInstanceObject> target_instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),

View File

@ -209,6 +209,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(TableInit, "table.init")
CASE_OP(ElemDrop, "elem.drop")
CASE_OP(TableCopy, "table.copy")
CASE_OP(TableGrow, "table.grow")
// SIMD opcodes.
CASE_SIMD_OP(Splat, "splat")

View File

@ -420,7 +420,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, bool hasBigIntFeature);
V(MemoryFill, 0xfc0b, v_iii) \
V(TableInit, 0xfc0c, v_iii) \
V(ElemDrop, 0xfc0d, v_v) \
V(TableCopy, 0xfc0e, v_iii)
V(TableCopy, 0xfc0e, v_iii) \
V(TableGrow, 0xfc0f, i_ai)
#define FOREACH_ATOMIC_OPCODE(V) \
V(AtomicNotify, 0xfe00, i_ii) \
@ -547,7 +548,8 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, bool hasBigIntFeature);
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)
V(i_r, kWasmI32, kWasmAnyRef) \
V(i_ai, kWasmI32, kWasmAnyFunc, kWasmI32)
#define FOREACH_SIMD_SIGNATURE(V) \
V(s_s, kWasmS128, kWasmS128) \

View File

@ -25,8 +25,9 @@ class TestSignatures {
sig_i_ff(1, 2, kIntFloatTypes4),
sig_i_d(1, 1, kIntDoubleTypes4),
sig_i_dd(1, 2, kIntDoubleTypes4),
sig_i_r(1, 1, kIntRefTypes4),
sig_i_rr(1, 2, kIntRefTypes4),
sig_i_r(1, 1, kIntAnyRefTypes4),
sig_i_rr(1, 2, kIntAnyRefTypes4),
sig_i_a(1, 1, kIntAnyFuncTypes4),
sig_l_v(1, 0, kLongTypes4),
sig_l_l(1, 1, kLongTypes4),
sig_l_ll(1, 2, kLongTypes4),
@ -49,15 +50,17 @@ class TestSignatures {
for (int i = 0; i < 4; i++) kFloatTypes4[i] = kWasmF32;
for (int i = 0; i < 4; i++) kDoubleTypes4[i] = kWasmF64;
for (int i = 0; i < 4; i++) kRefTypes4[i] = kWasmAnyRef;
for (int i = 0; i < 4; i++) kIntLongTypes4[i] = kWasmI64;
for (int i = 0; i < 4; i++) kIntFloatTypes4[i] = kWasmF32;
for (int i = 0; i < 4; i++) kIntDoubleTypes4[i] = kWasmF64;
for (int i = 0; i < 4; i++) kIntRefTypes4[i] = kWasmAnyRef;
for (int i = 1; i < 4; i++) kIntLongTypes4[i] = kWasmI64;
for (int i = 1; i < 4; i++) kIntFloatTypes4[i] = kWasmF32;
for (int i = 1; i < 4; i++) kIntDoubleTypes4[i] = kWasmF64;
for (int i = 1; i < 4; i++) kIntAnyRefTypes4[i] = kWasmAnyRef;
for (int i = 1; i < 4; i++) kIntAnyFuncTypes4[i] = kWasmAnyFunc;
for (int i = 0; i < 4; i++) kSimd128IntTypes4[i] = kWasmS128;
kIntLongTypes4[0] = kWasmI32;
kIntFloatTypes4[0] = kWasmI32;
kIntDoubleTypes4[0] = kWasmI32;
kIntRefTypes4[0] = kWasmI32;
kIntAnyRefTypes4[0] = kWasmI32;
kIntAnyFuncTypes4[0] = kWasmI32;
kSimd128IntTypes4[1] = kWasmI32;
}
@ -77,6 +80,7 @@ class TestSignatures {
FunctionSig* i_ll() { return &sig_i_ll; }
FunctionSig* i_r() { return &sig_i_r; }
FunctionSig* i_rr() { return &sig_i_rr; }
FunctionSig* i_a() { return &sig_i_a; }
FunctionSig* f_f() { return &sig_f_f; }
FunctionSig* f_ff() { return &sig_f_ff; }
@ -112,7 +116,8 @@ class TestSignatures {
ValueType kIntLongTypes4[4];
ValueType kIntFloatTypes4[4];
ValueType kIntDoubleTypes4[4];
ValueType kIntRefTypes4[4];
ValueType kIntAnyRefTypes4[4];
ValueType kIntAnyFuncTypes4[4];
ValueType kSimd128IntTypes4[4];
FunctionSig sig_i_v;
@ -126,6 +131,7 @@ class TestSignatures {
FunctionSig sig_i_dd;
FunctionSig sig_i_r;
FunctionSig sig_i_rr;
FunctionSig sig_i_a;
FunctionSig sig_l_v;
FunctionSig sig_l_l;

View File

@ -618,6 +618,9 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
#define WASM_ELEM_DROP(seg) WASM_NUMERIC_OP(kExprElemDrop), U32V_1(seg)
#define WASM_TABLE_COPY(dst, src, size) \
dst, src, size, WASM_NUMERIC_OP(kExprTableCopy), TABLE_ZERO, TABLE_ZERO
#define WASM_TABLE_GROW(table, initial_value, delta) \
initial_value, delta, WASM_NUMERIC_OP(kExprTableGrow), \
static_cast<byte>(table)
//------------------------------------------------------------------------------
// Memory Operations.

View File

@ -0,0 +1,201 @@
// Copyright 2019 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: --expose-wasm --experimental-wasm-anyref
load("test/mjsunit/wasm/wasm-module-builder.js");
function dummy_func(val) {
let builder = new WasmModuleBuilder();
builder.addFunction("dummy", kSig_i_v)
.addBody([kExprI32Const, val])
.exportAs("dummy");
return builder.instantiate().exports.dummy;
}
let kSig_i_ri = makeSig([kWasmAnyRef, kWasmI32], [kWasmI32]);
let kSig_r_i = makeSig([kWasmI32], [kWasmAnyRef]);
let kSig_i_ai = makeSig([kWasmAnyFunc, kWasmI32], [kWasmI32]);
function testGrowInternalAnyRefTable(table_index) {
print(arguments.callee.name, table_index);
const builder = new WasmModuleBuilder();
const initial_size = 5;
// Add 10 tables, we only test one.
for (let i = 0; i < 10; ++i) {
builder.addTable(kWasmAnyRef, initial_size).index;
}
builder.addFunction('grow', kSig_i_ri)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, table_index])
.exportFunc();
builder.addFunction('get', kSig_r_i)
.addBody([kExprGetLocal, 0, kExprGetTable, table_index])
.exportFunc();
const instance = builder.instantiate();
let size = initial_size;
assertEquals(null, instance.exports.get(size - 2));
function growAndCheck(element, grow_by) {
assertTraps(kTrapTableOutOfBounds, () => instance.exports.get(size));
assertEquals(size, instance.exports.grow(element, grow_by));
for (let i = 0; i < grow_by; ++i) {
assertEquals(element, instance.exports.get(size + i));
}
size += grow_by;
}
growAndCheck("Hello", 3);
growAndCheck(undefined, 4);
growAndCheck(4, 2);
growAndCheck({Hello: "World"}, 3);
growAndCheck(null, 2);
}
testGrowInternalAnyRefTable(0);
testGrowInternalAnyRefTable(7);
testGrowInternalAnyRefTable(9);
function testGrowInternalAnyFuncTable(table_index) {
print(arguments.callee.name, table_index);
const builder = new WasmModuleBuilder();
let size = 5;
for (let i = 0; i < 10; ++i) {
builder.addTable(kWasmAnyFunc, size).index;
}
builder.addFunction('grow', kSig_i_ai)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, table_index])
.exportFunc();
const sig_index = builder.addType(kSig_i_v);
builder.addFunction('call', kSig_i_i)
.addBody([kExprGetLocal, 0, kExprCallIndirect, sig_index, table_index])
.exportFunc();
const instance = builder.instantiate();
assertTraps(kTrapFuncSigMismatch, () => instance.exports.call(size - 2));
function growAndCheck(element, grow_by) {
assertTraps(kTrapFuncInvalid, () => instance.exports.call(size));
assertEquals(size, instance.exports.grow(dummy_func(element), grow_by));
for (let i = 0; i < grow_by; ++i) {
assertEquals(element, instance.exports.call(size + i));
}
size += grow_by;
}
growAndCheck(56, 3);
growAndCheck(12, 4);
assertEquals(size, instance.exports.grow(null, 1));
assertTraps(kTrapFuncSigMismatch, () => instance.exports.call(size));
}
testGrowInternalAnyFuncTable(0);
testGrowInternalAnyFuncTable(7);
testGrowInternalAnyFuncTable(9);
(function testGrowImportedTable() {
print(arguments.callee.name);
let size = 3;
const builder = new WasmModuleBuilder();
const table_index = builder.addImportedTable("imp", "table", size, undefined, kWasmAnyRef);
builder.addFunction('grow', kSig_i_ri)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, table_index])
.exportFunc();
const table = new WebAssembly.Table({element: "anyref", initial: size});
const instance = builder.instantiate({imp: {table: table}});
assertEquals(null, table.get(size - 2));
function growAndCheck(element, grow_by) {
assertEquals(size, instance.exports.grow(element, grow_by));
for (let i = 0; i < grow_by; ++i) {
assertEquals(element, table.get(size + i));
}
size += grow_by;
}
growAndCheck("Hello", 3);
growAndCheck(undefined, 4);
growAndCheck(4, 2);
growAndCheck({ Hello: "World" }, 3);
growAndCheck(null, 2);
})();
(function testGrowTableOutOfBounds() {
print(arguments.callee.name);
const initial = 3;
const maximum = 10;
const max_delta = maximum - initial;
const invalid_delta = max_delta + 1;
const builder = new WasmModuleBuilder();
const import_ref = builder.addImportedTable(
"imp", "table_ref", initial, maximum, kWasmAnyRef);
const import_func = builder.addImportedTable(
"imp", "table_func", initial, maximum, kWasmAnyFunc);
const internal_ref = builder.addTable(kWasmAnyRef, initial, maximum).index;
const internal_func = builder.addTable(kWasmAnyFunc, initial, maximum).index;
builder.addFunction('grow_imported_ref', kSig_i_ri)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, import_ref])
.exportFunc();
builder.addFunction('grow_imported_func', kSig_i_ai)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, import_func])
.exportFunc();
builder.addFunction('grow_internal_ref', kSig_i_ri)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, internal_ref])
.exportFunc();
builder.addFunction('grow_internal_func', kSig_i_ai)
.addBody([kExprGetLocal, 0,
kExprGetLocal, 1,
kNumericPrefix, kExprTableGrow, internal_func])
.exportFunc();
const table_ref = new WebAssembly.Table(
{ element: "anyref", initial: initial, maximum: maximum });
const table_func = new WebAssembly.Table(
{element: "anyfunc", initial: initial, maximum: maximum});
const instance = builder.instantiate(
{imp: {table_ref: table_ref, table_func: table_func}});
const ref = { foo: "bar" };
const func = dummy_func(17);
// First check that growing out-of-bounds is not possible.
assertEquals(-1, instance.exports.grow_imported_ref(ref, invalid_delta));
assertEquals(initial, table_ref.length);
assertEquals(-1, instance.exports.grow_imported_func(func, invalid_delta));
assertEquals(initial, table_func.length);
assertEquals(-1, instance.exports.grow_internal_ref(ref, invalid_delta));
assertEquals(-1, instance.exports.grow_internal_func(func, invalid_delta));
// Check that we can grow to the maximum size.
assertEquals(initial, instance.exports.grow_imported_ref(ref, max_delta));
assertEquals(maximum, table_ref.length);
assertEquals(initial, instance.exports.grow_imported_func(func, max_delta));
assertEquals(maximum, table_func.length);
assertEquals(initial, instance.exports.grow_internal_ref(ref, max_delta));
assertEquals(initial, instance.exports.grow_internal_func(func, max_delta));
})();

View File

@ -390,6 +390,7 @@ let kExprMemoryFill = 0x0b;
let kExprTableInit = 0x0c;
let kExprElemDrop = 0x0d;
let kExprTableCopy = 0x0e;
let kExprTableGrow = 0x0f;
// Atomic opcodes.
let kExprAtomicNotify = 0x00;

View File

@ -3169,16 +3169,48 @@ TEST_F(FunctionBodyDecoderTest, TableCopy) {
{WASM_TABLE_COPY(WASM_ZERO, WASM_ZERO, WASM_ZERO)});
}
TEST_F(FunctionBodyDecoderTest, BulkTableOpsWithoutTable) {
TEST_F(FunctionBodyDecoderTest, TableGrow) {
TestModuleBuilder builder;
builder.InitializeTable();
builder.AddPassiveElementSegment();
byte tab_func = builder.AddTable(kWasmAnyFunc, 10, true, 20);
byte tab_ref = builder.AddTable(kWasmAnyRef, 10, true, 20);
WASM_FEATURE_SCOPE(bulk_memory);
ExpectFailure(sigs.v_v(),
{WASM_TABLE_INIT(0, WASM_ZERO, WASM_ZERO, WASM_ZERO)});
ExpectFailure(sigs.v_v(), {WASM_ELEM_DROP(0)});
ExpectFailure(sigs.v_v(), {WASM_TABLE_COPY(WASM_ZERO, WASM_ZERO, WASM_ZERO)});
module = builder.module();
ExpectFailure(sigs.i_a(),
{WASM_TABLE_GROW(tab_func, WASM_REF_NULL, WASM_ONE)});
WASM_FEATURE_SCOPE(anyref);
ExpectValidates(sigs.i_a(),
{WASM_TABLE_GROW(tab_func, WASM_REF_NULL, WASM_ONE)});
ExpectValidates(sigs.i_r(),
{WASM_TABLE_GROW(tab_ref, WASM_REF_NULL, WASM_ONE)});
// Anyfunc table cannot be initialized with an anyref value.
ExpectFailure(sigs.i_r(),
{WASM_TABLE_GROW(tab_func, WASM_GET_LOCAL(0), WASM_ONE)});
// Anyref table can be initialized with an anyfunc value.
ExpectValidates(sigs.i_a(),
{WASM_TABLE_GROW(tab_ref, WASM_GET_LOCAL(0), WASM_ONE)});
// Check that the table index gets verified.
ExpectFailure(sigs.i_r(),
{WASM_TABLE_GROW(tab_ref + 2, WASM_REF_NULL, WASM_ONE)});
}
TEST_F(FunctionBodyDecoderTest, TableOpsWithoutTable) {
TestModuleBuilder builder;
builder.AddTable(kWasmAnyRef, 10, true, 20);
{
WASM_FEATURE_SCOPE(anyref);
ExpectFailure(sigs.i_v(), {WASM_TABLE_GROW(0, WASM_REF_NULL, WASM_ONE)});
}
{
WASM_FEATURE_SCOPE(bulk_memory);
builder.AddPassiveElementSegment();
ExpectFailure(sigs.v_v(),
{WASM_TABLE_INIT(0, WASM_ZERO, WASM_ZERO, WASM_ZERO)});
ExpectFailure(sigs.v_v(), {WASM_ELEM_DROP(0)});
ExpectFailure(sigs.v_v(),
{WASM_TABLE_COPY(WASM_ZERO, WASM_ZERO, WASM_ZERO)});
}
}
class BranchTableIteratorTest : public TestWithZone {