/ runtime / CodeCache.cpp
CodeCache.cpp
  1  /*
  2   * Copyright (C) 2012-2019 Apple Inc. All Rights Reserved.
  3   *
  4   * Redistribution and use in source and binary forms, with or without
  5   * modification, are permitted provided that the following conditions
  6   * are met:
  7   * 1. Redistributions of source code must retain the above copyright
  8   *    notice, this list of conditions and the following disclaimer.
  9   * 2. Redistributions in binary form must reproduce the above copyright
 10   *    notice, this list of conditions and the following disclaimer in the
 11   *    documentation and/or other materials provided with the distribution.
 12   *
 13   * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 14   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 15   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 16   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 17   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 18   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 19   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 20   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 21   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 22   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 23   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 24   */
 25  
 26  #include "config.h"
 27  #include "CodeCache.h"
 28  
 29  #include "BytecodeGenerator.h"
 30  #include "IndirectEvalExecutable.h"
 31  
 32  namespace JSC {
 33  
 34  void CodeCacheMap::pruneSlowCase()
 35  {
 36      m_minCapacity = std::max(m_size - m_sizeAtLastPrune, static_cast<int64_t>(0));
 37      m_sizeAtLastPrune = m_size;
 38      m_timeAtLastPrune = MonotonicTime::now();
 39  
 40      if (m_capacity < m_minCapacity)
 41          m_capacity = m_minCapacity;
 42  
 43      while (m_size > m_capacity || !canPruneQuickly()) {
 44          MapType::iterator it = m_map.begin();
 45  
 46          writeCodeBlock(it->value.cell->vm(), it->key, it->value);
 47  
 48          m_size -= it->key.length();
 49          m_map.remove(it);
 50      }
 51  }
 52  
 53  static void generateUnlinkedCodeBlockForFunctions(VM& vm, UnlinkedCodeBlock* unlinkedCodeBlock, const SourceCode& parentSource, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error)
 54  {
 55      auto generate = [&](UnlinkedFunctionExecutable* unlinkedExecutable, CodeSpecializationKind constructorKind) {
 56          if (constructorKind == CodeForConstruct && SourceParseModeSet(SourceParseMode::AsyncArrowFunctionMode, SourceParseMode::AsyncMethodMode, SourceParseMode::AsyncFunctionMode).contains(unlinkedExecutable->parseMode()))
 57              return;
 58  
 59          SourceCode source = unlinkedExecutable->linkedSourceCode(parentSource);
 60          UnlinkedFunctionCodeBlock* unlinkedFunctionCodeBlock = unlinkedExecutable->unlinkedCodeBlockFor(vm, source, constructorKind, codeGenerationMode, error, unlinkedExecutable->parseMode());
 61          if (unlinkedFunctionCodeBlock)
 62              generateUnlinkedCodeBlockForFunctions(vm, unlinkedFunctionCodeBlock, source, codeGenerationMode, error);
 63      };
 64  
 65      // FIXME: We should also generate CodeBlocks for CodeForConstruct
 66      // https://bugs.webkit.org/show_bug.cgi?id=193823
 67      for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionDecls(); i++)
 68          generate(unlinkedCodeBlock->functionDecl(i), CodeForCall);
 69      for (unsigned i = 0; i < unlinkedCodeBlock->numberOfFunctionExprs(); i++)
 70          generate(unlinkedCodeBlock->functionExpr(i), CodeForCall);
 71  }
 72  
 73  template <class UnlinkedCodeBlockType, class ExecutableType = ScriptExecutable>
 74  UnlinkedCodeBlockType* generateUnlinkedCodeBlockImpl(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, DerivedContextType derivedContextType, bool isArrowFunctionContext, const TDZEnvironment* variablesUnderTDZ = nullptr, const VariableEnvironment* parentScopePrivateNames = nullptr, ExecutableType* executable = nullptr)
 75  {
 76      typedef typename CacheTypes<UnlinkedCodeBlockType>::RootNode RootNode;
 77      bool isInsideOrdinaryFunction = executable && executable->isInsideOrdinaryFunction();
 78  
 79      std::unique_ptr<RootNode> rootNode = parse<RootNode>(
 80          vm, source, Identifier(), JSParserBuiltinMode::NotBuiltin, strictMode, scriptMode, CacheTypes<UnlinkedCodeBlockType>::parseMode, SuperBinding::NotNeeded, error, nullptr, ConstructorKind::None, derivedContextType, evalContextType, nullptr, parentScopePrivateNames, nullptr, isInsideOrdinaryFunction);
 81  
 82      if (!rootNode)
 83          return nullptr;
 84  
 85      unsigned lineCount = rootNode->lastLine() - rootNode->firstLine();
 86      unsigned startColumn = rootNode->startColumn() + 1;
 87      bool endColumnIsOnStartLine = !lineCount;
 88      unsigned unlinkedEndColumn = rootNode->endColumn();
 89      unsigned endColumn = unlinkedEndColumn + (endColumnIsOnStartLine ? startColumn : 1);
 90      unsigned arrowContextFeature = isArrowFunctionContext ? ArrowFunctionContextFeature : 0;
 91      if (executable)
 92          executable->recordParse(rootNode->features() | arrowContextFeature, rootNode->hasCapturedVariables(), rootNode->lastLine(), endColumn);
 93  
 94      bool usesEval = rootNode->features() & EvalFeature;
 95      ECMAMode ecmaMode = rootNode->features() & StrictModeFeature ? ECMAMode::strict() : ECMAMode::sloppy();
 96      NeedsClassFieldInitializer needsClassFieldInitializer = NeedsClassFieldInitializer::No;
 97      if constexpr (std::is_same_v<ExecutableType, DirectEvalExecutable>)
 98          needsClassFieldInitializer = executable->needsClassFieldInitializer();
 99      ExecutableInfo executableInfo(usesEval, false, false, ConstructorKind::None, scriptMode, SuperBinding::NotNeeded, CacheTypes<UnlinkedCodeBlockType>::parseMode, derivedContextType, needsClassFieldInitializer, isArrowFunctionContext, false, evalContextType);
100  
101      UnlinkedCodeBlockType* unlinkedCodeBlock = UnlinkedCodeBlockType::create(vm, executableInfo, codeGenerationMode);
102      unlinkedCodeBlock->recordParse(rootNode->features(), rootNode->hasCapturedVariables(), lineCount, unlinkedEndColumn);
103      if (!source.provider()->sourceURLDirective().isNull())
104          unlinkedCodeBlock->setSourceURLDirective(source.provider()->sourceURLDirective());
105      if (!source.provider()->sourceMappingURLDirective().isNull())
106          unlinkedCodeBlock->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective());
107  
108      RefPtr<TDZEnvironmentLink> parentVariablesUnderTDZ;
109      if (variablesUnderTDZ)
110          parentVariablesUnderTDZ = TDZEnvironmentLink::create(vm.m_compactVariableMap->get(*variablesUnderTDZ), nullptr);
111      error = BytecodeGenerator::generate(vm, rootNode.get(), source, unlinkedCodeBlock, codeGenerationMode, parentVariablesUnderTDZ, ecmaMode);
112  
113      if (error.isValid())
114          return nullptr;
115  
116      return unlinkedCodeBlock;
117  }
118  
119  template <class UnlinkedCodeBlockType, class ExecutableType>
120  UnlinkedCodeBlockType* generateUnlinkedCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ = nullptr, const VariableEnvironment* parentScopePrivateNames = nullptr)
121  {
122      return generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType, ExecutableType>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, executable->derivedContextType(), executable->isArrowFunctionContext(), variablesUnderTDZ, parentScopePrivateNames, executable);
123  }
124  
125  UnlinkedEvalCodeBlock* generateUnlinkedCodeBlockForDirectEval(VM& vm, DirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType, const TDZEnvironment* variablesUnderTDZ, const VariableEnvironment* parentScopePrivateNames)
126  {
127      return generateUnlinkedCodeBlock<UnlinkedEvalCodeBlock>(vm, executable, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, variablesUnderTDZ, parentScopePrivateNames);
128  }
129  
130  template <class UnlinkedCodeBlockType>
131  std::enable_if_t<!std::is_same<UnlinkedCodeBlockType, UnlinkedEvalCodeBlock>::value, UnlinkedCodeBlockType*>
132  recursivelyGenerateUnlinkedCodeBlock(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType)
133  {
134      bool isArrowFunctionContext = false;
135      UnlinkedCodeBlockType* unlinkedCodeBlock = generateUnlinkedCodeBlockImpl<UnlinkedCodeBlockType>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType, DerivedContextType::None, isArrowFunctionContext);
136      if (!unlinkedCodeBlock)
137          return nullptr;
138  
139      generateUnlinkedCodeBlockForFunctions(vm, unlinkedCodeBlock, source, codeGenerationMode, error);
140      return unlinkedCodeBlock;
141  }
142  
143  UnlinkedProgramCodeBlock* recursivelyGenerateUnlinkedCodeBlockForProgram(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType)
144  {
145      return recursivelyGenerateUnlinkedCodeBlock<UnlinkedProgramCodeBlock>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType);
146  }
147  
148  UnlinkedModuleProgramCodeBlock* recursivelyGenerateUnlinkedCodeBlockForModuleProgram(VM& vm, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType)
149  {
150      return recursivelyGenerateUnlinkedCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType);
151  }
152  
153  template <class UnlinkedCodeBlockType, class ExecutableType>
154  UnlinkedCodeBlockType* CodeCache::getUnlinkedGlobalCodeBlock(VM& vm, ExecutableType* executable, const SourceCode& source, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType)
155  {
156      DerivedContextType derivedContextType = executable->derivedContextType();
157      bool isArrowFunctionContext = executable->isArrowFunctionContext();
158      SourceCodeKey key(
159          source, String(), CacheTypes<UnlinkedCodeBlockType>::codeType, strictMode, scriptMode, 
160          derivedContextType, evalContextType, isArrowFunctionContext, codeGenerationMode,
161          WTF::nullopt);
162      UnlinkedCodeBlockType* unlinkedCodeBlock = m_sourceCode.findCacheAndUpdateAge<UnlinkedCodeBlockType>(vm, key);
163      if (unlinkedCodeBlock && Options::useCodeCache()) {
164          unsigned lineCount = unlinkedCodeBlock->lineCount();
165          unsigned startColumn = unlinkedCodeBlock->startColumn() + source.startColumn().oneBasedInt();
166          bool endColumnIsOnStartLine = !lineCount;
167          unsigned endColumn = unlinkedCodeBlock->endColumn() + (endColumnIsOnStartLine ? startColumn : 1);
168          executable->recordParse(unlinkedCodeBlock->codeFeatures(), unlinkedCodeBlock->hasCapturedVariables(), source.firstLine().oneBasedInt() + lineCount, endColumn);
169          if (unlinkedCodeBlock->sourceURLDirective())
170              source.provider()->setSourceURLDirective(unlinkedCodeBlock->sourceURLDirective());
171          if (unlinkedCodeBlock->sourceMappingURLDirective())
172              source.provider()->setSourceMappingURLDirective(unlinkedCodeBlock->sourceMappingURLDirective());
173          return unlinkedCodeBlock;
174      }
175  
176      unlinkedCodeBlock = generateUnlinkedCodeBlock<UnlinkedCodeBlockType, ExecutableType>(vm, executable, source, strictMode, scriptMode, codeGenerationMode, error, evalContextType);
177  
178      if (unlinkedCodeBlock && Options::useCodeCache()) {
179          m_sourceCode.addCache(key, SourceCodeValue(vm, unlinkedCodeBlock, m_sourceCode.age()));
180  
181          key.source().provider().cacheBytecode([&] {
182              return encodeCodeBlock(vm, key, unlinkedCodeBlock);
183          });
184      }
185  
186      return unlinkedCodeBlock;
187  }
188  
189  UnlinkedProgramCodeBlock* CodeCache::getUnlinkedProgramCodeBlock(VM& vm, ProgramExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error)
190  {
191      return getUnlinkedGlobalCodeBlock<UnlinkedProgramCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, EvalContextType::None);
192  }
193  
194  UnlinkedEvalCodeBlock* CodeCache::getUnlinkedEvalCodeBlock(VM& vm, IndirectEvalExecutable* executable, const SourceCode& source, JSParserStrictMode strictMode, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error, EvalContextType evalContextType)
195  {
196      return getUnlinkedGlobalCodeBlock<UnlinkedEvalCodeBlock>(vm, executable, source, strictMode, JSParserScriptMode::Classic, codeGenerationMode, error, evalContextType);
197  }
198  
199  UnlinkedModuleProgramCodeBlock* CodeCache::getUnlinkedModuleProgramCodeBlock(VM& vm, ModuleProgramExecutable* executable, const SourceCode& source, OptionSet<CodeGenerationMode> codeGenerationMode, ParserError& error)
200  {
201      return getUnlinkedGlobalCodeBlock<UnlinkedModuleProgramCodeBlock>(vm, executable, source, JSParserStrictMode::Strict, JSParserScriptMode::Module, codeGenerationMode, error, EvalContextType::None);
202  }
203  
204  UnlinkedFunctionExecutable* CodeCache::getUnlinkedGlobalFunctionExecutable(VM& vm, const Identifier& name, const SourceCode& source, OptionSet<CodeGenerationMode> codeGenerationMode, Optional<int> functionConstructorParametersEndPosition, ParserError& error)
205  {
206      bool isArrowFunctionContext = false;
207      SourceCodeKey key(
208          source, name.string(), SourceCodeType::FunctionType,
209          JSParserStrictMode::NotStrict,
210          JSParserScriptMode::Classic,
211          DerivedContextType::None,
212          EvalContextType::None,
213          isArrowFunctionContext,
214          codeGenerationMode,
215          functionConstructorParametersEndPosition);
216      UnlinkedFunctionExecutable* executable = m_sourceCode.findCacheAndUpdateAge<UnlinkedFunctionExecutable>(vm, key);
217      if (executable && Options::useCodeCache()) {
218          if (!executable->sourceURLDirective().isNull())
219              source.provider()->setSourceURLDirective(executable->sourceURLDirective());
220          if (!executable->sourceMappingURLDirective().isNull())
221              source.provider()->setSourceMappingURLDirective(executable->sourceMappingURLDirective());
222          return executable;
223      }
224  
225      JSTextPosition positionBeforeLastNewline;
226      std::unique_ptr<ProgramNode> program = parseFunctionForFunctionConstructor(vm, source, error, &positionBeforeLastNewline, functionConstructorParametersEndPosition);
227      if (!program) {
228          RELEASE_ASSERT(error.isValid());
229          return nullptr;
230      }
231  
232      // This function assumes an input string that would result in a single function declaration.
233      StatementNode* funcDecl = program->singleStatement();
234      if (UNLIKELY(!funcDecl)) {
235          JSToken token;
236          error = ParserError(ParserError::SyntaxError, ParserError::SyntaxErrorIrrecoverable, token, "Parser error", -1);
237          return nullptr;
238      }
239      ASSERT(funcDecl->isFuncDeclNode());
240  
241      FunctionMetadataNode* metadata = static_cast<FuncDeclNode*>(funcDecl)->metadata();
242      ASSERT(metadata);
243      if (!metadata)
244          return nullptr;
245      
246      metadata->overrideName(name);
247      metadata->setEndPosition(positionBeforeLastNewline);
248      // The Function constructor only has access to global variables, so no variables will be under TDZ unless they're
249      // in the global lexical environment, which we always TDZ check accesses from.
250      ConstructAbility constructAbility = constructAbilityForParseMode(metadata->parseMode());
251      UnlinkedFunctionExecutable* functionExecutable = UnlinkedFunctionExecutable::create(vm, source, metadata, UnlinkedNormalFunction, constructAbility, JSParserScriptMode::Classic, nullptr, DerivedContextType::None, NeedsClassFieldInitializer::No);
252  
253      if (!source.provider()->sourceURLDirective().isNull())
254          functionExecutable->setSourceURLDirective(source.provider()->sourceURLDirective());
255      if (!source.provider()->sourceMappingURLDirective().isNull())
256          functionExecutable->setSourceMappingURLDirective(source.provider()->sourceMappingURLDirective());
257  
258      if (Options::useCodeCache())
259          m_sourceCode.addCache(key, SourceCodeValue(vm, functionExecutable, m_sourceCode.age()));
260      return functionExecutable;
261  }
262  
263  void CodeCache::updateCache(const UnlinkedFunctionExecutable* executable, const SourceCode& parentSource, CodeSpecializationKind kind, const UnlinkedFunctionCodeBlock* codeBlock)
264  {
265      parentSource.provider()->updateCache(executable, parentSource, kind, codeBlock);
266  }
267  
268  void CodeCache::write(VM& vm)
269  {
270      for (auto& it : m_sourceCode)
271          writeCodeBlock(vm, it.key, it.value);
272  }
273  
274  void writeCodeBlock(VM& vm, const SourceCodeKey& key, const SourceCodeValue& value)
275  {
276      UnlinkedCodeBlock* codeBlock = jsDynamicCast<UnlinkedCodeBlock*>(vm, value.cell.get());
277      if (!codeBlock)
278          return;
279  
280      key.source().provider().commitCachedBytecode();
281  }
282  
283  static SourceCodeKey sourceCodeKeyForSerializedBytecode(VM&, const SourceCode& sourceCode, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, OptionSet<CodeGenerationMode> codeGenerationMode)
284  {
285      return SourceCodeKey(
286          sourceCode, String(), codeType, strictMode, scriptMode,
287          DerivedContextType::None, EvalContextType::None, false, codeGenerationMode,
288          WTF::nullopt);
289  }
290  
291  SourceCodeKey sourceCodeKeyForSerializedProgram(VM& vm, const SourceCode& sourceCode)
292  {
293      JSParserStrictMode strictMode = JSParserStrictMode::NotStrict;
294      JSParserScriptMode scriptMode = JSParserScriptMode::Classic;
295      return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ProgramType, strictMode, scriptMode, { });
296  }
297  
298  SourceCodeKey sourceCodeKeyForSerializedModule(VM& vm, const SourceCode& sourceCode)
299  {
300      JSParserStrictMode strictMode = JSParserStrictMode::Strict;
301      JSParserScriptMode scriptMode = JSParserScriptMode::Module;
302      return sourceCodeKeyForSerializedBytecode(vm, sourceCode, SourceCodeType::ModuleType, strictMode, scriptMode, { });
303  }
304  
305  RefPtr<CachedBytecode> serializeBytecode(VM& vm, UnlinkedCodeBlock* codeBlock, const SourceCode& source, SourceCodeType codeType, JSParserStrictMode strictMode, JSParserScriptMode scriptMode, FileSystem::PlatformFileHandle fd, BytecodeCacheError& error, OptionSet<CodeGenerationMode> codeGenerationMode)
306  {
307      return encodeCodeBlock(vm,
308          sourceCodeKeyForSerializedBytecode(vm, source, codeType, strictMode, scriptMode, codeGenerationMode), codeBlock, fd, error);
309  }
310  
311  }