Optimize loads from variables that might be shadowed by variables
authorager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 18 Feb 2009 13:04:28 +0000 (13:04 +0000)
committerager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 18 Feb 2009 13:04:28 +0000 (13:04 +0000)
introduced by eval.

In the cases where calls to eval have not introduced any variables, we
do not need to perform a runtime call.  Instead, we verify that the
context extension objects have not been created and perform a direct
load.

Not implemented for ARM yet and the scope resolution code could use
some better abstractions.  I'd like to do that in a separate
changelist.
Review URL: http://codereview.chromium.org/20419

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1298 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/codegen-arm.cc
src/codegen-ia32.cc
src/codegen-ia32.h
src/contexts.cc
src/scopes.cc
src/scopes.h
src/variables.cc
src/variables.h
test/mjsunit/global-load-from-eval-in-with.js [new file with mode: 0644]
test/mjsunit/local-load-from-eval.js [new file with mode: 0644]
test/mjsunit/property-load-across-eval.js [new file with mode: 0644]

index fa289fca2e0d9ed32994eab17911e05581a82a20..a8a99e8ca5385331bf8f9f921e1bf238bd362091 100644 (file)
@@ -1105,7 +1105,7 @@ void CodeGenerator::VisitDeclaration(Declaration* node) {
   if (slot != NULL && slot->type() == Slot::LOOKUP) {
     // Variables with a "LOOKUP" slot were introduced as non-locals
     // during variable resolution and must have mode DYNAMIC.
-    ASSERT(var->mode() == Variable::DYNAMIC);
+    ASSERT(var->is_dynamic());
     // For now, just do a runtime call.
     frame_->Push(cp);
     __ mov(r0, Operand(var->name()));
@@ -1983,7 +1983,7 @@ void CodeGenerator::VisitConditional(Conditional* node) {
 
 void CodeGenerator::LoadFromSlot(Slot* slot, TypeofState typeof_state) {
   if (slot->type() == Slot::LOOKUP) {
-    ASSERT(slot->var()->mode() == Variable::DYNAMIC);
+    ASSERT(slot->var()->is_dynamic());
 
     // For now, just do a runtime call.
     frame_->Push(cp);
@@ -2000,7 +2000,7 @@ void CodeGenerator::LoadFromSlot(Slot* slot, TypeofState typeof_state) {
   } else {
     // Note: We would like to keep the assert below, but it fires because of
     // some nasty code in LoadTypeofExpression() which should be removed...
-    // ASSERT(slot->var()->mode() != Variable::DYNAMIC);
+    // ASSERT(!slot->var()->is_dynamic());
 
     // Special handling for locals allocated in registers.
     __ ldr(r0, SlotOperand(slot, r2));
@@ -3348,7 +3348,7 @@ void Reference::SetValue(InitState init_state) {
       Slot* slot = expression_->AsVariableProxy()->AsVariable()->slot();
       ASSERT(slot != NULL);
       if (slot->type() == Slot::LOOKUP) {
-        ASSERT(slot->var()->mode() == Variable::DYNAMIC);
+        ASSERT(slot->var()->is_dynamic());
 
         // For now, just do a runtime call.
         frame->Push(cp);
@@ -3380,7 +3380,7 @@ void Reference::SetValue(InitState init_state) {
         frame->Push(r0);
 
       } else {
-        ASSERT(slot->var()->mode() != Variable::DYNAMIC);
+        ASSERT(!slot->var()->is_dynamic());
 
         Label exit;
         if (init_state == CONST_INIT) {
index 296397f4c5d58f78f3867a9cde3e4afe02f47ef7..2d5a127f6d4d43676167eca97b19b1d9000af393 100644 (file)
@@ -422,7 +422,7 @@ Operand CodeGenerator::SlotOperand(Slot* slot, Register tmp) {
       ASSERT(!tmp.is(esi));  // do not overwrite context register
       Register context = esi;
       int chain_length = scope()->ContextChainLength(slot->var()->scope());
-      for (int i = chain_length; i-- > 0;) {
+      for (int i = 0; i < chain_length; i++) {
         // Load the closure.
         // (All contexts, even 'with' contexts, have a closure,
         // and it is the same for all contexts inside a function.
@@ -450,6 +450,32 @@ Operand CodeGenerator::SlotOperand(Slot* slot, Register tmp) {
 }
 
 
+Operand CodeGenerator::ContextSlotOperandCheckExtensions(Slot* slot,
+                                                         Register tmp,
+                                                         Label* slow) {
+  ASSERT(slot->type() == Slot::CONTEXT);
+  int index = slot->index();
+  __ mov(tmp, Operand(esi));
+  for (Scope* s = scope(); s != slot->var()->scope(); s = s->outer_scope()) {
+    if (s->num_heap_slots() > 0) {
+      if (s->calls_eval()) {
+        // Check that extension is NULL.
+        __ cmp(ContextOperand(tmp, Context::EXTENSION_INDEX), Immediate(0));
+        __ j(not_equal, slow, not_taken);
+      }
+      __ mov(tmp, ContextOperand(tmp, Context::CLOSURE_INDEX));
+      __ mov(tmp, FieldOperand(tmp, JSFunction::kContextOffset));
+    }
+  }
+  // Check that last extension is NULL.
+  __ cmp(ContextOperand(tmp, Context::EXTENSION_INDEX), Immediate(0));
+  __ j(not_equal, slow, not_taken);
+  __ mov(tmp, ContextOperand(tmp, Context::FCONTEXT_INDEX));
+  return ContextOperand(tmp, index);
+}
+
+
+
 // Loads a value on TOS. If it is a boolean value, the result may have been
 // (partially) translated into branches, or it may have set the condition
 // code register. If force_cc is set, the value is forced to set the
@@ -1404,7 +1430,7 @@ void CodeGenerator::VisitDeclaration(Declaration* node) {
   if (slot != NULL && slot->type() == Slot::LOOKUP) {
     // Variables with a "LOOKUP" slot were introduced as non-locals
     // during variable resolution and must have mode DYNAMIC.
-    ASSERT(var->mode() == Variable::DYNAMIC);
+    ASSERT(var->is_dynamic());
     // For now, just do a runtime call.
     frame_->Push(esi);
     frame_->Push(Immediate(var->name()));
@@ -2323,23 +2349,44 @@ void CodeGenerator::VisitConditional(Conditional* node) {
 
 void CodeGenerator::LoadFromSlot(Slot* slot, TypeofState typeof_state) {
   if (slot->type() == Slot::LOOKUP) {
-    ASSERT(slot->var()->mode() == Variable::DYNAMIC);
+    ASSERT(slot->var()->is_dynamic());
+
+    Label slow, done;
+
+    // Generate fast-case code for variables that might be shadowed by
+    // eval-introduced variables.  Eval is used a lot without
+    // introducing variables.  In those cases, we do not want to
+    // perform a runtime call for all variables in the scope
+    // containing the eval.
+    if (slot->var()->mode() == Variable::DYNAMIC_GLOBAL) {
+      LoadFromGlobalSlotCheckExtensions(slot, typeof_state, ebx, &slow);
+      __ jmp(&done);
+
+    } else if (slot->var()->mode() == Variable::DYNAMIC_LOCAL) {
+      Slot* potential_slot = slot->var()->local_if_not_shadowed()->slot();
+      __ mov(eax,
+             ContextSlotOperandCheckExtensions(potential_slot,
+                                               ebx,
+                                               &slow));
+      __ jmp(&done);
+    }
 
-    // For now, just do a runtime call.
+    __ bind(&slow);
     frame_->Push(esi);
     frame_->Push(Immediate(slot->var()->name()));
-
     if (typeof_state == INSIDE_TYPEOF) {
       __ CallRuntime(Runtime::kLoadContextSlotNoReferenceError, 2);
     } else {
       __ CallRuntime(Runtime::kLoadContextSlot, 2);
     }
+
+    __ bind(&done);
     frame_->Push(eax);
 
   } else {
     // Note: We would like to keep the assert below, but it fires because of
     // some nasty code in LoadTypeofExpression() which should be removed...
-    // ASSERT(slot->var()->mode() != Variable::DYNAMIC);
+    // ASSERT(!slot->var()->is_dynamic());
     if (slot->var()->mode() == Variable::CONST) {
       // Const slots may contain 'the hole' value (the constant hasn't been
       // initialized yet) which needs to be converted into the 'undefined'
@@ -2359,6 +2406,48 @@ void CodeGenerator::LoadFromSlot(Slot* slot, TypeofState typeof_state) {
 }
 
 
+void CodeGenerator::LoadFromGlobalSlotCheckExtensions(Slot* slot,
+                                                      TypeofState typeof_state,
+                                                      Register tmp,
+                                                      Label* slow) {
+  // Check that no extension objects have been created by calls to
+  // eval from the current scope to the global scope.
+  __ mov(tmp, Operand(esi));
+  for (Scope* s = scope(); s != NULL; s = s->outer_scope()) {
+    if (s->num_heap_slots() > 0) {
+      if (s->calls_eval()) {
+        // Check that extension is NULL.
+        __ cmp(ContextOperand(tmp, Context::EXTENSION_INDEX), Immediate(0));
+        __ j(not_equal, slow, not_taken);
+      }
+      // Load next context in chain.
+      __ mov(tmp, ContextOperand(tmp, Context::CLOSURE_INDEX));
+      __ mov(tmp, FieldOperand(tmp, JSFunction::kContextOffset));
+    }
+    // If no outer scope calls eval, we do not need to check more
+    // context extensions.
+    if (!s->outer_scope_calls_eval()) break;
+  }
+
+  // All extension objects were empty and it is safe to use a global
+  // load IC call.
+  Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
+  // Load the global object.
+  LoadGlobal();
+  // Setup the name register.
+  __ mov(ecx, slot->var()->name());
+  // Call IC stub.
+  if (typeof_state == INSIDE_TYPEOF) {
+    __ call(ic, RelocInfo::CODE_TARGET);
+  } else {
+    __ call(ic, RelocInfo::CODE_TARGET_CONTEXT);
+  }
+
+  // Pop the global object. The result is in eax.
+  frame_->Pop();
+}
+
+
 void CodeGenerator::VisitSlot(Slot* node) {
   Comment cmnt(masm_, "[ Slot");
   LoadFromSlot(node, typeof_state());
@@ -4013,7 +4102,7 @@ void Reference::SetValue(InitState init_state) {
       Slot* slot = expression_->AsVariableProxy()->AsVariable()->slot();
       ASSERT(slot != NULL);
       if (slot->type() == Slot::LOOKUP) {
-        ASSERT(slot->var()->mode() == Variable::DYNAMIC);
+        ASSERT(slot->var()->is_dynamic());
 
         // For now, just do a runtime call.
         frame->Push(esi);
@@ -4045,7 +4134,7 @@ void Reference::SetValue(InitState init_state) {
         frame->Push(eax);
 
       } else {
-        ASSERT(slot->var()->mode() != Variable::DYNAMIC);
+        ASSERT(!slot->var()->is_dynamic());
 
         Label exit;
         if (init_state == CONST_INIT) {
index 68ec56184854d4fc29c19a61ef365c2d0c124f16..d79de373f88a3844eb8cbd82d5ed8b4ceb0f47fb 100644 (file)
@@ -267,6 +267,9 @@ class CodeGenerator: public AstVisitor {
 
   Operand SlotOperand(Slot* slot, Register tmp);
 
+  Operand ContextSlotOperandCheckExtensions(Slot* slot,
+                                            Register tmp,
+                                            Label* slow);
 
   // Expressions
   Operand GlobalObject() const {
@@ -284,6 +287,10 @@ class CodeGenerator: public AstVisitor {
 
   // Read a value from a slot and leave it on top of the expression stack.
   void LoadFromSlot(Slot* slot, TypeofState typeof_state);
+  void LoadFromGlobalSlotCheckExtensions(Slot* slot,
+                                         TypeofState typeof_state,
+                                         Register tmp,
+                                         Label* slow);
 
   // Special code for typeof expressions: Unfortunately, we must
   // be careful when loading the expression in 'typeof'
index 8155999ccef0fe90708a91c1f943ae3ef1bb74d6..ec84a1d63fe41f4732a2ab36b5b5bb509100ad45 100644 (file)
@@ -134,10 +134,12 @@ Handle<Object> Context::Lookup(Handle<String> name, ContextLookupFlags flags,
         // declared variables that were introduced through declaration nodes)
         // must not appear here.
         switch (mode) {
-          case Variable::INTERNAL :  // fall through
-          case Variable::VAR      : *attributes = NONE; break;
-          case Variable::CONST    : *attributes = READ_ONLY; break;
-          case Variable::DYNAMIC  : UNREACHABLE(); break;
+          case Variable::INTERNAL:  // fall through
+          case Variable::VAR: *attributes = NONE; break;
+          case Variable::CONST: *attributes = READ_ONLY; break;
+          case Variable::DYNAMIC: UNREACHABLE(); break;
+          case Variable::DYNAMIC_GLOBAL: UNREACHABLE(); break;
+          case Variable::DYNAMIC_LOCAL: UNREACHABLE(); break;
           case Variable::TEMPORARY: UNREACHABLE(); break;
         }
         return context;
index 87497d00a09cfa542c3b6f727de398917ccb69b6..5949326df9bb044fe0a04aeec0b5b156fe12280b 100644 (file)
@@ -139,6 +139,7 @@ Scope::Scope(Scope* outer_scope, Type type)
     scope_calls_eval_(false),
     outer_scope_calls_eval_(false),
     inner_scope_calls_eval_(false),
+    outer_scope_is_eval_scope_(false),
     force_eager_compilation_(false),
     num_stack_slots_(0),
     num_heap_slots_(0) {
@@ -312,7 +313,8 @@ void Scope::AllocateVariables() {
   // and assume they may invoke eval themselves. Eventually we could capture
   // this information in the ScopeInfo and then use it here (by traversing
   // the call chain stack, at compile time).
-  PropagateScopeInfo(is_eval_scope());
+  bool eval_scope = is_eval_scope();
+  PropagateScopeInfo(eval_scope, eval_scope);
 
   // 2) Resolve variables.
   Scope* global_scope = NULL;
@@ -442,6 +444,7 @@ void Scope::Print(int n) {
   if (scope_calls_eval_) Indent(n1, "// scope calls 'eval'\n");
   if (outer_scope_calls_eval_) Indent(n1, "// outer scope calls 'eval'\n");
   if (inner_scope_calls_eval_) Indent(n1, "// inner scope calls 'eval'\n");
+  if (outer_scope_is_eval_scope_) Indent(n1, "// outer scope is 'eval' scope\n");
   if (num_stack_slots_ > 0) { Indent(n1, "// ");
   PrintF("%d stack slots\n", num_stack_slots_); }
   if (num_heap_slots_ > 0) { Indent(n1, "// ");
@@ -482,20 +485,18 @@ void Scope::Print(int n) {
 #endif  // DEBUG
 
 
-Variable* Scope::NonLocal(Handle<String> name) {
-  // Space optimization: reuse existing non-local with the same name.
+Variable* Scope::NonLocal(Handle<String> name, Variable::Mode mode) {
+  // Space optimization: reuse existing non-local with the same name
+  // and mode.
   for (int i = 0; i < nonlocals_.length(); i++) {
     Variable* var = nonlocals_[i];
-    if (var->name().is_identical_to(name)) {
-      ASSERT(var->mode() == Variable::DYNAMIC);
+    if (var->name().is_identical_to(name) && var->mode() == mode) {
       return var;
     }
   }
 
-  // Otherwise create a new new-local and add it to the list.
-  Variable* var = new Variable(
-    NULL /* we don't know the scope */,
-    name, Variable::DYNAMIC, true, false);
+  // Otherwise create a new non-local and add it to the list.
+  Variable* var = new Variable(NULL, name, mode, true, false);
   nonlocals_.Add(var);
 
   // Allocate it by giving it a dynamic lookup.
@@ -511,7 +512,9 @@ Variable* Scope::NonLocal(Handle<String> name) {
 // because the variable is just a guess (and may be shadowed by another
 // variable that is introduced dynamically via an 'eval' call or a 'with'
 // statement).
-Variable* Scope::LookupRecursive(Handle<String> name, bool inner_lookup) {
+Variable* Scope::LookupRecursive(Handle<String> name,
+                                 bool inner_lookup,
+                                 Variable** invalidated_local) {
   // If we find a variable, but the current scope calls 'eval', the found
   // variable may not be the correct one (the 'eval' may introduce a
   // property with the same name). In that case, remember that the variable
@@ -542,7 +545,7 @@ Variable* Scope::LookupRecursive(Handle<String> name, bool inner_lookup) {
       var = function_;
 
     } else if (outer_scope_ != NULL) {
-      var = outer_scope_->LookupRecursive(name, true /* inner lookup */);
+      var = outer_scope_->LookupRecursive(name, true, invalidated_local);
       // We may have found a variable in an outer scope. However, if
       // the current scope is inside a 'with', the actual variable may
       // be a property introduced via the 'with' statement. Then, the
@@ -563,8 +566,10 @@ Variable* Scope::LookupRecursive(Handle<String> name, bool inner_lookup) {
     var->is_accessed_from_inner_scope_ = true;
 
   // If the variable we have found is just a guess, invalidate the result.
-  if (guess)
+  if (guess) {
+    *invalidated_local = var;
     var = NULL;
+  }
 
   return var;
 }
@@ -578,7 +583,8 @@ void Scope::ResolveVariable(Scope* global_scope, VariableProxy* proxy) {
   if (proxy->var() != NULL) return;
 
   // Otherwise, try to resolve the variable.
-  Variable* var = LookupRecursive(proxy->name(), false);
+  Variable* invalidated_local = NULL;
+  Variable* var = LookupRecursive(proxy->name(), false, &invalidated_local);
 
   if (proxy->inside_with()) {
     // If we are inside a local 'with' statement, all bets are off
@@ -587,7 +593,7 @@ void Scope::ResolveVariable(Scope* global_scope, VariableProxy* proxy) {
     // Note that we must do a lookup anyway, because if we find one,
     // we must mark that variable as potentially accessed from this
     // inner scope (the property may not be in the 'with' object).
-    var = NonLocal(proxy->name());
+    var = NonLocal(proxy->name(), Variable::DYNAMIC);
 
   } else {
     // We are not inside a local 'with' statement.
@@ -601,11 +607,22 @@ void Scope::ResolveVariable(Scope* global_scope, VariableProxy* proxy) {
       // or we don't know about the outer scope (because we are
       // in an eval scope).
       if (!is_global_scope() &&
-          (is_eval_scope() || outer_scope_calls_eval_ ||
-           scope_calls_eval_ || scope_inside_with_)) {
-        // We must look up the variable at runtime, and we don't
-        // know anything else.
-        var = NonLocal(proxy->name());
+          (scope_inside_with_ || outer_scope_is_eval_scope_)) {
+        // If we are inside a with statement or the code is executed
+        // using eval, we give up and look up the variable at runtime.
+        var = NonLocal(proxy->name(), Variable::DYNAMIC);
+
+      } else if (!is_global_scope() &&
+                 (scope_calls_eval_ || outer_scope_calls_eval_)) {
+        // If the code is not executed using eval and there are no
+        // with scopes, either we have a local or a global variable
+        // that might be shadowed by an eval-introduced variable.
+        if (invalidated_local != NULL) {
+          var = NonLocal(proxy->name(), Variable::DYNAMIC_LOCAL);
+          var->set_local_if_not_shadowed(invalidated_local);
+        } else {
+          var = NonLocal(proxy->name(), Variable::DYNAMIC_GLOBAL);
+        }
 
       } else {
         // We must have a global variable.
@@ -643,15 +660,21 @@ void Scope::ResolveVariablesRecursively(Scope* global_scope) {
 }
 
 
-bool Scope::PropagateScopeInfo(bool outer_scope_calls_eval) {
+bool Scope::PropagateScopeInfo(bool outer_scope_calls_eval,
+                               bool outer_scope_is_eval_scope) {
   if (outer_scope_calls_eval) {
     outer_scope_calls_eval_ = true;
   }
 
-  bool b = scope_calls_eval_ || outer_scope_calls_eval_;
+  if (outer_scope_is_eval_scope) {
+    outer_scope_is_eval_scope_ = true;
+  }
+
+  bool calls_eval = scope_calls_eval_ || outer_scope_calls_eval_;
+  bool is_eval = is_eval_scope() || outer_scope_is_eval_scope_;
   for (int i = 0; i < inner_scopes_.length(); i++) {
     Scope* inner_scope = inner_scopes_[i];
-    if (inner_scope->PropagateScopeInfo(b)) {
+    if (inner_scope->PropagateScopeInfo(calls_eval, is_eval)) {
       inner_scope_calls_eval_ = true;
     }
     if (inner_scope->force_eager_compilation_) {
index 2c04053f8dfe821a97dc4651ab373c9ca9e960e8..30545ba4239e9c3f2c169fc5891299d6e8488ecc 100644 (file)
@@ -166,10 +166,13 @@ class Scope: public ZoneObject {
   bool is_function_scope() const  { return type_ == FUNCTION_SCOPE; }
   bool is_global_scope() const  { return type_ == GLOBAL_SCOPE; }
 
+  // Information about which scopes calls eval.
+  bool calls_eval() const  { return scope_calls_eval_; }
+  bool outer_scope_calls_eval() const  { return outer_scope_calls_eval_; }
+
   // The scope immediately surrounding this scope, or NULL.
   Scope* outer_scope() const  { return outer_scope_; }
 
-
   // ---------------------------------------------------------------------------
   // Accessors.
 
@@ -290,6 +293,7 @@ class Scope: public ZoneObject {
   // Computed via PropagateScopeInfo.
   bool outer_scope_calls_eval_;
   bool inner_scope_calls_eval_;
+  bool outer_scope_is_eval_scope_;
   bool force_eager_compilation_;
 
   // Computed via AllocateVariables; function scopes only.
@@ -298,15 +302,18 @@ class Scope: public ZoneObject {
 
   // Create a non-local variable with a given name.
   // These variables are looked up dynamically at runtime.
-  Variable* NonLocal(Handle<String> name);
+  Variable* NonLocal(Handle<String> name, Variable::Mode mode);
 
   // Variable resolution.
-  Variable* LookupRecursive(Handle<String> name, bool inner_lookup);
+  Variable* LookupRecursive(Handle<String> name,
+                            bool inner_lookup,
+                            Variable** invalidated_local);
   void ResolveVariable(Scope* global_scope, VariableProxy* proxy);
   void ResolveVariablesRecursively(Scope* global_scope);
 
   // Scope analysis.
-  bool PropagateScopeInfo(bool outer_scope_calls_eval);
+  bool PropagateScopeInfo(bool outer_scope_calls_eval,
+                          bool outer_scope_is_eval_scope);
   bool HasTrivialContext() const;
 
   // Predicates.
index 75adc72c92741208da5cb81d3beeda26f2693277..1d7d6e4c3a2421f2ddd90a3de3f5af30c48a4f58 100644 (file)
@@ -110,6 +110,8 @@ const char* Variable::Mode2String(Mode mode) {
     case VAR: return "VAR";
     case CONST: return "CONST";
     case DYNAMIC: return "DYNAMIC";
+    case DYNAMIC_GLOBAL: return "DYNAMIC_GLOBAL";
+    case DYNAMIC_LOCAL: return "DYNAMIC_LOCAL";
     case INTERNAL: return "INTERNAL";
     case TEMPORARY: return "TEMPORARY";
   }
@@ -143,6 +145,7 @@ Variable::Variable(Scope* scope,
     mode_(mode),
     is_valid_LHS_(is_valid_LHS),
     is_this_(is_this),
+    local_if_not_shadowed_(NULL),
     is_accessed_from_inner_scope_(false),
     rewrite_(NULL) {
   // names must be canonicalized for fast equality checks
@@ -156,5 +159,4 @@ bool Variable::is_global() const {
   return mode_ != TEMPORARY && scope_ != NULL && scope_->is_global_scope();
 }
 
-
 } }  // namespace v8::internal
index 21eb235a32109b1a083f2c248a398e1965b70784..00ba34557c4d7636ac01df26cbc28ddd8cc08218 100644 (file)
@@ -113,13 +113,27 @@ class Variable: public ZoneObject {
   enum Mode {
     // User declared variables:
     VAR,       // declared via 'var', and 'function' declarations
+
     CONST,     // declared via 'const' declarations
 
     // Variables introduced by the compiler:
-    DYNAMIC,   // always require dynamic lookup (we don't know the declaration)
-    INTERNAL,  // like VAR, but not user-visible (may or may not be in a
-               // context)
-    TEMPORARY  // temporary variables (not user-visible), never in a context
+    DYNAMIC,         // always require dynamic lookup (we don't know
+                     // the declaration)
+
+    DYNAMIC_GLOBAL,  // requires dynamic lookup, but we know that the
+                     // variable is global unless it has been shadowed
+                     // by an eval-introduced variable
+
+    DYNAMIC_LOCAL,   // requires dynamic lookup, but we know that the
+                     // variable is local and where it is unless it
+                     // has been shadowed by an eval-introduced
+                     // variable
+
+    INTERNAL,        // like VAR, but not user-visible (may or may not
+                     // be in a context)
+
+    TEMPORARY        // temporary variables (not user-visible), never
+                     // in a context
   };
 
   // Printing support
@@ -150,9 +164,24 @@ class Variable: public ZoneObject {
     return !is_this() && name().is_identical_to(n);
   }
 
+  bool is_dynamic() const {
+    return (mode_ == DYNAMIC ||
+            mode_ == DYNAMIC_GLOBAL ||
+            mode_ == DYNAMIC_LOCAL);
+  }
+
   bool is_global() const;
   bool is_this() const { return is_this_; }
 
+  Variable* local_if_not_shadowed() const {
+    ASSERT(mode_ == DYNAMIC_LOCAL && local_if_not_shadowed_ != NULL);
+    return local_if_not_shadowed_;
+  }
+
+  void set_local_if_not_shadowed(Variable* local) {
+    local_if_not_shadowed_ = local;
+  }
+
   Expression* rewrite() const  { return rewrite_; }
   Slot* slot() const;
 
@@ -168,6 +197,8 @@ class Variable: public ZoneObject {
   bool is_valid_LHS_;
   bool is_this_;
 
+  Variable* local_if_not_shadowed_;
+
   // Usage info.
   bool is_accessed_from_inner_scope_;  // set by variable resolver
   UseCount var_uses_;  // uses of the variable value
diff --git a/test/mjsunit/global-load-from-eval-in-with.js b/test/mjsunit/global-load-from-eval-in-with.js
new file mode 100644 (file)
index 0000000..d733f6c
--- /dev/null
@@ -0,0 +1,59 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Tests global loads from eval inside of a with statement.
+
+var x = 27;
+
+function test(obj, source) {
+  with (obj) {
+    eval(source);
+  }
+}
+
+// Test shadowing in eval scope.
+test({ x: 42 }, "assertEquals(42, x)");
+test({ y: 42 }, "assertEquals(27, x)");
+
+// Test shadowing in local scope inside an eval scope.
+test({ x: 42 }, "function f() { assertEquals(42, x) }; f();");
+test({ y: 42 }, "function f() { assertEquals(27, x) }; f();");
+
+// Test shadowing in local scope inside an eval scope.  Deeper nesting
+// this time.
+test({ x: 42 }, "function f() { function g() { assertEquals(42, x) }; g() }; f();");
+test({ y: 42 }, "function f() { function g() { assertEquals(27, x) }; g() }; f();");
+
+// Test shadowing in local scope inside an eval scope with eval calls in the eval scopes.
+test({ x: 42 }, "function f() { eval('1'); assertEquals(42, x) }; f();");
+test({ y: 42 }, "function f() { eval('1'); assertEquals(27, x) }; f();");
+
+// Test shadowing in local scope inside an eval scope with eval calls
+// in the eval scopes.  Deeper nesting this time.
+test({ x: 42 }, "function f() { function g() { eval('1'); assertEquals(42, x) }; g() }; f();");
+test({ y: 42 }, "function f() { function g() { eval('1'); assertEquals(27, x) }; g() }; f();");
+
diff --git a/test/mjsunit/local-load-from-eval.js b/test/mjsunit/local-load-from-eval.js
new file mode 100644 (file)
index 0000000..faf28db
--- /dev/null
@@ -0,0 +1,37 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Tests loads of local properties from eval.
+
+function test(source) {
+  var x = 27;
+  eval(source);
+}
+
+test("assertEquals(27, x);");
+test("(function() { assertEquals(27, x) })();");
+
diff --git a/test/mjsunit/property-load-across-eval.js b/test/mjsunit/property-load-across-eval.js
new file mode 100644 (file)
index 0000000..8271f4c
--- /dev/null
@@ -0,0 +1,85 @@
+// Copyright 2009 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Tests loading of properties across eval calls.
+
+var x = 1;
+
+// Test loading across an eval call that does not shadow variables.
+function testNoShadowing() {
+  var y = 2;
+  function f() {
+    eval('1');
+    assertEquals(1, x);
+    assertEquals(2, y);
+    function g() {
+      assertEquals(1, x);
+      assertEquals(2, y);
+    }
+    g();
+  }
+  f();
+}
+
+testNoShadowing();
+
+// Test loading across eval calls that do not shadow variables.
+function testNoShadowing2() {
+  var y = 2;
+  eval('1');
+  function f() {
+    eval('1');
+    assertEquals(1, x);
+    assertEquals(2, y);
+    function g() {
+      assertEquals(1, x);
+      assertEquals(2, y);
+    }
+    g();
+  }
+  f();
+}
+
+testNoShadowing2();
+
+// Test loading across an eval call that shadows variables.
+function testShadowing() {
+  var y = 2;
+  function f() {
+    eval('var x = 3; var y = 4;');
+    assertEquals(3, x);
+    assertEquals(4, y);
+    function g() {
+      assertEquals(3, x);
+      assertEquals(4, y);
+    }
+    g();
+  }
+  f();
+}
+
+testShadowing();