Bug 1819215 - wasm: Change subtyping rules for functions. r=rhunt

Previously, function subtyping was only checking the invariance.
Now, return types are covariant and arguments are contravariant.

Differential Revision: https://phabricator.services.mozilla.com/D175697
This commit is contained in:
Julien Pages 2023-04-24 16:05:59 +00:00
parent a49062ba28
commit 7390510f3f
3 changed files with 104 additions and 2 deletions

View File

@ -0,0 +1,79 @@
// |jit-test| skip-if: !wasmGcEnabled()
// Validate rules for function subtyping:
// - Same number of parameters and results
// - Function return types are covariant
// - Function parameter types are contravariant
wasmValidateText(
`(module
(type $A (struct (field i32)))
(type $B (sub $A (struct (field i32) (field i32))))
(type $C (sub $B (struct (field i32) (field i32) (field i64))))
(type $D (sub $B (struct (field i32) (field i32) (field f32))))
;; Same types (invariant)
(type $func1 (func (param (ref $A) (ref $A)) (result (ref $C))))
(type $func2 (sub $func1 (func (param (ref $A) (ref $A)) (result (ref $C)))))
;; Covariant return types are valid
(type $func3 (func (param (ref $A) (ref $A)) (result (ref $B))))
(type $func4 (sub $func3 (func (param (ref $A) (ref $A)) (result (ref $C)))))
(type $func5 (func (param (ref $A) (ref $A)) (result (ref $A) (ref $B))))
(type $func6 (sub $func5 (func (param (ref $A) (ref $A)) (result (ref $D) (ref $C)))))
;; Contravariant parameter types are valid
(type $func7 (func (param (ref $A) (ref $C)) (result (ref $C))))
(type $func8 (sub $func7 (func (param (ref $A) (ref $B)) (result (ref $C)))))
(type $func9 (func (param (ref $D) (ref $C)) (result (ref $C))))
(type $func10 (sub $func9 (func (param (ref $A) (ref $B)) (result (ref $C)))))
;; Mixing covariance and contravariance
(type $func11 (func (param (ref $D) (ref $C)) (result (ref $A))))
(type $func12 (sub $func11 (func (param (ref $A) (ref $B)) (result (ref $C)))))
)
`);
// Validate that incorrect subtyping examples are failing as expected
const typeError = /incompatible super type/;
var code =`
(module
(type $A (struct (field i32)))
(type $B (sub $A (struct (field i32) (field i32))))
(type $C (sub $B (struct (field i32) (field i32) (field i64))))
(type $D (sub $B (struct (field i32) (field i32) (field f32))))
;; Not the same number of arguments/results
(type $func1 (func (param (ref $A) (ref $A)) (result (ref $C))))
(type $func2 (sub $func1 (func (param (ref $A) (ref $A)) (result (ref $C) (ref $A)))))
)`;
wasmFailValidateText(code, typeError);
code =`
(module
(type $A (struct (field i32)))
(type $B (sub $A (struct (field i32) (field i32))))
(type $C (sub $B (struct (field i32) (field i32) (field i64))))
(type $D (sub $B (struct (field i32) (field i32) (field f32))))
;; Contravariant result types are invalid
(type $func3 (func (param (ref $A) (ref $A)) (result (ref $C))))
(type $func4 (sub $func3 (func (param (ref $A) (ref $A)) (result (ref $A)))))
)`;
wasmFailValidateText(code, typeError);
code =`
(module
(type $A (struct (field i32)))
(type $B (sub $A (struct (field i32) (field i32))))
(type $C (sub $B (struct (field i32) (field i32) (field i64))))
(type $D (sub $B (struct (field i32) (field i32) (field f32))))
;; Covariant parameters are invalid
(type $func5 (func (param (ref $A) (ref $A)) (result (ref $C))))
(type $func6 (sub $func5 (func (param (ref $B) (ref $A)) (result (ref $C)))))
)`;
wasmFailValidateText(code, typeError);

View File

@ -189,8 +189,31 @@ class FuncType {
// relationship.
static bool canBeSubTypeOf(const FuncType& subType,
const FuncType& superType) {
// Temporarily only support equality for function subtyping
return FuncType::strictlyEquals(subType, superType);
// A subtype must have exactly as many arguments as its supertype
if (subType.args().length() != superType.args().length()) {
return false;
}
// A subtype must have exactly as many returns as its supertype
if (subType.results().length() != superType.results().length()) {
return false;
}
// Function result types are covariant
for (uint32_t i = 0; i < superType.results().length(); i++) {
if (!ValType::isSubTypeOf(subType.results()[i], superType.results()[i])) {
return false;
}
}
// Function argument types are contravariant
for (uint32_t i = 0; i < superType.args().length(); i++) {
if (!ValType::isSubTypeOf(superType.args()[i], subType.args()[i])) {
return false;
}
}
return true;
}
bool canHaveJitEntry() const;