WebAssembly / interface-types

Other
641 stars 57 forks source link

Binding type for dicts #3

Closed flagxor closed 5 years ago

flagxor commented 6 years ago

Passing in dicts is a common Web API pattern. We should consider making this efficient. We probably need to take into account the idea that several versions of the set of keys might exist.

lukewagner commented 6 years ago

In particular, while one would have the generic ability to create an object (through imported Object.create), store it into a Table<anyref>, and set properties on it (through imported Reflect.set), it might be useful to have the ability to efficiently express calls like foo({x:1, y:2}) by just passing the scalars (1, 2) since the implementation of the WebIDL binding might be able to take these scalars directly, avoiding object creation altogether.

One could imagine a compound argument/return host binding that says "create a dict with x set to the next i32 arg and y set to the next next i32 arg".

A rough sketch as to how this might look in C++ is:

// generated from .idl file:
template <class... Field> struct PASS_AS_SCALARS dict { ... };
template <class... Field> dict<FieldT...> make_dict(Field&&...);
struct WEBIDL_DICT_FIELD x { int32_t value; x(int32_t v) : value(v) {} };
struct WEBIDL_DICT_FIELD y { int32_t value; y(int32_t v) : value(v) {} };
template <class... Field> void WEBIDL_IMPORT foo(dict<Field...>);

// called like:
foo(make_dict(x(1), y(2)));

assuming WEBIDL_* macros map to some magic attribute syntax and PASS_AS_SCALARS ensures that dict values are passed as individual scalar fields in args instead of through linear memory. Something similar in C is probably achievable through fancy macros.

magcius commented 6 years ago

How would that approach work for optional keys? e.g. you can do canvas.getContext('webgl', { antialias: false }) or canvas.getContext('webgl', { alpha: false }). We could require that all arguments are passed explicitly in some sort of sorted key order, but I don't believe there's an equivalent value to undefined right now in WASM. (The lack of a value that maps to undefined or equivalent in host bindings should probably be covered somehow by this spec, anyway)

annevk commented 6 years ago

Would sorted key order still work if we add a key?

magcius commented 6 years ago

I misunderstood Luke's proposal. Something not clear to me in the spec is that you can import a binding multiple times with different annotations. Luke is suggesting some complex annotation syntax for dictionaries which corresponds to "construct a dictionary, the first argument is set to x, the second argument is set to y".

To handle optional keys, you import the binding multiple times with differing annotations.

This has the obvious downside that I can't specify whether a key is missing or not at runtime, but that might just be better off done by the Object.create / Reflect.set method.

lukewagner commented 6 years ago

@magcius Exactly right. If a dict is being used to implement named+optional parameters, then there should probably be a small static set of imported signatures that were used for a given underlying WebIDL method that takes a dictionary. And also right that, as fallback, one could generate the dictionary dynamically, at some runtime cost.

@annevk Here we are not relying on any sort of static order; the binding is mapping parameters to (a subset of) dictionary key names.

Just as a concrete example, if the foo in my above example had the WebIDL signature:

dictionary args { a:double, b:double, x:long, y:long, z:double }
void foo(args);

you could write the following C++ (as described in my first comment):

foo(make_dict(x(1), a(2.0));
foo(make_dict(a(1.0), y(2), b(3.0));

and you'd get two wasm imports:

(import "" "foo" (func $foo1 (param i32) (param f64)))
(import "" "foo" (func $foo2 (param f64) (param i32) (param f64)))

and each would have a host binding (making up .wat syntax on the fly, so bear with me):

(binding $foo1 (dictionary (key "x") (key "a")))
(binding $foo2 (dictionary (key "a") (key "y") (key "b")))

and this would basically capture what in JS you'd write as:

foo({x:1, a:2});
foo({a:1, y:2, b:3});
pchickey commented 5 years ago

Closing as out-of-date: these concepts don't map to the current proposal, which has evolved a lot since this issue was opened. Some relevant discussion continues in #61