Bug 1529957 - Baldr: create lazy stubs even more lazily (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Tue, 26 Mar 2019 20:09:43 -0500
changeset 466811 ace72ffa3103e0c937c995b08f9127960d4ed183
parent 466810 5de97868cb80ae505bf52312dc47590e221d64bb
child 466812 babe08d04201153f26d7b1f0fa7400bcc7f1790c
push id35781
push useropoprus@mozilla.com
push dateFri, 29 Mar 2019 21:56:26 +0000
treeherdermozilla-central@4526b65c502e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1529957
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1529957 - Baldr: create lazy stubs even more lazily (r=bbouvier) Differential Revision: https://phabricator.services.mozilla.com/D25010
js/src/vm/JSFunction.h
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmJS.cpp
--- a/js/src/vm/JSFunction.h
+++ b/js/src/vm/JSFunction.h
@@ -728,17 +728,17 @@ class JSFunction : public js::NativeObje
     u.native.extra.wasmFuncIndex_ = funcIndex;
   }
   uint32_t wasmFuncIndex() const {
     MOZ_ASSERT(isWasm() || isAsmJSNative());
     MOZ_ASSERT(!isWasmWithJitEntry());
     return u.native.extra.wasmFuncIndex_;
   }
   void setWasmJitEntry(void** entry) {
-    MOZ_ASSERT(entry);
+    MOZ_ASSERT(*entry);
     MOZ_ASSERT(isWasm());
     MOZ_ASSERT(!isWasmWithJitEntry());
     flags_ |= WASM_JIT_ENTRY;
     u.native.extra.wasmJitEntry_ = entry;
     MOZ_ASSERT(isWasmWithJitEntry());
   }
   void** wasmJitEntry() const {
     MOZ_ASSERT(isWasmWithJitEntry());
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -827,18 +827,20 @@ void LazyStubTier::setJitEntries(const M
 bool LazyStubTier::hasStub(uint32_t funcIndex) const {
   size_t match;
   return BinarySearch(ProjectLazyFuncIndex(exports_), 0, exports_.length(),
                       funcIndex, &match);
 }
 
 void* LazyStubTier::lookupInterpEntry(uint32_t funcIndex) const {
   size_t match;
-  MOZ_ALWAYS_TRUE(BinarySearch(ProjectLazyFuncIndex(exports_), 0,
-                               exports_.length(), funcIndex, &match));
+  if (!BinarySearch(ProjectLazyFuncIndex(exports_), 0, exports_.length(),
+                    funcIndex, &match)) {
+    return nullptr;
+  }
   const LazyFuncExport& fe = exports_[match];
   const LazyStubSegment& stub = *stubSegments_[fe.lazyStubSegmentIndex];
   return stub.base() + stub.codeRanges()[fe.funcCodeRangeIndex].begin();
 }
 
 void LazyStubTier::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
                                  size_t* data) const {
   *data += sizeof(this);
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -231,16 +231,21 @@ class FuncExport {
   const FuncType& funcType() const { return funcType_; }
   uint32_t funcIndex() const { return pod.funcIndex_; }
   uint32_t eagerInterpEntryOffset() const {
     MOZ_ASSERT(pod.eagerInterpEntryOffset_ != UINT32_MAX);
     MOZ_ASSERT(hasEagerStubs());
     return pod.eagerInterpEntryOffset_;
   }
 
+  bool canHaveJitEntry() const {
+    return !funcType_.temporarilyUnsupportedAnyRef() &&
+           JitOptions.enableWasmJitEntry;
+  }
+
   bool clone(const FuncExport& src) {
     mozilla::PodAssign(&pod, &src.pod);
     return funcType_.clone(src.funcType_);
   }
 
   WASM_DECLARE_SERIALIZABLE(FuncExport)
 };
 
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -1478,53 +1478,149 @@ SharedArrayRawBuffer* Instance::sharedMe
 }
 
 WasmInstanceObject* Instance::objectUnbarriered() const {
   return object_.unbarrieredGet();
 }
 
 WasmInstanceObject* Instance::object() const { return object_; }
 
-bool Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args) {
-  // If there has been a moving grow, this Instance should have been notified.
-  MOZ_RELEASE_ASSERT(!memory_ || tlsData()->memoryBase ==
-                                     memory_->buffer().dataPointerEither());
+static bool EnsureEntryStubs(const Instance& instance, uint32_t funcIndex,
+                             const FuncExport** funcExport,
+                             void** interpEntry) {
+  Tier tier = instance.code().bestTier();
+
+  size_t funcExportIndex;
+  *funcExport =
+      &instance.metadata(tier).lookupFuncExport(funcIndex, &funcExportIndex);
+
+  const FuncExport& fe = **funcExport;
+  if (fe.hasEagerStubs()) {
+    *interpEntry = instance.codeBase(tier) + fe.eagerInterpEntryOffset();
+    return true;
+  }
+
+  MOZ_ASSERT(!instance.isAsmJS(), "only wasm can lazily export functions");
+
+  // If the best tier is Ion, life is simple: background compilation has
+  // already completed and has been committed, so there's no risk of race
+  // conditions here.
+  //
+  // If the best tier is Baseline, there could be a background compilation
+  // happening at the same time. The background compilation will lock the
+  // first tier lazy stubs first to stop new baseline stubs from being
+  // generated, then the second tier stubs to generate them.
+  //
+  // - either we take the tier1 lazy stub lock before the background
+  // compilation gets it, then we generate the lazy stub for tier1. When the
+  // background thread gets the tier1 lazy stub lock, it will see it has a
+  // lazy stub and will recompile it for tier2.
+  // - or we don't take the lock here first. Background compilation won't
+  // find a lazy stub for this function, thus won't generate it. So we'll do
+  // it ourselves after taking the tier2 lock.
+
+  auto stubs = instance.code(tier).lazyStubs().lock();
+  *interpEntry = stubs->lookupInterpEntry(fe.funcIndex());
+  if (*interpEntry) {
+    return true;
+  }
+
+  // The best tier might have changed after we've taken the lock.
+  Tier prevTier = tier;
+  tier = instance.code().bestTier();
+  const CodeTier& codeTier = instance.code(tier);
+  if (tier == prevTier) {
+    if (!stubs->createOne(funcExportIndex, codeTier)) {
+      return false;
+    }
 
-  Tier tier = code().bestTier();
+    *interpEntry = stubs->lookupInterpEntry(fe.funcIndex());
+    MOZ_ASSERT(*interpEntry);
+    return true;
+  }
+
+  MOZ_RELEASE_ASSERT(prevTier == Tier::Baseline && tier == Tier::Optimized);
+  auto stubs2 = instance.code(tier).lazyStubs().lock();
+
+  // If it didn't have a stub in the first tier, background compilation
+  // shouldn't have made one in the second tier.
+  MOZ_ASSERT(!stubs2->hasStub(fe.funcIndex()));
+
+  if (!stubs2->createOne(funcExportIndex, codeTier)) {
+    return false;
+  }
+
+  *interpEntry = stubs2->lookupInterpEntry(fe.funcIndex());
+  MOZ_ASSERT(*interpEntry);
+  return true;
+}
+
+static bool GetInterpEntry(Instance& instance, uint32_t funcIndex,
+                           CallArgs args, void** interpEntry,
+                           const FuncType** funcType) {
+  const FuncExport* funcExport;
+  if (!EnsureEntryStubs(instance, funcIndex, &funcExport, interpEntry)) {
+    return false;
+  }
 
-  const FuncExport& func = metadata(tier).lookupFuncExport(funcIndex);
+  // EnsureEntryStubs() has ensured jit-entry stubs have been created and
+  // installed in funcIndex's JumpTable entry, so we can now set the
+  // JSFunction's jit-entry. See WasmInstanceObject::getExportedFunction().
+  if (!funcExport->hasEagerStubs() && funcExport->canHaveJitEntry()) {
+    JSFunction& callee = args.callee().as<JSFunction>();
+    MOZ_ASSERT(!callee.isAsmJSNative(), "asm.js only has eager stubs");
+    if (!callee.isWasmWithJitEntry()) {
+      callee.setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex));
+    }
+  }
 
-  if (func.funcType().hasI64ArgOrRet()) {
+  *funcType = &funcExport->funcType();
+  return true;
+}
+
+bool Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args) {
+  if (memory_) {
+    // If there has been a moving grow, this Instance should have been notified.
+    MOZ_RELEASE_ASSERT(memory_->buffer().dataPointerEither() == memoryBase());
+  }
+
+  void* interpEntry;
+  const FuncType* funcType;
+  if (!GetInterpEntry(*this, funcIndex, args, &interpEntry, &funcType)) {
+    return false;
+  }
+
+  if (funcType->hasI64ArgOrRet()) {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_BAD_I64_TYPE);
     return false;
   }
 
   // The calling convention for an external call into wasm is to pass an
   // array of 16-byte values where each value contains either a coerced int32
   // (in the low word), or a double value (in the low dword) value, with the
   // coercions specified by the wasm signature. The external entry point
   // unpacks this array into the system-ABI-specified registers and stack
   // memory and then calls into the internal entry point. The return value is
   // stored in the first element of the array (which, therefore, must have
   // length >= 1).
   Vector<ExportArg, 8> exportArgs(cx);
-  if (!exportArgs.resize(Max<size_t>(1, func.funcType().args().length()))) {
+  if (!exportArgs.resize(Max<size_t>(1, funcType->args().length()))) {
     return false;
   }
 
   ASSERT_ANYREF_IS_JSOBJECT;
   Rooted<GCVector<JSObject*, 8, SystemAllocPolicy>> anyrefs(cx);
 
   DebugCodegen(DebugChannel::Function, "wasm-function[%d]; arguments ",
                funcIndex);
   RootedValue v(cx);
-  for (size_t i = 0; i < func.funcType().args().length(); ++i) {
+  for (size_t i = 0; i < funcType->args().length(); ++i) {
     v = i < args.length() ? args[i] : UndefinedValue();
-    switch (func.funcType().arg(i).code()) {
+    switch (funcType->arg(i).code()) {
       case ValType::I32:
         if (!ToInt32(cx, v, (int32_t*)&exportArgs[i])) {
           DebugCodegen(DebugChannel::Function, "call to ToInt32 failed!\n");
           return false;
         }
         DebugCodegen(DebugChannel::Function, "i32(%d) ",
                      *(int32_t*)&exportArgs[i]);
         break;
@@ -1568,43 +1664,36 @@ bool Instance::callExport(JSContext* cx,
       case ValType::NullRef: {
         MOZ_CRASH("NullRef not expressible");
       }
     }
   }
 
   DebugCodegen(DebugChannel::Function, "\n");
 
+  // Copy over reference values from the rooted array, if any.
+  if (anyrefs.length() > 0) {
+    DebugCodegen(DebugChannel::Function, "; ");
+    size_t nextRef = 0;
+    for (size_t i = 0; i < funcType->args().length(); ++i) {
+      if (funcType->arg(i).isReference()) {
+        ASSERT_ANYREF_IS_JSOBJECT;
+        *(void**)&exportArgs[i] = (void*)anyrefs[nextRef++];
+        DebugCodegen(DebugChannel::Function, "ptr(#%d) = %p ", int(nextRef - 1),
+                     *(void**)&exportArgs[i]);
+      }
+    }
+    anyrefs.clear();
+  }
+
   {
     JitActivation activation(cx);
 
-    void* callee;
-    if (func.hasEagerStubs()) {
-      callee = codeBase(tier) + func.eagerInterpEntryOffset();
-    } else {
-      callee = code(tier).lazyStubs().lock()->lookupInterpEntry(funcIndex);
-    }
-
-    // Copy over reference values from the rooted array, if any.
-    if (anyrefs.length() > 0) {
-      DebugCodegen(DebugChannel::Function, "; ");
-      size_t nextRef = 0;
-      for (size_t i = 0; i < func.funcType().args().length(); ++i) {
-        if (func.funcType().arg(i).isReference()) {
-          ASSERT_ANYREF_IS_JSOBJECT;
-          *(void**)&exportArgs[i] = (void*)anyrefs[nextRef++];
-          DebugCodegen(DebugChannel::Function, "ptr(#%d) = %p ",
-                       int(nextRef - 1), *(void**)&exportArgs[i]);
-        }
-      }
-      anyrefs.clear();
-    }
-
     // Call the per-exported-function trampoline created by GenerateEntry.
-    auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, callee);
+    auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, interpEntry);
     if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), tlsData())) {
       return false;
     }
   }
 
   if (isAsmJS() && args.isConstructing()) {
     // By spec, when a JS function is called as a constructor and this
     // function returns a primary type, which is the case for all asm.js
@@ -1614,23 +1703,23 @@ bool Instance::callExport(JSContext* cx,
     if (!obj) {
       return false;
     }
     args.rval().set(ObjectValue(*obj));
     return true;
   }
 
   // Note that we're not rooting the return value; we depend on UnboxAnyRef()
-  // not allocating for this to be safe.  The constraint has been noted in that
-  // function.
+  // not allocating for this to be safe.  The constraint has been noted in
+  // that function.
   void* retAddr = &exportArgs[0];
 
   DebugCodegen(DebugChannel::Function, "wasm-function[%d]; returns ",
                funcIndex);
-  switch (func.funcType().ret().code()) {
+  switch (funcType->ret().code()) {
     case ExprType::Void:
       args.rval().set(UndefinedValue());
       DebugCodegen(DebugChannel::Function, "void");
       break;
     case ExprType::I32:
       args.rval().set(Int32Value(*(int32_t*)retAddr));
       DebugCodegen(DebugChannel::Function, "i32(%d)", *(int32_t*)retAddr);
       break;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -1403,124 +1403,67 @@ static bool WasmCall(JSContext* cx, unsi
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedFunction callee(cx, &args.callee().as<JSFunction>());
 
   Instance& instance = ExportedFunctionToInstance(callee);
   uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
   return instance.callExport(cx, funcIndex, args);
 }
 
-static bool EnsureLazyEntryStub(const Instance& instance,
-                                size_t funcExportIndex, const FuncExport& fe) {
-  if (fe.hasEagerStubs()) {
-    return true;
-  }
-
-  MOZ_ASSERT(!instance.isAsmJS(), "only wasm can lazily export functions");
-
-  // If the best tier is Ion, life is simple: background compilation has
-  // already completed and has been committed, so there's no risk of race
-  // conditions here.
-  //
-  // If the best tier is Baseline, there could be a background compilation
-  // happening at the same time. The background compilation will lock the
-  // first tier lazy stubs first to stop new baseline stubs from being
-  // generated, then the second tier stubs to generate them.
-  //
-  // - either we take the tier1 lazy stub lock before the background
-  // compilation gets it, then we generate the lazy stub for tier1. When the
-  // background thread gets the tier1 lazy stub lock, it will see it has a
-  // lazy stub and will recompile it for tier2.
-  // - or we don't take the lock here first. Background compilation won't
-  // find a lazy stub for this function, thus won't generate it. So we'll do
-  // it ourselves after taking the tier2 lock.
-
-  Tier prevTier = instance.code().bestTier();
-
-  auto stubs = instance.code(prevTier).lazyStubs().lock();
-  if (stubs->hasStub(fe.funcIndex())) {
-    return true;
-  }
-
-  // The best tier might have changed after we've taken the lock.
-  Tier tier = instance.code().bestTier();
-  const CodeTier& codeTier = instance.code(tier);
-  if (tier == prevTier) {
-    return stubs->createOne(funcExportIndex, codeTier);
-  }
-
-  MOZ_ASSERT(prevTier == Tier::Baseline && tier == Tier::Optimized);
-
-  auto stubs2 = instance.code(tier).lazyStubs().lock();
-
-  // If it didn't have a stub in the first tier, background compilation
-  // shouldn't have made one in the second tier.
-  MOZ_ASSERT(!stubs2->hasStub(fe.funcIndex()));
-
-  return stubs2->createOne(funcExportIndex, codeTier);
-}
-
 /* static */
 bool WasmInstanceObject::getExportedFunction(
     JSContext* cx, HandleWasmInstanceObject instanceObj, uint32_t funcIndex,
     MutableHandleFunction fun) {
   if (ExportMap::Ptr p = instanceObj->exports().lookup(funcIndex)) {
     fun.set(p->value());
     return true;
   }
 
   const Instance& instance = instanceObj->instance();
-  const MetadataTier& metadata = instance.metadata(instance.code().bestTier());
-
-  size_t funcExportIndex;
   const FuncExport& funcExport =
-      metadata.lookupFuncExport(funcIndex, &funcExportIndex);
-
-  if (!EnsureLazyEntryStub(instance, funcExportIndex, funcExport)) {
-    return false;
-  }
-
-  const FuncType& funcType = funcExport.funcType();
-  unsigned numArgs = funcType.args().length();
+      instance.metadata(instance.code().bestTier()).lookupFuncExport(funcIndex);
+  unsigned numArgs = funcExport.funcType().args().length();
 
   if (instance.isAsmJS()) {
     // asm.js needs to act like a normal JS function which means having the
     // name from the original source and being callable as a constructor.
     RootedAtom name(cx, instance.getFuncDisplayAtom(cx, funcIndex));
     if (!name) {
       return false;
     }
     fun.set(NewNativeConstructor(cx, WasmCall, numArgs, name,
                                  gc::AllocKind::FUNCTION_EXTENDED,
                                  SingletonObject, JSFunction::ASMJS_CTOR));
     if (!fun) {
       return false;
     }
+
+    // asm.js does not support jit entries.
     fun->setWasmFuncIndex(funcIndex);
   } else {
     RootedAtom name(cx, NumberToAtom(cx, funcIndex));
     if (!name) {
       return false;
     }
 
-    // Functions with anyref don't have jit entries yet.
-    bool disableJitEntry = funcType.temporarilyUnsupportedAnyRef() ||
-                           !JitOptions.enableWasmJitEntry;
-
     fun.set(NewNativeFunction(cx, WasmCall, numArgs, name,
                               gc::AllocKind::FUNCTION_EXTENDED, SingletonObject,
                               JSFunction::WASM));
     if (!fun) {
       return false;
     }
 
-    if (disableJitEntry) {
+    // Some applications eagerly access all table elements which currently
+    // triggers worst-case behavior for lazy stubs, since each will allocate a
+    // separate 4kb code page. Most eagerly-accessed functions are not called,
+    // so instead wait until Instance::callExport() to create the entry stubs.
+    if (funcExport.hasEagerStubs() && funcExport.canHaveJitEntry()) {
+      fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex));
+    } else {
       fun->setWasmFuncIndex(funcIndex);
-    } else {
-      fun->setWasmJitEntry(instance.code().getAddressOfJitEntry(funcIndex));
     }
   }
 
   fun->setExtendedSlot(FunctionExtended::WASM_INSTANCE_SLOT,
                        ObjectValue(*instanceObj));
 
   void* tlsData = instanceObj->instance().tlsData();
   fun->setExtendedSlot(FunctionExtended::WASM_TLSDATA_SLOT,
@@ -1585,21 +1528,21 @@ WasmFunctionScope* WasmInstanceObject::g
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
   return funcScope;
 }
 
 bool wasm::IsExportedFunction(JSFunction* fun) {
-  return fun->maybeNative() == WasmCall;
+  return fun->kind() == JSFunction::Wasm || fun->kind() == JSFunction::AsmJS;
 }
 
 bool wasm::IsExportedWasmFunction(JSFunction* fun) {
-  return IsExportedFunction(fun) && !ExportedFunctionToInstance(fun).isAsmJS();
+  return fun->kind() == JSFunction::Wasm;
 }
 
 bool wasm::IsExportedFunction(const Value& v, MutableHandleFunction f) {
   if (!v.isObject()) {
     return false;
   }
 
   JSObject& obj = v.toObject();