[builtins] Re-add similar String wrapper optimization for StringAdd.
authorbmeurer <bmeurer@chromium.org>
Wed, 23 Sep 2015 13:49:09 +0000 (06:49 -0700)
committerCommit bot <commit-bot@chromium.org>
Wed, 23 Sep 2015 13:49:21 +0000 (13:49 +0000)
For string wrappers (JSValue instances with [[StringData]] internal
fields), we can shortcirciut the ToPrimitive if

  (a) the {input} map matches the initial map of the String function,
  (b) the {input} [[Prototype]] is the unmodified %StringPrototype% (i.e.
      no one monkey-patched toString, @@toPrimitive or valueOf), and
  (c) the %ObjectPrototype% (i.e. the [[Prototype]] of the
      %StringPrototype%) is also unmodified, that is no one sneaked a
      @@toPrimitive into the %ObjectPrototype%.

If all these assumptions hold, we can just take the [[StringData]] value
and return it.

This just repairs a regression introduced by removing the
weird (and broken) intrinsic %_IsStringWrapperSafeForDefaultValue, which
was intendend to something similar to this, although less efficient and
wrong in the presence of @@toPrimitive. Long-term we might want to move
into the direction of having a ToPrimitiveStub that can do common cases
while staying in JavaScript land (i.e. not going to C++).

R=jarin@chromium.org
BUG=chromium:532524
LOG=n

Review URL: https://codereview.chromium.org/1366563002

Cr-Commit-Position: refs/heads/master@{#30890}

src/bootstrapper.cc
src/code-stubs-hydrogen.cc
src/contexts.h

index 386d8ca..b40a96a 100644 (file)
@@ -2334,7 +2334,15 @@ bool Genesis::InstallNatives(ContextType context_type) {
                            USE_CUSTOM_MINIMUM_CAPACITY);
   native_context()->set_function_cache(*function_cache);
 
-  // Store the map for the string prototype after the natives has been compiled
+  // Store the map for the %ObjectPrototype% after the natives has been compiled
+  // and the Object function has been set up.
+  Handle<JSFunction> object_function(native_context()->object_function());
+  DCHECK(JSObject::cast(object_function->initial_map()->prototype())
+             ->HasFastProperties());
+  native_context()->set_object_function_prototype_map(
+      HeapObject::cast(object_function->initial_map()->prototype())->map());
+
+  // Store the map for the %StringPrototype% after the natives has been compiled
   // and the String function has been set up.
   Handle<JSFunction> string_function(native_context()->string_function());
   DCHECK(JSObject::cast(
index 59d3a8b..9f78222 100644 (file)
@@ -113,7 +113,8 @@ class CodeStubGraphBuilderBase : public HGraphBuilder {
                                         HValue* shared_info,
                                         HValue* native_context);
 
-  HValue* CheckString(HValue* input, bool convert);
+  HValue* BuildToString(HValue* input, bool convert);
+  HValue* BuildToPrimitive(HValue* input, HValue* input_map);
 
  private:
   HValue* BuildArraySingleArgumentConstructor(JSArrayBuilder* builder);
@@ -1458,7 +1459,7 @@ Handle<Code> BinaryOpWithAllocationSiteStub::GenerateCode() {
 }
 
 
-HValue* CodeStubGraphBuilderBase::CheckString(HValue* input, bool convert) {
+HValue* CodeStubGraphBuilderBase::BuildToString(HValue* input, bool convert) {
   if (!convert) return BuildCheckString(input);
   IfBuilder if_inputissmi(this);
   HValue* inputissmi = if_inputissmi.If<HIsSmiAndBranch>(input);
@@ -1496,11 +1497,8 @@ HValue* CodeStubGraphBuilderBase::CheckString(HValue* input, bool convert) {
       }
       if_inputisprimitive.Else();
       {
-        // TODO(bmeurer): Add support for fast ToPrimitive conversion using
-        // a dedicated ToPrimitiveStub.
-        Add<HPushArguments>(input);
-        Push(Add<HCallRuntime>(Runtime::FunctionForId(Runtime::kToPrimitive),
-                               1));
+        // Convert the input to a primitive.
+        Push(BuildToPrimitive(input, input_map));
       }
       if_inputisprimitive.End();
       // Convert the primitive to a string value.
@@ -1518,6 +1516,83 @@ HValue* CodeStubGraphBuilderBase::CheckString(HValue* input, bool convert) {
 }
 
 
+HValue* CodeStubGraphBuilderBase::BuildToPrimitive(HValue* input,
+                                                   HValue* input_map) {
+  // Get the native context of the caller.
+  HValue* native_context = BuildGetNativeContext();
+
+  // Determine the initial map of the %ObjectPrototype%.
+  HValue* object_function_prototype_map =
+      Add<HLoadNamedField>(native_context, nullptr,
+                           HObjectAccess::ForContextSlot(
+                               Context::OBJECT_FUNCTION_PROTOTYPE_MAP_INDEX));
+
+  // Determine the initial map of the %StringPrototype%.
+  HValue* string_function_prototype_map =
+      Add<HLoadNamedField>(native_context, nullptr,
+                           HObjectAccess::ForContextSlot(
+                               Context::STRING_FUNCTION_PROTOTYPE_MAP_INDEX));
+
+  // Determine the initial map of the String function.
+  HValue* string_function = Add<HLoadNamedField>(
+      native_context, nullptr,
+      HObjectAccess::ForContextSlot(Context::STRING_FUNCTION_INDEX));
+  HValue* string_function_initial_map = Add<HLoadNamedField>(
+      string_function, nullptr, HObjectAccess::ForPrototypeOrInitialMap());
+
+  // Determine the map of the [[Prototype]] of {input}.
+  HValue* input_prototype =
+      Add<HLoadNamedField>(input_map, nullptr, HObjectAccess::ForPrototype());
+  HValue* input_prototype_map =
+      Add<HLoadNamedField>(input_prototype, nullptr, HObjectAccess::ForMap());
+
+  // For string wrappers (JSValue instances with [[StringData]] internal
+  // fields), we can shortcirciut the ToPrimitive if
+  //
+  //  (a) the {input} map matches the initial map of the String function,
+  //  (b) the {input} [[Prototype]] is the unmodified %StringPrototype% (i.e.
+  //      no one monkey-patched toString, @@toPrimitive or valueOf), and
+  //  (c) the %ObjectPrototype% (i.e. the [[Prototype]] of the
+  //      %StringPrototype%) is also unmodified, that is no one sneaked a
+  //      @@toPrimitive into the %ObjectPrototype%.
+  //
+  // If all these assumptions hold, we can just take the [[StringData]] value
+  // and return it.
+  // TODO(bmeurer): This just repairs a regression introduced by removing the
+  // weird (and broken) intrinsic %_IsStringWrapperSafeForDefaultValue, which
+  // was intendend to something similar to this, although less efficient and
+  // wrong in the presence of @@toPrimitive. Long-term we might want to move
+  // into the direction of having a ToPrimitiveStub that can do common cases
+  // while staying in JavaScript land (i.e. not going to C++).
+  IfBuilder if_inputisstringwrapper(this);
+  if_inputisstringwrapper.If<HCompareObjectEqAndBranch>(
+      input_map, string_function_initial_map);
+  if_inputisstringwrapper.And();
+  if_inputisstringwrapper.If<HCompareObjectEqAndBranch>(
+      input_prototype_map, string_function_prototype_map);
+  if_inputisstringwrapper.And();
+  if_inputisstringwrapper.If<HCompareObjectEqAndBranch>(
+      Add<HLoadNamedField>(Add<HLoadNamedField>(input_prototype_map, nullptr,
+                                                HObjectAccess::ForPrototype()),
+                           nullptr, HObjectAccess::ForMap()),
+      object_function_prototype_map);
+  if_inputisstringwrapper.Then();
+  {
+    Push(BuildLoadNamedField(
+        input, FieldIndex::ForInObjectOffset(JSValue::kValueOffset)));
+  }
+  if_inputisstringwrapper.Else();
+  {
+    // TODO(bmeurer): Add support for fast ToPrimitive conversion using
+    // a dedicated ToPrimitiveStub.
+    Add<HPushArguments>(input);
+    Push(Add<HCallRuntime>(Runtime::FunctionForId(Runtime::kToPrimitive), 1));
+  }
+  if_inputisstringwrapper.End();
+  return Pop();
+}
+
+
 template <>
 HValue* CodeStubGraphBuilder<StringAddStub>::BuildCodeInitializedStub() {
   StringAddStub* stub = casted_stub();
@@ -1530,11 +1605,11 @@ HValue* CodeStubGraphBuilder<StringAddStub>::BuildCodeInitializedStub() {
   // Make sure that both arguments are strings if not known in advance.
   if ((flags & STRING_ADD_CHECK_LEFT) == STRING_ADD_CHECK_LEFT) {
     left =
-        CheckString(left, (flags & STRING_ADD_CONVERT) == STRING_ADD_CONVERT);
+        BuildToString(left, (flags & STRING_ADD_CONVERT) == STRING_ADD_CONVERT);
   }
   if ((flags & STRING_ADD_CHECK_RIGHT) == STRING_ADD_CHECK_RIGHT) {
-    right =
-        CheckString(right, (flags & STRING_ADD_CONVERT) == STRING_ADD_CONVERT);
+    right = BuildToString(right,
+                          (flags & STRING_ADD_CONVERT) == STRING_ADD_CONVERT);
   }
 
   return BuildStringAdd(left, right, HAllocationMode(pretenure_flag));
index 1692038..62c1880 100644 (file)
@@ -219,6 +219,7 @@ enum BindingFlags {
   V(NORMALIZED_MAP_CACHE_INDEX, Object, normalized_map_cache)                  \
   V(NUMBER_FUNCTION_INDEX, JSFunction, number_function)                        \
   V(OBJECT_FUNCTION_INDEX, JSFunction, object_function)                        \
+  V(OBJECT_FUNCTION_PROTOTYPE_MAP_INDEX, Map, object_function_prototype_map)   \
   V(OPAQUE_REFERENCE_FUNCTION_INDEX, JSFunction, opaque_reference_function)    \
   V(REGEXP_FUNCTION_INDEX, JSFunction, regexp_function)                        \
   V(REGEXP_RESULT_MAP_INDEX, Map, regexp_result_map)                           \