Commit 8a40e88d authored by Ben L. Titzer's avatar Ben L. Titzer Committed by Commit Bot

[wasm] Implement table.copy bytecode

The table.copy bytecode copies a range of table entries in a similar
way to memcopy. This CL implements the behavior in a runtime call
that calls into the wasm engine.

R=mstarzinger@chromium.org,binji@chromium.org
BUG=v8:7747

Change-Id: I420451202b1b78ea92cbd10387a644ed57e111c8
Reviewed-on: https://chromium-review.googlesource.com/c/1414919
Commit-Queue: Ben Titzer <titzer@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#58858}
parent 07cff327
......@@ -1260,6 +1260,7 @@ namespace internal {
TFS(ThrowWasmTrapFuncSigMismatch) \
TFS(ThrowWasmTrapDataSegmentDropped) \
TFS(ThrowWasmTrapElemSegmentDropped) \
TFS(ThrowWasmTrapTableOutOfBounds) \
TFC(BigIntToWasmI64, BigIntToWasmI64, 1) \
TFC(WasmBigIntToI64, BigIntToI64, 1) \
\
......
......@@ -2917,6 +2917,19 @@ Node* WasmGraphBuilder::BuildChangeSmiToInt32(Node* value) {
return value;
}
Node* WasmGraphBuilder::BuildConvertUint32ToSmiWithSaturation(Node* value,
uint32_t maxval) {
DCHECK(Smi::IsValid(maxval));
Node* max = Uint32Constant(maxval);
Node* check = graph()->NewNode(mcgraph()->machine()->Uint32LessThanOrEqual(),
value, max);
Node* valsmi = BuildChangeUint31ToSmi(value);
Node* maxsmi = graph()->NewNode(mcgraph()->common()->NumberConstant(maxval));
Diamond d(graph(), mcgraph()->common(), check, BranchHint::kTrue);
d.Chain(Control());
return d.Phi(MachineRepresentation::kTagged, valsmi, maxsmi);
}
void WasmGraphBuilder::InitInstanceCache(
WasmInstanceCacheNodes* instance_cache) {
DCHECK_NOT_NULL(instance_node_);
......@@ -4427,15 +4440,12 @@ Node* WasmGraphBuilder::TableInit(uint32_t table_index,
uint32_t elem_segment_index, Node* dst,
Node* src, Node* size,
wasm::WasmCodePosition position) {
// TODO(titzer): bounds check table indexes against maximum table size.
Node* args[] = {
// --
graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)),
graph()->NewNode(mcgraph()->common()->NumberConstant(elem_segment_index)),
BuildChangeUint31ToSmi(dst), // --
BuildChangeUint31ToSmi(src), // --
BuildChangeUint31ToSmi(size) // --
};
BuildConvertUint32ToSmiWithSaturation(dst, wasm::kV8MaxWasmTableSize),
BuildConvertUint32ToSmiWithSaturation(src, wasm::kV8MaxWasmTableSize),
BuildConvertUint32ToSmiWithSaturation(size, wasm::kV8MaxWasmTableSize)};
Node* result =
BuildCallToRuntime(Runtime::kWasmTableInit, args, arraysize(args));
......@@ -4454,14 +4464,13 @@ Node* WasmGraphBuilder::TableDrop(uint32_t elem_segment_index,
mcgraph()->Int32Constant(1), Effect(), Control()));
}
Node* WasmGraphBuilder::TableCopy(Node* dst, Node* src, Node* size,
wasm::WasmCodePosition position) {
// TODO(titzer): bounds check table indexes against maximum table size.
Node* WasmGraphBuilder::TableCopy(uint32_t table_index, Node* dst, Node* src,
Node* size, wasm::WasmCodePosition position) {
Node* args[] = {
BuildChangeUint31ToSmi(dst), // --
BuildChangeUint31ToSmi(src), // --
BuildChangeUint31ToSmi(size) // --
};
graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)),
BuildConvertUint32ToSmiWithSaturation(dst, wasm::kV8MaxWasmTableSize),
BuildConvertUint32ToSmiWithSaturation(src, wasm::kV8MaxWasmTableSize),
BuildConvertUint32ToSmiWithSaturation(size, wasm::kV8MaxWasmTableSize)};
Node* result =
BuildCallToRuntime(Runtime::kWasmTableCopy, args, arraysize(args));
......
......@@ -377,7 +377,7 @@ class WasmGraphBuilder {
Node* TableInit(uint32_t table_index, uint32_t elem_segment_index, Node* dst,
Node* src, Node* size, wasm::WasmCodePosition position);
Node* TableDrop(uint32_t elem_segment_index, wasm::WasmCodePosition position);
Node* TableCopy(Node* dst, Node* src, Node* size,
Node* TableCopy(uint32_t table_index, Node* dst, Node* src, Node* size,
wasm::WasmCodePosition position);
bool has_simd() const { return has_simd_; }
......@@ -440,6 +440,7 @@ class WasmGraphBuilder {
Node* BoundsCheckMemRange(Node* start, Node* size, wasm::WasmCodePosition);
Node* CheckBoundsAndAlignment(uint8_t access_size, Node* index,
uint32_t offset, wasm::WasmCodePosition);
Node* Uint32ToUintptr(Node*);
const Operator* GetSafeLoadOperator(int offset, wasm::ValueType type);
const Operator* GetSafeStoreOperator(int offset, wasm::ValueType type);
......@@ -521,6 +522,8 @@ class WasmGraphBuilder {
Node* BuildChangeUint31ToSmi(Node* value);
Node* BuildSmiShiftBitsConstant();
Node* BuildChangeSmiToInt32(Node* value);
// generates {index > max ? Smi(max) : Smi(index)}
Node* BuildConvertUint32ToSmiWithSaturation(Node* index, uint32_t maxval);
// Asm.js specific functionality.
Node* BuildI32AsmjsSConvertF32(Node* input);
......
......@@ -1646,7 +1646,8 @@ enum class LoadSensitivity {
V(TrapFuncInvalid) \
V(TrapFuncSigMismatch) \
V(TrapDataSegmentDropped) \
V(TrapElemSegmentDropped)
V(TrapElemSegmentDropped) \
V(TrapTableOutOfBounds)
enum KeyedAccessLoadMode {
STANDARD_LOAD,
......
......@@ -517,6 +517,7 @@ namespace internal {
T(WasmTrapTypeError, "wasm function signature contains illegal type") \
T(WasmTrapDataSegmentDropped, "data segment has been dropped") \
T(WasmTrapElemSegmentDropped, "element segment has been dropped") \
T(WasmTrapTableOutOfBounds, "table access out of bounds") \
T(WasmExceptionError, "wasm exception") \
/* Asm.js validation related */ \
T(AsmJsInvalid, "Invalid asm.js: %") \
......
......@@ -322,7 +322,8 @@ RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) {
RUNTIME_FUNCTION(Runtime_WasmTableInit) {
HandleScope scope(isolate);
DCHECK_EQ(5, args.length());
auto instance = GetWasmInstanceOnStackTop(isolate);
auto instance =
Handle<WasmInstanceObject>(GetWasmInstanceOnStackTop(isolate), isolate);
CONVERT_UINT32_ARG_CHECKED(table_index, 0);
CONVERT_UINT32_ARG_CHECKED(elem_segment_index, 1);
CONVERT_UINT32_ARG_CHECKED(dst, 2);
......@@ -346,20 +347,26 @@ RUNTIME_FUNCTION(Runtime_WasmTableInit) {
RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
auto instance = GetWasmInstanceOnStackTop(isolate);
CONVERT_UINT32_ARG_CHECKED(dst, 0);
CONVERT_UINT32_ARG_CHECKED(src, 1);
CONVERT_UINT32_ARG_CHECKED(size, 2);
PrintF("TableCopy(dst=%u, src=%u, size=%u)\n", dst, src, size);
USE(instance);
USE(dst);
USE(src);
USE(size);
UNREACHABLE();
DCHECK_EQ(4, args.length());
auto instance =
Handle<WasmInstanceObject>(GetWasmInstanceOnStackTop(isolate), isolate);
CONVERT_UINT32_ARG_CHECKED(table_index, 0);
CONVERT_UINT32_ARG_CHECKED(dst, 1);
CONVERT_UINT32_ARG_CHECKED(src, 2);
CONVERT_UINT32_ARG_CHECKED(count, 3);
bool oob = !WasmInstanceObject::CopyTableEntries(
isolate, instance, table_index, dst, src, count);
if (oob) {
// Handle out-of-bounds access here in the runtime call, rather
// than having the lower-level layers deal with JS exceptions.
DCHECK(isolate->context().is_null());
isolate->set_context(instance->native_context());
Handle<Object> error_obj = isolate->factory()->NewWasmRuntimeError(
MessageTemplate::kWasmTrapTableOutOfBounds);
return isolate->Throw(*error_obj);
}
return ReadOnlyRoots(isolate).undefined_value();
}
} // namespace internal
} // namespace v8
......@@ -540,7 +540,7 @@ namespace internal {
F(WasmThrowCreate, 2, 1) \
F(WasmThrowTypeError, 0, 1) \
F(WasmTableInit, 5, 1) \
F(WasmTableCopy, 3, 1) \
F(WasmTableCopy, 4, 1) \
F(WasmCompileLazy, 2, 1)
#define FOR_EACH_INTRINSIC_RETURN_PAIR_IMPL(F, I) \
......
......@@ -547,7 +547,7 @@ class WasmGraphBuildingInterface {
}
void TableCopy(FullDecoder* decoder, const TableIndexImmediate<validate>& imm,
Vector<Value> args) {
BUILD(TableCopy, args[0].node, args[1].node, args[2].node,
BUILD(TableCopy, imm.index, args[0].node, args[1].node, args[2].node,
decoder->position());
}
......
......@@ -1337,6 +1337,7 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
WasmTableObject::New(isolate_, table.initial_size, maximum,
&table_instance.js_wrappers);
}
instance->set_table_object(*table_instance.table_object);
desc.set_value(table_instance.table_object);
break;
}
......
......@@ -852,8 +852,6 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
return;
}
// TODO(titzer): Change this to MaybeHandle<WasmExportedFunction>
DCHECK(WasmExportedFunction::IsWasmExportedFunction(*function));
auto exported_function = Handle<WasmExportedFunction>::cast(function);
Handle<WasmInstanceObject> target_instance(exported_function->instance(),
isolate);
......@@ -1198,6 +1196,16 @@ Address IndirectFunctionTableEntry::target() {
return instance_->indirect_function_table_targets()[index_];
}
void IndirectFunctionTableEntry::CopyFrom(
const IndirectFunctionTableEntry& that) {
instance_->indirect_function_table_sig_ids()[index_] =
that.instance_->indirect_function_table_sig_ids()[that.index_];
instance_->indirect_function_table_targets()[index_] =
that.instance_->indirect_function_table_targets()[that.index_];
instance_->indirect_function_table_refs()->set(
index_, that.instance_->indirect_function_table_refs()->get(that.index_));
}
void ImportedFunctionEntry::SetWasmToJs(
Isolate* isolate, Handle<JSReceiver> callable,
const wasm::WasmCode* wasm_to_js_wrapper) {
......@@ -1404,6 +1412,54 @@ Address WasmInstanceObject::GetCallTarget(uint32_t func_index) {
return native_module->GetCallTargetForFunction(func_index);
}
namespace {
void CopyTableEntriesImpl(Handle<WasmInstanceObject> instance, uint32_t dst,
uint32_t src, uint32_t count) {
for (uint32_t i = 0; i < count; i++) {
auto from_entry = IndirectFunctionTableEntry(instance, dst + i);
auto to_entry = IndirectFunctionTableEntry(instance, src + i);
from_entry.CopyFrom(to_entry);
}
}
} // namespace
// static
bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t dst,
uint32_t src, uint32_t count) {
CHECK_EQ(0, table_index); // TODO(titzer): multiple tables in TableCopy
auto max = instance->indirect_function_table_size();
if (dst > max || count > (max - dst)) return false; // out-of-bounds
if (src > max || count > (max - src)) return false; // out-of-bounds
if (!instance->has_table_object()) {
// No table object, only need to update this instance.
CopyTableEntriesImpl(instance, dst, src, count);
return true;
}
Handle<WasmTableObject> table =
Handle<WasmTableObject>(instance->table_object(), isolate);
// Broadcast table copy operation to all instances that import this table.
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
Handle<WasmInstanceObject> target_instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
CopyTableEntriesImpl(target_instance, dst, src, count);
}
// Copy the function entries.
Handle<FixedArray> functions(table->functions(), isolate);
for (uint32_t i = 0; i < count; i++) {
functions->set(dst + i, functions->get(src + i));
}
return true;
}
// static
Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig,
......
......@@ -61,6 +61,8 @@ class IndirectFunctionTableEntry {
void Set(int sig_id, Handle<WasmInstanceObject> target_instance,
int target_func_index);
void CopyFrom(const IndirectFunctionTableEntry& that);
Object object_ref();
int sig_id();
Address target();
......@@ -499,6 +501,12 @@ class WasmInstanceObject : public JSObject {
Address GetCallTarget(uint32_t func_index);
// Copies table entries. Returns {false} if the ranges are out-of-bounds.
static bool CopyTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t dst, uint32_t src,
uint32_t count) V8_WARN_UNUSED_RESULT;
// Iterates all fields in the object except the untagged fields.
class BodyDescriptor;
......
......@@ -373,23 +373,3 @@ function getMemoryFill(mem) {
instance.exports.drop();
assertTraps(kTrapElemSegmentDropped, () => instance.exports.drop());
})();
(function TestTableCopy0() {
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
builder.setTableBounds(5, 5);
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero])
.exportAs("copy");
let instance = builder.instantiate();
let copy = instance.exports.copy;
// TODO(titzer): we only check that a function containing TableCopy can be compiled.
// copy(1, 2, 3);
})();
// 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: --experimental-wasm-bulk-memory
load("test/mjsunit/wasm/wasm-constants.js");
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])
.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, 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])
.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);
})();
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, 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])
.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 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])
.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));
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;
let table = new WebAssembly.Table({element: "anyfunc",
initial: kTableSize,
maximum: kTableSize});
let module = (() => {
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);
builder.addImportedTable("m", "table", kTableSize, kTableSize);
var g = builder.addImportedGlobal("m", "g", kWasmI32);
for (let i = 0; i < kTableSize; i++) {
let f = builder.addFunction("", kSig_i_v)
.addBody([
kExprGetGlobal, g,
...wasmI32Const(i),
kExprI32Add
]);
f.exportAs(`f${i}`);
}
builder.addFunction("copy", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableCopy, kTableZero])
.exportAs("copy");
builder.addFunction("call", sig_i_i)
.addBody([
kExprGetLocal, 0,
kExprCallIndirect, sig_i_v, kTableZero])
.exportAs("call");
return builder.toModule();
})();
// Two different instances with different globals, to verify that
// dispatch tables get updated with the right instance.
let x = new WebAssembly.Instance(module, {m: {g: 1000, table: table}});
let y = new WebAssembly.Instance(module, {m: {g: 2000, table: table}});
let x_call = x.exports.call;
let y_call = y.exports.call;
assertNotEquals(x.exports.f3, y.exports.f3);
table.set(0, x.exports.f0);
table.set(1, x.exports.f1);
table.set(2, x.exports.f2);
table.set(3, y.exports.f3);
table.set(4, y.exports.f4);
assertEquals(2003, table.get(3)(3));
assertEquals(2003, x_call(3));
assertEquals(2003, y_call(3));
// Check that calling copy on either of them updates the dispatch table
// on both of them.
assertCall(x_call, 1000, 1001, 1002, 2003, 2004);
assertCall(y_call, 1000, 1001, 1002, 2003, 2004);
x.exports.copy(0, 1, 1);
assertCall(x_call, 1001, 1001, 1002, 2003, 2004);
assertCall(y_call, 1001, 1001, 1002, 2003, 2004);
y.exports.copy(0, 1, 2);
assertCall(x_call, 1001, 1002, 1002, 2003, 2004);
assertCall(y_call, 1001, 1002, 1002, 2003, 2004);
x.exports.copy(3, 0, 2);
assertCall(x_call, 1001, 1002, 1002, 1001, 1002);
assertCall(y_call, 1001, 1002, 1002, 1001, 1002);
})();
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