Reland "[regexp] Guarantee an allocated regexp stack"

This is a reland of 97ed8b277b

Original change's description:
> [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: Peter Marshall <petermarshall@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#64326}

Change-Id: If345c09bdbfc8dc6b63f016c3f10ffda811bbb6d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1866771
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64401}
This commit is contained in:
Jakob Gruber 2019-10-21 08:29:52 +02:00 committed by Commit Bot
parent bc33cc6354
commit 8f58c84e45
5 changed files with 63 additions and 77 deletions

View File

@ -384,8 +384,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()));
@ -504,21 +502,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);

View File

@ -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(

View File

@ -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") \

View File

@ -22,17 +22,23 @@ RegExpStackScope::~RegExpStackScope() {
regexp_stack_->Reset();
}
RegExpStack::RegExpStack() : isolate_(nullptr) {}
RegExpStack::~RegExpStack() {
thread_local_.Free();
}
RegExpStack::RegExpStack() : thread_local_(this), isolate_(nullptr) {}
RegExpStack::~RegExpStack() { thread_local_.FreeAndInvalidate(); }
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);
DCHECK(thread_local_.owns_memory_);
}
size_t size = sizeof(thread_local_);
MemCopy(reinterpret_cast<void*>(to), &thread_local_, size);
thread_local_ = ThreadLocal();
thread_local_ = ThreadLocal(this);
return to + size;
}
@ -43,39 +49,47 @@ char* RegExpStack::RestoreStack(char* from) {
return from + size;
}
void RegExpStack::Reset() { thread_local_.ResetToStaticStack(this); }
void RegExpStack::Reset() {
if (thread_local_.memory_size_ > kMinimumStackSize) {
DeleteArray(thread_local_.memory_);
thread_local_ = ThreadLocal();
}
void RegExpStack::ThreadLocal::ResetToStaticStack(RegExpStack* regexp_stack) {
if (owns_memory_) DeleteArray(memory_);
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;
}
void RegExpStack::ThreadLocal::FreeAndInvalidate() {
if (owns_memory_) DeleteArray(memory_);
void RegExpStack::ThreadLocal::Free() {
if (memory_size_ > 0) {
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) {
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_);
}

View File

@ -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,12 +66,12 @@ 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);
char* RestoreStack(char* from);
void FreeThreadResources() { thread_local_.Free(); }
void FreeThreadResources() { thread_local_.ResetToStaticStack(this); }
// Maximal size of allocated stack area.
static constexpr size_t kMaximumStackSize = 64 * MB;
@ -80,41 +80,43 @@ 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) {
ResetToStaticStack(regexp_stack);
}
// 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;
}
void Free();
byte* memory_ = nullptr;
byte* memory_top_ = nullptr;
size_t memory_size_ = 0;
Address limit_ = kNullAddress;
bool owns_memory_ = false; // Whether memory_ is owned and must be freed.
void ResetToStaticStack(RegExpStack* regexp_stack);
void FreeAndInvalidate();
};
// 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_);