regexp-stack.h (7094B)
1 // Copyright 2009 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_REGEXP_REGEXP_STACK_H_ 6 #define V8_REGEXP_REGEXP_STACK_H_ 7 8 #include "irregexp/RegExpShim.h" 9 10 namespace v8 { 11 namespace internal { 12 13 class RegExpStack; 14 15 // Maintains a per-v8thread stack area that can be used by irregexp 16 // implementation for its backtracking stack. 17 class V8_NODISCARD RegExpStackScope final { 18 public: 19 // Create and delete an instance to control the life-time of a growing stack. 20 21 // Initializes the stack memory area if necessary. 22 explicit RegExpStackScope(Isolate* isolate); 23 ~RegExpStackScope(); // Releases the stack if it has grown. 24 RegExpStackScope(const RegExpStackScope&) = delete; 25 RegExpStackScope& operator=(const RegExpStackScope&) = delete; 26 27 RegExpStack* stack() const { return regexp_stack_; } 28 29 private: 30 RegExpStack* const regexp_stack_; 31 const ptrdiff_t old_sp_top_delta_; 32 }; 33 34 // TODO(426514762): Currently this entire object is sandbox-accessible as some 35 // fields of it are being written to. This is unsafe though and we'll need to 36 // fix this. See the addition TODOs related to https://crbug.com/426514762. 37 class RegExpStack final { 38 public: 39 RegExpStack(const RegExpStack&) = delete; 40 RegExpStack& operator=(const RegExpStack&) = delete; 41 42 static RegExpStack* New(); 43 static void Delete(RegExpStack* instance); 44 45 #if defined(V8_TARGET_ARCH_PPC64) || defined(V8_TARGET_ARCH_S390X) 46 static constexpr int kSlotSize = kSystemPointerSize; 47 #else 48 static constexpr int kSlotSize = kInt32Size; 49 #endif 50 // Number of allocated locations on the stack below the limit. No sequence of 51 // pushes must be longer than this without doing a stack-limit check. 52 static constexpr int kStackLimitSlackSlotCount = 32; 53 static constexpr int kStackLimitSlackSize = 54 kStackLimitSlackSlotCount * kSlotSize; 55 56 Address begin() const { 57 return reinterpret_cast<Address>(thread_local_.memory_); 58 } 59 Address end() const { 60 DCHECK_NE(0, thread_local_.memory_size_); 61 DCHECK_EQ(thread_local_.memory_top_, 62 thread_local_.memory_ + thread_local_.memory_size_); 63 return reinterpret_cast<Address>(thread_local_.memory_top_); 64 } 65 Address memory_top() const { return end(); } 66 67 Address stack_pointer() const { 68 return reinterpret_cast<Address>(thread_local_.stack_pointer_); 69 } 70 71 size_t memory_size() const { return thread_local_.memory_size_; } 72 73 // If the stack pointer gets below the limit, we should react and 74 // either grow the stack or report an out-of-stack exception. 75 // There is only a limited number of locations below the stack limit, 76 // so users of the stack should check the stack limit during any 77 // sequence of pushes longer that this. 78 Address* limit_address_address() { return &thread_local_.limit_; } 79 80 // Ensures that there is a memory area with at least the specified size. 81 // If passing zero, the default/minimum size buffer is allocated. 82 Address EnsureCapacity(size_t size); 83 84 // Thread local archiving. 85 static constexpr int ArchiveSpacePerThread() { 86 return static_cast<int>(kThreadLocalSize); 87 } 88 char* ArchiveStack(char* to); 89 char* RestoreStack(char* from); 90 void FreeThreadResources() { thread_local_.ResetToStaticStack(this); } 91 92 // Maximal size of allocated stack area. 93 static constexpr size_t kMaximumStackSize = 64 * MB; 94 95 RegExpStack(); 96 ~RegExpStack(); 97 98 private: 99 // Artificial limit used when the thread-local state has been destroyed. 100 static const Address kMemoryTop = 101 static_cast<Address>(static_cast<uintptr_t>(-1)); 102 103 // In addition to dynamically-allocated, variable-sized stacks, we also have 104 // a statically allocated and sized area that is used whenever no dynamic 105 // stack is allocated. This guarantees that a stack is always available and 106 // we can skip availability-checks later on. 107 static constexpr size_t kStaticStackSize = 1 * KB; 108 // It's at least double the slack size to ensure that we have a bit of 109 // breathing room before NativeRegExpMacroAssembler::GrowStack must be 110 // called. 111 static_assert(kStaticStackSize >= 2 * kStackLimitSlackSize); 112 static_assert(kStaticStackSize <= kMaximumStackSize); 113 // TODO(426514762): this buffer is being written to from generated code. 114 // We could probably just allocate dedicated OS pages for it like we do for 115 // dynamically-sized stack buffers though (see EnsureCapacity). 116 uint8_t static_stack_[kStaticStackSize] = {0}; 117 118 // Minimal size of dynamically-allocated stack area. 119 static constexpr size_t kMinimumDynamicStackSize = 2 * KB; 120 static_assert(kMinimumDynamicStackSize == 2 * kStaticStackSize); 121 122 // Structure holding the allocated memory, size and limit. Thread switching 123 // archives and restores this struct. 124 struct ThreadLocal { 125 explicit ThreadLocal(RegExpStack* regexp_stack) { 126 ResetToStaticStack(regexp_stack); 127 } 128 129 // If memory_size_ > 0 then 130 // - memory_, memory_top_, stack_pointer_ must be non-nullptr 131 // - memory_top_ = memory_ + memory_size_ 132 // - memory_ <= stack_pointer_ <= memory_top_ 133 uint8_t* memory_ = nullptr; 134 uint8_t* memory_top_ = nullptr; 135 size_t memory_size_ = 0; 136 // TODO(426514762): this field is currently written to from generated code. 137 // Either we find a way to avoid that, or we have to move this field to 138 // it's own sandbox-accessible memory page. 139 uint8_t* stack_pointer_ = nullptr; 140 Address limit_ = kNullAddress; 141 bool owns_memory_ = false; // Whether memory_ is owned and must be freed. 142 143 void ResetToStaticStack(RegExpStack* regexp_stack); 144 void ResetToStaticStackIfEmpty(RegExpStack* regexp_stack) { 145 if (stack_pointer_ == memory_top_) ResetToStaticStack(regexp_stack); 146 } 147 void FreeAndInvalidate(); 148 149 // Allocates and returns new memory for a dynamic stack. 150 static uint8_t* NewDynamicStack(size_t size); 151 // If a dynamic stack is used, delete its memory. 152 void DeleteDynamicStack(); 153 }; 154 static constexpr size_t kThreadLocalSize = sizeof(ThreadLocal); 155 156 Address memory_top_address_address() { 157 return reinterpret_cast<Address>(&thread_local_.memory_top_); 158 } 159 160 Address stack_pointer_address() { 161 return reinterpret_cast<Address>(&thread_local_.stack_pointer_); 162 } 163 164 // A position-independent representation of the stack pointer. 165 ptrdiff_t sp_top_delta() const { 166 ptrdiff_t result = 167 reinterpret_cast<intptr_t>(thread_local_.stack_pointer_) - 168 reinterpret_cast<intptr_t>(thread_local_.memory_top_); 169 DCHECK_LE(result, 0); 170 return result; 171 } 172 173 // Resets the buffer if it has grown beyond the default/minimum size and is 174 // empty. 175 void ResetIfEmpty() { thread_local_.ResetToStaticStackIfEmpty(this); } 176 177 // Whether the ThreadLocal storage has been invalidated. 178 bool IsValid() const { return thread_local_.memory_ != nullptr; } 179 180 ThreadLocal thread_local_; 181 182 friend class ExternalReference; 183 friend class RegExpStackScope; 184 }; 185 186 } // namespace internal 187 } // namespace v8 188 189 #endif // V8_REGEXP_REGEXP_STACK_H_