Commit 97ed8b27 authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

[regexp] Guarantee an allocated regexp stack

The regexp stack is used during execution of jitted regexp matcher
code.  Previously, the stack was initially not present / nullptr, and
we had to explicitly check for this condition and bail out in builtin
code.

This CL changes behavior to guarantee a present stack by adding a
statically-allocated area that is used whenever no
dynamically-allocated stack exists.

Change-Id: I52934425ae72cf0e5d13fab2b9d63d37ca76fcf3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1852126
Auto-Submit: Jakob Gruber <jgruber@chromium.org>
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64326}
parent 20447f16
...@@ -398,8 +398,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal( ...@@ -398,8 +398,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
ExternalConstant(ExternalReference::isolate_address(isolate())); ExternalConstant(ExternalReference::isolate_address(isolate()));
TNode<ExternalReference> regexp_stack_memory_top_address = ExternalConstant( TNode<ExternalReference> regexp_stack_memory_top_address = ExternalConstant(
ExternalReference::address_of_regexp_stack_memory_top_address(isolate())); ExternalReference::address_of_regexp_stack_memory_top_address(isolate()));
TNode<ExternalReference> regexp_stack_memory_size_address = ExternalConstant(
ExternalReference::address_of_regexp_stack_memory_size(isolate()));
TNode<ExternalReference> static_offsets_vector_address = ExternalConstant( TNode<ExternalReference> static_offsets_vector_address = ExternalConstant(
ExternalReference::address_of_static_offsets_vector(isolate())); ExternalReference::address_of_static_offsets_vector(isolate()));
...@@ -518,21 +516,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal( ...@@ -518,21 +516,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
GotoIf(TaggedIsSmi(var_code.value()), &runtime); GotoIf(TaggedIsSmi(var_code.value()), &runtime);
TNode<Code> code = CAST(var_code.value()); TNode<Code> code = CAST(var_code.value());
// Ensure that a RegExp stack is allocated when using compiled Irregexp.
// TODO(jgruber): Guarantee an allocated stack and remove this check.
{
Label next(this);
GotoIfNot(TaggedIsSmi(var_bytecode.value()), &next);
CSA_ASSERT(this, SmiEqual(CAST(var_bytecode.value()),
SmiConstant(JSRegExp::kUninitializedValue)));
TNode<IntPtrT> stack_size = UncheckedCast<IntPtrT>(
Load(MachineType::IntPtr(), regexp_stack_memory_size_address));
Branch(IntPtrEqual(stack_size, IntPtrZero()), &runtime, &next);
BIND(&next);
}
Label if_success(this), if_exception(this, Label::kDeferred); Label if_success(this), if_exception(this, Label::kDeferred);
{ {
IncrementCounter(isolate()->counters()->regexp_entry_native(), 1); IncrementCounter(isolate()->counters()->regexp_entry_native(), 1);
......
...@@ -504,16 +504,6 @@ ExternalReference ExternalReference::address_of_regexp_stack_limit_address( ...@@ -504,16 +504,6 @@ ExternalReference ExternalReference::address_of_regexp_stack_limit_address(
return ExternalReference(isolate->regexp_stack()->limit_address_address()); return ExternalReference(isolate->regexp_stack()->limit_address_address());
} }
ExternalReference ExternalReference::address_of_regexp_stack_memory_address(
Isolate* isolate) {
return ExternalReference(isolate->regexp_stack()->memory_address_address());
}
ExternalReference ExternalReference::address_of_regexp_stack_memory_size(
Isolate* isolate) {
return ExternalReference(isolate->regexp_stack()->memory_size_address());
}
ExternalReference ExternalReference::address_of_regexp_stack_memory_top_address( ExternalReference ExternalReference::address_of_regexp_stack_memory_top_address(
Isolate* isolate) { Isolate* isolate) {
return ExternalReference( return ExternalReference(
......
...@@ -74,9 +74,6 @@ class StatsCounter; ...@@ -74,9 +74,6 @@ class StatsCounter;
V(stack_is_iterable_address, "IsolateData::stack_is_iterable_address") \ V(stack_is_iterable_address, "IsolateData::stack_is_iterable_address") \
V(address_of_regexp_stack_limit_address, \ V(address_of_regexp_stack_limit_address, \
"RegExpStack::limit_address_address()") \ "RegExpStack::limit_address_address()") \
V(address_of_regexp_stack_memory_address, \
"RegExpStack::memory_address_address()") \
V(address_of_regexp_stack_memory_size, "RegExpStack::memory_size_address()") \
V(address_of_regexp_stack_memory_top_address, \ V(address_of_regexp_stack_memory_top_address, \
"RegExpStack::memory_top_address_address()") \ "RegExpStack::memory_top_address_address()") \
V(address_of_static_offsets_vector, "OffsetsVector::static_offsets_vector") \ V(address_of_static_offsets_vector, "OffsetsVector::static_offsets_vector") \
......
...@@ -22,7 +22,7 @@ RegExpStackScope::~RegExpStackScope() { ...@@ -22,7 +22,7 @@ RegExpStackScope::~RegExpStackScope() {
regexp_stack_->Reset(); regexp_stack_->Reset();
} }
RegExpStack::RegExpStack() : isolate_(nullptr) {} RegExpStack::RegExpStack() : thread_local_(this), isolate_(nullptr) {}
RegExpStack::~RegExpStack() { RegExpStack::~RegExpStack() {
thread_local_.Free(); thread_local_.Free();
...@@ -30,9 +30,17 @@ RegExpStack::~RegExpStack() { ...@@ -30,9 +30,17 @@ RegExpStack::~RegExpStack() {
char* RegExpStack::ArchiveStack(char* to) { char* RegExpStack::ArchiveStack(char* to) {
if (!thread_local_.owns_memory_) {
// Force dynamic stacks prior to archiving. Any growth will do. A dynamic
// stack is needed because stack archival & restoration rely on `memory_`
// pointing at a fixed-location backing store, whereas the static stack is
// tied to a RegExpStack instance.
EnsureCapacity(thread_local_.memory_size_ + 1);
}
size_t size = sizeof(thread_local_); size_t size = sizeof(thread_local_);
MemCopy(reinterpret_cast<void*>(to), &thread_local_, size); MemCopy(reinterpret_cast<void*>(to), &thread_local_, size);
thread_local_ = ThreadLocal(); thread_local_ = ThreadLocal(this);
return to + size; return to + size;
} }
...@@ -45,37 +53,44 @@ char* RegExpStack::RestoreStack(char* from) { ...@@ -45,37 +53,44 @@ char* RegExpStack::RestoreStack(char* from) {
void RegExpStack::Reset() { void RegExpStack::Reset() {
if (thread_local_.memory_size_ > kMinimumStackSize) { STATIC_ASSERT(kMinimumDynamicStackSize > kStaticStackSize);
if (thread_local_.memory_size_ > kMinimumDynamicStackSize) {
DCHECK(thread_local_.owns_memory_);
DeleteArray(thread_local_.memory_); DeleteArray(thread_local_.memory_);
thread_local_ = ThreadLocal(); thread_local_ = ThreadLocal(this);
} }
} }
void RegExpStack::ThreadLocal::Free() { void RegExpStack::ThreadLocal::Free() {
if (memory_size_ > 0) { if (owns_memory_) DeleteArray(memory_);
DeleteArray(memory_);
Clear(); // This stack may not be used after being freed. Just reset to invalid values
} // to ensure we don't accidentally use old memory areas.
memory_ = nullptr;
memory_top_ = nullptr;
memory_size_ = 0;
limit_ = kMemoryTop;
} }
Address RegExpStack::EnsureCapacity(size_t size) { Address RegExpStack::EnsureCapacity(size_t size) {
if (size > kMaximumStackSize) return kNullAddress; if (size > kMaximumStackSize) return kNullAddress;
if (size < kMinimumStackSize) size = kMinimumStackSize; if (size < kMinimumDynamicStackSize) size = kMinimumDynamicStackSize;
if (thread_local_.memory_size_ < size) { if (thread_local_.memory_size_ < size) {
byte* new_memory = NewArray<byte>(size); byte* new_memory = NewArray<byte>(size);
if (thread_local_.memory_size_ > 0) { if (thread_local_.memory_size_ > 0) {
// Copy original memory into top of new memory. // Copy original memory into top of new memory.
MemCopy(new_memory + size - thread_local_.memory_size_, MemCopy(new_memory + size - thread_local_.memory_size_,
thread_local_.memory_, thread_local_.memory_size_); thread_local_.memory_, thread_local_.memory_size_);
DeleteArray(thread_local_.memory_); if (thread_local_.owns_memory_) DeleteArray(thread_local_.memory_);
} }
thread_local_.memory_ = new_memory; thread_local_.memory_ = new_memory;
thread_local_.memory_top_ = new_memory + size; thread_local_.memory_top_ = new_memory + size;
thread_local_.memory_size_ = size; thread_local_.memory_size_ = size;
thread_local_.limit_ = reinterpret_cast<Address>(new_memory) + thread_local_.limit_ = reinterpret_cast<Address>(new_memory) +
kStackLimitSlack * kSystemPointerSize; kStackLimitSlack * kSystemPointerSize;
thread_local_.owns_memory_ = true;
} }
return reinterpret_cast<Address>(thread_local_.memory_top_); return reinterpret_cast<Address>(thread_local_.memory_top_);
} }
......
...@@ -41,7 +41,7 @@ class RegExpStack { ...@@ -41,7 +41,7 @@ class RegExpStack {
// Number of allocated locations on the stack below the limit. // Number of allocated locations on the stack below the limit.
// No sequence of pushes must be longer that this without doing a stack-limit // No sequence of pushes must be longer that this without doing a stack-limit
// check. // check.
static const int kStackLimitSlack = 32; static constexpr int kStackLimitSlack = 32;
// Gives the top of the memory used as stack. // Gives the top of the memory used as stack.
Address stack_base() { Address stack_base() {
...@@ -66,7 +66,7 @@ class RegExpStack { ...@@ -66,7 +66,7 @@ class RegExpStack {
Address EnsureCapacity(size_t size); Address EnsureCapacity(size_t size);
// Thread local archiving. // Thread local archiving.
static int ArchiveSpacePerThread() { static constexpr int ArchiveSpacePerThread() {
return static_cast<int>(sizeof(ThreadLocal)); return static_cast<int>(sizeof(ThreadLocal));
} }
char* ArchiveStack(char* to); char* ArchiveStack(char* to);
...@@ -80,41 +80,47 @@ class RegExpStack { ...@@ -80,41 +80,47 @@ class RegExpStack {
RegExpStack(); RegExpStack();
~RegExpStack(); ~RegExpStack();
// Artificial limit used when no memory has been allocated. // Artificial limit used when the thread-local state has been destroyed.
static const Address kMemoryTop = static const Address kMemoryTop =
static_cast<Address>(static_cast<uintptr_t>(-1)); static_cast<Address>(static_cast<uintptr_t>(-1));
// Minimal size of allocated stack area. // Minimal size of dynamically-allocated stack area.
static const size_t kMinimumStackSize = 1 * KB; static constexpr size_t kMinimumDynamicStackSize = 1 * KB;
// In addition to dynamically-allocated, variable-sized stacks, we also have
// a statically allocated and sized area that is used whenever no dynamic
// stack is allocated. This guarantees that a stack is always available and
// we can skip availability-checks later on.
// It's double the slack size to ensure that we have a bit of breathing room
// before NativeRegExpMacroAssembler::GrowStack must be called.
static constexpr size_t kStaticStackSize =
2 * kStackLimitSlack * kSystemPointerSize;
byte static_stack_[kStaticStackSize] = {0};
STATIC_ASSERT(kStaticStackSize <= kMaximumStackSize);
// Structure holding the allocated memory, size and limit. // Structure holding the allocated memory, size and limit.
struct ThreadLocal { struct ThreadLocal {
ThreadLocal() { Clear(); } explicit ThreadLocal(RegExpStack* regexp_stack) {
memory_ = regexp_stack->static_stack_;
memory_top_ = regexp_stack->static_stack_ + kStaticStackSize;
memory_size_ = kStaticStackSize;
limit_ = reinterpret_cast<Address>(regexp_stack->static_stack_) +
kStackLimitSlack * kSystemPointerSize;
owns_memory_ = false;
}
// If memory_size_ > 0 then memory_ and memory_top_ must be non-nullptr // If memory_size_ > 0 then memory_ and memory_top_ must be non-nullptr
// and memory_top_ = memory_ + memory_size_ // and memory_top_ = memory_ + memory_size_
byte* memory_; byte* memory_;
byte* memory_top_; byte* memory_top_;
size_t memory_size_; size_t memory_size_;
Address limit_; Address limit_;
void Clear() { bool owns_memory_; // Whether memory_ is owned and must be freed.
memory_ = nullptr;
memory_top_ = nullptr;
memory_size_ = 0;
limit_ = kMemoryTop;
}
void Free(); void Free();
}; };
// Address of allocated memory.
Address memory_address_address() {
return reinterpret_cast<Address>(&thread_local_.memory_);
}
// Address of size of allocated memory.
Address memory_size_address() {
return reinterpret_cast<Address>(&thread_local_.memory_size_);
}
// Address of top of memory used as stack. // Address of top of memory used as stack.
Address memory_top_address_address() { Address memory_top_address_address() {
return reinterpret_cast<Address>(&thread_local_.memory_top_); return reinterpret_cast<Address>(&thread_local_.memory_top_);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment