Commit 61ea7c48 authored by Ben L. Titzer's avatar Ben L. Titzer Committed by Commit Bot

[wasm] Implement table.init bytecode

The table.init bytecode copies a range of elements from an element
segment into a table, trapping if the segment is not passive, is
dropped, or would cause out-of-bounds accesses.

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

Change-Id: Ib27af9cca45a464fd1f876ddd092e99941481896
Reviewed-on: https://chromium-review.googlesource.com/c/1430063
Commit-Queue: Ben Titzer <titzer@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59037}
parent aecb020e
...@@ -4390,6 +4390,7 @@ Node* WasmGraphBuilder::TableInit(uint32_t table_index, ...@@ -4390,6 +4390,7 @@ Node* WasmGraphBuilder::TableInit(uint32_t table_index,
uint32_t elem_segment_index, Node* dst, uint32_t elem_segment_index, Node* dst,
Node* src, Node* size, Node* src, Node* size,
wasm::WasmCodePosition position) { wasm::WasmCodePosition position) {
CheckElemSegmentIsPassiveAndNotDropped(elem_segment_index, position);
Node* args[] = { Node* args[] = {
graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)), graph()->NewNode(mcgraph()->common()->NumberConstant(table_index)),
graph()->NewNode(mcgraph()->common()->NumberConstant(elem_segment_index)), graph()->NewNode(mcgraph()->common()->NumberConstant(elem_segment_index)),
......
...@@ -303,6 +303,20 @@ RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) { ...@@ -303,6 +303,20 @@ RUNTIME_FUNCTION(Runtime_WasmI64AtomicWait) {
timeout_ms); timeout_ms);
} }
namespace {
Object ThrowTableOutOfBounds(Isolate* isolate,
Handle<WasmInstanceObject> instance) {
// Handle out-of-bounds access here in the runtime call, rather
// than having the lower-level layers deal with JS exceptions.
if (isolate->context().is_null()) {
isolate->set_context(instance->native_context());
}
Handle<Object> error_obj = isolate->factory()->NewWasmRuntimeError(
MessageTemplate::kWasmTrapTableOutOfBounds);
return isolate->Throw(*error_obj);
}
} // namespace
RUNTIME_FUNCTION(Runtime_WasmTableInit) { RUNTIME_FUNCTION(Runtime_WasmTableInit) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK_EQ(5, args.length()); DCHECK_EQ(5, args.length());
...@@ -312,21 +326,15 @@ RUNTIME_FUNCTION(Runtime_WasmTableInit) { ...@@ -312,21 +326,15 @@ RUNTIME_FUNCTION(Runtime_WasmTableInit) {
CONVERT_UINT32_ARG_CHECKED(elem_segment_index, 1); CONVERT_UINT32_ARG_CHECKED(elem_segment_index, 1);
CONVERT_UINT32_ARG_CHECKED(dst, 2); CONVERT_UINT32_ARG_CHECKED(dst, 2);
CONVERT_UINT32_ARG_CHECKED(src, 3); CONVERT_UINT32_ARG_CHECKED(src, 3);
CONVERT_UINT32_ARG_CHECKED(size, 4); CONVERT_UINT32_ARG_CHECKED(count, 4);
PrintF(
"TableInit(table_index=%u, elem_segment_index=%u, dst=%u, src=%u, "
"size=%u)\n",
table_index, elem_segment_index, dst, src, size);
USE(instance); DCHECK(isolate->context().is_null());
USE(table_index); isolate->set_context(instance->native_context());
USE(elem_segment_index);
USE(dst);
USE(src);
USE(size);
UNREACHABLE(); bool oob = !WasmInstanceObject::InitTableEntries(
isolate, instance, table_index, elem_segment_index, dst, src, count);
if (oob) return ThrowTableOutOfBounds(isolate, instance);
return ReadOnlyRoots(isolate).undefined_value();
} }
RUNTIME_FUNCTION(Runtime_WasmTableCopy) { RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
...@@ -341,15 +349,7 @@ RUNTIME_FUNCTION(Runtime_WasmTableCopy) { ...@@ -341,15 +349,7 @@ RUNTIME_FUNCTION(Runtime_WasmTableCopy) {
bool oob = !WasmInstanceObject::CopyTableEntries( bool oob = !WasmInstanceObject::CopyTableEntries(
isolate, instance, table_index, dst, src, count); isolate, instance, table_index, dst, src, count);
if (oob) { if (oob) return ThrowTableOutOfBounds(isolate, instance);
// 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(); return ReadOnlyRoots(isolate).undefined_value();
} }
} // namespace internal } // namespace internal
......
This diff is collapsed.
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
#ifndef V8_WASM_MODULE_INSTANTIATE_H_ #ifndef V8_WASM_MODULE_INSTANTIATE_H_
#define V8_WASM_MODULE_INSTANTIATE_H_ #define V8_WASM_MODULE_INSTANTIATE_H_
#include <stdint.h>
#include "include/v8config.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -28,6 +31,10 @@ MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject( ...@@ -28,6 +31,10 @@ MaybeHandle<WasmInstanceObject> InstantiateToInstanceObject(
Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports, Handle<WasmModuleObject> module_object, MaybeHandle<JSReceiver> imports,
MaybeHandle<JSArrayBuffer> memory); MaybeHandle<JSArrayBuffer> memory);
bool LoadElemSegment(Isolate* isolate, Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t segment_index, uint32_t dst,
uint32_t src, uint32_t count) V8_WARN_UNUSED_RESULT;
} // namespace wasm } // namespace wasm
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
......
...@@ -233,6 +233,8 @@ OPTIONAL_ACCESSORS(WasmInstanceObject, exceptions_table, FixedArray, ...@@ -233,6 +233,8 @@ OPTIONAL_ACCESSORS(WasmInstanceObject, exceptions_table, FixedArray,
ACCESSORS(WasmInstanceObject, undefined_value, Oddball, kUndefinedValueOffset) ACCESSORS(WasmInstanceObject, undefined_value, Oddball, kUndefinedValueOffset)
ACCESSORS(WasmInstanceObject, null_value, Oddball, kNullValueOffset) ACCESSORS(WasmInstanceObject, null_value, Oddball, kNullValueOffset)
ACCESSORS(WasmInstanceObject, centry_stub, Code, kCEntryStubOffset) ACCESSORS(WasmInstanceObject, centry_stub, Code, kCEntryStubOffset)
OPTIONAL_ACCESSORS(WasmInstanceObject, wasm_exported_functions, FixedArray,
kWasmExportedFunctionsOffset)
inline bool WasmInstanceObject::has_indirect_function_table() { inline bool WasmInstanceObject::has_indirect_function_table() {
return indirect_function_table_sig_ids() != nullptr; return indirect_function_table_sig_ids() != nullptr;
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "src/wasm/jump-table-assembler.h" #include "src/wasm/jump-table-assembler.h"
#include "src/wasm/module-compiler.h" #include "src/wasm/module-compiler.h"
#include "src/wasm/module-decoder.h" #include "src/wasm/module-decoder.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-code-manager.h"
#include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-limits.h"
...@@ -1477,6 +1478,48 @@ bool WasmInstanceObject::CopyTableEntries(Isolate* isolate, ...@@ -1477,6 +1478,48 @@ bool WasmInstanceObject::CopyTableEntries(Isolate* isolate,
return true; return true;
} }
// static
bool WasmInstanceObject::InitTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_index,
uint32_t segment_index, uint32_t dst,
uint32_t src, uint32_t count) {
// Note that this implementation just calls through to module instantiation.
// This is intentional, so that the runtime only depends on the object
// methods, and not the module instantiation logic.
return wasm::LoadElemSegment(isolate, instance, table_index, segment_index,
dst, src, count);
}
MaybeHandle<WasmExportedFunction> WasmInstanceObject::GetWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int index) {
MaybeHandle<WasmExportedFunction> result;
if (instance->has_wasm_exported_functions()) {
Object val = instance->wasm_exported_functions()->get(index);
if (!val->IsUndefined(isolate)) {
result = Handle<WasmExportedFunction>(WasmExportedFunction::cast(val),
isolate);
}
}
return result;
}
void WasmInstanceObject::SetWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int index,
Handle<WasmExportedFunction> val) {
Handle<FixedArray> functions;
if (!instance->has_wasm_exported_functions()) {
// lazily-allocate the wasm exported functions.
functions = isolate->factory()->NewFixedArray(
static_cast<int>(instance->module()->functions.size()));
instance->set_wasm_exported_functions(*functions);
} else {
functions =
Handle<FixedArray>(instance->wasm_exported_functions(), isolate);
}
functions->set(index, *val);
}
// static // static
Handle<WasmExceptionObject> WasmExceptionObject::New( Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig, Isolate* isolate, const wasm::FunctionSig* sig,
......
...@@ -40,6 +40,7 @@ class WasmDebugInfo; ...@@ -40,6 +40,7 @@ class WasmDebugInfo;
class WasmExceptionTag; class WasmExceptionTag;
class WasmInstanceObject; class WasmInstanceObject;
class WasmModuleObject; class WasmModuleObject;
class WasmExportedFunction;
template <class CppType> template <class CppType>
class Managed; class Managed;
...@@ -411,6 +412,7 @@ class WasmInstanceObject : public JSObject { ...@@ -411,6 +412,7 @@ class WasmInstanceObject : public JSObject {
DECL_ACCESSORS(undefined_value, Oddball) DECL_ACCESSORS(undefined_value, Oddball)
DECL_ACCESSORS(null_value, Oddball) DECL_ACCESSORS(null_value, Oddball)
DECL_ACCESSORS(centry_stub, Code) DECL_ACCESSORS(centry_stub, Code)
DECL_OPTIONAL_ACCESSORS(wasm_exported_functions, FixedArray)
DECL_PRIMITIVE_ACCESSORS(memory_start, byte*) DECL_PRIMITIVE_ACCESSORS(memory_start, byte*)
DECL_PRIMITIVE_ACCESSORS(memory_size, size_t) DECL_PRIMITIVE_ACCESSORS(memory_size, size_t)
DECL_PRIMITIVE_ACCESSORS(memory_mask, size_t) DECL_PRIMITIVE_ACCESSORS(memory_mask, size_t)
...@@ -454,6 +456,7 @@ class WasmInstanceObject : public JSObject { ...@@ -454,6 +456,7 @@ class WasmInstanceObject : public JSObject {
V(kUndefinedValueOffset, kTaggedSize) \ V(kUndefinedValueOffset, kTaggedSize) \
V(kNullValueOffset, kTaggedSize) \ V(kNullValueOffset, kTaggedSize) \
V(kCEntryStubOffset, kTaggedSize) \ V(kCEntryStubOffset, kTaggedSize) \
V(kWasmExportedFunctionsOffset, kTaggedSize) \
V(kEndOfTaggedFieldsOffset, 0) \ V(kEndOfTaggedFieldsOffset, 0) \
/* Raw data. */ \ /* Raw data. */ \
V(kIndirectFunctionTableSizeOffset, kUInt32Size) \ V(kIndirectFunctionTableSizeOffset, kUInt32Size) \
...@@ -509,9 +512,24 @@ class WasmInstanceObject : public JSObject { ...@@ -509,9 +512,24 @@ class WasmInstanceObject : public JSObject {
uint32_t table_index, uint32_t dst, uint32_t src, uint32_t table_index, uint32_t dst, uint32_t src,
uint32_t count) V8_WARN_UNUSED_RESULT; uint32_t count) V8_WARN_UNUSED_RESULT;
// Copy table entries from an element segment. Returns {false} if the ranges
// are out-of-bounds.
static bool InitTableEntries(Isolate* isolate,
Handle<WasmInstanceObject> instance,
uint32_t table_index, uint32_t segment_index,
uint32_t dst, uint32_t src,
uint32_t count) V8_WARN_UNUSED_RESULT;
// Iterates all fields in the object except the untagged fields. // Iterates all fields in the object except the untagged fields.
class BodyDescriptor; class BodyDescriptor;
static MaybeHandle<WasmExportedFunction> GetWasmExportedFunction(
Isolate* isolate, Handle<WasmInstanceObject> instance, int index);
static void SetWasmExportedFunction(Isolate* isolate,
Handle<WasmInstanceObject> instance,
int index,
Handle<WasmExportedFunction> val);
OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject) OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject)
private: private:
......
...@@ -321,28 +321,6 @@ function getMemoryFill(mem) { ...@@ -321,28 +321,6 @@ function getMemoryFill(mem) {
kTrapMemOutOfBounds, () => memoryFill(kPageSize + 1, v, kPageSize)); kTrapMemOutOfBounds, () => memoryFill(kPageSize + 1, v, kPageSize));
})(); })();
(function TestTableInit0() {
let builder = new WasmModuleBuilder();
let sig_v_iii = builder.addType(kSig_v_iii);
builder.setTableBounds(5, 5);
builder.addElementSegment(0, false, []);
builder.addElementSegment(0, false, []);
builder.addFunction("init0", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableInit, kTableZero, kSegmentZero])
.exportAs("init0");
let instance = builder.instantiate();
let init = instance.exports.init0;
// TODO(titzer): we only check that a function containing TableInit can be compiled.
// init(1, 2, 3);
})();
(function TestTableDropActive() { (function TestTableDropActive() {
const builder = new WasmModuleBuilder(); const builder = new WasmModuleBuilder();
builder.setTableBounds(5, 5); builder.setTableBounds(5, 5);
......
// 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 addFunction(builder, k) {
let m = builder.addFunction("", kSig_i_v)
.addBody([...wasmI32Const(k)]);
return m;
}
function addFunctions(builder, count, exportf = false) {
let o = {};
for (var i = 0; i < count; i++) {
let name = `f${i}`;
o[name] = addFunction(builder, i);
if (exportf) o[name].exportAs(name);
}
return o;
}
function assertTable(obj, ...elems) {
for (var i = 0; i < elems.length; i++) {
assertEquals(elems[i], obj.get(i));
}
}
(function TestTableInitInBounds() {
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, true);
builder.addPassiveElementSegment(
[o.f0.index, o.f1.index, o.f2.index, o.f3.index, o.f4.index]);
}
builder.addFunction("init0", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableInit, kTableZero, kSegmentZero])
.exportAs("init0");
builder.addExportOfKind("table", kExternalTable, 0);
let instance = builder.instantiate();
let x = instance.exports;
assertTable(x.table, null, null, null, null, null);
// 0-count is not oob.
x.init0(0, 0, 0);
assertTable(x.table, null, null, null, null, null);
x.init0(kTableSize+1, 0, 0);
assertTable(x.table, null, null, null, null, null);
x.init0(0, kTableSize+1, 0);
assertTable(x.table, null, null, null, null, null);
// test actual writes.
x.init0(0, 0, 1);
assertTable(x.table, x.f0, null, null, null, null);
x.init0(0, 0, 2);
assertTable(x.table, x.f0, x.f1, null, null, null);
x.init0(0, 0, 3);
assertTable(x.table, x.f0, x.f1, x.f2, null, null);
x.init0(3, 0, 2);
assertTable(x.table, x.f0, x.f1, x.f2, x.f0, x.f1);
x.init0(3, 1, 2);
assertTable(x.table, x.f0, x.f1, x.f2, x.f1, x.f2);
x.init0(3, 2, 2);
assertTable(x.table, x.f0, x.f1, x.f2, x.f2, x.f3);
x.init0(3, 3, 2);
assertTable(x.table, x.f0, x.f1, x.f2, x.f3, x.f4);
})();
(function TestTableInitOob() {
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.addPassiveElementSegment(
[o.f0.index, o.f1.index, o.f2.index, o.f3.index, o.f4.index]);
}
builder.addFunction("init0", sig_v_iii)
.addBody([
kExprGetLocal, 0,
kExprGetLocal, 1,
kExprGetLocal, 2,
kNumericPrefix, kExprTableInit, kTableZero, kSegmentZero])
.exportAs("init0");
builder.addExportOfKind("table", kExternalTable, 0);
let instance = builder.instantiate();
let x = instance.exports;
assertTable(x.table, null, null, null, null, null);
assertThrows(() => x.init0(0, 0, 6));
assertThrows(() => x.init0(0, 1, 5));
assertThrows(() => x.init0(0, 2, 4));
assertThrows(() => x.init0(0, 3, 3));
assertThrows(() => x.init0(0, 4, 2));
assertThrows(() => x.init0(0, 5, 1));
assertThrows(() => x.init0(0, 0, 6));
assertThrows(() => x.init0(1, 0, 5));
assertThrows(() => x.init0(2, 0, 4));
assertThrows(() => x.init0(3, 0, 3));
assertThrows(() => x.init0(4, 0, 2));
assertThrows(() => x.init0(5, 0, 1));
assertThrows(() => x.init0(10, 0, 1));
assertThrows(() => x.init0(0, 10, 1));
})();
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