Commit ad9cf534 authored by titzer's avatar titzer Committed by Commit bot

[wasm] Add support for exporting WebAssembly.Table instances.

R=bradnelson@chromium.org, rossberg@chromium.org
BUG=v8:5507

Review-Url: https://codereview.chromium.org/2443353002
Cr-Commit-Position: refs/heads/master@{#40554}
parent 5452f97e
......@@ -425,22 +425,9 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
}
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::Handle<i::JSFunction> table_ctor(
i_isolate->native_context()->wasm_table_constructor());
i::Handle<i::JSObject> table_obj =
i_isolate->factory()->NewJSObject(table_ctor);
i::Handle<i::FixedArray> fixed_array =
i_isolate->factory()->NewFixedArray(initial);
i::Object* null = i_isolate->heap()->null_value();
for (int i = 0; i < initial; ++i) fixed_array->set(i, null);
table_obj->SetInternalField(kWasmTableArrayFieldIndex, *fixed_array);
table_obj->SetInternalField(
kWasmTableMaximumFieldIndex,
has_maximum.FromJust()
? static_cast<i::Object*>(i::Smi::FromInt(maximum))
: static_cast<i::Object*>(i_isolate->heap()->undefined_value()));
i::Handle<i::Symbol> table_sym(i_isolate->native_context()->wasm_table_sym());
i::Object::SetProperty(table_obj, table_sym, table_obj, i::STRICT).Check();
i::Handle<i::FixedArray> fixed_array;
i::Handle<i::JSObject> table_obj = i::WasmJs::CreateWasmTableObject(
i_isolate, initial, has_maximum.FromJust(), maximum, &fixed_array);
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(table_obj));
}
......@@ -721,6 +708,28 @@ i::Handle<i::JSObject> i::WasmJs::CreateWasmMemoryObject(
return memory_obj;
}
i::Handle<i::JSObject> i::WasmJs::CreateWasmTableObject(
i::Isolate* i_isolate, uint32_t initial, bool has_maximum, uint32_t maximum,
i::Handle<i::FixedArray>* array) {
i::Handle<i::JSFunction> table_ctor(
i_isolate->native_context()->wasm_table_constructor());
i::Handle<i::JSObject> table_obj =
i_isolate->factory()->NewJSObject(table_ctor);
*array = i_isolate->factory()->NewFixedArray(initial);
i::Object* null = i_isolate->heap()->null_value();
// TODO(titzer): consider moving FixedArray to size_t.
for (int i = 0; i < static_cast<int>(initial); ++i) (*array)->set(i, null);
table_obj->SetInternalField(kWasmTableArrayFieldIndex, *(*array));
table_obj->SetInternalField(
kWasmTableMaximumFieldIndex,
has_maximum
? static_cast<i::Object*>(i::Smi::FromInt(maximum))
: static_cast<i::Object*>(i_isolate->heap()->undefined_value()));
i::Handle<i::Symbol> table_sym(i_isolate->native_context()->wasm_table_sym());
i::Object::SetProperty(table_obj, table_sym, table_obj, i::STRICT).Check();
return table_obj;
}
// TODO(titzer): we use the API to create the function template because the
// internal guts are too ugly to replicate here.
static i::Handle<i::FunctionTemplateInfo> NewTemplate(i::Isolate* i_isolate,
......
......@@ -28,6 +28,12 @@ class WasmJs {
Handle<JSArrayBuffer> buffer,
bool has_maximum, int maximum);
static Handle<JSObject> CreateWasmTableObject(Isolate* isolate,
uint32_t initial,
bool has_maximum,
uint32_t maximum,
Handle<FixedArray>* array);
static bool IsWasmMemoryObject(Isolate* isolate, Handle<Object> value);
static Handle<JSArrayBuffer> GetWasmMemoryArrayBuffer(Isolate* isolate,
......
......@@ -1037,6 +1037,8 @@ class WasmInstanceBuilder {
//--------------------------------------------------------------------------
int function_table_count =
static_cast<int>(module_->function_tables.size());
std::vector<InitializedTable> inited_tables;
inited_tables.reserve(module_->function_tables.size());
if (function_table_count > 0) {
Handle<FixedArray> old_function_tables =
compiled_module_->function_tables();
......@@ -1044,8 +1046,18 @@ class WasmInstanceBuilder {
factory->NewFixedArray(function_table_count);
for (int index = 0; index < function_table_count; ++index) {
WasmIndirectFunctionTable& table = module_->function_tables[index];
uint32_t size = table.max_size > 0 ? table.max_size : table.size;
Handle<FixedArray> new_table = factory->NewFixedArray(size * 2);
uint32_t table_size = table.size;
Handle<FixedArray> new_table = factory->NewFixedArray(table_size * 2);
inited_tables.push_back({Handle<JSObject>::null(),
Handle<FixedArray>::null(), new_table,
std::vector<WasmFunction*>()});
InitializedTable& init_table = inited_tables.back();
if (table.exported) {
init_table.new_entries.insert(init_table.new_entries.begin(),
table_size, nullptr);
}
for (int i = 0; i < new_table->length(); ++i) {
static const int kInvalidSigIndex = -1;
// Fill the table with invalid signature indexes so that uninitialized
......@@ -1054,17 +1066,20 @@ class WasmInstanceBuilder {
}
for (auto table_init : module_->table_inits) {
uint32_t base = EvalUint32InitExpr(globals, table_init.offset);
uint32_t table_size = static_cast<uint32_t>(new_table->length());
if (base > table_size ||
(base + table_init.entries.size() > table_size)) {
thrower_->CompileError("table initializer is out of bounds");
continue;
}
for (size_t i = 0; i < table_init.entries.size(); ++i) {
FunctionSig* sig = module_->functions[table_init.entries[i]].sig;
WasmFunction* func = &module_->functions[table_init.entries[i]];
if (table.exported) {
init_table.new_entries[i + base] = func;
}
FunctionSig* sig = func->sig;
int32_t sig_index = table.map.Find(sig);
new_table->set(static_cast<int>(i + base), Smi::FromInt(sig_index));
new_table->set(static_cast<int>(i + base + size),
new_table->set(static_cast<int>(i + base + table_size),
code_table->get(table_init.entries[i]));
}
}
......@@ -1086,7 +1101,7 @@ class WasmInstanceBuilder {
//--------------------------------------------------------------------------
// Set up the exports object for the new instance.
//--------------------------------------------------------------------------
ProcessExports(globals, code_table, instance);
ProcessExports(globals, inited_tables, code_table, instance);
if (num_imported_functions > 0 || !owner.is_null()) {
// If the code was cloned, or new imports were compiled, patch.
......@@ -1174,6 +1189,14 @@ class WasmInstanceBuilder {
}
private:
// Represents the initialized state of a table.
struct InitializedTable {
Handle<JSObject> table_object; // WebAssembly.Table instance
Handle<FixedArray> js_functions; // JSFunctions exported
Handle<FixedArray> dispatch_table; // internal (code, sig) pairs
std::vector<WasmFunction*> new_entries; // overwriting entries
};
Isolate* isolate_;
WasmModule* module_;
ErrorThrower* thrower_;
......@@ -1495,10 +1518,27 @@ class WasmInstanceBuilder {
// Process the exports, creating wrappers for functions, tables, memories,
// and globals.
void ProcessExports(MaybeHandle<JSArrayBuffer> globals,
std::vector<InitializedTable>& inited_tables,
Handle<FixedArray> code_table,
Handle<JSObject> instance) {
if (module_->export_table.size() == 0) return;
// Allocate a table to cache the exported JSFunctions if needed.
bool has_exported_functions = module_->num_exported_functions > 0;
if (!has_exported_functions) {
for (auto table : module_->function_tables) {
if (table.exported) {
has_exported_functions = true;
break;
}
}
}
Handle<FixedArray> exported_functions =
has_exported_functions
? isolate_->factory()->NewFixedArray(
static_cast<int>(module_->functions.size()))
: Handle<FixedArray>::null();
Handle<JSObject> exports_object = instance;
if (module_->origin == kWasmOrigin) {
// Create the "exports" object.
......@@ -1526,17 +1566,36 @@ class WasmInstanceBuilder {
WasmFunction& function = module_->functions[exp.index];
int export_index =
static_cast<int>(module_->functions.size() + func_index);
Handle<Code> export_code =
code_table->GetValueChecked<Code>(isolate_, export_index);
desc.set_value(WrapExportCodeAsJSFunction(
isolate_, export_code, name, function.sig, func_index, instance));
Handle<Object> value(exported_functions->get(exp.index), isolate_);
Handle<JSFunction> js_function;
if (value->IsJSFunction()) {
// There already is a JSFunction for this WASM function.
js_function = Handle<JSFunction>::cast(value);
} else {
// Wrap the exported code as a JSFunction.
Handle<Code> export_code =
code_table->GetValueChecked<Code>(isolate_, export_index);
js_function =
WrapExportCodeAsJSFunction(isolate_, export_code, name,
function.sig, func_index, instance);
exported_functions->set(exp.index, *js_function);
}
desc.set_value(js_function);
func_index++;
break;
}
case kExternalTable:
// TODO(titzer): create a WebAssembly.Table instance.
// TODO(titzer): should it have the same identity as an import?
case kExternalTable: {
InitializedTable& init_table = inited_tables[exp.index];
WasmIndirectFunctionTable& table =
module_->function_tables[exp.index];
if (init_table.table_object.is_null()) {
init_table.table_object = WasmJs::CreateWasmTableObject(
isolate_, table.size, table.max_size != 0, table.max_size,
&init_table.js_functions);
}
desc.set_value(init_table.table_object);
break;
}
case kExternalMemory: {
// Export the memory as a WebAssembly.Memory object.
Handle<Object> memory_object(
......@@ -1588,6 +1647,47 @@ class WasmInstanceBuilder {
return;
}
}
// Fill out the contents of WebAssembly.Table instances.
if (!exported_functions.is_null()) {
// TODO(titzer): We compile JS->WASM wrappers for any function that is a
// member of an exported indirect table that is not itself exported. This
// should be done at compile time and cached instead.
WasmInstance temp_instance(module_);
temp_instance.context = isolate_->native_context();
temp_instance.mem_size = 0;
temp_instance.mem_start = nullptr;
temp_instance.globals_start = nullptr;
ModuleEnv module_env;
module_env.module = module_;
module_env.instance = &temp_instance;
module_env.origin = module_->origin;
// Fill the exported tables with JSFunctions.
for (auto inited_table : inited_tables) {
if (inited_table.js_functions.is_null()) continue;
for (int i = 0; i < static_cast<int>(inited_table.new_entries.size());
i++) {
if (inited_table.new_entries[i] == nullptr) continue;
int func_index =
static_cast<int>(inited_table.new_entries[i]->func_index);
if (!exported_functions->get(func_index)->IsJSFunction()) {
// No JSFunction entry yet exists for this function. Create one.
Handle<Code> wasm_code(Code::cast(code_table->get(func_index)),
isolate_);
Handle<Code> wrapper_code = compiler::CompileJSToWasmWrapper(
isolate_, &module_env, wasm_code, func_index);
Handle<JSFunction> js_function = WrapExportCodeAsJSFunction(
isolate_, wrapper_code, isolate_->factory()->empty_string(),
module_->functions[func_index].sig, func_index, instance);
exported_functions->set(func_index, *js_function);
}
inited_table.js_functions->set(i,
exported_functions->get(func_index));
}
}
}
}
};
......
......@@ -8,6 +8,7 @@ load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
(function testExportedMain() {
print("TestExportedMain...");
var kReturnValue = 88;
var builder = new WasmModuleBuilder();
......@@ -28,6 +29,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
})();
(function testExportedTwice() {
print("TestExportedTwice...");
var kReturnValue = 99;
var builder = new WasmModuleBuilder();
......@@ -49,10 +51,12 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(kReturnValue, module.exports.foo());
assertEquals(kReturnValue, module.exports.blah());
assertSame(module.exports.blah, module.exports.foo);
})();
(function testNumericName() {
print("TestNumericName...");
var kReturnValue = 93;
var builder = new WasmModuleBuilder();
......@@ -74,6 +78,7 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
})();
(function testExportNameClash() {
print("TestExportNameClash...");
var builder = new WasmModuleBuilder();
builder.addFunction("one", kSig_v_v).addBody([kExprNop]).exportAs("main");
......@@ -87,3 +92,23 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertContains("Duplicate export", e.toString());
}
})();
(function testExportMultipleIdentity() {
print("TestExportMultipleIdentity...");
var builder = new WasmModuleBuilder();
builder.addFunction("one", kSig_v_v).addBody([kExprNop])
.exportAs("a")
.exportAs("b")
.exportAs("c");
let instance = builder.instantiate();
let e = instance.exports;
assertEquals("function", typeof e.a);
assertEquals("function", typeof e.b);
assertEquals("function", typeof e.c);
assertSame(e.a, e.b);
assertSame(e.a, e.c);
assertEquals("a", e.a.name);
})();
......@@ -73,7 +73,7 @@ module = (function () {
kExprGetLocal, 0,
kExprCallIndirect, sig_i_ii
])
.exportFunc()
.exportFunc();
builder.appendToTable([mul.index, add.index, popcnt.index, main.index]);
return builder.instantiate({mul: function(a, b) { return a * b | 0; }});
......@@ -85,10 +85,7 @@ assertTraps(kTrapFuncSigMismatch, "module.exports.main(2, 12, 33)");
assertTraps(kTrapFuncSigMismatch, "module.exports.main(3, 12, 33)");
assertTraps(kTrapFuncInvalid, "module.exports.main(4, 12, 33)");
module = (function () {
var builder = new WasmModuleBuilder();
function AddFunctions(builder) {
var mul = builder.addFunction("mul", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
......@@ -107,6 +104,14 @@ module = (function () {
kExprGetLocal, 1, // --
kExprI32Sub // --
]);
return {mul: mul, add: add, sub: sub};
}
module = (function () {
var builder = new WasmModuleBuilder();
var f = AddFunctions(builder);
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprI32Const, 33, // --
......@@ -115,7 +120,7 @@ module = (function () {
kExprCallIndirect, 0]) // --
.exportAs("main");
builder.appendToTable([mul.index, add.index, sub.index]);
builder.appendToTable([f.mul.index, f.add.index, f.sub.index]);
return builder.instantiate();
})();
......@@ -133,24 +138,7 @@ assertTraps(kTrapFuncInvalid, "module.exports.main(12, 3)");
function instanceWithTable(base, length) {
var builder = new WasmModuleBuilder();
var mul = builder.addFunction("mul", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Mul // --
]);
var add = builder.addFunction("add", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Add // --
]);
var sub = builder.addFunction("sub", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Sub // --
]);
var f = AddFunctions(builder);
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprI32Const, 33, // --
......@@ -160,7 +148,7 @@ assertTraps(kTrapFuncInvalid, "module.exports.main(12, 3)");
.exportAs("main");
builder.setFunctionTableLength(length);
builder.addFunctionTableInit(base, false, [add.index, sub.index, mul.index]);
builder.addFunctionTableInit(base, false, [f.add.index, f.sub.index, f.mul.index]);
return builder.instantiate();
}
......@@ -187,24 +175,7 @@ assertTraps(kTrapFuncInvalid, "module.exports.main(12, 3)");
var builder = new WasmModuleBuilder();
var mul = builder.addFunction("mul", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Mul // --
]);
var add = builder.addFunction("add", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Add // --
]);
var sub = builder.addFunction("sub", kSig_i_ii)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Sub // --
]);
var f = AddFunctions(builder);
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprI32Const, 33, // --
......@@ -215,7 +186,7 @@ assertTraps(kTrapFuncInvalid, "module.exports.main(12, 3)");
builder.setFunctionTableLength(10);
var g = builder.addImportedGlobal("base", undefined, kAstI32);
builder.addFunctionTableInit(g, true, [mul.index, add.index, sub.index]);
builder.addFunctionTableInit(g, true, [f.mul.index, f.add.index, f.sub.index]);
var module = new WebAssembly.Module(builder.toBuffer());
......
// Copyright 2016 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
load("test/mjsunit/wasm/wasm-constants.js");
load("test/mjsunit/wasm/wasm-module-builder.js");
function AddFunctions(builder) {
let sig_index = builder.addType(kSig_i_ii);
let mul = builder.addFunction("mul", sig_index)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Mul // --
]);
let add = builder.addFunction("add", sig_index)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Add // --
]);
let sub = builder.addFunction("sub", sig_index)
.addBody([
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprI32Sub // --
]);
return {mul: mul, add: add, sub: sub};
}
(function ExportedTableTest() {
print("ExportedTableTest...");
let builder = new WasmModuleBuilder();
let d = builder.addImport("js_div", kSig_i_ii);
let f = AddFunctions(builder);
builder.addFunction("main", kSig_i_ii)
.addBody([
kExprI32Const, 33, // --
kExprGetLocal, 0, // --
kExprGetLocal, 1, // --
kExprCallIndirect, 0]) // --
.exportAs("main");
f.add.exportAs("blarg");
builder.setFunctionTableLength(10);
let g = builder.addImportedGlobal("base", undefined, kAstI32);
builder.addFunctionTableInit(g, true, [f.mul.index, f.add.index,
f.sub.index,
d]);
builder.addExportOfKind("table", kExternalTable, 0);
let module = new WebAssembly.Module(builder.toBuffer());
function js_div(a, b) { return (a / b) | 0; }
for (let i = 0; i < 5; i++) {
print(" base = " + i);
let instance = new WebAssembly.Instance(module, {base: i, js_div: js_div});
main = instance.exports.main;
let table = instance.exports.table;
assertTrue(table instanceof WebAssembly.Table);
assertEquals(10, table.length);
for (let j = 0; j < i; j++) {
assertSame(null, table.get(j));
}
let mul = table.get(i+0);
let add = table.get(i+1);
let sub = table.get(i+2);
print(" mul=" + mul);
print(" add=" + add);
print(" sub=" + sub);
assertEquals("function", typeof mul);
assertEquals("function", typeof add);
assertEquals("function", typeof sub);
assertEquals(2, mul.length);
assertEquals(2, add.length);
assertEquals(2, sub.length);
assertEquals("blarg", add.name);
let exp_div = table.get(i+3);
assertEquals("function", typeof exp_div);
print(" js_div=" + exp_div);
// Should have a new, wrapped version of the import.
assertFalse(js_div == exp_div);
for (let j = i + 4; j < 10; j++) {
assertSame(null, table.get(j));
}
assertEquals(-33, mul(-11, 3));
assertEquals(4444444, add(3333333, 1111111));
assertEquals(-9999, sub(1, 10000));
assertEquals(-44, exp_div(-88.1, 2));
}
})();
......@@ -120,7 +120,8 @@ class WasmGlobalBuilder {
}
exportAs(name) {
this.module.exports.push({name: name, kind: kExternalGlobal, index: this.index});
this.module.exports.push({name: name, kind: kExternalGlobal,
index: this.index});
return this;
}
}
......@@ -197,7 +198,8 @@ class WasmModuleBuilder {
}
addImportedMemory(module, name, initial = 0, maximum) {
let o = {module: module, name: name, kind: kExternalMemory, initial: initial, maximum: maximum};
let o = {module: module, name: name, kind: kExternalMemory,
initial: initial, maximum: maximum};
this.imports.push(o);
return this;
}
......@@ -207,6 +209,11 @@ class WasmModuleBuilder {
return this;
}
addExportOfKind(name, kind, index) {
this.exports.push({name: name, kind: kind, index: index});
return this;
}
addDataSegment(addr, data, is_global = false) {
this.segments.push({addr: addr, data: data, is_global: is_global});
return this.segments.length - 1;
......@@ -217,10 +224,13 @@ class WasmModuleBuilder {
}
addFunctionTableInit(base, is_global, array) {
this.function_table_inits.push({base: base, is_global: is_global, array: array});
this.function_table_inits.push({base: base, is_global: is_global,
array: array});
if (!is_global) {
var length = base + array.length;
if (length > this.function_table_length) this.function_table_length = length;
if (length > this.function_table_length) {
this.function_table_length = length;
}
}
return this;
}
......
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