Revision: 24663
Author:   [email protected]
Date:     Thu Oct 16 13:19:36 2014 UTC
Log:      Track usage of "this" and "arguments" in Scope

This adds flags in Scope to track wheter a Scope uses "this" and,
"arguments". The information is exposed via Scope::uses_this(),
and Scope::uses_arguments(), respectively. Flags for tracking
usage on any inner scope uses are available as well via
Scope::inner_uses_this(), and Scope::inner_uses_arguments().

Knowing whether scopes use "this" and "arguments" will be handy
to generate the code needed to capture their values when generating
the code for arrow functions.

BUG=v8:2700
LOG=
[email protected]

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

Patch from Adrian Perez de Castro <[email protected]>.
https://code.google.com/p/v8/source/detail?r=24663

Modified:
 /branches/bleeding_edge/src/ast-value-factory.h
 /branches/bleeding_edge/src/globals.h
 /branches/bleeding_edge/src/objects.h
 /branches/bleeding_edge/src/preparser.h
 /branches/bleeding_edge/src/runtime/runtime-debug.cc
 /branches/bleeding_edge/src/scopeinfo.cc
 /branches/bleeding_edge/src/scopes.cc
 /branches/bleeding_edge/src/scopes.h
 /branches/bleeding_edge/test/cctest/test-parsing.cc

=======================================
--- /branches/bleeding_edge/src/ast-value-factory.h Thu Oct 2 13:05:11 2014 UTC +++ /branches/bleeding_edge/src/ast-value-factory.h Thu Oct 16 13:19:36 2014 UTC
@@ -87,6 +87,8 @@
         reinterpret_cast<const uint16_t*>(literal_bytes_.start());
     return *c;
   }
+
+  V8_INLINE bool IsArguments(AstValueFactory* ast_value_factory) const;

   // For storing AstRawStrings in a hash map.
   uint32_t hash() const {
@@ -340,6 +342,10 @@
 #undef F
 };

+
+bool AstRawString::IsArguments(AstValueFactory* ast_value_factory) const {
+  return ast_value_factory->arguments_string() == this;
+}
 } }  // namespace v8::internal

 #undef STRING_CONSTANTS
=======================================
--- /branches/bleeding_edge/src/globals.h       Thu Oct  2 15:48:48 2014 UTC
+++ /branches/bleeding_edge/src/globals.h       Thu Oct 16 13:19:36 2014 UTC
@@ -648,7 +648,8 @@
GLOBAL_SCOPE, // The top-level scope for a program or a top-level eval.
   CATCH_SCOPE,     // The scope introduced by catch.
   BLOCK_SCOPE,     // The scope introduced by a new block.
-  WITH_SCOPE       // The scope introduced by with.
+  WITH_SCOPE,      // The scope introduced by with.
+  ARROW_SCOPE      // The top-level scope for an arrow function literal.
 };


=======================================
--- /branches/bleeding_edge/src/objects.h       Thu Oct 16 11:42:47 2014 UTC
+++ /branches/bleeding_edge/src/objects.h       Thu Oct 16 13:19:36 2014 UTC
@@ -4312,13 +4312,13 @@
   };

   // Properties of scopes.
- class ScopeTypeField: public BitField<ScopeType, 0, 3> {}; - class CallsEvalField: public BitField<bool, 3, 1> {}; - class StrictModeField: public BitField<StrictMode, 4, 1> {}; - class FunctionVariableField: public BitField<FunctionVariableInfo, 5, 2> {}; - class FunctionVariableMode: public BitField<VariableMode, 7, 3> {};
-  class AsmModuleField : public BitField<bool, 10, 1> {};
-  class AsmFunctionField : public BitField<bool, 11, 1> {};
+  class ScopeTypeField : public BitField<ScopeType, 0, 4> {};
+  class CallsEvalField : public BitField<bool, 4, 1> {};
+  class StrictModeField : public BitField<StrictMode, 5, 1> {};
+ class FunctionVariableField : public BitField<FunctionVariableInfo, 6, 2> {};
+  class FunctionVariableMode : public BitField<VariableMode, 8, 3> {};
+  class AsmModuleField : public BitField<bool, 11, 1> {};
+  class AsmFunctionField : public BitField<bool, 12, 1> {};

// BitFields representing the encoded information for context locals in the
   // ContextLocalInfoEntries part.
=======================================
--- /branches/bleeding_edge/src/preparser.h     Thu Oct  9 10:40:18 2014 UTC
+++ /branches/bleeding_edge/src/preparser.h     Thu Oct 16 13:19:36 2014 UTC
@@ -615,7 +615,9 @@
     return PreParserIdentifier(kConstructorIdentifier);
   }
   bool IsEval() const { return type_ == kEvalIdentifier; }
-  bool IsArguments() const { return type_ == kArgumentsIdentifier; }
+  bool IsArguments(const AstValueFactory* = NULL) const {
+    return type_ == kArgumentsIdentifier;
+  }
   bool IsYield() const { return type_ == kYieldIdentifier; }
   bool IsPrototype() const { return type_ == kPrototypeIdentifier; }
   bool IsConstructor() const { return type_ == kConstructorIdentifier; }
@@ -956,6 +958,8 @@

bool IsDeclared(const PreParserIdentifier& identifier) const { return false; } void DeclareParameter(const PreParserIdentifier& identifier, VariableMode) {}
+  void RecordArgumentsUsage() {}
+  void RecordThisUsage() {}

   // Allow scope->Foo() to work.
   PreParserScope* operator->() { return this; }
@@ -1615,6 +1619,8 @@
       ReportMessage("strict_eval_arguments");
       *ok = false;
     }
+    if (name->IsArguments(this->ast_value_factory()))
+      scope_->RecordArgumentsUsage();
     return name;
   } else if (strict_mode() == SLOPPY &&
              (next == Token::FUTURE_STRICT_RESERVED_WORD ||
@@ -1645,7 +1651,11 @@
     *ok = false;
     return Traits::EmptyIdentifier();
   }
-  return this->GetSymbol(scanner());
+
+  IdentifierT name = this->GetSymbol(scanner());
+  if (name->IsArguments(this->ast_value_factory()))
+    scope_->RecordArgumentsUsage();
+  return name;
 }


@@ -1660,7 +1670,11 @@
     *ok = false;
     return Traits::EmptyIdentifier();
   }
-  return this->GetSymbol(scanner());
+
+  IdentifierT name = this->GetSymbol(scanner());
+  if (name->IsArguments(this->ast_value_factory()))
+    scope_->RecordArgumentsUsage();
+  return name;
 }


@@ -1738,6 +1752,7 @@
   switch (token) {
     case Token::THIS: {
       Consume(Token::THIS);
+      scope_->RecordThisUsage();
       result = this->ThisExpression(scope_, factory());
       break;
     }
@@ -2591,9 +2606,7 @@
 typename ParserBase<Traits>::ExpressionT ParserBase<
Traits>::ParseArrowFunctionLiteral(int start_pos, ExpressionT params_ast,
                                        bool* ok) {
-  // TODO(aperez): Change this to use ARROW_SCOPE
-  typename Traits::Type::ScopePtr scope =
-      this->NewScope(scope_, FUNCTION_SCOPE);
+ typename Traits::Type::ScopePtr scope = this->NewScope(scope_, ARROW_SCOPE);
   typename Traits::Type::StatementList body;
   typename Traits::Type::AstProperties ast_properties;
   BailoutReason dont_optimize_reason = kNoReason;
=======================================
--- /branches/bleeding_edge/src/runtime/runtime-debug.cc Fri Oct 10 14:59:53 2014 UTC +++ /branches/bleeding_edge/src/runtime/runtime-debug.cc Thu Oct 16 13:19:36 2014 UTC
@@ -1132,7 +1132,8 @@
           context_ = Handle<Context>(context_->previous(), isolate_);
         }
       }
-      if (scope_info->scope_type() == FUNCTION_SCOPE) {
+      if (scope_info->scope_type() == FUNCTION_SCOPE ||
+          scope_info->scope_type() == ARROW_SCOPE) {
         nested_scope_chain_.Add(scope_info);
       }
     } else {
@@ -1142,7 +1143,8 @@

       // Check whether we are in global, eval or function code.
       Handle<ScopeInfo> scope_info(shared_info->scope_info());
-      if (scope_info->scope_type() != FUNCTION_SCOPE) {
+      if (scope_info->scope_type() != FUNCTION_SCOPE &&
+          scope_info->scope_type() != ARROW_SCOPE) {
         // Global or eval code.
         CompilationInfoWithZone info(script);
         if (scope_info->scope_type() == GLOBAL_SCOPE) {
@@ -1215,6 +1217,7 @@
       Handle<ScopeInfo> scope_info = nested_scope_chain_.last();
       switch (scope_info->scope_type()) {
         case FUNCTION_SCOPE:
+        case ARROW_SCOPE:
DCHECK(context_->IsFunctionContext() | | !scope_info->HasContext());
           return ScopeTypeLocal;
         case MODULE_SCOPE:
=======================================
--- /branches/bleeding_edge/src/scopeinfo.cc    Fri Sep 19 12:50:50 2014 UTC
+++ /branches/bleeding_edge/src/scopeinfo.cc    Thu Oct 16 13:19:36 2014 UTC
@@ -170,11 +170,11 @@
     int context_locals = ContextLocalCount();
     bool function_name_context_slot =
         FunctionVariableField::decode(Flags()) == CONTEXT;
-    bool has_context = context_locals > 0 ||
-        function_name_context_slot ||
-        scope_type() == WITH_SCOPE ||
-        (scope_type() == FUNCTION_SCOPE && CallsEval()) ||
-        scope_type() == MODULE_SCOPE;
+    bool has_context = context_locals > 0 || function_name_context_slot ||
+                       scope_type() == WITH_SCOPE ||
+                       (scope_type() == ARROW_SCOPE && CallsEval()) ||
+                       (scope_type() == FUNCTION_SCOPE && CallsEval()) ||
+                       scope_type() == MODULE_SCOPE;
     if (has_context) {
       return Context::MIN_CONTEXT_SLOTS + context_locals +
           (function_name_context_slot ? 1 : 0);
=======================================
--- /branches/bleeding_edge/src/scopes.cc       Mon Oct  6 12:56:11 2014 UTC
+++ /branches/bleeding_edge/src/scopes.cc       Thu Oct 16 13:19:36 2014 UTC
@@ -160,12 +160,16 @@
   scope_inside_with_ = false;
   scope_contains_with_ = false;
   scope_calls_eval_ = false;
+  scope_uses_this_ = false;
+  scope_uses_arguments_ = false;
   asm_module_ = false;
   asm_function_ = outer_scope != NULL && outer_scope->asm_module_;
   // Inherit the strict mode from the parent scope.
   strict_mode_ = outer_scope != NULL ? outer_scope->strict_mode_ : SLOPPY;
   outer_scope_calls_sloppy_eval_ = false;
   inner_scope_calls_eval_ = false;
+  inner_scope_uses_this_ = false;
+  inner_scope_uses_arguments_ = false;
   force_eager_compilation_ = false;
   force_context_allocation_ = (outer_scope != NULL && !is_function_scope())
       ? outer_scope->has_forced_context_allocation() : false;
@@ -779,6 +783,7 @@
     case CATCH_SCOPE: return "catch";
     case BLOCK_SCOPE: return "block";
     case WITH_SCOPE: return "with";
+    case ARROW_SCOPE: return "arrow";
   }
   UNREACHABLE();
   return NULL;
@@ -885,6 +890,12 @@
   if (scope_inside_with_) Indent(n1, "// scope inside 'with'\n");
   if (scope_contains_with_) Indent(n1, "// scope contains 'with'\n");
   if (scope_calls_eval_) Indent(n1, "// scope calls 'eval'\n");
+  if (scope_uses_this_) Indent(n1, "// scope uses 'this'\n");
+  if (scope_uses_arguments_) Indent(n1, "// scope uses 'arguments'\n");
+  if (inner_scope_uses_this_) Indent(n1, "// inner scope uses 'this'\n");
+  if (inner_scope_uses_arguments_) {
+    Indent(n1, "// inner scope uses 'arguments'\n");
+  }
   if (outer_scope_calls_sloppy_eval_) {
     Indent(n1, "// outer scope calls 'eval' in sloppy context\n");
   }
@@ -1166,6 +1177,17 @@
     if (inner->scope_calls_eval_ || inner->inner_scope_calls_eval_) {
       inner_scope_calls_eval_ = true;
     }
+ // If the inner scope is an arrow function, propagate the flags tracking
+    // usage of this/arguments, but do not propagate them out from normal
+    // functions.
+    if (!inner->is_function_scope() || inner->is_arrow_scope()) {
+      if (inner->scope_uses_this_ || inner->inner_scope_uses_this_) {
+        inner_scope_uses_this_ = true;
+      }
+ if (inner->scope_uses_arguments_ || inner->inner_scope_uses_arguments_) {
+        inner_scope_uses_arguments_ = true;
+      }
+    }
     if (inner->force_eager_compilation_) {
       force_eager_compilation_ = true;
     }
=======================================
--- /branches/bleeding_edge/src/scopes.h        Fri Sep 19 12:50:50 2014 UTC
+++ /branches/bleeding_edge/src/scopes.h        Thu Oct 16 13:19:36 2014 UTC
@@ -210,6 +210,12 @@

   // Inform the scope that the corresponding code contains an eval call.
void RecordEvalCall() { if (!is_global_scope()) scope_calls_eval_ = true; }
+
+  // Inform the scope that the corresponding code uses "this".
+  void RecordThisUsage() { scope_uses_this_ = true; }
+
+  // Inform the scope that the corresponding code uses "arguments".
+  void RecordArgumentsUsage() { scope_uses_arguments_ = true; }

   // Set the strict mode flag (unless disabled by a global flag).
void SetStrictMode(StrictMode strict_mode) { strict_mode_ = strict_mode; }
@@ -262,12 +268,15 @@

   // Specific scope types.
   bool is_eval_scope() const { return scope_type_ == EVAL_SCOPE; }
-  bool is_function_scope() const { return scope_type_ == FUNCTION_SCOPE; }
+  bool is_function_scope() const {
+    return scope_type_ == FUNCTION_SCOPE || scope_type_ == ARROW_SCOPE;
+  }
   bool is_module_scope() const { return scope_type_ == MODULE_SCOPE; }
   bool is_global_scope() const { return scope_type_ == GLOBAL_SCOPE; }
   bool is_catch_scope() const { return scope_type_ == CATCH_SCOPE; }
   bool is_block_scope() const { return scope_type_ == BLOCK_SCOPE; }
   bool is_with_scope() const { return scope_type_ == WITH_SCOPE; }
+  bool is_arrow_scope() const { return scope_type_ == ARROW_SCOPE; }
   bool is_declaration_scope() const {
     return is_eval_scope() || is_function_scope() ||
         is_module_scope() || is_global_scope();
@@ -291,6 +300,15 @@
   bool inside_with() const { return scope_inside_with_; }
   // Does this scope contain a with statement.
   bool contains_with() const { return scope_contains_with_; }
+
+  // Does this scope access "this".
+  bool uses_this() const { return scope_uses_this_; }
+  // Does any inner scope access "this".
+  bool inner_uses_this() const { return inner_scope_uses_this_; }
+  // Does this scope access "arguments".
+  bool uses_arguments() const { return scope_uses_arguments_; }
+  // Does any inner scope access "arguments".
+  bool inner_uses_arguments() const { return inner_scope_uses_arguments_; }

// ---------------------------------------------------------------------------
   // Accessors.
@@ -468,6 +486,10 @@
// This scope or a nested catch scope or with scope contain an 'eval' call. At
   // the 'eval' call site this scope is the declaration scope.
   bool scope_calls_eval_;
+  // This scope uses "this".
+  bool scope_uses_this_;
+  // This scope uses "arguments".
+  bool scope_uses_arguments_;
   // This scope contains an "use asm" annotation.
   bool asm_module_;
   // This scope's outer context is an asm module.
@@ -481,6 +503,8 @@
   // Computed via PropagateScopeInfo.
   bool outer_scope_calls_sloppy_eval_;
   bool inner_scope_calls_eval_;
+  bool inner_scope_uses_this_;
+  bool inner_scope_uses_arguments_;
   bool force_eager_compilation_;
   bool force_context_allocation_;

=======================================
--- /branches/bleeding_edge/test/cctest/test-parsing.cc Mon Sep 29 14:15:48 2014 UTC +++ /branches/bleeding_edge/test/cctest/test-parsing.cc Thu Oct 16 13:19:36 2014 UTC
@@ -915,6 +915,122 @@
   }
   return character_length;
 }
+
+
+TEST(ScopeUsesThisAndArguments) {
+  static const struct {
+    const char* prefix;
+    const char* suffix;
+  } surroundings[] = {
+    { "function f() {", "}" },
+    { "var f = () => {", "}" },
+  };
+
+  static const struct {
+    const char* body;
+    bool uses_this;
+    bool uses_arguments;
+    bool inner_uses_this;
+    bool inner_uses_arguments;
+  } source_data[] = {
+    { "",
+      false, false, false, false },
+    { "return this",
+      true, false, false, false },
+    { "return arguments",
+      false, true, false, false },
+    { "return arguments[0]",
+      false, true, false, false },
+    { "return this + arguments[0]",
+      true, true, false, false },
+    { "return x => this + x",
+      false, false, true, false },
+    { "this.foo = 42;",
+      true, false, false, false },
+    { "this.foo();",
+      true, false, false, false },
+    { "if (foo()) { this.f() }",
+      true, false, false, false },
+    { "if (arguments.length) { this.f() }",
+      true, true, false, false },
+    { "while (true) { this.f() }",
+      true, false, false, false },
+    { "if (true) { while (true) this.foo(arguments) }",
+      true, true, false, false },
+    // Multiple nesting levels must work as well.
+    { "while (true) { while (true) { while (true) return this } }",
+      true, false, false, false },
+    { "if (1) { return () => { while (true) new this() } }",
+      false, false, true, false },
+    // Note that propagation of the inner_uses_this() value does not
+    // cross boundaries of normal functions onto parent scopes.
+    { "return function (x) { return this + x }",
+      false, false, false, false },
+    { "var x = function () { this.foo = 42 };",
+      false, false, false, false },
+    { "if (1) { return function () { while (true) new this() } }",
+      false, false, false, false },
+    { "return function (x) { return () => this }",
+      false, false, false, false },
+    // Flags must be correctly set when using block scoping.
+    { "\"use strict\"; while (true) { let x; this, arguments; }",
+      false, false, true, true },
+    { "\"use strict\"; if (foo()) { let x; this.f() }",
+      false, false, true, false },
+    { "\"use strict\"; if (1) {"
+      "  let x; return function () { return this + arguments }"
+      "}",
+      false, false, false, false },
+  };
+
+  i::Isolate* isolate = CcTest::i_isolate();
+  i::Factory* factory = isolate->factory();
+
+  v8::HandleScope handles(CcTest::isolate());
+  v8::Handle<v8::Context> context = v8::Context::New(CcTest::isolate());
+  v8::Context::Scope context_scope(context);
+
+  isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() -
+                                        128 * 1024);
+
+  for (unsigned j = 0; j < arraysize(surroundings); ++j) {
+    for (unsigned i = 0; i < arraysize(source_data); ++i) {
+      int kProgramByteSize = i::StrLength(surroundings[j].prefix) +
+                             i::StrLength(surroundings[j].suffix) +
+                             i::StrLength(source_data[i].body);
+      i::ScopedVector<char> program(kProgramByteSize + 1);
+      i::SNPrintF(program, "%s%s%s", surroundings[j].prefix,
+                  source_data[i].body, surroundings[j].suffix);
+      i::Handle<i::String> source =
+          factory->NewStringFromUtf8(i::CStrVector(program.start()))
+              .ToHandleChecked();
+      i::Handle<i::Script> script = factory->NewScript(source);
+      i::CompilationInfoWithZone info(script);
+ i::Parser::ParseInfo parse_info = {isolate->stack_guard()->real_climit(),
+                                         isolate->heap()->HashSeed(),
+                                         isolate->unicode_cache()};
+      i::Parser parser(&info, &parse_info);
+      parser.set_allow_arrow_functions(true);
+      parser.set_allow_harmony_scoping(true);
+      info.MarkAsGlobal();
+      parser.Parse();
+      CHECK(i::Rewriter::Rewrite(&info));
+      CHECK(i::Scope::Analyze(&info));
+      CHECK(info.function() != NULL);
+
+      i::Scope* global_scope = info.function()->scope();
+      CHECK(global_scope->is_global_scope());
+      CHECK_EQ(1, global_scope->inner_scopes()->length());
+
+      i::Scope* scope = global_scope->inner_scopes()->at(0);
+      CHECK_EQ(source_data[i].uses_this, scope->uses_this());
+      CHECK_EQ(source_data[i].uses_arguments, scope->uses_arguments());
+      CHECK_EQ(source_data[i].inner_uses_this, scope->inner_uses_this());
+      CHECK_EQ(source_data[i].inner_uses_arguments,
+               scope->inner_uses_arguments());
+    }
+  }
+}


 TEST(ScopePositions) {
@@ -974,11 +1090,10 @@
       "    infunction;\n"
       "  }", "\n"
       "  more;", i::FUNCTION_SCOPE, i::SLOPPY },
-    // TODO(aperez): Change to use i::ARROW_SCOPE when implemented
     { "  start;\n", "(a,b) => a + b", "; more;",
-      i::FUNCTION_SCOPE, i::SLOPPY },
+      i::ARROW_SCOPE, i::SLOPPY },
     { "  start;\n", "(a,b) => { return a+b; }", "\nmore;",
-      i::FUNCTION_SCOPE, i::SLOPPY },
+      i::ARROW_SCOPE, i::SLOPPY },
     { "  start;\n"
       "  (function fun", "(a,b) { infunction; }", ")();",
       i::FUNCTION_SCOPE, i::SLOPPY },

--
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to