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 "src/base/logging.h"
9#include "src/base/macros.h"
10#include "src/common/globals.h"
11
12namespace v8 {
13namespace internal {
14
15class RegExpStack;
16
17// Maintains a per-v8thread stack area that can be used by irregexp
18// implementation for its backtracking stack.
19class V8_NODISCARD RegExpStackScope final {
20 public:
21  // Create and delete an instance to control the life-time of a growing stack.
22
23  // Initializes the stack memory area if necessary.
24  explicit RegExpStackScope(Isolate* isolate);
25  ~RegExpStackScope();  // Releases the stack if it has grown.
26  RegExpStackScope(const RegExpStackScope&) = delete;
27  RegExpStackScope& operator=(const RegExpStackScope&) = delete;
28
29  RegExpStack* stack() const { return regexp_stack_; }
30
31 private:
32  RegExpStack* const regexp_stack_;
33  const ptrdiff_t old_sp_top_delta_;
34};
35
36class RegExpStack final {
37 public:
38  RegExpStack();
39  ~RegExpStack();
40  RegExpStack(const RegExpStack&) = delete;
41  RegExpStack& operator=(const RegExpStack&) = delete;
42
43  // Number of allocated locations on the stack below the limit. No sequence of
44  // pushes must be longer than this without doing a stack-limit check.
45  static constexpr int kStackLimitSlack = 32;
46
47  Address memory_top() const {
48    DCHECK_NE(0, thread_local_.memory_size_);
49    DCHECK_EQ(thread_local_.memory_top_,
50              thread_local_.memory_ + thread_local_.memory_size_);
51    return reinterpret_cast<Address>(thread_local_.memory_top_);
52  }
53
54  Address stack_pointer() const {
55    return reinterpret_cast<Address>(thread_local_.stack_pointer_);
56  }
57
58  size_t memory_size() const { return thread_local_.memory_size_; }
59
60  // If the stack pointer gets below the limit, we should react and
61  // either grow the stack or report an out-of-stack exception.
62  // There is only a limited number of locations below the stack limit,
63  // so users of the stack should check the stack limit during any
64  // sequence of pushes longer that this.
65  Address* limit_address_address() { return &thread_local_.limit_; }
66
67  // Ensures that there is a memory area with at least the specified size.
68  // If passing zero, the default/minimum size buffer is allocated.
69  Address EnsureCapacity(size_t size);
70
71  // Thread local archiving.
72  static constexpr int ArchiveSpacePerThread() {
73    return static_cast<int>(kThreadLocalSize);
74  }
75  char* ArchiveStack(char* to);
76  char* RestoreStack(char* from);
77  void FreeThreadResources() { thread_local_.ResetToStaticStack(this); }
78
79  // Maximal size of allocated stack area.
80  static constexpr size_t kMaximumStackSize = 64 * MB;
81
82 private:
83  // Artificial limit used when the thread-local state has been destroyed.
84  static const Address kMemoryTop =
85      static_cast<Address>(static_cast<uintptr_t>(-1));
86
87  // Minimal size of dynamically-allocated stack area.
88  static constexpr size_t kMinimumDynamicStackSize = 1 * KB;
89
90  // In addition to dynamically-allocated, variable-sized stacks, we also have
91  // a statically allocated and sized area that is used whenever no dynamic
92  // stack is allocated. This guarantees that a stack is always available and
93  // we can skip availability-checks later on.
94  // It's double the slack size to ensure that we have a bit of breathing room
95  // before NativeRegExpMacroAssembler::GrowStack must be called.
96  static constexpr size_t kStaticStackSize =
97      2 * kStackLimitSlack * kSystemPointerSize;
98  byte static_stack_[kStaticStackSize] = {0};
99
100  STATIC_ASSERT(kStaticStackSize <= kMaximumStackSize);
101
102  // Structure holding the allocated memory, size and limit. Thread switching
103  // archives and restores this struct.
104  struct ThreadLocal {
105    explicit ThreadLocal(RegExpStack* regexp_stack) {
106      ResetToStaticStack(regexp_stack);
107    }
108
109    // If memory_size_ > 0 then
110    //  - memory_, memory_top_, stack_pointer_ must be non-nullptr
111    //  - memory_top_ = memory_ + memory_size_
112    //  - memory_ <= stack_pointer_ <= memory_top_
113    byte* memory_ = nullptr;
114    byte* memory_top_ = nullptr;
115    size_t memory_size_ = 0;
116    byte* stack_pointer_ = nullptr;
117    Address limit_ = kNullAddress;
118    bool owns_memory_ = false;  // Whether memory_ is owned and must be freed.
119
120    void ResetToStaticStack(RegExpStack* regexp_stack);
121    void ResetToStaticStackIfEmpty(RegExpStack* regexp_stack) {
122      if (stack_pointer_ == memory_top_) ResetToStaticStack(regexp_stack);
123    }
124    void FreeAndInvalidate();
125  };
126  static constexpr size_t kThreadLocalSize = sizeof(ThreadLocal);
127
128  Address memory_top_address_address() {
129    return reinterpret_cast<Address>(&thread_local_.memory_top_);
130  }
131
132  Address stack_pointer_address() {
133    return reinterpret_cast<Address>(&thread_local_.stack_pointer_);
134  }
135
136  // A position-independent representation of the stack pointer.
137  ptrdiff_t sp_top_delta() const {
138    ptrdiff_t result =
139        reinterpret_cast<intptr_t>(thread_local_.stack_pointer_) -
140        reinterpret_cast<intptr_t>(thread_local_.memory_top_);
141    DCHECK_LE(result, 0);
142    return result;
143  }
144
145  // Resets the buffer if it has grown beyond the default/minimum size and is
146  // empty.
147  void ResetIfEmpty() { thread_local_.ResetToStaticStackIfEmpty(this); }
148
149  // Whether the ThreadLocal storage has been invalidated.
150  bool IsValid() const { return thread_local_.memory_ != nullptr; }
151
152  ThreadLocal thread_local_;
153
154  friend class ExternalReference;
155  friend class RegExpStackScope;
156};
157
158}  // namespace internal
159}  // namespace v8
160
161#endif  // V8_REGEXP_REGEXP_STACK_H_
162