diff --git a/src/builtins/base.tq b/src/builtins/base.tq index a239491fa9..d2b40090a7 100644 --- a/src/builtins/base.tq +++ b/src/builtins/base.tq @@ -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; diff --git a/src/runtime/runtime-wasm.cc b/src/runtime/runtime-wasm.cc index 288bfa1141..ed7365a6a4 100644 --- a/src/runtime/runtime-wasm.cc +++ b/src/runtime/runtime-wasm.cc @@ -498,9 +498,10 @@ RUNTIME_FUNCTION(Runtime_WasmIndirectCallCheckSignatureAndGetTargetInstance) { bool is_null; MaybeHandle maybe_target_instance; int function_index; + MaybeHandle 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 target_instance = @@ -555,15 +558,18 @@ RUNTIME_FUNCTION(Runtime_WasmIndirectCallGetTargetAddress) { bool is_null; MaybeHandle maybe_target_instance; int function_index; + MaybeHandle 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 target_instance = maybe_target_instance.ToHandleChecked(); diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc index cae675b1bb..4fde255ce9 100644 --- a/src/wasm/module-instantiate.cc +++ b/src/wasm/module-instantiate.cc @@ -919,15 +919,22 @@ bool InstanceBuilder::InitializeImportedIndirectFunctionTable( bool is_null; MaybeHandle maybe_target_instance; int function_index; + MaybeHandle 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 js_function; + if (maybe_js_function.ToHandle(&js_function)) { + WasmInstanceObject::ImportWasmJSFunctionIntoTable(isolate_, instance, i, + js_function); + continue; + } Handle target_instance = maybe_target_instance.ToHandleChecked(); diff --git a/src/wasm/wasm-objects-inl.h b/src/wasm/wasm-objects-inl.h index da42ad73fb..715187bc2a 100644 --- a/src/wasm/wasm-objects-inl.h +++ b/src/wasm/wasm-objects-inl.h @@ -323,6 +323,7 @@ SMI_ACCESSORS(WasmJSFunctionData, serialized_parameter_count, kSerializedParameterCountOffset) ACCESSORS(WasmJSFunctionData, serialized_signature, PodArray, kSerializedSignatureOffset) +ACCESSORS(WasmJSFunctionData, callable, JSReceiver, kCallableOffset) ACCESSORS(WasmJSFunctionData, wrapper_code, Code, kWrapperCodeOffset) // WasmCapiFunction diff --git a/src/wasm/wasm-objects.cc b/src/wasm/wasm-objects.cc index d8e9d7f4ee..4c9782e707 100644 --- a/src/wasm/wasm-objects.cc +++ b/src/wasm/wasm-objects.cc @@ -895,10 +895,11 @@ bool WasmTableObject::IsValidElement(Isolate* isolate, Handle 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 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::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 table, + int entry_index, + Handle function) { + // We simply need to update the IFTs for each instance that imports + // this table. + Handle 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 instance( + WasmInstanceObject::cast( + dispatch_tables->get(i + kDispatchTableInstanceOffset)), + isolate); + WasmInstanceObject::ImportWasmJSFunctionIntoTable(isolate, instance, + entry_index, function); + } +} + void WasmTableObject::UpdateDispatchTables( Isolate* isolate, Handle table, int entry_index, Handle capi_function) { @@ -1118,7 +1149,7 @@ void WasmTableObject::SetFunctionTablePlaceholder( void WasmTableObject::GetFunctionTableEntry( Isolate* isolate, Handle table, int entry_index, bool* is_valid, bool* is_null, MaybeHandle* instance, - int* function_index) { + int* function_index, MaybeHandle* 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::cast(element); *instance = handle(target_func->instance(), isolate); *function_index = target_func->function_index(); + *maybe_js_function = MaybeHandle(); return; - } else if (element->IsTuple2()) { + } + if (WasmJSFunction::IsWasmJSFunction(*element)) { + *instance = MaybeHandle(); + *maybe_js_function = Handle::cast(element); + return; + } + if (element->IsTuple2()) { auto tuple = Handle::cast(element); *instance = handle(WasmInstanceObject::cast(tuple->value1()), isolate); *function_index = Smi::cast(tuple->value2()).value(); + *maybe_js_function = MaybeHandle(); return; } *is_valid = false; @@ -1860,6 +1899,53 @@ void WasmInstanceObject::SetWasmExportedFunction( functions->set(index, *val); } +// static +void WasmInstanceObject::ImportWasmJSFunctionIntoTable( + Isolate* isolate, Handle instance, int table_index, + Handle 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 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_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 tuple = + isolate->factory()->NewTuple2(instance, callable, AllocationType::kOld); + IndirectFunctionTableEntry(instance, table_index) + .Set(sig_id, call_target, *tuple); +} + // static Handle WasmExceptionObject::New( Isolate* isolate, const wasm::FunctionSig* sig, @@ -2158,6 +2244,7 @@ Handle 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::New(Isolate* isolate, return Handle::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(); diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h index be3fd0bde6..515e278f73 100644 --- a/src/wasm/wasm-objects.h +++ b/src/wasm/wasm-objects.h @@ -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 Managed; @@ -292,11 +293,16 @@ class V8_EXPORT_PRIVATE WasmTableObject : public JSObject { static void Fill(Isolate* isolate, Handle table, uint32_t start, Handle entry, uint32_t count); + // TODO(mstarzinger): Unify these three methods into one. static void UpdateDispatchTables(Isolate* isolate, Handle table, int entry_index, wasm::FunctionSig* sig, Handle target_instance, int target_func_index); + static void UpdateDispatchTables(Isolate* isolate, + Handle table, + int entry_index, + Handle function); static void UpdateDispatchTables(Isolate* isolate, Handle 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 table, - int entry_index, bool* is_valid, - bool* is_null, - MaybeHandle* 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 table, int entry_index, + bool* is_valid, bool* is_null, MaybeHandle* instance, + int* function_index, MaybeHandle* maybe_js_function); OBJECT_CONSTRUCTORS(WasmTableObject, JSObject); }; @@ -597,6 +601,14 @@ class WasmInstanceObject : public JSObject { int index, Handle 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 instance, + int table_index, + Handle js_function); + OBJECT_CONSTRUCTORS(WasmInstanceObject, JSObject); private: @@ -681,6 +693,7 @@ class WasmJSFunction : public JSFunction { static Handle New(Isolate* isolate, wasm::FunctionSig* sig, Handle 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) + DECL_ACCESSORS(callable, JSReceiver) DECL_ACCESSORS(wrapper_code, Code) DECL_CAST(WasmJSFunctionData) diff --git a/test/mjsunit/wasm/type-reflection.js b/test/mjsunit/wasm/type-reflection.js index a179cd09c4..2818cf7f8c 100644 --- a/test/mjsunit/wasm/type-reflection.js +++ b/test/mjsunit/wasm/type-reflection.js @@ -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)); +})();