-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for WebAssembly GC recursion groups #740
Merged
webkit-early-warning-system
merged 1 commit into
WebKit:main
from
takikawa:eng/Wasm-GC-Add-support-for-recursion-groups
Aug 16, 2022
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
//@ runWebAssemblySuite("--useWebAssemblyTypedFunctionReferences=true", "--useWebAssemblyGC=true") | ||
|
||
import * as assert from "../assert.js"; | ||
import { compile, instantiate } from "./wast-wrapper.js"; | ||
|
||
function module(bytes, valid = true) { | ||
let buffer = new ArrayBuffer(bytes.length); | ||
let view = new Uint8Array(buffer); | ||
for (let i = 0; i < bytes.length; ++i) { | ||
view[i] = bytes.charCodeAt(i); | ||
} | ||
return new WebAssembly.Module(buffer); | ||
} | ||
|
||
function testRecDeclaration() { | ||
instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(func (type 0)) | ||
) | ||
`); | ||
|
||
assert.throws( | ||
() => compile(` | ||
(module | ||
(rec (type (struct)) (type (func))) | ||
(func (type 0)) | ||
) | ||
`), | ||
WebAssembly.CompileError, | ||
"type signature was not a function signature" | ||
); | ||
|
||
instantiate(` | ||
(module | ||
(rec | ||
(type (func (result (ref 1)))) | ||
(type (func (result (ref 0))))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec | ||
(type (func)) | ||
(type (func (result (ref 0))))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(rec (type (func)) (type (struct))) | ||
(elem declare funcref (ref.func 0)) | ||
(func (type 0)) | ||
(func (result (ref 2)) (ref.func 0)) | ||
) | ||
`); | ||
|
||
{ | ||
let m1 = instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(func (export "f") (type 0)) | ||
) | ||
`); | ||
instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(func (import "m" "f") (type 0)) | ||
(start 0) | ||
) | ||
`, { m: { f: m1.exports.f } }); | ||
} | ||
|
||
{ | ||
let m1 = instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(func (export "f") (type 0)) | ||
) | ||
`); | ||
assert.throws( | ||
() => instantiate(` | ||
(module | ||
(rec (type (struct)) (type (func))) | ||
(func (import "m" "f") (type 1)) | ||
(start 0) | ||
) | ||
`, { m: { f: m1.exports.f } }), | ||
WebAssembly.LinkError, | ||
"imported function m:f signature doesn't match the provided WebAssembly function's signature" | ||
); | ||
} | ||
|
||
{ | ||
let m1 = instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(elem declare funcref (ref.func 0)) | ||
(func) | ||
(func (export "f") (result (ref 0)) (ref.func 0)) | ||
) | ||
`); | ||
assert.throws( | ||
() => instantiate(` | ||
(module | ||
(rec (type (struct)) (type (func))) | ||
(func (import "m" "f") (type 1)) | ||
) | ||
`, { m: { f: m1.exports.f } }), | ||
WebAssembly.LinkError, | ||
"imported function m:f signature doesn't match the provided WebAssembly function's signature" | ||
); | ||
} | ||
|
||
assert.throws( | ||
() => compile(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(rec (type (struct)) (type (func))) | ||
(global (ref 0) (ref.func 0)) | ||
(func (type 3)) | ||
) | ||
`), | ||
WebAssembly.CompileError, | ||
"Global init_expr opcode of type Ref doesn't match global's type Ref" | ||
); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func (result (ref 1)))) | ||
(type (func (result (ref 0))))) | ||
(elem declare funcref (ref.func 0)) | ||
(elem declare funcref (ref.func 1)) | ||
(func (type 0) (ref.func 1)) | ||
(func (type 1) (ref.func 0)) | ||
) | ||
`); | ||
|
||
assert.throws( | ||
() => compile(` | ||
(module | ||
(rec (type (func (result (ref 1)))) | ||
(type (func (result (ref 0))))) | ||
(elem declare funcref (ref.func 0)) | ||
(elem declare funcref (ref.func 1)) | ||
(func (type 0) (ref.func 1)) | ||
(func (type 1) (ref.func 1)) | ||
) | ||
`), | ||
WebAssembly.CompileError, | ||
"control flow returns with unexpected type. Ref is not a Ref, in function at index 1" | ||
); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func (param i32))) (type (struct))) | ||
(elem declare funcref (ref.func 0)) | ||
(func (type 0)) | ||
(func (call_ref (i32.const 42) (ref.func 0))) | ||
(start 1) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func (result i32))) (type (struct))) | ||
(rec (type (struct)) (type (func (result i32)))) | ||
(func (type 0) | ||
(block (type 3) | ||
(i32.const 42))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func (result i32))) (type (struct))) | ||
(rec (type (struct)) (type (func (result i32)))) | ||
(func (type 0) | ||
(loop (type 3) | ||
(i32.const 42))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func (result i32))) (type (struct))) | ||
(rec (type (struct)) (type (func (result i32)))) | ||
(func (type 0) | ||
(i32.const 1) | ||
(if (type 3) (then (i32.const 42)) (else (i32.const 43)))) | ||
) | ||
`); | ||
|
||
instantiate(` | ||
(module | ||
(rec (type (func)) (type (struct))) | ||
(table 5 funcref) | ||
(elem (offset (i32.const 0)) funcref (ref.func 0)) | ||
(func (type 0)) | ||
(func (call_indirect (type 0) (i32.const 0))) | ||
(start 1) | ||
) | ||
`); | ||
|
||
// Ensure implicit rec groups are accounted for, and treated | ||
// correctly with regards to equality. | ||
instantiate(` | ||
(module | ||
(type $a (struct (field i32))) | ||
(rec (type $b (struct (field i32)))) | ||
(type $c (struct (field i32))) | ||
|
||
(func (result (ref null $a)) (ref.null $b)) | ||
(func (result (ref null $a)) (ref.null $c)) | ||
(func (result (ref null $b)) (ref.null $a)) | ||
(func (result (ref null $b)) (ref.null $c)) | ||
(func (result (ref null $c)) (ref.null $a)) | ||
(func (result (ref null $c)) (ref.null $b))) | ||
`); | ||
|
||
// This is the same test as above, but using a particular binary encoding. | ||
// The encoding for this test specifically uses both shorthand and the full | ||
// rec form to test the equivalence of the two. | ||
new WebAssembly.Instance(module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x9e\x80\x80\x80\x00\x06\x5f\x01\x7f\x00\x4f\x01\x5f\x01\x7f\x00\x5f\x01\x7f\x00\x60\x00\x01\x6c\x00\x60\x00\x01\x6c\x01\x60\x00\x01\x6c\x02\x03\x87\x80\x80\x80\x00\x06\x03\x03\x04\x04\x05\x05\x0a\xb7\x80\x80\x80\x00\x06\x84\x80\x80\x80\x00\x00\xd0\x01\x0b\x84\x80\x80\x80\x00\x00\xd0\x02\x0b\x84\x80\x80\x80\x00\x00\xd0\x00\x0b\x84\x80\x80\x80\x00\x00\xd0\x02\x0b\x84\x80\x80\x80\x00\x00\xd0\x00\x0b\x84\x80\x80\x80\x00\x00\xd0\x01\x0b")); | ||
} | ||
|
||
testRecDeclaration(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is wrong. I believe all wasm callees are still FunctionSignatures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT this is required due to the semantics of recursion groups. The reason is that with the addition of recursion groups, in general all type signatures have to be compared by projection, even if you know it will be a function signature underneath.
The reason is you may have function types like the following:
Even though
$f1
and$f2
look the same, and are indeed the same if you unfold the projections, they are not equal types because they are in different recursion groups.(it seems a bit silly in this very simple case, but you can construct more interesting examples where
$f1
and$f2
would unfold to the same type that, say, reference another member of the recursion group. And where these other members are not equal types.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kmiller68 was trying to explain this to me, so I am wondering if I can ask few clarifying questions:
Types are stored as instances of TypeInformation, and expanded to produce a FunctionSignature (including for struct types)?
Projection in this case means taking a TypeDefinition, and selecting an element of it without any substitution? So:
$r1 is given a type index
$s1 is represented by TypeDefinition(StructType(Projection($r1, 0)))
$tree is TypeDefinition(Projection($r1, 0))
Expanding replaces a projection with a new projection that has had one step of unrolling applied, with the recursion group as the target of the unrolling
Unrolling is applying one step of substitution, replacing references to the recursion group with its expansion. So:
expand($tree) <=>
unroll(Projection($r1, 0) with respect to $r1) <=>
TypeDefinition(StructType(i32), TypeDefinition(Projection($r1, 0)))
This new expansion is given its own type index, and comparisons are made by type index?
We need to compare types by type index because the type definitions are iso-recursive? So a TypeDefinition and its .expand() are not the same type?
If they were equirecursive, then we would have to perform a dfs expanding until we could prove that the types did/did not match?
Sorry to bug you with all these questions, I will continue playing with this patch to see how many of these questions I can answer by myself.