[wasm] Support {WebAssembly.Function} in tables.

This adds preliminary support for storing constructed WebAssembly
functions in tables. Note that for now only tables at index #0 are
supported, extending it to other tables indexes will be done as a
follow-up.

R=ahaas@chromium.org
TEST=mjsunit/wasm/type-reflection
BUG=v8:7742

Change-Id: I9aa07813e07f0ceb4eafe37af412b45c7d235722
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1640209
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62210}
This commit is contained in:
Michael Starzinger 2019-06-17 12:09:06 +02:00 committed by Commit Bot
parent ac79b539ec
commit f066d764cc
7 changed files with 160 additions and 17 deletions

View File

@ -887,6 +887,7 @@ extern class WasmExportedFunctionData extends Struct {
}
extern class WasmJSFunctionData extends Struct {
callable: JSReceiver;
wrapper_code: Code;
serialized_return_count: Smi;
serialized_parameter_count: Smi;

View File

@ -498,9 +498,10 @@ RUNTIME_FUNCTION(Runtime_WasmIndirectCallCheckSignatureAndGetTargetInstance) {
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(
isolate, table_obj, entry_index, &is_valid, &is_null,
&maybe_target_instance, &function_index);
&maybe_target_instance, &function_index, &maybe_js_function);
CHECK(is_valid);
if (is_null) {
@ -510,6 +511,8 @@ RUNTIME_FUNCTION(Runtime_WasmIndirectCallCheckSignatureAndGetTargetInstance) {
// performed before a signature, according to the spec.
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapFuncSigMismatch);
}
// TODO(7742): Test and implement indirect calls for non-zero tables.
CHECK(maybe_js_function.is_null());
// Now we do the signature check.
Handle<WasmInstanceObject> target_instance =
@ -555,15 +558,18 @@ RUNTIME_FUNCTION(Runtime_WasmIndirectCallGetTargetAddress) {
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(
isolate, table_obj, entry_index, &is_valid, &is_null,
&maybe_target_instance, &function_index);
&maybe_target_instance, &function_index, &maybe_js_function);
CHECK(is_valid);
// The null-check should already have been done in
// Runtime_WasmIndirectCallCheckSignatureAndGetTargetInstance. That runtime
// function should always be called first.
CHECK(!is_null);
// TODO(7742): Test and implement indirect calls for non-zero tables.
CHECK(maybe_js_function.is_null());
Handle<WasmInstanceObject> target_instance =
maybe_target_instance.ToHandleChecked();

View File

@ -919,15 +919,22 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable(
bool is_null;
MaybeHandle<WasmInstanceObject> maybe_target_instance;
int function_index;
MaybeHandle<WasmJSFunction> maybe_js_function;
WasmTableObject::GetFunctionTableEntry(isolate_, table_object, i, &is_valid,
&is_null, &maybe_target_instance,
&function_index);
&function_index, &maybe_js_function);
if (!is_valid) {
thrower_->LinkError("table import %d[%d] is not a wasm function",
import_index, i);
return false;
}
if (is_null) continue;
Handle<WasmJSFunction> js_function;
if (maybe_js_function.ToHandle(&js_function)) {
WasmInstanceObject::ImportWasmJSFunctionIntoTable(isolate_, instance, i,
js_function);
continue;
}
Handle<WasmInstanceObject> target_instance =
maybe_target_instance.ToHandleChecked();

View File

@ -323,6 +323,7 @@ SMI_ACCESSORS(WasmJSFunctionData, serialized_parameter_count,
kSerializedParameterCountOffset)
ACCESSORS(WasmJSFunctionData, serialized_signature, PodArray<wasm::ValueType>,
kSerializedSignatureOffset)
ACCESSORS(WasmJSFunctionData, callable, JSReceiver, kCallableOffset)
ACCESSORS(WasmJSFunctionData, wrapper_code, Code, kWrapperCodeOffset)
// WasmCapiFunction

View File

@ -895,10 +895,11 @@ bool WasmTableObject::IsValidElement(Isolate* isolate,
Handle<Object> entry) {
// Anyref tables take everything.
if (table->type() == wasm::kWasmAnyRef) return true;
// Anyfunc tables can store {null} or {WasmExportedFunction} or
// {WasmCapiFunction} objects.
// Anyfunc tables can store {null}, {WasmExportedFunction}, {WasmJSFunction},
// or {WasmCapiFunction} objects.
if (entry->IsNull(isolate)) return true;
return WasmExportedFunction::IsWasmExportedFunction(*entry) ||
WasmJSFunction::IsWasmJSFunction(*entry) ||
WasmCapiFunction::IsWasmCapiFunction(*entry);
}
@ -932,6 +933,9 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
DCHECK_NOT_NULL(wasm_function->sig);
UpdateDispatchTables(isolate, table, entry_index, wasm_function->sig,
target_instance, func_index);
} else if (WasmJSFunction::IsWasmJSFunction(*entry)) {
UpdateDispatchTables(isolate, table, entry_index,
Handle<WasmJSFunction>::cast(entry));
} else {
DCHECK(WasmCapiFunction::IsWasmCapiFunction(*entry));
UpdateDispatchTables(isolate, table, entry_index,
@ -1022,6 +1026,33 @@ void WasmTableObject::UpdateDispatchTables(
}
}
void WasmTableObject::UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index,
Handle<WasmJSFunction> function) {
// We simply need to update the IFTs for each instance that imports
// this table.
Handle<FixedArray> dispatch_tables(table->dispatch_tables(), isolate);
DCHECK_EQ(0, dispatch_tables->length() % kDispatchTableNumElements);
for (int i = 0; i < dispatch_tables->length();
i += kDispatchTableNumElements) {
int table_index =
Smi::cast(dispatch_tables->get(i + kDispatchTableIndexOffset)).value();
if (table_index > 0) {
// Only table 0 has a dispatch table in the instance at the moment.
// TODO(ahaas): Introduce dispatch tables for the other tables as well.
continue;
}
Handle<WasmInstanceObject> instance(
WasmInstanceObject::cast(
dispatch_tables->get(i + kDispatchTableInstanceOffset)),
isolate);
WasmInstanceObject::ImportWasmJSFunctionIntoTable(isolate, instance,
entry_index, function);
}
}
void WasmTableObject::UpdateDispatchTables(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
Handle<WasmCapiFunction> capi_function) {
@ -1118,7 +1149,7 @@ void WasmTableObject::SetFunctionTablePlaceholder(
void WasmTableObject::GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index) {
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function) {
DCHECK_EQ(table->type(), wasm::kWasmAnyFunc);
DCHECK_LT(entry_index, table->entries().length());
// We initialize {is_valid} with {true}. We may change it later.
@ -1132,11 +1163,19 @@ void WasmTableObject::GetFunctionTableEntry(
auto target_func = Handle<WasmExportedFunction>::cast(element);
*instance = handle(target_func->instance(), isolate);
*function_index = target_func->function_index();
*maybe_js_function = MaybeHandle<WasmJSFunction>();
return;
} else if (element->IsTuple2()) {
}
if (WasmJSFunction::IsWasmJSFunction(*element)) {
*instance = MaybeHandle<WasmInstanceObject>();
*maybe_js_function = Handle<WasmJSFunction>::cast(element);
return;
}
if (element->IsTuple2()) {
auto tuple = Handle<Tuple2>::cast(element);
*instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate);
*function_index = Smi::cast(tuple->value2()).value();
*maybe_js_function = MaybeHandle<WasmJSFunction>();
return;
}
*is_valid = false;
@ -1860,6 +1899,53 @@ void WasmInstanceObject::SetWasmExportedFunction(
functions->set(index, *val);
}
// static
void WasmInstanceObject::ImportWasmJSFunctionIntoTable(
Isolate* isolate, Handle<WasmInstanceObject> instance, int table_index,
Handle<WasmJSFunction> js_function) {
// Deserialize the signature encapsulated with the {WasmJSFunction}.
// Note that {SignatureMap::Find} may return {-1} if the signature is
// not found; it will simply never match any check.
Zone zone(isolate->allocator(), ZONE_NAME);
wasm::FunctionSig* sig = js_function->GetSignature(&zone);
auto sig_id = instance->module()->signature_map.Find(*sig);
// Compile a wrapper for the target callable.
Handle<JSReceiver> callable(js_function->GetCallable(), isolate);
wasm::WasmCodeRefScope code_ref_scope;
Address call_target = kNullAddress;
if (sig_id >= 0) {
wasm::NativeModule* native_module =
instance->module_object().native_module();
// TODO(mstarzinger): Cache and reuse wrapper code.
const wasm::WasmFeatures enabled = native_module->enabled_features();
compiler::WasmImportCallKind kind =
compiler::GetWasmImportCallKind(callable, sig, enabled.bigint);
DCHECK_NE(compiler::WasmImportCallKind::kLinkError, kind);
wasm::CompilationEnv env = native_module->CreateCompilationEnv();
wasm::WasmCompilationResult result = compiler::CompileWasmImportCallWrapper(
isolate->wasm_engine(), &env, kind, sig, false);
std::unique_ptr<wasm::WasmCode> wasm_code = native_module->AddCode(
result.func_index, result.code_desc, result.frame_slot_count,
result.tagged_parameter_slots, std::move(result.protected_instructions),
std::move(result.source_positions), GetCodeKind(result),
wasm::ExecutionTier::kNone);
wasm::WasmCode* published_code =
native_module->PublishCode(std::move(wasm_code));
isolate->counters()->wasm_generated_code_size()->Increment(
published_code->instructions().length());
isolate->counters()->wasm_reloc_size()->Increment(
published_code->reloc_info().length());
call_target = published_code->instruction_start();
}
// Update the dispatch table.
Handle<Tuple2> tuple =
isolate->factory()->NewTuple2(instance, callable, AllocationType::kOld);
IndirectFunctionTableEntry(instance, table_index)
.Set(sig_id, call_target, *tuple);
}
// static
Handle<WasmExceptionObject> WasmExceptionObject::New(
Isolate* isolate, const wasm::FunctionSig* sig,
@ -2158,6 +2244,7 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
function_data->set_serialized_return_count(return_count);
function_data->set_serialized_parameter_count(parameter_count);
function_data->set_serialized_signature(*serialized_sig);
function_data->set_callable(*callable);
// TODO(7742): Make this callable by using a proper wrapper code.
function_data->set_wrapper_code(
isolate->builtins()->builtin(Builtins::kIllegal));
@ -2172,6 +2259,10 @@ Handle<WasmJSFunction> WasmJSFunction::New(Isolate* isolate,
return Handle<WasmJSFunction>::cast(js_function);
}
JSReceiver WasmJSFunction::GetCallable() const {
return shared().wasm_js_function_data().callable();
}
wasm::FunctionSig* WasmJSFunction::GetSignature(Zone* zone) {
WasmJSFunctionData function_data = shared().wasm_js_function_data();
int sig_size = function_data.serialized_signature().length();

View File

@ -40,9 +40,10 @@ class SeqOneByteString;
class WasmCapiFunction;
class WasmDebugInfo;
class WasmExceptionTag;
class WasmInstanceObject;
class WasmModuleObject;
class WasmExportedFunction;
class WasmInstanceObject;
class WasmJSFunction;
class WasmModuleObject;
template <class CppType>
class Managed;
@ -292,11 +293,16 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
static void Fill(Isolate* isolate, Handle<WasmTableObject> table,
uint32_t start, Handle<Object> entry, uint32_t count);
// TODO(mstarzinger): Unify these three methods into one.
static void UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index, wasm::FunctionSig* sig,
Handle<WasmInstanceObject> target_instance,
int target_func_index);
static void UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index,
Handle<WasmJSFunction> function);
static void UpdateDispatchTables(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index,
@ -312,14 +318,12 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject {
int func_index);
// This function reads the content of a function table entry and returns it
// through the out parameters {is_valid}, {is_null}, {instance}, and
// {function_index}.
static void GetFunctionTableEntry(Isolate* isolate,
Handle<WasmTableObject> table,
int entry_index, bool* is_valid,
bool* is_null,
MaybeHandle<WasmInstanceObject>* instance,
int* function_index);
// through the out parameters {is_valid}, {is_null}, {instance},
// {function_index}, and {maybe_js_function}.
static void GetFunctionTableEntry(
Isolate* isolate, Handle<WasmTableObject> table, int entry_index,
bool* is_valid, bool* is_null, MaybeHandle<WasmInstanceObject>* instance,
int* function_index, MaybeHandle<WasmJSFunction>* maybe_js_function);
OBJECT_CONSTRUCTORS(WasmTableObject, JSObject);
};
@ -597,6 +601,14 @@ class WasmInstanceObject : public JSObject {
int index,
Handle<WasmExportedFunction> val);
// Imports a constructed {WasmJSFunction} into the indirect function table of
// this instance. Note that this might trigger wrapper compilation, since a
// {WasmJSFunction} is instance-independent and just wraps a JS callable.
static void ImportWasmJSFunctionIntoTable(Isolate* isolate,
Handle<WasmInstanceObject> instance,
int table_index,
Handle<WasmJSFunction> js_function);
OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject);
private:
@ -681,6 +693,7 @@ class WasmJSFunction : public JSFunction {
static Handle<WasmJSFunction> New(Isolate* isolate, wasm::FunctionSig* sig,
Handle<JSReceiver> callable);
JSReceiver GetCallable() const;
// Deserializes the signature of this function using the provided zone. Note
// that lifetime of the signature is hence directly coupled to the zone.
wasm::FunctionSig* GetSignature(Zone* zone);
@ -761,6 +774,7 @@ class WasmJSFunctionData : public Struct {
DECL_INT_ACCESSORS(serialized_return_count)
DECL_INT_ACCESSORS(serialized_parameter_count)
DECL_ACCESSORS(serialized_signature, PodArray<wasm::ValueType>)
DECL_ACCESSORS(callable, JSReceiver)
DECL_ACCESSORS(wrapper_code, Code)
DECL_CAST(WasmJSFunctionData)

View File

@ -270,3 +270,26 @@ load('test/mjsunit/wasm/wasm-module-builder.js');
assertEquals(expected, type)
});
})();
(function TestFunctionTableSetAndCall() {
let builder = new WasmModuleBuilder();
let fun1 = new WebAssembly.Function({parameters:[], results:["i32"]}, _ => 7);
let fun2 = new WebAssembly.Function({parameters:[], results:["i32"]}, _ => 9);
let fun3 = new WebAssembly.Function({parameters:[], results:["f64"]}, _ => 0);
let table = new WebAssembly.Table({element: "anyfunc", initial: 2});
let table_index = builder.addImportedTable("m", "table", 2);
let sig_index = builder.addType(kSig_i_v);
table.set(0, fun1);
builder.addFunction('main', kSig_i_i)
.addBody([
kExprGetLocal, 0,
kExprCallIndirect, sig_index, table_index
])
.exportFunc();
let instance = builder.instantiate({ m: { table: table }});
assertEquals(7, instance.exports.main(0));
table.set(1, fun2);
assertEquals(9, instance.exports.main(1));
table.set(1, fun3);
assertTraps(kTrapFuncSigMismatch, () => instance.exports.main(1));
})();