diff --git a/src/accessors.cc b/src/accessors.cc index ca6796ef69..abf4680dde 100644 --- a/src/accessors.cc +++ b/src/accessors.cc @@ -202,6 +202,68 @@ Handle Accessors::ArrayLengthInfo( attributes); } +// +// Accessors::ModuleNamespaceToStringTag +// + +void Accessors::ModuleNamespaceToStringTagGetter( + v8::Local name, const v8::PropertyCallbackInfo& info) { + i::Isolate* isolate = reinterpret_cast(info.GetIsolate()); + info.GetReturnValue().Set( + Utils::ToLocal(isolate->factory()->NewStringFromAsciiChecked("Module"))); +} + +Handle Accessors::ModuleNamespaceToStringTagInfo( + Isolate* isolate, PropertyAttributes attributes) { + Handle name = isolate->factory()->to_string_tag_symbol(); + return MakeAccessor(isolate, name, &ModuleNamespaceToStringTagGetter, nullptr, + attributes); +} + +// +// Accessors::ModuleNamespaceEntry +// + +void Accessors::ModuleNamespaceEntryGetter( + v8::Local name, const v8::PropertyCallbackInfo& info) { + i::Isolate* isolate = reinterpret_cast(info.GetIsolate()); + HandleScope scope(isolate); + JSModuleNamespace* holder = + JSModuleNamespace::cast(*Utils::OpenHandle(*info.Holder())); + Handle result; + if (!holder->GetExport(Handle::cast(Utils::OpenHandle(*name))) + .ToHandle(&result)) { + isolate->OptionalRescheduleException(false); + } else { + info.GetReturnValue().Set(Utils::ToLocal(result)); + } +} + +void Accessors::ModuleNamespaceEntrySetter( + v8::Local name, v8::Local val, + const v8::PropertyCallbackInfo& info) { + i::Isolate* isolate = reinterpret_cast(info.GetIsolate()); + HandleScope scope(isolate); + Factory* factory = isolate->factory(); + Handle holder = + Handle::cast(Utils::OpenHandle(*info.Holder())); + + if (info.ShouldThrowOnError()) { + isolate->Throw(*factory->NewTypeError( + MessageTemplate::kStrictReadOnlyProperty, Utils::OpenHandle(*name), + i::Object::TypeOf(isolate, holder), holder)); + isolate->OptionalRescheduleException(false); + } else { + info.GetReturnValue().Set(Utils::ToLocal(factory->ToBoolean(false))); + } +} + +Handle Accessors::ModuleNamespaceEntryInfo( + Isolate* isolate, Handle name, PropertyAttributes attributes) { + return MakeAccessor(isolate, name, &ModuleNamespaceEntryGetter, + &ModuleNamespaceEntrySetter, attributes); +} + // // Accessors::StringLength diff --git a/src/accessors.h b/src/accessors.h index 2171a35c74..813695c696 100644 --- a/src/accessors.h +++ b/src/accessors.h @@ -30,6 +30,7 @@ class AccessorInfo; V(FunctionName) \ V(FunctionLength) \ V(FunctionPrototype) \ + V(ModuleNamespaceToStringTag) \ V(ScriptColumnOffset) \ V(ScriptCompilationType) \ V(ScriptContextData) \ @@ -48,10 +49,11 @@ class AccessorInfo; V(StringLength) #define ACCESSOR_SETTER_LIST(V) \ - V(ReconfigureToDataProperty) \ V(ArrayLengthSetter) \ V(ErrorStackSetter) \ - V(FunctionPrototypeSetter) + V(FunctionPrototypeSetter) \ + V(ModuleNamespaceEntrySetter) \ + V(ReconfigureToDataProperty) // Accessors contains all predefined proxy accessors. @@ -74,6 +76,12 @@ class Accessors : public AllStatic { ACCESSOR_SETTER_LIST(ACCESSOR_SETTER_DECLARATION) #undef ACCESSOR_SETTER_DECLARATION + static void ModuleNamespaceEntryGetter( + v8::Local name, + const v8::PropertyCallbackInfo& info); + static Handle ModuleNamespaceEntryInfo( + Isolate* isolate, Handle name, PropertyAttributes attributes); + enum DescriptorId { #define ACCESSOR_INFO_DECLARATION(name) \ k##name##Getter, \ diff --git a/src/ast/ast-types.cc b/src/ast/ast-types.cc index a075e8e787..3f85f479e9 100644 --- a/src/ast/ast-types.cc +++ b/src/ast/ast-types.cc @@ -208,6 +208,7 @@ AstType::bitset AstBitsetType::Lub(i::Map* map) { case JS_DATE_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_GENERATOR_OBJECT_TYPE: + case JS_MODULE_NAMESPACE_TYPE: case JS_ARRAY_BUFFER_TYPE: case JS_ARRAY_TYPE: case JS_REGEXP_TYPE: // TODO(rossberg): there should be a RegExp type. diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index bec9138fa0..45957f10b0 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -2102,6 +2102,25 @@ void Genesis::InitializeGlobal(Handle global_object, Context::JS_SET_FUN_INDEX); } + { // -- J S M o d u l e N a m e s p a c e + Handle map = + factory->NewMap(JS_MODULE_NAMESPACE_TYPE, JSModuleNamespace::kSize); + Map::SetPrototype(map, isolate->factory()->null_value()); + native_context()->set_js_module_namespace_map(*map); + + // Install @@toStringTag. + PropertyAttributes attribs = + static_cast(DONT_ENUM | READ_ONLY); + Handle toStringTag = + Accessors::ModuleNamespaceToStringTagInfo(isolate, attribs); + AccessorConstantDescriptor d(factory->to_string_tag_symbol(), toStringTag, + attribs); + Map::EnsureDescriptorSlack(map, 1); + map->AppendDescriptor(&d); + + // TODO(neis): Implement and install @@iterator. + } + { // -- I t e r a t o r R e s u l t Handle map = factory->NewMap(JS_OBJECT_TYPE, JSIteratorResult::kSize); diff --git a/src/compiler/types.cc b/src/compiler/types.cc index 3827cccd9f..7e28fe2a0d 100644 --- a/src/compiler/types.cc +++ b/src/compiler/types.cc @@ -203,6 +203,7 @@ Type::bitset BitsetType::Lub(i::Map* map) { case JS_DATE_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_GENERATOR_OBJECT_TYPE: + case JS_MODULE_NAMESPACE_TYPE: case JS_ARRAY_BUFFER_TYPE: case JS_ARRAY_TYPE: case JS_REGEXP_TYPE: // TODO(rossberg): there should be a RegExp type. diff --git a/src/contexts.h b/src/contexts.h index 72f8751b6a..0c16cbbb0c 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -177,6 +177,7 @@ enum ContextLookupFlags { js_array_fast_holey_double_elements_map_index) \ V(JS_MAP_FUN_INDEX, JSFunction, js_map_fun) \ V(JS_MAP_MAP_INDEX, Map, js_map_map) \ + V(JS_MODULE_NAMESPACE_MAP, Map, js_module_namespace_map) \ V(JS_SET_FUN_INDEX, JSFunction, js_set_fun) \ V(JS_SET_MAP_INDEX, Map, js_set_map) \ V(JS_WEAK_MAP_FUN_INDEX, JSFunction, js_weak_map_fun) \ diff --git a/src/factory.cc b/src/factory.cc index da1a1b8ac4..9fff37ef5a 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -1735,6 +1735,10 @@ void Factory::NewJSArrayStorage(Handle array, array->set_length(Smi::FromInt(length)); } +Handle Factory::NewJSModuleNamespace() { + Handle map = isolate()->js_module_namespace_map(); + return Handle::cast(NewJSObjectFromMap(map)); +} Handle Factory::NewJSGeneratorObject( Handle function) { @@ -1765,10 +1769,11 @@ Handle Factory::NewModule(Handle code) { Handle module = Handle::cast(NewStruct(MODULE_TYPE)); module->set_code(*code); - module->set_exports(*exports); - module->set_requested_modules(*requested_modules); - module->set_flags(0); module->set_embedder_data(isolate()->heap()->undefined_value()); + module->set_exports(*exports); + module->set_flags(0); + module->set_module_namespace(isolate()->heap()->undefined_value()); + module->set_requested_modules(*requested_modules); return module; } diff --git a/src/factory.h b/src/factory.h index 82c2317cc0..3d73c334ab 100644 --- a/src/factory.h +++ b/src/factory.h @@ -486,6 +486,8 @@ class Factory final { Handle NewJSGeneratorObject(Handle function); + Handle NewJSModuleNamespace(); + Handle NewModule(Handle code); Handle NewJSArrayBuffer( diff --git a/src/heap/objects-visiting.cc b/src/heap/objects-visiting.cc index 9393fcc615..09a02a21a8 100644 --- a/src/heap/objects-visiting.cc +++ b/src/heap/objects-visiting.cc @@ -107,6 +107,7 @@ StaticVisitorBase::VisitorId StaticVisitorBase::GetVisitorId( case JS_ARGUMENTS_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_GENERATOR_OBJECT_TYPE: + case JS_MODULE_NAMESPACE_TYPE: case JS_VALUE_TYPE: case JS_DATE_TYPE: case JS_ARRAY_TYPE: diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc index a6b38ec3a9..fc5a4a4fa6 100644 --- a/src/interpreter/bytecode-generator.cc +++ b/src/interpreter/bytecode-generator.cc @@ -678,6 +678,9 @@ void BytecodeGenerator::GenerateBytecodeBody() { // Visit declarations within the function scope. VisitDeclarations(scope()->declarations()); + // Emit initializing assignments for module namespace imports (if any). + VisitModuleNamespaceImports(); + // Perform a stack-check before the body. builder()->StackCheck(info()->literal()->start_position()); @@ -877,6 +880,24 @@ void BytecodeGenerator::VisitFunctionDeclaration(FunctionDeclaration* decl) { } } +void BytecodeGenerator::VisitModuleNamespaceImports() { + if (!scope()->is_module_scope()) return; + + RegisterAllocationScope register_scope(this); + Register module_request = register_allocator()->NewRegister(); + + ModuleDescriptor* descriptor = scope()->AsModuleScope()->module(); + for (auto entry : descriptor->namespace_imports()) { + builder() + ->LoadLiteral(Smi::FromInt(entry->module_request)) + .StoreAccumulatorInRegister(module_request) + .CallRuntime(Runtime::kGetModuleNamespace, module_request); + Variable* var = scope()->LookupLocal(entry->local_name); + DCHECK_NOT_NULL(var); + VisitVariableAssignment(var, Token::INIT, FeedbackVectorSlot::Invalid()); + } +} + void BytecodeGenerator::VisitDeclarations( ZoneList* declarations) { RegisterAllocationScope register_scope(this); diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h index 03067de08d..0447aa8f45 100644 --- a/src/interpreter/bytecode-generator.h +++ b/src/interpreter/bytecode-generator.h @@ -143,6 +143,7 @@ class BytecodeGenerator final : public AstVisitor { ObjectLiteralProperty* property, Register value_out); void VisitForInAssignment(Expression* expr, FeedbackVectorSlot slot); + void VisitModuleNamespaceImports(); // Visit the header/body of a loop iteration. void VisitIterationHeader(IterationStatement* stmt, diff --git a/src/objects-body-descriptors-inl.h b/src/objects-body-descriptors-inl.h index 0252b64650..d288802474 100644 --- a/src/objects-body-descriptors-inl.h +++ b/src/objects-body-descriptors-inl.h @@ -468,6 +468,7 @@ ReturnType BodyDescriptorApply(InstanceType type, T1 p1, T2 p2, T3 p3) { case JS_VALUE_TYPE: case JS_DATE_TYPE: case JS_ARRAY_TYPE: + case JS_MODULE_NAMESPACE_TYPE: case JS_TYPED_ARRAY_TYPE: case JS_DATA_VIEW_TYPE: case JS_SET_TYPE: diff --git a/src/objects-debug.cc b/src/objects-debug.cc index e6893cdc25..2986982c0f 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -140,6 +140,9 @@ void HeapObject::HeapObjectVerify() { case JS_ARRAY_TYPE: JSArray::cast(this)->JSArrayVerify(); break; + case JS_MODULE_NAMESPACE_TYPE: + JSModuleNamespace::cast(this)->JSModuleNamespaceVerify(); + break; case JS_SET_TYPE: JSSet::cast(this)->JSSetVerify(); break; @@ -919,7 +922,13 @@ void PromiseContainer::PromiseContainerVerify() { after_debug_event()->ObjectVerify(); } +void JSModuleNamespace::JSModuleNamespaceVerify() { + CHECK(IsJSModuleNamespace()); + module()->ObjectVerify(); +} + void Module::ModuleVerify() { + Isolate* isolate = GetIsolate(); CHECK(IsModule()); CHECK(code()->IsSharedFunctionInfo() || code()->IsJSFunction()); code()->ObjectVerify(); @@ -928,6 +937,8 @@ void Module::ModuleVerify() { VerifySmiField(kFlagsOffset); embedder_data()->ObjectVerify(); CHECK(shared()->name()->IsSymbol()); + CHECK(module_namespace()->IsUndefined(isolate) || + module_namespace()->IsJSModuleNamespace()); // TODO(neis): Check more. } diff --git a/src/objects-inl.h b/src/objects-inl.h index 24343dc185..4e34116d13 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -3277,6 +3277,7 @@ CAST_ACCESSOR(JSGlobalProxy) CAST_ACCESSOR(JSMap) CAST_ACCESSOR(JSMapIterator) CAST_ACCESSOR(JSMessageObject) +CAST_ACCESSOR(JSModuleNamespace) CAST_ACCESSOR(JSObject) CAST_ACCESSOR(JSProxy) CAST_ACCESSOR(JSReceiver) @@ -5706,8 +5707,11 @@ BOOL_ACCESSORS(PrototypeInfo, bit_field, should_be_fast_map, kShouldBeFastBit) ACCESSORS(ContextExtension, scope_info, ScopeInfo, kScopeInfoOffset) ACCESSORS(ContextExtension, extension, Object, kExtensionOffset) +ACCESSORS(JSModuleNamespace, module, Module, kModuleOffset) + ACCESSORS(Module, code, Object, kCodeOffset) ACCESSORS(Module, exports, ObjectHashTable, kExportsOffset) +ACCESSORS(Module, module_namespace, HeapObject, kModuleNamespaceOffset) ACCESSORS(Module, requested_modules, FixedArray, kRequestedModulesOffset) SMI_ACCESSORS(Module, flags, kFlagsOffset) BOOL_ACCESSORS(Module, flags, evaluated, kEvaluatedBit) @@ -6664,6 +6668,8 @@ bool JSGeneratorObject::is_executing() const { return continuation() == kGeneratorExecuting; } +TYPE_CHECKER(JSModuleNamespace, JS_MODULE_NAMESPACE_TYPE) + ACCESSORS(JSValue, value, Object, kValueOffset) diff --git a/src/objects.cc b/src/objects.cc index 71f6356e46..2bf4fe3adb 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -6556,7 +6556,7 @@ Maybe JSReceiver::DefineOwnProperty(Isolate* isolate, return JSProxy::DefineOwnProperty(isolate, Handle::cast(object), key, desc, should_throw); } - // TODO(jkummerow): Support Modules (ES6 9.4.6.6) + // TODO(neis): Special case for JSModuleNamespace? // OrdinaryDefineOwnProperty, by virtue of calling // DefineOwnPropertyIgnoreAttributes, can handle arguments (ES6 9.4.4.2) @@ -18218,6 +18218,9 @@ Object* ObjectHashTable::Lookup(Handle key) { return Lookup(isolate, key, Smi::cast(hash)->value()); } +Object* ObjectHashTable::ValueAt(int entry) { + return get(EntryToValueIndex(entry)); +} Object* ObjectHashTable::Lookup(Handle key, int32_t hash) { return Lookup(GetIsolate(), key, hash); @@ -19565,6 +19568,23 @@ bool JSReceiver::HasProxyInPrototype(Isolate* isolate) { return false; } +MaybeHandle JSModuleNamespace::GetExport(Handle name) { + Isolate* isolate = name->GetIsolate(); + + Handle object(module()->exports()->Lookup(name), isolate); + if (object->IsTheHole(isolate)) { + return isolate->factory()->undefined_value(); + } + + Handle value(Handle::cast(object)->value(), isolate); + if (value->IsTheHole(isolate)) { + THROW_NEW_ERROR( + isolate, NewReferenceError(MessageTemplate::kNotDefined, name), Object); + } + + return value; +} + namespace { template @@ -19596,6 +19616,35 @@ class UnorderedStringSet StringHandleEqual(), zone_allocator>(zone)) {} }; +class UnorderedModuleSet + : public std::unordered_set, HandleValueHash, + ModuleHandleEqual, + zone_allocator>> { + public: + explicit UnorderedModuleSet(Zone* zone) + : std::unordered_set, HandleValueHash, + ModuleHandleEqual, zone_allocator>>( + 2 /* bucket count */, HandleValueHash(), + ModuleHandleEqual(), zone_allocator>(zone)) {} +}; + +class UnorderedStringMap + : public std::unordered_map< + Handle, Handle, HandleValueHash, + StringHandleEqual, + zone_allocator, Handle>>> { + public: + explicit UnorderedStringMap(Zone* zone) + : std::unordered_map< + Handle, Handle, HandleValueHash, + StringHandleEqual, + zone_allocator, Handle>>>( + 2 /* bucket count */, HandleValueHash(), + StringHandleEqual(), + zone_allocator, Handle>>( + zone)) {} +}; + } // anonymous namespace class Module::ResolveSet @@ -19919,5 +19968,138 @@ MaybeHandle Module::Evaluate(Handle module) { return Execution::Call(isolate, resume, generator, 0, nullptr); } +namespace { + +void FetchStarExports(Handle module, Zone* zone, + UnorderedModuleSet* visited) { + DCHECK(module->code()->IsJSFunction()); // Instantiated. + + bool cycle = !visited->insert(module).second; + if (cycle) return; + + Isolate* isolate = module->GetIsolate(); + Handle exports(module->exports(), isolate); + UnorderedStringMap more_exports(zone); + + // TODO(neis): Only allocate more_exports if there are star exports. + // Maybe split special_exports into indirect_exports and star_exports. + + Handle special_exports(module->info()->special_exports(), + isolate); + for (int i = 0, n = special_exports->length(); i < n; ++i) { + Handle entry( + ModuleInfoEntry::cast(special_exports->get(i)), isolate); + if (!entry->export_name()->IsUndefined(isolate)) { + continue; // Indirect export. + } + + int module_request = Smi::cast(entry->module_request())->value(); + Handle requested_module( + Module::cast(module->requested_modules()->get(module_request)), + isolate); + + // Recurse. + FetchStarExports(requested_module, zone, visited); + + // Collect all of [requested_module]'s exports that must be added to + // [module]'s exports (i.e. to [exports]). We record these in + // [more_exports]. Ambiguities (conflicting exports) are marked by mapping + // the name to undefined instead of a Cell. + Handle requested_exports(requested_module->exports(), + isolate); + for (int i = 0, n = requested_exports->Capacity(); i < n; ++i) { + Handle key(requested_exports->KeyAt(i), isolate); + if (!requested_exports->IsKey(isolate, *key)) continue; + Handle name = Handle::cast(key); + + if (name->Equals(isolate->heap()->default_string())) continue; + if (!exports->Lookup(name)->IsTheHole(isolate)) continue; + + Handle cell(Cell::cast(requested_exports->ValueAt(i)), isolate); + auto insert_result = more_exports.insert(std::make_pair(name, cell)); + if (!insert_result.second) { + auto it = insert_result.first; + if (*it->second == *cell || it->second->IsUndefined(isolate)) { + // We already recorded this mapping before, or the name is already + // known to be ambiguous. In either case, there's nothing to do. + } else { + DCHECK(it->second->IsCell()); + // Different star exports provide different cells for this name, hence + // mark the name as ambiguous. + it->second = isolate->factory()->undefined_value(); + } + } + } + } + + // Copy [more_exports] into [exports]. + for (const auto& elem : more_exports) { + if (elem.second->IsUndefined(isolate)) continue; // Ambiguous export. + DCHECK(!elem.first->Equals(isolate->heap()->default_string())); + DCHECK(elem.second->IsCell()); + exports = ObjectHashTable::Put(exports, elem.first, elem.second); + } + module->set_exports(*exports); +} + +} // anonymous namespace + +Handle Module::GetModuleNamespace(Handle module, + int module_request) { + Isolate* isolate = module->GetIsolate(); + Handle requested_module( + Module::cast(module->requested_modules()->get(module_request)), isolate); + return Module::GetModuleNamespace(requested_module); +} + +Handle Module::GetModuleNamespace(Handle module) { + Isolate* isolate = module->GetIsolate(); + + Handle object(module->module_namespace(), isolate); + if (!object->IsUndefined(isolate)) { + // Namespace object already exists. + return Handle::cast(object); + } + + // Create the namespace object (initially empty). + Handle ns = isolate->factory()->NewJSModuleNamespace(); + ns->set_module(*module); + module->set_module_namespace(*ns); + + // Collect the export names. + Zone zone(isolate->allocator()); + UnorderedModuleSet visited(&zone); + FetchStarExports(module, &zone, &visited); + Handle exports(module->exports(), isolate); + ZoneVector> names(&zone); + names.reserve(exports->NumberOfElements()); + for (int i = 0, n = exports->Capacity(); i < n; ++i) { + Handle key(exports->KeyAt(i), isolate); + if (!exports->IsKey(isolate, *key)) continue; + DCHECK(exports->ValueAt(i)->IsCell()); + names.push_back(Handle::cast(key)); + } + DCHECK_EQ(names.size(), exports->NumberOfElements()); + + // Sort them alphabetically. + struct { + bool operator()(Handle a, Handle b) { + return String::Compare(a, b) == ComparisonResult::kLessThan; + } + } StringLess; + std::sort(names.begin(), names.end(), StringLess); + + // Create the corresponding properties in the namespace object. + PropertyAttributes attr = DONT_DELETE; + for (const auto& name : names) { + JSObject::SetAccessor( + ns, Accessors::ModuleNamespaceEntryInfo(isolate, name, attr)) + .Check(); + } + JSObject::PreventExtensions(ns, THROW_ON_ERROR).ToChecked(); + + return ns; +} + } // namespace internal } // namespace v8 diff --git a/src/objects.h b/src/objects.h index 848de5f41d..927c6c7f46 100644 --- a/src/objects.h +++ b/src/objects.h @@ -71,6 +71,7 @@ // - JSValue // - JSDate // - JSMessageObject +// - JSModuleNamespace // - JSProxy // - FixedArrayBase // - ByteArray @@ -416,6 +417,7 @@ const int kStubMinorKeyBits = kSmiValueSize - kStubMajorKeyBits - 1; V(JS_ARGUMENTS_TYPE) \ V(JS_CONTEXT_EXTENSION_OBJECT_TYPE) \ V(JS_GENERATOR_OBJECT_TYPE) \ + V(JS_MODULE_NAMESPACE_TYPE) \ V(JS_GLOBAL_OBJECT_TYPE) \ V(JS_GLOBAL_PROXY_TYPE) \ V(JS_API_OBJECT_TYPE) \ @@ -717,6 +719,7 @@ enum InstanceType { JS_ARGUMENTS_TYPE, JS_CONTEXT_EXTENSION_OBJECT_TYPE, JS_GENERATOR_OBJECT_TYPE, + JS_MODULE_NAMESPACE_TYPE, JS_ARRAY_TYPE, JS_ARRAY_BUFFER_TYPE, JS_TYPED_ARRAY_TYPE, @@ -886,6 +889,7 @@ class LayoutDescriptor; class LiteralsArray; class LookupIterator; class FieldType; +class Module; class ModuleDescriptor; class ModuleInfoEntry; class ModuleInfo; @@ -970,6 +974,7 @@ template inline bool Is(Object* obj); V(JSObject) \ V(JSContextExtensionObject) \ V(JSGeneratorObject) \ + V(JSModuleNamespace) \ V(Map) \ V(DescriptorArray) \ V(FrameArray) \ @@ -3951,6 +3956,9 @@ class ObjectHashTable: public HashTable key, int32_t hash); Object* Lookup(Isolate* isolate, Handle key, int32_t hash); + // Returns the value at entry. + Object* ValueAt(int entry); + // Adds (or overwrites) the value associated with the given key. static Handle Put(Handle table, Handle key, @@ -7863,6 +7871,29 @@ class JSGeneratorObject: public JSObject { DISALLOW_IMPLICIT_CONSTRUCTORS(JSGeneratorObject); }; +// When importing a module namespace (import * as foo from "bar"), a +// JSModuleNamespace object (representing module "bar") is created and bound to +// the declared variable (foo). A module can have at most one namespace object. +class JSModuleNamespace : public JSObject { + public: + DECLARE_CAST(JSModuleNamespace) + DECLARE_VERIFIER(JSModuleNamespace) + + // The actual module whose namespace is being represented. + DECL_ACCESSORS(module, Module) + + // Retrieve the value exported by [module] under the given [name]. If there is + // no such export, return Just(undefined). If the export is uninitialized, + // schedule an exception and return Nothing. + MUST_USE_RESULT MaybeHandle GetExport(Handle name); + + static const int kModuleOffset = JSObject::kHeaderSize; + static const int kSize = kModuleOffset + kPointerSize; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(JSModuleNamespace); +}; + // A Module object is a mapping from export names to cells // This is still very much in flux. class Module : public Struct { @@ -7871,13 +7902,15 @@ class Module : public Struct { DECLARE_VERIFIER(Module) DECLARE_PRINTER(Module) - // The code representing this Module, either a - // SharedFunctionInfo or a JSFunction depending - // on whether it's been instantiated. + // The code representing this Module, either a SharedFunctionInfo or a + // JSFunction depending on whether it's been instantiated. DECL_ACCESSORS(code, Object) DECL_ACCESSORS(exports, ObjectHashTable) + // The namespace object (or undefined). + DECL_ACCESSORS(module_namespace, HeapObject) + // [[RequestedModules]]: Modules imported or re-exported by this module. // Corresponds 1-to-1 to the module specifier strings in // ModuleInfo::module_requests. @@ -7887,7 +7920,7 @@ class Module : public Struct { // are only evaluated a single time. DECL_BOOLEAN_ACCESSORS(evaluated) - // Storage for [[Evaluated]] + // Storage for [[Evaluated]]. DECL_INT_ACCESSORS(flags) // Embedder-specified data @@ -7920,12 +7953,18 @@ class Module : public Struct { static Handle LoadImport(Handle module, Handle name, int module_request); + // Get the namespace object for [module_request] of [module]. If it doesn't + // exist yet, it is created. + static Handle GetModuleNamespace(Handle module, + int module_request); + static const int kCodeOffset = HeapObject::kHeaderSize; static const int kExportsOffset = kCodeOffset + kPointerSize; static const int kRequestedModulesOffset = kExportsOffset + kPointerSize; static const int kFlagsOffset = kRequestedModulesOffset + kPointerSize; static const int kEmbedderDataOffset = kFlagsOffset + kPointerSize; - static const int kSize = kEmbedderDataOffset + kPointerSize; + static const int kModuleNamespaceOffset = kEmbedderDataOffset + kPointerSize; + static const int kSize = kModuleNamespaceOffset + kPointerSize; private: enum { kEvaluatedBit }; @@ -7934,6 +7973,10 @@ class Module : public Struct { static void CreateIndirectExport(Handle module, Handle name, Handle entry); + // Get the namespace object for [module]. If it doesn't exist yet, it is + // created. + static Handle GetModuleNamespace(Handle module); + // The [must_resolve] argument indicates whether or not an exception should be // thrown in case the module does not provide an export named [name] // (including when a cycle is detected). An exception is always thrown in the diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc index 53475eed40..a4d0429b2f 100644 --- a/src/runtime/runtime-object.cc +++ b/src/runtime/runtime-object.cc @@ -960,6 +960,14 @@ RUNTIME_FUNCTION(Runtime_CreateDataProperty) { return *value; } +RUNTIME_FUNCTION(Runtime_GetModuleNamespace) { + HandleScope scope(isolate); + DCHECK(args.length() == 1); + CONVERT_SMI_ARG_CHECKED(module_request, 0); + Handle module(isolate->context()->module()); + return *Module::GetModuleNamespace(module, module_request); +} + RUNTIME_FUNCTION(Runtime_LoadModuleExport) { HandleScope scope(isolate); DCHECK(args.length() == 1); @@ -972,9 +980,9 @@ RUNTIME_FUNCTION(Runtime_LoadModuleImport) { HandleScope scope(isolate); DCHECK(args.length() == 2); CONVERT_ARG_HANDLE_CHECKED(String, name, 0); - CONVERT_ARG_HANDLE_CHECKED(Smi, module_request, 1); + CONVERT_SMI_ARG_CHECKED(module_request, 1); Handle module(isolate->context()->module()); - return *Module::LoadImport(module, name, module_request->value()); + return *Module::LoadImport(module, name, module_request); } RUNTIME_FUNCTION(Runtime_StoreModuleExport) { diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 7c55b13d8f..e217817f9b 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -420,6 +420,7 @@ namespace internal { F(CreateIterResultObject, 2, 1) \ F(IsAccessCheckNeeded, 1, 1) \ F(CreateDataProperty, 3, 1) \ + F(GetModuleNamespace, 1, 1) \ F(LoadModuleExport, 1, 1) \ F(LoadModuleImport, 2, 1) \ F(StoreModuleExport, 2, 1) diff --git a/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden b/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden index 3d4f5f7cc7..1568ffafc3 100644 --- a/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden +++ b/test/cctest/interpreter/bytecode_expectations/CallRuntime.golden @@ -78,7 +78,7 @@ bytecodes: [ /* 15 S> */ B(LdrUndefined), R(0), B(CreateArrayLiteral), U8(0), U8(0), U8(9), B(Star), R(1), - B(CallJSRuntime), U8(141), R(0), U8(2), + B(CallJSRuntime), U8(142), R(0), U8(2), /* 44 S> */ B(Return), ] constant pool: [ diff --git a/test/mjsunit/modules-namespace1.js b/test/mjsunit/modules-namespace1.js new file mode 100644 index 0000000000..29d1a5e459 --- /dev/null +++ b/test/mjsunit/modules-namespace1.js @@ -0,0 +1,82 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MODULE + +let ja = 42; +export {ja as yo}; +export const bla = "blaa"; +export {foo as foo_again}; +// See further below for the actual star import that declares "foo". + +// The object itself. +assertEquals("object", typeof foo); +assertThrows(() => foo = 666, TypeError); +assertFalse(Reflect.isExtensible(foo)); +assertTrue(Reflect.preventExtensions(foo)); +assertThrows(() => Reflect.apply(foo, {}, [])); +assertThrows(() => Reflect.construct(foo, {}, [])); +assertSame(null, Reflect.getPrototypeOf(foo)); +// TODO(neis): The next one should be False. +assertTrue(Reflect.setPrototypeOf(foo, null)); +assertFalse(Reflect.setPrototypeOf(foo, {})); +assertSame(null, Reflect.getPrototypeOf(foo)); +// TODO(neis): The next one should include @@iterator at the end. +assertEquals( + ["bla", "foo_again", "yo", Symbol.toStringTag], + Reflect.ownKeys(foo)); + +// Its "yo" property. +assertEquals( + {value: 42, enumerable: true, configurable: false, writable: true}, + Reflect.getOwnPropertyDescriptor(foo, "yo")); +assertFalse(Reflect.deleteProperty(foo, "yo")); +assertTrue(Reflect.has(foo, "yo")); +// TODO(neis): The next three should be False. +assertTrue(Reflect.set(foo, "yo", true)); +assertTrue(Reflect.defineProperty(foo, "yo", + Reflect.getOwnPropertyDescriptor(foo, "yo"))); +assertTrue(Reflect.defineProperty(foo, "yo", {})); +assertFalse(Reflect.defineProperty(foo, "yo", {get() {return 1}})); +assertEquals(42, Reflect.get(foo, "yo")); +assertEquals(43, (ja++, foo.yo)); + +// Its "foo_again" property. +assertSame(foo, foo.foo_again); + +// Its @@toStringTag property. +assertTrue(Reflect.has(foo, Symbol.toStringTag)); +assertEquals("string", typeof Reflect.get(foo, Symbol.toStringTag)); +assertEquals( + {value: "Module", configurable: true, writable: false, enumerable: false}, + Reflect.getOwnPropertyDescriptor(foo, Symbol.toStringTag)); + +// TODO(neis): Its @@iterator property. +// assertTrue(Reflect.has(foo, Symbol.iterator)); +// assertEquals("function", typeof Reflect.get(foo, Symbol.iterator)); +// assertEquals(["bla", "yo"], [...foo]); +// assertThrows(() => (42, foo[Symbol.iterator])(), TypeError); +// assertSame(foo[Symbol.iterator]().__proto__, +// ([][Symbol.iterator]()).__proto__.__proto__); + +// TODO(neis): Clarify spec w.r.t. other symbols. + +// Nonexistant properties. +let nonexistant = ["gaga", 123, Symbol('')]; +for (let key of nonexistant) { + assertSame(undefined, Reflect.getOwnPropertyDescriptor(foo, key)); + assertTrue(Reflect.deleteProperty(foo, key)); + assertFalse(Reflect.set(foo, key, true)); + assertSame(undefined, Reflect.get(foo, key)); + assertFalse(Reflect.defineProperty(foo, key, {get() {return 1}})); + assertFalse(Reflect.has(foo, key)); +} + +// The actual star import that we are testing. Namespace imports are +// initialized before evaluation +import * as foo from "modules-namespace1.js"; + +// There can be only one namespace object. +import * as bar from "modules-namespace1.js"; +assertSame(foo, bar); diff --git a/test/mjsunit/modules-namespace2.js b/test/mjsunit/modules-namespace2.js new file mode 100644 index 0000000000..cc0c73f5cd --- /dev/null +++ b/test/mjsunit/modules-namespace2.js @@ -0,0 +1,22 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MODULE + +// TODO(neis): Symbol.iterator +assertEquals( + ["b", "c", "get_a", "ns2", "set_a", "zzz", Symbol.toStringTag], + Reflect.ownKeys(ns)); +// assertEquals(["b", "c", "get_a", "ns2", "set_a", "zzz"], [...ns]); + +import * as foo from "modules-skip-1.js"; +assertSame(foo.a, ns.b); +assertSame(foo.a, ns.c); +assertSame(foo.get_a, ns.get_a); +assertSame(foo.set_a, ns.set_a); +assertEquals(123, ns.zzz); + +assertSame(ns, ns.ns2.ns); +import * as ns from "modules-skip-namespace.js"; +export {ns}; diff --git a/test/mjsunit/modules-namespace3.js b/test/mjsunit/modules-namespace3.js new file mode 100644 index 0000000000..df9ef7806b --- /dev/null +++ b/test/mjsunit/modules-namespace3.js @@ -0,0 +1,11 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MODULE + +import * as foo from "modules-namespace3.js"; +export * from "modules-namespace3.js"; +export var bar; +assertEquals(["bar", "default"], Object.getOwnPropertyNames(foo)); +export default function() {}; diff --git a/test/mjsunit/modules-namespace4.js b/test/mjsunit/modules-namespace4.js new file mode 100644 index 0000000000..5a20f5e727 --- /dev/null +++ b/test/mjsunit/modules-namespace4.js @@ -0,0 +1,37 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// MODULE + +import * as foo from "modules-namespace4.js"; + +assertSame(undefined, a); +assertThrows(() => b, ReferenceError); +assertThrows(() => c, ReferenceError); +assertEquals(45, d()); + +assertSame(undefined, foo.a); +assertThrows(() => foo.b, ReferenceError); +assertThrows(() => foo.c, ReferenceError); +assertEquals(45, foo.d()); +assertThrows(() => foo.default, ReferenceError); +assertSame(undefined, foo.doesnotexist); + +export var a = 42; +export let b = 43; +export const c = 44; +export function d() { return 45 }; +export default 46; + +assertEquals(42, a); +assertEquals(43, b); +assertEquals(44, c); +assertEquals(45, d()); + +assertEquals(42, foo.a); +assertEquals(43, foo.b); +assertEquals(44, foo.c); +assertEquals(45, foo.d()); +assertEquals(46, foo.default); +assertSame(undefined, foo.doesnotexist); diff --git a/test/mjsunit/modules-skip-2.js b/test/mjsunit/modules-skip-2.js index fdd576a988..d5ff578b49 100644 --- a/test/mjsunit/modules-skip-2.js +++ b/test/mjsunit/modules-skip-2.js @@ -5,3 +5,4 @@ export {a as b, default} from "modules-skip-1.js"; import {a as tmp} from "modules-skip-1.js"; export {tmp as c}; +export const zzz = 999; diff --git a/test/mjsunit/modules-skip-namespace.js b/test/mjsunit/modules-skip-namespace.js new file mode 100644 index 0000000000..ff6a7b81d3 --- /dev/null +++ b/test/mjsunit/modules-skip-namespace.js @@ -0,0 +1,13 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//assertEquals( +// ["ns", Symbol.toStringTag, Symbol.iterator], Reflect.ownKeys(ns2)); +//assertEquals(["ns"], [...ns2]); + +export * from "modules-skip-4.js"; +export * from "modules-skip-5.js"; +export var zzz = 123; +export {ns2}; +import * as ns2 from "modules-namespace2.js";