[MERGE #5032 @sethbrenith] Share type handler for strict-mode arguments object
Merge pull request #5032 from sethbrenith:user/sethb/strict-args
The Preact test in Speedometer is spending around 3% of its script time in LoadHeapArguments, which is currently slower for strict mode than sloppy mode because we don't share types. This change updates CreateHeapArguments to use a single shared type for all strict-mode `arguments` objects, which improves the following microbenchmark by about 34%. Overall Preact improvement is much more modest; I'd guess 0.3% based on trace data comparison.
```javascript
function arg() {
"use strict";
return Array.prototype.slice.call(arguments)[0];
}
const start = Date.now();
for (let i = 0; i < 1e6; ++i) {
arg(i);
}
print(Date.now() - start);
```
diff --git a/lib/Runtime/Language/JavascriptOperators.cpp b/lib/Runtime/Language/JavascriptOperators.cpp
index 2207fbd..c278d5b 100644
--- a/lib/Runtime/Language/JavascriptOperators.cpp
+++ b/lib/Runtime/Language/JavascriptOperators.cpp
@@ -6910,6 +6910,10 @@
JavascriptLibrary *library = scriptContext->GetLibrary();
HeapArgumentsObject *argsObj = library->CreateHeapArguments(frameObj, formalsCount, !!funcCallee->IsStrictMode());
+#if DBG
+ DynamicTypeHandler* typeHandler = argsObj->GetTypeHandler();
+#endif
+
//
// Set the number of arguments of Arguments Object
//
@@ -6929,6 +6933,8 @@
StackScriptFunction::EnsureBoxed(BOX_PARAM(funcCallee, nullptr, _u("callee"))), scriptContext);
}
+ AssertMsg(argsObj->GetTypeHandler() == typeHandler || scriptContext->IsScriptContextInDebugMode(), "type handler should not transition because we initialized it correctly");
+
return argsObj;
}
diff --git a/lib/Runtime/Library/JavascriptLibrary.cpp b/lib/Runtime/Library/JavascriptLibrary.cpp
index 333ed21..5c630b8 100644
--- a/lib/Runtime/Library/JavascriptLibrary.cpp
+++ b/lib/Runtime/Library/JavascriptLibrary.cpp
@@ -398,6 +398,45 @@
heapArgumentsType = DynamicType::New(scriptContext, TypeIds_Arguments, objectPrototype, nullptr,
SimpleDictionaryTypeHandler::New(scriptContext, HeapArgumentsPropertyDescriptors, _countof(HeapArgumentsPropertyDescriptors), 0, 0, true, true), true, true);
+ TypePath *const strictHeapArgumentsTypePath = TypePath::New(recycler);
+ strictHeapArgumentsTypePath->Add(BuiltInPropertyRecords::callee);
+ strictHeapArgumentsTypePath->Add<true /*isSetter*/>(BuiltInPropertyRecords::callee);
+ strictHeapArgumentsTypePath->Add(BuiltInPropertyRecords::length);
+ strictHeapArgumentsTypePath->Add(BuiltInPropertyRecords::_symbolIterator);
+ ObjectSlotAttributes *strictHeapArgumentsAttributes = RecyclerNewArrayLeaf(recycler, ObjectSlotAttributes, strictHeapArgumentsTypePath->GetPathSize());
+ strictHeapArgumentsAttributes[0] = (ObjectSlotAttributes)(ObjectSlotAttr_Writable | ObjectSlotAttr_Accessor);
+ strictHeapArgumentsAttributes[1] = ObjectSlotAttr_Setter;
+ strictHeapArgumentsAttributes[2] = (ObjectSlotAttributes)PropertyBuiltInMethodDefaults;
+ strictHeapArgumentsAttributes[3] = (ObjectSlotAttributes)PropertyBuiltInMethodDefaults;
+ for (int i = 4; i < strictHeapArgumentsTypePath->GetPathSize(); ++i)
+ {
+ strictHeapArgumentsAttributes[i] = ObjectSlotAttr_Default;
+ }
+ PathTypeSetterSlotIndex * strictHeapArgumentsSetters = RecyclerNewArrayLeaf(recycler, PathTypeSetterSlotIndex, strictHeapArgumentsTypePath->GetPathSize());
+ strictHeapArgumentsSetters[0] = 1;
+ for (int i = 1; i < strictHeapArgumentsTypePath->GetPathSize(); ++i)
+ {
+ strictHeapArgumentsSetters[i] = NoSetterSlot;
+ }
+ strictHeapArgumentsType = DynamicType::New(
+ scriptContext,
+ TypeIds_Arguments,
+ objectPrototype,
+ nullptr,
+ PathTypeHandlerWithAttr::New(
+ scriptContext,
+ strictHeapArgumentsTypePath,
+ strictHeapArgumentsAttributes,
+ strictHeapArgumentsSetters,
+ 1 /*setterCount*/,
+ strictHeapArgumentsTypePath->GetPathLength(),
+ strictHeapArgumentsTypePath->GetPathLength() /*inlineSlotCapacity*/,
+ sizeof(HeapArgumentsObject) /*offsetOfInlineSlots*/,
+ true /*isLocked*/,
+ true /*isShared*/),
+ true /*isLocked*/,
+ true /*isShared*/);
+
#define INIT_SIMPLE_TYPE(field, typeId, prototype) \
field = DynamicType::New(scriptContext, typeId, prototype, nullptr, \
PathTypeHandlerNoAttr::New(scriptContext, this->GetRootPath(), 0, 0, 0, true, true), true, true)
@@ -5517,26 +5556,15 @@
HeapArgumentsObject* JavascriptLibrary::CreateHeapArguments(Var frameObj, uint32 formalCount, bool isStrictMode)
{
AssertMsg(heapArgumentsType, "Where's heapArgumentsType?");
+ Assert(strictHeapArgumentsType);
Recycler *recycler = this->GetRecycler();
EnsureArrayPrototypeValuesFunction(); //InitializeArrayPrototype can be delay loaded, which could prevent us from access to array.prototype.values
- DynamicType * argumentsType = nullptr;
+ DynamicType * argumentsType = isStrictMode ? strictHeapArgumentsType : heapArgumentsType;
- if (isStrictMode)
- {
- //TODO: Make DictionaryTypeHandler shareable - So that Arguments' type can be cached on the javascriptLibrary.
- DictionaryTypeHandler * dictTypeHandlerForArgumentsInStrictMode = DictionaryTypeHandler::CreateTypeHandlerForArgumentsInStrictMode(recycler, scriptContext);
- argumentsType = DynamicType::New(scriptContext, TypeIds_Arguments, objectPrototype, nullptr,
- dictTypeHandlerForArgumentsInStrictMode, false, false);
- }
- else
- {
- argumentsType = heapArgumentsType;
- }
-
- return RecyclerNew(recycler, HeapArgumentsObject, recycler,
+ return RecyclerNewPlusZ(recycler, argumentsType->GetTypeHandler()->GetInlineSlotCapacity() * sizeof(Var), HeapArgumentsObject, recycler,
frameObj != GetNull() ? static_cast<ActivationObject*>(frameObj) : nullptr,
formalCount, argumentsType);
}
diff --git a/lib/Runtime/Library/JavascriptLibrary.h b/lib/Runtime/Library/JavascriptLibrary.h
index dba6d89..7078d61 100644
--- a/lib/Runtime/Library/JavascriptLibrary.h
+++ b/lib/Runtime/Library/JavascriptLibrary.h
@@ -262,6 +262,7 @@
Field(DynamicType *) generatorConstructorPrototypeObjectType;
Field(DynamicType *) constructorPrototypeObjectType;
Field(DynamicType *) heapArgumentsType;
+ Field(DynamicType *) strictHeapArgumentsType;
Field(DynamicType *) activationObjectType;
Field(DynamicType *) arrayType;
Field(DynamicType *) nativeIntArrayType;
diff --git a/lib/Runtime/Types/DictionaryTypeHandler.cpp b/lib/Runtime/Types/DictionaryTypeHandler.cpp
index e91c102..38a3784 100644
--- a/lib/Runtime/Types/DictionaryTypeHandler.cpp
+++ b/lib/Runtime/Types/DictionaryTypeHandler.cpp
@@ -13,18 +13,6 @@
}
template <typename T>
- DictionaryTypeHandlerBase<T>* DictionaryTypeHandlerBase<T>::CreateTypeHandlerForArgumentsInStrictMode(Recycler * recycler, ScriptContext * scriptContext)
- {
- DictionaryTypeHandlerBase<T> * dictTypeHandler = New(recycler, 8, 0, 0);
-
- dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::callee), PropertyWritable, scriptContext);
- dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::length), PropertyBuiltInMethodDefaults, scriptContext);
- dictTypeHandler->Add(scriptContext->GetPropertyName(Js::PropertyIds::_symbolIterator), PropertyBuiltInMethodDefaults, scriptContext);
-
- return dictTypeHandler;
- }
-
- template <typename T>
DictionaryTypeHandlerBase<T>::DictionaryTypeHandlerBase(Recycler* recycler) :
DynamicTypeHandler(1),
nextPropertyIndex(0)
diff --git a/lib/Runtime/Types/DictionaryTypeHandler.h b/lib/Runtime/Types/DictionaryTypeHandler.h
index 92a08ba..94cf3d5 100644
--- a/lib/Runtime/Types/DictionaryTypeHandler.h
+++ b/lib/Runtime/Types/DictionaryTypeHandler.h
@@ -62,8 +62,6 @@
// Create a new type handler for a future DynamicObject. This is for public usage. "initialCapacity" indicates desired slotCapacity, subject to alignment round up.
static DictionaryTypeHandlerBase* New(Recycler * recycler, int initialCapacity, uint16 inlineSlotCapacity, uint16 offsetOfInlineSlots);
- static DictionaryTypeHandlerBase* CreateTypeHandlerForArgumentsInStrictMode(Recycler * recycler, ScriptContext * scriptContext);
-
BOOL IsBigDictionaryTypeHandler();
virtual BOOL IsLockable() const override { return false; }
diff --git a/lib/Runtime/Types/TypePath.h b/lib/Runtime/Types/TypePath.h
index 004da92..12283bc 100644
--- a/lib/Runtime/Types/TypePath.h
+++ b/lib/Runtime/Types/TypePath.h
@@ -245,13 +245,14 @@
return nullptr;
}
+ template<bool isSetter = false>
int Add(const PropertyRecord * propertyRecord)
{
#ifdef SUPPORT_FIXED_FIELDS_ON_PATH_TYPES
Assert(this->GetPathLength() == this->GetMaxInitializedLength());
this->GetData()->maxInitializedLength++;
#endif
- return AddInternal<true>(propertyRecord);
+ return AddInternal<!isSetter>(propertyRecord);
}
uint8 GetPathLength() { return this->GetData()->pathLength; }