Bug 1356680 - Baldr: make builtin thunks JSRuntime-independent (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Mon, 24 Apr 2017 13:35:14 -0500
changeset 354683 a5f5fc5a1dbca541f9f7214e5b09a4928b7a81bd
parent 354682 56c8ce11c6293b8b59e2a1b15b07da50545b3206
child 354684 cb6f60edd4d096d7bef1bc63dd19965abc39315b
push id31709
push userihsiao@mozilla.com
push dateTue, 25 Apr 2017 03:21:59 +0000
treeherdermozilla-central@85932a5027c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1356680
milestone55.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 1356680 - Baldr: make builtin thunks JSRuntime-independent (r=bbouvier) MozReview-Commit-ID: 2bIvhjULsg6
js/src/jit/InlinableNatives.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MacroAssembler-inl.h
js/src/moz.build
js/src/vm/Initialization.cpp
js/src/vm/MutexIDs.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/wasm/WasmBuiltins.cpp
js/src/wasm/WasmBuiltins.h
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmFrameIterator.cpp
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmRuntime.cpp
js/src/wasm/WasmRuntime.h
js/src/wasm/WasmSignalHandlers.cpp
js/src/wasm/WasmStubs.cpp
js/src/wasm/WasmStubs.h
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
--- a/js/src/jit/InlinableNatives.h
+++ b/js/src/jit/InlinableNatives.h
@@ -153,16 +153,17 @@ struct JSJitInfo;
 
 namespace js {
 namespace jit {
 
 enum class InlinableNative : uint16_t {
 #define ADD_NATIVE(native) native,
     INLINABLE_NATIVE_LIST(ADD_NATIVE)
 #undef ADD_NATIVE
+    Limit
 };
 
 #define ADD_NATIVE(native) extern const JSJitInfo JitInfo_##native;
     INLINABLE_NATIVE_LIST(ADD_NATIVE)
 #undef ADD_NATIVE
 
 } // namespace jit
 } // namespace js
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -351,16 +351,18 @@ IonBuilder::inlineNativeCall(CallInfo& c
         return inlineObjectIsTypeDescr(callInfo);
       case InlinableNative::IntrinsicTypeDescrIsSimpleType:
         return inlineHasClass(callInfo,
                               &ScalarTypeDescr::class_, &ReferenceTypeDescr::class_);
       case InlinableNative::IntrinsicTypeDescrIsArrayType:
         return inlineHasClass(callInfo, &ArrayTypeDescr::class_);
       case InlinableNative::IntrinsicSetTypedObjectOffset:
         return inlineSetTypedObjectOffset(callInfo);
+      case InlinableNative::Limit:
+        break;
     }
 
     MOZ_CRASH("Shouldn't get here");
 }
 
 IonBuilder::InliningResult
 IonBuilder::inlineNativeGetter(CallInfo& callInfo, JSFunction* target)
 {
--- a/js/src/jit/MacroAssembler-inl.h
+++ b/js/src/jit/MacroAssembler-inl.h
@@ -22,16 +22,18 @@
 #elif defined(JS_CODEGEN_MIPS32)
 # include "jit/mips32/MacroAssembler-mips32-inl.h"
 #elif defined(JS_CODEGEN_MIPS64)
 # include "jit/mips64/MacroAssembler-mips64-inl.h"
 #elif !defined(JS_CODEGEN_NONE)
 # error "Unknown architecture!"
 #endif
 
+#include "wasm/WasmBuiltins.h"
+
 namespace js {
 namespace jit {
 
 //{{{ check_macroassembler_style
 // ===============================================================
 // Frame manipulation functions.
 
 uint32_t
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -363,26 +363,26 @@ UNIFIED_SOURCES += [
     'vm/WeakMapPtr.cpp',
     'vm/Xdr.cpp',
     'wasm/AsmJS.cpp',
     'wasm/WasmBaselineCompile.cpp',
     'wasm/WasmBinaryIterator.cpp',
     'wasm/WasmBinaryToAST.cpp',
     'wasm/WasmBinaryToExperimentalText.cpp',
     'wasm/WasmBinaryToText.cpp',
+    'wasm/WasmBuiltins.cpp',
     'wasm/WasmCode.cpp',
     'wasm/WasmCompartment.cpp',
     'wasm/WasmCompile.cpp',
     'wasm/WasmFrameIterator.cpp',
     'wasm/WasmGenerator.cpp',
     'wasm/WasmInstance.cpp',
     'wasm/WasmIonCompile.cpp',
     'wasm/WasmJS.cpp',
     'wasm/WasmModule.cpp',
-    'wasm/WasmRuntime.cpp',
     'wasm/WasmSignalHandlers.cpp',
     'wasm/WasmStubs.cpp',
     'wasm/WasmTable.cpp',
     'wasm/WasmTextToBinary.cpp',
     'wasm/WasmTextUtils.cpp',
     'wasm/WasmTypes.cpp',
     'wasm/WasmValidate.cpp'
 ]
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -26,16 +26,17 @@
 #include "unicode/utypes.h"
 #endif // ENABLE_INTL_API
 #include "vm/DateTime.h"
 #include "vm/HelperThreads.h"
 #include "vm/Runtime.h"
 #include "vm/Time.h"
 #include "vm/TraceLogging.h"
 #include "vtune/VTuneWrapper.h"
+#include "wasm/WasmBuiltins.h"
 #include "wasm/WasmInstance.h"
 
 using JS::detail::InitState;
 using JS::detail::libraryInitState;
 using js::FutexThread;
 
 InitState JS::detail::libraryInitState;
 
@@ -187,18 +188,20 @@ JS_ShutDown(void)
     PRMJ_NowShutdown();
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
     js::FinishDateTimeState();
 
-    if (!JSRuntime::hasLiveRuntimes())
+    if (!JSRuntime::hasLiveRuntimes()) {
+        js::wasm::ReleaseBuiltinThunks();
         js::jit::ReleaseProcessExecutableMemory();
+    }
 
     libraryInitState = InitState::ShutDown;
 }
 
 JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
 {
     MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -21,16 +21,18 @@
   _(RuntimeExclusiveAccess,      200) \
                                       \
   _(GlobalHelperThreadState,     300) \
                                       \
   _(ShellAsyncTasks,             350) \
                                       \
   _(GCLock,                      400) \
                                       \
+  _(WasmInitBuiltinThunks,       450) \
+                                      \
   _(SharedImmutableStringsCache, 500) \
   _(FutexThread,                 500) \
   _(PromiseTaskPtrVector,        500) \
   _(GeckoProfilerStrings,        500) \
   _(ProtectedRegionTree,         500) \
   _(WasmSigIdSet,                500) \
   _(ShellOffThreadState,         500) \
   _(SimulatorCacheLock,          500) \
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -262,33 +262,28 @@ JSRuntime::init(JSContext* cx, uint32_t 
         sharedImmutableStrings_ = js::SharedImmutableStringsCache::Create();
         if (!sharedImmutableStrings_)
             return false;
     }
 
     if (!caches().init())
         return false;
 
-    if (!wasm().init())
-        return false;
-
     return true;
 }
 
 void
 JSRuntime::destroyRuntime()
 {
     MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
     MOZ_ASSERT(childRuntimeCount == 0);
     MOZ_ASSERT(initialized_);
 
     sharedIntlData.ref().destroyInstance();
 
-    wasm().destroy();
-
     if (gcInitialized) {
         /*
          * Finish any in-progress GCs first. This ensures the parseWaitingOnGC
          * list is empty in CancelOffThreadParses.
          */
         JSContext* cx = TlsContext.get();
         if (JS::IsIncrementalGCInProgress(cx))
             FinishGC(cx);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -49,17 +49,16 @@
 #include "vm/DateTime.h"
 #include "vm/GeckoProfiler.h"
 #include "vm/MallocProvider.h"
 #include "vm/Scope.h"
 #include "vm/SharedImmutableStringsCache.h"
 #include "vm/Stack.h"
 #include "vm/Stopwatch.h"
 #include "vm/Symbol.h"
-#include "wasm/WasmRuntime.h"
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
 #endif
 
 namespace js {
 
@@ -531,23 +530,16 @@ struct JSRuntime : public js::MallocProv
 
   public:
     js::UnprotectedData<JS::BuildIdOp> buildIdOp;
 
     /* AsmJSCache callbacks are runtime-wide. */
     js::UnprotectedData<JS::AsmJSCacheOps> asmJSCacheOps;
 
   private:
-    // All runtime data needed for wasm and defined in wasm/WasmRuntime.h.
-    js::ActiveThreadData<js::wasm::Runtime> wasmRuntime_;
-
-  public:
-    js::wasm::Runtime& wasm() { return wasmRuntime_.ref(); }
-
-  private:
     js::UnprotectedData<const JSPrincipals*> trustedPrincipals_;
   public:
     void setTrustedPrincipals(const JSPrincipals* p) { trustedPrincipals_ = p; }
     const JSPrincipals* trustedPrincipals() const { return trustedPrincipals_; }
 
     js::ActiveThreadData<const JSWrapObjectCallbacks*> wrapObjectCallbacks;
     js::ActiveThreadData<js::PreserveWrapperCallback> preserveWrapperCallback;
 
rename from js/src/wasm/WasmRuntime.cpp
rename to js/src/wasm/WasmBuiltins.cpp
--- a/js/src/wasm/WasmRuntime.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -11,41 +11,48 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#include "wasm/WasmRuntime.h"
+#include "wasm/WasmBuiltins.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/BinarySearch.h"
 
 #include "fdlibm.h"
-
 #include "jslibmath.h"
 
+#include "jit/InlinableNatives.h"
 #include "jit/MacroAssembler.h"
-
+#include "threading/Mutex.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmStubs.h"
 
 #include "vm/Debugger-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace jit;
 using namespace wasm;
 
+using mozilla::Atomic;
 using mozilla::BinarySearchIf;
+using mozilla::HashGeneric;
 using mozilla::IsNaN;
+using mozilla::MakeEnumeratedRange;
 
-static const unsigned BUILTIN_THUNK_LIFO_SIZE = 128;
-static const CodeKind BUILTIN_THUNK_CODEKIND = CodeKind::OTHER_CODE;
+static const unsigned BUILTIN_THUNK_LIFO_SIZE = 64 * 1024;
+
+// ============================================================================
+// WebAssembly builtin C++ functions called from wasm code to implement internal
+// wasm operations.
 
 #if defined(JS_CODEGEN_ARM)
 extern "C" {
 
 extern MOZ_EXPORT int64_t
 __aeabi_idivmod(int, int);
 
 extern MOZ_EXPORT int64_t
@@ -552,112 +559,22 @@ AddressOf(SymbolicAddress imm, ABIFuncti
         return FuncCast(Instance::currentMemory_i32, *abiType);
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("Bad SymbolicAddress");
 }
 
-static bool
-GenerateBuiltinThunk(JSContext* cx, void* func, ABIFunctionType abiType, ExitReason exitReason,
-                     UniqueBuiltinThunk* thunk)
-{
-    if (!cx->compartment()->ensureJitCompartmentExists(cx))
-        return false;
-
-    LifoAlloc lifo(BUILTIN_THUNK_LIFO_SIZE);
-    TempAllocator tempAlloc(&lifo);
-    MacroAssembler masm(MacroAssembler::WasmToken(), tempAlloc);
-
-    CallableOffsets offsets = GenerateBuiltinNativeExit(masm, abiType, exitReason, func);
-
-    masm.finish();
-    if (masm.oom())
-        return false;
-
-    // The executable allocator operates on pointer-aligned sizes.
-    uint32_t codeLength = AlignBytes(masm.bytesNeeded(), sizeof(void*));
-
-    ExecutablePool* pool = nullptr;
-    ExecutableAllocator& allocator = cx->runtime()->jitRuntime()->execAlloc();
-    uint8_t* codeBase = (uint8_t*) allocator.alloc(cx, codeLength, &pool, BUILTIN_THUNK_CODEKIND);
-    if (!codeBase)
-        return false;
-
-    {
-        AutoWritableJitCode awjc(cx->runtime(), codeBase, codeLength);
-        AutoFlushICache afc("GenerateBuiltinThunk");
-
-        masm.executableCopy(codeBase);
-        masm.processCodeLabels(codeBase);
-        memset(codeBase + masm.bytesNeeded(), 0, codeLength - masm.bytesNeeded());
-
-#ifdef DEBUG
-        if (!masm.oom()) {
-            MOZ_ASSERT(masm.callSites().empty());
-            MOZ_ASSERT(masm.callFarJumps().empty());
-            MOZ_ASSERT(masm.trapSites().empty());
-            MOZ_ASSERT(masm.trapFarJumps().empty());
-            MOZ_ASSERT(masm.extractMemoryAccesses().empty());
-            MOZ_ASSERT(!masm.numSymbolicAccesses());
-        }
-#endif
-    }
-
-    *thunk = js::MakeUnique<BuiltinThunk>(codeBase, codeLength, pool, offsets);
-    return !!*thunk;
-}
-
-struct BuiltinMatcher
-{
-    const uint8_t* address;
-    explicit BuiltinMatcher(const uint8_t* address) : address(address) {}
-    int operator()(const UniqueBuiltinThunk& thunk) const {
-        if (address < thunk->base)
-            return -1;
-        if (uintptr_t(address) >= uintptr_t(thunk->base) + thunk->size)
-            return 1;
-        return 0;
-    }
-};
-
 bool
-wasm::Runtime::getBuiltinThunk(JSContext* cx, void* funcPtr, ABIFunctionType abiType,
-                               ExitReason exitReason, void** thunkPtr)
-{
-    TypedFuncPtr lookup(funcPtr, abiType);
-    auto ptr = builtinThunkMap_.lookupForAdd(lookup);
-    if (ptr) {
-        *thunkPtr = ptr->value();
-        return true;
-    }
-
-    UniqueBuiltinThunk thunk;
-    if (!GenerateBuiltinThunk(cx, funcPtr, abiType, exitReason, &thunk))
-        return false;
-
-    // Maintain sorted order of thunk addresses.
-    size_t i;
-    size_t size = builtinThunkVector_.length();
-    if (BinarySearchIf(builtinThunkVector_, 0, size, BuiltinMatcher(thunk->base), &i))
-        MOZ_CRASH("clobbering memory");
-
-    *thunkPtr = thunk->base + thunk->codeRange.begin();
-
-    return builtinThunkVector_.insert(builtinThunkVector_.begin() + i, Move(thunk)) &&
-           builtinThunkMap_.add(ptr, lookup, *thunkPtr);
-}
-
-bool
-wasm::NeedsBuiltinThunk(SymbolicAddress func)
+wasm::NeedsBuiltinThunk(SymbolicAddress sym)
 {
     // Some functions don't want to a thunk, because they already have one or
     // they don't have frame info.
-    switch (func) {
+    switch (sym) {
       case SymbolicAddress::HandleExecutionInterrupt: // GenerateInterruptExit
       case SymbolicAddress::HandleDebugTrap:          // GenerateDebugTrapStub
       case SymbolicAddress::HandleThrow:              // GenerateThrowStub
       case SymbolicAddress::ReportTrap:               // GenerateTrapExit
       case SymbolicAddress::ReportOutOfBounds:        // GenerateOutOfBoundsExit
       case SymbolicAddress::ReportUnalignedAccess:    // GeneratesUnalignedExit
       case SymbolicAddress::CallImport_Void:          // GenerateImportInterpExit
       case SymbolicAddress::CallImport_I32:
@@ -712,28 +629,284 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
         return true;
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("unexpected symbolic address");
 }
 
-bool
-wasm::Runtime::getBuiltinThunk(JSContext* cx, SymbolicAddress func, void** thunkPtr)
-{
-    ABIFunctionType abiType;
-    void* funcPtr = AddressOf(func, &abiType);
+// ============================================================================
+// JS builtins that can be imported by wasm modules and called efficiently
+// through thunks. These thunks conform to the internal wasm ABI and thus can be
+// patched in for import calls. Calling a JS builtin through a thunk is much
+// faster than calling out through the generic import call trampoline which will
+// end up in the slowest C++ Instance::callImport path.
+//
+// Each JS builtin can have several overloads. These must all be enumerated in
+// PopulateTypedNatives() so they can be included in the process-wide thunk set.
 
-    if (!NeedsBuiltinThunk(func)) {
-        *thunkPtr = funcPtr;
-        return true;
+#define FOR_EACH_UNARY_NATIVE(_)   \
+    _(math_sin, MathSin)           \
+    _(math_tan, MathTan)           \
+    _(math_cos, MathCos)           \
+    _(math_exp, MathExp)           \
+    _(math_log, MathLog)           \
+    _(math_asin, MathASin)         \
+    _(math_atan, MathATan)         \
+    _(math_acos, MathACos)         \
+    _(math_log10, MathLog10)       \
+    _(math_log2, MathLog2)         \
+    _(math_log1p, MathLog1P)       \
+    _(math_expm1, MathExpM1)       \
+    _(math_sinh, MathSinH)         \
+    _(math_tanh, MathTanH)         \
+    _(math_cosh, MathCosH)         \
+    _(math_asinh, MathASinH)       \
+    _(math_atanh, MathATanH)       \
+    _(math_acosh, MathACosH)       \
+    _(math_sign, MathSign)         \
+    _(math_trunc, MathTrunc)       \
+    _(math_cbrt, MathCbrt)
+
+#define FOR_EACH_BINARY_NATIVE(_)  \
+    _(ecmaAtan2, MathATan2)        \
+    _(ecmaHypot, MathHypot)        \
+    _(ecmaPow, MathPow)            \
+
+#define DEFINE_UNARY_FLOAT_WRAPPER(func, _)        \
+    static float func##_uncached_f32(float x) {    \
+        return float(func##_uncached(double(x)));  \
+    }
+
+#define DEFINE_BINARY_FLOAT_WRAPPER(func, _)       \
+    static float func##_f32(float x, float y) {    \
+        return float(func(double(x), double(y)));  \
     }
 
-    return getBuiltinThunk(cx, funcPtr, abiType, ExitReason(func), thunkPtr);
+FOR_EACH_UNARY_NATIVE(DEFINE_UNARY_FLOAT_WRAPPER)
+FOR_EACH_BINARY_NATIVE(DEFINE_BINARY_FLOAT_WRAPPER)
+
+#undef DEFINE_UNARY_FLOAT_WRAPPER
+#undef DEFINE_BINARY_FLOAT_WRAPPER
+
+struct TypedNative
+{
+    InlinableNative native;
+    ABIFunctionType abiType;
+
+    TypedNative(InlinableNative native, ABIFunctionType abiType)
+      : native(native),
+        abiType(abiType)
+    {}
+
+    typedef TypedNative Lookup;
+    static HashNumber hash(const Lookup& l) {
+        return HashGeneric(uint32_t(l.native), uint32_t(l.abiType));
+    }
+    static bool match(const TypedNative& lhs, const Lookup& rhs) {
+        return lhs.native == rhs.native && lhs.abiType == rhs.abiType;
+    }
+};
+
+using TypedNativeToFuncPtrMap =
+    HashMap<TypedNative, void*, TypedNative, SystemAllocPolicy>;
+
+static bool
+PopulateTypedNatives(TypedNativeToFuncPtrMap* typedNatives)
+{
+    if (!typedNatives->init())
+        return false;
+
+#define ADD_OVERLOAD(funcName, native, abiType)                                           \
+    if (!typedNatives->putNew(TypedNative(InlinableNative::native, abiType),              \
+                              FuncCast(funcName, abiType)))                               \
+        return false;
+
+#define ADD_UNARY_OVERLOADS(funcName, native)                                             \
+    ADD_OVERLOAD(funcName##_uncached, native, Args_Double_Double)                         \
+    ADD_OVERLOAD(funcName##_uncached_f32, native, Args_Float32_Float32)
+
+#define ADD_BINARY_OVERLOADS(funcName, native)                                            \
+    ADD_OVERLOAD(funcName, native, Args_Double_DoubleDouble)                              \
+    ADD_OVERLOAD(funcName##_f32, native, Args_Float32_Float32Float32)
+
+    FOR_EACH_UNARY_NATIVE(ADD_UNARY_OVERLOADS)
+    FOR_EACH_BINARY_NATIVE(ADD_BINARY_OVERLOADS)
+
+#undef ADD_UNARY_OVERLOADS
+#undef ADD_BINARY_OVERLOADS
+
+    return true;
+}
+
+#undef FOR_EACH_UNARY_NATIVE
+#undef FOR_EACH_BINARY_NATIVE
+
+// ============================================================================
+// Process-wide builtin thunk set
+//
+// Thunks are inserted between wasm calls and the C++ callee and achieve two
+// things:
+//  - bridging the few differences between the internal wasm ABI and the external
+//    native ABI (viz. float returns on x86 and soft-fp ARM)
+//  - executing an exit prologue/epilogue which in turn allows any asynchronous
+//    interrupt to see the full stack up to the wasm operation that called out
+//
+// Thunks are created for two kinds of C++ callees, enumerated above:
+//  - SymbolicAddress: for statically compiled calls in the wasm module
+//  - Imported JS builtins: optimized calls to imports
+//
+// All thunks are created up front, lazily, when the first wasm module is
+// compiled in the process. Thunks are kept alive until the JS engine shuts down
+// in the process. No thunks are created at runtime after initialization. This
+// simple scheme allows several simplifications:
+//  - no reference counting to keep thunks alive
+//  - no problems toggling W^X permissions which, because of multiple executing
+//    threads, would require each thunk allocation to be on its own page
+// The cost for creating all thunks at once is relatively low since all thunks
+// fit within the smallest executable quanta (64k).
+
+using TypedNativeToCodeRangeMap =
+    HashMap<TypedNative, uint32_t, TypedNative, SystemAllocPolicy>;
+
+using SymbolicAddressToCodeRangeArray =
+    EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, uint32_t>;
+
+struct BuiltinThunks
+{
+    uint8_t* codeBase;
+    size_t codeSize;
+    CodeRangeVector codeRanges;
+    TypedNativeToCodeRangeMap typedNativeToCodeRange;
+    SymbolicAddressToCodeRangeArray symbolicAddressToCodeRange;
+
+    BuiltinThunks()
+      : codeBase(nullptr), codeSize(0)
+    {}
+
+    ~BuiltinThunks() {
+        if (codeBase)
+            DeallocateExecutableMemory(codeBase, codeSize);
+    }
+};
+
+Mutex initBuiltinThunks(mutexid::WasmInitBuiltinThunks);
+Atomic<const BuiltinThunks*> builtinThunks;
+
+bool
+wasm::EnsureBuiltinThunksInitialized()
+{
+    LockGuard<Mutex> guard(initBuiltinThunks);
+    if (builtinThunks)
+        return true;
+
+    auto thunks = MakeUnique<BuiltinThunks>();
+    if (!thunks)
+        return false;
+
+    LifoAlloc lifo(BUILTIN_THUNK_LIFO_SIZE);
+    TempAllocator tempAlloc(&lifo);
+    MacroAssembler masm(MacroAssembler::WasmToken(), tempAlloc);
+
+    for (auto sym : MakeEnumeratedRange(SymbolicAddress::Limit)) {
+        if (!NeedsBuiltinThunk(sym)) {
+            thunks->symbolicAddressToCodeRange[sym] = UINT32_MAX;
+            continue;
+        }
+
+        uint32_t codeRangeIndex = thunks->codeRanges.length();
+        thunks->symbolicAddressToCodeRange[sym] = codeRangeIndex;
+
+        ABIFunctionType abiType;
+        void* funcPtr = AddressOf(sym, &abiType);
+        ExitReason exitReason(sym);
+        CallableOffsets offset = GenerateBuiltinThunk(masm, abiType, exitReason, funcPtr);
+        if (masm.oom() || !thunks->codeRanges.emplaceBack(CodeRange::BuiltinThunk, offset))
+            return false;
+    }
+
+    TypedNativeToFuncPtrMap typedNatives;
+    if (!PopulateTypedNatives(&typedNatives))
+        return false;
+
+    if (!thunks->typedNativeToCodeRange.init())
+        return false;
+
+    for (TypedNativeToFuncPtrMap::Range r = typedNatives.all(); !r.empty(); r.popFront()) {
+        TypedNative typedNative = r.front().key();
+
+        uint32_t codeRangeIndex = thunks->codeRanges.length();
+        if (!thunks->typedNativeToCodeRange.putNew(typedNative, codeRangeIndex))
+            return false;
+
+        ABIFunctionType abiType = typedNative.abiType;
+        void* funcPtr = r.front().value();
+        ExitReason exitReason = ExitReason::Fixed::BuiltinNative;
+        CallableOffsets offset = GenerateBuiltinThunk(masm, abiType, exitReason, funcPtr);
+        if (masm.oom() || !thunks->codeRanges.emplaceBack(CodeRange::BuiltinThunk, offset))
+            return false;
+    }
+
+    masm.finish();
+    if (masm.oom())
+        return false;
+
+    size_t allocSize = AlignBytes(masm.bytesNeeded(), ExecutableCodePageSize);
+
+    thunks->codeSize = allocSize;
+    thunks->codeBase = (uint8_t*)AllocateExecutableMemory(allocSize, ProtectionSetting::Writable);
+    if (!thunks->codeBase)
+        return false;
+
+    masm.executableCopy(thunks->codeBase, /* flushICache = */ false);
+    memset(thunks->codeBase + masm.bytesNeeded(), 0, allocSize - masm.bytesNeeded());
+
+    masm.processCodeLabels(thunks->codeBase);
+#ifdef DEBUG
+    MOZ_ASSERT(masm.callSites().empty());
+    MOZ_ASSERT(masm.callFarJumps().empty());
+    MOZ_ASSERT(masm.trapSites().empty());
+    MOZ_ASSERT(masm.trapFarJumps().empty());
+    MOZ_ASSERT(masm.extractMemoryAccesses().empty());
+    MOZ_ASSERT(!masm.numSymbolicAccesses());
+#endif
+
+    ExecutableAllocator::cacheFlush(thunks->codeBase, thunks->codeSize);
+    if (!ExecutableAllocator::makeExecutable(thunks->codeBase, thunks->codeSize))
+        return false;
+
+    builtinThunks = thunks.release();
+    return true;
+}
+
+void
+wasm::ReleaseBuiltinThunks()
+{
+    if (builtinThunks) {
+        const BuiltinThunks* ptr = builtinThunks;
+        js_delete(const_cast<BuiltinThunks*>(ptr));
+        builtinThunks = nullptr;
+    }
+}
+
+void*
+wasm::SymbolicAddressTarget(SymbolicAddress sym)
+{
+    MOZ_ASSERT(builtinThunks);
+
+    ABIFunctionType abiType;
+    void* funcPtr = AddressOf(sym, &abiType);
+
+    if (!NeedsBuiltinThunk(sym))
+        return funcPtr;
+
+    const BuiltinThunks& thunks = *builtinThunks;
+    uint32_t codeRangeIndex = thunks.symbolicAddressToCodeRange[sym];
+    return thunks.codeBase + thunks.codeRanges[codeRangeIndex].begin();
 }
 
 static ABIFunctionType
 ToABIFunctionType(const Sig& sig)
 {
     const ValTypeVector& args = sig.args();
     ExprType ret = sig.ret();
 
@@ -750,41 +923,45 @@ ToABIFunctionType(const Sig& sig)
           case ValType::F64: abiType |= (ArgType_Double << (ArgType_Shift * (i + 1))); break;
           default:           MOZ_CRASH("unhandled arg type");
         }
     }
 
     return ABIFunctionType(abiType);
 }
 
-bool
-wasm::Runtime::getBuiltinThunk(JSContext* cx, void* funcPtr, const Sig& sig, void** thunkPtr)
+void*
+wasm::MaybeGetBuiltinThunk(HandleFunction f, const Sig& sig, JSContext* cx)
 {
+    MOZ_ASSERT(builtinThunks);
+
+    if (!f->isNative() || !f->jitInfo() || f->jitInfo()->type() != JSJitInfo::InlinableNative)
+        return nullptr;
+
+    InlinableNative native = f->jitInfo()->inlinableNative;
     ABIFunctionType abiType = ToABIFunctionType(sig);
-#ifdef JS_SIMULATOR
-    funcPtr = Simulator::RedirectNativeFunction(funcPtr, abiType);
-#endif
-    ExitReason nativeExitReason(ExitReason::Fixed::BuiltinNative);
-    return getBuiltinThunk(cx, funcPtr, abiType, nativeExitReason, thunkPtr);
+    TypedNative typedNative(native, abiType);
+
+    const BuiltinThunks& thunks = *builtinThunks;
+    auto p = thunks.typedNativeToCodeRange.readonlyThreadsafeLookup(typedNative);
+    if (!p)
+        return nullptr;
+
+    return thunks.codeBase + thunks.codeRanges[p->value()].begin();
 }
 
-BuiltinThunk*
-wasm::Runtime::lookupBuiltin(void* pc)
+bool
+wasm::LookupBuiltinThunk(void* pc, const CodeRange** codeRange, uint8_t** codeBase)
 {
-    size_t index;
-    size_t length = builtinThunkVector_.length();
-    if (!BinarySearchIf(builtinThunkVector_, 0, length, BuiltinMatcher((uint8_t*)pc), &index))
-        return nullptr;
-    return builtinThunkVector_[index].get();
-}
+    if (!builtinThunks)
+        return false;
 
-void
-wasm::Runtime::destroy()
-{
-    builtinThunkVector_.clear();
-    if (builtinThunkMap_.initialized())
-        builtinThunkMap_.clear();
+    const BuiltinThunks& thunks = *builtinThunks;
+    if (pc < thunks.codeBase || pc >= thunks.codeBase + thunks.codeSize)
+        return false;
+
+    *codeBase = thunks.codeBase;
+
+    CodeRange::OffsetInCode target((uint8_t*)pc - thunks.codeBase);
+    *codeRange = LookupInSorted(thunks.codeRanges, target);
+
+    return !!*codeRange;
 }
-
-BuiltinThunk::~BuiltinThunk()
-{
-    executablePool->release(size, BUILTIN_THUNK_CODEKIND);
-}
rename from js/src/wasm/WasmRuntime.h
rename to js/src/wasm/WasmBuiltins.h
--- a/js/src/wasm/WasmRuntime.h
+++ b/js/src/wasm/WasmBuiltins.h
@@ -11,96 +11,52 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#ifndef wasm_runtime_h
-#define wasm_runtime_h
+#ifndef wasm_builtins_h
+#define wasm_builtins_h
 
-#include "NamespaceImports.h"
-
-#include "jit/IonTypes.h"
 #include "wasm/WasmTypes.h"
 
-using mozilla::HashGeneric;
-
 namespace js {
-
-namespace jit {
-    class ExecutablePool;
-}
-
 namespace wasm {
 
-class ExitReason;
+// A SymbolicAddress that NeedsBuiltinThunk() will call through a thunk to the
+// C++ function. This will be true for all normal calls from normal wasm
+// function code. Only calls to C++ from other exits/thunks do not need a thunk.
+
+bool
+NeedsBuiltinThunk(SymbolicAddress sym);
+
+// This function queries whether pc is in one of the process's builtin thunks
+// and, if so, returns the CodeRange and pointer to the code segment that the
+// CodeRange is relative to.
 
 bool
-NeedsBuiltinThunk(SymbolicAddress imm);
-
-struct TypedFuncPtr
-{
-    void* funcPtr;
-    jit::ABIFunctionType abiType;
-
-    TypedFuncPtr(void* funcPtr, jit::ABIFunctionType abiType)
-      : funcPtr(funcPtr),
-        abiType(abiType)
-    {}
+LookupBuiltinThunk(void* pc, const CodeRange** codeRange, uint8_t** codeBase);
 
-    typedef TypedFuncPtr Lookup;
-    static HashNumber hash(const Lookup& l) {
-        return HashGeneric(l.funcPtr, uint32_t(l.abiType));
-    }
-    static bool match(const TypedFuncPtr& lhs, const Lookup& rhs) {
-        return lhs.funcPtr == rhs.funcPtr && lhs.abiType == rhs.abiType;
-    }
-};
-
-typedef HashMap<TypedFuncPtr, void*, TypedFuncPtr, SystemAllocPolicy> BuiltinThunkMap;
-
-struct BuiltinThunk
-{
-    jit::ExecutablePool* executablePool;
-    CodeRange codeRange;
-    size_t size;
-    uint8_t* base;
+// EnsureBuiltinThunksInitialized() must be called, and must succeed, before
+// SymbolicAddressTarget() or MaybeGetBuiltinThunk(). This function creates all
+// thunks for the process. ReleaseBuiltinThunks() should be called before
+// ReleaseProcessExecutableMemory() so that the latter can assert that all
+// executable code has been released.
 
-    BuiltinThunk(uint8_t* base, size_t size, jit::ExecutablePool* executablePool,
-                 CallableOffsets offsets)
-      : executablePool(executablePool),
-        codeRange(CodeRange(CodeRange::BuiltinNativeExit, offsets)),
-        size(size),
-        base(base)
-    {}
-    ~BuiltinThunk();
-};
+bool
+EnsureBuiltinThunksInitialized();
 
-typedef UniquePtr<BuiltinThunk> UniqueBuiltinThunk;
-typedef Vector<UniqueBuiltinThunk, 4, SystemAllocPolicy> BuiltinThunkVector;
-
-// wasm::Runtime contains all the needed information for wasm that has the
-// lifetime of a JSRuntime.
+void*
+SymbolicAddressTarget(SymbolicAddress sym);
 
-class Runtime
-{
-    BuiltinThunkMap builtinThunkMap_;
-    BuiltinThunkVector builtinThunkVector_;
-
-    bool getBuiltinThunk(JSContext* cx, void* funcPtr, jit::ABIFunctionType type,
-                         ExitReason exitReason, void** thunkPtr);
+void*
+MaybeGetBuiltinThunk(HandleFunction f, const Sig& sig, JSContext* cx);
 
-  public:
-    bool init() { return builtinThunkMap_.init(); }
-    void destroy();
-
-    bool getBuiltinThunk(JSContext* cx, SymbolicAddress func, void** thunkPtr);
-    bool getBuiltinThunk(JSContext* cx, void* funcPtr, const Sig& sig, void** thunkPtr);
-    BuiltinThunk* lookupBuiltin(void* pc);
-};
+void
+ReleaseBuiltinThunks();
 
 } // namespace wasm
 } // namespace js
 
-#endif // wasm_runtime_h
+#endif // wasm_builtins_h
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -90,36 +90,36 @@ AllocateCodeSegment(JSContext* cx, uint3
 
     cx->zone()->updateJitCodeMallocBytes(codeLength);
 
     wasmCodeAllocations++;
     return (uint8_t*)p;
 }
 
 static bool
-StaticallyLink(JSContext* cx, CodeSegment& cs, const LinkData& linkData)
+StaticallyLink(CodeSegment& cs, const LinkData& linkData)
 {
     for (LinkData::InternalLink link : linkData.internalLinks) {
         uint8_t* patchAt = cs.base() + link.patchAtOffset;
         void* target = cs.base() + link.targetOffset;
         if (link.isRawPointerPatch())
             *(void**)(patchAt) = target;
         else
             Assembler::PatchInstructionImmediate(patchAt, PatchedImmPtr(target));
     }
 
+    if (!EnsureBuiltinThunksInitialized())
+        return false;
+
     for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
         const Uint32Vector& offsets = linkData.symbolicLinks[imm];
         if (offsets.empty())
             continue;
 
-        void* target = nullptr;
-        if (!cx->runtime()->wasm().getBuiltinThunk(cx, imm, &target))
-            return false;
-
+        void* target = SymbolicAddressTarget(imm);
         for (uint32_t offset : offsets) {
             uint8_t* patchAt = cs.base() + offset;
             Assembler::PatchDataWithValueCheck(CodeLocationLabel(patchAt),
                                                PatchedImmPtr(target),
                                                PatchedImmPtr((void*)-1));
         }
     }
 
@@ -206,17 +206,17 @@ CodeSegment::create(JSContext* cx,
     cs->unalignedAccessCode_ = codeBase + linkData.unalignedAccessOffset;
 
     {
         JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
         AutoFlushICache afc("CodeSegment::create");
         AutoFlushICache::setRange(uintptr_t(codeBase), cs->length());
 
         memcpy(codeBase, bytecode.begin(), bytecode.length());
-        if (!StaticallyLink(cx, *cs, linkData))
+        if (!StaticallyLink(*cs, linkData))
             return nullptr;
     }
 
     // Reprotect the whole region to avoid having separate RW and RX mappings.
     uint32_t size = JS_ROUNDUP(cs->length(), ExecutableCodePageSize);
     if (!ExecutableAllocator::makeExecutable(codeBase, size)) {
         ReportOutOfMemory(cx);
         return nullptr;
@@ -548,25 +548,18 @@ Code::lookupCallSite(void* returnAddress
         return nullptr;
 
     return &metadata_->callSites[match];
 }
 
 const CodeRange*
 Code::lookupRange(void* pc) const
 {
-    CodeRange::PC target((uint8_t*)pc - segment_->base());
-    size_t lowerBound = 0;
-    size_t upperBound = metadata_->codeRanges.length();
-
-    size_t match;
-    if (!BinarySearch(metadata_->codeRanges, lowerBound, upperBound, target, &match))
-        return nullptr;
-
-    return &metadata_->codeRanges[match];
+    CodeRange::OffsetInCode target((uint8_t*)pc - segment_->base());
+    return LookupInSorted(metadata_->codeRanges, target);
 }
 
 struct MemoryAccessOffset
 {
     const MemoryAccessVector& accesses;
     explicit MemoryAccessOffset(const MemoryAccessVector& accesses) : accesses(accesses) {}
     uintptr_t operator[](size_t index) const {
         return accesses[index].insnOffset();
--- a/js/src/wasm/WasmFrameIterator.cpp
+++ b/js/src/wasm/WasmFrameIterator.cpp
@@ -536,17 +536,17 @@ ProfilingFrameIterator::initFromExitFP()
       case CodeRange::Function:
         fp = CallerFPFromFP(fp);
         callerPC_ = ReturnAddressFromFP(fp);
         callerFP_ = CallerFPFromFP(fp);
         AssertMatchesCallSite(*activation_, callerPC_, callerFP_);
         break;
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::BuiltinNativeExit:
+      case CodeRange::BuiltinThunk:
       case CodeRange::TrapExit:
       case CodeRange::DebugTrap:
       case CodeRange::Inline:
       case CodeRange::Throw:
       case CodeRange::Interrupt:
       case CodeRange::FarJumpIsland:
         MOZ_CRASH("Unexpected CodeRange kind");
     }
@@ -572,48 +572,41 @@ ProfilingFrameIterator::ProfilingFrameIt
 {
     // In the case of ImportJitExit, the fp register may be temporarily
     // clobbered on return from Ion so always use activation.fp when it is set.
     if (activation.exitFP()) {
         initFromExitFP();
         return;
     }
 
-    code_ = activation_->compartment()->wasm.lookupCode(state.pc);
+    uint8_t* fp = (uint8_t*)state.fp;
+    uint8_t* pc = (uint8_t*)state.pc;
+    void** sp = (void**)state.sp;
 
-    const CodeRange* codeRange = nullptr;
-    uint8_t* codeBase = nullptr;
-    if (!code_) {
-        // Optimized builtin exits (see MaybeGetMatchingBuiltin in
-        // WasmInstance.cpp) are outside module's code.
-        AutoNoteSingleThreadedRegion anstr;
-        if (BuiltinThunk* thunk = activation_->cx()->runtime()->wasm().lookupBuiltin(state.pc)) {
-            codeRange = &thunk->codeRange;
-            codeBase = (uint8_t*) thunk->base;
-        } else {
-            // If pc isn't in any wasm code or builtin exit, we must be between
-            // pushing the WasmActivation and entering wasm code.
-            MOZ_ASSERT(done());
-            return;
-        }
-    } else {
-        codeRange = code_->lookupRange(state.pc);
+    // Get the CodeRange describing pc and the base address to which the
+    // CodeRange is relative. If the pc is not in a wasm module or a builtin
+    // thunk, then execution must be entering from or leaving to the C++ caller
+    // that pushed the WasmActivation.
+    const CodeRange* codeRange;
+    uint8_t* codeBase;
+    code_ = activation_->compartment()->wasm.lookupCode(pc);
+    if (code_) {
+        codeRange = code_->lookupRange(pc);
         codeBase = code_->segment().base();
+    } else if (!LookupBuiltinThunk(pc, &codeRange, &codeBase)) {
+        MOZ_ASSERT(done());
+        return;
     }
 
     // When the pc is inside the prologue/epilogue, the innermost call's Frame
     // is not complete and thus fp points to the second-to-innermost call's
     // Frame. Since fp can only tell you about its caller, naively unwinding
     // while pc is in the prologue/epilogue would skip the second-to-innermost
     // call. To avoid this problem, we use the static structure of the code in
     // the prologue and epilogue to do the Right Thing.
-    uint8_t* fp = (uint8_t*)state.fp;
-    uint8_t* pc = (uint8_t*)state.pc;
-    void** sp = (void**)state.sp;
-
     uint32_t offsetInCode = pc - codeBase;
     MOZ_ASSERT(offsetInCode >= codeRange->begin());
     MOZ_ASSERT(offsetInCode < codeRange->end());
 
     // Compute the offset of the pc from the (normal) entry of the code range.
     // The stack state of the pc for the entire table-entry is equivalent to
     // that of the first pc of the normal-entry. Thus, we can simplify the below
     // case analysis by redirecting all pc-in-table-entry cases to the
@@ -628,17 +621,17 @@ ProfilingFrameIterator::ProfilingFrameIt
         offsetFromEntry = offsetInCode - codeRange->begin();
     }
 
     switch (codeRange->kind()) {
       case CodeRange::Function:
       case CodeRange::FarJumpIsland:
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::BuiltinNativeExit:
+      case CodeRange::BuiltinThunk:
       case CodeRange::TrapExit:
 #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
         if (offsetFromEntry == BeforePushRetAddr || codeRange->isThunk()) {
             // The return address is still in lr and fp holds the caller's fp.
             callerPC_ = state.lr;
             callerFP_ = fp;
             AssertMatchesCallSite(*activation_, callerPC_, callerFP_);
         } else
@@ -738,17 +731,17 @@ ProfilingFrameIterator::operator++()
     switch (codeRange_->kind()) {
       case CodeRange::Entry:
         MOZ_ASSERT(callerFP_ == nullptr);
         callerPC_ = nullptr;
         break;
       case CodeRange::Function:
       case CodeRange::ImportJitExit:
       case CodeRange::ImportInterpExit:
-      case CodeRange::BuiltinNativeExit:
+      case CodeRange::BuiltinThunk:
       case CodeRange::TrapExit:
       case CodeRange::DebugTrap:
       case CodeRange::Inline:
       case CodeRange::FarJumpIsland:
         stackAddress_ = callerFP_;
         callerPC_ = ReturnAddressFromFP(callerFP_);
         AssertMatchesCallSite(*activation_, callerPC_, CallerFPFromFP(callerFP_));
         callerFP_ = CallerFPFromFP(callerFP_);
@@ -903,17 +896,17 @@ ProfilingFrameIterator::label() const
       case ExitReason::Fixed::DebugTrap:
         return debugTrapDescription;
     }
 
     switch (codeRange_->kind()) {
       case CodeRange::Function:          return code_->profilingLabel(codeRange_->funcIndex());
       case CodeRange::Entry:             return "entry trampoline (in wasm)";
       case CodeRange::ImportJitExit:     return importJitDescription;
-      case CodeRange::BuiltinNativeExit: return builtinNativeDescription;
+      case CodeRange::BuiltinThunk:      return builtinNativeDescription;
       case CodeRange::ImportInterpExit:  return importInterpDescription;
       case CodeRange::TrapExit:          return trapDescription;
       case CodeRange::DebugTrap:         return debugTrapDescription;
       case CodeRange::Inline:            return "inline stub (in wasm)";
       case CodeRange::FarJumpIsland:     return "interstitial (in wasm)";
       case CodeRange::Throw:             MOZ_FALLTHROUGH;
       case CodeRange::Interrupt:         MOZ_CRASH("does not have a frame");
     }
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -16,17 +16,17 @@
  * limitations under the License.
  */
 
 #include "wasm/WasmInstance.h"
 
 #include "jit/BaselineJIT.h"
 #include "jit/InlinableNatives.h"
 #include "jit/JitCommon.h"
-
+#include "wasm/WasmBuiltins.h"
 #include "wasm/WasmModule.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/ArrayBufferObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
@@ -312,138 +312,16 @@ Instance::growMemory_i32(Instance* insta
 /* static */ uint32_t
 Instance::currentMemory_i32(Instance* instance)
 {
     uint32_t byteLength = instance->memoryLength();
     MOZ_ASSERT(byteLength % wasm::PageSize == 0);
     return byteLength / wasm::PageSize;
 }
 
-// asm.js has the ability to call directly into Math builtins, which wasm can't
-// do. Instead, wasm code generators have to pass the builtins as function
-// imports, resulting in slow import calls.
-//
-// However, we can optimize this by detecting that an import is just a JSNative
-// builtin and have wasm call straight to the builtin's C++ code, since the
-// ABIs perfectly match at the call site.
-//
-// Even though we could call into float32 variants of the math functions, we
-// do not do it, so as not to change the results.
-
-#define FOREACH_UNCACHED_MATH_BUILTIN(_) \
-    _(math_sin, MathSin)           \
-    _(math_tan, MathTan)           \
-    _(math_cos, MathCos)           \
-    _(math_exp, MathExp)           \
-    _(math_log, MathLog)           \
-    _(math_asin, MathASin)         \
-    _(math_atan, MathATan)         \
-    _(math_acos, MathACos)         \
-    _(math_log10, MathLog10)       \
-    _(math_log2, MathLog2)         \
-    _(math_log1p, MathLog1P)       \
-    _(math_expm1, MathExpM1)       \
-    _(math_sinh, MathSinH)         \
-    _(math_tanh, MathTanH)         \
-    _(math_cosh, MathCosH)         \
-    _(math_asinh, MathASinH)       \
-    _(math_atanh, MathATanH)       \
-    _(math_acosh, MathACosH)       \
-    _(math_sign, MathSign)         \
-    _(math_trunc, MathTrunc)       \
-    _(math_cbrt, MathCbrt)
-
-#define UNARY_FLOAT_WRAPPER(func)                 \
-    float func##_f32(float x) {                   \
-        return float(func(double(x)));            \
-    }
-
-#define BINARY_FLOAT_WRAPPER(func)                \
-    float func##_f32(float x, float y) {          \
-        return float(func(double(x), double(y))); \
-    }
-
-#define DEFINE_FLOAT_WRAPPER(name, _) UNARY_FLOAT_WRAPPER(name##_uncached)
-FOREACH_UNCACHED_MATH_BUILTIN(DEFINE_FLOAT_WRAPPER)
-
-BINARY_FLOAT_WRAPPER(ecmaAtan2)
-BINARY_FLOAT_WRAPPER(ecmaHypot)
-BINARY_FLOAT_WRAPPER(ecmaPow)
-
-#undef DEFINE_FLOAT_WRAPPER
-#undef BINARY_FLOAT_WRAPPER
-#undef UNARY_FLOAT_WRAPPER
-
-static void*
-IsMatchingBuiltin(HandleFunction f, const Sig& sig)
-{
-    if (!f->isNative() || !f->jitInfo() || f->jitInfo()->type() != JSJitInfo::InlinableNative)
-        return nullptr;
-
-    ExprType ret = sig.ret();
-    const ValTypeVector& args = sig.args();
-
-#define UNARY_BUILTIN(double_func, float_func)                 \
-        if (args.length() != 1)                                \
-            break;                                             \
-        if (args[0] == ValType::F64 && ret == ExprType::F64)   \
-            return JS_FUNC_TO_DATA_PTR(void*, double_func);    \
-        if (args[0] == ValType::F32 && ret == ExprType::F32)   \
-            return JS_FUNC_TO_DATA_PTR(void*, float_func);     \
-        break;
-
-#define BINARY_BUILTIN(double_func, float_func)                                         \
-        if (args.length() != 2)                                                         \
-            break;                                                                      \
-        if (args[0] == ValType::F64 && args[1] == ValType::F64 && ret == ExprType::F64) \
-            return JS_FUNC_TO_DATA_PTR(void*, double_func);                             \
-        if (args[0] == ValType::F32 && args[1] == ValType::F32 && ret == ExprType::F32) \
-            return JS_FUNC_TO_DATA_PTR(void*, float_func);                              \
-        break;
-
-    switch (f->jitInfo()->inlinableNative) {
-#define MAKE_CASE(funcName, inlinableNative)                        \
-      case InlinableNative::inlinableNative:                        \
-        UNARY_BUILTIN(funcName##_uncached, funcName##_uncached_f32)
-
-      FOREACH_UNCACHED_MATH_BUILTIN(MAKE_CASE)
-
-      case InlinableNative::MathATan2:
-        BINARY_BUILTIN(ecmaAtan2, ecmaAtan2_f32)
-      case InlinableNative::MathHypot:
-        BINARY_BUILTIN(ecmaHypot, ecmaHypot_f32)
-      case InlinableNative::MathPow:
-        BINARY_BUILTIN(ecmaPow, ecmaPow_f32)
-
-      default:
-        break;
-    }
-
-#undef MAKE_CASE
-#undef UNARY_BUILTIN
-#undef BINARY_BUILTIN
-#undef FOREACH_UNCACHED_MATH_BUILTIN
-
-    return nullptr;
-}
-
-static void*
-MaybeGetMatchingBuiltin(JSContext* cx, HandleFunction f, const Sig& sig)
-{
-    void* funcPtr = IsMatchingBuiltin(f, sig);
-    if (!funcPtr)
-        return nullptr;
-
-    void* thunkPtr = nullptr;
-    if (!cx->runtime()->wasm().getBuiltinThunk(cx, funcPtr, sig, &thunkPtr))
-        return nullptr;
-
-    return thunkPtr;
-}
-
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    UniqueCode code,
                    UniqueGlobalSegment globals,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
                    const ValVector& globalImports)
@@ -474,17 +352,17 @@ Instance::Instance(JSContext* cx,
         if (!isAsmJS() && IsExportedWasmFunction(f)) {
             WasmInstanceObject* calleeInstanceObj = ExportedFunctionToInstanceObject(f);
             const CodeRange& codeRange = calleeInstanceObj->getExportedFunctionCodeRange(f);
             Instance& calleeInstance = calleeInstanceObj->instance();
             import.tls = calleeInstance.tlsData();
             import.code = calleeInstance.codeSegment().base() + codeRange.funcNormalEntry();
             import.baselineScript = nullptr;
             import.obj = calleeInstanceObj;
-        } else if (void* thunk = MaybeGetMatchingBuiltin(cx, f, fi.sig())) {
+        } else if (void* thunk = MaybeGetBuiltinThunk(f, fi.sig(), cx)) {
             import.tls = tlsData();
             import.code = thunk;
             import.baselineScript = nullptr;
             import.obj = f;
         } else {
             import.tls = tlsData();
             import.code = codeBase() + fi.interpExitCodeOffset();
             import.baselineScript = nullptr;
--- a/js/src/wasm/WasmSignalHandlers.cpp
+++ b/js/src/wasm/WasmSignalHandlers.cpp
@@ -20,16 +20,17 @@
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 
 #include "jit/AtomicOperations.h"
 #include "jit/Disassembler.h"
 #include "vm/Runtime.h"
+#include "wasm/WasmBuiltins.h"
 #include "wasm/WasmInstance.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using JS::GenericNaN;
 using mozilla::DebugOnly;
@@ -1538,23 +1539,27 @@ js::InterruptRunningJitCode(JSContext* c
     // halts the thread and callers our JitInterruptHandler (which has already
     // been installed by EnsureSignalHandlersInstalled).
     pthread_t thread = (pthread_t)cx->threadNative();
     pthread_kill(thread, sInterruptSignal);
 #endif
 }
 
 MOZ_COLD bool
-js::wasm::IsPCInWasmCode(void *pc)
+js::wasm::IsPCInWasmCode(void* pc)
 {
     JSContext* cx = TlsContext.get();
     if (!cx)
         return false;
 
     MOZ_RELEASE_ASSERT(!cx->handlingSegFault);
 
     WasmActivation* activation = cx->wasmActivationStack();
     if (!activation)
         return false;
 
-    return !!activation->compartment()->wasm.lookupCode(pc) ||
-           !!activation->cx()->runtime()->wasm().lookupBuiltin(pc);
+    if (activation->compartment()->wasm.lookupCode(pc))
+        return true;
+
+    const CodeRange* codeRange;
+    uint8_t* codeBase;
+    return LookupBuiltinThunk(pc, &codeRange, &codeBase);
 }
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -894,18 +894,18 @@ struct ABIFunctionArgs
         uint32_t abi = uint32_t(abiType);
         while (i--)
             abi = abi >> ArgType_Shift;
         return ToMIRType(ABIArgType(abi));
     }
 };
 
 CallableOffsets
-wasm::GenerateBuiltinNativeExit(MacroAssembler& masm, ABIFunctionType abiType,
-                                ExitReason exitReason, void* func)
+wasm::GenerateBuiltinThunk(MacroAssembler& masm, ABIFunctionType abiType, ExitReason exitReason,
+                           void* funcPtr)
 {
     masm.setFramePushed(0);
 
     ABIFunctionArgs args(abiType);
     uint32_t framePushed = StackDecrementForCall(masm, ABIStackAlignment, args);
 
     CallableOffsets offsets;
     GenerateExitPrologue(masm, framePushed, exitReason, &offsets);
@@ -931,17 +931,17 @@ wasm::GenerateBuiltinNativeExit(MacroAss
         }
 
         Address src(masm.getStackPointer(), offsetToCallerStackArgs + i->offsetFromArgBase());
         Address dst(masm.getStackPointer(), i->offsetFromArgBase());
         StackCopy(masm, i.mirType(), scratch, src, dst);
     }
 
     AssertStackAlignment(masm, ABIStackAlignment);
-    masm.call(ImmPtr(func, ImmPtr::NoCheckToken()));
+    masm.call(ImmPtr(funcPtr, ImmPtr::NoCheckToken()));
 
 #if defined(JS_CODEGEN_X86)
     // x86 passes the return value on the x87 FP stack.
     Operand op(esp, 0);
     MIRType retType = ToMIRType(ABIArgType(abiType & ArgType_Mask));
     if (retType == MIRType::Float32) {
         masm.fstp32(op);
         masm.loadFloat32(op, ReturnFloat32Reg);
--- a/js/src/wasm/WasmStubs.h
+++ b/js/src/wasm/WasmStubs.h
@@ -44,18 +44,18 @@ GenerateImportFunction(jit::MacroAssembl
 extern CallableOffsets
 GenerateImportInterpExit(jit::MacroAssembler& masm, const FuncImport& fi, uint32_t funcImportIndex,
                          jit::Label* throwLabel);
 
 extern CallableOffsets
 GenerateImportJitExit(jit::MacroAssembler& masm, const FuncImport& fi, jit::Label* throwLabel);
 
 extern CallableOffsets
-GenerateBuiltinNativeExit(jit::MacroAssembler& masm, jit::ABIFunctionType abiType,
-                          ExitReason exitReason, void* func);
+GenerateBuiltinThunk(jit::MacroAssembler& masm, jit::ABIFunctionType abiType, ExitReason exitReason,
+                     void* func);
 
 extern CallableOffsets
 GenerateTrapExit(jit::MacroAssembler& masm, Trap trap, jit::Label* throwLabel);
 
 extern Offsets
 GenerateOutOfBoundsExit(jit::MacroAssembler& masm, jit::Label* throwLabel);
 
 extern Offsets
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -685,18 +685,18 @@ CodeRange::CodeRange(Kind kind, Offsets 
       case FarJumpIsland:
       case Inline:
       case Throw:
       case Interrupt:
         break;
       case Function:
       case TrapExit:
       case ImportJitExit:
-      case BuiltinNativeExit:
       case ImportInterpExit:
+      case BuiltinThunk:
         MOZ_CRASH("should use more specific constructor");
     }
 #endif
 }
 
 CodeRange::CodeRange(Kind kind, CallableOffsets offsets)
   : begin_(offsets.begin),
     ret_(offsets.ret),
@@ -707,18 +707,18 @@ CodeRange::CodeRange(Kind kind, Callable
     kind_(kind)
 {
     MOZ_ASSERT(begin_ < ret_);
     MOZ_ASSERT(ret_ < end_);
 #ifdef DEBUG
     switch (kind_) {
       case TrapExit:
       case ImportJitExit:
-      case BuiltinNativeExit:
       case ImportInterpExit:
+      case BuiltinThunk:
         break;
       case Entry:
       case DebugTrap:
       case FarJumpIsland:
       case Inline:
       case Throw:
       case Interrupt:
       case Function:
@@ -735,8 +735,21 @@ CodeRange::CodeRange(uint32_t funcIndex,
     funcLineOrBytecode_(funcLineOrBytecode),
     funcBeginToNormalEntry_(offsets.normalEntry - begin_),
     kind_(Function)
 {
     MOZ_ASSERT(begin_ < ret_);
     MOZ_ASSERT(ret_ < end_);
     MOZ_ASSERT(offsets.normalEntry - begin_ <= UINT8_MAX);
 }
+
+const CodeRange*
+wasm::LookupInSorted(const CodeRangeVector& codeRanges, CodeRange::OffsetInCode target)
+{
+    size_t lowerBound = 0;
+    size_t upperBound = codeRanges.length();
+
+    size_t match;
+    if (!BinarySearch(codeRanges, lowerBound, upperBound, target, &match))
+        return nullptr;
+
+    return &codeRanges[match];
+}
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -826,17 +826,17 @@ struct FuncOffsets : CallableOffsets
 class CodeRange
 {
   public:
     enum Kind {
         Function,          // function definition
         Entry,             // calls into wasm from C++
         ImportJitExit,     // fast-path calling from wasm into JIT code
         ImportInterpExit,  // slow-path calling from wasm into C++ interp
-        BuiltinNativeExit, // fast-path calling from wasm into a C++ native
+        BuiltinThunk,      // fast-path calling from wasm into a C++ native
         TrapExit,          // calls C++ to report and jumps to throw stub
         DebugTrap,         // calls C++ to handle debug event
         FarJumpIsland,     // inserted to connect otherwise out-of-range insns
         Inline,            // stub that is jumped-to within prologue/epilogue
         Throw,             // special stack-unwinding stub
         Interrupt          // stub executes asynchronously to interrupt wasm
     };
 
@@ -870,17 +870,17 @@ class CodeRange
     Kind kind() const {
         return kind_;
     }
 
     bool isFunction() const {
         return kind() == Function;
     }
     bool isImportExit() const {
-        return kind() == ImportJitExit || kind() == ImportInterpExit || kind() == BuiltinNativeExit;
+        return kind() == ImportJitExit || kind() == ImportInterpExit || kind() == BuiltinThunk;
     }
     bool isTrapExit() const {
         return kind() == TrapExit;
     }
     bool isInline() const {
         return kind() == Inline;
     }
     bool isThunk() const {
@@ -912,32 +912,36 @@ class CodeRange
         MOZ_ASSERT(isFunction());
         return funcIndex_;
     }
     uint32_t funcLineOrBytecode() const {
         MOZ_ASSERT(isFunction());
         return funcLineOrBytecode_;
     }
 
-    // A sorted array of CodeRanges can be looked up via BinarySearch and PC.
+    // A sorted array of CodeRanges can be looked up via BinarySearch and
+    // OffsetInCode.
 
-    struct PC {
+    struct OffsetInCode {
         size_t offset;
-        explicit PC(size_t offset) : offset(offset) {}
+        explicit OffsetInCode(size_t offset) : offset(offset) {}
         bool operator==(const CodeRange& rhs) const {
             return offset >= rhs.begin() && offset < rhs.end();
         }
         bool operator<(const CodeRange& rhs) const {
             return offset < rhs.begin();
         }
     };
 };
 
 WASM_DECLARE_POD_VECTOR(CodeRange, CodeRangeVector)
 
+extern const CodeRange*
+LookupInSorted(const CodeRangeVector& codeRanges, CodeRange::OffsetInCode target);
+
 // A wasm::Trap represents a wasm-defined trap that can occur during execution
 // which triggers a WebAssembly.RuntimeError. Generated code may jump to a Trap
 // symbolically, passing the bytecode offset to report as the trap offset. The
 // generated jump will be bound to a tiny stub which fills the offset and
 // then jumps to a per-Trap shared stub at the end of the module.
 
 enum class Trap
 {