-
Notifications
You must be signed in to change notification settings - Fork 11
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
Guest shares host by default #50
Conversation
972e129
to
4ce940d
Compare
// its host's. | ||
// The constructor copies the own properties over its own globalThis by | ||
// assignment. | ||
globals: Object, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it resolve #38?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it expressly does not allow a virtualized global object. The globalThis
of compartment with a dedicated global object is an ordinary object with a null prototype.
We have in the past received feedback that engines covet the definition of global environment records and that we should not allow that behavior to be virtualized. We would benefit from a fresh confirmation if that’s still the case, cc @codehag, @syg.
We certainly do not need globalThis to be virtualizable, even for Hardened JavaScript, so we have not asked. However, even if we could pass globalThis
to a Loader
(née Compartment
) constructor, that doesn’t tell the entire story for global environment record and module environment records.
And, we would like to keep the door open if in the future, someone were to champion the use of Loader
(or Compartment
) for REPL-style evaluate
, where each Script can add new const
and let
bindings to the global contour (or whatever that’s actually called).
Closer to home, for Hardened JavaScript, it may be useful to introduce a scope that overshadows globalThis
for both Scripts and Modules that guest code can only access lexically and not discover by enumerating properties of globalThis
. We used a technique like this to prototype metering, which is similar in spirit to adding an instrumentation/coverage collector in scope. (We’ve since left that prototype behind since XS added native support for metering).
I think this change needs more care. We need to preserve a way for a Loader to borrow the host load hook and not borrow the static module record cache for Hot Module Replacement or other kinds of reloading, like one might see with a test watcher. |
Interesting considerations! Currently, reexport.js import x2 from './x.wasm' as 'module';
export { x2 }; a.js import x1 from './x.wasm' as 'module';
import { x2 } from './reexport.js';
x1 === x2; // true - module map retains identity On the other hand, if passing the module to a worker over structured clone, the identity would be broken while the underlying internal compilation can be shared. As for loaders inheriting the identity of compiled records of the parent host by default, could it not be possible to just explicitly do this passing of the compiled object instead, like permitting a static module reflection reference to be returned by the load hook? This seems like it would handle the same use case without needing to explicitly treat the registry systems as nested or iterated and copied? |
We need to figure out the identity issue for modules blocks as well. Here is a long thread on the topic: tc39/proposal-module-expressions#58 |
This change is not ready to land because it fails to adequately account for the case where a guest compartment needs a fresh static record memo (so it can reload) and also reuse the host’s loader’s load hooks. (I’ve made the mistake of conflating the effects of A: Share by default (which I suspect to be more common)
I think I would like to convince this group that option B is generally better, even though detached global by default will be surprising the to majority of users. On the other hand, I suspect that attaching the static module record by default will also have surprising negative effects. We could pursue a hybrid, but I know that my mind at least has a hobgoblin. Please let me know what you think and I’ll update this change to reflect the general favor of the champion group. |
It occurs to me, we could also reify |
This is basically how Node's experimental loader API works. In that it basically passes This API could do something similar i.e.: interface LoaderOptions {
loadHook: (
specifier: string,
defaultLoadHook: (specifier: string) => Promise<ModuleDescriptor>
) => Promise<ModuleDescriptor>;
// Ditto for resolveHook
} const loader = new Loader({
async loadHook(specifier, defaultLoadHook) {
// Delegate to host when needed
if (specifier.startsWith("node:")) {
return await defaultLoadHook(specifier);
}
// Load it myself
},
}); |
By way of contrast, the same is possible without revealing the host’s loadHook to guest code, and it’s possible to distinguish the desire to create a new instance or share an instance. const loader = new Loader({
async loadHook(specifier) {
// Delegate to host when needed
if (specifier.startsWith("node:")) {
return { instance: specifier };
}
// Load it myself
},
}); |
My understanding is that the global environment record (for evaluated scripts) and module environment records (for evaluated modules) will need to keep a marker to the intrinsic |
Sorry I still do not understand 😂 because you can pass the eval around, then how do you figure out if it should be run in the child compartment or the incubator compartment? For example, I have an intrinsic Then what will happen if I pass the |
And now I understand the nature of the question! And I find that I do not have an answer, short of abandoning the notion of Compartments with shared globals. I hope the brain trust will join me in pondering a solution. |
Are we talking about direct or indirect eval? Indirect eval should unquestionably be associated with that If we're talking direct eval, I see only two issues:
|
Is the idea that i.e. More specifically we would have an operations like GetFunctionRealm except called GetFunctionLoader. |
I'm jumping into the middle without having absorbed enough context first. I may be missing or misunderstanding the real question. |
It is perhaps not a fundamental hole in the notion of shared-globals, but at this moment I see no way that |
As @erights points out, direct and indirect eval require separate consideration. For direct eval, a sensible answer may be possible because the script is evaluated in the module, which in turn has the correct loader associated with it. For indirect eval, my previous answer stands. The only answer I can imagine is that the dynamic import would invoke the loader associated with the compartment that first created the global environment. |
What you propose is clearly necessary. The crux is that the |
For direct eval, good. Is that settled? Are there remaining open questions? Well, one: which eval functions count as an original eval function when deciding whether an
Where does the |
In any case, for indirect eval, it must not matter where it is called from. All that matters is where it was created. |
As a further clarification, a direct eval does not call the eval function bound to the |
I think we’re in agreement about what the behavior must be, assuming that we make it possible to create a guest compartment that shares its global environment with its host (the topic of this change). The question is whether that is acceptable. That is to say, assuming
|
My tentative opinion is that this is unfortunate but acceptable. The only known alternative is to not provide a shared-global mode. I do not speak for every champion, but I for one would be willing to pitch either option. |
For Realm, the answer is No (it is an indirect eval when
Yes, it should be bounded to where it was created, but the problem is we want to share globals, which means the child compartment will have the same
One of the possible solution is to give up the Let's make the child compartment an exotic object, and fall through all queries besides |
dceaa45
to
3c0b96d
Compare
I’ve moved this question to a topic #65 |
I am closing this to signal my intention to follow-up with a proposal for |
This proposal would change the default behavior of the Loader constructor such that it creates a useful guest
Loader
by default, by sharing the host’s global object, static memos, and hooks. Loader constructor options override these behaviors selectively.The motivation for this case is to provide a direct answer to @guybedford and @lucacasonato who proposed that one of the requirements for WASM is the ability to defer execution of a WASM module such that it be linked in a new module graph, possibly in multiple module graphs. This aligns well with the needs for Hot Module Replacement.
I propose as a straw-man that this framing of the Loader API would make possible a very clear way to express the designed intent:
The
import static
declaration would defer execution but populate the static module record memo of the host’s realm loader withx.wasm
and its transitive dependencies. The guest compartments would inherit these static module records by default.Developing beyond this idea, host virtualization hooks on the loader can interpose to provide alternate linkage or fall through to the host. In this example, the new loader would inherit the instance of
'y.wasm'
, which we presume'x.wasm'
imports. Consequently, the new loader has its own instance ofx.wasm
and an instance ofy.wasm
shared with the host.