[stringref] Add support for stringrefs in tables

Bug: v8:12868
Change-Id: I42ef3e15b2a7fd2ef157aa0e657ddf98973e8d79
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3644956
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Andy Wingo <wingo@igalia.com>
Cr-Commit-Position: refs/heads/main@{#80605}
This commit is contained in:
Andy Wingo 2022-05-17 15:17:57 +02:00 committed by V8 LUCI CQ
parent 9137ecb400
commit 010de10eb8
7 changed files with 203 additions and 15 deletions

View File

@ -460,13 +460,6 @@ RUNTIME_FUNCTION(Runtime_WasmFunctionTableSet) {
DCHECK_LT(table_index, instance->tables().length());
auto table = handle(
WasmTableObject::cast(instance->tables().get(table_index)), isolate);
// We only use the runtime call for function references.
DCHECK(
table->instance().IsUndefined()
? table->type() == wasm::kWasmFuncRef
: IsSubtypeOf(table->type(), wasm::kWasmFuncRef,
WasmInstanceObject::cast(table->instance()).module()));
if (!WasmTableObject::IsInBounds(isolate, table, entry_index)) {
return ThrowWasmError(isolate, MessageTemplate::kWasmTrapTableOutOfBounds);
}

View File

@ -2005,6 +2005,7 @@ auto Table::set(size_t index, const Ref* ref) -> bool {
i::HandleScope handle_scope(isolate);
i::Handle<i::Object> obj = WasmRefToV8(isolate, ref);
// TODO(7748): Generalize the condition if other table types are allowed.
// TODO(12868): Enforce type restrictions for stringref tables.
if ((table->type() == i::wasm::kWasmFuncRef || table->type().has_index()) &&
!obj->IsNull()) {
obj = i::WasmInternalFunction::FromExternal(obj, isolate).ToHandleChecked();

View File

@ -1164,6 +1164,9 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
} else if (string->StringEquals(v8_str(isolate, "externref"))) {
// externref is known as anyref as of wasm-gc.
type = i::wasm::kWasmAnyRef;
} else if (enabled_features.has_stringref() &&
string->StringEquals(v8_str(isolate, "stringref"))) {
type = i::wasm::kWasmStringRef;
} else {
thrower.TypeError(
"Descriptor property 'element' must be a WebAssembly reference type");
@ -1222,6 +1225,24 @@ void WebAssemblyTable(const v8::FunctionCallbackInfo<v8::Value>& args) {
for (uint32_t index = 0; index < static_cast<uint32_t>(initial); ++index) {
i::WasmTableObject::Set(i_isolate, table_obj, index, element);
}
} else if (initial > 0) {
switch (table_obj->type().heap_representation()) {
case i::wasm::HeapType::kString:
thrower.TypeError(
"Missing initial value when creating stringref table");
return;
case i::wasm::HeapType::kStringViewWtf8:
thrower.TypeError("stringview_wtf8 has no JS representation");
return;
case i::wasm::HeapType::kStringViewWtf16:
thrower.TypeError("stringview_wtf16 has no JS representation");
return;
case i::wasm::HeapType::kStringViewIter:
thrower.TypeError("stringview_iter has no JS representation");
return;
default:
break;
}
}
v8::ReturnValue<v8::Value> return_value = args.GetReturnValue();
return_value.Set(Utils::ToLocal(i::Handle<i::JSObject>::cast(table_obj)));
@ -2072,6 +2093,19 @@ void WebAssemblyTableGet(const v8::FunctionCallbackInfo<v8::Value>& args) {
return;
}
if (receiver->type() == i::wasm::kWasmStringViewWtf8) {
thrower.TypeError("stringview_wtf8 has no JS representation");
return;
}
if (receiver->type() == i::wasm::kWasmStringViewWtf16) {
thrower.TypeError("stringview_wtf16 has no JS representation");
return;
}
if (receiver->type() == i::wasm::kWasmStringViewIter) {
thrower.TypeError("stringview_iter has no JS representation");
return;
}
i::Handle<i::Object> result =
i::WasmTableObject::Get(i_isolate, receiver, index);
if (result->IsWasmInternalFunction()) {

View File

@ -618,6 +618,10 @@ struct WasmTable {
if (!type.is_object_reference()) return false;
HeapType heap_type = type.heap_type();
return heap_type == HeapType::kFunc || heap_type == HeapType::kAny ||
heap_type == HeapType::kString ||
heap_type == HeapType::kStringViewWtf8 ||
heap_type == HeapType::kStringViewWtf16 ||
heap_type == HeapType::kStringViewIter ||
(module != nullptr && heap_type.is_index() &&
module->has_signature(heap_type.ref_index()));
}

View File

@ -381,6 +381,10 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
switch (table->type().heap_representation()) {
case wasm::HeapType::kAny:
case wasm::HeapType::kString:
case wasm::HeapType::kStringViewWtf8:
case wasm::HeapType::kStringViewWtf16:
case wasm::HeapType::kStringViewIter:
entries->set(entry_index, *entry);
return;
case wasm::HeapType::kFunc:
@ -390,10 +394,6 @@ void WasmTableObject::Set(Isolate* isolate, Handle<WasmTableObject> table,
case wasm::HeapType::kData:
case wasm::HeapType::kArray:
case wasm::HeapType::kI31:
case wasm::HeapType::kString:
case wasm::HeapType::kStringViewWtf8:
case wasm::HeapType::kStringViewWtf16:
case wasm::HeapType::kStringViewIter:
// TODO(7748): Implement once we have struct/arrays/i31ref/string tables.
UNREACHABLE();
case wasm::HeapType::kBottom:
@ -427,6 +427,10 @@ Handle<Object> WasmTableObject::Get(Isolate* isolate,
switch (table->type().heap_representation()) {
case wasm::HeapType::kAny:
case wasm::HeapType::kString:
case wasm::HeapType::kStringViewWtf8:
case wasm::HeapType::kStringViewWtf16:
case wasm::HeapType::kStringViewIter:
return entry;
case wasm::HeapType::kFunc:
if (entry->IsWasmInternalFunction()) return entry;
@ -2268,7 +2272,22 @@ bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
DCHECK(expected.is_reference());
switch (expected.kind()) {
case kOptRef:
if (value->IsNull(isolate)) return true;
if (value->IsNull(isolate)) {
HeapType::Representation repr = expected.heap_representation();
switch (repr) {
case HeapType::kStringViewWtf8:
*error_message = "stringview_wtf8 has no JS representation";
return false;
case HeapType::kStringViewWtf16:
*error_message = "stringview_wtf16 has no JS representation";
return false;
case HeapType::kStringViewIter:
*error_message = "stringview_iter has no JS representation";
return false;
default:
return true;
}
}
V8_FALLTHROUGH;
case kRef: {
HeapType::Representation repr = expected.heap_representation();
@ -2316,6 +2335,19 @@ bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
}
return true;
}
case HeapType::kString:
if (value->IsString()) return true;
*error_message = "wrong type (expected a string)";
return false;
case HeapType::kStringViewWtf8:
*error_message = "stringview_wtf8 has no JS representation";
return false;
case HeapType::kStringViewWtf16:
*error_message = "stringview_wtf16 has no JS representation";
return false;
case HeapType::kStringViewIter:
*error_message = "stringview_iter has no JS representation";
return false;
default:
if (module == nullptr) {
*error_message =

View File

@ -232,4 +232,129 @@ function assertInvalid(fn, message) {
assertEquals('bar', instance.exports.get_stringref());
})();
// TODO(wingo): Test stringrefs in tables.
(function TestDefinedTables() {
let kSig_w_v = makeSig([], [kWasmStringRef]);
let kSig_v_w = makeSig([kWasmStringRef], []);
let kSig_x_v = makeSig([], [kWasmStringViewWtf8]);
let kSig_y_v = makeSig([], [kWasmStringViewWtf16]);
let kSig_z_v = makeSig([], [kWasmStringViewIter]);
let builder = new WasmModuleBuilder();
builder.addTable(kWasmStringRef, 1).exportAs('w');
builder.addTable(kWasmStringViewWtf8, 1).exportAs('x');
builder.addTable(kWasmStringViewWtf16, 1).exportAs('y');
builder.addTable(kWasmStringViewIter, 1).exportAs('z');
builder.addFunction("get_stringref", kSig_w_v)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprTableGet, 0,
]);
builder.addFunction("set_stringref", kSig_v_w)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprLocalGet, 0,
kExprTableSet, 0,
]);
builder.addFunction("get_stringview_wtf8", kSig_x_v)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprTableGet, 1,
]);
builder.addFunction("get_stringview_wtf16", kSig_y_v)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprTableGet, 2,
]);
builder.addFunction("get_stringview_iter", kSig_z_v)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprTableGet, 3,
]);
let instance = builder.instantiate()
assertEquals(null, instance.exports.get_stringref());
instance.exports.set_stringref('foo');
assertEquals('foo', instance.exports.get_stringref());
assertEquals('foo', instance.exports.w.get(0));
instance.exports.w.set(0, 'bar');
assertEquals('bar', instance.exports.w.get(0));
assertEquals('bar', instance.exports.get_stringref());
assertThrows(()=>instance.exports.get_stringview_wtf8(),
TypeError, "type incompatibility when transforming from/to JS");
assertThrows(()=>instance.exports.get_stringview_wtf16(),
TypeError, "type incompatibility when transforming from/to JS");
assertThrows(()=>instance.exports.get_stringview_iter(),
TypeError, "type incompatibility when transforming from/to JS");
for (let [table, type] of [[instance.exports.x, 'stringview_wtf8'],
[instance.exports.y, 'stringview_wtf16'],
[instance.exports.z, 'stringview_iter']]) {
let unsupportedGetMessage =
`WebAssembly.Table.get(): ${type} has no JS representation`;
let unsupportedSetMessage =
'WebAssembly.Table.set(): Argument 1 is invalid for table of type '
+ `${type}ref`;
assertThrows(()=>table.get(0), TypeError, unsupportedGetMessage);
assertThrows(()=>{table.set(0, null);}, TypeError, unsupportedSetMessage);
}
})();
(function TestImportedTables() {
for (let type of ['stringview_wtf8', 'stringview_wtf16',
'stringview_iter']) {
let msg = "WebAssembly.Table(): Descriptor property 'element' must be" +
" a WebAssembly reference type";
assertThrows(()=>new WebAssembly.Table({ element: type, initial: 1 }),
TypeError, msg);
assertThrows(()=>new WebAssembly.Table({ element: type, initial: 1 },
null),
TypeError, msg);
}
assertThrows(()=>new WebAssembly.Table({ element: 'stringref', initial: 1 }),
TypeError,
"WebAssembly.Table(): " +
"Missing initial value when creating stringref table");
let kSig_w_v = makeSig([], [kWasmStringRef]);
let kSig_v_w = makeSig([kWasmStringRef], []);
let builder = new WasmModuleBuilder();
builder.addImportedTable('env', 't', 0, undefined, kWasmStringRef);
builder.addFunction("get_stringref", kSig_w_v)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprTableGet, 0,
]);
builder.addFunction("set_stringref", kSig_v_w)
.exportFunc()
.addBody([
kExprI32Const, 0,
kExprLocalGet, 0,
kExprTableSet, 0,
]);
let t = new WebAssembly.Table({ element: 'stringref', initial: 1 },
null);
let instance = builder.instantiate({env: {t: t}})
assertEquals(null, instance.exports.get_stringref());
instance.exports.set_stringref('foo');
assertEquals('foo', instance.exports.get_stringref());
assertEquals('foo', t.get(0));
t.set(0, 'bar');
assertEquals('bar', t.get(0));
assertEquals('bar', instance.exports.get_stringref());
})();

View File

@ -35,8 +35,7 @@ for (let [name, code] of [['string', kWasmStringRef],
assertValid(b => b.addArray(code, true));
assertValid(b => b.addType(makeSig([], [code])));
assertValid(b => b.addGlobal(code, true, default_init));
// TODO(wingo): Table of strings not yet implemented.
// assertValid(b => b.addTable(code, 0));
assertValid(b => b.addTable(code, 0));
assertValid(b => b.addPassiveElementSegment([default_init], code));
assertValid(b => b.addTag(makeSig([code], [])));
assertValid(