Commit 5d587693 authored by Andreas Haas's avatar Andreas Haas Committed by Commit Bot

[wasm][anyref] Implement sub-typing of anyref, anyfunc, and nullref

For the reference types anyref, anyfunc, and nullref, there exist
sub-typing rules. The spec says

A reference type reftype1 matches a reference type reftype2 if and only
if:

* Either both reftype1 and reftype2 are the same.
* Or reftype1 is nullref.
* Or reftype2 is anyref.

This CL introduces the type nullref for ref-null, and implements the
sub-typing rules in the function-body-decoder.

Note that because of the sub-typing check validation performance may
regresses. In that case we can optimize the sub-typing check.

R=titzer@chromium.org, clemensh@chromium.org

Bug: v8:7581
Change-Id: I55bab72a109f3374da3770d141b0fc8067aad8b2
Reviewed-on: https://chromium-review.googlesource.com/c/1430061
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarBen Titzer <titzer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59043}
parent 304e74c8
...@@ -1902,7 +1902,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -1902,7 +1902,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
} }
case kExprRefNull: { case kExprRefNull: {
CHECK_PROTOTYPE_OPCODE(anyref); CHECK_PROTOTYPE_OPCODE(anyref);
auto* value = Push(kWasmAnyRef); auto* value = Push(kWasmNullRef);
CALL_INTERFACE_IF_REACHABLE(RefNull, value); CALL_INTERFACE_IF_REACHABLE(RefNull, value);
len = 1; len = 1;
break; break;
...@@ -2563,9 +2563,16 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2563,9 +2563,16 @@ class WasmFullDecoder : public WasmDecoder<validate> {
return stack_.data() + old_size; return stack_.data() + old_size;
} }
V8_INLINE bool IsSubType(ValueType expected, ValueType actual) {
return (expected == actual) ||
(expected == kWasmAnyRef && actual == kWasmNullRef) ||
(expected == kWasmAnyRef && actual == kWasmAnyFunc) ||
(expected == kWasmAnyFunc && actual == kWasmNullRef);
}
V8_INLINE Value Pop(int index, ValueType expected) { V8_INLINE Value Pop(int index, ValueType expected) {
auto val = Pop(); auto val = Pop();
if (!VALIDATE(val.type == expected || val.type == kWasmVar || if (!VALIDATE(IsSubType(expected, val.type) || val.type == kWasmVar ||
expected == kWasmVar)) { expected == kWasmVar)) {
this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s", this->errorf(val.pc, "%s[%d] expected type %s, found %s of type %s",
SafeOpcodeNameAt(this->pc_), index, SafeOpcodeNameAt(this->pc_), index,
...@@ -2612,7 +2619,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2612,7 +2619,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
for (uint32_t i = 0; i < merge->arity; ++i) { for (uint32_t i = 0; i < merge->arity; ++i) {
Value& val = stack_values[i]; Value& val = stack_values[i];
Value& old = (*merge)[i]; Value& old = (*merge)[i];
if (val.type == old.type) continue; if (IsSubType(old.type, val.type)) continue;
// If {val.type} is polymorphic, which results from unreachable, make // If {val.type} is polymorphic, which results from unreachable, make
// it more specific by using the merge value's expected type. // it more specific by using the merge value's expected type.
// If it is not polymorphic, this is a type error. // If it is not polymorphic, this is a type error.
...@@ -2683,7 +2690,7 @@ class WasmFullDecoder : public WasmDecoder<validate> { ...@@ -2683,7 +2690,7 @@ class WasmFullDecoder : public WasmDecoder<validate> {
for (uint32_t i = 0; i < num_returns; ++i) { for (uint32_t i = 0; i < num_returns; ++i) {
auto& val = stack_values[i]; auto& val = stack_values[i];
ValueType expected_type = this->sig_->GetReturn(i); ValueType expected_type = this->sig_->GetReturn(i);
if (val.type == expected_type) continue; if (IsSubType(expected_type, val.type)) continue;
// If {val.type} is polymorphic, which results from unreachable, // If {val.type} is polymorphic, which results from unreachable,
// make it more specific by using the return's expected type. // make it more specific by using the return's expected type.
// If it is not polymorphic, this is a type error. // If it is not polymorphic, this is a type error.
......
...@@ -25,6 +25,7 @@ enum ValueType : uint8_t { ...@@ -25,6 +25,7 @@ enum ValueType : uint8_t {
kWasmS128, kWasmS128,
kWasmAnyRef, kWasmAnyRef,
kWasmAnyFunc, kWasmAnyFunc,
kWasmNullRef,
kWasmExceptRef, kWasmExceptRef,
kWasmVar, kWasmVar,
}; };
...@@ -341,6 +342,8 @@ class V8_EXPORT_PRIVATE ValueTypes { ...@@ -341,6 +342,8 @@ class V8_EXPORT_PRIVATE ValueTypes {
return "anyref"; return "anyref";
case kWasmAnyFunc: case kWasmAnyFunc:
return "anyfunc"; return "anyfunc";
case kWasmNullRef:
return "nullref";
case kWasmExceptRef: case kWasmExceptRef:
return "exn"; return "exn";
case kWasmS128: case kWasmS128:
......
...@@ -136,3 +136,65 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -136,3 +136,65 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
const main = builder.instantiate().exports.main; const main = builder.instantiate().exports.main;
assertEquals(null, main()); assertEquals(null, main());
})(); })();
(function testAssignNullRefToAnyFuncLocal() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_a_a);
builder.addFunction('main', sig_index)
.addBody([kExprRefNull, kExprSetLocal, 0, kExprGetLocal, 0])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main(main));
})();
(function testImplicitReturnNullRefAsAnyFunc() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_a_v);
builder.addFunction('main', sig_index)
.addBody([kExprRefNull])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
(function testExplicitReturnNullRefAsAnyFunc() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_a_v);
builder.addFunction('main', sig_index)
.addBody([kExprRefNull, kExprReturn])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
(function testImplicitReturnAnyFuncAsAnyRef() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_r_v);
builder.addFunction('main', sig_index)
.addLocals({anyfunc_count: 1})
.addBody([kExprGetLocal, 0])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
(function testExplicitReturnAnyFuncAsAnyRef() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_r_v);
builder.addFunction('main', sig_index)
.addLocals({anyfunc_count: 1})
.addBody([kExprGetLocal, 0, kExprReturn])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
...@@ -181,3 +181,27 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -181,3 +181,27 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
assertEquals(null, instance.exports.main()); assertEquals(null, instance.exports.main());
})(); })();
(function testImplicitReturnNullRefAsAnyRef() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_r_v);
builder.addFunction('main', sig_index)
.addBody([kExprRefNull])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
(function testExplicitReturnNullRefAsAnyRef() {
print(arguments.callee.name);
const builder = new WasmModuleBuilder();
const sig_index = builder.addType(kSig_r_v);
builder.addFunction('main', sig_index)
.addBody([kExprRefNull, kExprReturn])
.exportFunc();
const main = builder.instantiate().exports.main;
assertEquals(null, main());
})();
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