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 support for exported classes #102

Open
mathe42 opened this issue Sep 29, 2021 · 4 comments
Open

add support for exported classes #102

mathe42 opened this issue Sep 29, 2021 · 4 comments
Assignees
Labels
enhancement New feature or request

Comments

@mathe42
Copy link
Contributor

mathe42 commented Sep 29, 2021

I currently look at how we can support classes.

Example:

Assemblyscript

export class example {
  constructor(private s: string) {}

  public getString() { return this.s }
}

In JS:

const e = new exports.example('answer: 42')
console.log(e.getString()) // "answer: 42"

This in combination with #64 would allow some cool things!

To do that I would wrap the full class in a JS-Class that calls the correspondig function by runtime and loader.

FinalizationRegistry

I want to do the following to get full garbage collection (in a wrapper function for the classes):

const classPtr = new exports.Foo()
exports.__pin(classPtr)
const foo = Foo.wrap(classPtr)

// We only need one of these for all classes
const registry = new FinalizationRegistry(ptr => {
   exports.__unpin(ptr)
});

registry.register(foo, classPtr);

This would allow full GarbageCollection for Browsers that support FinalizationRegistry (see https://caniuse.com/mdn-javascript_builtins_finalizationregistry). Without FinalizationRegistry I see no way to add this wrapping without memory leaks (any ideas?).

But in that case the developer can allways call __unpin so I think there is nothing to worry about.

@mathe42
Copy link
Contributor Author

mathe42 commented Sep 29, 2021

Or instead of wrapping useing a proxy.

@torch2424 torch2424 self-assigned this Sep 29, 2021
@torch2424 torch2424 added the enhancement New feature or request label Sep 29, 2021
@mathe42
Copy link
Contributor Author

mathe42 commented Sep 29, 2021

Got a small working prototype (all manual but can be automated):

Assemblyscript

export class Foo {
  constructor(public str: string) {}

  getString(): string {
    return this.str
  }
}

Host:

import * as AsBind from "/node_modules/as-bind/dist/as-bind.esm.js";

const asyncTask = async () => {
  const wasm = await fetch("/build/optimized.wasm").then(v => v.arrayBuffer());

  const asBindInstance = await AsBind.instantiate(wasm);

  let e = asBindInstance.exports

  // When not supported noop
  const pointerRegistry = FinalizationRegistry ? new FinalizationRegistry(ptr => {
    e.__unpin(ptr)
  }) : {register(){}};

  function createProxyClass(klass, definition) {
    const newConstructor = new Proxy(klass, {
      construct(target, args) {
        // TODO: wrap args - get from definition
        const ptr = new target(...args)
        return newConstructor.wrap(ptr)
      },
      get(_, prop) {
        if (prop === 'wrap') {
          return (ptr) => {
            e.__pin(ptr)

            const instance = klass.wrap(ptr)

            const a = new Proxy({}, {
              ownKeys() {
                return Reflect.ownKeys(instance);
              },
              defineProperty() {
                throw new Error("Not allowed!")
              },
              deleteProperty() {
                throw new Error("Not allowed!")
              },
              get(_, ...args) {
                if (prop === '__collect') {
                  return () => e.__unpin(ptr)
                }
                const value = Reflect.get(instance, ...args)
                // TODO: wrap function - or wrap value
                return value
              },
              set(_, ...args) {
                // TODO: check if is settable; wrap value if needed
                return Reflect.set(instance, ...args)
              },
              setPrototypeOf() {
                throw new Error("Not allowed!")
              },
              preventExtensions() {
                throw new Error("Not allowed!")
              },
              isExtensible() {
                return false;
              },
              has(_, p) {
                return Reflect.has(instance, p)
              },
              getPrototypeOf() {
                throw new Error("Not allowed!")
              }
            })

            // handle GC
            pointerRegistry.register(a, ptr)

            return a
          }
        }
        return undefined
      },
      set() {
        throw new Error("Not allowed!")
      }
    })

    return newConstructor
  }


  window.e = e
  window.Foo = createProxyClass(e.Foo, {
    constructor: {
      memberType: "constructor",
      parameterTypes: ["~lib/string/String"],
      returnType: null
    },
    str: {
      memberType: "field",
      set: true,
      get: true,
      type: "~lib/string/String"
    },
    getString: {
      memberType: "function",
      parameterTypes: [],
      returnType: "~lib/string/String"
    }
  })

  // example usage
  const sptr = e.__newString("test")
  const f = new Foo(sptr)
  console.log(e.__getString(f.getString())) // "test"
  const sptr2 = e.__newString("test 42")
  f.str = sptr2
  console.log(e.__getString(f.getString())) // "test 42"
};
asyncTask();

@mathe42
Copy link
Contributor Author

mathe42 commented Sep 29, 2021

@torch2424 If this looks good to you I will integrate it. :D

@torch2424
Copy link
Owner

@mathe42 Thank you very much for drafting up this implementation! 😄 Yeah! I like this approach and it makes sense to me, so let's go for it! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants