Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions JSTests/modules/import-slots-aliases-shadowing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { shouldBe } from "./resources/assert.js";
import * as aliases from "./import-slots/aliases.js";
import * as shadow from "./import-slots/shadow.js";

for (let i = 0; i < testLoopCount; ++i) {
shouldBe(aliases.read(), `${i},${i},${i},${i},true`);
aliases.increment();
shouldBe(shadow.read("parameter"), "parameter,second:second,third:first");
shouldBe(shadow.inner(), "second:first,local");
shouldBe(shadow.globals(), "string,not the global Map,NaN-like,function");
}
shouldBe(typeof Map, "function");
15 changes: 15 additions & 0 deletions JSTests/modules/import-slots-assign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { shouldBe, shouldThrow } from "./resources/assert.js";
import * as assign from "./import-slots/assign.js";
import { count } from "./import-slots/values.js";

// An import is not assignable from the importing module, whatever it is bound to.
const readonly = "TypeError: Attempted to assign to readonly property.";
for (let i = 0; i < testLoopCount; ++i) {
for (const name of ["assign", "increment", "compound", "destructuring", "objectDestructuring", "forIn", "forOf", "assignFunction", "assignDefault", "assignNamespace"])
shouldThrow(assign[name], readonly);
shouldThrow(assign.assignNamespaceProperty, readonly);
assign.logical();
shouldBe(assign.read(), 0);
shouldBe(count, 0);
shouldBe(assign.mutateObject(), true);
}
20 changes: 20 additions & 0 deletions JSTests/modules/import-slots-basic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { shouldBe } from "./resources/assert.js";
import defaultFunction, { count, increment, set, object, constant, variable, declared, Klass } from "./import-slots/values.js";
import { first, second } from "./import-slots/second.js";
import { first as thirdFirst, third } from "./import-slots/third.js";
import thirdDefault from "./import-slots/third.js";

// Every kind of export, read at the top level and from functions.
shouldBe(JSON.stringify([count, constant, variable, declared(), Klass.tag, defaultFunction(), object.tag]), `[0,42,"var","declared","class","default","object"]`);
shouldBe([first, second, thirdFirst, third, thirdDefault.tag].join(), "second:first,second:second,third:first,third:third,third:default");

function read() { return count; }
for (let i = 0; i < testLoopCount; ++i) {
shouldBe(increment(), i + 1);
shouldBe(read(), i + 1);
shouldBe(count, i + 1);
}
set(-1);
shouldBe(read(), -1);
shouldBe(typeof increment, "function");
shouldBe(typeof count, "number");
20 changes: 20 additions & 0 deletions JSTests/modules/import-slots-cycles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { shouldBe } from "./resources/assert.js";
import { sawFromB, bValueSeen, viaB, setA, aValue } from "./import-slots/cycle-a.js";
import { earlyError, readA, b } from "./import-slots/cycle-b.js";
import { read, bump, value } from "./import-slots/self.js";

// cycle-b is evaluated first: it can call cycle-a's hoisted function but not read its `let`.
shouldBe(sawFromB, "b:a()");
shouldBe(bValueSeen, "bValue");
shouldBe(viaB, "a");
shouldBe(earlyError, "ReferenceError: Cannot access 'aValue' before initialization.");
for (let i = 0; i < testLoopCount; ++i) {
setA(i);
shouldBe(readA(), i);
shouldBe(aValue, i);
shouldBe(b(), "b:a()");
}
// A module that imports itself.
shouldBe(JSON.stringify(read()), "[1,1,true]");
shouldBe(bump(), 2);
shouldBe(JSON.stringify([read(), value]), "[[2,2,true],2]");
12 changes: 12 additions & 0 deletions JSTests/modules/import-slots-default-forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { shouldBe } from "./resources/assert.js";
import { read, rename, named } from "./import-slots/default-forms.js";
import theDefault from "./import-slots/default-named-function.js";

shouldBe(read(), "default,anonymous function,default,42,named,true,42");
shouldBe(named(), "named");
shouldBe(theDefault(), "named");
rename();
// `export default function named` exports the variable, so the reassignment is visible both ways.
shouldBe(read(), "default,anonymous function,default,42,replaced,true,42");
shouldBe(named(), "replaced");
shouldBe(theDefault(), "replaced");
19 changes: 19 additions & 0 deletions JSTests/modules/import-slots-dynamic-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { shouldBe } from "./resources/assert.js";
import { count, increment, object } from "./import-slots/values.js";
import * as staticNamespace from "./import-slots/values.js";

const namespace = await import("./import-slots/values.js");
shouldBe(namespace === staticNamespace, true);
shouldBe(namespace.object === object, true);
for (let i = 1; i <= testLoopCount; ++i) {
namespace.increment();
shouldBe(count, i);
shouldBe(namespace.count, i);
shouldBe(staticNamespace.count, i);
}
shouldBe(namespace.increment === increment, true);
// Imported for the first time dynamically, after this module started running.
const late = await import("./import-slots/late.js");
shouldBe(late.sometimes(true), "42,0,third:third");
const again = await import("./import-slots/late.js");
shouldBe(again === late, true);
21 changes: 21 additions & 0 deletions JSTests/modules/import-slots-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { shouldBe } from "./resources/assert.js";
import { count, increment } from "./import-slots/values.js";

async function rejection(specifier) {
try {
await import(specifier);
} catch (error) {
return error instanceof SyntaxError;
}
return "loaded";
}
// Linking fails the same way every time, and leaves the modules it shares with others usable.
for (let i = 0; i < 3; ++i) {
shouldBe(await rejection("./import-slots/imports-missing.js"), true);
shouldBe(await rejection("./import-slots/imports-conflict.js"), true);
}
shouldBe(increment(), 1);
shouldBe(count, 1);
const star = await import("./import-slots/star-conflict.js");
shouldBe(JSON.stringify(Object.keys(star).sort()), `["bump","live","second","third"]`);
shouldBe("first" in star, false);
31 changes: 31 additions & 0 deletions JSTests/modules/import-slots-forms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { shouldBe } from "./resources/assert.js";
import * as forms from "./import-slots/forms.js";

async function drain(iterable) { const values = []; for await (const value of iterable) values.push(value); return values; }
for (let i = 0; i < testLoopCount; ++i) {
forms.set(i);
shouldBe(forms.arrow(), i);
shouldBe(forms.nested(), i);
shouldBe(forms.viaEval(), i);
shouldBe(forms.viaIndirectEval(), "undefined");
shouldBe(forms.viaFunctionConstructor(), "undefined");
shouldBe(forms.defaultParameter(), i);
shouldBe(forms.defaultParameter("given"), "given");
shouldBe(forms.withTypeof(), "number,number,object,object");
shouldBe(forms.WithStatic.read(), i);
shouldBe(new forms.WithStatic().accessor, i);
shouldBe(forms.objectLiteral.accessor, i);
shouldBe(forms.objectLiteral.method(), i);
shouldBe(forms.destructure(), `${i},${i}`);
shouldBe(forms.labelled(), i);
shouldBe(forms.templated(), `${i}:42`);
shouldBe(forms.optional(), "object,object");
shouldBe(forms.inTryFinally(), i);
shouldBe(forms.arrow(), i + 1);
}
shouldBe(forms.WithStatic.captured, 0);
shouldBe(forms.objectLiteral.computed42, 0);
forms.set(10);
shouldBe(JSON.stringify([...forms.generator()]), "[10,11]");
shouldBe(await forms.asynchronous(), 11);
shouldBe(JSON.stringify(await drain(forms.asyncGenerator())), "[11]");
16 changes: 16 additions & 0 deletions JSTests/modules/import-slots-late-first-use.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { shouldBe } from "./resources/assert.js";
import { sometimes, loop, increment, bump } from "./import-slots/late.js";

// Imports first read on a path that is only taken once the function is already hot, so the
// optimizing tiers meet a binding no lower tier has read.
for (let i = 0; i < testLoopCount; ++i)
shouldBe(sometimes(false), 0);
shouldBe(loop(testLoopCount, false), 0);
increment();
bump();
shouldBe(sometimes(true), "42,1,third:third");
shouldBe(loop(testLoopCount, true), "42,1,third:third");
for (let i = 0; i < testLoopCount; ++i) {
bump();
shouldBe(sometimes(i & 1), (i & 1) ? `42,${i + 2},third:third` : 1);
}
17 changes: 17 additions & 0 deletions JSTests/modules/import-slots-live-bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { shouldBe } from "./resources/assert.js";
import { count, increment, set } from "./import-slots/values.js";
import { total, valuesNamespace, valuesDefault } from "./import-slots/reexport-named.js";
import { count as throughStar, own } from "./import-slots/reexport-star.js";
import { grandTotal, third } from "./import-slots/reexport-chain.js";
import { one } from "./import-slots/live-aliases.js";

// One variable seen through a direct import, a renaming re-export, a star re-export, a chain of
// both, a namespace re-export and another module's alias of it.
function all() { return [count, total, throughStar, grandTotal, valuesNamespace.count, one()].join(); }
for (let i = 1; i <= testLoopCount; ++i) {
increment();
shouldBe(all(), Array(6).fill(i).join());
}
set("reset");
shouldBe(all(), Array(6).fill("reset").join());
shouldBe([own, third, valuesDefault()].join(), "own,third:third,default");
30 changes: 30 additions & 0 deletions JSTests/modules/import-slots-loaders-concurrent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

// Several loaders, the global object's own among them, start loading the same modules before any
// of them has linked or evaluated one: whichever record links a module first, the others are
// compared with it while its body has yet to run (tla-main.js and tla-dep.js suspend in theirs).
const loaders = [$vm.createModuleLoader(), $vm.createModuleLoader(), $vm.createModuleLoader()];
const started = [];
for (const specifier of ["./import-slots/tla-main.js", "./import-slots/loader-main.js"]) {
started.push(import(specifier));
for (const loader of loaders)
started.push($vm.moduleLoaderImport(loader, specifier));
}
const instances = await Promise.all(started);
const asyncs = instances.slice(0, 4);
const mains = instances.slice(4);

for (const instance of asyncs) {
shouldBe(JSON.stringify([instance.seenAtStart, instance.seenAfterAwait]), `[["ready","after"],["ready","after",1]]`);
shouldBe(JSON.stringify(instance.read()), `["ready",1]`);
}
// (tla-main.js has incremented its loader's values.js once.)
for (let k = 0; k < mains.length; ++k)
shouldBe(mains[k].run(k + 1), `${k + 2},${k + 1}`);
for (let i = 0; i < testLoopCount; ++i)
shouldBe(mains.map(main => main.read()).join(";"), [1, 2, 3, 4].map(n => `${n + 1},${n + 1},${n},true,third:default`).join(";"));
for (let k = 1; k < 4; ++k) {
shouldBe(sameCode(mains[0].run, mains[k].run), true);
shouldBe(sameCode(asyncs[0].read, asyncs[k].read), true);
}
33 changes: 33 additions & 0 deletions JSTests/modules/import-slots-loaders-cycles-tla.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

const loaders = [$vm.createModuleLoader(), $vm.createModuleLoader(), $vm.createModuleLoader()];
const cycles = [];
for (const loader of loaders)
cycles.push([await $vm.moduleLoaderImport(loader, "./import-slots/cycle-a.js"), await $vm.moduleLoaderImport(loader, "./import-slots/cycle-b.js")]);
for (let k = 0; k < cycles.length; ++k) {
const [a, b] = cycles[k];
shouldBe([a.sawFromB, a.bValueSeen, a.viaB, b.earlyError].join(), "b:a(),bValue,a,ReferenceError: Cannot access 'aValue' before initialization.");
a.setA(`set${k}`);
}
for (let i = 0; i < testLoopCount; ++i) {
for (let k = 0; k < cycles.length; ++k)
shouldBe([cycles[k][1].readA(), cycles[k][0].aValue].join(), `set${k},set${k}`);
}
shouldBe(sameCode(cycles[1][1].readA, cycles[0][1].readA), true);

const selves = [];
for (const loader of loaders)
selves.push(await $vm.moduleLoaderImport(loader, "./import-slots/self.js"));
selves[1].bump();
selves[2].bump();
selves[2].bump();
shouldBe(JSON.stringify(selves.map(self => self.read())), "[[1,1,true],[2,2,true],[3,3,true]]");

const asyncs = [];
for (const loader of loaders)
asyncs.push(await $vm.moduleLoaderImport(loader, "./import-slots/tla-main.js"));
for (const instance of asyncs) {
shouldBe(JSON.stringify([instance.seenAtStart, instance.seenAfterAwait]), `[["ready","after"],["ready","after",1]]`);
shouldBe(JSON.stringify(instance.read()), `["ready",1]`);
}
27 changes: 27 additions & 0 deletions JSTests/modules/import-slots-loaders-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { shouldBe } from "./resources/assert.js";

// A module whose imports do not resolve fails to link in every loader, each time it is asked for,
// with the same error; the modules it would have shared with others stay usable, one instance
// per loader.
const loaders = [$vm.createModuleLoader(), $vm.createModuleLoader(), $vm.createModuleLoader()];
async function rejection(loader, specifier) {
try {
await $vm.moduleLoaderImport(loader, specifier);
} catch (error) {
return [error instanceof SyntaxError, String(error)].join();
}
return "loaded";
}
for (const specifier of ["./import-slots/imports-missing.js", "./import-slots/imports-conflict.js"]) {
const expected = await rejection(loaders[0], specifier);
shouldBe(expected.startsWith("true,SyntaxError: "), true);
for (let round = 0; round < 2; ++round) {
for (const loader of loaders)
shouldBe(await rejection(loader, specifier), expected);
}
}
const values = [];
for (const loader of loaders)
values.push(await $vm.moduleLoaderImport(loader, "./import-slots/values.js"));
values[1].increment();
shouldBe(values.map(instance => instance.count).join(), "0,1,0");
27 changes: 27 additions & 0 deletions JSTests/modules/import-slots-loaders-first-instance-collected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

// The only instance of a module is collected, and its code with it, before another loader asks for
// the module: there is nothing to take code from or to be compared with, so the next instance
// links its own, and the ones after that share with it. (The instances below behave the same
// whether or not the collector has got to the first one by then.)
const load = () => $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/loader-main.js");
await (async () => {
const first = await load();
shouldBe(first.run(testLoopCount), `${testLoopCount},${testLoopCount}`);
})();
for (let i = 0; i < 4; ++i) {
await new Promise(resolve => setTimeout(resolve, 0));
fullGC();
}
const a = await load();
shouldBe(a.run(1), "1,1");
shouldBe(a.read(), "1,1,1,true,third:default");
const b = await load();
const c = await load();
shouldBe(typeof $vm.codeBlockFor(b.run), "string");
shouldBe(sameCode(a.run, b.run), true);
shouldBe(sameCode(a.read, c.read), true);
shouldBe(b.run(2), "2,2");
for (let i = 0; i < testLoopCount; ++i)
shouldBe([a.read(), b.read(), c.read()].join(";"), "1,1,1,true,third:default;2,2,2,true,third:default;0,0,0,true,third:default");
21 changes: 21 additions & 0 deletions JSTests/modules/import-slots-loaders-gc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { shouldBe } from "./resources/assert.js";

// Instances come and go; the ones that stay keep reading their own bindings from code that
// instances since collected also ran.
const keep = [];
for (let round = 0; round < 12; ++round) {
const instance = await $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/loader-main.js");
shouldBe(instance.run(round + 1), `${round + 1},${round + 1}`);
if (!(round % 4))
keep.push([instance, round + 1]);
if (round % 3 === 2)
fullGC();
}
fullGC();
for (let i = 0; i < testLoopCount; ++i) {
for (const [instance, n] of keep)
shouldBe(instance.read(), `${n},${n},${n},true,third:default`);
}
const fresh = await $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/loader-main.js");
shouldBe(fresh.read(), "0,0,0,true,third:default");
shouldBe((await fresh.loadValues()) === fresh.values, true);
23 changes: 23 additions & 0 deletions JSTests/modules/import-slots-loaders-json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

// An import from a module that is not a source text module: what the importer's code embeds is the
// binding's offset in that module's environment, and records whose offsets agree share the code.
// Each loader has its own JSON module, so its own object.
const load = () => $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/json-user.js");
const a = await load();
const b = await load();
const c = await load();
b.increment();
b.data.n = 5;
for (let i = 0; i < testLoopCount; ++i)
shouldBe([a.read(), b.read(), c.read()].join(";"), "json,1,0,true;json,5,1,true;json,1,0,true");
shouldBe(sameCode(a.read, b.read), true);
shouldBe(sameCode(a.read, c.read), true);
shouldBe(a.data === b.data, false);
shouldBe(a.data === c.data, false);

const own = await import("./import-slots/json-user.js");
shouldBe(own.read(), "json,1,0,true");
shouldBe(sameCode(own.read, a.read), true);
shouldBe(own.data === a.data, false);
19 changes: 19 additions & 0 deletions JSTests/modules/import-slots-loaders-late-first-use.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

// One instance makes the code hot without ever reading some of its imports; another instance,
// running that same code, is the first to read them.
const load = () => $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/late.js");
const a = await load();
shouldBe(a.loop(testLoopCount, false), 0);
const b = await load();
const c = await load();
shouldBe(typeof $vm.codeBlockFor(b.sometimes), "string");
shouldBe(sameCode(a.sometimes, b.sometimes), true);
b.bump();
c.bump();
c.bump();
c.increment();
shouldBe([b.sometimes(true), c.sometimes(true), a.sometimes(true)].join(";"), "42,1,third:third;42,2,third:third;42,0,third:third");
shouldBe([b.loop(testLoopCount, true), c.loop(testLoopCount, true), a.loop(testLoopCount, true)].join(";"), "42,1,third:third;42,2,third:third;42,0,third:third");
shouldBe([a.sometimes(false), b.sometimes(false), c.sometimes(false)].join(), "0,0,1");
19 changes: 19 additions & 0 deletions JSTests/modules/import-slots-loaders-many.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { shouldBe } from "./resources/assert.js";
import { sameCode } from "./import-slots/code.js";

const load = () => $vm.moduleLoaderImport($vm.createModuleLoader(), "./import-slots/many.js");
const [a, b, c] = [await load(), await load(), await load()];
let base = 0;
for (let m = 0; m < 8; ++m) {
for (let i = 0; i < 16; ++i)
base += m * 100 + i;
}
for (let i = 0; i < testLoopCount; ++i)
shouldBe(a.sum(), base);
shouldBe(typeof $vm.codeBlockFor(b.sum), "string");
shouldBe(sameCode(a.sum, b.sum), true);
b.bumpEverything();
c.bumpEverything();
c.bumpEverything();
for (let i = 0; i < testLoopCount; ++i)
shouldBe([a.sum(), b.sum(), c.sum(), a.last(), b.last(), c.last(), a.first(), b.first(), c.first()].join(), [base, base + 128, base + 256, 715, 716, 717, 0, 1, 2].join());
Loading
Loading