tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit d48a6a97232229679e7b2f70f3521016a55e444a
parent 0e17b2f9c4321f55c4299bc18515e2c27616d2b2
Author: Ryan Hunt <rhunt@eqrion.net>
Date:   Tue, 18 Nov 2025 16:57:30 +0000

Bug 1990931 - wasm: Move stack limit from wasm::Instance to wasm::Context. r=yury

This allows us to update the stack limit without acquiring any mutex
or iterating over all the instances on a thread.

Differential Revision: https://phabricator.services.mozilla.com/D271459

Diffstat:
Mjs/src/jit/MacroAssembler.cpp | 19++++++++++++-------
Mjs/src/vm/JSContext.cpp | 15++++-----------
Mjs/src/vm/JSContext.h | 1+
Mjs/src/wasm/WasmBCFrame.h | 10++++++----
Mjs/src/wasm/WasmBaselineCompile.cpp | 2+-
Ajs/src/wasm/WasmContext.cpp | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/wasm/WasmContext.h | 40++++++++++++++++++++++++++++++----------
Mjs/src/wasm/WasmFrameIter.cpp | 9++++++---
Mjs/src/wasm/WasmInstance.cpp | 12------------
Mjs/src/wasm/WasmInstance.h | 16----------------
Mjs/src/wasm/WasmPI.cpp | 11++++++++---
Mjs/src/wasm/WasmRealm.cpp | 19-------------------
Mjs/src/wasm/WasmRealm.h | 6------
Mjs/src/wasm/moz.build | 1+
14 files changed, 136 insertions(+), 92 deletions(-)

diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp @@ -5836,16 +5836,20 @@ void MacroAssembler::wasmTrap(wasm::Trap trap, } uint32_t MacroAssembler::wasmReserveStackChecked(uint32_t amount, Label* fail) { + Register scratch1 = ABINonArgReg0; + Register scratch2 = ABINonArgReg1; + loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), scratch2); + if (amount > MAX_UNCHECKED_LEAF_FRAME_SIZE) { // The frame is large. Don't bump sp until after the stack limit check so // that the trap handler isn't called with a wild sp. - Register scratch = ABINonArgReg0; - moveStackPtrTo(scratch); - branchPtr(Assembler::Below, scratch, Imm32(amount), fail); - subPtr(Imm32(amount), scratch); + moveStackPtrTo(scratch1); + branchPtr(Assembler::Below, scratch1, Imm32(amount), fail); + subPtr(Imm32(amount), scratch1); branchPtr(Assembler::AboveOrEqual, - Address(InstanceReg, wasm::Instance::offsetOfStackLimit()), - scratch, fail); + Address(scratch2, JSContext::offsetOfWasm() + + wasm::Context::offsetOfStackLimit()), + scratch1, fail); reserveStack(amount); // The stack amount was reserved after branching to the fail label. return 0; @@ -5853,7 +5857,8 @@ uint32_t MacroAssembler::wasmReserveStackChecked(uint32_t amount, Label* fail) { reserveStack(amount); branchStackPtrRhs(Assembler::AboveOrEqual, - Address(InstanceReg, wasm::Instance::offsetOfStackLimit()), + Address(scratch2, JSContext::offsetOfWasm() + + wasm::Context::offsetOfStackLimit()), fail); // The stack amount was reserved before branching to the fail label. return amount; diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp @@ -1308,16 +1308,6 @@ JSContext::JSContext(JSRuntime* runtime, const JS::ContextOptions& options) } } -#ifdef ENABLE_WASM_JSPI -bool js::IsSuspendableStackActive(JSContext* cx) { - return cx->wasm().suspendableStackLimit != JS::NativeStackLimitMin; -} - -JS::NativeStackLimit js::GetSuspendableStackLimit(JSContext* cx) { - return cx->wasm().suspendableStackLimit; -} -#endif - JSContext::~JSContext() { #ifdef DEBUG // Clear the initialized_ first, so that ProtectedData checks will allow us to @@ -1623,7 +1613,10 @@ void JSContext::resetJitStackLimit() { jitStackLimitNoInterrupt = jitStackLimit; } -void JSContext::initJitStackLimit() { resetJitStackLimit(); } +void JSContext::initJitStackLimit() { + resetJitStackLimit(); + wasm_.initStackLimit(this); +} JSScript* JSContext::currentScript(jsbytecode** ppc, AllowCrossRealm allowCrossRealm) { diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h @@ -634,6 +634,7 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext, public: js::wasm::Context& wasm() { return wasm_; } + static constexpr size_t offsetOfWasm() { return offsetof(JSContext, wasm_); } /* Temporary arena pool used while compiling and decompiling. */ static const size_t TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 4 * 1024; diff --git a/js/src/wasm/WasmBCFrame.h b/js/src/wasm/WasmBCFrame.h @@ -539,11 +539,13 @@ class BaseStackFrame final : public BaseStackFrameAllocator { // Note the platform scratch register may be used by branchPtr(), so // generally tmp must be something else. - void checkStack(Register tmp, Label* stackOverflowTrap) { - stackAddOffset_ = masm.sub32FromStackPtrWithPatch(tmp); + void checkStack(Register tmp1, Register tmp2, Label* stackOverflowTrap) { + masm.loadPtr(Address(InstanceReg, wasm::Instance::offsetOfCx()), tmp2); + stackAddOffset_ = masm.sub32FromStackPtrWithPatch(tmp1); masm.branchPtr(Assembler::AboveOrEqual, - Address(InstanceReg, wasm::Instance::offsetOfStackLimit()), - tmp, stackOverflowTrap); + Address(tmp2, JSContext::offsetOfWasm() + + wasm::Context::offsetOfStackLimit()), + tmp1, stackOverflowTrap); } void patchCheckStack() { diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp @@ -589,7 +589,7 @@ bool BaseCompiler::beginFunction() { if (!oolStackOverflowTrap) { return false; } - fr.checkStack(ABINonArgReg0, oolStackOverflowTrap->entry()); + fr.checkStack(ABINonArgReg0, ABINonArgReg1, oolStackOverflowTrap->entry()); OutOfLineCode* oolInterruptTrap = addOutOfLineCode( new (alloc_) OutOfLineResumableTrap(Trap::CheckInterrupt, trapSiteDesc(), diff --git a/js/src/wasm/WasmContext.cpp b/js/src/wasm/WasmContext.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * + * Copyright 2025 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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/WasmContext.h" + +#include "js/friend/StackLimits.h" +#include "vm/JSContext.h" + +using namespace js::wasm; + +Context::Context() + : triedToInstallSignalHandlers(false), + haveSignalHandlers(false), + stackLimit(JS::NativeStackLimitMin) +#ifdef ENABLE_WASM_JSPI + , + onSuspendableStack(0), + suspendableStacksCount(0) +#endif +{ +} + +void Context::initStackLimit(JSContext* cx) { + // The wasm stack limit is the same as the jit stack limit. We also don't + // use the stack limit for triggering interrupts. + stackLimit = cx->jitStackLimitNoInterrupt; +} + +#ifdef ENABLE_WASM_JSPI +void Context::enterSuspendableStack(JS::NativeStackLimit newStackLimit) { + MOZ_ASSERT(onSuspendableStack == 0); + onSuspendableStack = 1; + stackLimit = newStackLimit; +} + +void Context::leaveSuspendableStack(JSContext* cx) { + MOZ_ASSERT(onSuspendableStack != 0); + onSuspendableStack = 0; + initStackLimit(cx); +} +#endif + +#ifdef ENABLE_WASM_JSPI +bool js::IsSuspendableStackActive(JSContext* cx) { + return cx->wasm().onSuspendableStack != 0; +} + +JS::NativeStackLimit js::GetSuspendableStackLimit(JSContext* cx) { + MOZ_ASSERT(IsSuspendableStackActive(cx)); + return cx->wasm().stackLimit; +} +#endif diff --git a/js/src/wasm/WasmContext.h b/js/src/wasm/WasmContext.h @@ -19,7 +19,13 @@ #ifndef wasm_context_h #define wasm_context_h -#include "mozilla/DoublyLinkedList.h" +#ifdef ENABLE_WASM_JSPI +# include "mozilla/DoublyLinkedList.h" + +# include "gc/Barrier.h" +#endif // ENABLE_WASM_JSPI + +#include "js/NativeStackLimits.h" namespace js::wasm { @@ -51,24 +57,38 @@ class SuspenderContext { class Context { public: - Context() - : triedToInstallSignalHandlers(false), - haveSignalHandlers(false) + Context(); + + static constexpr size_t offsetOfStackLimit() { + return offsetof(Context, stackLimit); + } #ifdef ENABLE_WASM_JSPI - , - suspendableStackLimit(JS::NativeStackLimitMin), - suspendableStacksCount(0) -#endif - { + static constexpr size_t offsetOfOnSuspendableStack() { + return offsetof(Context, onSuspendableStack); } +#endif + + void initStackLimit(JSContext* cx); + +#ifdef ENABLE_WASM_JSPI + void enterSuspendableStack(JS::NativeStackLimit newStackLimit); + void leaveSuspendableStack(JSContext* cx); +#endif // Used by wasm::EnsureThreadSignalHandlers(cx) to install thread signal // handlers once per JSContext/thread. bool triedToInstallSignalHandlers; bool haveSignalHandlers; + // Like JSContext::jitStackLimit but used for wasm code. Wasm code doesn't + // use the stack limit for interrupts, but it does update it for stack + // switching. + JS::NativeStackLimit stackLimit; + #ifdef ENABLE_WASM_JSPI - JS::NativeStackLimit suspendableStackLimit; + // Boolean value set to true when the top wasm frame is currently executed on + // a suspendable stack. Aligned to int32_t to be used on JIT code. + int32_t onSuspendableStack; mozilla::Atomic<uint32_t> suspendableStacksCount; SuspenderContext promiseIntegration; #endif diff --git a/js/src/wasm/WasmFrameIter.cpp b/js/src/wasm/WasmFrameIter.cpp @@ -1000,15 +1000,18 @@ void wasm::GenerateJitExitPrologue(MacroAssembler& masm, { # if defined(JS_CODEGEN_ARM64) AutoForbidPoolsAndNops afp(&masm, - /* number of instructions in scope = */ 2); + /* number of instructions in scope = */ 3); # endif offsets->begin = masm.currentOffset(); Label fallback; masm.bind(&fallback, BufferOffset(fallbackOffset)); const Register scratch = ABINonArgReg0; - masm.load32(Address(InstanceReg, Instance::offsetOfOnSuspendableStack()), - scratch); + masm.loadPtr(Address(InstanceReg, Instance::offsetOfCx()), scratch); + masm.load32( + Address(scratch, JSContext::offsetOfWasm() + + wasm::Context::offsetOfOnSuspendableStack()), + scratch); masm.branchTest32(Assembler::NonZero, scratch, scratch, &fallback); } diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp @@ -2374,7 +2374,6 @@ Instance::Instance(JSContext* cx, Handle<WasmInstanceObject*> object, const SharedCode& code, SharedTableVector&& tables, UniqueDebugState maybeDebug) : realm_(cx->realm()), - onSuspendableStack_(false), allocSites_(nullptr), jsJitExceptionHandler_( cx->runtime()->jitRuntime()->getExceptionTail().value), @@ -2434,7 +2433,6 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports, cx_ = cx; valueBoxClass_ = AnyRef::valueBoxClass(); interrupt_ = false; - stackLimit_ = cx->stackLimitForJitCode(JS::StackForUntrustedScript); jumpTable_ = code_->tieringJumpTable(); debugFilter_ = nullptr; callRefMetrics_ = nullptr; @@ -2833,16 +2831,6 @@ bool Instance::isInterrupted() const { return interrupt_; } void Instance::resetInterrupt() { interrupt_ = false; } -void Instance::setTemporaryStackLimit(JS::NativeStackLimit limit) { - stackLimit_ = limit; - onSuspendableStack_ = true; -} - -void Instance::resetTemporaryStackLimit(JSContext* cx) { - stackLimit_ = cx->stackLimitForJitCode(JS::StackForUntrustedScript); - onSuspendableStack_ = false; -} - int32_t Instance::computeInitialHotnessCounter(uint32_t funcIndex, size_t codeSectionSize) { MOZ_ASSERT(code().mode() == CompileMode::LazyTiering); diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h @@ -119,16 +119,9 @@ class alignas(16) Instance { // The tag object of the pending exception. GCPtr<AnyRef> pendingExceptionTag_; - // Equal to cx->stackLimitForJitCode(JS::StackForUntrustedScript). - JS::NativeStackLimit stackLimit_; - // Set to 1 when wasm should call CheckForInterrupt. mozilla::Atomic<uint32_t, mozilla::Relaxed> interrupt_; - // Boolean value set to true when instance code is executed on a suspendable - // stack. Aligned to int32_t to be used on JIT code. - int32_t onSuspendableStack_; - // The address of the realm()->zone()->needsIncrementalBarrier(). This is // specific to this instance and not a process wide field, and so it cannot // be linked into code. @@ -314,15 +307,9 @@ class alignas(16) Instance { static constexpr size_t offsetOfPendingExceptionTag() { return offsetof(Instance, pendingExceptionTag_); } - static constexpr size_t offsetOfStackLimit() { - return offsetof(Instance, stackLimit_); - } static constexpr size_t offsetOfInterrupt() { return offsetof(Instance, interrupt_); } - static constexpr size_t offsetOfOnSuspendableStack() { - return offsetof(Instance, onSuspendableStack_); - } static constexpr size_t offsetOfAllocSites() { return offsetof(Instance, allocSites_); } @@ -395,9 +382,6 @@ class alignas(16) Instance { bool isInterrupted() const; void resetInterrupt(); - void setTemporaryStackLimit(JS::NativeStackLimit limit); - void resetTemporaryStackLimit(JSContext* cx); - void setAllocationMetadataBuilder( const js::AllocationMetadataBuilder* allocationMetadataBuilder) { allocationMetadataBuilder_ = allocationMetadataBuilder; diff --git a/js/src/wasm/WasmPI.cpp b/js/src/wasm/WasmPI.cpp @@ -52,6 +52,11 @@ #endif #ifdef XP_WIN +// We only need the `windows.h` header, but this file can get unified built +// with WasmSignalHandlers.cpp, which requires `winternal.h` to be included +// before the `windows.h` header, and so we must include it here for that case. +# include <winternl.h> // must include before util/WindowsWrapper.h's `#undef`s + # include "util/WindowsWrapper.h" #endif @@ -433,7 +438,7 @@ void SuspenderObject::trace(JSTracer* trc, JSObject* obj) { void SuspenderObject::setMoribund(JSContext* cx) { MOZ_ASSERT(state() == SuspenderState::Active); - ResetInstanceStackLimits(cx); + cx->wasm().leaveSuspendableStack(cx); # if defined(_WIN32) data()->restoreTIBStackFields(); # endif @@ -448,7 +453,7 @@ void SuspenderObject::setMoribund(JSContext* cx) { void SuspenderObject::setActive(JSContext* cx) { data()->setState(SuspenderState::Active); - UpdateInstanceStackLimitsForSuspendableStack(cx, getStackMemoryLimit()); + cx->wasm().enterSuspendableStack(getStackMemoryLimit()); # if defined(_WIN32) data()->updateTIBStackFields(); # endif @@ -456,7 +461,7 @@ void SuspenderObject::setActive(JSContext* cx) { void SuspenderObject::setSuspended(JSContext* cx) { data()->setState(SuspenderState::Suspended); - ResetInstanceStackLimits(cx); + cx->wasm().leaveSuspendableStack(cx); # if defined(_WIN32) data()->restoreTIBStackFields(); # endif diff --git a/js/src/wasm/WasmRealm.cpp b/js/src/wasm/WasmRealm.cpp @@ -135,22 +135,3 @@ void wasm::ResetInterruptState(JSContext* cx) { instance->resetInterrupt(); } } - -#ifdef ENABLE_WASM_JSPI -void wasm::UpdateInstanceStackLimitsForSuspendableStack( - JSContext* cx, JS::NativeStackLimit limit) { - auto runtimeInstances = cx->runtime()->wasmInstances.lock(); - cx->wasm().suspendableStackLimit = limit; - for (Instance* instance : runtimeInstances.get()) { - instance->setTemporaryStackLimit(limit); - } -} - -void wasm::ResetInstanceStackLimits(JSContext* cx) { - auto runtimeInstances = cx->runtime()->wasmInstances.lock(); - cx->wasm().suspendableStackLimit = JS::NativeStackLimitMin; - for (Instance* instance : runtimeInstances.get()) { - instance->resetTemporaryStackLimit(cx); - } -} -#endif // ENABLE_WASM_JSPI diff --git a/js/src/wasm/WasmRealm.h b/js/src/wasm/WasmRealm.h @@ -78,12 +78,6 @@ extern void InterruptRunningCode(JSContext* cx); void ResetInterruptState(JSContext* cx); -#ifdef ENABLE_WASM_JSPI -void UpdateInstanceStackLimitsForSuspendableStack(JSContext* cx, - JS::NativeStackLimit limit); -void ResetInstanceStackLimits(JSContext* cx); -#endif - } // namespace wasm } // namespace js diff --git a/js/src/wasm/moz.build b/js/src/wasm/moz.build @@ -24,6 +24,7 @@ UNIFIED_SOURCES += [ "WasmCode.cpp", "WasmCodegenTypes.cpp", "WasmCompile.cpp", + "WasmContext.cpp", "WasmDebug.cpp", "WasmDebugFrame.cpp", "WasmDump.cpp",