Commit 24b88776 authored by Jakob Kummerow's avatar Jakob Kummerow Committed by Commit Bot

[modules] Speed up access to module exports

By adding LoadIC support for JSModuleNamespace objects. The index
of the corresponding slot in the Module's "exports" dictionary is
cached in the feedback vector, so the value can be loaded directly,
without having to call the C++ accessor.
This speeds up the "foo" property access in code like the following
snippet by about 10x:
  import * as m from "module.js"
  m.foo;

Bug: v8:1569
Change-Id: I152abedcbdc6f90b5bedd203cfdf97ed88d1137c
Reviewed-on: https://chromium-review.googlesource.com/631136
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47625}
parent 109e8b98
......@@ -3460,6 +3460,10 @@ Node* CodeStubAssembler::IsCallable(Node* object) {
return IsCallableMap(LoadMap(object));
}
Node* CodeStubAssembler::IsCell(Node* object) {
return WordEqual(LoadMap(object), LoadRoot(Heap::kCellMapRootIndex));
}
Node* CodeStubAssembler::IsConstructorMap(Node* map) {
CSA_ASSERT(this, IsMap(map));
return IsSetWord32(LoadMapBitField(map), 1 << Map::kIsConstructor);
......
......@@ -869,6 +869,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsExtensibleMap(Node* map);
Node* IsCallableMap(Node* map);
Node* IsCallable(Node* object);
Node* IsCell(Node* object);
Node* IsConsStringInstanceType(Node* instance_type);
Node* IsConstructorMap(Node* map);
Node* IsConstructor(Node* object);
......
......@@ -262,7 +262,7 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
Label constant(this), field(this), normal(this, Label::kDeferred),
interceptor(this, Label::kDeferred), nonexistent(this),
accessor(this, Label::kDeferred), proxy(this, Label::kDeferred),
global(this, Label::kDeferred);
global(this, Label::kDeferred), module_export(this, Label::kDeferred);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)),
......@@ -277,10 +277,13 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)),
&accessor);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)),
&global);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), &proxy);
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), &global,
&interceptor);
Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport)),
&module_export, &interceptor);
BIND(&field);
HandleLoadField(holder, handler_word, &var_double_value, &rebox_double,
......@@ -393,6 +396,31 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
p->slot, p->vector);
}
BIND(&module_export);
{
Comment("module export");
Node* index = DecodeWord<LoadHandler::ExportsIndexBits>(handler_word);
Node* module =
LoadObjectField(p->receiver, JSModuleNamespace::kModuleOffset,
MachineType::TaggedPointer());
Node* exports = LoadObjectField(module, Module::kExportsOffset,
MachineType::TaggedPointer());
Node* cell = LoadFixedArrayElement(exports, index);
// The handler is only installed for exports that exist.
CSA_ASSERT(this, IsCell(cell));
Node* value = LoadCellValue(cell);
Label is_the_hole(this, Label::kDeferred);
GotoIf(IsTheHole(value), &is_the_hole);
exit_point->Return(value);
BIND(&is_the_hole);
{
Node* message = SmiConstant(MessageTemplate::kNotDefined);
exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context,
message, p->name);
}
}
BIND(&rebox_double);
exit_point->Return(AllocateHeapNumberWithValue(var_double_value.value()));
}
......@@ -1653,6 +1681,7 @@ void AccessorAssembler::GenericPropertyLoad(Node* receiver, Node* receiver_map,
BIND(&special_receiver);
{
// TODO(jkummerow): Consider supporting JSModuleNamespace.
GotoIfNot(Word32Equal(instance_type, Int32Constant(JS_PROXY_TYPE)), slow);
direct_exit.ReturnCallStub(
......
......@@ -64,6 +64,12 @@ Handle<Smi> LoadHandler::LoadApiGetter(Isolate* isolate, int descriptor) {
return handle(Smi::FromInt(config), isolate);
}
Handle<Smi> LoadHandler::LoadModuleExport(Isolate* isolate, int index) {
int config =
KindBits::encode(kModuleExport) | ExportsIndexBits::encode(index);
return handle(Smi::FromInt(config), isolate);
}
Handle<Smi> LoadHandler::EnableAccessCheckOnReceiver(Isolate* isolate,
Handle<Smi> smi_handler) {
int config = smi_handler->value();
......
......@@ -26,7 +26,8 @@ class LoadHandler {
kAccessor,
kInterceptor,
kProxy,
kNonExistent
kNonExistent,
kModuleExport
};
class KindBits : public BitField<Kind, 0, 4> {};
......@@ -77,6 +78,12 @@ class LoadHandler {
// Make sure we don't overflow the smi.
STATIC_ASSERT(ElementsKindBits::kNext <= kSmiValueSize);
//
// Encoding when KindBits contains kModuleExport.
//
class ExportsIndexBits : public BitField<unsigned, KindBits::kNext,
kSmiValueSize - KindBits::kNext> {};
// The layout of an Tuple3 handler representing a load of a field from
// prototype when prototype chain checks do not include non-existing lookups
// or access checks.
......@@ -120,6 +127,11 @@ class LoadHandler {
// Creates a Smi-handler for loading an Api getter property from fast object.
static inline Handle<Smi> LoadApiGetter(Isolate* isolate, int descriptor);
// Creates a Smi-handler for loading a Module export.
// |index| is the index to the "value" slot in the Module's "exports"
// dictionary.
static inline Handle<Smi> LoadModuleExport(Isolate* isolate, int index);
// Sets DoAccessCheckOnReceiverBits in given Smi-handler. The receiver
// check is a part of a prototype chain check.
static inline Handle<Smi> EnableAccessCheckOnReceiver(
......
......@@ -1020,6 +1020,17 @@ Handle<Object> LoadIC::GetMapIndependentHandler(LookupIterator* lookup) {
FieldIndex index = FieldIndex::ForInObjectOffset(object_offset, *map);
return SimpleFieldLoad(isolate(), index);
}
if (holder->IsJSModuleNamespace()) {
Handle<ObjectHashTable> exports(
Handle<JSModuleNamespace>::cast(holder)->module()->exports(),
isolate());
int entry = exports->FindEntry(isolate(), lookup->name(),
Smi::ToInt(lookup->name()->GetHash()));
// We found the accessor, so the entry must exist.
DCHECK(entry != ObjectHashTable::kNotFound);
int index = ObjectHashTable::EntryToValueIndex(entry);
return LoadHandler::LoadModuleExport(isolate(), index);
}
Handle<Object> accessors = lookup->GetAccessors();
if (accessors->IsAccessorPair()) {
......
......@@ -271,6 +271,7 @@ class ObjectHashTableShape : public BaseShape<Handle<Object>> {
static inline uint32_t HashForObject(Isolate* isolate, Object* object);
static inline Handle<Object> AsHandle(Isolate* isolate, Handle<Object> key);
static const int kPrefixSize = 0;
static const int kEntryValueIndex = 1;
static const int kEntrySize = 2;
static const bool kNeedsHoleCheck = false;
};
......@@ -311,16 +312,16 @@ class ObjectHashTable
Handle<Object> key, bool* was_present,
int32_t hash);
// Returns the index to the value of an entry.
static inline int EntryToValueIndex(int entry) {
return EntryToIndex(entry) + ObjectHashTableShape::kEntryValueIndex;
}
protected:
friend class MarkCompactCollector;
void AddEntry(int entry, Object* key, Object* value);
void RemoveEntry(int entry);
// Returns the index to the value of an entry.
static inline int EntryToValueIndex(int entry) {
return EntryToIndex(entry) + 1;
}
};
class ObjectHashSetShape : public ObjectHashTableShape {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment