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(
ExternalConstant(ExternalReference::isolate_address(isolate()));
TNode<ExternalReference> regexp_stack_memory_top_address = ExternalConstant(
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(
ExternalReference::address_of_static_offsets_vector(isolate()));
......@@ -518,21 +516,6 @@ TNode<HeapObject> RegExpBuiltinsAssembler::RegExpExecInternal(
GotoIf(TaggedIsSmi(var_code.value()), &runtime);
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);
{
IncrementCounter(isolate()->counters()->regexp_entry_native(), 1);
......
......@@ -504,16 +504,6 @@ ExternalReference ExternalReference::address_of_regexp_stack_limit_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(
Isolate* isolate) {
return ExternalReference(
......
......@@ -74,9 +74,6 @@ class StatsCounter;
V(stack_is_iterable_address, "IsolateData::stack_is_iterable_address") \
V(address_of_regexp_stack_limit_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, \
"RegExpStack::memory_top_address_address()") \
V(address_of_static_offsets_vector, "OffsetsVector::static_offsets_vector") \
......
......@@ -22,7 +22,7 @@ RegExpStackScope::~RegExpStackScope() {
regexp_stack_->Reset();
}
RegExpStack::RegExpStack() : isolate_(nullptr) {}
RegExpStack::RegExpStack() : thread_local_(this), isolate_(nullptr) {}
RegExpStack::~RegExpStack() {
thread_local_.Free();
......@@ -30,9 +30,17 @@ RegExpStack::~RegExpStack() {
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_);
MemCopy(reinterpret_cast<void*>(to), &thread_local_, size);
thread_local_ = ThreadLocal();
thread_local_ = ThreadLocal(this);
return to + size;
}
......@@ -45,37 +53,44 @@ char* RegExpStack::RestoreStack(char* from) {
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_);
thread_local_ = ThreadLocal();
thread_local_ = ThreadLocal(this);
}
}
void RegExpStack::ThreadLocal::Free() {
if (memory_size_ > 0) {
DeleteArray(memory_);
Clear();
}
if (owns_memory_) DeleteArray(memory_);
// 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) {
if (size > kMaximumStackSize) return kNullAddress;
if (size < kMinimumStackSize) size = kMinimumStackSize;
if (size < kMinimumDynamicStackSize) size = kMinimumDynamicStackSize;
if (thread_local_.memory_size_ < size) {
byte* new_memory = NewArray<byte>(size);
if (thread_local_.memory_size_ > 0) {
// Copy original memory into top of new memory.
MemCopy(new_memory + size - 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_top_ = new_memory + size;
thread_local_.memory_size_ = size;
thread_local_.limit_ = reinterpret_cast<Address>(new_memory) +
kStackLimitSlack * kSystemPointerSize;
thread_local_.owns_memory_ = true;
}
return reinterpret_cast<Address>(thread_local_.memory_top_);
}
......
......@@ -41,7 +41,7 @@ class RegExpStack {
// Number of allocated locations on the stack below the limit.
// No sequence of pushes must be longer that this without doing a stack-limit
// check.
static const int kStackLimitSlack = 32;
static constexpr int kStackLimitSlack = 32;
// Gives the top of the memory used as stack.
Address stack_base() {
......@@ -66,7 +66,7 @@ class RegExpStack {
Address EnsureCapacity(size_t size);
// Thread local archiving.
static int ArchiveSpacePerThread() {
static constexpr int ArchiveSpacePerThread() {
return static_cast<int>(sizeof(ThreadLocal));
}
char* ArchiveStack(char* to);
......@@ -80,41 +80,47 @@ class 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_cast<Address>(static_cast<uintptr_t>(-1));
// Minimal size of allocated stack area.
static const size_t kMinimumStackSize = 1 * KB;
// Minimal size of dynamically-allocated stack area.
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.
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
// and memory_top_ = memory_ + memory_size_
byte* memory_;
byte* memory_top_;
size_t memory_size_;
Address limit_;
void Clear() {
memory_ = nullptr;
memory_top_ = nullptr;
memory_size_ = 0;
limit_ = kMemoryTop;
}
bool owns_memory_; // Whether memory_ is owned and must be freed.
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 memory_top_address_address() {
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