WebAssembly / interface-types

Other
641 stars 57 forks source link

General Interface-level helper functions #65

Open jgravelle-google opened 4 years ago

jgravelle-google commented 4 years ago

We started discussing this in the last video call. I believe it will be useful in a variety of contexts to have helper functions in the interface. I propose allowing something like:

(@interface func $foo
  ;; can take mixed wasm + interface type signature
  (param $ptr i32) (param $str string)
  (result string f32)
  ;; adapter instructions
)

Similar to declarations of imports+exports, but with a body.

Use cases + examples:

  1. Rather than specifying locals in adapter functions or stack-modifying instructions (dup, pick, et al), we can reuse function arguments as a natural scoping mechanism. We need to do something here to avoid duplicating function calls to exports when we want to reference a value twice.
    (@interface func $read-cstr (param $ptr i32) (result string)
      ;; Helper function to read a C char* from a single pointer
      arg.get $ptr
      arg.get $ptr
      call-export "strlen"
      read-utf8 "mem"
    )
    ;; C function: char* getFoo(void);
    (@interface func (export "getFoo") (result string)
      ;; Need some way to deduplicate the result pointer here, we can't
      ;; duplicate the call to getFoo
      call-export "getFoo"
      call $read-cstr
    )
  2. For call-defer-export, we could use helper function to defer arbitrary adapter instructions rather than only wasm exports.

    (@interface func $fooExport (; signature of foo ;)
      ;; can still call exports via helper functions
      call-export "foo"
    )
    (@interface func $doSomethingComplex
      ;; ...
    )
    (@interface func (export "doThing")
      ;; ...
      defer $fooExport
      ;; ...
      defer $doSomethingComplex
      ;; ...
    )
    
  3. For code compression, toolchains can encode sequences of instructions as a single call to a helper function (e.g. for structs with many fields)
    (@interface func $ptrToFoo (param $ptr i32) (result $Foo)
      (make-record $Foo
        (call-export "getFieldA" (arg.get $ptr))
        (call-export "getFieldB" (arg.get $ptr))
        (call-export "getFieldC" (arg.get $ptr))
        (call-export "getFieldD" (arg.get $ptr))
        ;; ...
      )
    )
  4. For dealing with sequences, higher-order functions like map, fold, read-until, etc. are likely to be useful for doing things more complex than copying buffers of primitive values. (This depends on the design of sequences.)
lukewagner commented 4 years ago

Thanks for the writeup and agreed that this seems useful and even, for item (4) in your list, necessary. One important detail is that these helper functions should by design be inlineable into the calling adapter function, so that no actual dynamic call stack is needed. In particular, this rules out mutual recursion (one trivial way to implement/specify this is by saying that an adapter function with index i can only call adapter functions with index i-1).

fitzgen commented 4 years ago

Great proposal and write up, thanks for laying out the motivation so clearly.