[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:
parent
ac79b539ec
commit
f066d764cc
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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));
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user