runtime/thread.h (316 lines of code) (raw):
/* Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) */
#pragma once
#include "frame.h"
#include "globals.h"
#include "handles-decl.h"
#include "objects.h"
#include "os.h"
#include "symbols.h"
#include "vector.h"
namespace py {
class Frame;
class FrameVisitor;
class HandleScope;
class PointerVisitor;
class Runtime;
class Handles {
public:
Handles() = default;
Object* head() const { return head_; }
Object* push(Object* new_head) {
Object* old_head = head_;
head_ = new_head;
return old_head;
}
void pop(Object* new_head) { head_ = new_head; }
void visitPointers(PointerVisitor* visitor);
DISALLOW_COPY_AND_ASSIGN(Handles);
private:
Object* head_ = nullptr;
};
RawObject uninitializedInterpreterFunc(Thread*);
class Thread {
public:
static const int kDefaultStackSize = 1 * kMiB;
enum InterruptKind {
kSignal = 1 << 0,
kReinitInterpreter = 1 << 1,
kProfile = 1 << 2,
};
explicit Thread(Runtime* runtime, word size);
~Thread();
void begin();
static Thread* current();
static void setCurrentThread(Thread* thread);
void linkFrame(Frame* frame);
// Private method. Call pushCallFrame() or pushNativeFrame() isntead.
Frame* pushCallFrame(RawFunction function);
Frame* pushNativeFrame(word nargs);
Frame* pushGeneratorFrame(const GeneratorFrame& generator_frame);
Frame* popFrame();
Frame* popFrameToGeneratorFrame(const GeneratorFrame& generator_frame);
RawObject* stackPointer() { return stack_pointer_; }
void setStackPointer(RawObject* stack_pointer) {
stack_pointer_ = stack_pointer;
}
// Begin of the value stack of the current frame.
RawObject* valueStackBase();
// Returns the number of items on the value stack of the current frame.
word valueStackSize();
// Pop n items off the stack.
void stackDrop(word count);
// Insert value at offset on the stack.
void stackInsertAt(word offset, RawObject value);
// Return the object at offset from the top of the value stack (e.g. peek(0)
// returns the top of the stack)
RawObject stackPeek(word offset);
// Pop the top value off the stack and return it.
RawObject stackPop();
// Push value on the stack.
void stackPush(RawObject value);
// Remove value at offset on the stack.
void stackRemoveAt(word offset);
// Set value at offset on the stack.
void stackSetAt(word offset, RawObject value);
// Set the top value of the stack.
void stackSetTop(RawObject value);
// Return the top value of the stack.
RawObject stackTop();
// Runs a code object on the current thread.
NODISCARD RawObject exec(const Code& code, const Module& module,
const Object& implicit_globals);
NODISCARD RawObject callFunctionWithImplicitGlobals(
const Function& function, const Object& implicit_globals);
Thread* next() { return next_; }
void setNext(Thread* next) { next_ = next; }
Thread* prev() { return prev_; }
void setPrev(Thread* prev) { prev_ = prev; }
Handles* handles() { return &handles_; }
Runtime* runtime() { return runtime_; }
Frame* currentFrame() { return current_frame_; }
RawObject heapFrameAtDepth(word depth);
using InterpreterFunc = RawObject (*)(Thread*);
InterpreterFunc interpreterFunc() { return interpreter_func_; }
void setInterpreterFunc(InterpreterFunc func) { interpreter_func_ = func; }
void* interpreterData() { return interpreter_data_; }
void setInterpreterData(void* data) { interpreter_data_ = data; }
void clearInterrupt(InterruptKind kind);
void interrupt(InterruptKind kind);
bool isMainThread();
void visitRoots(PointerVisitor* visitor);
void visitStackRoots(PointerVisitor* visitor);
// Calls out to the interpreter to lookup and call a method on the receiver
// with the given argument(s). Returns Error<NotFound> if the method can't be
// found, or the result of the call otheriwse (which may be Error<Exception>).
NODISCARD RawObject invokeMethod1(const Object& receiver, SymbolId selector);
NODISCARD RawObject invokeMethod2(const Object& receiver, SymbolId selector,
const Object& arg1);
NODISCARD RawObject invokeMethod3(const Object& receiver, SymbolId selector,
const Object& arg1, const Object& arg2);
// Looks up a method on a type and invokes it with the given receiver and
// argument(s). Returns Error<NotFound> if the method can't be found, or the
// result of the call otheriwse (which may be Error<Exception>).
// ex: str.foo(receiver, arg1, ...)
NODISCARD RawObject invokeMethodStatic1(LayoutId type, SymbolId method_name,
const Object& receiver);
NODISCARD RawObject invokeMethodStatic2(LayoutId type, SymbolId method_name,
const Object& receiver,
const Object& arg1);
NODISCARD RawObject invokeMethodStatic3(LayoutId type, SymbolId method_name,
const Object& receiver,
const Object& arg1,
const Object& arg2);
NODISCARD RawObject invokeMethodStatic4(LayoutId type, SymbolId method_name,
const Object& receiver,
const Object& arg1,
const Object& arg2,
const Object& arg3);
// Calls out to the interpreter to lookup and call a function with the given
// argument(s). Returns Error<NotFound> if the function can't be found, or the
// result of the call otherwise (which may be Error<Exception>).
NODISCARD RawObject invokeFunction0(SymbolId module, SymbolId name);
NODISCARD RawObject invokeFunction1(SymbolId module, SymbolId name,
const Object& arg1);
NODISCARD RawObject invokeFunction2(SymbolId module, SymbolId name,
const Object& arg1, const Object& arg2);
NODISCARD RawObject invokeFunction3(SymbolId module, SymbolId name,
const Object& arg1, const Object& arg2,
const Object& arg3);
NODISCARD RawObject invokeFunction4(SymbolId module, SymbolId name,
const Object& arg1, const Object& arg2,
const Object& arg3, const Object& arg4);
NODISCARD RawObject invokeFunction5(SymbolId module, SymbolId name,
const Object& arg1, const Object& arg2,
const Object& arg3, const Object& arg4,
const Object& arg5);
NODISCARD RawObject invokeFunction6(SymbolId module, SymbolId name,
const Object& arg1, const Object& arg2,
const Object& arg3, const Object& arg4,
const Object& arg5, const Object& arg6);
// Raises an exception with the given type and returns an Error that must be
// returned up the stack by the caller.
RawObject raise(LayoutId type, RawObject value);
RawObject raiseWithType(RawObject type, RawObject value);
RawObject raiseWithFmt(LayoutId type, const char* fmt, ...);
// Raises a new exception, attaching the pending exception (if any) to the
// new exception's __cause__ and __context__ attributes.
RawObject raiseWithFmtChainingPendingAsCause(LayoutId type, const char* fmt,
...);
// Raises a TypeError exception for PyErr_BadArgument.
void raiseBadArgument();
// Raises a SystemError exception for PyErr_BadInternalCall.
RawObject raiseBadInternalCall();
// Raises a MemoryError exception and returns an Error that must be returned
// up the stack by the caller.
RawObject raiseMemoryError();
// Raises an OSError exception generated from the value of errno.
RawObject raiseOSErrorFromErrno(int errno_value);
RawObject raiseFromErrnoWithFilenames(const Object& type, int errno_value,
const Object& filename0,
const Object& filename1);
// Raises a TypeError exception of the form '<method> requires a <type(obj)>
// object but received a <expected_type>' and returns an Error object that
// must be returned up the stack by the caller.
RawObject raiseRequiresType(const Object& obj, SymbolId expected_type);
// Raise a StopAsyncIteration exception, indicating an asynchronous generator
// has returned. Note, unlike generators and coroutines, asynchoronous
// generators cannot have non-None return values.
RawObject raiseStopAsyncIteration();
// Raise a StopIteration exception with no value attribute set. This can
// indicate a a return value from a generator with value None, but can also
// mean the generator did not run, e.g. it is exhausted.
RawObject raiseStopIteration();
// Raise a StopIteration exception with the value attribute set. This is
// typically used to transport a return value from a generator or coroutine.
RawObject raiseStopIterationWithValue(const Object& value);
// Raises a TypeError exception and returns an Error object that must be
// returned up the stack by the caller.
RawObject raiseUnsupportedBinaryOperation(const Object& left,
const Object& right,
SymbolId op_name);
// Exception support
//
// We track two sets of exception state, a "pending" exception and a "caught"
// exception. Each one has a type, value, and traceback.
//
// An exception is pending from the moment it is raised until it is caught by
// a handler. It transitions from pending to caught right before execution of
// the handler. If the handler re-raises, the exception transitions back to
// pending to resume unwinding; otherwise, the caught exception is cleared
// when the handler block is popped.
//
// The pending exception is stored directly in the Thread, since there is at
// most one active at any given time. The caught exception is kept in a stack
// of ExceptionState objects, and the Thread holds a pointer to the top of
// the stack. When the runtime enters a generator or coroutine, it pushes the
// ExceptionState owned by that object onto this stack, allowing that state
// to be preserved if we yield in an except block. When there is no generator
// or coroutine running, the default ExceptionState created with this Thread
// holds the caught exception.
// Returns true if there is a pending exception.
bool hasPendingException();
// Returns true if there is a StopIteration exception pending.
bool hasPendingStopIteration();
// If there is a StopIteration exception pending, clear it and return
// true. Otherwise, return false.
bool clearPendingStopIteration();
// Assuming there is a StopIteration pending, returns its value, accounting
// for various potential states of normalization.
RawObject pendingStopIterationValue();
// If there's a pending exception, clears it.
void clearPendingException();
// If there's a pending exception, prints it and ignores it.
void ignorePendingException();
// Gets the type, value, or traceback of the pending exception. No pending
// exception is indicated with a type of None.
RawObject pendingExceptionType() { return pending_exc_type_; }
RawObject pendingExceptionValue() { return pending_exc_value_; }
RawObject pendingExceptionTraceback() { return pending_exc_traceback_; }
// Returns whether or not the pending exception type (which must be set) is a
// subtype of the given type.
bool pendingExceptionMatches(LayoutId type);
// Sets the type, value, or traceback of the pending exception.
void setPendingExceptionType(RawObject type) { pending_exc_type_ = type; }
void setPendingExceptionValue(RawObject value) { pending_exc_value_ = value; }
void setPendingExceptionTraceback(RawObject traceback) {
pending_exc_traceback_ = traceback;
}
// Sets the type, value, or traceback of the caught exception.
void setCaughtExceptionType(RawObject type);
void setCaughtExceptionValue(RawObject value);
void setCaughtExceptionTraceback(RawObject traceback);
// Gets or sets the current caught ExceptionState. See also
// topmostCaughtExceptionState().
RawObject caughtExceptionState();
void setCaughtExceptionState(RawObject state);
// Searches up the stack for the nearest caught exception. Returns None if
// there are no caught exceptions being handled, or an ExceptionState.
RawObject topmostCaughtExceptionState();
// If there is a current caught exception, attach it to the given exception's
// __context__ attribute.
//
// Returns the updated value, which may be the result of createException(type,
// value) if value is not an instance of BaseException.
RawObject chainExceptionContext(const Type& type, const Object& value);
// Returns true if and only if obj is not an Error and there is no pending
// exception, or obj is an Error<Exception> and there is a pending exception.
// Mostly used in assertions around call boundaries.
bool isErrorValueOk(RawObject obj);
// Walk all the frames on the stack starting with the top-most frame
void visitFrames(FrameVisitor* visitor);
int recursionDepth() { return recursion_depth_; }
int recursionEnter() {
DCHECK(recursion_depth_ <= recursion_limit_,
"recursion depth can't be more than the recursion limit");
return recursion_depth_++;
}
void recursionLeave() {
DCHECK(recursion_depth_ > 0, "recursion depth can't be less than 0");
recursion_depth_--;
}
int recursionLimit() { return recursion_limit_; }
// TODO(T62600497): Enforce the recursion limit
void setRecursionLimit(int limit) { recursion_limit_ = limit; }
RawObject reprEnter(const Object& obj);
void reprLeave(const Object& obj);
// Get/set the thread-global callbacks for asynchronous generators.
RawObject asyncgenHooksFirstIter() { return asyncgen_hooks_first_iter_; }
RawObject asyncgenHooksFinalizer() { return asyncgen_hooks_finalizer_; }
void setAsyncgenHooksFirstIter(RawObject first_iter) {
asyncgen_hooks_first_iter_ = first_iter;
}
void setAsyncgenHooksFinalizer(RawObject finalizer) {
asyncgen_hooks_finalizer_ = finalizer;
}
// Get/set the current thread-global _contextvars.Context
RawObject contextvarsContext() { return contextvars_context_; }
void setContextvarsContext(RawObject context) {
contextvars_context_ = context;
}
void countOpcodes(word count) { opcode_count_ += count; }
// Returns number of opcodes executed in this thread. This is only guaranteed
// to be accurate when `Runtime::supportProfiling()` is enabled.
word opcodeCount() { return opcode_count_; }
bool profilingEnabled();
void enableProfiling();
void disableProfiling();
RawObject profilingData() { return profiling_data_; }
void setProfilingData(RawObject data) { profiling_data_ = data; }
word strOffset(const Str& str, word index);
bool wouldStackOverflow(word size);
bool handleInterrupt(word size);
static int currentFrameOffset() { return offsetof(Thread, current_frame_); }
static int interpreterDataOffset() {
return offsetof(Thread, interpreter_data_);
}
static int opcodeCountOffset() { return offsetof(Thread, opcode_count_); }
static int runtimeOffset() { return offsetof(Thread, runtime_); }
static int limitOffset() { return offsetof(Thread, limit_); }
static int stackPointerOffset() { return offsetof(Thread, stack_pointer_); }
private:
Frame* pushInitialFrame();
Frame* openAndLinkFrame(word size, word locals_offset);
void handleInterruptWithFrame();
Frame* handleInterruptPushCallFrame(RawFunction function, word max_stack_size,
word initial_stack_size,
word locals_offset);
Frame* handleInterruptPushGeneratorFrame(
const GeneratorFrame& generator_frame, word size);
Frame* handleInterruptPushNativeFrame(word locals_offset);
Frame* pushCallFrameImpl(RawFunction function, word stack_size,
word locals_offset);
Frame* pushGeneratorFrameImpl(const GeneratorFrame& generator_frame,
word size);
Frame* pushNativeFrameImpl(word locals_offset);
Handles handles_;
byte* start_; // base address of the stack
byte* end_; // exclusive limit of the stack
byte* limit_; // current limit of the stack
RawObject* stack_pointer_;
// Has the runtime requested a thread interruption? (e.g. signals, GC)
uint8_t interrupt_flags_ = 0;
// Number of opcodes executed in the thread while opcode counting was enabled.
word opcode_count_ = 0;
// current_frame_ always points to the top-most frame on the stack.
Frame* current_frame_;
Thread* next_ = nullptr;
Thread* prev_ = nullptr;
Runtime* runtime_ = nullptr;
InterpreterFunc interpreter_func_ = uninitializedInterpreterFunc;
void* interpreter_data_ = nullptr;
// State of the pending exception.
RawObject pending_exc_type_ = RawNoneType::object();
RawObject pending_exc_value_ = RawNoneType::object();
RawObject pending_exc_traceback_ = RawNoneType::object();
// Stack of ExceptionStates for the current caught exception. Generators push
// their private state onto this stack before resuming, and pop it after
// suspending.
RawObject caught_exc_stack_ = RawNoneType::object();
RawObject api_repr_list_ = RawNoneType::object();
RawObject asyncgen_hooks_first_iter_ = RawNoneType::object();
RawObject asyncgen_hooks_finalizer_ = RawNoneType::object();
RawObject contextvars_context_ = RawNoneType::object();
RawObject profiling_data_ = RawNoneType::object();
RawObject str_offset_str_ = RawNoneType::object();
word str_offset_index_;
word str_offset_offset_;
// C-API current recursion depth used via _PyThreadState_GetRecursionDepth
int recursion_depth_ = 0;
// C-API recursion limit as set via Py_SetRecursionLimit.
int recursion_limit_ = 1000; // CPython's default: Py_DEFAULT_RECURSION_LIMIT
static thread_local Thread* current_thread_;
DISALLOW_COPY_AND_ASSIGN(Thread);
};
inline RawObject* Thread::valueStackBase() {
return reinterpret_cast<RawObject*>(current_frame_);
}
inline word Thread::valueStackSize() {
return valueStackBase() - stack_pointer_;
}
inline Frame* Thread::popFrame() {
Frame* frame = current_frame_;
DCHECK(!frame->isSentinel(), "cannot pop initial frame");
stack_pointer_ = frame->frameEnd();
current_frame_ = frame->previousFrame();
return current_frame_;
}
inline void Thread::stackDrop(word count) {
DCHECK(stack_pointer_ + count <= valueStackBase(), "stack underflow");
stack_pointer_ += count;
}
inline void Thread::stackInsertAt(word offset, RawObject value) {
RawObject* sp = stack_pointer_ - 1;
DCHECK(sp + offset < valueStackBase(), "stack underflow");
for (word i = 0; i < offset; i++) {
sp[i] = sp[i + 1];
}
sp[offset] = value;
stack_pointer_ = sp;
}
inline RawObject Thread::stackPeek(word offset) {
DCHECK(stack_pointer_ + offset < valueStackBase(), "stack underflow");
return *(stack_pointer_ + offset);
}
inline RawObject Thread::stackPop() {
DCHECK(stack_pointer_ + 1 <= valueStackBase(), "stack underflow");
return *(stack_pointer_++);
}
inline void Thread::stackPush(RawObject value) { *(--stack_pointer_) = value; }
inline void Thread::stackRemoveAt(word offset) {
DCHECK(stack_pointer_ + offset < valueStackBase(), "stack underflow");
RawObject* sp = stack_pointer_;
for (word i = offset; i >= 1; i--) {
sp[i] = sp[i - 1];
}
stack_pointer_ = sp + 1;
}
inline void Thread::stackSetAt(word offset, RawObject value) {
DCHECK(stack_pointer_ + offset < valueStackBase(), "stack underflow");
*(stack_pointer_ + offset) = value;
}
inline void Thread::stackSetTop(RawObject value) {
DCHECK(stack_pointer_ < valueStackBase(), "stack underflow");
*stack_pointer_ = value;
}
inline RawObject Thread::stackTop() {
DCHECK(stack_pointer_ < valueStackBase(), "stack underflow");
return *stack_pointer_;
}
inline bool Thread::profilingEnabled() { return interrupt_flags_ & kProfile; }
inline bool Thread::wouldStackOverflow(word size) {
// Check that there is sufficient space on the stack
return reinterpret_cast<byte*>(stack_pointer_) - size < limit_;
}
} // namespace py