Index: Source/JavaScriptCore/ChangeLog =================================================================== --- Source/JavaScriptCore/ChangeLog (revision 268822) +++ Source/JavaScriptCore/ChangeLog (working copy) @@ -1,3 +1,139 @@ +2020-10-23 Saam Barati + + Better cache our serialization of the outer TDZ environment when creating FunctionExecutables during bytecode generation + https://bugs.webkit.org/show_bug.cgi?id=199866 + + + Reviewed by NOBODY (OOPS!). + + This patch removes performance pathologies regarding programs with + many variables under TDZ (let/const). We had an algorithm for caching + the results of gathering all variables under TDZ, but that algorithm + wasn't nearly aggressive enough in its caching. This lead us to worst + case quadratic runtime, which could happens in practice for large functions. + + There are a few fixes here: + - Instead of flattening the entire TDZ stack, and caching that result, + we now cache each stack entry individually. So as you push/pop to the + TDZ environment stack, we no longer invalidate everything. Instead, we + will just need to cache the newly pushed entry. We also no longer invalidate + the cache for lifting a TDZ check. The compromise here is we may emit + more runtime TDZ checks for closure variables. This is better than N^2 + bytecode compile time perf, since a well predicted branch for a TDZ + check is essentially free. + - We no longer transform the CompactTDZEnvironment (formerly CompactVariableEnvironment) + from a Vector into a HashSet each time we generate code for an inner function. Instead, + CompactTDZEnvironment can be in two modes: compact and inflated. It starts life off in + compact mode (a vector), and will turn into an inflated mode if it's ever needed. Once + inflated, it'll stay this way until it's destructed. This improves our algorithm from being + O(EnvSize * NumFunctions) to O(EnvSize) at the cost of using more space in a HashTable versus a + Vector. In the future, we could consider just binary searching through this Vector, and never using + a hash table. + + * bytecode/UnlinkedFunctionExecutable.cpp: + (JSC::generateUnlinkedFunctionCodeBlock): + (JSC::UnlinkedFunctionExecutable::UnlinkedFunctionExecutable): + * bytecode/UnlinkedFunctionExecutable.h: + * bytecompiler/BytecodeGenerator.cpp: + (JSC::BytecodeGenerator::BytecodeGenerator): + (JSC::BytecodeGenerator::popLexicalScopeInternal): + (JSC::BytecodeGenerator::needsTDZCheck): + (JSC::BytecodeGenerator::liftTDZCheckIfPossible): + (JSC::BytecodeGenerator::pushTDZVariables): + (JSC::BytecodeGenerator::getVariablesUnderTDZ): + (JSC::BytecodeGenerator::preserveTDZStack): + (JSC::BytecodeGenerator::restoreTDZStack): + (JSC::BytecodeGenerator::emitNewInstanceFieldInitializerFunction): + * bytecompiler/BytecodeGenerator.h: + (JSC::BytecodeGenerator::generate): + (JSC::BytecodeGenerator::makeFunction): + * debugger/DebuggerCallFrame.cpp: + (JSC::DebuggerCallFrame::evaluateWithScopeExtension): + * interpreter/Interpreter.cpp: + (JSC::eval): + * parser/Parser.h: + (JSC::Parser::parse): + (JSC::parse): + * parser/VariableEnvironment.cpp: + (JSC::CompactTDZEnvironment::sortCompact): + (JSC::CompactTDZEnvironment::CompactTDZEnvironment): + (JSC::CompactTDZEnvironment::operator== const): + (JSC::CompactTDZEnvironment::toTDZEnvironmentSlow const): + (JSC::CompactTDZEnvironmentMap::get): + (JSC::CompactTDZEnvironmentMap::Handle::~Handle): + (JSC::CompactTDZEnvironmentMap::Handle::Handle): + (JSC::CompactVariableEnvironment::CompactVariableEnvironment): Deleted. + (JSC::CompactVariableEnvironment::operator== const): Deleted. + (JSC::CompactVariableEnvironment::toVariableEnvironment const): Deleted. + (JSC::CompactVariableMap::get): Deleted. + (JSC::CompactVariableMap::Handle::~Handle): Deleted. + (JSC::CompactVariableMap::Handle::Handle): Deleted. + * parser/VariableEnvironment.h: + (JSC::CompactTDZEnvironment::toTDZEnvironment const): + (JSC::CompactTDZEnvironmentKey::CompactTDZEnvironmentKey): + (JSC::CompactTDZEnvironmentKey::hash): + (JSC::CompactTDZEnvironmentKey::equal): + (JSC::CompactTDZEnvironmentKey::makeDeletedValue): + (JSC::CompactTDZEnvironmentKey::isHashTableDeletedValue const): + (JSC::CompactTDZEnvironmentKey::environment): + (WTF::HashTraits::emptyValue): + (WTF::HashTraits::isEmptyValue): + (WTF::HashTraits::constructDeletedValue): + (WTF::HashTraits::isDeletedValue): + (JSC::CompactTDZEnvironmentMap::Handle::environment const): + (JSC::CompactVariableEnvironment::hash const): Deleted. + (JSC::CompactVariableMapKey::CompactVariableMapKey): Deleted. + (JSC::CompactVariableMapKey::hash): Deleted. + (JSC::CompactVariableMapKey::equal): Deleted. + (JSC::CompactVariableMapKey::makeDeletedValue): Deleted. + (JSC::CompactVariableMapKey::isHashTableDeletedValue const): Deleted. + (JSC::CompactVariableMapKey::isHashTableEmptyValue const): Deleted. + (JSC::CompactVariableMapKey::environment): Deleted. + (WTF::HashTraits::emptyValue): Deleted. + (WTF::HashTraits::isEmptyValue): Deleted. + (WTF::HashTraits::constructDeletedValue): Deleted. + (WTF::HashTraits::isDeletedValue): Deleted. + (JSC::CompactVariableMap::Handle::Handle): Deleted. + (JSC::CompactVariableMap::Handle::operator=): Deleted. + (JSC::CompactVariableMap::Handle::operator bool const): Deleted. + (JSC::CompactVariableMap::Handle::environment const): Deleted. + (JSC::CompactVariableMap::Handle::swap): Deleted. + * runtime/CachedTypes.cpp: + (JSC::Decoder::handleForTDZEnvironment const): + (JSC::Decoder::setHandleForTDZEnvironment): + (JSC::CachedCompactTDZEnvironment::encode): + (JSC::CachedCompactTDZEnvironment::decode const): + (JSC::CachedCompactTDZEnvironmentMapHandle::encode): + (JSC::CachedCompactTDZEnvironmentMapHandle::decode const): + (JSC::CachedFunctionExecutableRareData::decode const): + (JSC::Decoder::handleForEnvironment const): Deleted. + (JSC::Decoder::setHandleForEnvironment): Deleted. + (JSC::CachedCompactVariableEnvironment::encode): Deleted. + (JSC::CachedCompactVariableEnvironment::decode const): Deleted. + (JSC::CachedCompactVariableMapHandle::encode): Deleted. + (JSC::CachedCompactVariableMapHandle::decode const): Deleted. + * runtime/CachedTypes.h: + * runtime/CodeCache.cpp: + (JSC::generateUnlinkedCodeBlockImpl): + (JSC::generateUnlinkedCodeBlock): + (JSC::generateUnlinkedCodeBlockForDirectEval): + (JSC::recursivelyGenerateUnlinkedCodeBlockForProgram): + (JSC::recursivelyGenerateUnlinkedCodeBlockForModuleProgram): + (JSC::CodeCache::getUnlinkedGlobalCodeBlock): + * runtime/CodeCache.h: + * runtime/Completion.cpp: + (JSC::generateProgramBytecode): + (JSC::generateModuleBytecode): + * runtime/DirectEvalExecutable.cpp: + (JSC::DirectEvalExecutable::create): + * runtime/DirectEvalExecutable.h: + * runtime/JSScope.cpp: + (JSC::JSScope::collectClosureVariablesUnderTDZ): + * runtime/JSScope.h: + * runtime/VM.cpp: + (JSC::VM::VM): + * runtime/VM.h: + 2020-10-21 Caitlin Potter [JSC] support op_get_private_name in DFG and FTL Index: Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp =================================================================== --- Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp (revision 268822) +++ Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp (working copy) @@ -72,9 +72,9 @@ static UnlinkedFunctionCodeBlock* genera UnlinkedFunctionCodeBlock* result = UnlinkedFunctionCodeBlock::create(vm, FunctionCode, ExecutableInfo(function->usesEval(), kind == CodeForConstruct, functionKind == UnlinkedBuiltinFunction, executable->constructorKind(), scriptMode, executable->superBinding(), parseMode, executable->derivedContextType(), executable->needsClassFieldInitializer(), false, isClassContext, EvalContextType::FunctionEvalContext), codeGenerationMode); - VariableEnvironment parentScopeTDZVariables = executable->parentScopeTDZVariables(); + auto parentScopeTDZVariables = executable->parentScopeTDZVariables(); ECMAMode ecmaMode = executable->isInStrictContext() ? ECMAMode::strict() : ECMAMode::sloppy(); - error = BytecodeGenerator::generate(vm, function.get(), source, result, codeGenerationMode, &parentScopeTDZVariables, ecmaMode); + error = BytecodeGenerator::generate(vm, function.get(), source, result, codeGenerationMode, parentScopeTDZVariables, ecmaMode); if (error.isValid()) return nullptr; @@ -82,7 +82,7 @@ static UnlinkedFunctionCodeBlock* genera return result; } -UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM& vm, Structure* structure, const SourceCode& parentSource, FunctionMetadataNode* node, UnlinkedFunctionKind kind, ConstructAbility constructAbility, JSParserScriptMode scriptMode, Optional parentScopeTDZVariables, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isBuiltinDefaultClassConstructor) +UnlinkedFunctionExecutable::UnlinkedFunctionExecutable(VM& vm, Structure* structure, const SourceCode& parentSource, FunctionMetadataNode* node, UnlinkedFunctionKind kind, ConstructAbility constructAbility, JSParserScriptMode scriptMode, Optional> parentScopeTDZVariables, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isBuiltinDefaultClassConstructor) : Base(vm, structure) , m_firstLineOffset(node->firstLine() - parentSource.firstLine().oneBasedInt()) , m_isInStrictContext(node->isInStrictContext()) Index: Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h =================================================================== --- Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h (revision 268822) +++ Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h (working copy) @@ -70,7 +70,7 @@ public: return &vm.unlinkedFunctionExecutableSpace.space; } - static UnlinkedFunctionExecutable* create(VM& vm, const SourceCode& source, FunctionMetadataNode* node, UnlinkedFunctionKind unlinkedFunctionKind, ConstructAbility constructAbility, JSParserScriptMode scriptMode, Optional parentScopeTDZVariables, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isBuiltinDefaultClassConstructor = false) + static UnlinkedFunctionExecutable* create(VM& vm, const SourceCode& source, FunctionMetadataNode* node, UnlinkedFunctionKind unlinkedFunctionKind, ConstructAbility constructAbility, JSParserScriptMode scriptMode, Optional> parentScopeTDZVariables, DerivedContextType derivedContextType, NeedsClassFieldInitializer needsClassFieldInitializer, bool isBuiltinDefaultClassConstructor = false) { UnlinkedFunctionExecutable* instance = new (NotNull, allocateCell(vm.heap)) UnlinkedFunctionExecutable(vm, vm.unlinkedFunctionExecutableStructure.get(), source, node, unlinkedFunctionKind, constructAbility, scriptMode, WTFMove(parentScopeTDZVariables), derivedContextType, needsClassFieldInitializer, isBuiltinDefaultClassConstructor); @@ -168,11 +168,11 @@ public: return !m_rareData->m_classSource.isNull(); } - VariableEnvironment parentScopeTDZVariables() const + Vector parentScopeTDZVariables() const { - if (!m_rareData || !m_rareData->m_parentScopeTDZVariables) - return VariableEnvironment(); - return m_rareData->m_parentScopeTDZVariables.environment().toVariableEnvironment(); + if (!m_rareData || m_rareData->m_parentScopeTDZVariables.isEmpty()) + return { }; + return m_rareData->m_parentScopeTDZVariables; } bool isArrowFunction() const { return isArrowFunctionParseMode(parseMode()); } @@ -208,7 +208,7 @@ public: SourceCode m_classSource; String m_sourceURLDirective; String m_sourceMappingURLDirective; - CompactVariableMap::Handle m_parentScopeTDZVariables; + Vector m_parentScopeTDZVariables; Vector m_instanceFieldLocations; }; @@ -229,7 +229,7 @@ public: } private: - UnlinkedFunctionExecutable(VM&, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, JSParserScriptMode, Optional, JSC::DerivedContextType, JSC::NeedsClassFieldInitializer, bool isBuiltinDefaultClassConstructor); + UnlinkedFunctionExecutable(VM&, Structure*, const SourceCode&, FunctionMetadataNode*, UnlinkedFunctionKind, ConstructAbility, JSParserScriptMode, Optional>, JSC::DerivedContextType, JSC::NeedsClassFieldInitializer, bool isBuiltinDefaultClassConstructor); UnlinkedFunctionExecutable(Decoder&, const CachedFunctionExecutable&); static void visitChildren(JSCell*, SlotVisitor&); Index: Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp =================================================================== --- Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp (revision 268822) +++ Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp (working copy) @@ -48,6 +48,7 @@ #include "Options.h" #include "PrivateFieldPutKind.h" #include "StrongInlines.h" +#include "SuperSampler.h" #include "UnlinkedCodeBlock.h" #include "UnlinkedEvalCodeBlock.h" #include "UnlinkedFunctionCodeBlock.h" @@ -288,7 +289,7 @@ ParserError BytecodeGenerator::generate( return ParserError(ParserError::ErrorNone); } -BytecodeGenerator::BytecodeGenerator(VM& vm, ProgramNode* programNode, UnlinkedProgramCodeBlock* codeBlock, OptionSet codeGenerationMode, const VariableEnvironment* parentScopeTDZVariables, ECMAMode ecmaMode) +BytecodeGenerator::BytecodeGenerator(VM& vm, ProgramNode* programNode, UnlinkedProgramCodeBlock* codeBlock, OptionSet codeGenerationMode, const CachedTDZStack& parentScopeTDZVariables, ECMAMode ecmaMode) : BytecodeGeneratorBase(makeUnique(vm, codeBlock), CodeBlock::llintBaselineCalleeSaveSpaceAsVirtualRegisters()) , m_codeGenerationMode(codeGenerationMode) , m_scopeNode(programNode) @@ -300,11 +301,10 @@ BytecodeGenerator::BytecodeGenerator(VM& , m_isBuiltinFunction(false) , m_usesNonStrictEval(false) , m_inTailPosition(false) - , m_hasCachedVariablesUnderTDZ(false) , m_needsToUpdateArrowFunctionContext(programNode->usesArrowFunction() || programNode->usesEval()) , m_ecmaMode(ecmaMode) { - ASSERT_UNUSED(parentScopeTDZVariables, !parentScopeTDZVariables->size()); + ASSERT_UNUSED(parentScopeTDZVariables, !parentScopeTDZVariables.size()); m_codeBlock->setNumParameters(1); // Allocate space for "this" @@ -336,7 +336,7 @@ BytecodeGenerator::BytecodeGenerator(VM& } } -BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, UnlinkedFunctionCodeBlock* codeBlock, OptionSet codeGenerationMode, const VariableEnvironment* parentScopeTDZVariables, ECMAMode ecmaMode) +BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, UnlinkedFunctionCodeBlock* codeBlock, OptionSet codeGenerationMode, const CachedTDZStack& parentScopeTDZVariables, ECMAMode ecmaMode) : BytecodeGeneratorBase(makeUnique(vm, codeBlock), CodeBlock::llintBaselineCalleeSaveSpaceAsVirtualRegisters()) , m_codeGenerationMode(codeGenerationMode) , m_scopeNode(functionNode) @@ -355,7 +355,6 @@ BytecodeGenerator::BytecodeGenerator(VM& // // Note that we intentionally enable tail call for naked constructors since it does not have special code for "return". , m_inTailPosition(Options::useTailCalls() && !isConstructor() && constructorKind() == ConstructorKind::None && ecmaMode.isStrict()) - , m_hasCachedVariablesUnderTDZ(false) , m_needsToUpdateArrowFunctionContext(functionNode->usesArrowFunction() || functionNode->usesEval()) , m_ecmaMode(ecmaMode) , m_derivedContextType(codeBlock->derivedContextType()) @@ -364,6 +363,9 @@ BytecodeGenerator::BytecodeGenerator(VM& functionSymbolTable->setUsesNonStrictEval(m_usesNonStrictEval); int symbolTableConstantIndex = 0; + m_parentScopeTDZVariables = parentScopeTDZVariables; + m_cachedVariablesUnderTDZ = m_parentScopeTDZVariables; + FunctionParameters& parameters = *functionNode->parameters(); // http://www.ecma-international.org/ecma-262/6.0/index.html#sec-functiondeclarationinstantiation // This implements IsSimpleParameterList in the Ecma 2015 spec. @@ -770,10 +772,6 @@ BytecodeGenerator::BytecodeGenerator(VM& emitPutDerivedConstructorToArrowFunctionContextScope(); } - // All "addVar()"s needs to happen before "initializeDefaultParameterValuesAndSetupFunctionScopeStack()" is called - // because a function's default parameter ExpressionNodes will use temporary registers. - pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize, TDZRequirement::UnderTDZ); - Ref