Commit 2fa55519 authored by Manos Koukoutos's avatar Manos Koukoutos Committed by V8 LUCI CQ

[wasm] Keep external function reference for externref tables/globals

See crrev.com/c/3277878 for context.

We should only transform extenral to internal function references when
passing a function value to a function-typed global or table. For their
externref counterparts, we should preserve the reference unchanged.

Bug: v8:11510, chromium:1273705
Change-Id: Ic1719c4d31e175f3a37ced6e4e4dfcd61a19ae57
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3302790
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78108}
parent 41285962
...@@ -2010,11 +2010,12 @@ auto Table::set(size_t index, const Ref* ref) -> bool { ...@@ -2010,11 +2010,12 @@ auto Table::set(size_t index, const Ref* ref) -> bool {
i::Isolate* isolate = table->GetIsolate(); i::Isolate* isolate = table->GetIsolate();
i::HandleScope handle_scope(isolate); i::HandleScope handle_scope(isolate);
i::Handle<i::Object> obj = WasmRefToV8(isolate, ref); i::Handle<i::Object> obj = WasmRefToV8(isolate, ref);
i::Handle<i::Object> entry; // TODO(7748): Generalize the condition if other table types are allowed.
if (!i::WasmInternalFunction::FromExternal(obj, isolate).ToHandle(&entry)) { if ((table->type() == i::wasm::kWasmFuncRef || table->type().has_index()) &&
entry = obj; !obj->IsNull()) {
obj = i::WasmInternalFunction::FromExternal(obj, isolate).ToHandleChecked();
} }
i::WasmTableObject::Set(isolate, table, static_cast<uint32_t>(index), entry); i::WasmTableObject::Set(isolate, table, static_cast<uint32_t>(index), obj);
return true; return true;
} }
...@@ -2028,13 +2029,13 @@ auto Table::grow(size_t delta, const Ref* ref) -> bool { ...@@ -2028,13 +2029,13 @@ auto Table::grow(size_t delta, const Ref* ref) -> bool {
i::Isolate* isolate = table->GetIsolate(); i::Isolate* isolate = table->GetIsolate();
i::HandleScope scope(isolate); i::HandleScope scope(isolate);
i::Handle<i::Object> obj = WasmRefToV8(isolate, ref); i::Handle<i::Object> obj = WasmRefToV8(isolate, ref);
i::Handle<i::Object> init_value; // TODO(7748): Generalize the condition if other table types are allowed.
if (!i::WasmInternalFunction::FromExternal(obj, isolate) if ((table->type() == i::wasm::kWasmFuncRef || table->type().has_index()) &&
.ToHandle(&init_value)) { !obj->IsNull()) {
init_value = obj; obj = i::WasmInternalFunction::FromExternal(obj, isolate).ToHandleChecked();
} }
int result = i::WasmTableObject::Grow( int result = i::WasmTableObject::Grow(isolate, table,
isolate, table, static_cast<uint32_t>(delta), init_value); static_cast<uint32_t>(delta), obj);
return result >= 0; return result >= 0;
} }
......
...@@ -1474,12 +1474,11 @@ bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance, ...@@ -1474,12 +1474,11 @@ bool InstanceBuilder::ProcessImportedGlobal(Handle<WasmInstanceObject> instance,
ReportLinkError(error_message, global_index, module_name, import_name); ReportLinkError(error_message, global_index, module_name, import_name);
return false; return false;
} }
auto stored_value = if (IsSubtypeOf(global.type, kWasmFuncRef, module_) && !value->IsNull()) {
WasmExternalFunction::IsWasmExternalFunction(*value) value =
? WasmInternalFunction::FromExternal(value, isolate_) WasmInternalFunction::FromExternal(value, isolate_).ToHandleChecked();
.ToHandleChecked() }
: value; WriteGlobalValue(global, WasmValue(value, global.type));
WriteGlobalValue(global, WasmValue(stored_value, global.type));
return true; return true;
} }
......
...@@ -1206,7 +1206,8 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -1206,7 +1206,8 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
"with the type of the new table."); "with the type of the new table.");
return; return;
} }
if (element->IsJSFunction()) { // TODO(7748): Generalize this if other table types are allowed.
if (type == i::wasm::kWasmFuncRef && !element->IsNull()) {
element = i::WasmInternalFunction::FromExternal(element, i_isolate) element = i::WasmInternalFunction::FromExternal(element, i_isolate)
.ToHandleChecked(); .ToHandleChecked();
} }
...@@ -1980,14 +1981,16 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -1980,14 +1981,16 @@ void WebAssemblyTableGrow(const v8::FunctionCallbackInfo<v8::Value>& args) {
init_value = DefaultReferenceValue(i_isolate, receiver->type()); init_value = DefaultReferenceValue(i_isolate, receiver->type());
} }
i::Handle<i::Object> internal_init_value; // TODO(7748): Generalize this if other table types are allowed.
if (!i::WasmInternalFunction::FromExternal(init_value, i_isolate) bool has_function_type =
.ToHandle(&internal_init_value)) { receiver->type() == i::wasm::kWasmFuncRef || receiver->type().has_index();
internal_init_value = init_value; if (has_function_type && !init_value->IsNull()) {
init_value = i::WasmInternalFunction::FromExternal(init_value, i_isolate)
.ToHandleChecked();
} }
int old_size = i::WasmTableObject::Grow(i_isolate, receiver, grow_by, int old_size =
internal_init_value); i::WasmTableObject::Grow(i_isolate, receiver, grow_by, init_value);
if (old_size < 0) { if (old_size < 0) {
thrower.RangeError("failed to grow table by %u", grow_by); thrower.RangeError("failed to grow table by %u", grow_by);
...@@ -2059,13 +2062,15 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) { ...@@ -2059,13 +2062,15 @@ void WebAssemblyTableSet(const v8::FunctionCallbackInfo<v8::Value>& args) {
return; return;
} }
i::Handle<i::Object> value; // TODO(7748): Generalize this if other table types are allowed.
if (!i::WasmInternalFunction::FromExternal(element, i_isolate) bool has_function_type = table_object->type() == i::wasm::kWasmFuncRef ||
.ToHandle(&value)) { table_object->type().has_index();
value = element; if (has_function_type && !element->IsNull()) {
element = i::WasmInternalFunction::FromExternal(element, i_isolate)
.ToHandleChecked();
} }
i::WasmTableObject::Set(i_isolate, table_object, index, value); i::WasmTableObject::Set(i_isolate, table_object, index, element);
} }
// WebAssembly.Table.type() -> TableType // WebAssembly.Table.type() -> TableType
......
// Copyright 2021 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.
const initial = function () {};
const descriptor = {"element": "externref", "initial": 3};
new WebAssembly.Table(descriptor, initial);
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// Flags: --experimental-wasm-reftypes // Flags: --experimental-wasm-typed-funcref --experimental-wasm-type-reflection
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
...@@ -136,3 +136,69 @@ function getDummy(val) { ...@@ -136,3 +136,69 @@ function getDummy(val) {
table.set(1); table.set(1);
assertEquals(table.get(1), undefined); assertEquals(table.get(1), undefined);
})(); })();
(function TestFunctionExternRefTableRoundtrip() {
// Test that
// - initialization, setting, and growing an externref table, and
// - (imported) externref globals
// preserve function references.
print(arguments.callee.name);
const js_function = function (i) { return i + 1; };
const wasm_js_function = new WebAssembly.Function(
{parameters:['i32', 'i32'], results: ['i32']},
function(a, b) { return a * b; })
let extern_type = wasmRefType(kWasmExternRef);
let builder = new WasmModuleBuilder();
let imported_global = builder.addImportedGlobal('m', 'n', extern_type, false);
let global = builder.addGlobal(kWasmExternRef, true).exportAs('global');
let table = builder.addTable(extern_type, 2, 10,
WasmInitExpr.GlobalGet(imported_global))
builder.addFunction(
'setup', makeSig([extern_type, extern_type], []))
.addBody([
kExprLocalGet, 0, kExprGlobalSet, global.index,
kExprI32Const, 1, kExprLocalGet, 0, kExprTableSet, table.index,
kExprLocalGet, 1, kExprI32Const, 1, kNumericPrefix,
kExprTableGrow, table.index, kExprDrop])
.exportFunc();
builder.addFunction('get', makeSig([kWasmI32], [kWasmExternRef]))
.addBody([kExprLocalGet, 0, kExprTableGet, table.index])
.exportFunc();
let instance = builder.instantiate({m : {n : js_function}});
instance.exports.setup(wasm_js_function, instance.exports.setup);
assertEquals(instance.exports.global.value, wasm_js_function);
assertEquals(instance.exports.get(0), js_function);
assertEquals(instance.exports.get(1), wasm_js_function);
assertEquals(instance.exports.get(2), instance.exports.setup);
})();
(function TestFunctionExternRefTableRoundtrip2() {
// Test that initialization, setting, and growing an externref table in the JS
// API preserves function references.
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addFunction('dummy', kSig_i_v)
.addBody([kExprI32Const, 0])
.exportAs('dummy');
let instance = builder.instantiate();
const js_function = function (i) { return i + 1; };
const wasm_js_function = new WebAssembly.Function(
{parameters:['i32', 'i32'], results: ['i32']},
function(a, b) { return a * b; })
const argument = { "element": "externref", "initial": 3 };
const table = new WebAssembly.Table(argument, js_function);
table.set(1, wasm_js_function);
table.set(2, instance.exports.dummy);
table.grow(1, wasm_js_function);
assertEquals(table.get(0), js_function);
assertEquals(table.get(1), wasm_js_function);
assertEquals(table.get(2), instance.exports.dummy);
assertEquals(table.get(3), wasm_js_function);
})();
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