Skip to content
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 detector for type reflection #67

Merged
merged 2 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A small library to detect which features of WebAssembly are supported.
- ✅ Tree-shakable (only bundle the detectors you use)
- ✅ Provided as an ES6, CommonJS and UMD module.
- ✅ CSP compatible
- ✅ All detectors add up to only ~680B gzipped
- ✅ All detectors add up to only ~700B gzipped

## Installation

Expand Down Expand Up @@ -71,6 +71,7 @@ All detectors return a `Promise<bool>`.
| `streamingCompilation()` | [Streaming Compilation](https://webassembly.github.io/spec/web-api/index.html#streaming-modules) |
| `tailCall()` | [Tail call](https://github.com/webassembly/tail-call) |
| `threads()` | [Threads](https://github.com/webassembly/threads) |
| `typeReflection()` | [Type Reflection](https://github.com/WebAssembly/js-types) |

## Why are all the tests async?

Expand Down
22 changes: 22 additions & 0 deletions src/detectors/type-reflection/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2023 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
;; Name: Type Reflection
;; Proposal: https://github.com/WebAssembly/js-types
;; Features: type-reflection
*/

export default async () => {
return "Function" in WebAssembly;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think this is enough. IINM, WebAssembly.Function is also expose by the JSPI proposal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe something like this could work?

return "Function" in WebAssembly && typeof WebAssembly["Function"]["type"] === "function" &&
    typeof WebAssembly.Memory.prototype["type"] === "function" &&
    typeof WebAssembly.Table.prototype["type"] === "function" &&
    typeof WebAssembly.Global.prototype["type"] === "function";

Doing some tests:

  • Chrome: Returns true with experimental wasm features enabled
  • Safari: Memory, Table and Global have the type() method, but it does not have WebAssembly.Function and functions exported from a WebAssembly.Instance do not have the type() method
  • Firefox: All conditions are false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me try it with d8 with various extra feature flags.

IINM, WebAssembly.Function is also expose by the JSPI proposal.

As I understand it, JSPI implies type reflection. E.g., passing --experimental-wasm-stack-switching --experimental-type-reflection is indistinguishable from just --experimental-wasm-stack-switching. To test this I made a little Python script:

I made feature_detect.js:

console.log(
  "Function" in WebAssembly,
  "Function" in WebAssembly &&
    typeof WebAssembly["Function"]["type"] === "function" &&
    typeof WebAssembly.Memory.prototype["type"] === "function" &&
    typeof WebAssembly.Table.prototype["type"] === "function" &&
    typeof WebAssembly.Global.prototype["type"] === "function"
);

and then tested with V8 version 11.5.69

$ v8 --experimental-wasm-type-reflection feature_detect.js
true true
$ v8 --experimental-wasm-stack-switching feature_detect.js
true true
$ v8 feature_detect.js
false false

For good measure I listed out all experimental flags with:

v8 --help | grep -o -E -- '--experimental[a-z0-9-]*' | sort -u

and checked that the script returns true true if either of these two flags is present and false false with any other collection of experimental flags. (Well I picked the other flags at random so I only tested a random sample.)

Test script
import subprocess
from random import randrange
from itertools import product
import sys

result = subprocess.run(
    "v8 --help | grep -o -E -- '--experimental-[a-z0-9-]*' | sort -u",
    shell=True,
    capture_output=True,
    encoding="utf8",
    check=True
)

experimental_flags = set([x for x in result.stdout.splitlines() if x])
experimental_flags.discard("--experimental-wasm-stack-switching")
experimental_flags.discard("--experimental-wasm-type-reflection")


def random_flags():
    return set(filter(lambda x: randrange(2), experimental_flags))


for [stack_switch, type_refl] in product([False, True], repeat=2):
    if stack_switch or type_refl:
        expected = "true true"
    else:
        expected = "false false"
    flags = []
    if stack_switch:
        flags.append("--experimental-wasm-stack-switching")
    if type_refl:
        flags.append("--experimental-wasm-type-reflection")
    for i in range(1000):
        rand_flags = random_flags()
        r = subprocess.run(
            ["/home/hood/.jsvu/v8", *flags, *rand_flags, "a.js"],
            capture_output=True,
            encoding="utf8",
        )
        if r.stdout.strip() != expected:
            from pprint import pprint
            pprint(flags + list(rand_flags))
            sys.exit(1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the moral of the story is that both approaches should do the same thing, at least on v8. Node is a similar story. I guess I should check also with firefox...

Anyways if you folks prefer, I am fine with switching to the more detailed check. I think we should add a comment though.

See also Emscripten's feature detection:

    // If the type reflection proposal is available, use the new
    // "WebAssembly.Function" constructor.
    // Otherwise, construct a minimal wasm module importing the JS function and
    // re-exporting it.
    if (typeof WebAssembly.Function == "function") {

https://github.com/emscripten-core/emscripten/blob/main/src/library_addfunction.js#L98-L105

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sbc100 do you have an opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I guess I may be overly focusing on v8 checks here...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the check as written seems like it should be fine.

JSPI depends on the type reflection proposal, but I don' think that needs to effect this test. JSPI itself doesn't provide this symbol.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahah: in firefox nightly v115.0a1 I get:

true false

but if I fix:

    typeof WebAssembly["Function"]["type"] === "function" &&

to

    typeof WebAssembly.Function.prototype["type"] === "function" &&

then I get true true again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Safari Version 16.3 (18614.4.6.1.6),

console.log(
  "Function" in WebAssembly,
  "Function" in WebAssembly &&
    typeof WebAssembly.Function.protype.type === "function",
    typeof WebAssembly.Memory.prototype["type"] === "function",
    typeof WebAssembly.Table.prototype["type"] === "function",
    typeof WebAssembly.Global.prototype["type"] === "function"
);

gives false false true true true as @juancastillo0 said.

In Safari Technology Preview Release 169 (Safari 16.4, WebKit 18616.1.12.2) I get the same thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks everyone! Sounds like we can leave this as is!

};