endo icon indicating copy to clipboard operation
endo copied to clipboard

Using XS native Compartments for Agoric Vat Workers

Open kriskowal opened this issue 5 years ago • 5 comments

[update 2024-04-29]

Agoric currently bundles contracts in a pre-compiled JSON envelope to avoid the runtime cost of loading and using Babel to transform ESM to a form that SES can digest. We could instead rely on the native XS ModuleSource behavior on XS if the native Compartment were sufficiently similar to the SES emulation of Compartment.

  • [ ] Change the Agoric/Endo bundle format such that it contains original sources instead of “precompiled” sources.
    • [ ] Refactor Bundling, Compartment Mapper, and SES together such that existing applications continue to function in Node.js and XSnap except using the XS native Compartment and ModuleSource instead of Babel. In the current world, bundleSource applies the module-to-program transform unconditionally. In the new world, bundleSource would capture original sources, but importBundle will use the Babel transform on Node.js and native ModuleSource on XS.
      • [ ] #2250
      • [ ] #2251
        • [x] https://github.com/endojs/endo/pull/2321
        • [x] https://github.com/endojs/endo/pull/2345
        • [x] https://github.com/endojs/endo/pull/2350
        • [x] https://github.com/endojs/endo/pull/2380
        • [x] https://github.com/endojs/endo/pull/2380
        • [ ] https://github.com/endojs/endo/pull/2470
        • [ ] https://github.com/endojs/endo/pull/2471
      • [ ] #2252
        • [ ] Either use Rollup for Node.js ModuleSource shim preamble or for test262-harness to support Node.js import preamble
        • [x] #2295
        • [x] https://github.com/endojs/endo/pull/2352
        • [x] https://github.com/endojs/endo/pull/2463
        • [x] https://github.com/endojs/endo/pull/2465
      • [ ] #2259
        • [ ] https://github.com/endojs/endo/pull/2593
      • [ ] #2254
        • [ ] Using an "imports" directive in @endo/import-bundle such that it only entrains @endo/evasive-transforms on Node.js and not with the "xs" tag.
          • [ ] #2203

Enables https://github.com/endojs/endo/issues/2116, https://github.com/endojs/endo/issues/2117

It follows that we will be able to close when we have adequate parity with native XS for virtual module source on XS https://github.com/endojs/endo/issues/1066


[update 2023-12-19]

Agoric currently bundles contracts in a pre-compiled JSON envelope to avoid the runtime cost of loading and using Babel to transform ESM to a form that SES can digest. We could instead rely on the native XS ModuleSource behavior on XS if the native Compartment were sufficiently similar to the SES emulation of Compartment.

  • [ ] establish 262 style tests that prove that we have adequate parity between XS Compartment and SES Comaprtment. This is likely to require changes to SES and a specialized thin variant of SES just for use on XS.
  • [ ] alter import bundle to take a dependency on @endo/static-module-record on Node.js but rely on the native ModuleSource constructor when running on XS. Differentiate these cases with a tag like endo in the package.json "exports" or "imports" property so that the SwingSet worker bootstrap for XS doesn’t entrain Babel when we generate that bundle.
  • [ ] alter compartment mapper to embed original sources instead of precompiled sources

[update 2020-01-21]

Agoric currently runs contracts in an XSnap worker using the SES shim for dynamic module-loading Compartment support. This works for now, and Moddable has made considerable progress toward making their native Compartment implementation a better alternative. To run a contract in a native Compartment, we would not need to use the bundleSource “censorship evasive transforms” that mitigate the weaknesses in the ses (hardened JavaScript shim) emulation of ESM (ECMAScript modules).

This issue stands open to track native compartment support. At time of writing, the ball is in our court. We need:

  • [ ] to move XSnap to the Endo repository so we can run common tests against both SES shim and XS native
  • [ ] adjust the SES shim to conditionally reveal the native Compartment when it’s available
  • [ ] get a test262 runner working in Endo CI https://github.com/endojs/endo/issues/1001
  • [ ] converge on implementation differences that we have agreed upon in design https://github.com/endojs/endo/issues/420, https://github.com/endojs/endo/issues/934

In the original description below, we are running the stopgap in production, and the longshot is not such a long shot anymore.


There are two avenues to running contract archives on Moddable’s XS. Both involve creating an XS binary for hosting dynamically-loaded modules from an archive (presumed Zip), including both CommonJS and ECMAScript modules. In both scenarios, this binary would implement Endo, ideally reüsing as much of its internals without modification as possible, so we have parity between running on Node.js with SES-Shim and running on XS without.

  1. Longshot: Fill out the XS Compartment API (C) such that it can incorporate modules at run-time.
  2. Stopgap: Emulate run-time modules using XS’s Compartment evaluator.

The Longshot is the most desirable long-term outcome. XS should in principle not need any part of the SES shim, since it’s a SES-only runtime. Also, our friends at Moddable are committed to implementing the Compartment specification, though the specification remains negotiable. To pursue this option, we may need to engage more closely with the XS project.

The Stopgap is possible (modulo risks) without major changes to XS, and largely reusing work we have already done on SES-Shim. We would need to implement the dynamic module loading feature as an xs-compartment-shim. This would reüse the transform-module Babel shim. We would need to factor some code out of the SES compartment-shim, presumably into the make-importer project Michael sketched.

The work break-down for the Longshot includes:

  1. XS Compartment would need to implement the StaticModuleRecord constructor, so ECMAScript modules can be parsed and analyzed.
  2. XS Compartment would need to implement importHook.
  3. On the first pass, importHook would need to be able to return a static module record provided by the above StaticModuleRecord and the Compartment and StaticModuleRecord can communicate behind the scenes with internal slots or weak maps to hide implementation details.
  4. On the second pass, importHook ould also support CommonJS, JSON, and potentially other modules by permitting the importHook to return implementations of static module record with the currently unspecified { imports, execute(exports, compartment, resolvedImports) } interface. This is possible as of #397 on Node.js and would be necessary for parity, though we could constrain the transitive dependencies of contracts to be limited to the currently rare ESM format.
  5. XS Compartment would need to implement resolveHook. There is some wiggle room here since we actually just need every Compartment to use our particular resolveHook that works like Node.js's resolver, but where every full specifier is either package-local (starting with . or ..) or from another package. With this hook, resolve('./aux.js', './main.js') is ./aux.js, whereas resolve('../foo.js', './main.js') is an error since it escapes the package root. Folks who have implemented resolution for Node.js do not use this math since Node.js effectively executes within a single compartment where module specifiers are fully qualified and resolution may involve IO. This would be antithetical to compartmentalizing third-party packages like LavaMoat.
  6. XS Compartment and SES-shim Compartment would need to settle some minor differences or Endo would need to feature-test and work-around them. Notably SES-shim exposes import (which returns a promise) but not the importNow. This is likely a case where the specification and the SES shim need to change, since distinguishing importNow will no longer beuseful when the Compartment specification meets the new top-level-await.

The work break-down for Stopgap includes:

  1. We would need to factor the Compartment constructor out of SES-shim such that it can be used by Endo in XS without the lockdown shim.
  2. We would need a tool that turns an application that uses Node.js-style packages into a single namespace (so they can be precompiled into an XS binary) and a JavaScript" kernel that partitions the packages into child compartments and executes them in topological order. This is because the XS Compartment only supports pre-compiled ESM and does not support Node.js-style packaging. The “kernel” must be a single package of ECMASCript modules.
  3. The transitive dependencies of the XS binary application would need to all be ECMAScript modules. If we got the longshot, we could write a much smaller bootstrap and use Endo both for the application-runner (which depends on a CommonJS Zip library and Babel) and the application.
  4. We will need to port a Zip library to ESM.
  5. We will need to ensure that Babel can run under ESM, instead of the CommonJS projection of its ESM implementation.

kriskowal avatar Aug 03 '20 21:08 kriskowal

... This is because the XS Compartment only supports pre-compiled ESM and does not support Node.js-style packaging. The “kernel” must be a single package of ECMASCript modules.

I wonder if that's quite true. I'm not sure what "Node.js-style packaging" means exactly. XS can import modules by reading .js from the filesystem at runtime. (I was in the past under the impression that it cannot, and I spread this misinformation around a bit, but I was wrong. IOU pointers to details)

I have incomplete understanding of importHook and a few other terms above, so I'm not sure how to share what I know about this topic. A meeting might be cost-effective.

dckc avatar Aug 03 '20 21:08 dckc

Thanks @dckc. Importing off the filesystem might be useful in some special cases. It would be useful to see an example of, say, writing a file to disk then importing it.

kriskowal avatar Aug 03 '20 21:08 kriskowal

here's hoping I find time to cook up such an example. (I'm pretty sure I've done it before... maybe I still have it around...)

Meanwhile, for reference: fxLoadScript and fxLoadModule a few lines previous.

dckc avatar Aug 03 '20 22:08 dckc

here's hoping I find time to cook up such an example. (I'm pretty sure I've done it before... maybe I still have it around...)

found it: https://gist.github.com/dckc/cbbd3e8469723b342cc90799ace7a287 from Jan 23 https://github.com/Agoric/agoric-sdk/issues/426#issuecomment-577522277

dckc avatar Aug 12 '20 00:08 dckc

The stopgap I described here has been working for some time now. Moddable has also recently made incredible strides toward making the longshot work too. I’m revising the title and description so that this issue continues to track native compartment support. https://github.com/endojs/endo/issues/848 is related.

kriskowal avatar Jan 21 '22 23:01 kriskowal