/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/wasm/stacks.h
340 строк
12 KB
Michaël Zasso
deps: update V8 to 14.6.202.33
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
f1e0b83
Код
Авторство
О чём код?
// Copyright 2021 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef V8_WASM_STACKS_H_ #define V8_WASM_STACKS_H_ #if !V8_ENABLE_WEBASSEMBLY #error This header should only be included if WebAssembly is enabled. #endif // !V8_ENABLE_WEBASSEMBLY #include <optional> #include "src/base/functional/function-ref.h" #include "src/common/globals.h" #include "src/flags/flags.h" #include "src/objects/visitors.h" #include "src/utils/allocation.h" #include "src/wasm/value-type.h" namespace v8 { class Isolate; namespace internal { class ThreadLocalTop; } } namespace v8::internal::wasm { class StackMemory; struct JumpBuffer { Address sp; Address fp; Address pc; void* stack_limit; StackMemory* parent = nullptr; bool is_on_central_stack; // We track the state below to prevent stack corruptions under the sandbox // security model. // Assuming that the external pointer to the jump buffer has been corrupted // and replaced with a different jump buffer, we check its state before // resuming it to verify that it is not Active or Retired. // The distinction between Suspended and Inactive may not be strictly // necessary since we currently always pass a single JS value in the return // register across stacks (either the Promise, the result of the Promise, or // the result of the export). However adding a state does not cost anything // and is more robust against potential changes in the calling conventions. enum StackState : int32_t { Active, // The (unique) active stack. The jump buffer is invalid in that // state. Suspended, // A stack suspended by WasmSuspend. Inactive, // A parent/ancestor of the active stack. In other words, a // stack that either called or resumed a suspendable stack. Retired // A finished stack. The jump buffer is invalid in that state. }; StackState state; }; class StackMemory { public: static std::unique_ptr<StackMemory> New() { return std::unique_ptr<StackMemory>(new StackMemory()); } // Returns a non-owning view of the central stack. This may be // the simulator's stack when running on the simulator. static StackMemory* GetCentralStackView(Isolate* isolate); ~StackMemory(); void* jslimit() const; Address base() const { Address memory_limit = active_segment_ ? active_segment_->base() : reinterpret_cast<Address>(limit_ + size_); #ifdef USE_SIMULATOR // To perform runtime calls with different signatures, some simulators // prepare a fixed number of arguments which is an upper bound of the actual // parameter count. The extra stack slots contain arbitrary data and are // never used, but with stack-switching this can happen close to the stack // start, so we need to reserve a safety gap to ensure that the addresses // are at least mapped to prevent a crash. constexpr int kStackBaseSafetyOffset = 20 * kSystemPointerSize; #else constexpr int kStackBaseSafetyOffset = 0; #endif return memory_limit - kStackBaseSafetyOffset; } void set_current_continuation(Tagged<WasmContinuationObject> cont) { current_cont_ = cont; } bool IsValidContinuation(Tagged<WasmContinuationObject> cont); JumpBuffer* jmpbuf() { return &jmpbuf_; } bool Contains(Address addr) { if (!owned_) { return reinterpret_cast<Address>(jslimit()) <= addr && addr < base(); } for (auto segment = first_segment_; segment; segment = segment->next_segment_) { if (reinterpret_cast<Address>(segment->limit_) <= addr && addr < segment->base()) { return true; } if (segment == active_segment_) break; } return false; } int id() { return id_; } bool IsActive() { return jmpbuf_.state == JumpBuffer::Active; } void set_index(size_t index) { index_ = index; } size_t index() { return index_; } size_t allocated_size() { size_t size = 0; auto segment = first_segment_; while (segment) { size += segment->size_; segment = segment->next_segment_; } return size; } void FillWith(uint8_t value) { auto segment = first_segment_; while (segment) { memset(segment->limit_, value, segment->size_); segment = segment->next_segment_; } } void Iterate(v8::internal::RootVisitor* v, Isolate* isolate, ThreadLocalTop* thread); Address old_fp() { return active_segment_->old_fp; } bool Grow(Address current_fp, size_t min_size); Address Shrink(); void ShrinkTo(Address stack_address); void Reset(); class StackSegment { public: Address base() const { return reinterpret_cast<Address>(limit_ + size_); } private: explicit StackSegment(size_t size); ~StackSegment(); uint8_t* limit_; size_t size_; // References to segments of segmented stack StackSegment* next_segment_ = nullptr; StackSegment* prev_segment_ = nullptr; Address old_fp = 0; friend class StackMemory; }; struct StackSwitchInfo { // Source FP and target SP of the frame that switched to the central stack. // The source FP is in the secondary stack, the target SP is in the central // stack. // The stack cannot be suspended while it is on the central stack, so there // can be at most one switch for a given stack. Address source_fp = kNullAddress; Address target_sp = kNullAddress; bool has_value() const { return source_fp != kNullAddress; } }; const StackSwitchInfo& stack_switch_info() const { return stack_switch_info_; } void set_stack_switch_info(Address fp, Address sp) { stack_switch_info_ = {fp, sp}; } void clear_stack_switch_info() { stack_switch_info_.source_fp = kNullAddress; } void set_func_ref(Tagged<WasmFuncRef> func_ref) { func_ref_ = func_ref; } static int func_ref_offset() { return OFFSET_OF(StackMemory, func_ref_); } static int JSCentralStackLimitMarginKB() { #if defined(DEBUG) || defined(V8_USE_ADDRESS_SANITIZER) return 80; #else return 40; #endif } static int JSGrowableStackLimitMarginKB() { if (!v8_flags.experimental_wasm_growable_stacks) { return JSCentralStackLimitMarginKB(); } // The limiting factor for this margin is the stack space used by outgoing // stack parameters in wasm. They can take up to 16KB (1000 simd // parameters, minus register parameters) and are not taken into account // by stack checks. // TODO(42204615): look into changing the stack check to take outgoing // stack parameters into account. static_assert(kMaxValueTypeSize == 16); static_assert(kV8MaxWasmFunctionParams == 1000); return 20; } friend class StackPool; constexpr static uint32_t stack_switch_source_fp_offset() { return OFFSET_OF(StackMemory, stack_switch_info_) + OFFSET_OF(StackMemory::StackSwitchInfo, source_fp); } constexpr static uint32_t stack_switch_target_sp_offset() { return OFFSET_OF(StackMemory, stack_switch_info_) + OFFSET_OF(StackMemory::StackSwitchInfo, target_sp); } constexpr static uint32_t jmpbuf_offset() { return OFFSET_OF(StackMemory, jmpbuf_); } constexpr static uint32_t current_continuation_offset() { return OFFSET_OF(StackMemory, current_cont_); } constexpr static uint32_t arg_buffer_offset() { return OFFSET_OF(StackMemory, arg_buffer_); } void set_arg_buffer(Address addr) { arg_buffer_ = addr; } void set_param_types(base::Vector<const CanonicalValueType> types) { param_types_ = types; } void bind_arguments(int count) { num_bound_args_ += count; DCHECK_LE(num_bound_args_, param_types_.size()); } void clear_bound_args() { param_types_ = {}; arg_buffer_ = kNullAddress; num_bound_args_ = 0; } Address central_stack_sp() const { return central_stack_sp_; } void set_central_stack_sp(Address sp) { central_stack_sp_ = sp; } bool has_frames() const { return !((jmpbuf_.state == JumpBuffer::Suspended && jmpbuf_.fp == kNullAddress) || jmpbuf_.state == JumpBuffer::Retired); } private: // This constructor allocates a new stack segment. StackMemory(); // Overload to represent a view of the libc stack. StackMemory(uint8_t* limit, size_t size); uint8_t* limit_; size_t size_; bool owned_; JumpBuffer jmpbuf_; // Stable ID. int id_; // Index of this stack in the global Isolate::wasm_stacks() vector. This // allows us to add and remove from the vector in constant time (see // return_switch()). size_t index_; // Top of the central stack when this stack becomes inactive. Address central_stack_sp_ = kNullAddress; StackSwitchInfo stack_switch_info_; StackSegment* first_segment_ = nullptr; StackSegment* active_segment_ = nullptr; // WasmFX specific fields below. Tagged<WasmContinuationObject> current_cont_ = {}; Tagged<WasmFuncRef> func_ref_ = {}; // Param type vector, to know which bound arguments are references and need to // be visited by the GC. The memory is owned by the type canonicalizer. base::Vector<const CanonicalValueType> param_types_; Address arg_buffer_ = kNullAddress; int num_bound_args_ = 0; // When adding fields here, also check if it needs to be cleared in // StackMemory::Reset() when the stack is moved to the stack pool after // retiring. }; constexpr int kStackSpOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, sp); constexpr int kStackFpOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, fp); constexpr int kStackPcOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, pc); constexpr int kStackLimitOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, stack_limit); constexpr int kStackParentOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, parent); constexpr int kStackStateOffset = wasm::StackMemory::jmpbuf_offset() + offsetof(JumpBuffer, state); // A pool of "finished" stacks, i.e. stacks whose last frame have returned and // whose memory can be reused for new suspendable computations. class StackPool { public: // Gets a stack from the free list if one exists, else allocates it. std::unique_ptr<StackMemory> GetOrAllocate(); // Adds a finished stack to the free list. void Add(std::unique_ptr<StackMemory> stack); // Decommit the stack memories and empty the freelist. void ReleaseFinishedStacks(); size_t Size() const; private: std::vector<std::unique_ptr<StackMemory>> freelist_; size_t size_ = 0; // If the next finished stack would move the total size above this limit, the // stack is freed instead of being added to the free list. static constexpr int kMaxSize = 4 * MB; }; using WasmFXArgBufferCallback = base::FunctionRef<void(size_t value_index, int offset)>; template <typename T> int IterateWasmFXArgBuffer(base::Vector<const T> types, WasmFXArgBufferCallback callback) { int offset = 0; // There can be an unknown number of bound arguments for a given cont target. // So we place the arguments from right to left in the buffer so that their // offsets only depend on the remaining unbound arguments. for (int i = static_cast<int>(types.size()) - 1; i >= 0; i--) { int param_size = types[i].value_kind_full_size(); offset = RoundUp(offset, param_size); callback(i, offset); offset += param_size; } return offset; } template <typename T> std::pair<int, int> GetBufferSizeAndAlignmentFor(base::Vector<const T> types) { int alignment = kSystemPointerSize; int size = IterateWasmFXArgBuffer(types, [&](size_t index, int offset) { alignment = std::max(alignment, types[index].value_kind_full_size()); }); return {size, alignment}; } } // namespace v8::internal::wasm #endif // V8_WASM_STACKS_H_