[JSC] Invalidate old scope operations using global lexical binding epoch
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 21 Jan 2019 01:39:06 +0000 (01:39 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 21 Jan 2019 01:39:06 +0000 (01:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193603
<rdar://problem/47380869>

Patch by Yusuke Suzuki <ysuzuki@apple.com> on 2019-01-20
Reviewed by Saam Barati.

JSTests:

* stress/let-lexical-binding-shadow-existing-global-property-ftl.js:
* stress/scope-operation-cache-global-property-before-deleting.js: Added.
(shouldThrow):
(bar):
* stress/scope-operation-cache-global-property-bump-counter.js: Added.
(shouldBe):
(get1):
(get2):
(get1If):
(get2If):
* stress/scope-operation-cache-global-property-even-if-it-fails.js: Added.
(shouldThrow):
(foo):

Source/JavaScriptCore:

Even if the global lexical binding does not shadow the global property at that time, we need to clear the cached information in
scope related operations since we may have a global property previously. Consider the following example,

    foo = 0;
    function get() { return foo; }
    print(get()); // 0
    print(get()); // 0
    delete globalThis.foo;
    $.evalScript(`const foo = 42;`);
    print(get()); // Should be 42, but it returns 0 if the cached information in get() is not cleared.

To invalidate the cache easily, we introduce global lexical binding epoch. It is bumped every time we introduce a new lexical binding
into JSGlobalLexicalEnvironment, since that name could shadow the global property name previously. In op_resolve_scope, we first check
the epoch stored in the metadata, and go to slow path if it is not equal to the current epoch. Our slow path code convert the scope
operation to the appropriate one even if the resolve type is not UnresolvedProperty type. After updating the resolve type of the bytecode,
we update the cached epoch to the current one, so that we can use the cached information as long as we stay in the same epoch.

In op_get_from_scope and op_put_to_scope, we do not use this epoch since Structure check can do the same thing instead. If op_resolve_type
is updated by the epoch, and if it starts returning JSGlobalLexicalEnvironment instead JSGlobalObject, obviously the structure check fails.
And in the slow path, we update op_get_from_scope and op_put_to_scope appropriately.

So, the metadata for scope related bytecodes are eventually updated to the appropriate one. In DFG and FTL, we use the watchpoint based approach.
In DFG and FTL, we concurrently attempt to get the watchpoint for the lexical binding and look into it by using `isStillValid()` to avoid
infinite compile-and-fail loop.

When the global lexical binding epoch overflows we iterate all the live CodeBlock and update the op_resolve_scope's epoch. Even if the shadowing
happens, it is OK if we bump the epoch, since op_resolve_scope will return JSGlobalLexicalEnvironment instead of JSGlobalObject, and following
structure check in op_put_to_scope and op_get_from_scope fail. We do not need to update op_get_from_scope and op_put_to_scope because of the same
reason.

* bytecode/BytecodeList.rb:
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::notifyLexicalBindingUpdate):
(JSC::CodeBlock::notifyLexicalBindingShadowing): Deleted.
* bytecode/CodeBlock.h:
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGDesiredGlobalProperties.cpp:
(JSC::DFG::DesiredGlobalProperties::isStillValidOnMainThread):
* dfg/DFGDesiredGlobalProperties.h:
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::watchGlobalProperty):
* dfg/DFGGraph.h:
* dfg/DFGPlan.cpp:
(JSC::DFG::Plan::isStillValidOnMainThread):
* jit/JITPropertyAccess.cpp:
(JSC::JIT::emit_op_resolve_scope):
* jit/JITPropertyAccess32_64.cpp:
(JSC::JIT::emit_op_resolve_scope):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* runtime/CommonSlowPaths.cpp:
(JSC::SLOW_PATH_DECL):
* runtime/CommonSlowPaths.h:
(JSC::CommonSlowPaths::tryCachePutToScopeGlobal):
(JSC::CommonSlowPaths::tryCacheGetFromScopeGlobal):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::bumpGlobalLexicalBindingEpoch):
(JSC::JSGlobalObject::getReferencedPropertyWatchpointSet):
(JSC::JSGlobalObject::ensureReferencedPropertyWatchpointSet):
(JSC::JSGlobalObject::notifyLexicalBindingShadowing): Deleted.
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::globalLexicalBindingEpoch const):
(JSC::JSGlobalObject::globalLexicalBindingEpochOffset):
(JSC::JSGlobalObject::addressOfGlobalLexicalBindingEpoch):
* runtime/Options.cpp:
(JSC::correctOptions):
(JSC::Options::initialize):
(JSC::Options::setOptions):
(JSC::Options::setOptionWithoutAlias):
* runtime/Options.h:
* runtime/ProgramExecutable.cpp:
(JSC::ProgramExecutable::initializeGlobalProperties):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@240220 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
JSTests/ChangeLog
JSTests/stress/let-lexical-binding-shadow-existing-global-property-ftl.js
JSTests/stress/scope-operation-cache-global-property-before-deleting.js [new file with mode: 0644]
JSTests/stress/scope-operation-cache-global-property-bump-counter.js [new file with mode: 0644]
JSTests/stress/scope-operation-cache-global-property-even-if-it-fails.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/BytecodeList.rb
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/bytecode/CodeBlock.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGDesiredGlobalProperties.cpp
Source/JavaScriptCore/dfg/DFGDesiredGlobalProperties.h
Source/JavaScriptCore/dfg/DFGGraph.cpp
Source/JavaScriptCore/dfg/DFGGraph.h
Source/JavaScriptCore/dfg/DFGPlan.cpp
Source/JavaScriptCore/jit/JITPropertyAccess.cpp
Source/JavaScriptCore/jit/JITPropertyAccess32_64.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
Source/JavaScriptCore/runtime/CommonSlowPaths.h
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/Options.cpp
Source/JavaScriptCore/runtime/Options.h
Source/JavaScriptCore/runtime/ProgramExecutable.cpp

index 777a853..d07e674 100644 (file)
@@ -1,3 +1,25 @@
+2019-01-20  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Invalidate old scope operations using global lexical binding epoch
+        https://bugs.webkit.org/show_bug.cgi?id=193603
+        <rdar://problem/47380869>
+
+        Reviewed by Saam Barati.
+
+        * stress/let-lexical-binding-shadow-existing-global-property-ftl.js:
+        * stress/scope-operation-cache-global-property-before-deleting.js: Added.
+        (shouldThrow):
+        (bar):
+        * stress/scope-operation-cache-global-property-bump-counter.js: Added.
+        (shouldBe):
+        (get1):
+        (get2):
+        (get1If):
+        (get2If):
+        * stress/scope-operation-cache-global-property-even-if-it-fails.js: Added.
+        (shouldThrow):
+        (foo):
+
 2019-01-17  Saam barati  <sbarati@apple.com>
 
         StringObjectUse should not be a structure check for the original string object structure
index 20b4322..b47b72c 100644 (file)
@@ -40,6 +40,7 @@ for (var i = 0; i < 1e6; ++i)
     shouldBe(get(), 3);
 
 foo();
+shouldBe(globalThis.bar, 4);
 shouldBe(bar, 4);
 shouldBe(get(), 4);
 
diff --git a/JSTests/stress/scope-operation-cache-global-property-before-deleting.js b/JSTests/stress/scope-operation-cache-global-property-before-deleting.js
new file mode 100644 (file)
index 0000000..ca9fe0d
--- /dev/null
@@ -0,0 +1,27 @@
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+noInline(shouldThrow);
+
+function bar()
+{
+    foo = 42;
+}
+
+bar();
+bar();
+delete globalThis.foo;
+$.evalScript(`const foo = 50`);
+
+shouldThrow(() => bar(), `TypeError: Attempted to assign to readonly property.`);
diff --git a/JSTests/stress/scope-operation-cache-global-property-bump-counter.js b/JSTests/stress/scope-operation-cache-global-property-bump-counter.js
new file mode 100644 (file)
index 0000000..d1dcdd0
--- /dev/null
@@ -0,0 +1,58 @@
+//@ runDefault("--thresholdForGlobalLexicalBindingEpoch=2")
+
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+foo1 = 1;
+foo2 = 2;
+function get1() {
+    return foo1;
+}
+noInline(get1);
+
+function get2() {
+    return foo2;
+}
+noInline(get2);
+
+function get1If(condition) {
+    if (condition)
+        return foo1;
+    return -1;
+}
+noInline(get1If);
+
+function get2If(condition) {
+    if (condition)
+        return foo2;
+    return -1;
+}
+noInline(get2If);
+
+for (var i = 0; i < 1e5; ++i) {
+    shouldBe(get1(), 1);
+    shouldBe(get2(), 2);
+    shouldBe(get1(), 1);
+    shouldBe(get2(), 2);
+    shouldBe(get1If(true), 1);
+    shouldBe(get2If(true), 2);
+    shouldBe(get1If(false), -1);
+    shouldBe(get2If(false), -1);
+}
+
+$.evalScript(`const foo1 = 41;`);
+$.evalScript(`const foo2 = 42;`);
+
+for (var i = 0; i < 1e3; ++i) {
+    shouldBe(get1(), 41);
+    shouldBe(get2(), 42);
+    shouldBe(get1(), 41);
+    shouldBe(get2(), 42);
+    shouldBe(get1If(false), -1);
+    shouldBe(get2If(false), -1);
+}
+shouldBe(get1If(true), 41);
+shouldBe(get2If(true), 42);
diff --git a/JSTests/stress/scope-operation-cache-global-property-even-if-it-fails.js b/JSTests/stress/scope-operation-cache-global-property-even-if-it-fails.js
new file mode 100644 (file)
index 0000000..51dac81
--- /dev/null
@@ -0,0 +1,23 @@
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+noInline(shouldThrow);
+
+function foo() {
+    bar = 4;
+}
+Object.preventExtensions(this);
+foo();
+$.evalScript('const bar = 3;');
+shouldThrow(() => foo(), `TypeError: Attempted to assign to readonly property.`);
index 8e9e584..40275a8 100644 (file)
@@ -1,3 +1,85 @@
+2019-01-20  Yusuke Suzuki  <ysuzuki@apple.com>
+
+        [JSC] Invalidate old scope operations using global lexical binding epoch
+        https://bugs.webkit.org/show_bug.cgi?id=193603
+        <rdar://problem/47380869>
+
+        Reviewed by Saam Barati.
+
+        Even if the global lexical binding does not shadow the global property at that time, we need to clear the cached information in
+        scope related operations since we may have a global property previously. Consider the following example,
+
+            foo = 0;
+            function get() { return foo; }
+            print(get()); // 0
+            print(get()); // 0
+            delete globalThis.foo;
+            $.evalScript(`const foo = 42;`);
+            print(get()); // Should be 42, but it returns 0 if the cached information in get() is not cleared.
+
+        To invalidate the cache easily, we introduce global lexical binding epoch. It is bumped every time we introduce a new lexical binding
+        into JSGlobalLexicalEnvironment, since that name could shadow the global property name previously. In op_resolve_scope, we first check
+        the epoch stored in the metadata, and go to slow path if it is not equal to the current epoch. Our slow path code convert the scope
+        operation to the appropriate one even if the resolve type is not UnresolvedProperty type. After updating the resolve type of the bytecode,
+        we update the cached epoch to the current one, so that we can use the cached information as long as we stay in the same epoch.
+
+        In op_get_from_scope and op_put_to_scope, we do not use this epoch since Structure check can do the same thing instead. If op_resolve_type
+        is updated by the epoch, and if it starts returning JSGlobalLexicalEnvironment instead JSGlobalObject, obviously the structure check fails.
+        And in the slow path, we update op_get_from_scope and op_put_to_scope appropriately.
+
+        So, the metadata for scope related bytecodes are eventually updated to the appropriate one. In DFG and FTL, we use the watchpoint based approach.
+        In DFG and FTL, we concurrently attempt to get the watchpoint for the lexical binding and look into it by using `isStillValid()` to avoid
+        infinite compile-and-fail loop.
+
+        When the global lexical binding epoch overflows we iterate all the live CodeBlock and update the op_resolve_scope's epoch. Even if the shadowing
+        happens, it is OK if we bump the epoch, since op_resolve_scope will return JSGlobalLexicalEnvironment instead of JSGlobalObject, and following
+        structure check in op_put_to_scope and op_get_from_scope fail. We do not need to update op_get_from_scope and op_put_to_scope because of the same
+        reason.
+
+        * bytecode/BytecodeList.rb:
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::notifyLexicalBindingUpdate):
+        (JSC::CodeBlock::notifyLexicalBindingShadowing): Deleted.
+        * bytecode/CodeBlock.h:
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGDesiredGlobalProperties.cpp:
+        (JSC::DFG::DesiredGlobalProperties::isStillValidOnMainThread):
+        * dfg/DFGDesiredGlobalProperties.h:
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::watchGlobalProperty):
+        * dfg/DFGGraph.h:
+        * dfg/DFGPlan.cpp:
+        (JSC::DFG::Plan::isStillValidOnMainThread):
+        * jit/JITPropertyAccess.cpp:
+        (JSC::JIT::emit_op_resolve_scope):
+        * jit/JITPropertyAccess32_64.cpp:
+        (JSC::JIT::emit_op_resolve_scope):
+        * llint/LowLevelInterpreter32_64.asm:
+        * llint/LowLevelInterpreter64.asm:
+        * runtime/CommonSlowPaths.cpp:
+        (JSC::SLOW_PATH_DECL):
+        * runtime/CommonSlowPaths.h:
+        (JSC::CommonSlowPaths::tryCachePutToScopeGlobal):
+        (JSC::CommonSlowPaths::tryCacheGetFromScopeGlobal):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::bumpGlobalLexicalBindingEpoch):
+        (JSC::JSGlobalObject::getReferencedPropertyWatchpointSet):
+        (JSC::JSGlobalObject::ensureReferencedPropertyWatchpointSet):
+        (JSC::JSGlobalObject::notifyLexicalBindingShadowing): Deleted.
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::globalLexicalBindingEpoch const):
+        (JSC::JSGlobalObject::globalLexicalBindingEpochOffset):
+        (JSC::JSGlobalObject::addressOfGlobalLexicalBindingEpoch):
+        * runtime/Options.cpp:
+        (JSC::correctOptions):
+        (JSC::Options::initialize):
+        (JSC::Options::setOptions):
+        (JSC::Options::setOptionWithoutAlias):
+        * runtime/Options.h:
+        * runtime/ProgramExecutable.cpp:
+        (JSC::ProgramExecutable::initializeGlobalProperties):
+
 2019-01-20  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
 
         [JSC] Shrink data structure size in JSC/heap
index bb5f126..115bd3b 100644 (file)
@@ -833,8 +833,11 @@ op :resolve_scope,
     },
     metadata: {
         resolveType: ResolveType, # offset 4
-        localScopeDepth: unsigned, # offset 5
-        _: { # offset 6
+        _0: { # offset 5
+            localScopeDepth: unsigned,
+            globalLexicalBindingEpoch: unsigned,
+        },
+        _1: { # offset 6
              # written during linking
              lexicalEnvironment: WriteBarrierBase[JSCell], # lexicalEnvironment && type == ModuleVar
              symbolTable: WriteBarrierBase[SymbolTable], # lexicalEnvironment && type != ModuleVar
index 1b5d222..dccc411 100644 (file)
@@ -2668,18 +2668,23 @@ void CodeBlock::tallyFrequentExitSites()
 }
 #endif // ENABLE(DFG_JIT)
 
-void CodeBlock::notifyLexicalBindingShadowing(VM& vm, const IdentifierSet& set)
+void CodeBlock::notifyLexicalBindingUpdate()
 {
     // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed.
     // https://bugs.webkit.org/show_bug.cgi?id=193347
     if (scriptMode() == JSParserScriptMode::Module)
         return;
     JSGlobalObject* globalObject = m_globalObject.get();
-
-    auto scope = DECLARE_THROW_SCOPE(vm);
+    JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalLexicalEnvironment*>(globalObject->globalScope());
+    SymbolTable* symbolTable = globalLexicalEnvironment->symbolTable();
 
     ConcurrentJSLocker locker(m_lock);
 
+    auto isShadowed = [&] (UniquedStringImpl* uid) {
+        ConcurrentJSLocker locker(symbolTable->m_lock);
+        return symbolTable->contains(locker, uid);
+    };
+
     for (const auto& instruction : *m_instructions) {
         OpcodeID opcodeID = instruction->opcodeID();
         switch (opcodeID) {
@@ -2689,72 +2694,13 @@ void CodeBlock::notifyLexicalBindingShadowing(VM& vm, const IdentifierSet& set)
             ResolveType originalResolveType = metadata.m_resolveType;
             if (originalResolveType == GlobalProperty || originalResolveType == GlobalPropertyWithVarInjectionChecks) {
                 const Identifier& ident = identifier(bytecode.m_var);
-                if (set.contains(ident.impl())) {
-                    // We pass JSGlobalLexicalScope as a start point of the scope chain.
-                    // It should immediately find the lexical binding because that's the reason why we perform this rewriting now.
-                    ResolveOp op = JSScope::abstractResolve(m_globalObject->globalExec(), bytecode.m_localScopeDepth, globalObject->globalScope(), ident, Get, bytecode.m_resolveType, InitializationMode::NotInitialization);
-                    scope.releaseAssertNoException();
-                    ASSERT(op.type == GlobalLexicalVarWithVarInjectionChecks || op.type == GlobalLexicalVar);
-                    metadata.m_resolveType = needsVarInjectionChecks(originalResolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar;
-                    metadata.m_localScopeDepth = 0;
-                    ASSERT(!op.lexicalEnvironment);
-                    JSScope* constantScope = JSScope::constantScopeForCodeBlock(metadata.m_resolveType, this);
-                    ASSERT(constantScope == globalObject->globalScope());
-                    metadata.m_constantScope.set(vm, this, constantScope);
-                    dataLogLnIf(CodeBlockInternal::verbose, "Rewrite op_resolve_scope from ", originalResolveType, " to ", metadata.m_resolveType);
-                }
+                if (isShadowed(ident.impl()))
+                    metadata.m_globalLexicalBindingEpoch = 0;
+                else
+                    metadata.m_globalLexicalBindingEpoch = globalObject->globalLexicalBindingEpoch();
             }
             break;
         }
-
-        case op_get_from_scope: {
-            auto bytecode = instruction->as<OpGetFromScope>();
-            auto& metadata = bytecode.metadata(this);
-            ResolveType originalResolveType = metadata.m_getPutInfo.resolveType();
-            if (originalResolveType == GlobalProperty || originalResolveType == GlobalPropertyWithVarInjectionChecks) {
-                const Identifier& ident = identifier(bytecode.m_var);
-                if (set.contains(ident.impl())) {
-                    // We pass JSGlobalLexicalScope as a start point of the scope chain.
-                    // It should immediately find the lexical binding because that's the reason why we perform this rewriting now.
-                    ResolveOp op = JSScope::abstractResolve(m_globalObject->globalExec(), bytecode.m_localScopeDepth, globalObject->globalScope(), ident, Get, bytecode.m_getPutInfo.resolveType(), InitializationMode::NotInitialization);
-                    scope.releaseAssertNoException();
-                    ASSERT(op.type == GlobalLexicalVarWithVarInjectionChecks || op.type == GlobalLexicalVar);
-                    metadata.m_getPutInfo = GetPutInfo(bytecode.m_getPutInfo.resolveMode(), needsVarInjectionChecks(originalResolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar, bytecode.m_getPutInfo.initializationMode());
-                    metadata.m_watchpointSet = op.watchpointSet;
-                    metadata.m_operand = op.operand;
-                    dataLogLnIf(CodeBlockInternal::verbose, "Rewrite op_get_from_scope from ", originalResolveType, " to ", metadata.m_getPutInfo.resolveType());
-                }
-            }
-            break;
-        }
-
-        case op_put_to_scope: {
-            auto bytecode = instruction->as<OpPutToScope>();
-            auto& metadata = bytecode.metadata(this);
-            ResolveType originalResolveType = metadata.m_getPutInfo.resolveType();
-            if (originalResolveType == GlobalProperty || originalResolveType == GlobalPropertyWithVarInjectionChecks) {
-                const Identifier& ident = identifier(bytecode.m_var);
-                if (set.contains(ident.impl())) {
-                    // We pass JSGlobalLexicalScope as a start point of the scope chain.
-                    // It should immediately find the lexical binding because that's the reason why we perform this rewriting now.
-                    ResolveOp op = JSScope::abstractResolve(m_globalObject->globalExec(), bytecode.m_symbolTableOrScopeDepth, globalObject->globalScope(), ident, Put, bytecode.m_getPutInfo.resolveType(), bytecode.m_getPutInfo.initializationMode());
-                    scope.releaseAssertNoException();
-                    ASSERT(op.type == GlobalLexicalVarWithVarInjectionChecks || op.type == GlobalLexicalVar || op.type == Dynamic);
-
-                    ResolveType resolveType = op.type;
-                    metadata.m_watchpointSet = nullptr;
-                    if (resolveType == GlobalLexicalVarWithVarInjectionChecks || resolveType == GlobalLexicalVar) {
-                        resolveType = needsVarInjectionChecks(originalResolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar;
-                        metadata.m_watchpointSet = op.watchpointSet;
-                    }
-                    metadata.m_getPutInfo = GetPutInfo(bytecode.m_getPutInfo.resolveMode(), resolveType, bytecode.m_getPutInfo.initializationMode());
-                    metadata.m_operand = op.operand;
-                    dataLogLnIf(CodeBlockInternal::verbose, "Rewrite op_put_to_scope from ", originalResolveType, " to ", metadata.m_getPutInfo.resolveType());
-                }
-            }
-            break;
-        }
-
         default:
             break;
         }
index c748fc0..c747426 100644 (file)
@@ -195,7 +195,7 @@ public:
     void visitChildren(SlotVisitor&);
     void finalizeUnconditionally(VM&);
 
-    void notifyLexicalBindingShadowing(VM&, const IdentifierSet&);
+    void notifyLexicalBindingUpdate();
 
     void dumpSource();
     void dumpSource(PrintStream&);
index 4e18df7..9d1614d 100644 (file)
@@ -6167,9 +6167,10 @@ void ByteCodeParser::parseBlock(unsigned limit)
             // https://bugs.webkit.org/show_bug.cgi?id=193347
             if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) {
                 if (resolveType == GlobalProperty || resolveType == GlobalPropertyWithVarInjectionChecks) {
-                    unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_var];
                     JSGlobalObject* globalObject = m_inlineStackTop->m_codeBlock->globalObject();
-                    m_graph.globalProperties().addLazily(DesiredGlobalProperty(globalObject, identifierNumber));
+                    unsigned identifierNumber = m_inlineStackTop->m_identifierRemap[bytecode.m_var];
+                    if (!m_graph.watchGlobalProperty(globalObject, identifierNumber))
+                        addToGraph(ForceOSRExit);
                 }
             }
 
@@ -6281,8 +6282,10 @@ void ByteCodeParser::parseBlock(unsigned limit)
             case GlobalPropertyWithVarInjectionChecks: {
                 // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed.
                 // https://bugs.webkit.org/show_bug.cgi?id=193347
-                if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module)
-                    m_graph.globalProperties().addLazily(DesiredGlobalProperty(globalObject, identifierNumber));
+                if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) {
+                    if (!m_graph.watchGlobalProperty(globalObject, identifierNumber))
+                        addToGraph(ForceOSRExit);
+                }
 
                 SpeculatedType prediction = getPrediction();
 
@@ -6456,8 +6459,10 @@ void ByteCodeParser::parseBlock(unsigned limit)
             case GlobalPropertyWithVarInjectionChecks: {
                 // FIXME: Currently, module code do not query to JSGlobalLexicalEnvironment. So this case should be removed once it is fixed.
                 // https://bugs.webkit.org/show_bug.cgi?id=193347
-                if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module)
-                    m_graph.globalProperties().addLazily(DesiredGlobalProperty(globalObject, identifierNumber));
+                if (m_inlineStackTop->m_codeBlock->scriptMode() != JSParserScriptMode::Module) {
+                    if (!m_graph.watchGlobalProperty(globalObject, identifierNumber))
+                        addToGraph(ForceOSRExit);
+                }
 
                 PutByIdStatus status;
                 if (uid)
index 8f7b9ce..ba6841d 100644 (file)
 
 namespace JSC { namespace DFG {
 
-bool DesiredGlobalProperties::isStillValidOnMainThread(DesiredIdentifiers& identifiers)
+bool DesiredGlobalProperties::isStillValidOnMainThread(VM& vm, DesiredIdentifiers& identifiers)
 {
+    bool isStillValid = true;
     for (const auto& property : m_set) {
         auto* uid = identifiers.at(property.identifierNumber());
-        if (auto* watchpointSet = property.globalObject()->getReferencedPropertyWatchpointSet(uid)) {
-            if (!watchpointSet->isStillValid())
-                return false;
+        JSGlobalObject* globalObject = property.globalObject();
+        {
+            SymbolTable* symbolTable = globalObject->globalLexicalEnvironment()->symbolTable();
+            ConcurrentJSLocker locker(symbolTable->m_lock);
+            if (!symbolTable->contains(locker, uid))
+                continue;
         }
+        // Set invalidated WatchpointSet here to prevent further compile-and-fail loop.
+        property.globalObject()->ensureReferencedPropertyWatchpointSet(uid).fireAll(vm, "Lexical binding shadows an existing global property");
+        isStillValid = false;
     }
-    return true;
+    return isStillValid;
 }
 
 void DesiredGlobalProperties::reallyAdd(CodeBlock* codeBlock, DesiredIdentifiers& identifiers, CommonData& common)
index e340a59..69bd36d 100644 (file)
@@ -47,7 +47,7 @@ public:
         m_set.add(WTFMove(property));
     }
 
-    bool isStillValidOnMainThread(DesiredIdentifiers&);
+    bool isStillValidOnMainThread(VM&, DesiredIdentifiers&);
 
     void reallyAdd(CodeBlock*, DesiredIdentifiers&, CommonData&);
 
index 83cc1d4..5a60afb 100644 (file)
@@ -1058,6 +1058,20 @@ bool Graph::isSafeToLoad(JSObject* base, PropertyOffset offset)
     return m_safeToLoad.contains(std::make_pair(base, offset));
 }
 
+bool Graph::watchGlobalProperty(JSGlobalObject* globalObject, unsigned identifierNumber)
+{
+    UniquedStringImpl* uid = identifiers()[identifierNumber];
+    // If we already have a WatchpointSet, and it is already invalidated, it means that this scope operation must be changed from GlobalProperty to GlobalLexicalVar,
+    // but we still have stale metadata here since we have not yet executed this bytecode operation since the invalidation. Just emitting ForceOSRExit to update the
+    // metadata when it reaches to this code.
+    if (auto* watchpoint = globalObject->getReferencedPropertyWatchpointSet(uid)) {
+        if (!watchpoint->isStillValid())
+            return false;
+    }
+    globalProperties().addLazily(DesiredGlobalProperty(globalObject, identifierNumber));
+    return true;
+}
+
 FullBytecodeLiveness& Graph::livenessFor(CodeBlock* codeBlock)
 {
     HashMap<CodeBlock*, std::unique_ptr<FullBytecodeLiveness>>::iterator iter = m_bytecodeLiveness.find(codeBlock);
index ee2e267..151f573 100644 (file)
@@ -793,6 +793,8 @@ public:
     bool watchCondition(const ObjectPropertyCondition&);
     bool watchConditions(const ObjectPropertyConditionSet&);
 
+    bool watchGlobalProperty(JSGlobalObject*, unsigned identifierNumber);
+
     // Checks if it's known that loading from the given object at the given offset is fine. This is
     // computed by tracking which conditions we track with watchCondition().
     bool isSafeToLoad(JSObject* base, PropertyOffset);
index e77723f..c5e899b 100644 (file)
@@ -573,7 +573,7 @@ void Plan::notifyReady()
 
 bool Plan::isStillValidOnMainThread()
 {
-    return m_globalProperties.isStillValidOnMainThread(m_identifiers);
+    return m_globalProperties.isStillValidOnMainThread(*m_vm, m_identifiers);
 }
 
 CompilationResult Plan::finalizeWithoutNotifyingCallback()
index 92f37ec..2f4f32e 100644 (file)
@@ -766,7 +766,17 @@ void JIT::emit_op_resolve_scope(const Instruction* currentInstruction)
     auto emitCode = [&] (ResolveType resolveType) {
         switch (resolveType) {
         case GlobalProperty:
-        case GlobalPropertyWithVarInjectionChecks:
+        case GlobalPropertyWithVarInjectionChecks: {
+            JSScope* constantScope = JSScope::constantScopeForCodeBlock(resolveType, m_codeBlock);
+            RELEASE_ASSERT(constantScope);
+            emitVarInjectionCheck(needsVarInjectionChecks(resolveType));
+            load32(&metadata.m_globalLexicalBindingEpoch, regT1);
+            addSlowCase(branch32(NotEqual, AbsoluteAddress(m_codeBlock->globalObject()->addressOfGlobalLexicalBindingEpoch()), regT1));
+            move(TrustedImmPtr(constantScope), regT0);
+            emitPutVirtualRegister(dst);
+            break;
+        }
+
         case GlobalVar:
         case GlobalVarWithVarInjectionChecks:
         case GlobalLexicalVar:
@@ -799,11 +809,17 @@ void JIT::emit_op_resolve_scope(const Instruction* currentInstruction)
     switch (resolveType) {
     case GlobalProperty:
     case GlobalPropertyWithVarInjectionChecks: {
-        // Since these GlobalProperty can be changed to GlobalLexicalVar, we should load the value from metadata.
-        JSScope** constantScopeSlot = metadata.m_constantScope.slot();
-        emitVarInjectionCheck(needsVarInjectionChecks(resolveType));
-        loadPtr(constantScopeSlot, regT0);
-        emitPutVirtualRegister(dst);
+        JumpList skipToEnd;
+        load32(&metadata.m_resolveType, regT0);
+
+        Jump notGlobalProperty = branch32(NotEqual, regT0, TrustedImm32(resolveType));
+        emitCode(resolveType);
+        skipToEnd.append(jump());
+
+        notGlobalProperty.link(this);
+        emitCode(needsVarInjectionChecks(resolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar);
+
+        skipToEnd.link(this);
         break;
     }
     case UnresolvedProperty:
index 33d6ae4..e2da115 100644 (file)
@@ -769,7 +769,18 @@ void JIT::emit_op_resolve_scope(const Instruction* currentInstruction)
     auto emitCode = [&] (ResolveType resolveType) {
         switch (resolveType) {
         case GlobalProperty:
-        case GlobalPropertyWithVarInjectionChecks:
+        case GlobalPropertyWithVarInjectionChecks: {
+            JSScope* constantScope = JSScope::constantScopeForCodeBlock(resolveType, m_codeBlock);
+            RELEASE_ASSERT(constantScope);
+            emitVarInjectionCheck(needsVarInjectionChecks(resolveType));
+            load32(&metadata.m_globalLexicalBindingEpoch, regT1);
+            addSlowCase(branch32(NotEqual, AbsoluteAddress(m_codeBlock->globalObject()->addressOfGlobalLexicalBindingEpoch()), regT1));
+            move(TrustedImm32(JSValue::CellTag), regT1);
+            move(TrustedImmPtr(constantScope), regT0);
+            emitStore(dst, regT1, regT0);
+            break;
+        }
+
         case GlobalVar:
         case GlobalVarWithVarInjectionChecks: 
         case GlobalLexicalVar:
@@ -803,12 +814,17 @@ void JIT::emit_op_resolve_scope(const Instruction* currentInstruction)
     switch (resolveType) {
     case GlobalProperty:
     case GlobalPropertyWithVarInjectionChecks: {
-        // Since these GlobalProperty can be changed to GlobalLexicalVar, we should load the value from metadata.
-        JSScope** constantScopeSlot = metadata.m_constantScope.slot();
-        emitVarInjectionCheck(needsVarInjectionChecks(resolveType));
-        move(TrustedImm32(JSValue::CellTag), regT1);
-        loadPtr(constantScopeSlot, regT0);
-        emitStore(dst, regT1, regT0);
+        JumpList skipToEnd;
+        load32(&metadata.m_resolveType, regT0);
+
+        Jump notGlobalProperty = branch32(NotEqual, regT0, TrustedImm32(resolveType));
+        emitCode(resolveType);
+        skipToEnd.append(jump());
+
+        notGlobalProperty.link(this);
+        emitCode(needsVarInjectionChecks(resolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar);
+
+        skipToEnd.link(this);
         break;
     }
     case UnresolvedProperty:
index 6f7deab..c443b48 100644 (file)
@@ -2092,11 +2092,20 @@ end
 
 llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch, metadata, return)
 
-    macro getConstantScope()
-        loadp OpResolveScope::Metadata::m_constantScope[t5],  t0
+    macro getConstantScope(dst)
+        loadp OpResolveScope::Metadata::m_constantScope[t5], dst
+    end
+
+    macro returnConstantScope()
+        getConstantScope(t0)
         return(CellTag, t0)
     end
 
+    macro globalLexicalBindingEpochCheck(slowPath, globalObject, scratch)
+        loadi OpResolveScope::Metadata::m_globalLexicalBindingEpoch[globalObject], scratch
+        bineq JSGlobalObject::m_globalLexicalBindingEpoch[globalObject], scratch, slowPath
+    end
+
     macro resolveScope()
         loadi OpResolveScope::Metadata::m_localScopeDepth[t5], t2
         get(m_scope, t0)
@@ -2117,15 +2126,17 @@ llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch
 
 #rGlobalProperty:
     bineq t0, GlobalProperty, .rGlobalVar
-    getConstantScope()
+    getConstantScope(t0)
+    globalLexicalBindingEpochCheck(.rDynamic, t0, t2)
+    return(CellTag, t0)
 
 .rGlobalVar:
     bineq t0, GlobalVar, .rGlobalLexicalVar
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalLexicalVar:
     bineq t0, GlobalLexicalVar, .rClosureVar
-    getConstantScope()
+    returnConstantScope()
 
 .rClosureVar:
     bineq t0, ClosureVar, .rModuleVar
@@ -2133,22 +2144,24 @@ llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch
 
 .rModuleVar:
     bineq t0, ModuleVar, .rGlobalPropertyWithVarInjectionChecks
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalPropertyWithVarInjectionChecks:
     bineq t0, GlobalPropertyWithVarInjectionChecks, .rGlobalVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic)
-    getConstantScope()
+    getConstantScope(t0)
+    globalLexicalBindingEpochCheck(.rDynamic, t0, t2)
+    return(CellTag, t0)
 
 .rGlobalVarWithVarInjectionChecks:
     bineq t0, GlobalVarWithVarInjectionChecks, .rGlobalLexicalVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic)
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalLexicalVarWithVarInjectionChecks:
     bineq t0, GlobalLexicalVarWithVarInjectionChecks, .rClosureVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic)
-    getConstantScope()
+    returnConstantScope()
 
 .rClosureVarWithVarInjectionChecks:
     bineq t0, ClosureVarWithVarInjectionChecks, .rDynamic
index 4e37a09..bd7d02c 100644 (file)
@@ -2150,11 +2150,20 @@ end
 llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch, metadata, return)
     metadata(t5, t0)
 
-    macro getConstantScope()
-        loadp OpResolveScope::Metadata::m_constantScope[t5],  t0
+    macro getConstantScope(dst)
+        loadp OpResolveScope::Metadata::m_constantScope[t5], dst
+    end
+
+    macro returnConstantScope()
+        getConstantScope(t0)
         return(t0)
     end
 
+    macro globalLexicalBindingEpochCheck(slowPath, globalObject, scratch)
+        loadi OpResolveScope::Metadata::m_globalLexicalBindingEpoch[globalObject], scratch
+        bineq JSGlobalObject::m_globalLexicalBindingEpoch[globalObject], scratch, slowPath
+    end
+
     macro resolveScope()
         loadi OpResolveScope::Metadata::m_localScopeDepth[t5], t2
         get(m_scope, t0)
@@ -2174,15 +2183,17 @@ llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch
 
 #rGlobalProperty:
     bineq t0, GlobalProperty, .rGlobalVar
-    getConstantScope()
+    getConstantScope(t0)
+    globalLexicalBindingEpochCheck(.rDynamic, t0, t2)
+    return(t0)
 
 .rGlobalVar:
     bineq t0, GlobalVar, .rGlobalLexicalVar
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalLexicalVar:
     bineq t0, GlobalLexicalVar, .rClosureVar
-    getConstantScope()
+    returnConstantScope()
 
 .rClosureVar:
     bineq t0, ClosureVar, .rModuleVar
@@ -2190,22 +2201,24 @@ llintOpWithMetadata(op_resolve_scope, OpResolveScope, macro (size, get, dispatch
 
 .rModuleVar:
     bineq t0, ModuleVar, .rGlobalPropertyWithVarInjectionChecks
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalPropertyWithVarInjectionChecks:
     bineq t0, GlobalPropertyWithVarInjectionChecks, .rGlobalVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic, t2)
-    getConstantScope()
+    getConstantScope(t0)
+    globalLexicalBindingEpochCheck(.rDynamic, t0, t2)
+    return(t0)
 
 .rGlobalVarWithVarInjectionChecks:
     bineq t0, GlobalVarWithVarInjectionChecks, .rGlobalLexicalVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic, t2)
-    getConstantScope()
+    returnConstantScope()
 
 .rGlobalLexicalVarWithVarInjectionChecks:
     bineq t0, GlobalLexicalVarWithVarInjectionChecks, .rClosureVarWithVarInjectionChecks
     varInjectionCheck(.rDynamic, t2)
-    getConstantScope()
+    returnConstantScope()
 
 .rClosureVarWithVarInjectionChecks:
     bineq t0, ClosureVarWithVarInjectionChecks, .rDynamic
@@ -2256,7 +2269,7 @@ llintOpWithMetadata(op_get_from_scope, OpGetFromScope, macro (size, get, dispatc
 
 #gGlobalProperty:
     bineq t0, GlobalProperty, .gGlobalVar
-    loadWithStructureCheck(OpGetFromScope, get, .gDynamic)
+    loadWithStructureCheck(OpGetFromScope, get, .gDynamic) # This structure check includes lexical binding epoch check since when the epoch is changed, scope will be changed too.
     getProperty()
 
 .gGlobalVar:
@@ -2277,7 +2290,7 @@ llintOpWithMetadata(op_get_from_scope, OpGetFromScope, macro (size, get, dispatc
 
 .gGlobalPropertyWithVarInjectionChecks:
     bineq t0, GlobalPropertyWithVarInjectionChecks, .gGlobalVarWithVarInjectionChecks
-    loadWithStructureCheck(OpGetFromScope, get, .gDynamic)
+    loadWithStructureCheck(OpGetFromScope, get, .gDynamic) # This structure check includes lexical binding epoch check since when the epoch is changed, scope will be changed too.
     getProperty()
 
 .gGlobalVarWithVarInjectionChecks:
@@ -2364,7 +2377,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m
 
 .pGlobalProperty:
     bineq t0, GlobalProperty, .pGlobalVar
-    loadWithStructureCheck(OpPutToScope, get, .pDynamic)
+    loadWithStructureCheck(OpPutToScope, get, .pDynamic) # This structure check includes lexical binding epoch check since when the epoch is changed, scope will be changed too.
     putProperty()
     writeBarrierOnOperands(size, get, m_scope, m_value)
     dispatch()
@@ -2391,7 +2404,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m
 
 .pGlobalPropertyWithVarInjectionChecks:
     bineq t0, GlobalPropertyWithVarInjectionChecks, .pGlobalVarWithVarInjectionChecks
-    loadWithStructureCheck(OpPutToScope, get, .pDynamic)
+    loadWithStructureCheck(OpPutToScope, get, .pDynamic) # This structure check includes lexical binding epoch check since when the epoch is changed, scope will be changed too.
     putProperty()
     writeBarrierOnOperands(size, get, m_scope, m_value)
     dispatch()
index b880458..bbbadab 100644 (file)
@@ -1067,29 +1067,31 @@ SLOW_PATH_DECL(slow_path_resolve_scope)
     // ModuleVar does not keep the scope register value alive in DFG.
     ASSERT(resolveType != ModuleVar);
 
-    if (resolveType == UnresolvedProperty || resolveType == UnresolvedPropertyWithVarInjectionChecks) {
+    switch (resolveType) {
+    case GlobalProperty:
+    case GlobalPropertyWithVarInjectionChecks:
+    case UnresolvedProperty:
+    case UnresolvedPropertyWithVarInjectionChecks: {
         if (resolvedScope->isGlobalObject()) {
             JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(resolvedScope);
             bool hasProperty = globalObject->hasProperty(exec, ident);
             CHECK_EXCEPTION();
             if (hasProperty) {
                 ConcurrentJSLocker locker(exec->codeBlock()->m_lock);
-                if (resolveType == UnresolvedProperty)
-                    metadata.m_resolveType = GlobalProperty;
-                else
-                    metadata.m_resolveType = GlobalPropertyWithVarInjectionChecks;
-
+                metadata.m_resolveType = needsVarInjectionChecks(resolveType) ? GlobalPropertyWithVarInjectionChecks : GlobalProperty;
                 metadata.m_globalObject = globalObject;
+                metadata.m_globalLexicalBindingEpoch = globalObject->globalLexicalBindingEpoch();
             }
         } else if (resolvedScope->isGlobalLexicalEnvironment()) {
             JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalLexicalEnvironment*>(resolvedScope);
             ConcurrentJSLocker locker(exec->codeBlock()->m_lock);
-            if (resolveType == UnresolvedProperty)
-                metadata.m_resolveType = GlobalLexicalVar;
-            else
-                metadata.m_resolveType = GlobalLexicalVarWithVarInjectionChecks;
+            metadata.m_resolveType = needsVarInjectionChecks(resolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar;
             metadata.m_globalLexicalEnvironment = globalLexicalEnvironment;
         }
+        break;
+    }
+    default:
+        break;
     }
 
     RETURN(resolvedScope);
index 130a0c8..7eeaf70 100644 (file)
@@ -124,28 +124,39 @@ inline void tryCachePutToScopeGlobal(
     // Covers implicit globals. Since they don't exist until they first execute, we didn't know how to cache them at compile time.
     auto& metadata = bytecode.metadata(exec);
     ResolveType resolveType = metadata.m_getPutInfo.resolveType();
-    if (resolveType != GlobalProperty && resolveType != GlobalPropertyWithVarInjectionChecks 
-        && resolveType != UnresolvedProperty && resolveType != UnresolvedPropertyWithVarInjectionChecks)
-        return;
 
-    if (resolveType == UnresolvedProperty || resolveType == UnresolvedPropertyWithVarInjectionChecks) {
+    switch (resolveType) {
+    case UnresolvedProperty:
+    case UnresolvedPropertyWithVarInjectionChecks: {
         if (scope->isGlobalObject()) {
-            ResolveType newResolveType = resolveType == UnresolvedProperty ? GlobalProperty : GlobalPropertyWithVarInjectionChecks;
-            resolveType = newResolveType;
+            ResolveType newResolveType = needsVarInjectionChecks(resolveType) ? GlobalPropertyWithVarInjectionChecks : GlobalProperty;
+            resolveType = newResolveType; // Allow below caching mechanism to kick in.
             ConcurrentJSLocker locker(codeBlock->m_lock);
             metadata.m_getPutInfo = GetPutInfo(metadata.m_getPutInfo.resolveMode(), newResolveType, metadata.m_getPutInfo.initializationMode());
-        } else if (scope->isGlobalLexicalEnvironment()) {
+            break;
+        }
+        FALLTHROUGH;
+    }
+    case GlobalProperty:
+    case GlobalPropertyWithVarInjectionChecks: {
+         // Global Lexical Binding Epoch is changed. Update op_get_from_scope from GlobalProperty to GlobalLexicalVar.
+        if (scope->isGlobalLexicalEnvironment()) {
             JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalLexicalEnvironment*>(scope);
-            ResolveType newResolveType = resolveType == UnresolvedProperty ? GlobalLexicalVar : GlobalLexicalVarWithVarInjectionChecks;
+            ResolveType newResolveType = needsVarInjectionChecks(resolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar;
             metadata.m_getPutInfo = GetPutInfo(metadata.m_getPutInfo.resolveMode(), newResolveType, metadata.m_getPutInfo.initializationMode());
             SymbolTableEntry entry = globalLexicalEnvironment->symbolTable()->get(ident.impl());
             ASSERT(!entry.isNull());
             ConcurrentJSLocker locker(codeBlock->m_lock);
             metadata.m_watchpointSet = entry.watchpointSet();
             metadata.m_operand = reinterpret_cast<uintptr_t>(globalLexicalEnvironment->variableAt(entry.scopeOffset()).slot());
+            return;
         }
+        break;
     }
-    
+    default:
+        return;
+    }
+
     if (resolveType == GlobalProperty || resolveType == GlobalPropertyWithVarInjectionChecks) {
         VM& vm = exec->vm();
         JSGlobalObject* globalObject = codeBlock->globalObject();
@@ -176,22 +187,36 @@ inline void tryCacheGetFromScopeGlobal(
     auto& metadata = bytecode.metadata(exec);
     ResolveType resolveType = metadata.m_getPutInfo.resolveType();
 
-    if (resolveType == UnresolvedProperty || resolveType == UnresolvedPropertyWithVarInjectionChecks) {
+    switch (resolveType) {
+    case UnresolvedProperty:
+    case UnresolvedPropertyWithVarInjectionChecks: {
         if (scope->isGlobalObject()) {
-            ResolveType newResolveType = resolveType == UnresolvedProperty ? GlobalProperty : GlobalPropertyWithVarInjectionChecks;
+            ResolveType newResolveType = needsVarInjectionChecks(resolveType) ? GlobalPropertyWithVarInjectionChecks : GlobalProperty;
             resolveType = newResolveType; // Allow below caching mechanism to kick in.
             ConcurrentJSLocker locker(exec->codeBlock()->m_lock);
             metadata.m_getPutInfo = GetPutInfo(metadata.m_getPutInfo.resolveMode(), newResolveType, metadata.m_getPutInfo.initializationMode());
-        } else if (scope->isGlobalLexicalEnvironment()) {
+            break;
+        }
+        FALLTHROUGH;
+    }
+    case GlobalProperty:
+    case GlobalPropertyWithVarInjectionChecks: {
+         // Global Lexical Binding Epoch is changed. Update op_get_from_scope from GlobalProperty to GlobalLexicalVar.
+        if (scope->isGlobalLexicalEnvironment()) {
             JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalLexicalEnvironment*>(scope);
-            ResolveType newResolveType = resolveType == UnresolvedProperty ? GlobalLexicalVar : GlobalLexicalVarWithVarInjectionChecks;
+            ResolveType newResolveType = needsVarInjectionChecks(resolveType) ? GlobalLexicalVarWithVarInjectionChecks : GlobalLexicalVar;
             SymbolTableEntry entry = globalLexicalEnvironment->symbolTable()->get(ident.impl());
             ASSERT(!entry.isNull());
             ConcurrentJSLocker locker(exec->codeBlock()->m_lock);
             metadata.m_getPutInfo = GetPutInfo(metadata.m_getPutInfo.resolveMode(), newResolveType, metadata.m_getPutInfo.initializationMode());
             metadata.m_watchpointSet = entry.watchpointSet();
             metadata.m_operand = reinterpret_cast<uintptr_t>(globalLexicalEnvironment->variableAt(entry.scopeOffset()).slot());
+            return;
         }
+        break;
+    }
+    default:
+        return;
     }
 
     // Covers implicit globals. Since they don't exist until they first execute, we didn't know how to cache them at compile time.
index bb70cf5..e540048 100644 (file)
@@ -1852,20 +1852,17 @@ const HashSet<String>& JSGlobalObject::intlPluralRulesAvailableLocales()
 }
 #endif // ENABLE(INTL)
 
-void JSGlobalObject::notifyLexicalBindingShadowing(VM& vm, const IdentifierSet& set)
+void JSGlobalObject::bumpGlobalLexicalBindingEpoch(VM& vm)
 {
-    auto scope = DECLARE_THROW_SCOPE(vm);
-#if ENABLE(DFG_JIT)
-    for (const auto& key : set)
-        ensureReferencedPropertyWatchpointSet(key.get()).fireAll(vm, "Lexical binding shadows the existing global properties");
-#endif
-    vm.heap.codeBlockSet().iterate([&] (CodeBlock* codeBlock) {
-        if (codeBlock->globalObject() != this)
-            return;
-        codeBlock->notifyLexicalBindingShadowing(vm, set);
-        scope.assertNoException();
-    });
-    scope.release();
+    if (++m_globalLexicalBindingEpoch == Options::thresholdForGlobalLexicalBindingEpoch()) {
+        // Since the epoch overflows, we should rewrite all the CodeBlock to adjust to the newly started generation.
+        m_globalLexicalBindingEpoch = 1;
+        vm.heap.codeBlockSet().iterate([&] (CodeBlock* codeBlock) {
+            if (codeBlock->globalObject() != this)
+                return;
+            codeBlock->notifyLexicalBindingUpdate();
+        });
+    }
 }
 
 void JSGlobalObject::queueMicrotask(Ref<Microtask>&& task)
@@ -1891,11 +1888,13 @@ bool JSGlobalObject::hasInteractiveDebugger() const
 #if ENABLE(DFG_JIT)
 WatchpointSet* JSGlobalObject::getReferencedPropertyWatchpointSet(UniquedStringImpl* uid)
 {
+    ConcurrentJSLocker locker(m_referencedGlobalPropertyWatchpointSetsLock);
     return m_referencedGlobalPropertyWatchpointSets.get(uid);
 }
 
 WatchpointSet& JSGlobalObject::ensureReferencedPropertyWatchpointSet(UniquedStringImpl* uid)
 {
+    ConcurrentJSLocker locker(m_referencedGlobalPropertyWatchpointSetsLock);
     return m_referencedGlobalPropertyWatchpointSets.ensure(uid, [] {
         return WatchpointSet::create(IsWatched);
     }).iterator->value.get();
index 3a1382d..d52d0fb 100644 (file)
@@ -486,10 +486,12 @@ public:
 #if ENABLE(DFG_JIT)
     using ReferencedGlobalPropertyWatchpointSets = HashMap<RefPtr<UniquedStringImpl>, Ref<WatchpointSet>, IdentifierRepHash>;
     ReferencedGlobalPropertyWatchpointSets m_referencedGlobalPropertyWatchpointSets;
+    ConcurrentJSLock m_referencedGlobalPropertyWatchpointSetsLock;
 #endif
 
     bool m_evalEnabled { true };
     bool m_webAssemblyEnabled { true };
+    unsigned m_globalLexicalBindingEpoch { 1 };
     String m_evalDisabledErrorMessage;
     String m_webAssemblyDisabledErrorMessage;
     RuntimeFlags m_runtimeFlags;
@@ -751,7 +753,10 @@ public:
     const HashSet<String>& intlPluralRulesAvailableLocales();
 #endif // ENABLE(INTL)
 
-    void notifyLexicalBindingShadowing(VM&, const IdentifierSet&);
+    void bumpGlobalLexicalBindingEpoch(VM&);
+    unsigned globalLexicalBindingEpoch() const { return m_globalLexicalBindingEpoch; }
+    static ptrdiff_t globalLexicalBindingEpochOffset() { return OBJECT_OFFSETOF(JSGlobalObject, m_globalLexicalBindingEpoch); }
+    unsigned* addressOfGlobalLexicalBindingEpoch() { return &m_globalLexicalBindingEpoch; }
 
     void setConsoleClient(ConsoleClient* consoleClient) { m_consoleClient = consoleClient; }
     ConsoleClient* consoleClient() const { return m_consoleClient; }
index 962d87e..8b05ca8 100644 (file)
@@ -376,6 +376,13 @@ static void overrideDefaults()
 #endif
 }
 
+static void correctOptions()
+{
+    unsigned thresholdForGlobalLexicalBindingEpoch = Options::thresholdForGlobalLexicalBindingEpoch();
+    if (thresholdForGlobalLexicalBindingEpoch == 0 || thresholdForGlobalLexicalBindingEpoch == 1)
+        Options::thresholdForGlobalLexicalBindingEpoch() = UINT_MAX;
+}
+
 static void recomputeDependentOptions()
 {
 #if !defined(NDEBUG)
@@ -572,6 +579,8 @@ void Options::initialize()
 #if 0
                 ; // Deconfuse editors that do auto indentation
 #endif
+
+            correctOptions();
     
             recomputeDependentOptions();
 
@@ -705,6 +714,8 @@ bool Options::setOptions(const char* optionsStr)
         }
     }
 
+    correctOptions();
+
     recomputeDependentOptions();
 
     dumpOptionsIfNeeded();
@@ -741,6 +752,7 @@ bool Options::setOptionWithoutAlias(const char* arg)
         bool success = parse(valueStr, value);                     \
         if (success) {                                             \
             name_() = value;                                       \
+            correctOptions();                                      \
             recomputeDependentOptions();                           \
             return true;                                           \
         }                                                          \
index 48f6e62..5421a83 100644 (file)
@@ -510,6 +510,7 @@ constexpr bool enableWebAssemblyStreamingApi = false;
     v(bool, traceBaselineJITExecution, false, Normal, nullptr) \
     v(optionString, diskCachePath, nullptr, Restricted, "") \
     v(bool, forceDiskCache, false, Restricted, "") \
+    v(unsigned, thresholdForGlobalLexicalBindingEpoch, UINT_MAX, Normal, "Threshold for global lexical binding epoch. If the epoch reaches to this value, CodeBlock metadata for scope operations will be revised globally. It needs to be greater than 1.") \
 
 
 enum OptionEquivalence {
index 1c21967..1a3f092 100644 (file)
@@ -107,7 +107,6 @@ JSObject* ProgramExecutable::initializeGlobalProperties(VM& vm, CallFrame* callF
     JSGlobalLexicalEnvironment* globalLexicalEnvironment = globalObject->globalLexicalEnvironment();
     const VariableEnvironment& variableDeclarations = unlinkedCodeBlock->variableDeclarations();
     const VariableEnvironment& lexicalDeclarations = unlinkedCodeBlock->lexicalDeclarations();
-    IdentifierSet shadowedProperties;
     // The ES6 spec says that no vars/global properties/let/const can be duplicated in the global scope.
     // This carried out section 15.1.8 of the ES6 spec: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-globaldeclarationinstantiation
     {
@@ -131,11 +130,9 @@ JSObject* ProgramExecutable::initializeGlobalProperties(VM& vm, CallFrame* callF
                 // Lexical bindings can shadow global properties if the given property's attribute is configurable.
                 // https://tc39.github.io/ecma262/#sec-globaldeclarationinstantiation step 5-c, `hasRestrictedGlobal` becomes false
                 // However we may emit GlobalProperty look up in bytecodes already and it may cache the value for the global scope.
-                // To make it invalid, we iterate all the CodeBlocks and rewrite the instruction to convert GlobalProperty to GlobalLexicalVar.
-                // 1. In LLInt, we always check metadata's resolveType. So rewritten instruction just works.
-                // 2. In Baseline JIT, we check metadata's resolveType in GlobalProperty case so that we can notice once it is changed.
+                // To make it invalid,
+                // 1. In LLInt and Baseline, we bump the global lexical binding epoch and it works.
                 // 3. In DFG and FTL, we watch the watchpoint and jettison once it is fired.
-                shadowedProperties.add(entry.key.get());
                 break;
             case GlobalPropertyLookUpStatus::NotFound:
                 break;
@@ -206,12 +203,18 @@ JSObject* ProgramExecutable::initializeGlobalProperties(VM& vm, CallFrame* callF
             RELEASE_ASSERT(offsetForAssert == offset);
         }
     }
-
-    if (!shadowedProperties.isEmpty()) {
-        globalObject->notifyLexicalBindingShadowing(vm, WTFMove(shadowedProperties));
-        throwScope.assertNoException();
+    if (lexicalDeclarations.size()) {
+#if ENABLE(DFG_JIT)
+        for (auto& entry : lexicalDeclarations) {
+            // If WatchpointSet exists, just fire it. Since DFG WatchpointSet addition is also done on the main thread, we can sync them.
+            // So that we do not create WatchpointSet here. DFG will create if necessary on the main thread.
+            // And it will only create not-invalidated watchpoint set if the global lexical environment binding doesn't exist, which is why this code works.
+            if (auto* watchpointSet = globalObject->getReferencedPropertyWatchpointSet(entry.key.get()))
+                watchpointSet->fireAll(vm, "Lexical binding shadows an existing global property");
+        }
+#endif
+        globalObject->bumpGlobalLexicalBindingEpoch(vm);
     }
-
     return nullptr;
 }