[wasm] Implement the table.fill instruction

The implementation is done with a runtime function.

R=mstarzinger@chromium.org

Bug: v8:7581
Change-Id: I5f27b1fdc7cc2baf6919b4db3bf053a350b91a74
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1596738
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61274}
This commit is contained in:
Andreas Haas 2019-05-07 13:00:04 +02:00 committed by Commit Bot
parent 4bb788182b
commit 8168c76976
16 changed files with 342 additions and 22 deletions

View File

@ -4767,6 +4767,17 @@ Node* WasmGraphBuilder::TableSize(uint32_t table_index) {
return BuildChangeSmiToInt32(table_size);
}
Node* WasmGraphBuilder::TableFill(uint32_t table_index, Node* start,
Node* value, Node* count) {
Node* args[] = {
graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)),
BuildConvertUint32ToSmiWithSaturation(start, FLAG_wasm_max_table_size),
value,
BuildConvertUint32ToSmiWithSaturation(count, FLAG_wasm_max_table_size)};
return BuildCallToRuntime(Runtime::kWasmTableFill, args, arraysize(args));
}
class WasmDecorator final : public GraphDecorator {
public:
explicit WasmDecorator(NodeOriginTable* origins, wasm::Decoder* decoder)

View File

@ -387,6 +387,7 @@ class WasmGraphBuilder {
Node* src, Node* size, wasm::WasmCodePosition position);
Node* TableGrow(uint32_t table_index, Node* value, Node* delta);
Node* TableSize(uint32_t table_index);
Node* TableFill(uint32_t table_index, Node* start, Node* value, Node* count);
bool has_simd() const { return has_simd_; }

View File

@ -622,5 +622,37 @@ RUNTIME_FUNCTION(Runtime_WasmTableGrow) {
return Smi::FromInt(result);
}
RUNTIME_FUNCTION(Runtime_WasmTableFill) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
auto instance =
Handle<WasmInstanceObject>(GetWasmInstanceOnStackTop(isolate), isolate);
CONVERT_UINT32_ARG_CHECKED(table_index, 0);
CONVERT_UINT32_ARG_CHECKED(start, 1);
CONVERT_ARG_CHECKED(Object, value_raw, 2);
// TODO(mstarzinger): Manually box because parameters are not visited yet.
Handle<Object> value(value_raw, isolate);
CONVERT_UINT32_ARG_CHECKED(count, 3);
Handle<WasmTableObject> table(
WasmTableObject::cast(instance->tables()->get(table_index)), isolate);
uint32_t table_size = static_cast<uint32_t>(table->entries()->length());
if (start >= table_size) {
return ThrowTableOutOfBounds(isolate, instance);
}
// Even when table.fill goes out-of-bounds, as many entries as possible are
// put into the table. Only afterwards we trap.
uint32_t fill_count = std::min(count, table_size - start);
WasmTableObject::Fill(isolate, table, start, value, fill_count);
if (fill_count < count) {
return ThrowTableOutOfBounds(isolate, instance);
}
return ReadOnlyRoots(isolate).undefined_value();
}
} // namespace internal
} // namespace v8

View File

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

View File

@ -2000,6 +2000,10 @@ class LiftoffCompiler {
Value* result) {
unsupported(decoder, "table.size");
}
void TableFill(FullDecoder* decoder, const TableIndexImmediate<validate>& imm,
Value& start, Value& value, Value& count) {
unsupported(decoder, "table.fill");
}
private:
LiftoffAssembler asm_;

View File

@ -735,7 +735,9 @@ struct ControlBase {
F(TableCopy, const TableCopyImmediate<validate>& imm, Vector<Value> args) \
F(TableGrow, const TableIndexImmediate<validate>& imm, const Value& value, \
const Value& delta, Value* result) \
F(TableSize, const TableIndexImmediate<validate>& imm, Value* result)
F(TableSize, const TableIndexImmediate<validate>& imm, Value* result) \
F(TableFill, const TableIndexImmediate<validate>& imm, const Value& start, \
const Value& value, const Value& count)
// Generic Wasm bytecode decoder with utilities for decoding immediates,
// lengths, etc.
@ -1300,7 +1302,8 @@ class WasmDecoder : public Decoder {
return 2 + imm.length;
}
case kExprTableGrow:
case kExprTableSize: {
case kExprTableSize:
case kExprTableFill: {
TableIndexImmediate<validate> imm(decoder, pc);
return 2 + imm.length;
}
@ -2233,7 +2236,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 || opcode == kExprTableSize) {
} else if (opcode == kExprTableGrow || opcode == kExprTableSize ||
opcode == kExprTableFill) {
CHECK_PROTOTYPE_OPCODE(anyref);
} else {
CHECK_PROTOTYPE_OPCODE(bulk_memory);
@ -2688,6 +2692,16 @@ class WasmFullDecoder : public WasmDecoder<validate> {
CALL_INTERFACE_IF_REACHABLE(TableSize, imm, result);
break;
}
case kExprTableFill: {
TableIndexImmediate<validate> imm(this, this->pc_ + 1);
if (!this->Validate(this->pc_, imm)) break;
len += imm.length;
auto count = Pop(2, sig->GetParam(2));
auto value = Pop(1, this->module_->tables[imm.index].type);
auto start = Pop(0, sig->GetParam(0));
CALL_INTERFACE_IF_REACHABLE(TableFill, imm, start, value, count);
break;
}
default:
this->error("invalid numeric opcode");
break;

View File

@ -577,6 +577,11 @@ class WasmGraphBuildingInterface {
result->node = BUILD(TableSize, imm.index);
}
void TableFill(FullDecoder* decoder, const TableIndexImmediate<validate>& imm,
Value& start, Value& value, Value& count) {
BUILD(TableFill, imm.index, start.node, value.node, count.node);
}
private:
SsaEnv* ssa_env_;
compiler::WasmGraphBuilder* builder_;

View File

@ -994,6 +994,19 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate,
return result;
}
void WasmTableObject::Fill(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t start, Handle<Object> entry,
uint32_t count) {
// Bounds checks must be done by the caller.
DCHECK_LT(start, table->entries()->length());
DCHECK_LE(count, table->entries()->length());
DCHECK_LE(start + count, table->entries()->length());
for (uint32_t i = 0; i < count; i++) {
WasmTableObject::Set(isolate, table, start + i, entry);
}
}
void WasmTableObject::UpdateDispatchTables(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
wasm::FunctionSig* sig, Handle<WasmInstanceObject> target_instance,

View File

@ -287,6 +287,9 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
static Handle<Object> Get(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t index);
static void Fill(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t start, Handle<Object> entry, uint32_t count);
static void UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index, wasm::FunctionSig* sig,

View File

@ -211,6 +211,7 @@ const char* WasmOpcodes::OpcodeName(WasmOpcode opcode) {
CASE_OP(TableCopy, "table.copy")
CASE_OP(TableGrow, "table.grow")
CASE_OP(TableSize, "table.size")
CASE_OP(TableFill, "table.fill")
// SIMD opcodes.
CASE_SIMD_OP(Splat, "splat")

View File

@ -405,24 +405,26 @@ bool IsJSCompatibleSignature(const FunctionSig* sig, bool hasBigIntFeature);
FOREACH_SIMD_1_OPERAND_1_PARAM_OPCODE(V) \
FOREACH_SIMD_1_OPERAND_2_PARAM_OPCODE(V)
#define FOREACH_NUMERIC_OPCODE(V) \
V(I32SConvertSatF32, 0xfc00, i_f) \
V(I32UConvertSatF32, 0xfc01, i_f) \
V(I32SConvertSatF64, 0xfc02, i_d) \
V(I32UConvertSatF64, 0xfc03, i_d) \
V(I64SConvertSatF32, 0xfc04, l_f) \
V(I64UConvertSatF32, 0xfc05, l_f) \
V(I64SConvertSatF64, 0xfc06, l_d) \
V(I64UConvertSatF64, 0xfc07, l_d) \
V(MemoryInit, 0xfc08, v_iii) \
V(DataDrop, 0xfc09, v_v) \
V(MemoryCopy, 0xfc0a, v_iii) \
V(MemoryFill, 0xfc0b, v_iii) \
V(TableInit, 0xfc0c, v_iii) \
V(ElemDrop, 0xfc0d, v_v) \
V(TableCopy, 0xfc0e, v_iii) \
V(TableGrow, 0xfc0f, i_ai) \
V(TableSize, 0xfc10, i_v)
#define FOREACH_NUMERIC_OPCODE(V) \
V(I32SConvertSatF32, 0xfc00, i_f) \
V(I32UConvertSatF32, 0xfc01, i_f) \
V(I32SConvertSatF64, 0xfc02, i_d) \
V(I32UConvertSatF64, 0xfc03, i_d) \
V(I64SConvertSatF32, 0xfc04, l_f) \
V(I64UConvertSatF32, 0xfc05, l_f) \
V(I64SConvertSatF64, 0xfc06, l_d) \
V(I64UConvertSatF64, 0xfc07, l_d) \
V(MemoryInit, 0xfc08, v_iii) \
V(DataDrop, 0xfc09, v_v) \
V(MemoryCopy, 0xfc0a, v_iii) \
V(MemoryFill, 0xfc0b, v_iii) \
V(TableInit, 0xfc0c, v_iii) \
V(ElemDrop, 0xfc0d, v_v) \
V(TableCopy, 0xfc0e, v_iii) \
V(TableGrow, 0xfc0f, i_ai) \
V(TableSize, 0xfc10, i_v) \
/*TableFill is polymorph in the second parameter. It's anyref or anyfunc.*/ \
V(TableFill, 0xfc11, v_iii)
#define FOREACH_ATOMIC_OPCODE(V) \
V(AtomicNotify, 0xfe00, i_ii) \

View File

@ -41,6 +41,8 @@ class TestSignatures {
sig_v_i(0, 1, kIntTypes4),
sig_v_ii(0, 2, kIntTypes4),
sig_v_iii(0, 3, kIntTypes4),
sig_v_r(0, 1, kRefTypes4),
sig_v_a(0, 1, kFuncTypes4),
sig_s_i(1, 1, kSimd128IntTypes4),
sig_ii_v(2, 0, kIntTypes4),
sig_iii_v(3, 0, kIntTypes4) {
@ -50,6 +52,7 @@ 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++) kFuncTypes4[i] = kWasmAnyFunc;
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;
@ -93,6 +96,8 @@ class TestSignatures {
FunctionSig* v_i() { return &sig_v_i; }
FunctionSig* v_ii() { return &sig_v_ii; }
FunctionSig* v_iii() { return &sig_v_iii; }
FunctionSig* v_r() { return &sig_v_r; }
FunctionSig* v_a() { return &sig_v_a; }
FunctionSig* s_i() { return &sig_s_i; }
FunctionSig* ii_v() { return &sig_ii_v; }
@ -113,6 +118,7 @@ class TestSignatures {
ValueType kFloatTypes4[4];
ValueType kDoubleTypes4[4];
ValueType kRefTypes4[4];
ValueType kFuncTypes4[4];
ValueType kIntLongTypes4[4];
ValueType kIntFloatTypes4[4];
ValueType kIntDoubleTypes4[4];
@ -149,6 +155,8 @@ class TestSignatures {
FunctionSig sig_v_i;
FunctionSig sig_v_ii;
FunctionSig sig_v_iii;
FunctionSig sig_v_r;
FunctionSig sig_v_a;
FunctionSig sig_s_i;
FunctionSig sig_ii_v;

View File

@ -623,6 +623,8 @@ inline WasmOpcode LoadStoreOpcodeOf(MachineType type, bool store) {
static_cast<byte>(table)
#define WASM_TABLE_SIZE(table) \
WASM_NUMERIC_OP(kExprTableSize), static_cast<byte>(table)
#define WASM_TABLE_FILL(table, times, value, start) \
times, value, start, WASM_NUMERIC_OP(kExprTableFill), static_cast<byte>(table)
//------------------------------------------------------------------------------
// Memory Operations.

View File

@ -0,0 +1,196 @@
// 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_v_iri = makeSig([kWasmI32, kWasmAnyRef, kWasmI32], []);
let kSig_v_iai = makeSig([kWasmI32, kWasmAnyFunc, kWasmI32], []);
let kSig_r_i = makeSig([kWasmI32], [kWasmAnyRef]);
const builder = new WasmModuleBuilder();
const size = 10;
const maximum = size;
const import_ref =
builder.addImportedTable('imp', 'table_ref', size, maximum, kWasmAnyRef);
const import_func =
builder.addImportedTable('imp', 'table_func', size, maximum, kWasmAnyFunc);
const internal_ref = builder.addTable(kWasmAnyRef, size, maximum).index;
const internal_func = builder.addTable(kWasmAnyFunc, size, maximum).index;
// Add fill and get functions for the anyref tables.
for (index of [import_ref, internal_ref]) {
builder.addFunction(`fill${index}`, kSig_v_iri)
.addBody([
kExprGetLocal, 0, kExprGetLocal, 1, kExprGetLocal, 2, kNumericPrefix,
kExprTableFill, index
])
.exportFunc();
builder.addFunction(`get${index}`, kSig_r_i)
.addBody([kExprGetLocal, 0, kExprGetTable, index])
.exportFunc();
}
// Add fill and call functions for the anyfunc tables.
const sig_index = builder.addType(kSig_i_v);
for (index of [import_func, internal_func]) {
builder.addFunction(`fill${index}`, kSig_v_iai)
.addBody([
kExprGetLocal, 0, kExprGetLocal, 1, kExprGetLocal, 2, kNumericPrefix,
kExprTableFill, index
])
.exportFunc();
builder.addFunction(`call${index}`, kSig_i_i)
.addBody([kExprGetLocal, 0, kExprCallIndirect, sig_index, index])
.exportFunc();
}
const table_ref =
new WebAssembly.Table({element: 'anyref', initial: size, maximum: maximum});
const table_func = new WebAssembly.Table(
{element: 'anyfunc', initial: size, maximum: maximum});
const instance =
builder.instantiate({imp: {table_ref: table_ref, table_func: table_func}});
function checkAnyRefTable(getter, start, count, value) {
for (i = 0; i < count; ++i) {
assertEquals(value, getter(start + i));
}
}
(function testAnyRefTableIsUninitialized() {
print(arguments.callee.name);
checkAnyRefTable(instance.exports[`get${import_ref}`], 0, size, null);
checkAnyRefTable(instance.exports[`get${internal_ref}`], 0, size, null);
})();
(function testAnyRefTableFill() {
print(arguments.callee.name);
// Fill table and check the content.
let start = 1;
let value = {foo: 23};
let count = 3;
instance.exports[`fill${import_ref}`](start, value, count);
checkAnyRefTable(instance.exports[`get${import_ref}`], start, count, value);
value = 'foo';
instance.exports[`fill${internal_ref}`](start, value, count);
checkAnyRefTable(instance.exports[`get${internal_ref}`], start, count, value);
})();
(function testAnyRefTableFillOOB() {
print(arguments.callee.name);
// Fill table out-of-bounds, check if the table got filled as much as
// possible.
let start = 7;
let value = {foo: 27};
// {maximum + 4} elements definitely don't fit into the table.
let count = maximum + 4;
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${import_ref}`](start, value, count));
checkAnyRefTable(
instance.exports[`get${import_ref}`], start, size - start, value);
value = 45;
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${internal_ref}`](start, value, count));
checkAnyRefTable(
instance.exports[`get${internal_ref}`], start, size - start, value);
})();
(function testAnyRefTableFillOOBCountZero() {
print(arguments.callee.name);
// Fill 0 elements at an oob position. This should trap.
let start = size + 32;
let value = 'bar';
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${import_ref}`](start, value, 0));
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${internal_ref}`](start, value, 0));
})();
function checkAnyFuncTable(call, start, count, value) {
for (i = 0; i < count; ++i) {
if (value) {
assertEquals(value, call(start + i));
} else {
assertTraps(kTrapFuncSigMismatch, () => call(start + i));
}
}
}
(function testAnyRefTableIsUninitialized() {
print(arguments.callee.name);
// Check that the table is uninitialized.
checkAnyFuncTable(instance.exports[`call${import_func}`], 0, size);
checkAnyFuncTable(instance.exports[`call${internal_func}`], 0, size);
})();
(function testAnyFuncTableFill() {
print(arguments.callee.name);
// Fill and check the result.
let start = 1;
let value = 44;
let count = 3;
instance.exports[`fill${import_func}`](start, dummy_func(value), count);
checkAnyFuncTable(
instance.exports[`call${import_func}`], start, count, value);
value = 21;
instance.exports[`fill${internal_func}`](start, dummy_func(value), count);
checkAnyFuncTable(
instance.exports[`call${internal_func}`], start, count, value);
})();
(function testAnyFuncTableFillOOB() {
print(arguments.callee.name);
// Fill table out-of-bounds, check if the table got filled as much as
// possible.
let start = 7;
let value = 38;
// {maximum + 4} elements definitely don't fit into the table.
let count = maximum + 4;
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${import_func}`](
start, dummy_func(value), count));
checkAnyFuncTable(
instance.exports[`call${import_func}`], start, size - start, value);
value = 46;
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${internal_func}`](
start, dummy_func(value), count));
checkAnyFuncTable(
instance.exports[`call${internal_func}`], start, size - start, value);
})();
(function testAnyFuncTableFillOOBCountZero() {
print(arguments.callee.name);
// Fill 0 elements at an oob position. This should trap.
let start = size + 32;
let value = dummy_func(33);
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${import_func}`](start, null, 0));
assertTraps(
kTrapTableOutOfBounds,
() => instance.exports[`fill${internal_func}`](start, null, 0));
})();

View File

@ -392,6 +392,7 @@ let kExprElemDrop = 0x0d;
let kExprTableCopy = 0x0e;
let kExprTableGrow = 0x0f;
let kExprTableSize = 0x10;
let kExprTableFill = 0x11;
// Atomic opcodes.
let kExprAtomicNotify = 0x00;

View File

@ -3206,6 +3206,31 @@ TEST_F(FunctionBodyDecoderTest, TableSize) {
ExpectFailure(sigs.i_v(), {WASM_TABLE_SIZE(tab + 2)});
}
TEST_F(FunctionBodyDecoderTest, TableFill) {
TestModuleBuilder builder;
byte tab_func = builder.AddTable(kWasmAnyFunc, 10, true, 20);
byte tab_ref = builder.AddTable(kWasmAnyRef, 10, true, 20);
module = builder.module();
ExpectFailure(sigs.v_a(),
{WASM_TABLE_FILL(tab_func, WASM_ONE, WASM_REF_NULL, WASM_ONE)});
WASM_FEATURE_SCOPE(anyref);
ExpectValidates(sigs.v_a(), {WASM_TABLE_FILL(tab_func, WASM_ONE,
WASM_REF_NULL, WASM_ONE)});
ExpectValidates(sigs.v_r(), {WASM_TABLE_FILL(tab_ref, WASM_ONE, WASM_REF_NULL,
WASM_ONE)});
// Anyfunc table cannot be initialized with an anyref value.
ExpectFailure(sigs.v_r(), {WASM_TABLE_FILL(tab_func, WASM_ONE,
WASM_GET_LOCAL(0), WASM_ONE)});
// Anyref table can be initialized with an anyfunc value.
ExpectValidates(sigs.v_a(), {WASM_TABLE_FILL(tab_ref, WASM_ONE,
WASM_GET_LOCAL(0), WASM_ONE)});
// Check that the table index gets verified.
ExpectFailure(sigs.v_r(), {WASM_TABLE_FILL(tab_ref + 2, WASM_ONE,
WASM_REF_NULL, WASM_ONE)});
}
TEST_F(FunctionBodyDecoderTest, TableOpsWithoutTable) {
TestModuleBuilder builder;
builder.AddTable(kWasmAnyRef, 10, true, 20);
@ -3213,8 +3238,9 @@ TEST_F(FunctionBodyDecoderTest, TableOpsWithoutTable) {
WASM_FEATURE_SCOPE(anyref);
ExpectFailure(sigs.i_v(), {WASM_TABLE_GROW(0, WASM_REF_NULL, WASM_ONE)});
ExpectFailure(sigs.i_v(), {WASM_TABLE_SIZE(0)});
ExpectFailure(sigs.i_r(),
{WASM_TABLE_FILL(0, WASM_ONE, WASM_REF_NULL, WASM_ONE)});
}
{
WASM_FEATURE_SCOPE(bulk_memory);
builder.AddPassiveElementSegment();