Thinking through Hoot FFI code generation

Thinking through what something like a groveller would look like, except instead helping to automate js<->hoot FFI.

Something like this seems to be totally ok:

await Scheme.load_main("game.wasm", {}, 
{
...
  js: {
    setObj: (obj, key, val) => ({
      ...obj,
      key: val
    }),
    objVal: (obj, key) => obj[key],
    emptyObj: () => ({}),
    method: (obj, key, ...args) => obj[key](...args),
    setter: (obj, key, val) => (obj[key] = val),
    constructor: (key, ...args) => new classes[key](...args),
    functions: (key, ...args) => new funcs[key](...args),
  },
...
})

Where classes and funcs is either something you handmake, or generate:

import * as THREE from "three";

const classes = {
  Scene: THREE.Scene,
  Vector2: THREE.Vector2,
};

After this, we can define anything we want in scheme. I am trying out just organizing things out by function signature, to reduce the amount of define-foreigns I have to do.

(define-syntax define-foreign-constructor
  (syntax-rules ()
    ((_  name ptype ...)
     (define-foreign name "js" "constructor"
       (ref string) ptype ... -> (ref extern)))))

(define-syntax define-foreign-method
  (syntax-rules (->)
    ((_  name ptype ... -> rtype)
     (define-foreign name "js" "method"
       (ref string) ptype ... -> rtype))))

(define-foreign-constructor constructor)
(define-foreign-constructor constructor-f64-f64 f64 f64)
(define-foreign-method method->f64 -> f64)


(define (make-scene) (constructor "Scene"))
(define (make-vector-2 x y) (constructor-f64-f64 "Vector2" x y))
(define (get-angle) (method->f64 "angle"))
;; and so on

This all makes it straightforward I think for a possible code generation. I have made some inroads already just using the Typescript Compiler API and generating files for hoot based off of type declaration files. Just classes/methods right now, but if it seems feasible I am sure this way could be extended to making record types from interfaces and such.

But I suspect this could probably be more flexibly done on the guile side though, where define-foreigns are only made as needed for their type (method/setter/constructor) and signature. Parsing a full library’s worth of API can produce a lot of guile code and really tick up your compile time. This is true even when any method across classes with the same name and signature is deduplicated. I am still learning about what’s even possible right now with macros in hoot (and also kind of a noob to scheme-type macros).

Hoping we could eventually get something like emscripten’s ccall, but for this wasm->js boundary.

2 Likes

We are planning to add a higher-level API for binding to JS specifically (define-foreign is generic for any wasm host) that will generate the necessary define-foreign code on the Scheme side and accumulate the necessary imports on the JS side. This will allow for the composition of many third-party libraries which at the moment has to be done manually. Kotlin is probably the language to look to for inspiration here. We also need to add tree-shaking of wasm imports to cull everything that isn’t used. The GitLab issue for this is Generate JS for FFI imports (#159) · Issues · spritely / Guile Hoot · GitLab

Taking it a step further, we could then generate bindings for all the web APIs by parsing the relevant WebIDL files. Kotlin does this. That’s a ways off for Hoot, though.

1 Like