01:01
<rbuckton>
  • When A handshakes with M:
    • M is able to establish that a PointA should have a PointM prototype and it will apply to every PointA it receives, from anywhere, within the scope of M's Agent.
    • A is able to establish that a PointM should have a PointA prototype and it will apply to every PointM it receives, from anywhere, within the scope of A's Agent.
  • When B handshakes with M:
    • M is able to establish that a PointB should have a PointM prototype and it will apply to every PointB it receives, from anywhere, within the scope of M's Agent.
    • B is able to establish that a PointM should have a PointB prototype and it will apply to every PointM it receives, from anywhere, within the scope of B's Agent.

As such:

  • M will be able to find behavior for both rect1.topLeft and rect1.topRight, because the handshake between M-A and M-B established that.
  • B will not be able to find a behavior for rect1.topLeft because registries RA and RB are independent.
  • A will not be able to find a behavior for rect1.bottomRight because registries RA and RB are independent.
01:03
<rbuckton>

However, if you use the same registry RAB with A and B:

  • B is able to establish that a PointA should have a PointB prototype because the registry correlates both PointA and PointB with PointM.
  • A is able to establish that a PointB should have a PointA prototype because the registry correlates both PointA and PointB with PointM.
01:06
<rbuckton>
If such a prototype is initialized lazily in [[GetPrototypeOf]], by the time B can receive a PointA, or that A can receive a PointB, both agents will have completed their handshake with M, so all information is known. This is another reason why my proposal uses a preload script. The preload script performs the worker side of the handshake before any other data can be shared between the worker and M, so you cannot have a stray PointA sent to B, or PointB sent to A, prior to a completed handshake on both sides.
01:13
<rbuckton>
Now, we could theoretically have a global registry instead, with the structs: {} map only used to correlate PointM and PointA when A is established. Workers will always be part of a tree that points back to some root agent, so there's always a way to collect these things. If the handshake establishes the relationship without the ability to pass messages, would that be sufficient to address concerns about a global registry?
01:16
<rbuckton>
Especially if the worker can't actually observe the exemplar during handshake, since the handshake process is handled by the runtime. We wouldn't even need communicate the actual exemplars through the handshake process, just the type identities of the exemplars.
01:21
<rbuckton>
Though there is the caveat that M could try to pass off a PointA as an exemplar to B's Rect, but we could probably just make that an error, i.e. the exemplars M sends during the handshake must have been created by a type created in M's Agent
01:22
<rbuckton>
and the same thing goes for A (or B) spinning up a Worker (A2) during handshake and passing off one A2's exemplars as one of its own.
03:05
<Mathieu Hofman>

Right I think an agent based registry can only work if:

  • the internal agent wide registry is an association from type to local behavior definition
  • there is a unique connection registry between 2 agents, and preparing a connection registry mapping (as creator or as a worker setting up), associates a connection specific string to a locally defined type only
  • you can only populate the agent wide registry through connection registries.

that means a worker A connected to a worker B through M but not sharing the same connection registry will not be able share behavior throughout. I'm still wondering about the special parent - child relationship these connection based registries seem to have, and how you can only have one connection registry between 2 agents or things fall apart. I can't explain why exactly right now, but this all feels awkward.

12:38
<rbuckton>
I'm wondering if we even need a connection-based registry if we can devise a global registry strategy that addresses Agoric's concerns about security. You'd discussed how a mutable global registry is a possible side channel for data exfiltration? I'm curious how serious the concern is and if you have a link to a paper or something else that could provide additional context? Is the concern related to how a Worker could abuse such a global registry, or how a script or module in the same Agent could abuse such a registry?
16:32
<shu>

the two choices are:

  1. a shared struct instance's [[Prototype]] is a shared field and holds a shared struct, with nonshared fields, into which you assign methods
  2. a shared struct instance's [[Prototype]] is a nonshared field and points to a per-agent local struct
rbuckton: after chatting with some other V8 engineers i'm coming back to the idea that perhaps (2) is better
16:53
<Mathieu Hofman>
I'm wondering if we even need a connection-based registry if we can devise a global registry strategy that addresses Agoric's concerns about security. You'd discussed how a mutable global registry is a possible side channel for data exfiltration? I'm curious how serious the concern is and if you have a link to a paper or something else that could provide additional context? Is the concern related to how a Worker could abuse such a global registry, or how a script or module in the same Agent could abuse such a registry?
The concern has usually manifested itself in the form of Realm-wide or Agent-wide state, but it's conceivable that the same concern could manifest for Agent cluster-wide state. The problem is that such global mutable state allows 2 parties that do not share any references besides the primordials objects to communicate. In JavaScript today, you can freeze all the intrinsics, and it's not possible for 2 pieces of code to communicate unless they're explicitly provided a reference to each other, or to a shared mutable object.
16:55
<rbuckton>
rbuckton: after chatting with some other V8 engineers i'm coming back to the idea that perhaps (2) is better
Would this affect subclassing or no? I imagine in a subclassing case, we would just collect all of the shared fields up front and put them on the instance, much like we do for private fields today, so I don't imagine it would.
16:57
<shu>
rbuckton: that's not clear to me yet. one challenge here is how to express the thread-localness of a superclass
16:58
<shu>
we want the fixed layout invariant to hold, so do you say like "shared struct A extends per-agent B", but what is B? it could be itself a shared struct but its layout gets copied into a thread-local version of the struct the first time [[Prototype]] is accessed in a thread
16:58
<shu>
should it be a non-shared struct declaration?
16:59
<shu>
(but it gets that layout copy behavior)
17:26
<rbuckton>
The concern has usually manifested itself in the form of Realm-wide or Agent-wide state, but it's conceivable that the same concern could manifest for Agent cluster-wide state. The problem is that such global mutable state allows 2 parties that do not share any references besides the primordials objects to communicate. In JavaScript today, you can freeze all the intrinsics, and it's not possible for 2 pieces of code to communicate unless they're explicitly provided a reference to each other, or to a shared mutable object.
Do you imagine such communication is possible in this case?
17:30
<rbuckton>
Lets assume you can't use the exemplar values themselves to communicate, i.e., the actual exemplars aren't exposed to user code on the other Agent.
17:32
<rbuckton>
The child thread can't send or receive structs to the parent thread during handshake, and by the time handshake has completed all correlation between the parent and child is frozen.
17:34
<rbuckton>
By the time A can observe a struct from B, the correlation between M, A, and B has already occurred and is frozen. You cannot dynamically attach new behavior, but we do lazily resolve the prototype based on correlation.
17:40
<rbuckton>
Maybe there's a small possibility of a timing related exploit if I can somehow spin up multiple additional workers on M and send an existing corelated struct to A to indicate 0 and new correlated struct to A indicating 1 and somehow measure the timing? That might be mitigated if correlation happens before normal communication can occur and prototype lookup always follows the same path, but you could potentially use structs who have narrow and wide correlation sets and measure timing that way, or update an agent-local correlation registry when two agent's communicate for the first time so that you pay that cost once.
17:43
<rbuckton>
There are possibly other ways to mitigate that as well.
17:46
<rbuckton>
Within a single Agent, when worker's aren't involved, you wouldn't be able to use this registry for communication because it would be inaccessible. You can also use CSP to lock down Worker to specific scripts, or disable it entirely.
17:49
<rbuckton>
If Worker is locked down via CSP, the only way you could leverage these for a timing attack would be to be handed a reference to a shared struct, which I would argue qualifies for being provided a reference to a shared mutable object.
18:00
<rbuckton>
If you have two isolated pieces of code in the same Agent that both have access to an unrestricted Worker, its possible they could already communicate with each other via resource starvation and timing attacks.
19:06
<Mathieu Hofman>
For same realm/agent, if the registry is string keyed, Alice can register "foo". If Bob can somehow figure out that "foo" is already registered, this is a one bit communication channel. There are likely multiple ways Bob could sense whether "foo" is registered.
19:20
<rbuckton>
With the global registry concept, all registration within a single Agent would happen via new SharedStructType (or via shared struct Foo {}). No errors would be reported except for running out of heap space (and crashing). When setting up a Worker, there is a correlation step to correlate the registrations within both Agents, but this only occurs at the time of the Worker handshake and should only be observable by interacting with that Worker or a shared struct provided to the worker.
19:22
<rbuckton>
As far as I can tell, there's no way to observe that within a single Agent/realm. You can't check if something is "registered" because all "registration" happens before the thing you would test exists.
19:24
<rbuckton>
The only way to observe correlation would be to use a Worker and a shared reference, which still only observes correlation between those two Agents.
19:25
<rbuckton>
There should be no way to get at the registry itself, and the only way to establish correlation is to already have a reference to the shared struct type.
19:26
<rbuckton>
You could observe whether A and B share correlation with M, but only if you already have access to shared data from A and B
19:27
<rbuckton>
There would be no error upon registration, because there is no addressable identity to forge, nor a way to forge it. Every shared struct type would have its own type identity, defined at the time of creation.
19:56
<Mathieu Hofman>
I think it depends on how the global registry works, how it handles collisions? Any mechanism that uses a forgeable value as key is likely observable, whether it errors, or first / last win. In the latter case, as you mention starting a worker and asking it to send you that type, and seeing what behavior you get, yours or the other one registered in the same realm. I really cannot imagine any way where a registry with forgeable keys can be made unobservable. You do mention "no way to get at the registry itself", which instead sounds like design we were talking about yesterday, not an agent wide string keyed registry, but instead a connection based string-keyed mapper. I agree that it may be possible to make that work, but I think it requires the "correlation registry" between 2 agents to be unique and immutable after start.
19:57
<shu>
rbuckton: actually how do you think we can syntactically express the shape of a shared struct's prototype, if that prototype is to be fixed layout but per-thread?
19:57
<shu>
there's not a good precedent to fall back on in class syntax
20:01
<rbuckton>
rbuckton: actually how do you think we can syntactically express the shape of a shared struct's prototype, if that prototype is to be fixed layout but per-thread?
How important is it that the prototype be fixed shape, especially if we're not actually sharing the prototype around anywhere?
20:01
<shu>
it's not as important but i feel it is still important
20:03
<shu>
part of my mental model of structs (shared or not) over ordinary objects is "the shape doesn't change", and that transitively applies via the prototype chain
20:03
<rbuckton>
I think it depends on how the global registry works, how it handles collisions? Any mechanism that uses a forgeable value as key is likely observable, whether it errors, or first / last win. In the latter case, as you mention starting a worker and asking it to send you that type, and seeing what behavior you get, yours or the other one registered in the same realm. I really cannot imagine any way where a registry with forgeable keys can be made unobservable.
You do mention "no way to get at the registry itself", which instead sounds like design we were talking about yesterday, not an agent wide string keyed registry, but instead a connection based string-keyed mapper. I agree that it may be possible to make that work, but I think it requires the "correlation registry" between 2 agents to be unique and immutable after start.
What collisions? What is forgeable? The only thing user-provided is the correlation token used to explain what prototype to choose for a foreign struct within an Agent, and that only affects that Agent's view of the struct, not any other agent.
20:04
<rbuckton>
part of my mental model of structs (shared or not) over ordinary objects is "the shape doesn't change", and that transitively applies via the prototype chain
Way back when I'd thought to have structs act as value objects, my intuition was that the prototype would be looked up during ToObject just like we do for Number, String, etc. so it had no bearing on the shape of struct's runtime representation.
20:04
<shu>
yes, that is a competing model
20:05
<rbuckton>
That's not the case now, but I still don't find see the necessity for a fixed shape for the prototype.
20:05
<shu>
and i am open to be convinced of that competing model
20:05
<shu>
it has some attractive properties, like, the dynamism feels more at home with the rest of the language
20:05
<shu>
it has an exact parallel to primitive prototype wrapping, as you've pointed out
20:06
<rbuckton>
The caveat is that it doesn't translate well to multiple realms in the same Agent
20:06
<rbuckton>
Unless you need to somehow define behavior independently per realm.
20:07
<rbuckton>
Which would be another spanner to throw into the behavior assignment discussion :)
20:07
<shu>
the downside to the primitive-like wrapping model is i had harbored some hopes "fixed layout" would translate to "easy" static analyzability of static property access on struct instances
20:08
<shu>
but if for knowing the location s.p requires giving up if p is from the prototype, that's too bad
20:08
<shu>
it's not the end of the world or anything
20:09
<shu>
The caveat is that it doesn't translate well to multiple realms in the same Agent
eh, i don't think it's a big stretch to choose per-realm instead of per-agent. in the p95 case i imagine apps have 1 realm per agent
20:09
<shu>
i'm pretty neutral on whether to choose per-realm or per-agent. agent is not a notion we expose right now, but realms are, so that's more natural
20:10
<shu>
you end up with weird DX papercuts if you do work with multiple realms in the same agent, but i guess any app that works with multiple realms already must deal with identity discontinuity to some extent
20:11
<shu>
okay, let's continue the thought experiment down the path of relaxing the fixed layout constraint to not apply to nonshared prototypes
20:11
<shu>
how would you express that in syntax?
20:12
<shu>
and how would we take care to not preclude a future with actual shareable functions
20:12
<Mathieu Hofman>
What collisions? What is forgeable? The only thing user-provided is the correlation token used to explain what prototype to choose for a foreign struct within an Agent, and that only affects that Agent's view of the struct, not any other agent.
Ok so we're in the case of the agent pair having a string keyed correlation registry at initialization of the connection, which I agreed seems fine at first sight, but I still feel weird about, and need to think more about it.
20:13
<Mathieu Hofman>
Which would be another spanner to throw into the behavior assignment discussion :)
Yeah I've been pondering that one myself, but avoided bringing it up
20:13
<shu>
Mathieu Hofman: i take it you'd prefer per-realm over per-agent?
20:15
<rbuckton>
I'm not sure any handshake mechanism will work per-realm unless you have to establish the handshake when the realm is created, and you can't do that in the browser on the main thread with frames.
20:15
<shu>
ah i hadn't thought that far, that's what you meant by spanner
20:16
<Mathieu Hofman>
Well let's say I don't want this to enable a realm to discover the object graph of another realm, if they were previously isolated. I think that's my constraint
20:17
<shu>

to answer my own syntax question earlier, this could work:

shared struct class S {
  static nonshared prototype;
}
20:17
<shu>
since currently, having static prototype is an early error
20:18
<rbuckton>
That's a bit strange, and it doesnt seem like it would work well with method declarations.
20:19
<shu>
why wouldn't it work well with method declarations?
20:19
<rbuckton>
It looks a bit like a field declaration.
20:20
<shu>
(and to clarify, are you thinking of method declarations in the possible future where they are specially-packaged-and-cloned, or the possible future where we have some exotic new callable that's truly shared)
20:20
<rbuckton>
I'm considering both
20:21
<shu>
It looks a bit like a field declaration.
indeed. my thought it's "modifying" the field
20:21
<shu>
well, the internal slot
20:22
<rbuckton>
I need to think on that a bit.
20:22
<shu>
it's by all means just an incantation
20:22
<shu>
not a composable bit of syntax
20:22
<shu>
ideas welcome, certainly, most things i've thought of are even uglier
20:25
<rbuckton>

You want a syntax that:

  • allows you to opt in or out of sharing for struct (for non-shared structs)
  • allows you to opt in or out of sharing for instance fields (for shared structs)
  • allows you to opt in or out of sharing for prototype methods (for shared structs in a future where code sharing exists)
  • allows you to opt in or out of fixed layout for the prototype (for shared and non-shared structs)
  • allows you to opt in or out of sharing for the prototype (for shared structs)
  • maybe allows you to opt in or out of sharing for the constructor (for shared structs in a future where code sharing exists)
  • maybe even allows you to opt in or out of sharing for static methods and static fields (for shared structs in a future where code sharing exists)

Does that cover everything?

20:28
<shu>

that seems comprehensive

  • for the MVP, i think "allows you to opt in or out of sharing for instance fields (for shared structs)" can be scrapped if we go with opting in or out of making prototype itself nonshared. if you need to express agent-localness manually, you can use a WeakMap
  • i think "allows you to opt in or out of fixed layout for the prototype (for shared and non-shared structs)" shouldn't be a toggle but a choice we make. either we decide the fixed layout invariant extends transitively up the proto chain, or it's limited to instance layout only
20:29
<rbuckton>
If possible I'd like to not have to go indirectly through a WeakMap.
20:29
<shu>
for arbitrary fields?
20:29
<rbuckton>
I'd also like to find a way to allow shared private fields, even if that privacy is only agent-local.
20:30
<shu>
let's punt on private fields for now :)
20:30
<rbuckton>
I'm not sure if its feasible, but I'd like to find a way to consider it.
20:31
<shu>
a big part of the reason i've moved back to thinking thread-local prototype being the superior solution is the performance footgun aspect of heavy thread-local field usage
20:32
<rbuckton>
I'm writing a lot of shared structs using TypeScript's soft private currently.
20:32
<shu>
the performance will be so extremely different, yet looks the same
20:32
<shu>
if we bottleneck that thread-local lookup to be just on [[Prototype]], then we ease the performance footgun concerns
20:33
<shu>
the expressivity still exists with WeakMaps
20:34
<shu>
private names should just work, with the big exception of the lexical scoping of #-names
20:34
<shu>
so the per-agent privacy "just works" but that feels weird
20:36
<shu>
well no, "just works" is too strong. there will need to syntax changes to allow #-names to be scoped in such a way that allows it to even be expressed with struct declarations
20:37
<rbuckton>
The issue with private names is whether #foo is accessible inside of a nonshared method in two different threads.
20:37
<shu>
right
20:38
<rbuckton>
for it to be useful, it has to be. But that weakens privacy.
20:39
<rbuckton>

So you either need to:

  1. disallow privacy
  2. disallow access to private names from one thread in another thread
  3. weak privacy for shared structs
  4. provide a friendship mechanism that you can somehow share in a trusted manner when handshaking with a child thread.
20:41
<rbuckton>
I think (2) is unusable, I'm sure someone won't be happy with (3), and I don't have a solution for (4) yet.
20:41
<rbuckton>
nonshared private fields are definitely doable.
20:42
<rbuckton>
the performance will be so extremely different, yet looks the same
I'd still rather have them, even if we need a different or additional scary-sounding keyword in the field declaration.
20:43
<shu>
yeah perhaps
20:43
<shu>
i agree (2) will be unusable
20:44
<rbuckton>
For private names, we could make you explicitly annotate them as shared to get the point across that their privacy is weaker.
20:44
<shu>
the only solution that composes i can think of is some kind of new exotic callable that's threadsafe
20:44
<shu>
and that this new exotic callable can close over # names
20:45
<shu>
but it can't close over normal bindings
20:45
<shu>
nobody liked the exotic callable idea the first time i brought it up though
20:45
<rbuckton>
why would we need that?
20:45
<rbuckton>
And I'm not even sure how you'd use that
20:46
<shu>

the private names thing can fall out of that, what i had in mind:

shared struct class S {
  #x;
  shared getX() { return this.#x; } // <- new exotic callable, can't close normal bindings, [[Realm]]-less, etc
}
20:47
<rbuckton>
In the "weaker privacy" model I was thinking about, private names are part of the type identity associated with a shared struct, and the handshake process that provides correlation between an exemplar and a prototype could be smart enough to correlate the private name as well.
20:48
<shu>
in the evaluation of S above, #x gets evaluated once and is closed over by this new shareable exotic callable, and you use those methods on instances and things just work
20:48
<shu>
we can't do this with normal functions obvoiusly because they're not shared things
20:49
<rbuckton>

So you do:

// structs.js
export shared struct S {
  shared #x; // weak shared private name
  
  nonshared getX() {
    return this.#x; // private name access is correlated for foreign struct types.
  }
}

// preload.js
import { S } from "./structs.js";
prepareWorker({ structs: { S } });
20:49
<rbuckton>
You just have the private name itself be correlated.
20:50
<shu>
not sure i grasp how the correlation works
20:50
<rbuckton>
though that is a step towards always using shared struct declarations for handshaking rather than just an exemplar and a prototype.
20:50
<rbuckton>
I'll see if I can summarize?
20:56
<shu>
i have a harebrained worse-is-better idea
21:00
<rbuckton>
  • Each agent maintains an independent registry of the shared struct types created within the agent.
  • Each registry is linked bi-directionally to the agent that spawned the thread/agent.
  • When spinning up a new Worker, you provide a set of shared struct types to correlate with the worker. In previous discussions these were exemplar structs, but we only really need the type identity of the shared struct type.
  • When the worker starts up, it has a "preload" phase where it can handle its side of the handshake, and provide a set of shared struct types to correlate with the parent thread.
  • During preload, the child thread cannot otherwise communicate with the parent thread via the worker/message port (any postMessage/onmessage ends up queued until the handshake has finished).
  • In the agent registry, or in a global registry, you use this correlation information to establish how to treat any given shared struct in a foreign Agent.
  • When you look up the prototype of a non-local shared struct, you interrogate the registry for an agent-local prototype to use based on this correlation information.
  • By the time you can actually invoke [[GetPrototypeOf]], all information you would need to correlate and resolve the prototype of a given shared struct should be known to the system.
21:02
<shu>
i'm not clear on the second-to-last bullet point
21:02
<shu>
how does "look up the prototype of a non-local shared struct" differ from [[GetPrototypeOf]]?
21:04
<rbuckton>
The last two bullet points are mostly part of the same thing.
21:05
<shu>
are you saying every [[GetPrototypeOf]] of an instance has a pre-hook that does correlation in the registry?
21:05
<shu>
i was hoping you'd set up the correlation once per type and not incur a check on every [[GetPrototypeOf]]
21:05
<rbuckton>
There are two options. One is "when we create the thread local prototype Object for the foreign shared struct type we just received, we can correlate it in the registry"
21:08
<rbuckton>
The other is lazily on [[GetPrototypeOf]], but we don't need the laziness if the runtime can do all of this work for you.
21:10
<shu>

so here's my harebrained worse-is-better idea:

what if shared struct S { } declarations evaluated to something that has a special packageForClone(), which returns some object that can be reevaluated (like direct eval, but i guess safer?)

shared struct S {
  static nonshared prototype;

  // The static block gets packaged _as source text_.
  static {
    // Set up thread-local prototype
    this.prototype.method = function() { whatever; }
  }
}

let thing = S.packageForClone();
thing.evaluate(); // Get a constructor back that can create instances of the same layout. The VM knows it's correlated with `S`-instances. Re-evaluates the static block _from source text_ at the point of evaluation.
21:12
<shu>
the return value of packageForClone() would be special cased in the structured clone algorithm to be cloneable
21:15
<rbuckton>
I still don't think this works because it makes assumptions about what is reachable in the child thread.
21:16
<shu>
how so?
21:16
<rbuckton>
The child thread might be running from a bundle that doesn't include some module names, because the methods that use them were removed from the child thread bundle due to tree shaking.
21:17
<shu>
it's like re-evaling a function's toString(), no assumptions are made per se, but if things get DCE'd because the tool wasn't aware it's implicitly being used somehow, then the tool needs those exceptions annotated, yeah
21:17
<rbuckton>
And its likely that the child thread already has a copy of all of the necessary behavior in memory, so now we're taking up even more memory in the worker thread for duplicate code.
21:18
<shu>
how did it get the necessary behavior in memory, import? this idea means you never import the right structs, you gotta always postMessage them
21:18
<shu>
but agreed that doesn't feel great
21:19
<rbuckton>
I'm not a fan of that design, tbh. Its too easy for code to become entangled.
21:19
<rbuckton>
I'll be back in a bit, in a meeting for the next hour.
21:19
<shu>
the minimal version of this idea is that shared struct constructors ought to be made structured cloneable in such a way that the VM can keep the type identity correlation across agents
21:20
<shu>
they can be safely cloned because these constructors don't call user code
21:21
<shu>
i guess that minimal version can be combined with your registry handshake. it makes the correlation of type identities automatic
22:06
<rbuckton>
I'm not sure I agree with that approach? I might need my shared struct constructor to access some per-thread configured object that may not be trivially serializable, such as accessing threadId in import { threadId } from "node:worker_threads" or, read from an environment variable via sys.getEnvironmentVariable(name), where sys must be correctly initialized for within that thread.
22:06
<rbuckton>
And both of these cases are present in the work I'm doing right now.
22:07
<rbuckton>
and I definitely want to be able to run user code so that I can appropriately set up shared struct instances, including providing suitable defaults to match the types I've defined.
22:08
<shu>
i think we're talking about 2 constructors
22:08
<rbuckton>
Yes and no.
22:08
<shu>
shared structs don't have user code constructors (i now also see that the README.md is incorrect)
22:08
<rbuckton>
They don't currently, correct.
22:09
<shu>
they just have a way to objects of the correct layout, let's call this constructor the "minter"
22:09
<shu>
you can wrap this in a per-thread constructor that does thread-local things
22:09
<rbuckton>
Sure, but you're talking about packing in the prototype members along with that, which we don't do anywhere else in JS.
22:09
<shu>
sorry i switched gears
22:09
<shu>
scratch the prototype members idea
22:10
<shu>
the minimal version is: the minter, and the minter alone, with no transitive properties, is a cloneable function that can be cloned across worker boundaries
22:10
<rbuckton>
Ok, but then I don't see why serializing the constructor is useful.
22:10
<shu>
ah, because the VM can keep tabs under the hood that it's correlated with shared structs of a particular type
22:11
<shu>

but i guess this doesn't work for your approach because you want to be able to

  1. import { S } from some place
  2. then correlate them
22:11
<shu>

instead of

  1. receive { S } via message from some coordination thread
  2. set up S.prototype
22:11
<rbuckton>
Those are the same thing to me
22:12
<rbuckton>
Just different levels of abstraction
22:12
<shu>
they aren't to me, because "import { S } from some place" already evaluates and binds an S that we'd need to correlate after-the-fact
22:12
<shu>
where as "receive S via message" gets the right S beforehand with no addition coordination needed
22:13
<shu>
it's the difference between 2 copies that are correlated and 1 copy
22:13
<rbuckton>
Ok, fair. Then the issue I have with the 2nd approach is one of timing.
22:13
<shu>
right, there is a conceptual startup barrier for all workers
22:14
<rbuckton>
And some workers might want to be able to create instances of a shared struct type ahead of the handshake process or message or whatever
22:14
<rbuckton>
Because sometimes I need to set up singleton values or run code against objects that also happen to be shared.
22:14
<shu>
yeah, that style is explicitly unsupported, or at least will always need to be reordered after the handshake barrier
22:15
<rbuckton>
With the approach I've been suggesting, it doesn't.
22:15
<rbuckton>
I'm already doing that, sans behavior, currently.
22:15
<shu>
i still don't understand how the correlation works
22:15
<shu>
are you free? maybe we can hop on a 30 minute call and talk it through
22:15
<rbuckton>
Sure
22:15
<shu>
i'll DM