Commit 8a7f562e authored by Ben Smith's avatar Ben Smith Committed by Commit Bot

[wasm] Implement bulk memory table.copy in interpreter

Most of the mjsunit/wasm/table-copy.js tests have been ported to
cctests, so they can be tested with all execution tiers.

Bug: v8:8965
Change-Id: I448719be30a4b2bddb9e2cffb4c74d3134db2f50
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1529548
Commit-Queue: Ben Smith <binji@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60396}
parent a617f38f
......@@ -1705,6 +1705,18 @@ class ThreadImpl {
len += imm.length;
return ok;
}
case kExprTableCopy: {
TableCopyImmediate<Decoder::kNoValidate> imm(decoder, code->at(pc));
auto size = Pop().to<uint32_t>();
auto src = Pop().to<uint32_t>();
auto dst = Pop().to<uint32_t>();
bool ok = WasmInstanceObject::CopyTableEntries(
instance_object_->GetIsolate(), instance_object_,
imm.table_dst.index, imm.table_src.index, dst, src, size);
if (!ok) DoTrap(kTrapTableOutOfBounds, pc);
len += imm.length;
return ok;
}
default:
FATAL("Unknown or unimplemented opcode #%d:%s", code->start[pc],
OpcodeName(code->start[pc]));
......
......@@ -4,6 +4,7 @@
#include "test/cctest/cctest.h"
#include "test/cctest/wasm/wasm-run-utils.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
namespace v8 {
......@@ -350,6 +351,222 @@ WASM_EXEC_TEST(DataDropThenMemoryInit) {
CHECK_EQ(0xDEADBEEF, r.Call());
}
WASM_EXEC_TEST(TableCopyInbounds) {
EXPERIMENTAL_FLAG_SCOPE(bulk_memory);
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
r.builder().AddIndirectFunctionTable(nullptr, kTableSize);
BUILD(
r,
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
kExprI32Const, 0);
for (uint32_t i = 0; i <= kTableSize; ++i) {
r.CheckCallViaJS(0, 0, 0, i); // nop
r.CheckCallViaJS(0, 0, i, kTableSize - i);
r.CheckCallViaJS(0, i, 0, kTableSize - i);
}
}
namespace {
template <typename... Args>
void CheckTable(Isolate* isolate, Handle<WasmTableObject> table, Args... args) {
uint32_t args_length = static_cast<uint32_t>(sizeof...(args));
CHECK_EQ(table->current_length(), args_length);
Handle<Object> handles[] = {args...};
for (uint32_t i = 0; i < args_length; ++i) {
CHECK(WasmTableObject::Get(isolate, table, i).is_identical_to(handles[i]));
}
}
template <typename WasmRunner, typename... Args>
void CheckTableCall(Isolate* isolate, Handle<WasmTableObject> table,
WasmRunner& r, uint32_t function_index, Args... args) {
uint32_t args_length = static_cast<uint32_t>(sizeof...(args));
CHECK_EQ(table->current_length(), args_length);
double expected[] = {args...};
for (uint32_t i = 0; i < args_length; ++i) {
Handle<Object> buffer[] = {isolate->factory()->NewNumber(i)};
r.CheckCallApplyViaJS(expected[i], function_index, buffer, 1);
}
}
} // namespace
WASM_EXEC_TEST(TableCopyElems) {
EXPERIMENTAL_FLAG_SCOPE(bulk_memory);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const uint32_t sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
BUILD(fn, WASM_I32V_1(i));
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
BUILD(
r,
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
kExprI32Const, 0);
auto table = handle(
WasmTableObject::cast(r.builder().instance_object()->tables().get(0)),
isolate);
auto f0 = WasmTableObject::Get(isolate, table, 0);
auto f1 = WasmTableObject::Get(isolate, table, 1);
auto f2 = WasmTableObject::Get(isolate, table, 2);
auto f3 = WasmTableObject::Get(isolate, table, 3);
auto f4 = WasmTableObject::Get(isolate, table, 4);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTable(isolate, table, f1, f1, f2, f3, f4);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTable(isolate, table, f1, f2, f2, f3, f4);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTable(isolate, table, f1, f2, f2, f1, f2);
r.CheckCallViaJS(0, 1, 0, 2);
CheckTable(isolate, table, f1, f1, f2, f1, f2);
}
WASM_EXEC_TEST(TableCopyCalls) {
EXPERIMENTAL_FLAG_SCOPE(bulk_memory);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const uint32_t sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
BUILD(fn, WASM_I32V_1(i));
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
WasmFunctionCompiler& call = r.NewFunction(sigs.i_i(), "call");
BUILD(call, WASM_CALL_INDIRECT0(sig_index, WASM_GET_LOCAL(0)));
const uint32_t call_index = call.function_index();
BUILD(
r,
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
kExprI32Const, 0);
auto table = handle(
WasmTableObject::cast(r.builder().instance_object()->tables().get(0)),
isolate);
CheckTableCall(isolate, table, r, call_index, 0, 1, 2, 3, 4);
r.CheckCallViaJS(0, 0, 1, 1);
CheckTableCall(isolate, table, r, call_index, 1, 1, 2, 3, 4);
r.CheckCallViaJS(0, 0, 1, 2);
CheckTableCall(isolate, table, r, call_index, 1, 2, 2, 3, 4);
r.CheckCallViaJS(0, 3, 0, 2);
CheckTableCall(isolate, table, r, call_index, 1, 2, 2, 1, 2);
}
WASM_EXEC_TEST(TableCopyOobWrites) {
EXPERIMENTAL_FLAG_SCOPE(bulk_memory);
Isolate* isolate = CcTest::InitIsolateOnce();
HandleScope scope(isolate);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
uint16_t function_indexes[kTableSize];
const uint32_t sig_index = r.builder().AddSignature(sigs.i_v());
for (uint32_t i = 0; i < kTableSize; ++i) {
WasmFunctionCompiler& fn = r.NewFunction(sigs.i_v(), "f");
BUILD(fn, WASM_I32V_1(i));
fn.SetSigIndex(sig_index);
function_indexes[i] = fn.function_index();
}
r.builder().AddIndirectFunctionTable(function_indexes, kTableSize);
BUILD(
r,
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
kExprI32Const, 0);
auto table = handle(
WasmTableObject::cast(r.builder().instance_object()->tables().get(0)),
isolate);
auto f0 = WasmTableObject::Get(isolate, table, 0);
auto f1 = WasmTableObject::Get(isolate, table, 1);
auto f2 = WasmTableObject::Get(isolate, table, 2);
auto f3 = WasmTableObject::Get(isolate, table, 3);
auto f4 = WasmTableObject::Get(isolate, table, 4);
CheckTable(isolate, table, f0, f1, f2, f3, f4);
// Non-overlapping, src < dst.
r.CheckCallViaJS(0xDEADBEEF, 3, 0, 3);
CheckTable(isolate, table, f0, f1, f2, f0, f1);
// Non-overlapping, dst < src.
r.CheckCallViaJS(0xDEADBEEF, 0, 4, 2);
CheckTable(isolate, table, f1, f1, f2, f0, f1);
// Overlapping, src < dst. This is required to copy backward, but the first
// access will be out-of-bounds, so nothing changes.
r.CheckCallViaJS(0xDEADBEEF, 3, 0, 99);
CheckTable(isolate, table, f1, f1, f2, f0, f1);
// Overlapping, dst < src.
r.CheckCallViaJS(0xDEADBEEF, 0, 1, 99);
CheckTable(isolate, table, f1, f2, f0, f1, f1);
}
WASM_EXEC_TEST(TableCopyOob1) {
EXPERIMENTAL_FLAG_SCOPE(bulk_memory);
WasmRunner<uint32_t, uint32_t, uint32_t, uint32_t> r(execution_tier);
const uint32_t kTableSize = 5;
r.builder().AddIndirectFunctionTable(nullptr, kTableSize);
BUILD(
r,
WASM_TABLE_COPY(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1), WASM_GET_LOCAL(2)),
kExprI32Const, 0);
r.CheckCallViaJS(0, 0, 0, 1); // nop
r.CheckCallViaJS(0, 0, 0, kTableSize); // nop
r.CheckCallViaJS(0xDEADBEEF, 0, 0, kTableSize + 1);
r.CheckCallViaJS(0xDEADBEEF, 1, 0, kTableSize);
r.CheckCallViaJS(0xDEADBEEF, 0, 1, kTableSize);
{
const uint32_t big = 1000000;
r.CheckCallViaJS(0xDEADBEEF, big, 0, 0);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 0);
}
for (uint32_t big = 4294967295; big > 1000; big >>= 1) {
r.CheckCallViaJS(0xDEADBEEF, big, 0, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, 0, big);
}
for (uint32_t big = -1000; big != 0; big <<= 1) {
r.CheckCallViaJS(0xDEADBEEF, big, 0, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, big, 1);
r.CheckCallViaJS(0xDEADBEEF, 0, 0, big);
}
}
} // namespace test_run_wasm_bulk_memory
} // namespace wasm
} // namespace internal
......
......@@ -40,6 +40,8 @@ TestingModuleBuilder::TestingModuleBuilder(
}
instance_object_ = InitInstanceObject();
Handle<FixedArray> tables(isolate_->factory()->NewFixedArray(0));
instance_object_->set_tables(*tables);
if (maybe_import) {
// Manually compile an import wrapper and insert it into the instance.
......@@ -161,6 +163,7 @@ Handle<JSFunction> TestingModuleBuilder::WrapCode(uint32_t index) {
void TestingModuleBuilder::AddIndirectFunctionTable(
const uint16_t* function_indexes, uint32_t table_size) {
auto instance = instance_object();
uint32_t table_index = static_cast<uint32_t>(test_module_->tables.size());
test_module_->tables.emplace_back();
WasmTable& table = test_module_->tables.back();
table.initial_size = table_size;
......@@ -169,12 +172,29 @@ void TestingModuleBuilder::AddIndirectFunctionTable(
table.type = kWasmAnyFunc;
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
instance_object(), table_size);
for (uint32_t i = 0; i < table_size; ++i) {
WasmFunction& function = test_module_->functions[function_indexes[i]];
int sig_id = test_module_->signature_map.Find(*function.sig);
IndirectFunctionTableEntry(instance, i)
.Set(sig_id, instance, function.func_index);
Handle<WasmTableObject> table_obj =
WasmTableObject::New(isolate_, table.type, table.initial_size,
table.has_maximum_size, table.maximum_size, nullptr);
WasmTableObject::AddDispatchTable(isolate_, table_obj, instance_object_,
table_index);
if (function_indexes) {
for (uint32_t i = 0; i < table_size; ++i) {
WasmFunction& function = test_module_->functions[function_indexes[i]];
int sig_id = test_module_->signature_map.Find(*function.sig);
IndirectFunctionTableEntry(instance, i)
.Set(sig_id, instance, function.func_index);
WasmTableObject::SetFunctionTablePlaceholder(
isolate_, table_obj, i, instance_object_, function_indexes[i]);
}
}
Handle<FixedArray> old_tables(instance_object_->tables(), isolate_);
Handle<FixedArray> new_tables = isolate_->factory()->CopyFixedArrayAndGrow(
old_tables, old_tables->length() + 1);
new_tables->set(old_tables->length(), *table_obj);
instance_object_->set_tables(*new_tables);
}
uint32_t TestingModuleBuilder::AddBytes(Vector<const byte> bytes) {
......
......@@ -181,6 +181,8 @@ class TestingModuleBuilder {
// Wrap the code so it can be called as a JS function.
Handle<JSFunction> WrapCode(uint32_t index);
// If function_indexes is {nullptr}, the contents of the table will be
// initialized with null functions.
void AddIndirectFunctionTable(const uint16_t* function_indexes,
uint32_t table_size);
......@@ -515,15 +517,19 @@ class WasmRunner : public WasmRunnerBase {
Handle<JSFunction> jsfunc = jsfuncs_[function_index];
Handle<Object> global(isolate->context()->global_object(), isolate);
MaybeHandle<Object> retval =
Execution::Call(isolate, jsfunc, global, count, buffer);
Execution::TryCall(isolate, jsfunc, global, count, buffer,
Execution::MessageHandling::kReport, nullptr);
CHECK(!retval.is_null());
Handle<Object> result = retval.ToHandleChecked();
if (result->IsSmi()) {
CHECK_EQ(expected, Smi::ToInt(*result));
if (retval.is_null()) {
CHECK_EQ(expected, static_cast<double>(0xDEADBEEF));
} else {
CHECK(result->IsHeapNumber());
CHECK_DOUBLE_EQ(expected, HeapNumber::cast(*result)->value());
Handle<Object> result = retval.ToHandleChecked();
if (result->IsSmi()) {
CHECK_EQ(expected, Smi::ToInt(*result));
} else {
CHECK(result->IsHeapNumber());
CHECK_DOUBLE_EQ(expected, HeapNumber::cast(*result)->value());
}
}
if (builder_.interpret()) {
......
......@@ -6,241 +6,18 @@
load("test/mjsunit/wasm/wasm-module-builder.js");
(function TestTableCopyInbounds() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
let kTableSize = 5;
builder.setTableBounds(kTableSize, kTableSize);
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero, kTableZero])
.exportAs("copy");
let instance = builder.instantiate();
let copy = instance.exports.copy;
for (let i = 0; i <= kTableSize; i++) {
copy(0, 0, i); // nop
copy(0, i, kTableSize - i);
copy(i, 0, kTableSize - i);
}
})();
function addFunction(builder, k) {
let m = builder.addFunction("", kSig_i_v)
.addBody([...wasmI32Const(k)]);
return m;
}
function addFunctions(builder, count) {
let o = {};
for (var i = 0; i < count; i++) {
o[`f${i}`] = addFunction(builder, i);
}
return o;
}
function assertTable(obj, ...elems) {
for (var i = 0; i < elems.length; i++) {
assertEquals(elems[i], obj.get(i));
}
}
(function TestTableCopyElems() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
let kTableSize = 5;
builder.setTableBounds(kTableSize, kTableSize);
{
let o = addFunctions(builder, kTableSize);
builder.addElementSegment(0, 0, false,
[o.f0.index, o.f1.index, o.f2.index, o.f3.index, o.f4.index]);
}
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero, kTableZero])
.exportAs("copy");
builder.addExportOfKind("table", kExternalTable, 0);
let instance = builder.instantiate();
let table = instance.exports.table;
let f0 = table.get(0), f1 = table.get(1), f2 = table.get(2),
f3 = table.get(3), f4 = table.get(4);
let copy = instance.exports.copy;
assertEquals(0, f0());
assertEquals(1, f1());
assertEquals(2, f2());
assertTable(table, f0, f1, f2, f3, f4);
copy(0, 1, 1);
assertTable(table, f1, f1, f2, f3, f4);
copy(0, 1, 2);
assertTable(table, f1, f2, f2, f3, f4);
copy(3, 0, 2);
assertTable(table, f1, f2, f2, f1, f2);
copy(1, 0, 2);
assertTable(table, f1, f1, f2, f1, f2);
})();
function assertCall(call, ...elems) {
for (var i = 0; i < elems.length; i++) {
assertEquals(elems[i], call(i));
}
}
(function TestTableCopyCalls() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
let sig_i_i = builder.addType(kSig_i_i);
let sig_i_v = builder.addType(kSig_i_v);
let kTableSize = 5;
builder.setTableBounds(kTableSize, kTableSize);
{
let o = addFunctions(builder, 5);
builder.addElementSegment(0, 0, false,
[o.f0.index, o.f1.index, o.f2.index, o.f3.index, o.f4.index]);
}
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero, kTableZero])
.exportAs("copy");
builder.addFunction("call", sig_i_i)
.addBody([
kExprGetLocal, 0,
kExprCallIndirect, sig_i_v, kTableZero])
.exportAs("call");
let instance = builder.instantiate();
let copy = instance.exports.copy;
let call = instance.exports.call;
assertCall(call, 0, 1, 2, 3, 4);
copy(0, 1, 1);
assertCall(call, 1, 1, 2, 3, 4);
copy(0, 1, 2);
assertCall(call, 1, 2, 2, 3, 4);
copy(3, 0, 2);
assertCall(call, 1, 2, 2, 1, 2);
})();
(function TestTableCopyOobWrites() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
let kTableSize = 5;
builder.setTableBounds(kTableSize, kTableSize);
{
let o = addFunctions(builder, kTableSize);
builder.addElementSegment(0, 0, false,
[o.f0.index, o.f1.index, o.f2.index]);
}
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero, kTableZero])
.exportAs("copy");
builder.addExportOfKind("table", kExternalTable, 0);
let instance = builder.instantiate();
let table = instance.exports.table;
let f0 = table.get(0), f1 = table.get(1), f2 = table.get(2);
let copy = instance.exports.copy;
// Non-overlapping, src < dst.
assertThrows(() => copy(3, 0, 3));
assertTable(table, f0, f1, f2, f0, f1);
// Non-overlapping, dst < src.
assertThrows(() => copy(0, 4, 2));
assertTable(table, f1, f1, f2, f0, f1);
// Overlapping, src < dst. This is required to copy backward, but the first
// access will be out-of-bounds, so nothing changes.
assertThrows(() => copy(3, 0, 99));
assertTable(table, f1, f1, f2, f0, f1);
// Overlapping, dst < src.
assertThrows(() => copy(0, 1, 99));
assertTable(table, f1, f2, f0, f1, f1);
})();
(function TestTableCopyOob1() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
let kTableSize = 5;
builder.setTableBounds(kTableSize, kTableSize);
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero, kTableZero])
.exportAs("copy");
let instance = builder.instantiate();
let copy = instance.exports.copy;
copy(0, 0, 1); // nop
copy(0, 0, kTableSize); // nop
assertThrows(() => copy(0, 0, kTableSize+1));
assertThrows(() => copy(1, 0, kTableSize));
assertThrows(() => copy(0, 1, kTableSize));
{
let big = 1000000;
assertThrows(() => copy(big, 0, 0));
assertThrows(() => copy(0, big, 0));
}
for (let big = 4294967295; big > 1000; big >>>= 1) {
assertThrows(() => copy(big, 0, 1));
assertThrows(() => copy(0, big, 1));
assertThrows(() => copy(0, 0, big));
}
for (let big = -1000; big != 0; big <<= 1) {
assertThrows(() => copy(big, 0, 1));
assertThrows(() => copy(0, big, 1));
assertThrows(() => copy(0, 0, big));
}
})();
(function TestTableCopyShared() {
print(arguments.callee.name);
let kTableSize = 5;
......
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