RFC: removal of JSRuntime
The existence of JSRuntime and JSContext complicates the QuickJS API because many functions are duplicated and sometimes it is not clear whether a JSRuntime or JSContext parameter is required. The rationale is that JSContext represent the JS Realm and JSRuntime the whole JS execution content. In many applications, having more than one Realm is not needed. Even when several realms are needed, it should be enough to have a current realm stored in the execution context and an API to change it.
So I propose to remove the JSRuntime type and to add a new JSRealm type. JSContext would become the JS execution context. New functions would be added to change the current realm and to create or destroy a realm. All the functions with a RT suffix would be removed.
If necessary, backward compatibility could be mostly kept by setting JSRuntime = JSContext and by redirecting the functions with a RT suffix to the corresponding functions without a RT suffix.
I actually quite like having realms and runtimes. In my multi-tenant application I create a realm per tenant so variables don't accidentally leak to other tenants. I could still create multiple realms with your proposal but then I would also have to define my custom classes per realm instead of once in the runtime making realm creation more expensive right? Also Atoms reside in the runtime right? Then creating multiple realm would also increase memory usage....
In Apache CouchDB we always clear and re-create both the runtime and the context so the simplification would be welcome for us not have to deal with both.
As mentioned I think this would negatively impact multi-tenant solutions. Currently we’re able to run 128 JSContext per-core with minimal memory usage because the largest atoms are shared from the JSRuntime. Creating new context from a pool allows us to have zero-startup time and memory usage is from an arena so we have predictable guarantees in our performance.
With this proposal however we’d now need to recreate those large atoms per-vm context (in real-time) which means lots new heap allocations.
We could of course implement a pluggable string interner like boa does... In my case I could then even save more memory because I can share it with my rust app... It would only need like three functions, create, dup, free
Sorry I was not clear: the removal of JSRuntime would not change the inner working of the realms, so there will be no slowdown nor memory increase when multiple realms are used. Here are some examples:
/* current API with a single realm */
{
JSRuntime *rt;
JSContext *ctx;
const char *eval1 = "print(\"hello\")";
rt = JS_NewRuntime();
ctx = JS_NewContext(rt);
JS_Eval(ctx, eval1, strlen(eval1), "<eval>", 0);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
}
/* new API with a single realm */
{
JSContext *ctx;
const char *eval1 = "print(\"hello\")";
ctx = JS_NewContext();
JS_Eval(ctx, eval1, strlen(eval1), "<eval>", 0);
JS_FreeContext(ctx);
}
/* current API several realms */
{
JSRuntime *rt;
JSContext *realm1, *realm2;
const char *eval1 = "print(\"hello realm1\")";
const char *eval2 = "print(\"hello realm2\")";
rt = JS_NewRuntime();
realm1 = JS_NewContext(rt);
realm2 = JS_NewContext(rt);
JS_Eval(realm1, eval1, strlen(eval1), "<eval>", 0);
JS_Eval(realm2, eval2, strlen(eval2), "<eval>", 0);
JS_FreeContext(realm2);
JS_FreeContext(realm1);
JS_FreeRuntime(rt);
}
/* new API several realms */
{
JSContext *ctx;
JSRealm *realm1, *realm2;
const char *eval1 = "print(\"hello realm1\")";
const char *eval2 = "print(\"hello realm2\")";
ctx = JS_NewContext();
/* Note: a default realm is created by JS_NewContext(). For
simplicity we don't use it here. */
realm1 = JS_NewRealm(ctx);
realm2 = JS_NewRealm(ctx);
JS_SetCurrentRealm(ctx, realm1);
JS_Eval(ctx, eval1, strlen(eval1), "<eval>", 0);
JS_SetCurrentRealm(ctx, realm2);
JS_Eval(ctx, eval2, strlen(eval2), "<eval>", 0);
JS_FreeRealm(realm2);
JS_FreeRealm(realm1);
JS_FreeContext(ctx);
}
Ok, so its a rename of runtime to context, and context to realm and normaly only using one context right... I can see how that simplifies calling the api. and I can still use different realms.
Now I wanted to ask to keep calling the top-level object a runtime but the spec actualy does talk more about execution context than runtimes. So I guess the 'naming things' table is going to look like this :)
| who | toplevel | sublevel |
|---|---|---|
| Spidermonkey | Runtime | Context |
| V8 | Isolate | Realm |
| quickjs | Context | Realm |
/* new API with a single realm */
{
JSContext *ctx;
const char *eval1 = "print(\"hello\")";
ctx = JS_NewContext();
JS_Eval(ctx, eval1, strlen(eval1), "<eval>", 0);
JS_FreeContext(ctx);
}
^ At least for our use case I quite like the new, simpler API.
In your new multi-realm example, how do we reset to the 'main' realm? or would you add variables for main_realm and current_realm so you can always Free the main realm?
It feels a bit 'finicky' for the multi-realm users...
Could we turn things around? Currently the context(realm) has a pointer to the runtime, so instead of passing the runtime(context) to every function you could pass the Context(realm)
so we could make single realm and multi-realm work like this
// single realm
{
JSRealm *rlm;
const char *eval1 = "print(\"hello\")";
// create a new runtime(Context) and a realm and sets rlm->rt = the new Context
rlm = JS_NewRealm();
// where eval needs the runtime it can use rlm->rt
JS_Eval(rlm, eval1, strlen(eval1), "<eval>", 0);
// FreeRealm should check if rlm is equal to rlm->rt->main_realm and then also call FreeContext(rlm->rt)
JS_FreeRealm(rlm);
}
// multi realm
{
JSRealm *main_rlm, *sub_rlm1, *sub_rlm2;
const char *eval1 = "print(\"hello\")";
// create a new runtime(Context) and a realm and sets rlm->rt = the new Context
main_rlm = JS_NewRealm();
// create a realm with the same runtime
sub_rlm1 = JS_DeriveRealm(main_rlm);
sub_rlm2 = JS_DeriveRealm(main_rlm);
// where eval needs the runtime it can use rlm->rt
JS_Eval(sub_rlm1, eval1, strlen(eval1), "<eval>", 0);
JS_Eval(sub_rlm2, eval1, strlen(eval1), "<eval>", 0);
// FreeRealm should check if rlm is equal to rlm->rt->main_realm and then also call FreeContext(rlm->rt)
JS_FreeRealm(sub_rlm1);
JS_FreeRealm(sub_rlm2);
JS_FreeRealm(main_rlm);
}
If the goal is to simplify the API I think this works nicely because it abstracts the difference between context/realm from the implementor and doesn't change the way you you work if switching between a single or multiple realms...
This could also simplify the refactoring work needed because you can just change the functions which now accept a runtime to accept a realm and user realm->rt
Just my 2 cents....