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

Suggestion: simpler shared functions #56

Open
dead-claudia opened this issue Oct 19, 2024 · 0 comments
Open

Suggestion: simpler shared functions #56

dead-claudia opened this issue Oct 19, 2024 · 0 comments

Comments

@dead-claudia
Copy link

Shared functions (and variables) could be the solution to the problem of methods, and if done correctly, could avoid most of the complexity of binding stuff to realms. They'd be heavily restricted, of course.

  • Shared functions would be declared via the shared keyword, much like async functions.
  • Shared variables would be declared like shared var, shared let, shared const, and shared using.
  • Shared functions inherit from SharedFunction.prototype, isolating its linkage fully from unshared data.
  • Shared functions, outside their body, can only access shared variables and properties of the global object.
    • The global object reference is that of the calling context's realm. This not only simplifies the calling sequence, but it also makes shared functions independently useful as a security primitive with realms.
  • Shared functions can be async, in which they use the calling context's realm to get the right Promise reference to construct and return.
  • Shared variables have the same read and write rules as shared struct members.
  • Shared structs' prototypes are themselves either null or shared structs. And yes, shared structs can extend other shared structs.
    • As per usual, SharedStruct.prototype.constructor === SharedStruct.
  • Shared struct constructors, static methods, and instance methods are implicitly shared. If you want to do things that can't be shared, it can't be in a shared struct definition.
  • Shared constants and read-only shared structs' properties do not require unsafe. Shared structs and their instance prototypes are themselves read-only, to improve ergonomics.
  • Built-in ES methods are only shared if explicitly stated as such.
  • To ease certain messaging use cases, registered struct Type and a registered "name" struct Type declaration exists to register a struct so that shared structs from other contexts can be matched to their local counterparts. A name can only be registered once, and a shared struct can only be registered once.
    • The name is configurable so minifiers and bundlers can still rename them and not either blow up bundle size or awkwardly dance around stuff.
    • Atomics.registeredKeyFor(Type) and Atomics.registeredTypeFor("Type") can inspect this registry.

Things shared variables and shared struct properties can be set to:

  • Shared functions
  • Shared arrays
  • Shared structs
  • Non-object values

When the shadow realms proposal comes around, those can also be added to this list, provided its methods employ the requisite locking to ensure it only runs on one calling agent at a time.

This should hopefully bring the flexibility needed. And with this API, I also propose the following modified API:

declare namespace Atomics {
    // same as before, including other new extensions...

    // New structs, all registered with internal names in reality
    export registered internal struct UnlockToken {
        constructor()
        get locked(): boolean
        unlock(): undefined
        [Symbol.dispose](): undefined
    }

    export registered internal struct Mutex {
        constructor()
        lock(token?: UnlockToken): UnlockToken
        lockIfAvailable(timeout: number, token?: UnlockToken): UnlockToken
    }

    export registered internal struct Condition {
        constructor()
        wait(token: UnlockToken): undefined
        waitFor(token: UnlockToken, timeout: number, predicate?: () => boolean): undefined
        notify(count?: number): undefined
    }
}

An example counter object might look like this:

shared struct Counter {
    value = 0
    inc() {
        let value = Atomics.load(this, "value")
        while (true) {
            let prev = Atomics.compareExchange(this, "value", value, value + 1)
            if (prev === value) return value + 1
            value = prev
        }
    }
}
StructRegistry.add(internalToken, Atomics.Mutex)

The code from https://github.com/tc39/proposal-structs/blob/main/ATTACHING-BEHAVIOR.md#coordinating-identity-continuity-among-workers could look like this instead:

// Inside worker A:
shared struct SharedThing {
  x;
  y;

  foo() {
    // Dynamically scoped to the caller's global, thus it always "just works"
    console.log("do a foo");
  }
};
let thing1 = new SharedThing;

workerB.postMessage(thing1);

// Inside worker B:
onmessage = (thing) => {
  // undefined is not a function, and `SharedThing` didn't even need to be defined!
  thing.foo();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant