diff --git a/JSTests/stress/arrow-function-captured-arguments-aliased.js b/JSTests/stress/arrow-function-captured-arguments-aliased.js new file mode 100644 index 0000000000000..e8c4c93276b18 --- /dev/null +++ b/JSTests/stress/arrow-function-captured-arguments-aliased.js @@ -0,0 +1,66 @@ +function createOptAll(count) { + count = 0; + + return () => { + arguments[0]++; + + return count; + }; +} + +function createOpt500(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 500) + return arguments[0]; + + return count; + }; +} + +function createOpt2000(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 2000) + return arguments[0]; + + return count; + }; +} + +function createOpt5000(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 5000) + return arguments[0]; + + return count; + }; +} + +function main() { + const testCount = 10000; + const createOptFuncs = [createOptAll, createOpt500, createOpt2000, createOpt5000]; + for (createOptFunc of createOptFuncs) { + const opt = createOptFunc(0); + for (let i = 0; i < testCount; i++) { + opt(); + } + + const expectedResult = testCount+1; + let actualResult = opt(); + if (actualResult != expectedResult) + print("Expected " + expectedResult + ", got " + actualResult); + } +} + +main(); diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index 0513a514b7e2e..57d3bc032fc8a 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -601,7 +601,12 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink ConcurrentJSLocker locker(symbolTable->m_lock); auto iter = symbolTable->find(locker, ident.impl()); ASSERT(iter != symbolTable->end(locker)); - iter->value.prepareToWatch(); + if (bytecode.m_getPutInfo.initializationMode() == InitializationMode::ScopedArgumentInitialization) { + ASSERT(bytecode.m_value.isArgument()); + unsigned argumentIndex = bytecode.m_value.toArgument() - 1; + symbolTable->prepareToWatchScopedArgument(iter->value, argumentIndex); + } else + iter->value.prepareToWatch(); metadata.m_watchpointSet = iter->value.watchpointSet(); } else metadata.m_watchpointSet = nullptr; diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp index a895c1770db51..51124e90201f0 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp @@ -539,17 +539,23 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke m_outOfMemoryDuringConstruction = true; return; } + + unsigned varOrAnonymous = UINT_MAX; + if (UniquedStringImpl* name = visibleNameForParameter(parameters.at(i).first)) { VarOffset varOffset(offset); SymbolTableEntry entry(varOffset); - // Stores to these variables via the ScopedArguments object will not do - // notifyWrite(), since that would be cumbersome. Also, watching formal - // parameters when "arguments" is in play is unlikely to be super profitable. - // So, we just disable it. - entry.disableWatching(m_vm); functionSymbolTable->set(NoLockingNecessary, name, entry); + +IGNORE_GCC_WARNINGS_BEGIN("dangling-reference") + const Identifier& ident = + static_cast(parameters.at(i).first)->boundProperty(); +IGNORE_GCC_WARNINGS_END + + varOrAnonymous = addConstant(ident); } - OpPutToScope::emit(this, m_lexicalEnvironmentRegister, UINT_MAX, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::NotInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset()); + + OpPutToScope::emit(this, m_lexicalEnvironmentRegister, varOrAnonymous, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::ScopedArgumentInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset()); } // This creates a scoped arguments object and copies the overflow arguments into the diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 26b0f9f643186..b1275d0d5e5ae 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -8103,6 +8103,9 @@ void ByteCodeParser::parseBlock(unsigned limit) // Must happen after the store. See comment for GetGlobalVar. addToGraph(NotifyWrite, OpInfo(watchpoints)); } + + // Keep scope alive until after put. + addToGraph(Phantom, scopeNode); break; } diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm index 70401e45be6d0..0c56cde3dccc6 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm @@ -2821,7 +2821,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0 andi InitializationModeMask, t0 rshifti InitializationModeShift, t0 - bineq t0, NotInitialization, .noNeedForTDZCheck + bilt t0, NotInitialization, .noNeedForTDZCheck loadp OpPutToScope::Metadata::m_operand[t5], t0 loadi TagOffset[t0], t0 bieq t0, EmptyValueTag, .pDynamic diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm index 52ef9a04efe14..d1027ed199932 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm @@ -3007,7 +3007,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0 andi InitializationModeMask, t0 rshifti InitializationModeShift, t0 - bineq t0, NotInitialization, .noNeedForTDZCheck + bilt t0, NotInitialization, .noNeedForTDZCheck loadp OpPutToScope::Metadata::m_operand[t5], t0 loadq [t0], t0 bqeq t0, ValueEmpty, .pDynamic diff --git a/Source/JavaScriptCore/runtime/GetPutInfo.h b/Source/JavaScriptCore/runtime/GetPutInfo.h index e12ba4bb7c986..ae65d57188cc6 100644 --- a/Source/JavaScriptCore/runtime/GetPutInfo.h +++ b/Source/JavaScriptCore/runtime/GetPutInfo.h @@ -83,7 +83,8 @@ enum ResolveType : unsigned { enum class InitializationMode : unsigned { Initialization, // "let x = 20;" ConstInitialization, // "const x = 20;" - NotInitialization // "x = 20;" + NotInitialization, // "x = 20;" + ScopedArgumentInitialization // Assign to scoped argument, which also has NotInitialization semantics. }; ALWAYS_INLINE const char* resolveModeName(ResolveMode resolveMode) @@ -120,7 +121,8 @@ ALWAYS_INLINE const char* initializationModeName(InitializationMode initializati static const char* const names[] = { "Initialization", "ConstInitialization", - "NotInitialization" + "NotInitialization", + "ScopedArgumentInitialization" }; return names[static_cast(initializationMode)]; } @@ -132,6 +134,7 @@ ALWAYS_INLINE bool isInitialization(InitializationMode initializationMode) case InitializationMode::ConstInitialization: return true; case InitializationMode::NotInitialization: + case InitializationMode::ScopedArgumentInitialization: return false; } ASSERT_NOT_REACHED(); diff --git a/Source/JavaScriptCore/runtime/ResourceExhaustion.h b/Source/JavaScriptCore/runtime/ResourceExhaustion.h index e4043506b5433..e4e1af664a529 100644 --- a/Source/JavaScriptCore/runtime/ResourceExhaustion.h +++ b/Source/JavaScriptCore/runtime/ResourceExhaustion.h @@ -29,6 +29,7 @@ namespace JSC { enum ResourceExhaustionCode { StructureIDExhaustion = 30, + MemoryExhaustion = 31, }; #define RELEASE_ASSERT_RESOURCE_AVAILABLE(assertion, resourceExhaustionCode, failureMessage) do { \ diff --git a/Source/JavaScriptCore/runtime/ScopedArguments.cpp b/Source/JavaScriptCore/runtime/ScopedArguments.cpp index d3131e6b93f17..71abecba5fa20 100644 --- a/Source/JavaScriptCore/runtime/ScopedArguments.cpp +++ b/Source/JavaScriptCore/runtime/ScopedArguments.cpp @@ -155,6 +155,7 @@ void ScopedArguments::unmapArgument(JSGlobalObject* globalObject, uint32_t i) return; } m_table.set(vm, this, maybeCloned); + m_table->clearWatchpointSet(i); } else storage()[i - namedLength].clear(); } diff --git a/Source/JavaScriptCore/runtime/ScopedArguments.h b/Source/JavaScriptCore/runtime/ScopedArguments.h index 385fdb67b61b1..a2f1df7e0164f 100644 --- a/Source/JavaScriptCore/runtime/ScopedArguments.h +++ b/Source/JavaScriptCore/runtime/ScopedArguments.h @@ -27,6 +27,7 @@ #include "GenericArguments.h" #include "JSLexicalEnvironment.h" +#include "Watchpoint.h" namespace JSC { @@ -111,9 +112,17 @@ class ScopedArguments final : public GenericArguments { { ASSERT_WITH_SECURITY_IMPLICATION(isMappedArgument(i)); unsigned namedLength = m_table->length(); - if (i < namedLength) + if (i < namedLength) { m_scope->variableAt(m_table->get(i)).set(vm, m_scope.get(), value); - else + + auto* watchpointSet = m_table->getWatchpointSet(i); + if (watchpointSet) { +#if ASSERT_ENABLED + ASSERT(m_scope->symbolTable()->hasScopedWatchpointSet(watchpointSet)); +#endif + watchpointSet->touch(vm, "Write to ScopedArgument."); + } + } else storage()[i - namedLength].set(vm, this, value); } diff --git a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp index fae2508d5d2d2..688b7d50b4efd 100644 --- a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp +++ b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp @@ -70,6 +70,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryCreate(VM& vm, uint32_t length) result->m_arguments = ArgumentsPtr::tryCreate(length); if (UNLIKELY(!result->m_arguments)) return nullptr; + result->m_watchpointSets.fill(nullptr, length); return result; } @@ -80,6 +81,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryClone(VM& vm) return nullptr; for (unsigned i = m_length; i--;) result->at(i) = this->at(i); + result->m_watchpointSets = this->m_watchpointSets; return result; } @@ -93,14 +95,18 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySetLength(VM& vm, uint32_t newLen newArguments.at(i, newLength) = this->at(i); m_length = newLength; m_arguments = WTFMove(newArguments); + m_watchpointSets.resize(newLength); return this; } ScopedArgumentsTable* result = tryCreate(vm, newLength); if (UNLIKELY(!result)) return nullptr; - for (unsigned i = std::min(m_length, newLength); i--;) + m_watchpointSets.resize(newLength); + for (unsigned i = std::min(m_length, newLength); i--;) { result->at(i) = this->at(i); + result->m_watchpointSets[i] = this->m_watchpointSets[i]; + } return result; } @@ -119,6 +125,15 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySet(VM& vm, uint32_t i, ScopeOffs return result; } +void ScopedArgumentsTable::trySetWatchpointSet(uint32_t i, WatchpointSet* watchpoints) +{ + ASSERT(watchpoints); + if (i >= m_watchpointSets.size()) + return; + + m_watchpointSets[i] = watchpoints; +} + Structure* ScopedArgumentsTable::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); diff --git a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h index a1f45a4d689dc..6d42c5da771c8 100644 --- a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h +++ b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h @@ -33,6 +33,8 @@ namespace JSC { +class WatchpointSet; + // This class's only job is to hold onto the list of ScopeOffsets for each argument that a // function has. Most of the time, the BytecodeGenerator will create one of these and it will // never be modified subsequently. There is a rare case where a ScopedArguments object is created @@ -67,6 +69,7 @@ class ScopedArgumentsTable final : public JSCell { ScopedArgumentsTable* trySetLength(VM&, uint32_t newLength); ScopeOffset get(uint32_t i) const { return at(i); } + WatchpointSet* getWatchpointSet(uint32_t i) const { return m_watchpointSets.at(i); } void lock() { @@ -74,7 +77,9 @@ class ScopedArgumentsTable final : public JSCell { } ScopedArgumentsTable* trySet(VM&, uint32_t index, ScopeOffset); - + void trySetWatchpointSet(uint32_t index, WatchpointSet* watchpoints); + void clearWatchpointSet(uint32_t index) { m_watchpointSets[index] = nullptr; } + DECLARE_INFO; static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); @@ -96,6 +101,7 @@ class ScopedArgumentsTable final : public JSCell { uint32_t m_length; bool m_locked; // Being locked means that there are multiple references to this object and none of them expect to see the others' modifications. This means that modifications need to make a copy first. ArgumentsPtr m_arguments; + Vector m_watchpointSets; }; } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/SymbolTable.cpp b/Source/JavaScriptCore/runtime/SymbolTable.cpp index b8bfc72f2780b..35d7603737f40 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.cpp +++ b/Source/JavaScriptCore/runtime/SymbolTable.cpp @@ -31,6 +31,7 @@ #include "CodeBlock.h" #include "JSCJSValueInlines.h" +#include "ResourceExhaustion.h" #include "TypeProfiler.h" #include @@ -150,19 +151,44 @@ SymbolTable* SymbolTable::cloneScopePart(VM& vm) result->m_nestedLexicalScope = m_nestedLexicalScope; result->m_scopeType = m_scopeType; + HashMap varOffsetToArgIndexMap; + + if (this->arguments()) { + // Copy the arguments, but not the WatchpointSets. We create new WatchpointSets as appropriate when we create the SymbolTableEntry + // copies below and propogate the new watchpointSets to the new ScopedArgumentsTable. + auto length = this->arguments()->length(); + ScopedArgumentsTable* arguments = ScopedArgumentsTable::tryCreate(vm, length); + RELEASE_ASSERT_RESOURCE_AVAILABLE(arguments, MemoryExhaustion, "Crash intentionally because memory is exhausted."); + + for (uint32_t index = 0; index < length; ++index) { + ScopeOffset offset = this->arguments()->get(index); + + arguments->trySet(vm, index, offset); + if (this->arguments()->getWatchpointSet(index)) + varOffsetToArgIndexMap.set(VarOffset(offset), index); + } + + result->m_arguments.set(vm, result, arguments); + } + + bool hasScopedArgumentWatchpoints = !varOffsetToArgIndexMap.isEmpty(); + for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) { if (!iter->value.varOffset().isScope()) continue; - result->m_map.add( - iter->key, - SymbolTableEntry(iter->value.varOffset(), iter->value.getAttributes())); + SymbolTableEntry entry(iter->value.varOffset(), iter->value.getAttributes()); + + if (hasScopedArgumentWatchpoints) { + auto findIter = varOffsetToArgIndexMap.find(iter->value.varOffset()); + if (findIter != varOffsetToArgIndexMap.end()) + result->prepareToWatchScopedArgument(entry, findIter->value); + } + + result->m_map.add(iter->key, WTFMove(entry)); } - + result->m_maxScopeOffset = m_maxScopeOffset; - - if (ScopedArgumentsTable* arguments = this->arguments()) - result->m_arguments.set(vm, result, arguments); - + if (m_rareData) { result->m_rareData = makeUnique(); @@ -285,6 +311,22 @@ RefPtr SymbolTable::globalTypeSetForVariable(const ConcurrentJSLocker& return iter->value; } +#if ASSERT_ENABLED +bool SymbolTable::hasScopedWatchpointSet(WatchpointSet* watchpointSet) +{ + for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) { + if (!iter->value.varOffset().isScope()) + continue; + + auto* entryWatchpointSet = iter->value.watchpointSet(); + if (entryWatchpointSet && entryWatchpointSet == watchpointSet) + return true; + } + + return false; +} +#endif + void SymbolTable::dump(PrintStream& out) const { ConcurrentJSLocker locker(m_lock); diff --git a/Source/JavaScriptCore/runtime/SymbolTable.h b/Source/JavaScriptCore/runtime/SymbolTable.h index cf0ff330496e4..0d8ebbd21b891 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.h +++ b/Source/JavaScriptCore/runtime/SymbolTable.h @@ -668,6 +668,7 @@ class SymbolTable final : public JSCell { return false; m_arguments.set(vm, this, table); } + return true; } @@ -687,6 +688,16 @@ class SymbolTable final : public JSCell { return true; } + void prepareToWatchScopedArgument(SymbolTableEntry& entry, uint32_t i) + { + entry.prepareToWatch(); + if (!m_arguments) + return; + + WatchpointSet* watchpoints = entry.watchpointSet(); + m_arguments->trySetWatchpointSet(i, watchpoints); + } + ScopedArgumentsTable* arguments() const { if (!m_arguments) @@ -737,6 +748,10 @@ class SymbolTable final : public JSCell { DECLARE_EXPORT_INFO; +#if ASSERT_ENABLED + bool hasScopedWatchpointSet(WatchpointSet*); +#endif + void finalizeUnconditionally(VM&); void dump(PrintStream&) const; diff --git a/Source/JavaScriptCore/runtime/VM.cpp b/Source/JavaScriptCore/runtime/VM.cpp index f1f5679eb00fc..92f618f9682b2 100644 --- a/Source/JavaScriptCore/runtime/VM.cpp +++ b/Source/JavaScriptCore/runtime/VM.cpp @@ -92,6 +92,7 @@ #include "PropertyTable.h" #include "RandomizingFuzzerAgent.h" #include "RegExpCache.h" +#include "ResourceExhaustion.h" #include "SamplingProfiler.h" #include "ScopedArguments.h" #include "ShadowChicken.h" @@ -299,7 +300,7 @@ VM::VM(VMType vmType, HeapType heapType, WTF::RunLoop* runLoop, bool* success) if (success) *success = false; else - RELEASE_ASSERT(bigInt); + RELEASE_ASSERT_RESOURCE_AVAILABLE(bigInt, MemoryExhaustion, "Crash intentionally because memory is exhausted."); } }