Commit f2de3fd6 authored by erik.corry@gmail.com's avatar erik.corry@gmail.com

Fix the stack limits setting API so it is usable.

Review URL: http://codereview.chromium.org/242074

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3005 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 48e6d470
// Copyright 2007-2008 the V8 project authors. All rights reserved.
// Copyright 2007-2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
......@@ -1981,8 +1981,13 @@ Handle<Boolean> V8EXPORT False();
/**
* A set of constraints that specifies the limits of the runtime's
* memory use.
* A set of constraints that specifies the limits of the runtime's memory use.
* You must set the heap size before initializing the VM - the size cannot be
* adjusted after the VM is initialized.
*
* If you are using threads then you should hold the V8::Locker lock while
* setting the stack limit and you must set a non-default stack limit separately
* for each thread.
*/
class V8EXPORT ResourceConstraints {
public:
......@@ -1992,6 +1997,7 @@ class V8EXPORT ResourceConstraints {
int max_old_space_size() const { return max_old_space_size_; }
void set_max_old_space_size(int value) { max_old_space_size_ = value; }
uint32_t* stack_limit() const { return stack_limit_; }
// Sets an address beyond which the VM's stack may not grow.
void set_stack_limit(uint32_t* value) { stack_limit_ = value; }
private:
int max_young_space_size_;
......@@ -2199,7 +2205,8 @@ class V8EXPORT V8 {
/**
* Initializes from snapshot if possible. Otherwise, attempts to
* initialize from scratch.
* initialize from scratch. This function is called implicitly if
* you use the API without calling it first.
*/
static bool Initialize();
......@@ -2749,15 +2756,15 @@ class Internals {
return ((reinterpret_cast<intptr_t>(value) & kHeapObjectTagMask) ==
kHeapObjectTag);
}
static inline bool HasSmiTag(internal::Object* value) {
return ((reinterpret_cast<intptr_t>(value) & kSmiTagMask) == kSmiTag);
}
static inline int SmiValue(internal::Object* value) {
return static_cast<int>(reinterpret_cast<intptr_t>(value)) >> kSmiTagSize;
}
static inline bool IsExternalTwoByteString(int instance_type) {
int representation = (instance_type & kFullStringRepresentationMask);
return representation == kExternalTwoByteRepresentationTag;
......
......@@ -342,9 +342,12 @@ ResourceConstraints::ResourceConstraints()
bool SetResourceConstraints(ResourceConstraints* constraints) {
bool result = i::Heap::ConfigureHeap(constraints->max_young_space_size(),
constraints->max_old_space_size());
if (!result) return false;
int semispace_size = constraints->max_young_space_size();
int old_gen_size = constraints->max_old_space_size();
if (semispace_size != 0 || old_gen_size != 0) {
bool result = i::Heap::ConfigureHeap(semispace_size, old_gen_size);
if (!result) return false;
}
if (constraints->stack_limit() != NULL) {
uintptr_t limit = reinterpret_cast<uintptr_t>(constraints->stack_limit());
i::StackGuard::SetStackLimit(limit);
......
......@@ -409,7 +409,7 @@ void Simulator::Initialize() {
Simulator::Simulator() {
ASSERT(initialized_);
Initialize();
// Setup simulator support first. Some of this information is needed to
// setup the architecture state.
size_t stack_size = 1 * 1024*1024; // allocate 1MB for stack
......@@ -501,6 +501,7 @@ void* Simulator::RedirectExternalReference(void* external_function,
// Get the active Simulator for the current thread.
Simulator* Simulator::current() {
Initialize();
Simulator* sim = reinterpret_cast<Simulator*>(
v8::internal::Thread::GetThreadLocal(simulator_key));
if (sim == NULL) {
......
......@@ -36,18 +36,23 @@
#ifndef V8_ARM_SIMULATOR_ARM_H_
#define V8_ARM_SIMULATOR_ARM_H_
#include "allocation.h"
#if defined(__arm__)
// When running without a simulator we call the entry directly.
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
(entry(p0, p1, p2, p3, p4))
// Calculated the stack limit beyond which we will throw stack overflow errors.
// This macro must be called from a C++ method. It relies on being able to take
// the address of "this" to get a value on the current execution stack and then
// calculates the stack limit based on that value.
#define GENERATED_CODE_STACK_LIMIT(limit) \
(reinterpret_cast<uintptr_t>(this) - limit)
// The stack limit beyond which we will throw stack overflow errors in
// generated code. Because generated code on arm uses the C stack, we
// just use the C stack limit.
class SimulatorStack : public v8::internal::AllStatic {
public:
static inline uintptr_t JsLimitFromCLimit(uintptr_t c_limit) {
return c_limit;
}
};
// Call the generated regexp code directly. The entry function pointer should
......@@ -64,12 +69,6 @@
assembler::arm::Simulator::current()->Call(FUNCTION_ADDR(entry), 5, \
p0, p1, p2, p3, p4))
// The simulator has its own stack. Thus it has a different stack limit from
// the C-based native code.
#define GENERATED_CODE_STACK_LIMIT(limit) \
(assembler::arm::Simulator::current()->StackLimit())
#define CALL_GENERATED_REGEXP_CODE(entry, p0, p1, p2, p3, p4, p5, p6) \
assembler::arm::Simulator::current()->Call( \
FUNCTION_ADDR(entry), 7, p0, p1, p2, p3, p4, p5, p6)
......@@ -219,6 +218,20 @@ class Simulator {
} } // namespace assembler::arm
// The simulator has its own stack. Thus it has a different stack limit from
// the C-based native code. Setting the c_limit to indicate a very small
// stack cause stack overflow errors, since the simulator ignores the input.
// This is unlikely to be an issue in practice, though it might cause testing
// trouble down the line.
class SimulatorStack : public v8::internal::AllStatic {
public:
static inline uintptr_t JsLimitFromCLimit(uintptr_t c_limit) {
return assembler::arm::Simulator::current()->StackLimit();
}
};
#endif // defined(__arm__)
#endif // V8_ARM_SIMULATOR_ARM_H_
......@@ -104,8 +104,6 @@ static Handle<JSFunction> MakeFunction(bool is_global,
ScriptDataImpl* pre_data) {
CompilationZoneScope zone_scope(DELETE_ON_EXIT);
// Make sure we have an initial stack limit.
StackGuard guard;
PostponeInterruptsScope postpone;
ASSERT(!i::Top::global_context().is_null());
......@@ -334,8 +332,6 @@ bool Compiler::CompileLazy(Handle<SharedFunctionInfo> shared,
// The VM is in the COMPILER state until exiting this function.
VMState state(COMPILER);
// Make sure we have an initial stack limit.
StackGuard guard;
PostponeInterruptsScope postpone;
// Compute name, source code and script data.
......
......@@ -61,9 +61,6 @@ static Handle<Object> Invoke(bool construct,
// Entering JavaScript.
VMState state(JS);
// Guard the stack against too much recursion.
StackGuard guard;
// Placeholder for return value.
Object* value = reinterpret_cast<Object*>(kZapValue);
......@@ -217,55 +214,6 @@ Handle<Object> Execution::GetConstructorDelegate(Handle<Object> object) {
StackGuard::ThreadLocal StackGuard::thread_local_;
StackGuard::StackGuard() {
// NOTE: Overall the StackGuard code assumes that the stack grows towards
// lower addresses.
ExecutionAccess access;
if (thread_local_.nesting_++ == 0) {
// Initial StackGuard is being set. We will set the stack limits based on
// the current stack pointer allowing the stack to grow kLimitSize from
// here.
// Ensure that either the stack limits are unset (kIllegalLimit) or that
// they indicate a pending interruption. The interrupt limit will be
// temporarily reset through the code below and reestablished if the
// interrupt flags indicate that an interrupt is pending.
ASSERT(thread_local_.jslimit_ == kIllegalLimit ||
(thread_local_.jslimit_ == kInterruptLimit &&
thread_local_.interrupt_flags_ != 0));
ASSERT(thread_local_.climit_ == kIllegalLimit ||
(thread_local_.climit_ == kInterruptLimit &&
thread_local_.interrupt_flags_ != 0));
uintptr_t limit = GENERATED_CODE_STACK_LIMIT(kLimitSize);
thread_local_.initial_jslimit_ = thread_local_.jslimit_ = limit;
Heap::SetStackLimit(limit);
// NOTE: The check for overflow is not safe as there is no guarantee that
// the running thread has its stack in all memory up to address 0x00000000.
thread_local_.initial_climit_ = thread_local_.climit_ =
reinterpret_cast<uintptr_t>(this) >= kLimitSize ?
reinterpret_cast<uintptr_t>(this) - kLimitSize : 0;
if (thread_local_.interrupt_flags_ != 0) {
set_limits(kInterruptLimit, access);
}
}
// Ensure that proper limits have been set.
ASSERT(thread_local_.jslimit_ != kIllegalLimit &&
thread_local_.climit_ != kIllegalLimit);
ASSERT(thread_local_.initial_jslimit_ != kIllegalLimit &&
thread_local_.initial_climit_ != kIllegalLimit);
}
StackGuard::~StackGuard() {
ExecutionAccess access;
if (--thread_local_.nesting_ == 0) {
set_limits(kIllegalLimit, access);
}
}
bool StackGuard::IsStackOverflow() {
ExecutionAccess access;
return (thread_local_.jslimit_ != kInterruptLimit &&
......@@ -285,15 +233,16 @@ void StackGuard::SetStackLimit(uintptr_t limit) {
ExecutionAccess access;
// If the current limits are special (eg due to a pending interrupt) then
// leave them alone.
uintptr_t jslimit = SimulatorStack::JsLimitFromCLimit(limit);
if (thread_local_.jslimit_ == thread_local_.initial_jslimit_) {
thread_local_.jslimit_ = limit;
Heap::SetStackLimit(limit);
thread_local_.jslimit_ = jslimit;
Heap::SetStackLimit(jslimit);
}
if (thread_local_.climit_ == thread_local_.initial_climit_) {
thread_local_.climit_ = limit;
}
thread_local_.initial_climit_ = limit;
thread_local_.initial_jslimit_ = limit;
thread_local_.initial_jslimit_ = jslimit;
}
......@@ -407,7 +356,58 @@ char* StackGuard::RestoreStackGuard(char* from) {
}
static internal::Thread::LocalStorageKey stack_limit_key =
internal::Thread::CreateThreadLocalKey();
void StackGuard::FreeThreadResources() {
Thread::SetThreadLocal(
stack_limit_key,
reinterpret_cast<void*>(thread_local_.initial_climit_));
}
void StackGuard::ThreadLocal::Clear() {
initial_jslimit_ = kIllegalLimit;
jslimit_ = kIllegalLimit;
initial_climit_ = kIllegalLimit;
climit_ = kIllegalLimit;
nesting_ = 0;
postpone_interrupts_nesting_ = 0;
interrupt_flags_ = 0;
Heap::SetStackLimit(kIllegalLimit);
}
void StackGuard::ThreadLocal::Initialize() {
if (initial_climit_ == kIllegalLimit) {
// Takes the address of the limit variable in order to find out where
// the top of stack is right now.
intptr_t limit = reinterpret_cast<intptr_t>(&limit) - kLimitSize;
initial_jslimit_ = SimulatorStack::JsLimitFromCLimit(limit);
jslimit_ = SimulatorStack::JsLimitFromCLimit(limit);
initial_climit_ = limit;
climit_ = limit;
Heap::SetStackLimit(SimulatorStack::JsLimitFromCLimit(limit));
}
nesting_ = 0;
postpone_interrupts_nesting_ = 0;
interrupt_flags_ = 0;
}
void StackGuard::ClearThread(const ExecutionAccess& lock) {
thread_local_.Clear();
}
void StackGuard::InitThread(const ExecutionAccess& lock) {
thread_local_.Initialize();
void* stored_limit = Thread::GetThreadLocal(stack_limit_key);
// You should hold the ExecutionAccess lock when you call this.
if (stored_limit != NULL) {
StackGuard::SetStackLimit(reinterpret_cast<intptr_t>(stored_limit));
}
}
......
......@@ -141,14 +141,13 @@ class Execution : public AllStatic {
class ExecutionAccess;
// Stack guards are used to limit the number of nested invocations of
// JavaScript and the stack size used in each invocation.
class StackGuard BASE_EMBEDDED {
// StackGuard contains the handling of the limits that are used to limit the
// number of nested invocations of JavaScript and the stack size used in each
// invocation.
class StackGuard : public AllStatic {
public:
StackGuard();
~StackGuard();
// Pass the address beyond which the stack should not grow. The stack
// is assumed to grow downwards.
static void SetStackLimit(uintptr_t limit);
static Address address_of_jslimit() {
......@@ -160,6 +159,12 @@ class StackGuard BASE_EMBEDDED {
static char* RestoreStackGuard(char* from);
static int ArchiveSpacePerThread();
static void FreeThreadResources();
// Sets up the default stack guard for this thread if it has not
// already been set up.
static void InitThread(const ExecutionAccess& lock);
// Clears the stack guard for this thread so it does not look as if
// it has been set up.
static void ClearThread(const ExecutionAccess& lock);
static bool IsStackOverflow();
static bool IsPreempted();
......@@ -176,6 +181,13 @@ class StackGuard BASE_EMBEDDED {
#endif
static void Continue(InterruptFlag after_what);
// This provides an asynchronous read of the stack limit for the current
// thread. There are no locks protecting this, but it is assumed that you
// have the global V8 lock if you are using multiple V8 threads.
static uintptr_t climit() {
return thread_local_.climit_;
}
static uintptr_t jslimit() {
return thread_local_.jslimit_;
}
......@@ -184,13 +196,6 @@ class StackGuard BASE_EMBEDDED {
// You should hold the ExecutionAccess lock when calling this method.
static bool IsSet(const ExecutionAccess& lock);
// This provides an asynchronous read of the stack limit for the current
// thread. There are no locks protecting this, but it is assumed that you
// have the global V8 lock if you are using multiple V8 threads.
static uintptr_t climit() {
return thread_local_.climit_;
}
// You should hold the ExecutionAccess lock when calling this method.
static void set_limits(uintptr_t value, const ExecutionAccess& lock) {
Heap::SetStackLimit(value);
......@@ -201,14 +206,9 @@ class StackGuard BASE_EMBEDDED {
// Reset limits to initial values. For example after handling interrupt.
// You should hold the ExecutionAccess lock when calling this method.
static void reset_limits(const ExecutionAccess& lock) {
if (thread_local_.nesting_ == 0) {
// No limits have been set yet.
set_limits(kIllegalLimit, lock);
} else {
thread_local_.jslimit_ = thread_local_.initial_jslimit_;
Heap::SetStackLimit(thread_local_.jslimit_);
thread_local_.climit_ = thread_local_.initial_climit_;
}
thread_local_.jslimit_ = thread_local_.initial_jslimit_;
Heap::SetStackLimit(thread_local_.jslimit_);
thread_local_.climit_ = thread_local_.initial_climit_;
}
// Enable or disable interrupts.
......@@ -218,24 +218,19 @@ class StackGuard BASE_EMBEDDED {
static const uintptr_t kLimitSize = kPointerSize * 128 * KB;
#ifdef V8_TARGET_ARCH_X64
static const uintptr_t kInterruptLimit = V8_UINT64_C(0xfffffffffffffffe);
static const uintptr_t kIllegalLimit = V8_UINT64_C(0xffffffffffffffff);
static const uintptr_t kIllegalLimit = V8_UINT64_C(0xfffffffffffffff8);
#else
static const uintptr_t kInterruptLimit = 0xfffffffe;
static const uintptr_t kIllegalLimit = 0xffffffff;
static const uintptr_t kIllegalLimit = 0xfffffff8;
#endif
class ThreadLocal {
public:
ThreadLocal()
: initial_jslimit_(kIllegalLimit),
jslimit_(kIllegalLimit),
initial_climit_(kIllegalLimit),
climit_(kIllegalLimit),
nesting_(0),
postpone_interrupts_nesting_(0),
interrupt_flags_(0) {
Heap::SetStackLimit(kIllegalLimit);
}
ThreadLocal() { Clear(); }
// You should hold the ExecutionAccess lock when you call Initialize or
// Clear.
void Initialize();
void Clear();
uintptr_t initial_jslimit_;
uintptr_t jslimit_;
uintptr_t initial_climit_;
......
......@@ -28,21 +28,22 @@
#ifndef V8_IA32_SIMULATOR_IA32_H_
#define V8_IA32_SIMULATOR_IA32_H_
#include "allocation.h"
// Since there is no simulator for the ia32 architecture the only thing we can
// do is to call the entry directly.
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
entry(p0, p1, p2, p3, p4);
// Calculated the stack limit beyond which we will throw stack overflow errors.
// This macro must be called from a C++ method. It relies on being able to take
// the address of "this" to get a value on the current execution stack and then
// calculates the stack limit based on that value.
// NOTE: The check for overflow is not safe as there is no guarantee that the
// running thread has its stack in all memory up to address 0x00000000.
#define GENERATED_CODE_STACK_LIMIT(limit) \
(reinterpret_cast<uintptr_t>(this) >= limit ? \
reinterpret_cast<uintptr_t>(this) - limit : 0)
// The stack limit beyond which we will throw stack overflow errors in
// generated code. Because generated code on ia32 uses the C stack, we
// just use the C stack limit.
class SimulatorStack : public v8::internal::AllStatic {
public:
static inline uintptr_t JsLimitFromCLimit(uintptr_t c_limit) {
return c_limit;
}
};
// Call the generated regexp code directly. The entry function pointer should
// expect seven int/pointer sized arguments and return an int.
......
......@@ -1184,7 +1184,6 @@ Parser::Parser(Handle<Script> script,
bool Parser::PreParseProgram(Handle<String> source,
unibrow::CharacterStream* stream) {
HistogramTimerScope timer(&Counters::pre_parse);
StackGuard guard;
AssertNoZoneAllocation assert_no_zone_allocation;
AssertNoAllocation assert_no_allocation;
NoHandleAllocation no_handle_allocation;
......@@ -4775,8 +4774,6 @@ bool ParseRegExp(FlatStringReader* input,
bool multiline,
RegExpCompileData* result) {
ASSERT(result != NULL);
// Make sure we have a stack guard.
StackGuard guard;
RegExpParser parser(input, &result->error, multiline);
RegExpTree* tree = parser.ParsePattern();
if (parser.failed()) {
......
......@@ -71,6 +71,14 @@ bool V8::Initialize(Deserializer *des) {
::assembler::arm::Simulator::Initialize();
#endif
{ // NOLINT
// Ensure that the thread has a valid stack guard. The v8::Locker object
// will ensure this too, but we don't have to use lockers if we are only
// using one thread.
ExecutionAccess lock;
StackGuard::InitThread(lock);
}
// Setup the object heap
ASSERT(!Heap::HasBeenSetup());
if (!Heap::Setup(create_heap_objects)) {
......
......@@ -60,6 +60,10 @@ Locker::Locker() : has_lock_(false), top_level_(true) {
// get the saved state for this thread and restore it.
if (internal::ThreadManager::RestoreThread()) {
top_level_ = false;
} else {
internal::ExecutionAccess access;
internal::StackGuard::ClearThread(access);
internal::StackGuard::InitThread(access);
}
}
ASSERT(internal::ThreadManager::IsLockedByCurrentThread());
......@@ -141,6 +145,8 @@ bool ThreadManager::RestoreThread() {
ThreadState* state =
reinterpret_cast<ThreadState*>(Thread::GetThreadLocal(thread_state_key));
if (state == NULL) {
// This is a new thread.
StackGuard::InitThread(access);
return false;
}
char* from = state->data();
......
......@@ -28,6 +28,7 @@
#ifndef V8_X64_SIMULATOR_X64_H_
#define V8_X64_SIMULATOR_X64_H_
#include "allocation.h"
// Since there is no simulator for the ia32 architecture the only thing we can
// do is to call the entry directly.
......@@ -35,15 +36,15 @@
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
entry(p0, p1, p2, p3, p4);
// Calculated the stack limit beyond which we will throw stack overflow errors.
// This macro must be called from a C++ method. It relies on being able to take
// the address of "this" to get a value on the current execution stack and then
// calculates the stack limit based on that value.
// NOTE: The check for overflow is not safe as there is no guarantee that the
// running thread has its stack in all memory up to address 0x00000000.
#define GENERATED_CODE_STACK_LIMIT(limit) \
(reinterpret_cast<uintptr_t>(this) >= limit ? \
reinterpret_cast<uintptr_t>(this) - limit : 0)
// The stack limit beyond which we will throw stack overflow errors in
// generated code. Because generated code on x64 uses the C stack, we
// just use the C stack limit.
class SimulatorStack : public v8::internal::AllStatic {
public:
static inline uintptr_t JsLimitFromCLimit(uintptr_t c_limit) {
return c_limit;
}
};
// Call the generated regexp code directly. The entry function pointer should
// expect seven int/pointer sized arguments and return an int.
......
// Copyright 2007-2008 the V8 project authors. All rights reserved.
// Copyright 2007-2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
......@@ -31,6 +31,7 @@
#include "api.h"
#include "compilation-cache.h"
#include "execution.h"
#include "snapshot.h"
#include "platform.h"
#include "top.h"
......@@ -7918,3 +7919,77 @@ THREADED_TEST(IdleNotification) {
for (int i = 0; i < 100; i++) v8::V8::IdleNotification(true);
for (int i = 0; i < 100; i++) v8::V8::IdleNotification(false);
}
static uint32_t* stack_limit;
static v8::Handle<Value> GetStackLimitCallback(const v8::Arguments& args) {
stack_limit = reinterpret_cast<uint32_t*>(i::StackGuard::climit());
return v8::Undefined();
}
// Uses the address of a local variable to determine the stack top now.
// Given a size, returns an address that is that far from the current
// top of stack.
static uint32_t* ComputeStackLimit(uint32_t size) {
uint32_t* answer = &size - (size / sizeof(size));
// If the size is very large and the stack is very near the bottom of
// memory then the calculation above may wrap around and give an address
// that is above the (downwards-growing) stack. In that case we return
// a very low address.
if (answer > &size) return reinterpret_cast<uint32_t*>(sizeof(size));
return answer;
}
TEST(SetResourceConstraints) {
static const int K = 1024;
uint32_t* set_limit = ComputeStackLimit(128 * K);
// Set stack limit.
v8::ResourceConstraints constraints;
constraints.set_stack_limit(set_limit);
CHECK(v8::SetResourceConstraints(&constraints));
// Execute a script.
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(GetStackLimitCallback);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("get_stack_limit"), fun);
CompileRun("get_stack_limit();");
CHECK(stack_limit == set_limit);
}
TEST(SetResourceConstraintsInThread) {
uint32_t* set_limit;
{
v8::Locker locker;
static const int K = 1024;
set_limit = ComputeStackLimit(128 * K);
// Set stack limit.
v8::ResourceConstraints constraints;
constraints.set_stack_limit(set_limit);
CHECK(v8::SetResourceConstraints(&constraints));
// Execute a script.
v8::HandleScope scope;
LocalContext env;
Local<v8::FunctionTemplate> fun_templ =
v8::FunctionTemplate::New(GetStackLimitCallback);
Local<Function> fun = fun_templ->GetFunction();
env->Global()->Set(v8_str("get_stack_limit"), fun);
CompileRun("get_stack_limit();");
CHECK(stack_limit == set_limit);
}
{
v8::Locker locker;
CHECK(stack_limit == set_limit);
}
}
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