refactor(ses): Decouple StaticModuleRecord
This change removes an internal weak map for all known static module
records and instead uses a duck-type to communicate them into the module
system.
MODIFY
packages/ses/src/module-instance.js
packages/ses/src/module-instance.js
@@ -84,19 +84,17 @@ export function makeThirdPartyModuleInstance(
// that the execution of the module instance populates.
export const makeModuleInstance = (
privateFields,
- moduleAnalysis,
moduleAliases,
moduleRecord,
importedInstances,
- globalObject,
) => {
+ const { compartment, moduleSpecifier, staticModuleRecord } = moduleRecord;
const {
- functorSource,
- fixedExportMap,
- liveExportMap,
- exportAlls,
- } = moduleAnalysis;
- const { compartment, moduleSpecifier } = moduleRecord;
+ __functorSource__: functorSource,
+ __fixedExportMap__: fixedExportMap = {},
+ __liveExportMap__: liveExportMap = {},
+ __exportAlls__: exportAlls = [],
+ } = staticModuleRecord;
const compartmentFields = privateFields.get(compartment);
@@ -382,7 +380,7 @@ export const makeModuleInstance = (
}
let optFunctor = compartment.evaluate(functorSource, {
- globalObject,
+ globalObject: compartment.globalThis,
transforms: __shimTransforms__,
__moduleShimLexicals__: localLexicals,
});

MODIFY
packages/ses/src/module-link.js
packages/ses/src/module-link.js
+/* eslint no-underscore-dangle: ["off"] */
+
// For brevity, in this file, as in module-load.js, the term "moduleRecord"
// without qualification means "module compartment record".
// This is a super-set of the "static module record", that is reusable between
// module's "imports" with the more specific "resolvedImports" as inferred from
// the particular compartment's "resolveHook".
+import { assert } from './error/assert.js';
import {
makeModuleInstance,
makeThirdPartyModuleInstance,
} from './module-instance.js';
-import { entries } from './commons.js';
+import { entries, isArray } from './commons.js';
+
+const { quote: q } = assert;
-// q, as in quote, for quoting strings in error messages.
-const q = JSON.stringify;
+function isObject(o) {
+ return Object(o) === o;
+}
// `link` creates `ModuleInstances` and `ModuleNamespaces` for a module and its
// transitive dependencies and connects their imports and exports.
@@ -23,7 +29,6 @@ const q = JSON.stringify;
// the actual `ModuleNamespace`.
export const link = (
compartmentPrivateFields,
- moduleAnalyses,
moduleAliases,
compartment,
moduleSpecifier,
@@ -39,51 +44,71 @@ export const link = (
// compartment is in context: the module record may be in another
// compartment, denoted by moduleRecord.compartment.
// eslint-disable-next-line no-use-before-define
- return instantiate(
- compartmentPrivateFields,
- moduleAnalyses,
- moduleAliases,
- moduleRecord,
- );
+ return instantiate(compartmentPrivateFields, moduleAliases, moduleRecord);
};
-const validateStaticModuleRecord = (staticModuleRecord, moduleAnalyses) => {
- const isObject = typeof staticModuleRecord === 'object';
- if (!isObject) {
- throw new TypeError(
- `importHook must return a StaticModuleRecord, got ${staticModuleRecord}`,
- );
+function isPrecompiled(staticModuleRecord) {
+ return typeof staticModuleRecord.__functorSource__ === 'string';
}
- const hasAnalysis = moduleAnalyses.has(staticModuleRecord);
- const hasImports = Array.isArray(staticModuleRecord.imports);
- const hasExports = typeof staticModuleRecord.execute === 'function';
-
- if (!hasAnalysis && !hasExports) {
- if (hasImports) {
- // In this case, the static module record has an `imports` property, but
- // no `execute` method.
- // This could either be a partially implemented custom static module
- // record or created by `StaticModuleRecord` in another realm.
- throw new TypeError(
- 'importHook must return a StaticModuleRecord constructed within the same Realm, or a custom record with both imports and an execute method',
+function validatePrecompiledStaticModuleRecord(staticModuleRecord) {
+ const {
+ __fixedExportMap__,
+ __liveExportMap__,
+ __exportAlls__,
+ } = staticModuleRecord;
+ assert(
+ isObject(__fixedExportMap__),
+ `Property '__fixedExportMap__' of a precompiled module record must be an object, got ${q(
+ __fixedExportMap__,
+ )}`,
);
- } else {
- // In this case, the static module record has no `imports` or `execute`
- // property, so it could not have been created by the
- // `StaticModuleRecord` from any realm.
- // From this we infer the intent was to produce a valid custom static
- // module record and clue accordingly.
- throw new TypeError(
- 'importHook must return a StaticModuleRecord with both imports and an execute method',
+ assert(
+ isObject(__liveExportMap__),
+ `Property '__liveExportMap__' of a precompiled module record must be an object, got ${q(
+ __liveExportMap__,
+ )}`,
+ );
+ assert(
+ isArray(__exportAlls__),
+ `Property '__exportAlls__' of a precompiled module record must be an array, got ${q(
+ __exportAlls__,
+ )}`,
);
}
+
+function isThirdParty(staticModuleRecord) {
+ return typeof staticModuleRecord.execute === 'function';
+}
+
+function validateThirdPartyStaticModuleRecord(staticModuleRecord) {
+ const { exports } = staticModuleRecord;
+ assert(
+ isArray(exports),
+ `Property 'exports' of a third-party static module record must be an array, got ${q(
+ exports,
+ )}`,
+ );
+}
+
+function validateStaticModuleRecord(staticModuleRecord) {
+ assert(
+ isObject(staticModuleRecord),
+ `Static module records must be of type object, got ${q(
+ staticModuleRecord,
+ )}`,
+ );
+ const { imports } = staticModuleRecord;
+ assert(
+ isArray(imports),
+ `Property 'imports' of a static module record must be an array, got ${q(
+ imports,
+ )}`,
+ );
}
-};
export const instantiate = (
compartmentPrivateFields,
- moduleAnalyses,
moduleAliases,
moduleRecord,
) => {
@@ -93,28 +118,27 @@ export const instantiate = (
resolvedImports,
staticModuleRecord,
} = moduleRecord;
- const { globalObject, instances } = compartmentPrivateFields.get(compartment);
+ const { instances } = compartmentPrivateFields.get(compartment);
// Memoize.
if (instances.has(moduleSpecifier)) {
return instances.get(moduleSpecifier);
}
- validateStaticModuleRecord(staticModuleRecord, moduleAnalyses);
+ validateStaticModuleRecord(staticModuleRecord);
const importedInstances = new Map();
let moduleInstance;
- const moduleAnalysis = moduleAnalyses.get(staticModuleRecord);
- if (moduleAnalysis !== undefined) {
+ if (isPrecompiled(staticModuleRecord)) {
+ validatePrecompiledStaticModuleRecord(staticModuleRecord);
moduleInstance = makeModuleInstance(
compartmentPrivateFields,
- moduleAnalysis,
moduleAliases,
moduleRecord,
importedInstances,
- globalObject,
);
- } else {
+ } else if (isThirdParty(staticModuleRecord)) {
+ validateThirdPartyStaticModuleRecord(staticModuleRecord);
moduleInstance = makeThirdPartyModuleInstance(
compartmentPrivateFields,
staticModuleRecord,
@@ -123,6 +147,8 @@ export const instantiate = (
moduleSpecifier,
resolvedImports,
);
+ } else {
+ throw new Error('Assertion failed'); // TODO
}
// Memoize.
@@ -132,7 +158,6 @@ export const instantiate = (
for (const [importSpecifier, resolvedSpecifier] of entries(resolvedImports)) {
const importedInstance = link(
compartmentPrivateFields,
- moduleAnalyses,
moduleAliases,
compartment,
resolvedSpecifier,

MODIFY
packages/ses/src/module-shim.js
packages/ses/src/module-shim.js
-// This module exports both Compartment and StaticModuleRecord because they
-// communicate through the moduleAnalyses private side-table.
+/* eslint no-underscore-dangle: ["off"] */
import babel from '@agoric/babel-standalone';
import { makeModuleAnalyzer } from '@agoric/transform-module';
@@ -24,22 +23,21 @@ const q = JSON.stringify;
const analyzeModule = makeModuleAnalyzer(babel);
-// moduleAnalyses are the private data of a StaticModuleRecord.
-// We use moduleAnalyses in the loader/linker to look up
-// the analysis corresponding to any StaticModuleRecord constructed by an
-// importHook.
-const moduleAnalyses = new WeakMap();
-
-/*
- * StaticModuleRecord captures the effort of parsing and analyzing module text
- * so a cache of StaticModuleRecords may be shared by multiple Compartments.
- */
-export function StaticModuleRecord(string, url) {
+// eslint-disable-next-line no-shadow
+export const __PrecompiledStaticModuleRecord__ = function StaticModuleRecord(
+ analysis,
+) {
if (new.target === undefined) {
- return new StaticModuleRecord(string, url);
+ return new __PrecompiledStaticModuleRecord__(analysis);
}
- const analysis = analyzeModule({ string, url });
+ const {
+ imports,
+ functorSource,
+ fixedExportMap,
+ liveExportMap,
+ exportAlls,
+ } = analysis;
// `keys` below is Object.keys which shows only the names of string-named
// enumerable own properties.
@@ -51,12 +49,24 @@ export function StaticModuleRecord(string, url) {
//
// The other subtle reason this is correct is that analysis.imports should
// only have identifier-named own properties.
- this.imports = keys(analysis.imports).sort();
+ this.imports = freeze(keys(imports).sort());
+ this.__functorSource__ = functorSource;
+ // We do not evidently need the values of this record, only the keys.
+ // this.__imports__ = freeze(imports);
+ this.__fixedExportMap__ = freeze(fixedExportMap);
+ this.__liveExportMap__ = freeze(liveExportMap);
+ this.__exportAlls__ = freeze(exportAlls);
freeze(this);
- freeze(this.imports);
+};
- moduleAnalyses.set(this, analysis);
+/*
+ * StaticModuleRecord captures the effort of parsing and analyzing module text
+ * so a cache of StaticModuleRecords may be shared by multiple Compartments.
+ */
+export function StaticModuleRecord(string, url) {
+ const analysis = analyzeModule({ string, url });
+ return new __PrecompiledStaticModuleRecord__(analysis);
}
const StaticModuleRecordPrototype = {
@@ -70,6 +80,10 @@ defineProperties(StaticModuleRecord, {
prototype: { value: StaticModuleRecordPrototype },
});
+defineProperties(__PrecompiledStaticModuleRecord__, {
+ prototype: { value: StaticModuleRecordPrototype },
+});
+
defineProperties(InertStaticModuleRecord, {
prototype: { value: StaticModuleRecordPrototype },
});
@@ -148,13 +162,7 @@ const ModularCompartmentPrototypeExtension = {
assertModuleHooks(this);
- const moduleInstance = link(
- privateFields,
- moduleAnalyses,
- moduleAliases,
- this,
- specifier,
- );
+ const moduleInstance = link(privateFields, moduleAliases, this, specifier);
moduleInstance.execute();
return moduleInstance.exportsProxy;
},

refactor(ses): Decouple StaticModuleRecord

This change removes an internal weak map for all known static module
records and instead uses a duck-type to communicate them into the module
system.