Bug 930414 - Implement ModuleEvaluation method r=shu

This commit is contained in:
Jon Coppeard 2015-09-23 15:47:40 +01:00
parent 1679690474
commit c5cebf0268
13 changed files with 226 additions and 2 deletions

View File

@ -142,3 +142,30 @@ function ModuleDeclarationInstantiation()
}
}
}
// 15.2.1.16.5 ModuleEvaluation()
function ModuleEvaluation()
{
if (!IsObject(this) || !IsModule(this))
return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation");
// Step 1
let module = this;
// Step 4
if (module.evaluated)
return undefined;
// Step 5
SetModuleEvaluated(this);
// Step 6
let requestedModules = module.requestedModules;
for (let i = 0; i < requestedModules.length; i++) {
let required = requestedModules[i];
let requiredModule = HostResolveImportedModule(module, required);
requiredModule.evaluation();
}
return EvaluateModule(module);
}

View File

@ -304,6 +304,7 @@ ModuleObject::init(HandleScript script)
{
MOZ_ASSERT(!script->enclosingStaticScope());
initReservedSlot(ScriptSlot, PrivateValue(script));
initReservedSlot(EvaluatedSlot, BooleanValue(false));
}
void
@ -340,6 +341,12 @@ ModuleObject::script() const
return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
}
bool
ModuleObject::evaluated() const
{
return getReservedSlot(EvaluatedSlot).toBoolean();
}
ModuleEnvironmentObject&
ModuleObject::initialEnvironment() const
{
@ -386,8 +393,23 @@ ModuleObject::createEnvironment()
setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot));
}
void
ModuleObject::setEvaluated()
{
MOZ_ASSERT(!evaluated());
setReservedSlot(EvaluatedSlot, TrueHandleValue);
}
bool
ModuleObject::evaluate(JSContext* cx, MutableHandleValue rval)
{
RootedScript script(cx, this->script());
return JS_ExecuteScript(cx, script, rval);
}
DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, environment, EnvironmentSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
@ -400,6 +422,7 @@ js::InitModuleClass(JSContext* cx, HandleObject obj)
static const JSPropertySpec protoAccessors[] = {
JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0),
JS_PSG("environment", ModuleObject_environmentGetter, 0),
JS_PSG("evaluated", ModuleObject_evaluatedGetter, 0),
JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
@ -411,6 +434,7 @@ js::InitModuleClass(JSContext* cx, HandleObject obj)
static const JSFunctionSpec protoFunctions[] = {
JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 3, 0),
JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0),
JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0),
JS_FS_END
};

View File

@ -88,6 +88,7 @@ class ModuleObject : public NativeObject
ScriptSlot = 0,
InitialEnvironmentSlot,
EnvironmentSlot,
EvaluatedSlot,
RequestedModulesSlot,
ImportEntriesSlot,
LocalExportEntriesSlot,
@ -113,6 +114,7 @@ class ModuleObject : public NativeObject
JSScript* script() const;
ModuleEnvironmentObject& initialEnvironment() const;
ModuleEnvironmentObject* environment() const;
bool evaluated() const;
ArrayObject& requestedModules() const;
ArrayObject& importEntries() const;
ArrayObject& localExportEntries() const;
@ -123,6 +125,9 @@ class ModuleObject : public NativeObject
void createEnvironment();
void setEvaluated();
bool evaluate(JSContext*cx, MutableHandleValue rval);
private:
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(js::FreeOp* fop, JSObject* obj);
@ -152,6 +157,7 @@ class MOZ_STACK_CLASS ModuleBuilder
JSContext* cx_;
RootedAtomVector requestedModules_;
RootedAtomVector importedBoundNames_;
RootedImportEntryVector importEntries_;
RootedExportEntryVector exportEntries_;

View File

@ -0,0 +1,26 @@
// Test ambigious export * statements.
"use strict";
load(libdir + "asserts.js");
let moduleRepo = new Map();
setModuleResolveHook(function(module, specifier) {
if (specifier in moduleRepo)
return moduleRepo[specifier];
throw "Module " + specifier + " not found";
});
let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;");
let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
let ms = [a, b, c];
ms.map((m) => m.declarationInstantiation());
ms.map((m) => m.evaluation(), moduleRepo.values());
let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;");
d.declarationInstantiation();
assertEq(d.evaluation(), 1);
let e = moduleRepo['e'] = parseModule("import { b } from 'c';");
assertThrowsInstanceOf(() => e.declarationInstantiation(), SyntaxError);

View File

@ -0,0 +1,99 @@
// Exercise ModuleEvaluation() concrete method.
load(libdir + "asserts.js");
let moduleRepo = new Map();
setModuleResolveHook(function(module, specifier) {
if (specifier in moduleRepo)
return moduleRepo[specifier];
throw "Module " + specifier + " not found";
});
function parseAndEvaluate(source) {
let m = parseModule(source);
m.declarationInstantiation();
return m.evaluation();
}
// Check the evaluation of an empty module succeeds.
assertEq(typeof parseAndEvaluate(""), "undefined");
// Check evaluation returns evaluation result the first time, then undefined.
let m = parseModule("1");
m.declarationInstantiation();
assertEq(m.evaluation(), 1);
assertEq(typeof m.evaluation(), "undefined");
// Check top level variables are initialized by evaluation.
m = parseModule("export var x = 2 + 2;");
assertEq(typeof m.initialEnvironment.x, "undefined");
m.declarationInstantiation();
m.evaluation();
assertEq(m.environment.x, 4);
m = parseModule("export let x = 2 * 3;");
m.declarationInstantiation();
m.evaluation();
assertEq(m.environment.x, 6);
// Set up a module to import from.
let a = moduleRepo['a'] =
parseModule(`var x = 1;
export { x };
export default 2;
export function f(x) { return x + 1; }`);
// Check we can evaluate top level definitions.
parseAndEvaluate("var foo = 1;");
parseAndEvaluate("let foo = 1;");
parseAndEvaluate("const foo = 1");
parseAndEvaluate("function foo() {}");
parseAndEvaluate("class foo { constructor() {} }");
// Check we can evaluate all module-related syntax.
parseAndEvaluate("export var foo = 1;");
parseAndEvaluate("export let foo = 1;");
parseAndEvaluate("export const foo = 1;");
parseAndEvaluate("var x = 1; export { x };");
parseAndEvaluate("export default 1");
parseAndEvaluate("export default class { constructor() {} };");
parseAndEvaluate("export default function() {};");
parseAndEvaluate("export default class foo { constructor() {} };");
parseAndEvaluate("export default function foo() {};");
parseAndEvaluate("import a from 'a';");
parseAndEvaluate("import { x } from 'a';");
parseAndEvaluate("import * as ns from 'a';");
parseAndEvaluate("export * from 'a'");
// Test default import
m = parseModule("import a from 'a'; a;")
m.declarationInstantiation();
assertEq(m.evaluation(), 2);
// Test named import
m = parseModule("import { x as y } from 'a'; y;")
m.declarationInstantiation();
assertEq(m.evaluation(), 1);
// Call exported function
m = parseModule("import { f } from 'a'; f(3);")
m.declarationInstantiation();
assertEq(m.evaluation(), 4);
// Test importing an indirect export
moduleRepo['b'] = parseModule("export { x as z } from 'a';");
assertEq(parseAndEvaluate("import { z } from 'b'; z"), 1);
// Test cyclic dependencies
moduleRepo['c1'] = parseModule("export var x = 1; export {y} from 'c2'");
moduleRepo['c2'] = parseModule("export var y = 2; export {x} from 'c1'");
assertDeepEq(parseAndEvaluate(`import { x as x1, y as y1 } from 'c1';
import { x as x2, y as y2 } from 'c2';
[x1, y1, x2, y2]`),
[1, 2, 1, 2]);
// Import access in functions
m = parseModule("import { x } from 'a'; function f() { return x; }")
m.declarationInstantiation();
m.evaluation();
assertEq(m.environment.f(), 1);

View File

@ -672,6 +672,11 @@ BaselineCompiler::initScopeChain()
if (!callVMNonOp(InitFunctionScopeObjectsInfo, phase))
return false;
}
} else if (module()) {
// Modules use a pre-created scope object.
Register scope = R1.scratchReg();
masm.movePtr(ImmGCPtr(&module()->initialEnvironment()), scope);
masm.storePtr(scope, frame.addressOfScopeChain());
} else {
// ScopeChain pointer in BaselineFrame has already been initialized
// in prologue.

View File

@ -48,7 +48,8 @@ BytecodeAnalysis::init(TempAllocator& alloc, GSNCache& gsn)
// Initialize the scope chain slot if either the function needs a CallObject
// or the script uses the scope chain. The latter case is handled below.
usesScopeChain_ = (script_->functionDelazifying() &&
usesScopeChain_ = script_->module() ||
(script_->functionDelazifying() &&
script_->functionDelazifying()->needsCallObject());
MOZ_ASSERT_IF(script_->hasAnyAliasedBindings(), usesScopeChain_);

View File

@ -247,6 +247,9 @@ class CompileInfo
JSFunction* funMaybeLazy() const {
return fun_;
}
ModuleObject* module() const {
return script_->module();
}
bool constructing() const {
return constructing_;
}

View File

@ -1229,6 +1229,9 @@ IonBuilder::initScopeChain(MDefinition* callee)
if (!scope)
return false;
}
} else if (ModuleObject* module = info().module()) {
// Modules use a pre-created scope object.
scope = constant(ObjectValue(module->initialEnvironment()));
} else {
// For global scripts without a non-syntactic global scope, the scope
// chain is the global object.

View File

@ -4691,6 +4691,9 @@ IsCacheableNameReadSlot(HandleObject scopeChain, HandleObject obj,
if (!IsCacheableGetPropReadSlotForIon(obj, holder, shape) &&
!IsCacheableNoProperty(obj, holder, shape, pc, output))
return false;
} else if (obj->is<ModuleEnvironmentObject>()) {
// We don't yet support lookups in a module environment.
return false;
} else if (obj->is<CallObject>()) {
MOZ_ASSERT(obj == holder);
if (!shape->hasDefaultGetter())

View File

@ -123,6 +123,10 @@ class BaselineCompilerShared
return script->functionNonDelazifying();
}
ModuleObject* module() const {
return script->module();
}
PCMappingSlotInfo getStackTopSlotInfo() {
MOZ_ASSERT(frame.numUnsyncedSlots() <= 2);
switch (frame.numUnsyncedSlots()) {

View File

@ -1013,7 +1013,8 @@ js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value*
return false;
Value thisv = ObjectValue(*thisObj);
return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), EXECUTE_GLOBAL,
ExecuteType type = script->module() ? EXECUTE_MODULE : EXECUTE_GLOBAL;
return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), type,
NullFramePtr() /* evalInFrame */, rval);
}

View File

@ -1320,6 +1320,26 @@ intrinsic_CreateImportBinding(JSContext* cx, unsigned argc, Value* vp)
return true;
}
static bool
intrinsic_SetModuleEvaluated(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
module->setEvaluated();
args.rval().setUndefined();
return true;
}
static bool
intrinsic_EvaluateModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
return module->evaluate(cx, args.rval());
}
// The self-hosting global isn't initialized with the normal set of builtins.
// Instead, individual C++-implemented functions that're required by
// self-hosted code are defined as global functions. Accessing these
@ -1568,6 +1588,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0),
JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 2, 0),
JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0),
JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
JS_FS_END
};